;; A-Normalizer
;; (c) 1996-7 Sebastian Good
;; (c) 1997-8 PLT, Rice University

; This file contains an "a-normalizer" for Zodiac abstract
;  syntax trees for Scheme.
; This linear time algorithm is adapted from "The Essence
;  of Compiling with Continuations"(Flanagan/Sabry/Duba/Felleisen)

; For unknown historical reasons, this phase is implemented as a
;  non-destructive procedure on ASTs.

; An expressions is given a name when
;  1) it is not already the RHS of a let-assignment
;  2) it is not a tail expression
;  3) the value is not known to be ignored
; There's also a special hack for the test part of an
;  `if' expression: it might be preserved as an
;  application inlined in the `if' form.

; After a-normalizations, all let expressions are "linearized": one
;  binding clause for each let-values expression. (Of course, the
;  single clause can bind multiple variables.) This linearization does
;  not apply to letrec expressions. Technically, it's the source of a bug
;  compared to bytecode MzScheme: when call/cc is used on the RHS
;  where an earlier LHS is mutable, the cell for the mutable LHS is
;  allocated too early.

;;; Annotatitons: ----------------------------------------------
;;    begin0 - lexical-binding for storing 0th expression result
;;    with-continuation-mark - lexical-binding for storing body
;;                             result
;;; ------------------------------------------------------------

(module anorm mzscheme
  (require mzlib/unit
	  mzlib/list
	  mzlib/etc)

  (require syntax/zodiac-sig)

  (require "sig.ss")
  (require "../sig.ss")

  (provide anorm@)
  (define-unit anorm@
      (import (prefix compiler:option: compiler:option^)
	      compiler:library^
	      compiler:cstructs^
	      (prefix zodiac: zodiac^)
	      compiler:zlayer^
	      compiler:driver^)
      (export compiler:anorm^)

      (define compiler:a-value?
	(one-of zodiac:quote-form? zodiac:varref? zodiac:quote-syntax-form?))

      (define a-normalize
	(letrec ([linearize-let-values
		  (lambda (ast)
		    (let ([vars (zodiac:let-values-form-vars ast)])
		      (cond
		       [(null? (cdr vars)) ast] ; to prevent N^2 behavior
		       [else
			(let linear ([vars vars]
				     [vals (zodiac:let-values-form-vals ast)])
			  (if (null? vars)
			      (zodiac:let-values-form-body ast)
			      (zodiac:make-let-values-form (zodiac:zodiac-stx ast)
							   (make-empty-box)
							   (list (car vars))
							   (list (car vals))
							   (linear (cdr vars) 
								   (cdr vals)))))])))]
		 [normalize-name
		  (lambda (ast k)
		    (normalize-name/special-a-values ast k (lambda (x) #f)))]
		 [normalize-name/special-a-values
		  ;; The magic goodie that names expressions.  If the expression
		  ;; handed in is not an immediate a-value, it is named and the
		  ;; computation continues; syntax correlation exists!
		  (lambda (ast k special-a-value?)
		    (a-normalize
		     ast
		     (lambda (exp)
		       (if (or (compiler:a-value? exp) (special-a-value? exp))
			   (k exp)
			   (let* ([tname (gensym)]
				  [tbound (zodiac:make-lexical-binding
					   (zodiac:zodiac-stx exp)
					   (make-empty-box)
					   tname
					   tname)]
				  [varref (zodiac:binding->lexical-varref tbound)])
			     ;; hack: #f annotation => not mutable, or anything else
			     ;; (The hack is resolved by the prephase:is-mutable?, etc.
			     ;; procedures.)
			     (set-annotation! tbound #f) 
			     (let ([body (k varref)])
			       (zodiac:make-let-values-form
				(zodiac:zodiac-stx exp)
				(make-empty-box)
				(list (list tbound))
				(list exp)
				body)))))))]
		 ;; This names a list of expressions (eg argument list)
		 [normalize-name*
		  (lambda (ast* k)
		    (if (null? ast*)
			(k null)
			(normalize-name
			 (car ast*)
			 (lambda (term)
			   (normalize-name* (cdr ast*)
					    (lambda (term*)
					      (k (cons term term*))))))))]


		 [a-normalize
		  (lambda (ast k)
		    (when (compiler:option:debug)
		      (zodiac:print-start! (debug:get-port) ast)
		      (newline (debug:get-port)))
		    (cond 
		     
		     ;;----------------------------------------------------------------
		     ;; LAMBDA EXPRESSIONS
		     ;;    We must make a recursive call to normalize the body.
		     ;;    Otherwise, we just pass them on.  Lambda must be queried
		     ;;    before a-value, since lambda might be an a-value
		     ;;
		     ;; (norm (lambda x M)) -> (lambda x (norm M))
		     ;;
		     [(zodiac:case-lambda-form? ast)
		      (k (zodiac:make-case-lambda-form
			  (zodiac:zodiac-stx ast)
			  (zodiac:parsed-back ast)
			  (zodiac:case-lambda-form-args ast)
			  (map (lambda (body)
				 (a-normalize body identity))
			       (zodiac:case-lambda-form-bodies ast))))]
		     
		     ;;--------------------------------------------------------------
		     ;; A-VALUES
		     ;;    a-values are passed along unharmed.  We have to handle
		     ;;    lambda separately above, but otherwise
		     ;;
		     ;; (norm a-value) -> a-value
		     ;;
		     [(compiler:a-value? ast) (k ast)]
		     
		     ;;--------------------------------------------------------------
		     ;; LET EXPRESSIONS
		     ;;    with let, we must normalize the bound expressions
		     ;;    as well as the body.  We only bind one variable per
		     ;;    let in Core Scheme, so we have to expand these out
		     ;;    Zodiac already tells us if something is unbound, so we
		     ;;    can linearize this let as we like.
		     ;;
		     ;;    we treat letrec separately to reduce the cost of 
		     ;;    optimization
		     ;;    later.  We don't have to look for special cases of set!
		     ;;    we do not guarantee a-values in the vals slot of the letrec
		     ;;    since we do each of those in its own context, otherwise we
		     ;;    can get bindings messed up.
		     ;;
		     ;; (norm (let x M B) k) -> 
		     ;;       (norm M (lambda V (let x V (norm B k))))
		     ;; (norm (letrec [x M] ... B)) -> 
		     ;;     (letrec [x (norm M)] ... (norm B))
		     ;;
		     [(zodiac:let-values-form? ast)
		      (if (null? (zodiac:let-values-form-vars ast))
			  (a-normalize (zodiac:let-values-form-body ast) k)
			  (let ([linear (linearize-let-values ast)])
			    (a-normalize
			     (car (zodiac:let-values-form-vals ast))
			     (lambda (V)
			       (zodiac:make-let-values-form 
				(zodiac:zodiac-stx linear)
				(zodiac:parsed-back linear)
				(zodiac:let-values-form-vars 
				 linear)
				(list V)
				(a-normalize 
				 (zodiac:let-values-form-body 
				  linear)
				 k))))))]
		     
		     [(zodiac:letrec-values-form? ast)
		      (let ([vals (map (lambda (val) (a-normalize val identity))
				       (zodiac:letrec-values-form-vals ast))])
			(zodiac:make-letrec-values-form
			 (zodiac:zodiac-stx ast)
			 (zodiac:parsed-back ast)
			 (zodiac:letrec-values-form-vars ast)
			 vals
			 (a-normalize (zodiac:letrec-values-form-body ast) k)))]
		     
		     ;;---------------------------------------------------------------
		     ;; IF EXPRESSIONS
		     ;;
		     ;; We do not make a recursive call for the test since it is in the
		     ;; current 'context'.  We want only a-values in the test slot,
		     ;; or an application of a primitive function to a-values. 
		     ;;
		     ;; We specially allow primitive applications
		     ;; of a-values so the optimizer can recognize tests that can be
		     ;; implemented primitively, e.g., (zero? x)
		     ;;
		     ;; (norm (if A B C) k) ->
		     ;;   (name A (lambda test (k (if test (norm B) (norm C)))))
		     ;;
		     [(zodiac:if-form? ast)
		      (normalize-name/special-a-values
		       (zodiac:if-form-test ast)
		       (lambda (test)
			 (k (zodiac:make-if-form (zodiac:zodiac-stx ast)
						 (zodiac:parsed-back ast)
						 test    
						 (a-normalize (zodiac:if-form-then ast)
							      identity)
						 (a-normalize (zodiac:if-form-else ast)
							      identity))))
		       (lambda (x)
			 (and (zodiac:app? x)
			      (let ([fun (zodiac:app-fun x)])
				(and (zodiac:top-level-varref? fun)
				     (varref:has-attribute? fun varref:primitive))))))]
		     
		     ;;----------------------------------------------------------------
		     ;; BEGIN EXPRESSIONS
		     ;;
		     ;;    Begins pass through as begins, but every body is 
		     ;;    a-normalized.
		     ;;    We are guaranteed no empty begins
		     ;;
		     ;; (norm (begin A B) k) ->
		     ;;    (norm A (lambda first (begin first (norm B k))))
		     ;;
		     [(zodiac:begin-form? ast)   
		      (k (zodiac:make-begin-form
			  (zodiac:zodiac-stx ast)
			  (zodiac:parsed-back ast)
			  (map (lambda (b) (a-normalize b identity)) 
			       (zodiac:begin-form-bodies ast))))]
		     
		     ;;----------------------------------------------------------------
		     ;; BEGIN0 EXPRESSIONS
		     ;;
		     ;;    The first is named in a special way, and the rest passes through
		     ;;
		     ;; (norm (begin0 A B) k) ->
		     ;;    (k (begin0 (norm A identity) (norm B identity)))
		     ;;
		     [(zodiac:begin0-form? ast)
		      (let* ([tname (gensym)]
			     [tbound (zodiac:make-lexical-binding
				      (zodiac:zodiac-stx ast)
				      (make-empty-box)
				      tname
				      tname)]
			     [begin0-exp
			      (zodiac:make-begin0-form
			       (zodiac:zodiac-stx ast)
			       (zodiac:parsed-back ast)
			       (list
				(a-normalize (zodiac:begin0-form-first ast) identity)
				(a-normalize (zodiac:begin0-form-rest ast) identity)))])
			(set-annotation! begin0-exp tbound)
			(k begin0-exp))]

		     ;;---------------------------------------------------------------
		     ;; SET! EXPRESSIONS / DEFINE EXPRESSIONS
		     ;;    
		     ;; (norm (set! x M)) -> (name M (lambda val (set! x M)))
		     ;; (norm (define x M))->(define x (norm M identity))
		     ;;
		     [(zodiac:set!-form? ast)
		      (normalize-name 
		       (zodiac:set!-form-val ast)
		       (lambda (norm-val)
			 (k (zodiac:make-set!-form
			     (zodiac:zodiac-stx ast)
			     (zodiac:parsed-back ast)
			     (zodiac:set!-form-var ast)
			     norm-val))))]
		     
		     [(zodiac:define-values-form? ast)
		      (k (zodiac:make-define-values-form
			  (zodiac:zodiac-stx ast)
			  (zodiac:parsed-back ast)
			  (zodiac:define-values-form-vars ast)
			  (a-normalize (zodiac:define-values-form-val ast) identity)))]
		     
		     ;;-----------------------------------------------------------
		     ;; GLOBALS
		     ;;
		     [(zodiac:global-prepare? ast)
		      (k ast)]
		     [(zodiac:global-lookup? ast)
		      (k ast)]
		     [(zodiac:global-assign? ast)
		      (normalize-name 
		       (zodiac:global-assign-expr ast)
		       (lambda (norm-expr)
			 (k (zodiac:make-global-assign
			     (zodiac:zodiac-stx ast)
			     (zodiac:parsed-back ast)
			     (zodiac:global-assign-vec ast)
			     (zodiac:global-assign-pos ast)
			     norm-expr))))]
		     [(zodiac:safe-vector-ref? ast)
		      (k ast)]

		     ;;----------------------------------------------------------
		     ;; DEFINE-SYNTAX
		     ;;
		     [(zodiac:define-syntaxes-form? ast)
		      (k (zodiac:make-define-syntaxes-form 
			  (zodiac:zodiac-stx ast)
			  (zodiac:parsed-back ast)
			  (zodiac:define-syntaxes-form-names ast)
			  (a-normalize (zodiac:define-syntaxes-form-expr ast) identity)))]
		     
		     ;;---------------------------------------------------------------
		     ;; APPLICATIONS
		     ;;    We will always apply the a-normalization to the function
		     ;;    position of arguments
		     ;;    first normalize the function, then the list of arguments
		     ;;
		     ;; (norm (M A ...) k) ->
		     ;;  (name M 
		     ;;       (lambda fun (name* A .. (lambda term .. (fun term ..)))))
		     [(zodiac:app? ast)
		      (normalize-name 
		       (zodiac:app-fun ast)
		       (lambda (norm-fun)
			 (normalize-name*
			  (zodiac:app-args ast)
			  (lambda (norm-terms)
			    (k (zodiac:make-app (zodiac:zodiac-stx ast)
						(zodiac:parsed-back ast)
						norm-fun
						norm-terms))))))]
		     
		     ;;-----------------------------------------------------------
		     ;; WITH-CONTINUATION-MARK
		     ;;
		     [(zodiac:with-continuation-mark-form? ast)
		      (normalize-name
		       (zodiac:with-continuation-mark-form-key ast)
		       (lambda (key)
			 (normalize-name
			  (zodiac:with-continuation-mark-form-val ast)
			  (lambda (val)
			    (let* ([tname (gensym)]
				   [tbound (zodiac:make-lexical-binding
					    (zodiac:zodiac-stx ast)
					    (make-empty-box)
					    tname
					    tname)]
				   [wcm (zodiac:make-with-continuation-mark-form
					 (zodiac:zodiac-stx ast)
					 (zodiac:parsed-back ast)
					 key val
					 (a-normalize
					  (zodiac:with-continuation-mark-form-body ast)
					  identity))])
			      (set-annotation! wcm tbound)
			      (k wcm))))))]

		     [else (error 'a-normalize "unsupported ~a" ast)]))])
	  a-normalize))))
