;;; -*- Mode:LISP; Package:(CLSCH LISP); Syntax:COMMON-LISP; Base:10 -*- ;;; An implementation of Scheme, less continuations, in Common Lisp. ;;; ***** Initial version - not fully debugged ***** ;;; The dialect here is a union of the features needed for ;;; (a) the examples in Abelson & Sussman's book "Structure and ;;; Interpretation of Computer Programs" ;;; (b) the problem sets for the 6.001 course ;;; (c) the "essential" subset of the Revised Revised Report on Scheme ;;; Scheme ;;; The only thing from (a) that's missing is MAKE-ENVIRONMENT. ;;; The only thing from (c) that's missing is CALL-WITH-CURRENT-CONTINUATION. ;;; Some things from (b) might be missing; I haven't checked thoroughly. ;;; A small number of important nonstandard features are present also: ;;; RANDOM, tables, "named LET", FLUID-LET, and DEFINE-MACRO. ;;; Implementing MAKE-ENVIRONMENT and CALL-WITH-CURRENT-CONTINUATION ;;; would be a major headache. Fortunately, Scheme is still fairly ;;; useful without them. ;;; *** RECENT CHANGES *** ;;; Version 156 (11/22/85) ;;; - JDR's latest set of fixes ;;; Version 154 (11/21/85) ;;; - Flushed idea of exporting things in Scheme package. ;;; - Translator passes an ENV argument around (doesn't use it yet). ;;; Version 149 (11/16/85) ;;; - Winning quasiquote macro. ;;; - Scheme's very own printer. ;;; Version 146 (11/12/85) ;;; - Eric Benson's colon read macro. ;;; - Fixed a bug in WRITE. ;;; - John Ramsdell's meta-. hacks. ;;; Version 134 (11/10/85) ;;; - Entry function S renamed to be SCHEME. ;;; - Object file extension is now SBIN istead of SFASL. ;;; - Improvements in package structure; SCHEME now inherits from CLSCH. ;;; E.g., Scheme's QUOTE is now the same as Lisp's. ;;; - Definition of a DEFINE macro in the SCHEME package, so that it ;;; will now sort of work to use Common Lisp LOAD and EVAL and editor ;;; commands to cause Scheme values to get defined. ;;; - No more call to MACROEXPAND (avoid Symbolics DEFSUBST screw). ;;; - Elimination of REC in DEFINE expansion (for speed and debugging). ;;; - Some nonstandard features: tables, FLUID-LET, and DEFINE-MACRO. ;;; - Scheme DEFINE also sets the function cell. ;;; - Trivial definition of NUMBER->STRING. ;;; A minor flaw: ;;; (define (foo x) y) is the same as ;;; (define foo (lambda (x) y)), NOT ;;; (define foo (rec foo (lambda (x) y))), as the RRRS would dictate. ;;; Fortunately, this delinquency leads to faster execution, and makes ;;; TRACE work besides. ;;; To do: ;;; - It would be nice if there was an evaluator. Code runs impossibly ;;; slow if it's not compiled; and debugging is a pain. ;;; - CAR and CDR of () should be error. ;;; - Error reporting is bad; e.g., no procedure names. ;;; - Need equivalent of GRINDEF. ;;; - Want optimization of LETREC. ;;; - Variants of the compiler: (1) real continuations, (2) CL's which ;;; already have tail recursion, (3) CL's which can kludge tail recursion ;;; (e.g. 3600's) ;;; Thanks to John Ramsdell of Mitre and Eric Benson of Lucid for various ;;; contributions. ;;; Jonathan Rees Aug-Sept 1985 (in-package 'clsch) ;Common Lisp SCHeme (shadow '(help lambda)) (export '(scheme help)) (defvar help "How to use Scheme: - To start a scheme read-eval-print loop, evaluate (CLSCH:SCHEME). - To exit scheme once in it, do (EXIT). - To get back into the scheme read-eval-print loop, e.g. when an error occurs, do (RESET). - Use MIT AI memo 848, the Revised Revised Report on Scheme, as a reference manual. All features marked 'essential' are here, along with many of the non-essential features. - CALL-WITH-CURRENT-CONTINUATION exists, but continuations will have indefinite extent only if the host Common Lisp's BLOCK tags do. - Additional features used in S&ICP are also defined, with the exception of COLLECT and MAKE-ENVIRONMENT. - The expression ## evaluates to the value of the read-eval-print loop's last evaluation. - To compile a scheme file, use scheme's COMPILE-FILE procedure.") ;;; Utility for avoiding compiler warnings (defmacro ignorable (&rest vars) `(progn ,@vars)) ;;;----- Scheme-like macros for Common Lisp code ;;; The main reason the following things exist is so that the translator ;;; itself can be written in Scheme instead of in Common Lisp. This is ;;; mostly just an exercise. (eval-when (global:compile global:eval global:load) (defun ampersandify (bvl) (do ((l bvl (cdr l)) (z '() (cons (car l) z))) ((atom l) (cond ((null l) (nreverse z)) (t (nreconc z `(&rest ,l))))))) (defun concatenate-symbol (&rest things) ; T (values (intern (apply #'concatenate 'string (mapcar #'string things))))) ) ;;; Same as SETQ but inhibits "not declared special" warning. (defmacro setq-global-value (var val) (let ((g (gensym))) `(let ((,g ,val)) (declare (special ,var)) (setq ,var ,g) ',var))) (defmacro define (pat &body body) (cond ((consp pat) `(defun ,(car pat) ,(ampersandify (cdr pat)) ,@body)) (t `(setq-global-value ,pat ,@body)))) ;;; LAMBDA is shadowed in the CLSCH package, to avoid a conflict here with ;;; any possible LAMBDA macro defined in the host Common Lisp. (defmacro lambda (vars &body body) `#'(lisp:lambda ,(ampersandify vars) ,@body)) (defmacro iterate (tag specs &body body) (let ((vars (mapcar #'car specs)) (id (gensym))) `(block ,id (let ,specs (tagbody ,id (macrolet ((,tag ,vars `(progn (psetq ,@(list ,@(mapcan #'(lisp:lambda (var) `(',var ,var)) vars))) (go ,',id)))) (return-from ,id (progn ,@body)))))))) #|The following version is better, but isn't efficiently compiled by most CL's. (defmacro iterate (tag specs &body body) `(labels ((,tag ,(mapcar #'car specs) ,@body)) (,tag ,@(mapcar #'cadr specs))))|# (defmacro defsynonym (dst src) `(defmacro ,dst (&rest x) `(,',src ,@x))) (defsynonym eq? eq) (defsynonym equal? equal) (defsynonym null? null) (defsynonym pair? consp) (defsynonym atom? atom) (defsynonym symbol? symbolp) (defsynonym append! nconc) (defsynonym reverse! nreverse) (defsynonym vector? simple-vector-p) (defconstant else t) ;;; Pretty useless, but the existence of the definitions may help ;;; indentation work on the 3600. (defmacro letrec (specs &body body) `(labels ,(mapcar #'(lisp:lambda (spec) (let ((var (car spec)) (val (cadr spec))) (cond ((and (pair? val) (eq? (car val) 'lambda)) `(,var ,(ampersandify (cadr val)) ,@(cddr val))) (t (error "losing LETREC spec - ~s" spec))))) specs) ,@body)) (defmacro fluid-let (specs &body body) `(let ,specs (declare (special ,@(mapcar #'car specs))) ,@body)) (defmacro define-macro ((name . bvl) &body body) `(progn 'compile (defmacro ,name ,(ampersandify bvl) ,@body) (*define-scheme-macro ',name ',name))) (defmacro set! (var val) `(setq ,var ,val)) ;;;----- Basic things which need to be defined before everything else ;;; Scheme lives within its own package, but happens to inherit the ;;; symbols that CLSCH exports. It does NOT inherit from LISP! (eval-when (eval load compile) (defvar clsch-package (find-package "CLSCH")) (defvar scheme-package (make-package 'scheme :use (list clsch-package)))) (eval-when (eval load compile) (export 'else clsch-package) (defun clsch-symbol (symbol) (cond ((eq (symbol-package symbol) clsch-package) symbol) (t (let ((symbol (intern (symbol-name symbol) scheme-package))) ;; (export symbol scheme-package) - necessary?? symbol)))) (defun clsch-export (symbol) (cond ((eq (symbol-package symbol) clsch-package) (export symbol clsch-package)) ((not (eq (symbol-package symbol) scheme-package)) (error "bogus symbol: (CLSCH-EXPORT '~s)" symbol))) symbol)) ;;; Registers for passing arguments (defvar *argument-registers* (list '**a1** '**a2** '**a3** '**a4** '**a5**)) (defvar *proc+arg-regs* (cons '**p** *argument-registers*)) (defvar *number-of-arg-regs* (length *argument-registers*)) (defvar **p**) (defvar **a1**) (defvar **a2**) (defvar **a3**) (defvar **a4**) (defvar **a5**) (define (ensure-enough-arg-regs n) (cond ((> n *number-of-arg-regs*) (iterate loop ((i (1+ *number-of-arg-regs*)) (l '())) (cond ((> i n) (setf (cdr (last *argument-registers*)) (reverse! l)) (setq *number-of-arg-regs* n)) (else (loop (1+ i) (cons (intern (format nil "**A~D**" i) (find-package "CLSCH")) l)))))))) ;;; The Scheme readtable isn't needed until run time, but this seems ;;; like a good place to put it. (defvar cl-readtable *readtable*) (defvar scheme-readtable (copy-readtable nil)) (defconstant quasiquote-marker 'quasiquote-marker) (defconstant unquote-marker 'unquote-marker) (defconstant splice-marker 'splice-marker) (defun sharp-excl-read-macro (stream subchar arg) (ignorable subchar arg) (let ((name (read stream t nil t))) (ccase name ((scheme::true) t) ((scheme::false) nil) ((scheme::null) '()) ((scheme::quasiquote) quasiquote-marker) ((scheme::unquote) unquote-marker) ((scheme::splice) splice-marker) ))) (defun sharp-sharp-read-macro (stream subchar arg) (cond (arg (funcall (get-dispatch-macro-character #\# #\# cl-readtable) stream subchar arg)) (t '*))) ;;; Thanks to Eric Benson for the following hack. ;;; Small bug: a symbol beginning ":#" can't be read. (defun read-alphabetic (stream char) (let ((following-char (peek-char nil stream nil stream t))) ;; EOF is OK, STREAM is EOF-VALUE. (if (or (eq following-char stream) (not (constituentp following-char))) (values (intern (string char))) (let ((subsymbol (read stream t nil t))) (check-type subsymbol symbol) (values (intern (concatenate 'string (string char) (symbol-name subsymbol)))))))) (defun constituentp (char) (multiple-value-bind (fun n-t-p) (get-macro-character char) (cond (fun (not n-t-p)) (t (and (graphic-char-p char) (not (find char " ',;`()[]{}"))))))) (defun quasiquote-read-macro (stream ignore) (list quasiquote-marker (read stream t nil t))) (defun unquote-read-macro (stream ignore) (list (let ((following-char (peek-char nil stream nil stream t))) (cond ((char= following-char #\@) (read-char stream) splice-marker) (t unquote-marker))) (read stream t nil t))) (defun illegal-read-macro (ignore char) (cerror "treat the character as whitespace" "illegal character read - ~s" char) (values)) (let ((*readtable* scheme-readtable)) (set-macro-character #\` #'quasiquote-read-macro) (set-macro-character #\, #'unquote-read-macro) #-Symbolics (set-macro-character #\: #'read-alphabetic t) #+Symbolics ;Symbolics Common Lisp has many bugs. (set-syntax-from-char #\: #\!) (set-macro-character #\| #'illegal-read-macro) (set-macro-character #\\ #'illegal-read-macro) (set-macro-character #\[ #'illegal-read-macro) (set-macro-character #\] #'illegal-read-macro) (set-macro-character #\{ #'illegal-read-macro) (set-macro-character #\} #'illegal-read-macro) (set-dispatch-macro-character #\# #\! #'sharp-excl-read-macro) (set-dispatch-macro-character #\# #\# #'sharp-sharp-read-macro)) ;;;----- The Scheme-to-Common-Lisp Compiler ;;; Each definition of a Scheme special form or procedure is annotated ;;; with one or more letters indicating why that feature is included in ;;; this Scheme implementation. ;;; E = essential feature of RRRSS ;;; R = inessential feature of RRRSS ;;; S = something used in the book S&ICP ;;; M = MIT Scheme feature needed to run 6.001 course software ;;; T = random T feature which JAR likes to use ;;; Brief description of compilation strategy: ;;; Scheme code is translated into Common Lisp code, which can then be ;;; either interpreted or compiled. Scheme parasitizes Common Lisp's ;;; data types. Also, the compiler doesn't need to keep track of ;;; lexical variable references, since Common Lisp's lexical scoping can ;;; be used. The main thing the compiler worries about is tail recursion. ;;; If a Scheme procedure returns a non-null second value, that means that ;;; it wants to do a tail-recursive call. A driver loop is expected to ;;; call the procedure in the **P** register. Procedures always expect ;;; to receive their arguments in registers **A1**, **A2**, etc. ;;; Currently the ENV argument is ignored. ;;; TRANSLATE returns two values: the output expression, and a flag ;;; saying whether or not the expression was trivial (i.e. made ;;; assignments to the argument registers). (define (translate exp env ret?) (cond ((not (pair? exp)) (translate-return exp t ret?)) ((special-form? exp) (translate-special-form exp env ret?)) (else (translate-call exp env ret?)))) (define (translate-return exp trivial? ret?) (ignorable ret?) (values exp trivial?)) ;! ;;; Procedure call (define (translate-call proc+arg-exps env ret?) (let ((proc-exp (car proc+arg-exps)) (arg-exps (cdr proc+arg-exps))) (cond ((and (symbol? proc-exp) (get proc-exp 'trivial)) (translate-trivial-call (get proc-exp 'trivial) arg-exps env t ret?)) ((and (pair? proc-exp) (eq? (car proc-exp) 'lambda)) ;; LAMBDA-combinations are trivial. (multiple-value-bind (code trivial?) (translate-lambda-body (cddr proc-exp) env ret?) (let ((new-bvl (ampersandify (cadr proc-exp)))) (mapc (lambda (b) (warn-if-redefining b "binding")) new-bvl) (translate-trivial-call `(lisp:lambda ,new-bvl ,code) arg-exps env trivial? ret?)))) (else (translate-nontrivial-call proc-exp arg-exps env ret?))))) (define (translate-trivial-call proc-code arg-exps env trivial? ret?) (iterate loop ((a arg-exps) (z '()) (trivial? trivial?)) (cond ((null? a) (translate-return `(,proc-code ,@(reverse! z)) trivial? ret?)) (else (multiple-value-bind (code arg-trivial?) (translate (car a) env nil) (loop (cdr a) (cons code z) (and trivial? arg-trivial?))))))) (define (translate-nontrivial-call proc-exp arg-exps env ret?) (let ((nargs (length arg-exps))) (ensure-enough-arg-regs nargs) (let ((setup-code (psetqify-args proc-exp arg-exps env))) (cond (ret? (values `(progn ,@setup-code (values nil ,nargs)) nil)) (else (values `(progn ,@setup-code (driver-loop ,nargs)) nil)))))) ;;; Tries to do SETQ's in preference to PSETQ's, where possible. (define (psetqify-args proc-exp arg-exps env) (iterate loop ((a (cons proc-exp arg-exps)) (r *proc+arg-regs*) (setqs '()) (psetqs '())) (cond ((null? a) (append (if (null? psetqs) '() `((psetq ,@(reverse! psetqs)))) (if (null? setqs) '() `(( setq ,@(reverse! setqs)))))) (else (multiple-value-bind (code trivial?) (translate (car a) env nil) (let ((reg (car r))) (cond (trivial? (loop (cdr a) (cdr r) (list* code reg setqs) psetqs)) (else (loop (cdr a) (cdr r) setqs (list* code reg psetqs)) )))))))) ;;; Generates adequate, but potentially inferior, code. (define (psetqify-args-ez proc-exp arg-exps env) `((psetq ,@(mapcan (lambda (reg exp) (list reg (translate exp env nil))) *proc+arg-regs* (cons proc-exp arg-exps))))) ;;; Scheme special forms (defvar special-form-table) (setq special-form-table (make-hash-table :test #'eq)) (define (special-form? exp) (and (pair? exp) (gethash (car exp) special-form-table nil))) (define (translate-special-form exp env ret?) (funcall (gethash (car exp) special-form-table) exp env ret?)) (defmacro define-scheme-special-form (name args &body body) ;; Symbolics CL sucks. Anonymous functions don't get compiled! (let ((internal-name (concatenate-symbol name '/scheme-special-form))) `(progn 'compile (export ',name clsch-package) (defun ,internal-name ,args ,@args ,@body) (setf (gethash ',name special-form-table) #',internal-name)))) ;;; (Definitions of special forms appear in alphabetical order.) ;;; BEGIN, SEQUENCE (define-scheme-special-form begin (exp env ret?) ;E (translate-sequence (cdr exp) env ret?)) (define-scheme-special-form sequence (exp env ret?) ; RS (translate-sequence (cdr exp) env ret?)) (define (translate-sequence exp-list env ret?) (cond ((null? (cdr exp-list)) (translate (car exp-list) env ret?)) (else (iterate loop ((l exp-list) (z '()) (trivial? t)) (multiple-value-bind (code arg-trivial?) (translate (car l) env (and (null? (cdr l)) ret?)) (let ((trivial? (and trivial? arg-trivial?))) (cond ((null? (cdr l)) (values `(progn ,@(reverse! (cons code z))) trivial?)) (else (loop (cdr l) (cons code z) trivial?))))))))) ;;; Top-level DEFINE (defvar top-level-env '()) (define (translate-top-level exp) (cond ((definition? exp) (translate-top-level-define exp)) (t (translate exp top-level-env nil)))) (define (translate-top-level-define exp) (let ((pat (cadr exp)) (body (cddr exp))) (multiple-value-bind (name val) (parse-define pat body) (warn-if-redefining name "defining") (multiple-value-bind (code trivial?) (translate val top-level-env nil) (translate-return `(*define ',name ,code) trivial? nil))))) (define (parse-define pat body) (cond ((pair? pat) (values (car pat) ;; `(rec ,(car pat) ...) `(lambda ;Want NAMED-LAMBDA ,(cdr pat) ,@body))) (else (values pat (car body))))) (define (definition? exp) (and (pair? exp) (eq? (car exp) 'scheme::define))) (define (warn-if-redefining name what) (cond ((get name 'trivial) (in-scheme-package (lambda () (format *error-output* "~&Warning: ~A ~S, which is a built-in procedure.~&" what name))) (remprop name 'trivial)))) ;;; IF (define-scheme-special-form if (exp env ret?) ;E (multiple-value-bind (test-code test-trivial?) (translate (cadr exp) env nil) (multiple-value-bind (con-code con-trivial?) (translate (caddr exp) env ret?) (multiple-value-bind (alt-code alt-trivial?) (translate (cadddr exp) env ret?) (values `(if ,test-code ,con-code ,alt-code) (and test-trivial? con-trivial? alt-trivial?)))))) ;;; LAMBDA (define-scheme-special-form lambda (exp env ret?) ;E S (multiple-value-bind (code trivial?) (translate-lambda-body (cddr exp) (cons (cadr exp) env) t) (ignorable trivial?) (translate-return `(create-procedure (lambda (nargs) ;; Eventually, check number of args. (ignorable nargs) (let ,(bind-arguments (cadr exp)) ,code)) nil) t ;yes, closures are trivial ret?))) (define (bind-arguments bvl) (ensure-enough-arg-regs (improper-length bvl)) (do ((l bvl (cdr l)) (a *argument-registers* (cdr a)) (n 0 (+ n 1)) (z '() (cons `(,(car l) ,(car a)) z))) ((not (pair? l)) (reverse! (cond ((null? l) z) (else (cons `(,l (get-n-ary-arg nargs ,n)) z))))) (assert (not (null? a))) (warn-if-redefining (car l) "binding"))) (define (improper-length l) (do ((l l (cdr l)) (n 0 (1+ n))) ((atom? l) n))) (define (translate-lambda-body body env ret?) (iterate loop ((b body) (vars '()) (z '())) (cond ((null? (cdr b)) (multiple-value-bind (code trivial?) (translate-sequence (reverse! (cons (car b) z)) (cons vars env) ret?) (values (if (null? vars) code `(let ,(mapcar (lambda (var) `(,var '*undefined*)) vars) ,code)) trivial?))) (else (let ((exp (car b))) (cond ((definition? exp) (multiple-value-bind (name val) (parse-define (cadr exp) (cddr exp)) (loop (cdr b) (cons name vars) (cons `(set! ,name ,val) z)))) (else (loop (cdr b) vars (cons (car b) z))))))))) (define-scheme-special-form delay (exp env ret?) ; S (translate-return `(make-delay :thunk-or-value #'(lisp:lambda () ,(translate (cadr exp) env nil))) t ret?)) (setf (get 'make-delay 'trivial) 'make-delay) ;;; QUOTE (define-scheme-special-form quote (exp env ret?) ;E S (translate-return `(quote ,(cadr exp)) t ret?)) ;;; SET! (define-scheme-special-form set! (exp env ret?) ;E S (let ((var (cadr exp)) (val-exp (caddr exp))) (multiple-value-bind (code trivial?) (translate val-exp env nil) (warn-if-redefining var "assigning") (translate-return `(setq ,var ,code) trivial? ret?)))) ;;; Renegade DEFINE-MACRO - sort of compatible with MIT Scheme (define-scheme-special-form define-macro (exp env ret?) ; M (let ((name (caadr exp)) (bvl (cdadr exp)) (body (cddr exp))) (let ((internal-name (concatenate-symbol name '/scheme-macro))) (multiple-value-bind (code trivial?) (translate-sequence body env nil) (ignorable trivial?) (translate-return `(progn 'compile (defmacro ,internal-name ,(ampersandify bvl) ,code) (*define-scheme-macro ',name ',internal-name)) t ret?))))) ;;; Renegade FLUID-LET - like MIT Scheme, but only works on top level ;;; variables! (For obvious reasons.) (define-scheme-special-form fluid-let (exp env ret?) (let ((specs (cadr exp)) (body (cddr exp))) (multiple-value-bind (code trivial?) (translate-sequence body env ret?) (ignorable trivial?) (translate-return `(let ,(mapcar (lambda (spec) `(,(car spec) ,(translate (cadr spec) env nil))) specs) (declare (special ,@(mapcar #'car specs))) ,code) nil ret?)))) ;;; Test routines for translator (define (tst exp) (pretty-print (translate-top-level (schemify exp))) '*) (define (schemify sexpr) ;don't try to use this with backquote forms! (cond ((null? sexpr) sexpr) ((symbol? sexpr) (intern (symbol-name sexpr) scheme-package)) ((pair? sexpr) (cons (schemify (car sexpr)) (schemify (cdr sexpr)))) (else sexpr))) #|(define (show obj) (check-type obj function) (pretty-print (si::undigest (sys::%p-contents-offset obj 1))) '*)|# ;;;----- Scheme macros (defmacro define-scheme-macro ((name . args) &body body) (let ((internal-name (concatenate-symbol name '/scheme-macro))) `(progn 'compile (export ',name clsch-package) (defmacro ,internal-name ,(ampersandify args) ,@body) (*define-scheme-macro ',name ',internal-name)))) (defun *define-scheme-macro (sym fun) (setf (gethash sym special-form-table) (lambda (exp env ret?) (translate (macroexpand-1 (cons fun (cdr exp))) env ret?))) sym) ;;; (Definitions of macros appear in alphabetical order.) (define-scheme-macro (and . forms) ; RS (labels ((expand-and (forms) (cond ((atom? forms) 't) ((atom? (cdr forms)) (car forms)) (else `(if ,(car forms) ,(expand-and (cdr forms)) nil))))) (expand-and forms))) ;;; case ; R ;;; collect ; S (!) (define-scheme-macro (cond . clauses) ;E S (labels ((expand-cond (clauses) (cond ((atom? clauses) ''**no-more-cond-clauses**) ((atom? (car clauses)) (cerror "ignore it" "atomic COND clause: ~s" (car clauses)) (expand-cond (cdr clauses))) ((atom? (cdar clauses)) `(or ,(caar clauses) ,(expand-cond (cdr clauses)))) ((eq? (caar clauses) 'else) (if (not (atom? (cdr clauses))) (cerror "ignore clauses following ELSE clause" "ELSE clause not last in COND: ~s" `(cond ,@clauses))) `(begin ,@(cdar clauses))) (else `(if ,(caar clauses) (begin ,@(cdar clauses)) ,(expand-cond (cdr clauses))))))) (expand-cond clauses))) (define-scheme-macro (cons-stream head tail) ; S `(scheme::cons ,head (delay ,tail))) (define-scheme-macro (do specs end &body body) ; R (let ((loop (gensym "DO"))) `(letrec ((,loop (lambda ,(mapcar #'car specs) (cond ,end (else ,@body (,loop ,@(mapcar (lambda (y) (if (and (cdr y) (cddr y)) (caddr y) (car y))) specs))))))) (,loop ,@(mapcar (lambda (y) (if (cdr y) (cadr y) 'nil)) specs))))) (define-scheme-macro (let specs &body body) ;E S (cond ((and (not (pair? specs)) ;Weird MIT extension (not (null? specs))) (let ((tag specs) (specs (car body)) (body (cdr body))) `(letrec ((,tag (lambda ,(mapcar #'car specs) ,@body))) (,tag ,@(mapcar #'cadr specs))))) (else `((lambda ,(mapcar #'car specs) ,@body) ,@(mapcar (lambda (x) (cond ((atom? (cdr x)) ''**let-missing-initializer**) (else (cadr x)))) specs))))) ;;; let* ; R (define-scheme-macro (letrec specs &body body) ;E (iterate loop ((s specs) (vars '()) (inits '())) (cond ((null? s) `((lambda ,vars ,@(reverse! inits) . ,body) ,@(mapcar (lambda (var) (ignorable var) ''**unbound-label**) vars))) (else (let ((spec (car s))) (cond ((atom? spec) (cerror "ignore it" "bad spec - (LETREC (... ~S ...) ...)" spec) (loop (cdr s) vars inits)) ((atom? (car spec)) (loop (cdr s) (cons (car spec) vars) (cons `(set! ,@spec) inits))) (else (loop (cdr s) (cons (caar spec) vars) (cons `(set! ,(caar spec) (lambda ,(cdar spec) ,@(cdr spec))) inits))))))))) ;;; make-environment ; S (!) (define-scheme-macro (or . args) ; RS (labels ((expand-or (args) (cond ((atom? args) ''nil) ((atom? (cdr args)) (car args)) (else `((lambda (p) (if p p ,(expand-or (cdr args)))) ,(car args)))))) (expand-or args))) (define-scheme-macro (rec var exp) ;E S `(letrec ((,var ,exp)) ,var)) (define-scheme-macro (trace var) `(set! ,var (*trace ,var ',var))) (define-scheme-macro (untrace var) `(set! ,var (*untrace ,var))) ;;; Quasiquote (*define-scheme-macro quasiquote-marker quasiquote-marker) (defmacro quasiquote-marker (x) (expand-quasiquote x 0)) (*define-scheme-macro unquote-marker unquote-marker) (defmacro unquote-marker (x) (cerror "as as if the comma wasn't there at all" "comma not inside backquote form - ,~S" x) x) (*define-scheme-macro splice-marker splice-marker) (defmacro splice-marker (x) (cerror "act as if the ,@ wasn't there at all" "\",@\" not inside backquote form - ,@~S" x) x) (define (expand-quasiquote x level) (multiple-value-bind (mode arg) (descend-quasiquote x level) (finalize-quasiquote mode arg))) (define (finalize-quasiquote mode arg) (cond ((eq? mode 'quote) `',arg) ((eq? mode 'unquote) arg) ((eq? mode 'splice) (cerror "act as if () had been seen instead of ,@