;;; "synclo.scm" Syntactic Closures		-*-Scheme-*-
;;; Copyright (c) 1989-91 Massachusetts Institute of Technology
;;;
;;; This material was developed by the Scheme project at the
;;; Massachusetts Institute of Technology, Department of Electrical
;;; Engineering and Computer Science.  Permission to copy this
;;; software, to redistribute it, and to use it for any purpose is
;;; granted, subject to the following restrictions and understandings.
;;;
;;; 1. Any copy made of this software must include this copyright
;;; notice in full.
;;;
;;; 2. Users of this software agree to make their best efforts (a) to
;;; return to the MIT Scheme project any improvements or extensions
;;; that they make, so that these may be included in future releases;
;;; and (b) to inform MIT of noteworthy uses of this software.
;;;
;;; 3. All materials developed as a consequence of the use of this
;;; software shall duly acknowledge such use, in accordance with the
;;; usual standards of acknowledging credit in academic research.
;;;
;;; 4. MIT has made no warrantee or representation that the operation
;;; of this software will be error-free, and MIT is under no
;;; obligation to provide any services, by way of maintenance, update,
;;; or otherwise.
;;;
;;; 5. In conjunction with products arising from the use of this
;;; material, there shall be no use of the name of the Massachusetts
;;; Institute of Technology nor of any adaptation thereof in any
;;; advertising, promotional, or sales literature without prior
;;; written consent from MIT in each case.

;;;; Syntactic Closures
;;; written by Alan Bawden
;;; extensively modified by Chris Hanson

;;; See "Syntactic Closures", by Alan Bawden and Jonathan Rees, in
;;; Proceedings of the 1988 ACM Conference on Lisp and Functional
;;; Programming, page 86.

;;;; Classifier
;;;  The classifier maps forms into items.  In addition to locating
;;;  definitions so that they can be properly processed, it also
;;;  identifies keywords and variables, which allows a powerful form
;;;  of syntactic binding to be implemented.

(define (classify/form form environment definition-environment)
  (cond ((identifier? form)
	 (syntactic-environment/lookup environment form))
	((syntactic-closure? form)
	 (let ((form (syntactic-closure/form form))
	       (environment
		(filter-syntactic-environment
		 (syntactic-closure/free-names form)
		 environment
		 (syntactic-closure/environment form))))
	   (classify/form form
			  environment
			  definition-environment)))
	((pair? form)
	 (let ((item
		(classify/subexpression (car form) environment)))
	   (cond ((keyword-item? item)
		  ((keyword-item/classifier item) form
						  environment
						  definition-environment))
		 ((list? (cdr form))
		  (let ((items
			 (classify/subexpressions (cdr form)
						  environment)))
		    (make-expression-item
		     (lambda ()
		       (output/combination
			(compile-item/expression item)
			(map compile-item/expression items)))
		     form)))
		 (else
		  (syntax-error "combination must be a proper list"
				form)))))
	(else
	 (make-expression-item ;don't quote literals evaluating to themselves
	   (if (or (boolean? form) (char? form) (number? form) (string? form))
	       (lambda () (output/literal-unquoted form))
	       (lambda () (output/literal-quoted form))) form))))

(define (classify/subform form environment definition-environment)
  (classify/form form
		 environment
		 definition-environment))

(define (classify/subforms forms environment definition-environment)
  (map (lambda (form)
	 (classify/subform form environment definition-environment))
       forms))

(define (classify/subexpression expression environment)
  (classify/subform expression environment environment))

(define (classify/subexpressions expressions environment)
  (classify/subforms expressions environment environment))

;;;; Compiler
;;;  The compiler maps items into the output language.

(define (compile-item/expression item)
  (let ((illegal
	 (lambda (item name)
	   (let ((decompiled (decompile-item item))) (newline)
	   (slib:error (string-append name
					" may not be used as an expression")
			 decompiled)))))
    (cond ((variable-item? item)
	   (output/variable (variable-item/name item)))
	  ((expression-item? item)
	   ((expression-item/compiler item)))
	  ((body-item? item)
	   (let ((items (flatten-body-items (body-item/components item))))
	     (if (null? items)
		 (illegal item "empty sequence")
		 (output/sequence (map compile-item/expression items)))))
	  ((definition-item? item)
	   (let ((binding ;allows later scheme errors, but allows top-level
		  (bind-definition-item! ;(if (not (defined? x)) define it)
		   scheme-syntactic-environment item))) ;as in Init.scm
	     (output/top-level-definition
	      (car binding)
	      (compile-item/expression (cdr binding)))))
	  ((keyword-item? item)
	   (illegal item "keyword"))
	  (else
	   (impl-error "unknown item" item)))))

(define (compile/subexpression expression environment)
  (compile-item/expression
   (classify/subexpression expression environment)))

(define (compile/top-level forms environment)
  ;; Top-level syntactic definitions affect all forms that appear
  ;; after them.
  (output/top-level-sequence
   (let forms-loop ((forms forms))
     (if (null? forms)
	 '()
	 (let items-loop
	     ((items
	       (item->list
		(classify/subform (car forms)
				  environment
				  environment))))
	   (cond ((null? items)
		  (forms-loop (cdr forms)))
		 ((definition-item? (car items))
		  (let ((binding
			 (bind-definition-item! environment (car items))))
		    (if binding
			(cons (output/top-level-definition
			       (car binding)
			       (compile-item/expression (cdr binding)))
			      (items-loop (cdr items)))
			(items-loop (cdr items)))))
		 (else
		  (cons (compile-item/expression (car items))
			(items-loop (cdr items))))))))))

;;;; De-Compiler
;;;  The de-compiler maps partly-compiled things back to the input language,
;;;  as far as possible.  Used to display more meaningful macro error messages.

(define (decompile-item item)
    (display " ")
    (cond ((variable-item? item) (variable-item/name item))
	  ((expression-item? item)
	   (decompile-item (expression-item/annotation item)))
	  ((body-item? item)
	   (let ((items (flatten-body-items (body-item/components item))))
	     (display "sequence")
	     (if (null? items)
		 "empty sequence"
		 "non-empty sequence")))
	  ((definition-item? item) "definition")
	  ((keyword-item? item)
	   (decompile-item (keyword-item/name item)));in case expression
	  ((syntactic-closure? item); (display "syntactic-closure;")
	   (decompile-item (syntactic-closure/form item)))
	  ((list? item) (display "(")
		(map decompile-item item) (display ")") "see list above")
	  ((string? item) item);explicit name-string for keyword-item
	  ((symbol? item) (display item) item) ;symbol for syntactic-closures
	  ((boolean? item) (display item) item) ;symbol for syntactic-closures
	  (else (write item) (impl-error "unknown item" item))))

;;;; Syntactic Closures

(define syntactic-closure-type
  (make-record-type "syntactic-closure" '(ENVIRONMENT FREE-NAMES FORM)))

(define make-syntactic-closure
  (record-constructor syntactic-closure-type '(ENVIRONMENT FREE-NAMES FORM)))

(define syntactic-closure?
  (record-predicate syntactic-closure-type))

(define syntactic-closure/environment
  (record-accessor syntactic-closure-type 'ENVIRONMENT))

(define syntactic-closure/free-names
  (record-accessor syntactic-closure-type 'FREE-NAMES))

(define syntactic-closure/form
  (record-accessor syntactic-closure-type 'FORM))

(define (make-syntactic-closure-list environment free-names forms)
  (map (lambda (form) (make-syntactic-closure environment free-names form))
       forms))

(define (strip-syntactic-closures object)
  (cond ((syntactic-closure? object)
	 (strip-syntactic-closures (syntactic-closure/form object)))
	((pair? object)
	 (cons (strip-syntactic-closures (car object))
	       (strip-syntactic-closures (cdr object))))
	((vector? object)
	 (let ((length (vector-length object)))
	   (let ((result (make-vector length)))
	     (do ((i 0 (+ i 1)))
		 ((= i length))
	       (vector-set! result i
			    (strip-syntactic-closures (vector-ref object i))))
	     result)))
	(else
	 object)))

(define (identifier? object)
  (or (symbol? object)
      (synthetic-identifier? object)))

(define (synthetic-identifier? object)
  (and (syntactic-closure? object)
       (identifier? (syntactic-closure/form object))))

(define (identifier->symbol identifier)
  (cond ((symbol? identifier)
	 identifier)
	((synthetic-identifier? identifier)
	 (identifier->symbol (syntactic-closure/form identifier)))
	(else
	 (impl-error "not an identifier" identifier))))

(define (identifier=? environment-1 identifier-1 environment-2 identifier-2)
  (let ((item-1 (syntactic-environment/lookup environment-1 identifier-1))
	(item-2 (syntactic-environment/lookup environment-2 identifier-2)))
    (or (eq? item-1 item-2)
	;; This is necessary because an identifier that is not
	;; explicitly bound by an environment is mapped to a variable
	;; item, and the variable items are not cached.  Therefore
	;; two references to the same variable result in two
	;; different variable items.
	(and (variable-item? item-1)
	     (variable-item? item-2)
	     (eq? (variable-item/name item-1)
		  (variable-item/name item-2))))))

;;;; Syntactic Environments

(define syntactic-environment-type
  (make-record-type
   "syntactic-environment"
   '(PARENT
     LOOKUP-OPERATION
     RENAME-OPERATION
     DEFINE-OPERATION
     BINDINGS-OPERATION)))

(define make-syntactic-environment
  (record-constructor syntactic-environment-type
		      '(PARENT
			LOOKUP-OPERATION
			RENAME-OPERATION
			DEFINE-OPERATION
			BINDINGS-OPERATION)))

(define syntactic-environment?
  (record-predicate syntactic-environment-type))

(define syntactic-environment/parent
  (record-accessor syntactic-environment-type 'PARENT))

(define syntactic-environment/lookup-operation
  (record-accessor syntactic-environment-type 'LOOKUP-OPERATION))

(define (syntactic-environment/assign! environment name item)
  (let ((binding
	 ((syntactic-environment/lookup-operation environment) name)))
    (if binding
	(set-cdr! binding item)
	(impl-error "can't assign unbound identifier" name))))

(define syntactic-environment/rename-operation
  (record-accessor syntactic-environment-type 'RENAME-OPERATION))

(define (syntactic-environment/rename environment name)
  ((syntactic-environment/rename-operation environment) name))

(define syntactic-environment/define!
  (let ((accessor
	 (record-accessor syntactic-environment-type 'DEFINE-OPERATION)))
    (lambda (environment name item)
      ((accessor environment) name item))))

(define syntactic-environment/bindings
  (let ((accessor
	 (record-accessor syntactic-environment-type 'BINDINGS-OPERATION)))
    (lambda (environment)
      ((accessor environment)))))

(define (syntactic-environment/lookup environment name)
  (let ((binding
	 ((syntactic-environment/lookup-operation environment) name)))
    (cond (binding
	   (let ((item (cdr binding)))
	     (if (reserved-name-item? item)
		 (syntax-error "premature reference to reserved name"
			       name)
		 item)))
	  ((symbol? name)
	   (make-variable-item name))
	  ((synthetic-identifier? name)
	   (syntactic-environment/lookup (syntactic-closure/environment name)
					 (syntactic-closure/form name)))
	  (else
	   (impl-error "not an identifier" name)))))

(define root-syntactic-environment
  (make-syntactic-environment
   #f
   (lambda (name)
     name
     #f)
   (lambda (name)
     name)
   (lambda (name item)
     (impl-error "can't bind name in root syntactic environment" name item))
   (lambda ()
     '())))

(define null-syntactic-environment
  (make-syntactic-environment
   #f
   (lambda (name)
     (impl-error "can't lookup name in null syntactic environment" name))
   (lambda (name)
     (impl-error "can't rename name in null syntactic environment" name))
   (lambda (name item)
     (impl-error "can't bind name in null syntactic environment" name item))
   (lambda ()
     '())))

(define (top-level-syntactic-environment parent)
  (let ((bound '()))
    (make-syntactic-environment
     parent
     (let ((parent-lookup (syntactic-environment/lookup-operation parent)))
       (lambda (name)
	 (or (assq name bound)
	     (parent-lookup name))))
     (lambda (name)
       name)
     (lambda (name item)
       (let ((binding (assq name bound)))
	 (if binding
	     (set-cdr! binding item)
	     (set! bound (cons (cons name item) bound)))))
     (lambda ()
       (map (lambda (pair) (cons (car pair) (cdr pair))) bound)))))

(define (internal-syntactic-environment parent)
  (let ((bound '())
	(free '()))
    (make-syntactic-environment
     parent
     (let ((parent-lookup (syntactic-environment/lookup-operation parent)))
       (lambda (name)
	 (or (assq name bound)
	     (assq name free)
	     (let ((binding (parent-lookup name)))
	       (if binding (set! free (cons binding free)))
	       binding))))
     (make-name-generator)
     (lambda (name item)
       (cond ((assq name bound)
	      =>
	      (lambda (association)
		(if (and (reserved-name-item? (cdr association))
			 (not (reserved-name-item? item)))
		    (set-cdr! association item)
		    (impl-error "can't redefine name; already bound" name))))
	     ((assq name free)
	      (if (reserved-name-item? item)
		  (syntax-error "premature reference to reserved name"
				name)
		  (impl-error "can't define name; already free" name)))
	     (else
	      (set! bound (cons (cons name item) bound)))))
     (lambda ()
       (map (lambda (pair) (cons (car pair) (cdr pair))) bound)))))

(define (filter-syntactic-environment names names-env else-env)
  (if (or (null? names)
	  (eq? names-env else-env))
      else-env
      (let ((make-operation
	     (lambda (get-operation)
	       (let ((names-operation (get-operation names-env))
		     (else-operation (get-operation else-env)))
		 (lambda (name)
		   ((if (memq name names) names-operation else-operation)
		    name))))))
	(make-syntactic-environment
	 else-env
	 (make-operation syntactic-environment/lookup-operation)
	 (make-operation syntactic-environment/rename-operation)
	 (lambda (name item)
	   (impl-error "can't bind name in filtered syntactic environment"
		       name item))
	 (lambda ()
	   (map (lambda (name)
		  (cons name
			(syntactic-environment/lookup names-env name)))
		names))))))

;;;; Items

;;; Reserved name items do not represent any form, but instead are
;;; used to reserve a particular name in a syntactic environment.  If
;;; the classifier refers to a reserved name, a syntax error is
;;; signalled.  This is used in the implementation of LETREC-SYNTAX
;;; to signal a meaningful error when one of the <init>s refers to
;;; one of the names being bound.

(define reserved-name-item-type
  (make-record-type "reserved-name-item" '()))

(define make-reserved-name-item
  (record-constructor reserved-name-item-type))	; '()

(define reserved-name-item?
  (record-predicate reserved-name-item-type))

;;; Keyword items represent macro keywords.

(define keyword-item-type
  (make-record-type "keyword-item" '(CLASSIFIER NAME)))
;  (make-record-type "keyword-item" '(CLASSIFIER)))

(define make-keyword-item
;  (lambda (cl) (display "make-keyword-item:") (write cl) (newline)
;	((record-constructor keyword-item-type '(CLASSIFIER)) cl)))
  (record-constructor keyword-item-type '(CLASSIFIER NAME)))
;  (record-constructor keyword-item-type '(CLASSIFIER)))

(define keyword-item?
  (record-predicate keyword-item-type))

(define keyword-item/classifier
  (record-accessor keyword-item-type 'CLASSIFIER))

(define keyword-item/name
  (record-accessor keyword-item-type 'NAME))

;;; Variable items represent run-time variables.

(define variable-item-type
  (make-record-type "variable-item" '(NAME)))

(define make-variable-item
  (record-constructor variable-item-type '(NAME)))

(define variable-item?
  (record-predicate variable-item-type))

(define variable-item/name
  (record-accessor variable-item-type 'NAME))

;;; Expression items represent any kind of expression other than a
;;; run-time variable or a sequence.  The ANNOTATION field is used to
;;; make expression items that can appear in non-expression contexts
;;; (for example, this could be used in the implementation of SETF).

(define expression-item-type
  (make-record-type "expression-item" '(COMPILER ANNOTATION)))

(define make-expression-item
  (record-constructor expression-item-type '(COMPILER ANNOTATION)))

(define expression-item?
  (record-predicate expression-item-type))

(define expression-item/compiler
  (record-accessor expression-item-type 'COMPILER))

(define expression-item/annotation
  (record-accessor expression-item-type 'ANNOTATION))

;;; Body items represent sequences (e.g. BEGIN).

(define body-item-type
  (make-record-type "body-item" '(COMPONENTS)))

(define make-body-item
  (record-constructor body-item-type '(COMPONENTS)))

(define body-item?
  (record-predicate body-item-type))

(define body-item/components
  (record-accessor body-item-type 'COMPONENTS))

;;; Definition items represent definitions, whether top-level or
;;; internal, keyword or variable.

(define definition-item-type
  (make-record-type "definition-item" '(BINDING-THEORY NAME VALUE)))

(define make-definition-item
  (record-constructor definition-item-type '(BINDING-THEORY NAME VALUE)))

(define definition-item?
  (record-predicate definition-item-type))

(define definition-item/binding-theory
  (record-accessor definition-item-type 'BINDING-THEORY))

(define definition-item/name
  (record-accessor definition-item-type 'NAME))

(define definition-item/value
  (record-accessor definition-item-type 'VALUE))

(define (bind-definition-item! environment item)
  ((definition-item/binding-theory item)
   environment
   (definition-item/name item)
   (promise:force (definition-item/value item))))

(define (syntactic-binding-theory environment name item)
  (if (or (keyword-item? item)
	  (variable-item? item))
      (begin
	(syntactic-environment/define! environment name item)
	#f)
      (syntax-error "syntactic binding value must be a keyword or a variable"
		    item)))

(define (variable-binding-theory environment name item)
  ;; If ITEM isn't a valid expression, an error will be signalled by
  ;; COMPILE-ITEM/EXPRESSION later.
  (cons (bind-variable! environment name) item))

(define (overloaded-binding-theory environment name item)
  (if (keyword-item? item)
      (begin
	(syntactic-environment/define! environment name item)
	#f)
      (cons (bind-variable! environment name) item)))

;;;; Classifiers, Compilers, Expanders

(define (sc-expander->classifier expander keyword-environment)
  (lambda (form environment definition-environment)
    (classify/form (expander form environment)
		   keyword-environment
		   definition-environment)))

(define (er-expander->classifier expander keyword-environment)
  (sc-expander->classifier (er->sc-expander expander) keyword-environment))

(define (er->sc-expander expander)
  (lambda (form environment)
    (capture-syntactic-environment
     (lambda (keyword-environment)
       (make-syntactic-closure
	environment '()
	(expander form
		  (let ((renames '()))
		    (lambda (identifier)
		      (let ((association (assq identifier renames)))
			(if association
			    (cdr association)
			    (let ((rename
				   (make-syntactic-closure
				    keyword-environment
				    '()
				    identifier)))
			      (set! renames
				    (cons (cons identifier rename)
					  renames))
			      rename)))))
		  (lambda (x y)
		    (identifier=? environment x
				  environment y))))))))

(define (classifier->keyword classifier)
  (make-syntactic-closure
   (let ((environment
	  (internal-syntactic-environment null-syntactic-environment)))
     (syntactic-environment/define! environment
				    'KEYWORD
				    (make-keyword-item classifier "c->k"))
     environment)
   '()
   'KEYWORD))

(define (compiler->keyword compiler)
  (classifier->keyword (compiler->classifier compiler)))

(define (classifier->form classifier)
  `(,(classifier->keyword classifier)))

(define (compiler->form compiler)
  (classifier->form (compiler->classifier compiler)))

(define (compiler->classifier compiler)
  (lambda (form environment definition-environment)
    definition-environment		;ignore
    (make-expression-item
     (lambda () (compiler form environment)) form)))

;;;; Macrologies
;;;  A macrology is a procedure that accepts a syntactic environment
;;;  as an argument, producing a new syntactic environment that is an
;;;  extension of the argument.

(define (make-primitive-macrology generate-definitions)
  (lambda (base-environment)
    (let ((environment (top-level-syntactic-environment base-environment)))
      (let ((define-classifier
	      (lambda (keyword classifier)
		(syntactic-environment/define!
		 environment
		 keyword
		 (make-keyword-item classifier keyword)))))
	(generate-definitions
	 define-classifier
	 (lambda (keyword compiler)
	   (define-classifier keyword (compiler->classifier compiler)))))
      environment)))

(define (make-expander-macrology object->classifier generate-definitions)
  (lambda (base-environment)
    (let ((environment (top-level-syntactic-environment base-environment)))
      (generate-definitions
       (lambda (keyword object)
	 (syntactic-environment/define!
	  environment
	  keyword
	  (make-keyword-item (object->classifier object environment) keyword)))
       base-environment)
      environment)))

(define (make-sc-expander-macrology generate-definitions)
  (make-expander-macrology sc-expander->classifier generate-definitions))

(define (make-er-expander-macrology generate-definitions)
  (make-expander-macrology er-expander->classifier generate-definitions))

(define (compose-macrologies . macrologies)
  (lambda (environment)
    (do ((macrologies macrologies (cdr macrologies))
	 (environment environment ((car macrologies) environment)))
	((null? macrologies) environment))))

;;;; Utilities

(define (bind-variable! environment name)
  (let ((rename (syntactic-environment/rename environment name)))
    (syntactic-environment/define! environment
				   name
				   (make-variable-item rename))
    rename))

(define (reserve-names! names environment)
  (let ((item (make-reserved-name-item)))
    (for-each (lambda (name)
		(syntactic-environment/define! environment name item))
	      names)))

(define (capture-syntactic-environment expander)
  (classifier->form
   (lambda (form environment definition-environment)
     form				;ignore
     (classify/form (expander environment)
		    environment
		    definition-environment))))

(define (unspecific-expression)
  (compiler->form
   (lambda (form environment)
     form environment			;ignore
     (output/unspecific))))

(define (unassigned-expression)
  (compiler->form
   (lambda (form environment)
     form environment			;ignore
     (output/unassigned))))

(define (syntax-quote expression)
  `(,(compiler->keyword
      (lambda (form environment)
	environment			;ignore
	(syntax-check '(KEYWORD DATUM) form)
	(output/literal-quoted (cadr form))))
    ,expression))

(define (flatten-body-items items)
  (append-map item->list items))

(define (item->list item)
  (if (body-item? item)
      (flatten-body-items (body-item/components item))
      (list item)))

(define (output/let names values body)
  (if (null? names)
      body
      (output/combination (output/lambda names body) values)))

(define (output/letrec names values body)
  (if (null? names)
      body
      (output/let
       names
       (map (lambda (name) name (output/unassigned)) names)
       (output/sequence
	(list (if (null? (cdr names))
		  (output/assignment (car names) (car values))
		  (let ((temps (map (make-name-generator) names)))
		    (output/let
		     temps
		     values
		     (output/sequence
		      (map output/assignment names temps)))))
	      body)))))

(define (output/top-level-sequence expressions)
  (if (null? expressions)
      (output/unspecific)
      (output/sequence expressions)))
