; Copyright (c) 1993, 1994 Richard Kelsey and Jonathan Rees.  See file COPYING.


; Front end for Scheme 48 compilation, optimization, or whatever.

; Entry point for debugging or casual use.

(define (expand-form form p)
  (let-fluid $assignments #f
    (lambda ()
      (expand-begin (scan-forms (list form) p #f)
		    (package->environment p)))))


; After processing a package body, we change the types of all
; unassigned top-level variables from (VARIABLE <type>) to <type>.

(define (expand-stuff stuff p)
  (let* ((table (make-table))
	 (env (package->environment p))
	 (new-stuff
	  (let-fluid $assignments (cons p table)
	    (lambda ()
	      (map (lambda (filename+nodes)
		     (let ((filename (car filename+nodes))
			   (nodes (cdr filename+nodes)))
		       (cons filename
				 (let ((env (bind-source-file-name filename env)))
				   (map (lambda (node)
					  (expand node env))
					nodes)))))
		   stuff)))))
    (for-each (lambda (filename+nodes)
		(for-each (lambda (node)
			    (if (define-node? node)
				(maybe-update-known-type node p table)))
			  (cdr filename+nodes)))
	      new-stuff)
    new-stuff))

(set-optimizer! 'expand expand-stuff)

(define (maybe-update-known-type node p table)
  (let ((lhs (cadr (node-form node))))
    (if (not (table-ref table lhs))
	(let ((new-type (reconstruct-type (caddr (node-form node))
					  (package->environment p))))
	  (if (subtype? new-type any-values-type)
	      (package-define! p lhs (if (subtype? new-type value-type)
					 new-type
					 value-type))
	      (warn "ill-typed right-hand side"
		    (schemify node)
		    (type->sexp new-type #t)))))))

(define lambda-node? (node-predicate 'lambda))


; --------------------
; Expand a single form.

(define (expand form env)
  (let ((node (classify form env)))
    (if (already-expanded? node)
	node
	((get-expander (node-operator-id node)) node env))))

(define expanders
  (make-operator-table (lambda (node env)
			 (let ((form (node-form node)))
			   (make-expanded node
					  (cons (car form)
						(map (lambda (arg-exp)
						       (expand arg-exp env))
						     (cdr form))))))))

(define (define-expander name type proc)
  (operator-define! expanders name type proc))

(define (get-expander id)
  (operator-table-ref expanders id))

(define-expander 'literal #f
  (lambda (node env)
    (set-expanded node)))

(define-expander 'name #f
  (lambda (node env)
    (note-reference! node)
    node))

(define-expander 'call #f
  (lambda (node env)
    (let ((exp (node-form node)))
      (let ((proc-node (expand (car exp) env)))
	(note-operator! proc-node)
	(make-expanded node
		       (cons proc-node
			     (map (lambda (arg-exp)
				    (expand arg-exp env))
				  (cdr exp))))))))


; Special operators

(define-expander 'quote syntax-type
  (lambda (node env)
    (set-expanded node)))

(define-expander 'lambda syntax-type
  (lambda (node env)
    (set-fluid! $inferior-lambdas? #t)
    (let-fluid $inferior-lambdas? #f
      (lambda ()
	(let* ((exp (node-form node))
	       (formals (cadr exp)))
	  (with-lexicals (normalize-formals formals) env
	    (lambda (env lexicals)
	      (let ((node (make-expanded node
					 (list (car exp)
					       formals
					       (expand-body (cddr exp) env)))))
		(if (not (fluid $inferior-lambdas?))
		    (node-set! node 'no-inferior-lambdas #t))
		node))))))))

(define with-lexicals
  (let ((operator/name (get-operator 'name)))
    (lambda (vars env proc)
      (let* ((lexicals (map make-lexical vars))
	     (var-nodes (map (lambda (formal lexical)
			       (let ((var-node
				      (make-node operator/name formal)))
				 (node-set! var-node 'lexical lexical)
				 var-node))
			     vars
			     lexicals))
	     (node (proc (bind vars var-nodes env) lexicals)))
	(node-set! node 'var-nodes var-nodes)
	node))))


(define-expander 'letrec syntax-type
  (lambda (node env)
    (set-fluid! $inferior-lambdas? #t)	;foo
    (let* ((exp (node-form node))
	   (specs (cadr exp))
	   (body (cddr exp)))
      (with-lexicals (map car specs) env
	(lambda (env lexicals)
	  (let* ((specs (map (lambda (spec)
			       (list (car spec)
				     (expand (cadr spec) env)))
			     specs))
		 (node (make-expanded node
				      (list (car exp)
					    specs
					    (expand-body body env)))))
	    (if (and (every (lambda (spec)
			      (lambda-node? (cadr spec)))
			    specs)
		     (every (lambda (lexical)
			      (and (= (lexical-assignment-count lexical) 0)
				   (= (lexical-reference-count lexical)
				      (lexical-operator-count lexical))))
			    lexicals))
		(node-set! node 'pure-letrec #t))
	    node))))))

(define $inferior-lambdas? (make-fluid #t))

(define expand-body
  (let ((operator/letrec (get-operator 'letrec syntax-type)))
    (lambda (body env)
      (scan-body body
		 env
		 (lambda (defs exps)	;defs is a list of define nodes
		   (if (null? defs)
		       (expand-begin exps env)
		       (expand (make-node
				   operator/letrec
				   `(letrec ,(map (lambda (def)
						    (cdr (node-form def)))
						  defs)
				      ,@exps))
			       env)))))))

(define expand-begin
  (let ((op (get-operator 'begin syntax-type)))
    (lambda (exp-list env)
      (let ((nodes (map (lambda (exp) (expand exp env))
			exp-list)))
	(if (null? (cdr nodes))
	    (car nodes)
	    (set-expanded (make-node op (cons 'begin nodes))))))))

(define-expander 'set! syntax-type
  (lambda (node env)
    (let ((exp (node-form node)))
      (let ((lhs (classify (cadr exp) env))
	    (rhs (expand (caddr exp) env)))
	(if (name-node? lhs)
	    (begin (if (node-ref lhs 'lexical)
		       (note-assignment! lhs)
		       (note-top-level-assignment! (node-form lhs)))
		   (make-expanded node (list (car exp) lhs rhs)))
	    (expand (syntax-error "invalid assignment" (node-form node))
		    env))))))

(define name-node? (node-predicate 'name 'leaf))

(define (name-node-binding node cenv)
  (or (node-ref node 'binding)
      (lookup cenv (node-form node))))

(define-expander 'define syntax-type
  (lambda (node env)
    (let ((form (node-form node)))
      (make-expanded node
		     (list (car form)
			   (cadr form)
			   (expand (caddr form) env))))))

(define-expander 'if syntax-type
  (lambda (node env)
    (let ((exp (node-form node)))
      (make-expanded node
		     (list (car exp)
			   (expand (cadr exp) env)
			   (expand (caddr exp) env)
			   (expand (cadddr exp) env))))))

(define-expander 'primitive-procedure syntax-type
  (lambda (node env)
    (set-expanded node)))


; --------------------
; Expanded nodes

(define (make-expanded node form)
  (set-expanded (make-similar-node node form)))

(define (set-expanded node)
  (node-set! node 'expanded #t)
  node)

(define (already-expanded? node)
  (node-ref node 'expanded))

; --------------------
; Keep track of which defined top-level variables are assigned

(define $assignments (make-fluid #f))

(define (note-top-level-assignment! name)
  (let ((package+table (fluid $assignments)))
    (if package+table
	(if (generated? name)
	    (if (eq? (generated-env name) (car package+table))
		(table-set! (cdr package+table) (generated-symbol name) #t))
	    (table-set! (cdr package+table) name #t)))))


; --------------------
; Lexical information structures record the number of times that a
; variable is used.

(define (make-lexical name)
  (vector 0 0 0))

(define (lexical-accessor j)
  (lambda (lex)
    (vector-ref lex j)))

(define (lexical-incrementator j)
  (lambda (node)
    (let ((v (node-ref node 'lexical)))
      (if v
	  (vector-set! v j (+ (vector-ref v j) 1))))))

(define lexical-reference-count  (lexical-accessor 0))
(define lexical-operator-count   (lexical-accessor 1))
(define lexical-assignment-count (lexical-accessor 2))

(define note-reference!  (lexical-incrementator 0))
(define note-operator!   (lexical-incrementator 1))
(define note-assignment! (lexical-incrementator 2))

