;;; "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 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)))