; Copyright (c) 1993, 1994 Richard Kelsey and Jonathan Rees. See file COPYING. ; Syntactic stuff: transforms and operators. (define usual-operator-type (procedure-type any-arguments-type value-type #f)) ; -------------------- ; Operators (= special operators and primitives) (define-record-type operator :operator (make-operator type nargs uid name) operator? (type operator-type set-operator-type!) (nargs operator-nargs) (uid operator-uid) (name operator-name)) (define-record-discloser :operator (lambda (s) (list 'operator (operator-name s) (type->sexp (operator-type s) #t)))) (define (get-operator name . type-option) (let ((type (if (null? type-option) #f (car type-option))) (probe (table-ref operators-table name))) (if (operator? probe) (let ((previous-type (operator-type probe))) (cond ((not type)) ((symbol? type) ; 'leaf or 'internal (if (not (eq? type previous-type)) (warn "operator type inconsistency" name type previous-type))) ((subtype? type previous-type) ;Improvement (set-operator-type! probe type)) ((not (subtype? previous-type type)) (warn "operator type inconsistency" name (type->sexp previous-type 'foo) (type->sexp type 'foo)))) probe) (let* ((uid *operator-uid*) (type (or type usual-operator-type)) (op (make-operator type (if (and (not (symbol? type)) (fixed-arity-procedure-type? type)) (procedure-type-arity type) #f) uid name))) (if (>= uid number-of-operators) (warn "too many operators" (operator-name op) (operator-type op))) (set! *operator-uid* (+ *operator-uid* 1)) (table-set! operators-table (operator-name op) op) (vector-set! the-operators uid op) op)))) (define *operator-uid* 0) (define operators-table (make-table)) (define number-of-operators 200) ;Fixed-size limits bad, but speed good (define the-operators (make-vector number-of-operators #f)) ; -------------------- ; Operator tables (for fast dispatch) (define (make-operator-table default . mumble-option) (let ((v (make-vector number-of-operators default))) (if (not (null? mumble-option)) (define-usual-suspects v (car mumble-option))) v)) (define operator-table-ref vector-ref) (define (operator-lookup table op) (operator-table-ref table (operator-uid op))) (define (operator-define! table name proc-or-type . proc-option) (if (null? proc-option) (vector-set! table ;Obsolescent (operator-uid (if (pair? name) (get-operator (car name) (cadr name)) (get-operator name))) proc-or-type) (vector-set! table (operator-uid (get-operator name proc-or-type)) (car proc-option)))) ; -------------------- ; Nodes ; A node is an annotated expression (or definition or other form). ; The FORM component of a node is an S-expression of the same form as ; the S-expression representation of the expression. E.g. for ; literals, the form is the literal value; for variables the form is ; the variable name; for IF expressions the form is a 4-element list ; (ignored test con alt). Nodes also have a tag identifying what kind ; of node it is (literal, variable, if, etc.) and a property list. (define-record-type node :node (really-make-node uid form plist) node? (uid node-operator-id) (form node-form) (plist node-plist set-node-plist!)) (define-record-discloser :node (lambda (n) (list (operator-name (node-operator n)) (node-form n)))) (define (make-node operator form) (really-make-node (operator-uid operator) form '())) (define (node-ref node key) (let ((probe (assq key (node-plist node)))) (if probe (cdr probe) #f))) (define (node-set! node key value) ;gross (if value (let ((probe (assq key (node-plist node)))) (if probe (set-cdr! probe value) (set-node-plist! node (cons (cons key value) (node-plist node))))) (let loop ((l (node-plist node)) (prev #f)) (cond ((null? l) 'lose) ((eq? key (caar l)) (if prev (set-cdr! prev (cdr l)) (set-node-plist! node (cdr l)))) (else (loop (cdr l) l)))))) (define (node-operator node) (vector-ref the-operators (node-operator-id node))) (define (node-predicate name . type-option) (let ((id (operator-uid (apply get-operator name type-option)))) (lambda (node) (= (node-operator-id node) id)))) (define (make-similar-node node form) (if (equal? form (node-form node)) node (make-node (node-operator node) form))) ; -------------------- ; Generated names ; Generated names make lexically-scoped macros work. They're the same ; as what Alan Bawden and Chris Hanson call "aliases". The parent ; field is always another name (perhaps generated). The parent chain ; provides an access path to the name's binding, should one ever be ; needed. That is: If name M is bound to a transform T that generates ; name G as an alias for name N, then M is (generated-parent-name G), ; so we can get the binding of G by accessing the binding of N in T's ; environment of closure, and we get T by looking up M in the ; environment in which M is *used*. (define-record-type generated :generated (make-generated symbol token env parent-name) generated? (symbol generated-symbol) (token generated-token) (env generated-env) (parent-name generated-parent-name)) (define-record-discloser :generated (lambda (name) (list 'generated (generated-symbol name) (generated-uid name)))) (define (generate-name symbol env parent-name) ;for opt/inline.scm (make-generated symbol (cons #f #f) env parent-name)) ;foo (define (generated-uid g) (let ((t (generated-token g))) (or (car t) (let ((uid *generated-uid*)) (set! *generated-uid* (+ *generated-uid* 1)) (set-car! t uid) uid)))) (define *generated-uid* 0) (define (name->symbol name) (if (symbol? name) name (string->symbol (string-append (symbol->string (generated-symbol name)) "##" (number->string (generated-uid name)))))) (define (name-hash name) (cond ((symbol? name) (string-hash (symbol->string name))) ((generated? name) (name-hash (generated-symbol name))) (else (error "invalid name" name)))) ; Used by QUOTE to turn generated names back into symbols (define (desyntaxify thing) (cond ((or (boolean? thing) (null? thing) (number? thing) (symbol? thing) (char? thing)) thing) ((string? thing) (make-immutable! thing)) ((generated? thing) (desyntaxify (generated-symbol thing))) ((pair? thing) (make-immutable! (let ((x (desyntaxify (car thing))) (y (desyntaxify (cdr thing)))) (if (and (eq? x (car thing)) (eq? y (cdr thing))) thing (cons x y))))) ((vector? thing) (make-immutable! (let ((new (make-vector (vector-length thing) #f))) (let loop ((i 0) (same? #t)) (if (>= i (vector-length thing)) (if same? thing new) (let ((x (desyntaxify (vector-ref thing i)))) (vector-set! new i x) (loop (+ i 1) (and same? (eq? x (vector-ref thing i)))))))))) ((operator? thing) (warn "operator in quotation" thing) (operator-name thing)) ;Foo (else (warn "invalid datum in quotation" thing) thing))) ; -------------------- ; Transforms ; A transform represents a source-to-source rewrite rule: either a ; macro or an in-line procedure. (define-record-type transform :transform (really-make-transform xformer env type aux-names source id) transform? (xformer transform-procedure) (env transform-env) (type transform-type) (aux-names transform-aux-names) (source transform-source) ;for reification (id transform-id)) (define (make-transform thing env type source id) (let ((type (if (or (pair? type) (symbol? type)) (sexp->type type #t) type))) (make-immutable! (if (pair? thing) (really-make-transform (car thing) env type (cdr thing) source id) (really-make-transform thing env type #f source id))))) (define-record-discloser :transform (lambda (m) (list 'transform (transform-id m)))) (define (maybe-transform t exp env-of-use) (let* ((token (cons #f #f)) (new-env (bind-aliases token t env-of-use)) (rename (make-name-generator (transform-env t) token (node-form (car exp)))) (compare (lambda (name1 name2) (or (eqv? name1 name2) (and (name? name1) (name? name2) (same-denotation? (lookup new-env name1) (lookup new-env name2))))))) (values ((transform-procedure t) exp rename compare) new-env token))) (define (bind-aliases token t env-of-use) (let ((env-of-definition (transform-env t))) (if (procedure? env-of-definition) (lambda (name) (if (and (generated? name) (eq? (generated-token name) token)) (lookup env-of-definition (generated-symbol name)) (lookup env-of-use name))) env-of-use))) ;Lose (define (make-name-generator env token parent-name) (let ((alist '())) ;list of (symbol . generated) (lambda (symbol) (if (symbol? symbol) (let ((probe (assq symbol alist))) (if probe (cdr probe) (let ((new-name (make-generated symbol token env parent-name))) (set! alist (cons (cons symbol new-name) alist)) new-name))) (error "non-symbol argument to rename procedure" symbol parent-name))))) (define (same-denotation? x y) (or (equal? x y) (and (binding? x) (binding? y) (eq? (binding-place x) (binding-place y))))) ; -------------------- ; Bindings: the things that are usually returned by LOOKUP. ; Representation is #(type place operator-or-transform-or-#f). ; For top-level bindings, place is usually a location. (define binding? vector?) (define (binding-type b) (vector-ref b 0)) (define (binding-place b) (vector-ref b 1)) (define (binding-static b) (vector-ref b 2)) (define (set-binding-place! b place) (vector-set! b 1 place)) (define (make-binding type place static) (let ((b (make-vector 3 place))) (vector-set! b 0 type) (vector-set! b 2 static) b)) (define (clobber-binding! b type place static) (vector-set! b 0 type) (if place (set-binding-place! b place)) (vector-set! b 2 static)) ; Return a binding that's similar to the given one, but has its type ; replaced with the given type. (define (impose-type type b integrate?) (if (or (eq? type syntax-type) (not (binding? b))) b (make-binding (if (eq? type undeclared-type) (let ((type (binding-type b))) (if (variable-type? type) (variable-value-type type) type)) type) (binding-place b) (if integrate? (binding-static b) #f)))) ; Return a binding that's similar to the given one, but has any ; procedure integration or other unnecesary static information ; removed. But don't remove static information for macros (or ; structures, interfaces, etc.) (define (forget-integration b) (if (and (binding-static b) (subtype? (binding-type b) any-values-type)) (make-binding (binding-type b) (binding-place b) #f) b)) ; -------------------- ; Expression classifier. Returns a node. (define (classify form env) (cond ((node? form) (if (and (name-node? form) (not (node-ref form 'binding))) (classify-name (node-form form) env) form)) ((name? form) (classify-name form env)) ((pair? form) (let ((op-node (classify (car form) env))) (if (name-node? op-node) (let ((probe (node-ref op-node 'binding))) (if (binding? probe) (let ((s (binding-static probe))) (cond ((operator? s) (classify-operator-form s op-node form env)) ((and (transform? s) (eq? (binding-type probe) syntax-type)) ;; Non-syntax transforms (i.e. procedure ;; integrations) get done by MAYBE-TRANSFORM-CALL. (classify-macro-application s (cons op-node (cdr form)) env)) (else (classify-call op-node form env)))) (classify-call op-node form env))) (classify-call op-node form env)))) ((literal? form) (classify-literal form)) ;; ((qualified? form) ...) (else (classify (syntax-error "invalid expression" form) env)))) (define call-node? (node-predicate 'call 'internal)) (define name-node? (node-predicate 'name 'leaf)) (define classify-literal (let ((op (get-operator 'literal 'leaf))) (lambda (exp) (make-node op exp)))) (define classify-call (let ((operator/call (get-operator 'call 'internal))) (lambda (proc-node exp env) (make-node operator/call (if (eq? proc-node (car exp)) exp ;+++ (cons proc-node (cdr exp))))))) ; An environment is a procedure that takes a name and returns one of ; the following: ; ; 1. A binding record. ; 2. A node, which is taken to be a substitution for the name. ; 3. Another name, meaning that the first name is unbound. The name ; returned will be a symbol even if the original name was generated. ; ; In case 1, CLASSIFY caches the binding as the node's BINDING property. ; In case 2, it simply returns the node. (define (classify-name name env) (let ((binding (lookup env name))) (if (node? binding) binding (let ((node (make-node operator/name name))) (if (not (unbound? binding)) (node-set! node 'binding binding)) node)))) (define operator/name (get-operator 'name 'leaf)) ; Expand a macro or in-line procedure application. (define (classify-macro-application t form env-of-use) (classify-transform-application t form env-of-use (lambda () (classify (syntax-error "use of macro doesn't match definition" (cons (schemify (car form) env-of-use) (desyntaxify (cdr form)))) env-of-use)))) (define classify-transform-application (let ((operator/with-aliases (get-operator 'with-aliases syntax-type))) (lambda (t form env-of-use lose) (call-with-values (lambda () (maybe-transform t form env-of-use)) (lambda (new-form new-env token) (cond ((eq? new-form form) (lose)) ((eq? new-env env-of-use) (classify new-form new-env)) (else (make-node operator/with-aliases `(with-aliases ,(car form) ,token ,new-form))))))))) (define (maybe-transform-call proc-node node env) (if (name-node? proc-node) (let ((b (or (node-ref proc-node 'binding) (lookup env (node-form proc-node))))) (if (binding? b) (let ((s (binding-static b))) (cond ((transform? s) (classify-transform-application s (node-form node) env (lambda () node))) ;; ((operator? s) (make-node s (node-form node))) (else node))) node)) node)) ; -------------------- ; Specialist classifiers for particular operators (define (classify-operator-form op op-node form env) ((operator-table-ref classifiers (operator-uid op)) op op-node form env)) (define classifiers (make-operator-table (lambda (op op-node form env) (if (let ((nargs (operator-nargs op))) (or (not nargs) (= nargs (length (cdr form))))) (make-node op (cons op-node (cdr form))) (classify-call op-node form env))))) (define (define-classifier name proc) (operator-define! classifiers name syntax-type proc)) ; Remove generated names from quotations. (define-classifier 'quote (lambda (op op-node exp env) (make-node op (list op-node (desyntaxify (cadr exp)))))) ; Convert one-armed IF to two-armed IF. (define-classifier 'if (lambda (op op-node exp env) (make-node op (cons op-node (if (null? (cdddr exp)) (append (cdr exp) (list (unspecific-node))) (cdr exp)))))) (define unspecific-node (let ((op (get-operator 'unspecific (proc () unspecific-type)))) (lambda () (make-node op '(unspecific))))) ; Rewrite (define (name . vars) body ...) ; as (define foo (lambda vars body ...)). (define-classifier 'define (let ((operator/lambda (get-operator 'lambda syntax-type)) (operator/unassigned (get-operator 'unassigned (proc () value-type)))) ;foo (lambda (op op-node form env) (let ((pat (cadr form))) (make-node op (cons op-node (if (pair? pat) (list (car pat) (make-node operator/lambda `(lambda ,(cdr pat) ,@(cddr form)))) (list pat (if (null? (cddr form)) (make-node operator/unassigned `(unassigned)) (caddr form)))))))))) ;(define (make-define-node op op-node lhs rhs) ; (make-node op (list op-node lhs rhs))) (define define-node? (node-predicate 'define)) (define define-syntax-node? (node-predicate 'define-syntax syntax-type)) ; For the module system: (define-classifier 'structure-ref (lambda (op op-node form env) (let ((struct-node (classify (cadr form) env)) (lose (lambda () (classify (syntax-error "invalid structure reference" form) env)))) (if (and (name? (caddr form)) (name-node? struct-node)) (let ((b (node-ref struct-node 'binding))) (if (and (binding? b) (binding-static b)) ; (structure? ...) (classify (generate-name (desyntaxify (caddr form)) (binding-static b) (node-form struct-node)) env) (lose))) (lose))))) ; Magical Scheme 48 internal thing, mainly for use by the ; DEFINE-PACKAGE macro. (define-classifier '%file-name% (let ((operator/quote (get-operator 'quote syntax-type))) (lambda (op op-node form env) (make-node operator/quote `',(get-funny env funny-name/source-file-name))))) (define funny-name/source-file-name (string->symbol ".source-file-name.")) (define (bind-source-file-name filename env) (if filename (bind1 funny-name/source-file-name (make-binding syntax-type #f filename) env) env)) ; To do: ; Check syntax of others special forms ; -------------------- ; Environments (define (lookup env name) (env name)) (define (bind1 name binding env) (lambda (a-name) (if (eq? a-name name) binding (lookup env a-name)))) ; corollary (define (bind names bindings env) (cond ((null? names) env) (else (bind1 (car names) (car bindings) (bind (cdr names) (cdr bindings) env))))) (define (bindrec names env->bindings env) (set! env (bind names (env->bindings (lambda (a-name) (env a-name))) env)) env) ; -------------------- ; Utilities (define (literal? exp) (or (number? exp) (char? exp) (string? exp) (boolean? exp))) (define (number-of-required-args formals) (do ((l formals (cdr l)) (i 0 (+ i 1))) ((not (pair? l)) i))) (define (n-ary? formals) (cond ((null? formals) #f) ((pair? formals) (n-ary? (cdr formals))) (else #t))) (define (normalize-formals formals) (cond ((null? formals) '()) ((pair? formals) (cons (car formals) (normalize-formals (cdr formals)))) (else (list formals)))) (define (syntax? d) (cond ((operator? d) (eq? (operator-type d) syntax-type)) ((transform? d) (eq? (transform-type d) syntax-type)) (else #f))) (define (name? thing) (or (symbol? thing) (generated? thing))) (define unbound? name?) ; -------------------- ; LET-SYNTAX and friends (define (define-usual-suspects table mumble) (operator-define! table 'let-syntax syntax-type (mumble (lambda (node env) (let* ((form (node-form node)) (specs (cadr form))) (values (caddr form) (bind (map car specs) (map (lambda (spec) (make-binding syntax-type (list 'let-syntax) (process-syntax (cadr spec) env (car spec) env))) specs) env)))))) (operator-define! table 'letrec-syntax syntax-type (mumble (lambda (node env) (let* ((form (node-form node)) (specs (cadr form))) (values (caddr form) (bindrec (map car specs) (lambda (new-env) (map (lambda (spec) (make-binding syntax-type (list 'letrec-syntax) (process-syntax (cadr spec) new-env (car spec) new-env))) specs)) env)))))) (operator-define! table 'with-aliases syntax-type (mumble (lambda (node env) (let ((form (node-form node))) (values (cadddr form) (bind-aliases (caddr form) (binding-static (node-ref (cadr form) 'binding)) env))))))) (define (process-syntax form env name env-or-whatever) (let ((eval+env (force (reflective-tower env)))) (make-transform ((car eval+env) form (cdr eval+env)) env-or-whatever syntax-type form name))) (define (get-funny env name) (let ((binding (lookup env name))) (if (binding? binding) (binding-static binding) #f))) ; An environment's "reflective tower" is a promise that is expected to ; deliver, when forced, a pair (eval . env). (define funny-name/reflective-tower (string->symbol ".reflective-tower.")) (define (reflective-tower env) (or (get-funny env funny-name/reflective-tower) (error "environment has no environment for syntax" env))) ; -------------------- ; The horror of internal defines ; The continuation argument to SCAN-BODY takes two arguments: a list ; of definition nodes, and a list of other things (nodes and ; expressions). (define (scan-body forms env cont) (if (or (null? forms) (null? (cdr forms))) (cont '() forms) ;+++ tiny compiler speedup? (scan-body-forms forms env '() (lambda (defs exps env) (cont defs exps))))) (define (scan-body-forms forms env defs cont) (if (null? forms) (cont defs '() env) (let ((node (classify (car forms) env)) (forms (cdr forms))) (cond ((define-node? node) (scan-body-forms forms (let ((name (cadr (node-form node)))) (bind1 name ;; Shadow, and don't cache lookup (make-node operator/name name) env)) (cons node defs) cont)) ((begin-node? node) (scan-body-forms (cdr (node-form node)) env defs (lambda (new-defs exps env) (cond ((null? exps) (scan-body-forms forms env new-defs cont)) ((eq? new-defs defs) (cont defs (append exps forms) env)) (else (body-lossage node env)))))) (else (cont defs (cons node forms) env)))))) (define (body-lossage node env) (syntax-error "definitions and expressions intermixed" (schemify node env))) (define begin-node? (node-predicate 'begin syntax-type)) ; -------------------- ; Variable types (define (variable-type type) (list 'variable type)) (define (variable-type? type) (and (pair? type) (eq? (car type) 'variable))) (define variable-value-type cadr) ; Used in two places: ; 1. GET-LOCATION checks to see if the context of use (either variable ; reference or assignment) is compatible with the declared type. ; 2. CHECK-STRUCTURE checks to see if the reconstructed type is compatible ; with any type declared in the interface. (define (compatible-types? have-type want-type) (if (variable-type? want-type) (and (variable-type? have-type) (same-type? (variable-value-type have-type) (variable-value-type want-type))) (meet? (if (variable-type? have-type) (variable-value-type have-type) have-type) want-type))) ; Usual type for Scheme variables. (define usual-variable-type (variable-type value-type)) (define undeclared-type ':undeclared) ;cf. really-export macro ; Associate a reader (parser) with an environment. (define funny-name/reader (string->symbol ".reader.")) ;(define (set-package-reader! p reader) ; (package-define-funny! p funny-name/reader reader)) (define (environment-reader env) (or (get-funny env funny-name/reader) read))