;;; -*- Mode: Lisp; Package: (CLSCH LISP); Syntax: Common-lisp -*- ;;; An implementation of Scheme, less continuations, in Common Lisp. ;;; ***** Initial version - not fully debugged ;;; ***** Please do not redistribute without permission (yet). ;;; ***** This version has significant performance and other problems. ;;; ***** A rewrite is in progress. ;;; ***** This isn't recommended for educational use, since the only ;;; ***** debugging tool is TRACE. There's no backtrace, stack debugger, ;;; ***** or break package. ;;; The dialect here is a union of the features needed for ;;; (a) the code 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 (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 full CALL-WITH-CURRENT-CONTINUATION would be a major ;;; headache. Fortunately, Scheme is still moderately useful without ;;; upwards continuations. ;;; Thanks to John Ramsdell of Mitre Corp. and Eric Benson of Lucid Inc. for ;;; various contributions. ;;; Hacked intermittently from August 1985 to April 86 ;;; by Jonathan Rees, MIT ;;; *** CHANGE LOG *** ;;; Version 184 (4/23/86) ;;; - CASE, 3600 backquote compatibility ;;; Version 181 (4/16/86) ;;; - Curried define, BOOLEAN?, #T and #F, #S (short), #L (long), #D (decimal) ;;; Version 180 (4/7/86) ;;; - 2 tiny fixes (ignore and iterate) ;;; Version 179 (3/10/86) ;;; - Fixed various problems found by Eric Benson and John Ramsdell ;;; Version 174 (2/27/86) ;;; - Tested with query system, CLISP, etc. and fixed a few bugs. ;;; - Microfeatures (%ge env), (lisp), #I and #E, #L = use Lisp syntax. ;;; - Implemented Eric Benson's incredible read table hack, which ;;; unfortunately doesn't work at all on the 3600. Parameterized by ;;; *use-roadblock-method?*. ;;; Version 160 (12/7/85) ;;; - Revamped top level dispatch of TRANSLATE. ;;; - Generic operation system for use internal to the implementation. ;;; - Hacko implementation of MAKE-ENVIRONMENT. ;;; - Extend EXPLODE to work on numbers. ;;; 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. Also the new report is going to fix this. ;;; Another minor flaw (which e.g. makes the explicit control evaluator ;;; from S&ICP fail to run): ;;; Forward references don't work properly with MAKE-ENVIRONMENT, i.e. ;;; if an EVAL in a non-top-level environment refers to something ;;; which isn't defined until a later EVAL, you'll lose. In particular, ;;; the 6.001 register machine simulator can do backwards jumps but not ;;; forward jumps (!). ;;; To do: ;;; - It would be nice if there was an evaluator. Code runs too ;;; slowly if it's not compiled; and debugging is a pain. ;;; - CAR and CDR of () should be error. ;;; - () and #F should be distinct. ;;; - 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) ;;; ********************************************************************** ;;; Package setup, etc. (in-package 'clsch) ;Common Lisp SCHeme ;;; 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)))) (shadow '(help lambda) clsch-package) (export '(help lambda else scheme quote set! begin if delay define-macro fluid-let the-environment) clsch-package) (import '(scheme) (package #-3600 "USER" #+3600 "CL-USER")) (shadow '(define nil t) scheme-package) (proclaim '(special *current-environment*)) (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 macros like COLLECT and DEFINE-MACHINE. - 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.") ;;; Utilities for avoiding compiler warnings (defmacro ignorable (&rest vars) `(progn ,@vars)) ;;; The following may need to be adjusted for particular implementations, ;;; since there isn't really any reliable way to do this. ;;; (Macros can't expand into declarations.) (defmacro ignore (&rest vars) `(progn ,@vars)) (defmacro with-package-and-readtable (pkg rt &body body) #-Symbolics `(let ((*package* ,pkg) (*readtable* ,rt)) ,@body) #+Symbolics `(zl:standard-value-let ((zl:package ,pkg) (zl:readtable ,rt)) ,@body)) ;;;----- 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 (eval load compile) (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)) ;#-Lucid (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. ;#+Lucid ;(defmacro iterate (tag specs &body body) ; `(labels ((,tag ,(mapcar #'car specs) ; ,@body)) ; (,tag ,@(mapcar #'cadr specs)))) (defun symbol? (x) (and x (symbolp x))) (eval-when (eval compile) (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 number? numberp) (defsynonym append! nconc) (defsynonym reverse! nreverse) ) (defconstant else t) ;;; Pretty useless, but the existence of these 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 (eval-when (eval load compile) (defun clsch-symbol (symbol) (cond ((eq (symbol-package symbol) clsch-package) symbol) (t (let ((symbol (intern (symbol-name symbol) scheme-package))) 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) (ignore 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-E/I/S/D-read-macro (stream subchar arg) (ignore subchar arg) (read stream t nil t)) (defun sharp-L-read-macro (stream subchar arg) ;long (ignore subchar arg) (coerce (read stream t nil t) 'double-float)) (defun sharp-T/F-read-macro (stream subchar arg) (ignore arg stream) (let (#+3600(subchar (character subchar))) (ccase subchar ((#\t #\T) t) ((#\f #\F) nil)))) (defun sharp-Q-read-macro (stream subchar arg) ; #Q: lisp syntax (ignore subchar arg) (with-package-and-readtable clsch-package cl-readtable (read stream t nil t))) (defun sharp-sharp-read-macro (stream subchar arg) (cond (arg (funcall (get-dispatch-macro-character #\# #\# cl-readtable) stream subchar arg)) (t '(%out)))) ;;; 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 c) (ignore c) (list quasiquote-marker (read stream t nil t))) (defun unquote-read-macro (stream c) (ignore c) (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 (stream char) (ignore stream) (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) screws up string reading (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) (set-dispatch-macro-character #\# #\E #'sharp-E/I/S/D-read-macro) ;exact (set-dispatch-macro-character #\# #\I #'sharp-E/I/S/D-read-macro) ;inexact (set-dispatch-macro-character #\# #\D #'sharp-E/I/S/D-read-macro) ;decimal (set-dispatch-macro-character #\# #\S #'sharp-E/I/S/D-read-macro) ;short (set-dispatch-macro-character #\# #\L #'sharp-L-read-macro) ;long (set-dispatch-macro-character #\# #\T #'sharp-T/F-read-macro) ;true (set-dispatch-macro-character #\# #\F #'sharp-T/F-read-macro) ;false (set-dispatch-macro-character #\# #\Q #'sharp-Q-read-macro)) ;lisp ;;; Weird primitive object system. For internal use only. ;;; Generics and instances (defstruct (instance (:predicate instance?) (:print-function print-instance))) (defun instance-type (instance) (type-of instance)) (defmacro define-type (foo &body slots) (multiple-value-bind (name options) (if (atom foo) (values foo '()) (values (car foo) (cdr foo))) (multiple-value-bind (parent options) (let ((probe (assoc ':include options))) (if probe (values probe (remove-if (lambda (x) (eq? (car x) ':include)) options)) '(:include instance))) `(defstruct (,name ,parent (:print-function print-instance) (:predicate ,(concatenate-symbol name '?)) ,@options) ,@slots)))) ;;; Operations & discriminators (defmacro define-operation ((name instance-arg . other-args) &body body) `(*define-operation ',name ,(if (null body) nil `(lambda (,instance-arg ,@other-args) ,@body)))) (define-type discriminator name (default-method nil) (methods '())) (defun *define-operation (name default-method) (cond ((or (not (get name 'discriminator)) (not (fboundp name))) (let ((d (make-discriminator :name name))) (if default-method (setf (discriminator-default-method d) default-method)) (setf (symbol-function name) (lambda (instance . args) (apply (let ((probe (assoc (instance-type instance) (discriminator-methods d)))) (if probe (cdr probe) (default-method d))) instance args))) (setf (get name 'discriminator) d))))) (defun default-method (d) (or (discriminator-default-method d) (lambda (&rest args) (error "operation ~s not handled; arguments = ~s" (discriminator-name d) args)))) (define-operation (identification obj) (ignore obj) nil) (define-operation (set-identification obj id) (ignore obj id) nil) (define-operation (object-hash obj) (ignore obj) nil) (define-operation (print-instance obj stream guk) (ignore guk) (print-with-braces obj stream (string-capitalize (symbol-name (type-of obj))))) (defun print-with-braces (obj stream type-string &rest stuff) (format stream "#{~A" type-string) (let ((probe (identification obj))) (cond (probe (write-char #\space stream) (scheme::write probe stream)) (else (let ((probe (object-hash obj))) (cond (probe (write-char #\space stream) (scheme::write probe stream))))))) (mapc (lambda (x) (write-char #\space stream) (scheme::write x stream)) stuff) (format stream "}")) ;;; Methods (defmacro define-method ((name instance-arg . other-args) &body body) (cond ((consp instance-arg) (let ((instance-arg (car instance-arg)) (instance-type (cadr instance-arg))) `(progn 'compile (define-operation (,name ,instance-arg ,@other-args)) (*define-method ',name ',instance-type (lambda (,instance-arg ,@other-args) ,@body))))) (t `(define-operation (,name ,instance-arg ,@other-args) ,@body)))) (defun *define-method (operation instance-type method) (let ((d (get operation 'discriminator))) (let ((probe (assoc instance-type (discriminator-methods d)))) (if probe (setf (cdr probe) method) (push (cons instance-type method) (discriminator-methods d))))) operation) (define-method (identification (d discriminator)) (discriminator-name d)) ;;;----- 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. ;;; The top-level form translator returns a Common Lisp LAMBDA ;;; expression. To evaluate the original expression in an environment ;;; E, just FUNCALL this LAMBDA-expression passing it E. (define (translate-top-level exp env) (multiple-value-bind (type x exp) (determine-syntactic-type exp) (ignore x) `(lisp:lambda (%%the-environment%%) %%the-environment%% ;OK if it's ignored ,(cond ((eq? type 'definition) (translate-top-level-sequence (list exp) env)) ((eq? type 'begin) (translate-top-level-sequence (cdr exp) env)) (else (translate exp env nil)))))) (define (translate-top-level-sequence body env) (multiple-value-bind (new-body new-vars) (scan-defines body env) (multiple-value-bind (code trivial?) (translate-sequence new-body env nil) (ignore trivial?) (if new-vars `(progn (prepare-to-define! %%the-environment%% ',new-vars) ,code) code)))) ;;; 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?) (multiple-value-bind (type translator exp) (determine-syntactic-type exp) (ignore type) (funcall translator exp env ret?))) (defvar syntax-table) (setq syntax-table (make-hash-table :test #'eq)) ;;; Returns 3 values: the type (symbol), translator (function), and ;;; a possibly updated version of the expression. (define (determine-syntactic-type exp) (cond ((pair? exp) (cond ((not (symbol? (car exp))) (values 'combination #'translate-combination exp)) (else (let ((probe (gethash (car exp) syntax-table))) (cond ((not probe) (values 'combination #'translate-combination exp)) ((eq? (car probe) 'macro) (determine-syntactic-type (expand-macro-form (cdr probe) exp))) (else (let ((type (car probe))) (cond ((eq? type 'set!) (warn-if-redefining (cadr exp) "assigning")) ((eq? type 'definition) (warn-if-redefining (cadr exp) "redefining"))) (values type (cdr probe) exp)))))))) ((symbol? exp) (values 'variable #'translate-variable exp)) (else (values 'quote #'translate-quote `(quote ,exp))))) (defmacro define-scheme-syntax (name args &body body) ;; Symbolics CL sucks. Anonymous functions don't get compiled! (let ((aux-name (concatenate-symbol 'translate- name))) `(progn 'compile (defun ,aux-name ,args ,@args ,@body) (*define-scheme-syntax ',name ',name #',aux-name)))) (define (*define-scheme-syntax name type info) (setf (gethash name syntax-table) (cons type info)) name) ;;; QUOTE (define-scheme-syntax quote (exp env ret?) ;E S (translate-return `(quote ,(cadr exp)) t ret?)) ;;; VARIABLE (define-scheme-syntax variable (exp env ret?) (translate-return (translate-reference env exp) t ret?)) ;;; SET! (define-scheme-syntax set! (exp env ret?) ;E S (let ((var (cadr exp))) (multiple-value-bind (type translator val-exp) (determine-syntactic-type (caddr exp)) (multiple-value-bind (code trivial?) (cond ((eq? type 'lambda) (really-translate-lambda val-exp env nil var)) (t (funcall translator val-exp env nil))) (translate-return (translate-assignment env var code) trivial? ret?))))) ;;; BEGIN (define-scheme-syntax begin (exp env ret?) ;E (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?))))))))) ;;; DEFINITION (define-scheme-syntax definition (exp env ret?) (cerror "compile a SET! in place of the DEFINE" "DEFINE in illegal context - ~s" (cons 'define (cdr exp))) (translate `(set! ,@(cdr exp)) env ret?)) ;;; IF (define-scheme-syntax 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-syntax lambda (exp env ret?) ;E S (really-translate-lambda exp env ret? nil)) (defun really-translate-lambda (exp env ret? name) (multiple-value-bind (code trivial?) (translate-lambda-body (cadr exp) (cddr exp) env t) (ignore trivial?) (translate-return `(create-procedure (lambda (nargs) ;; Eventually, check number of args. (ignore nargs) (let ,(bind-arguments (cadr exp)) ,code)) ',name) 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 arg-vars body env ret?) (let ((env (extend-environment arg-vars env))) (multiple-value-bind (new-body new-vars) (scan-defines body env) (multiple-value-bind (code trivial?) (translate-sequence new-body env ret?) (values (if (null? new-vars) code `(let ,(mapcar (lambda (var) `(,var '*undefined*)) new-vars) ,code)) trivial?))))) (define (scan-defines body env) ;Side-affects env (iterate loop ((b body) (vars '()) (z '())) (cond ((null? b) (values (reverse! z) (reverse! vars))) (else (multiple-value-bind (type x exp) (determine-syntactic-type (car b)) (ignore x) (cond ((eq? type 'definition) (let ((name (cadr exp)) (val (caddr exp))) (process-definition! env name) (loop (cdr b) (cons name vars) (cons `(set! ,name ,val) z)))) (else (loop (cdr b) vars (cons exp z))))))))) ;;; COMBINATION (define-scheme-syntax combination (exp env ret?) (let ((proc-exp (car exp)) (arg-exps (cdr exp))) (multiple-value-bind (type x proc-exp) (determine-syntactic-type proc-exp) (ignore x) (cond ((eq? type 'variable) (let ((probe (get proc-exp 'trivial))) (cond (probe (translate-trivial-call probe arg-exps env t ret?)) (t (translate-nontrivial-call proc-exp arg-exps env ret?))))) ((eq? type 'lambda) ;; LAMBDA-combinations are trivial. (multiple-value-bind (code trivial?) (translate-lambda-body (cadr proc-exp) (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))))) ;;; Utilities (define (warn-if-redefining name what) (cond ((get name 'trivial) (with-package-and-readtable scheme-package scheme-readtable (format *error-output* "~&Warning: ~A ~S, which is a built-in procedure.~&" what name)) (remprop name 'trivial)))) ;;; This is how to actually deliver the object code for some expression. (define (translate-return exp trivial? ret?) (ignore ret?) (values exp trivial?)) ;;;; Nonstandard special forms ;;; DELAY ;;; Could be a macro, but this is a speed hack. (define-scheme-syntax delay (exp env ret?) ; S (translate-return `(make-delay :thunk-or-value #'(lisp:lambda () ,(translate (cadr exp) env nil))) t ret?)) ;;; DEFINE-MACRO - sort of compatible with MIT Scheme (define-scheme-syntax 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) (ignore trivial?) (translate-return `(progn 'compile (defmacro ,internal-name ,(ampersandify bvl) ,code) (*define-scheme-macro ',name ',internal-name)) t ret?))))) ;;; FLUID-LET - like MIT Scheme, but only works on top level ;;; variables, at least for now. (define-scheme-syntax fluid-let (exp env ret?) (let ((specs (cadr exp)) (body (cddr exp))) (multiple-value-bind (code trivial?) (translate-sequence body env ret?) (ignore trivial?) (translate-return `(let ,(mapcar (lambda (spec) `(,(car spec) ,(translate (cadr spec) env nil))) specs) (declare (special ,@(mapcar #'car specs))) ,code) nil ret?)))) ;;; THE-ENVIRONMENT (define-scheme-syntax the-environment (exp env ret?) ; feature (translate-return (translate-environment env) t ret?)) ;;; EXPRESSION-QUOTE (define-scheme-syntax expression-quote (exp env ret?) ; feature (ignore env) (translate-return `'(begin ,@(cdr exp)) t ret?)) ;;; Compile-time environments ;;; Same data structure is used for run-time environments. Beware! (define-type package-environment name package) (define-method (identification (env package-environment)) (package-environment-name env)) (define-method (translate-reference (env package-environment) sym) (let ((sp (symbol-package sym)) (ep (package-environment-package env))) (cond ((or (eq? sp ep) (eq? sp clsch-package)) ;kludge sym) (else (values (intern (symbol-name sym) ep)))))) (define-method (translate-assignment (env package-environment) sym code) (let ((sym (translate-reference env sym))) `(setq-global-value ,sym ,code))) (define-method (process-definition! (env package-environment) sym) (ignore env sym) nil) (define-method (translate-environment (env package-environment)) (ignore env) '%%the-environment%%) ;;; Contour (exists only during compilation) (define-type contour variables parent) (define (extend-environment vars env) (make-contour :parent env :variables vars)) (define (belongs-to-contour? var env) (iterate loop ((vars (contour-variables env))) (cond ((atom? vars) (eq? var vars)) ((eq? var (car vars)) t) (else (loop (cdr vars)))))) (define-method (translate-reference (env contour) var) (if (belongs-to-contour? var env) var (translate-reference (contour-parent env) var))) (define-method (translate-assignment (env contour) var code) (if (belongs-to-contour? var env) `(setq ,var ,code) (translate-assignment (contour-parent env) var code))) (define-method (process-definition! (env contour) var) (push var (contour-variables env))) (define-method (translate-environment (env contour)) (do ((env env (contour-parent env)) (vars (contour-variables env) (append (contour-variables env) vars))) ((not (contour? env)) `(*the-environment %%the-environment%% ',vars (list ,@vars))))) ;;; Test routines for translator #|(define (tst exp) (pp (translate-top-level exp *current-environment*)) '*) (define (show obj) (check-type obj procedure) (pp (si::undigest (sys::%p-contents-offset (procedure-function obj) 1))) '*)|# ;;;----- Scheme macros (defmacro define-scheme-macro ((name . args) &body body) (let ((aux-name (concatenate-symbol name '/scheme-macro))) `(progn 'compile (export ',name clsch-package) (defmacro ,aux-name ,(ampersandify args) ,@body) (*define-scheme-macro ',name ',aux-name)))) (define (expand-macro-form aux-name exp) (macroexpand-1 (cons aux-name (cdr exp)))) (defun *define-scheme-macro (sym mac) (*define-scheme-syntax sym 'macro mac)) ;;; (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 (define-scheme-macro (case key . clauses) (let ((var '%%key%%)) `(let ((,var ,key)) (cond ,@(mapcar (lambda (clause) `(,(cond ((eq (car clause) 'else) 'else) ((null (car clause)) `nil) ((null (cdr (car clause))) `(eqv? ,var ',(caar clause))) (t `(memv ,var ',(car clause)))) ,@(cdr clause))) clauses))))) ;;; 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))) (defmacro define/scheme-macro (pat &body body) ; E (labels ((z (pat body) (cond ((pair? pat) (z (car pat) `((lambda ,(cdr pat) ,@body)))) (else `(definition ,pat ,@body))))) (z pat body))) (*define-scheme-macro 'scheme::define 'define/scheme-macro) (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) (ignore 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))))))))) (define-scheme-macro (make-environment . body) `(*make-environment (the-environment) (expression-quote ,@body))) (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 (sequence . body) ; RS `(begin ,@body)) (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 "act 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 ,@