;;; -*- 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 ,@
" ",@ in illegal context - ,@~s" arg)) (else `(,mode ,@arg)))) ;;; The two return values, mode and arg, are interpreted as follows: ;;; mode arg meaning ;;; QUOTE x 'x ;;; UNQUOTE x x ;;; LIST (x1 x2 ...) (LIST x1 x2 ...) ;;; CONS* (x1 x2 ...) (CONS* x1 x2 ...) ;;; APPEND (x1 x2 ...) (APPEND x1 x2 ...) (define (descend-quasiquote x level) (cond ((vector? x) (descend-quasiquote-vector x level)) ((atom? x) (values 'quote x)) ((interesting-to-quasiquote? x quasiquote-marker) (descend-quasiquote-pair x (1+ level))) ((interesting-to-quasiquote? x unquote-marker) (cond ((= level 0) (values 'unquote (cadr x))) (else ;; BUG: ,,@ doesn't work. I think this is the spot ;; where it would have to be hacked in. (descend-quasiquote-pair x (- level 1))))) ((interesting-to-quasiquote? x splice-marker) (cond ((= level 0) (values 'splice (cadr x))) (else (descend-quasiquote-pair x (- level 1))))) (else (descend-quasiquote-pair x level)))) (define (descend-quasiquote-pair x level) (multiple-value-bind (car-mode car-arg) (descend-quasiquote (car x) level) (multiple-value-bind (cdr-mode cdr-arg) (descend-quasiquote (cdr x) level) (cond ((and (eq? car-mode 'quote) (eq? cdr-mode 'quote)) (values 'quote x)) ((eq? car-mode 'splice) ;; (,@mumble ...) (cond ((and (eq? cdr-mode 'quote) (null? cdr-arg)) (values 'unquote car-arg)) ((eq? cdr-mode 'scheme::append) (values 'scheme::append (cons car-arg cdr-arg))) (else (values 'scheme::append (list car-arg (finalize-quasiquote cdr-mode cdr-arg)))))) ((and (eq? cdr-mode 'quote) (null? cdr-arg)) (values 'scheme::list (list (finalize-quasiquote car-mode car-arg)))) ((or (eq? cdr-mode 'scheme::list) (eq? cdr-mode 'cons*)) (values cdr-mode (cons (finalize-quasiquote car-mode car-arg) cdr-arg))) (else (values 'cons* (list (finalize-quasiquote car-mode car-arg) (finalize-quasiquote cdr-mode cdr-arg)))))))) ;;; #(a b c) ==> '#(a b c) ;;; #(a ,b c) ==> (vector 'a b 'c) ;;; #(a ,@b ,c) ==> (list->vector (append '(a) b (list c))) ;;; Isn't there some elegant way to do this? (define (descend-quasiquote-vector x level) (iterate loop ((i (- (vector-length x) 1)) (modes '()) (args '()) (flag nil)) (cond ((>= i 0) (multiple-value-bind (mode arg) (descend-quasiquote (vector-ref x i) level) ;; mode = quote, unquote, splice, list, cons* (loop (- i 1) (cons mode modes) (cons arg args) (or flag (not (eq? mode 'quote)))))) ((not flag) (values x 'quote)) ((memq 'splice modes) (values 'list->vector (list (cons 'append (mapcar (lambda (mode arg) (cond ((eq? mode 'splice) arg) ;; Not good. ((eq? mode 'quote) (list 'quote (list arg))) (else (list 'list (finalize-quasiquote mode arg))))) modes args))))) (else (values 'vector (mapcar #'finalize-quasiquote modes args)))))) (define (interesting-to-quasiquote? x marker) (and (pair? x) (eq? (car x) marker) (pair? (cdr x)) (null? (cddr x)))) ;;;----- Runtime system (define-type procedure function name) (defun create-procedure (fun name) (make-procedure :function fun :name name)) (define-method (identification (p procedure)) (let ((n (procedure-name p))) (if (not (number? n)) n nil))) (defvar *procedure-uid-counter* 0) (define-method (object-hash (p procedure)) (let ((n (procedure-name p))) (cond ((number? n) n) ((null? n) (setf (procedure-name p) (incf *procedure-uid-counter*)))))) (define-method (set-identification (obj procedure) name) (if (not (symbol? (procedure-name obj))) (setf (procedure-name obj) name))) (defun driver-loop (nargs) (prog (val) loop (multiple-value-setq (val nargs) (funcall (procedure-function **p**) nargs)) (if (not nargs) (return val)) (go loop))) (defun get-n-ary-arg (nargs n) (do ((i n (1+ i)) (l (nthcdr n *argument-registers*) (cdr l)) (z '() (cons (symbol-value (car l)) z))) ((= i nargs) (nreverse z)) (declare (fixnum i)))) (defun function->procedure (fun name) (create-procedure (lambda (nargs) (apply fun (get-n-ary-arg nargs 0))) ;Suboptimal name)) (defun procedure->function (proc) (lambda args (driver-loop (set-registers-from-list proc args)))) ;;; Returns number of args. (defun set-registers-from-list (proc args) (let ((nargs (length args))) (ensure-enough-arg-regs nargs) (mapc (lambda (arg-reg arg) (setf (symbol-value arg-reg) arg)) *argument-registers* args) (setf **p** proc) nargs)) (defun scheme-call (proc &rest args) (driver-loop (set-registers-from-list proc args))) ;;; Macros for defining scheme procedures ;;; A nontrivial procedure is one which potentially modifies the ;;; registers. (defmacro define-scheme-nontrivial ((name . bvl) &body body) (let ((scheme-name (clsch-symbol name))) `(progn 'compile (clsch-export ',scheme-name) (defun ,scheme-name ,(ampersandify bvl) ,@body) (setq-global-value ,scheme-name (function->procedure #',scheme-name ',scheme-name))))) (defmacro define-scheme (pat &body body) (cond ((consp pat) (let ((scheme-name (clsch-symbol (car pat)))) `(progn 'compile (define-scheme-nontrivial ,pat ,@body) (setf (get ',scheme-name 'trivial) ',scheme-name)))) (t (let ((scheme-name (clsch-symbol pat))) `(progn 'compile (clsch-export ',scheme-name) (setq-global-value ,scheme-name ,(car body))))))) (define-scheme-nontrivial (eval exp env) (funcall (translate-top-level exp env) env)) ;;; Environment manipulation (define-method (*access (env package-environment) name) (symbol-value (translate-reference env name))) (define-method (*assign! (env package-environment) name value) (set-symbol-value-etc (translate-reference env name) value) name) (defun set-symbol-value-etc (name value) (setf (symbol-value name) value) (set-identification value name) ;; Set function cell, but avoid clobbering things like WRITE and LAMBDA. (if (and (procedure? value) (eq (symbol-package name) scheme-package) (not (get name 'trivial))) (setf (symbol-function name) (procedure->function value))) #+Symbolics (si:record-source-file-name name 'scheme::define)) ; JDR Hack. (define-method (prepare-to-define! (env package-environment) names) (ignore names env) nil) ;;; First-class (but somewhat broken) environments (define-type environment parent variables values mutable?) (defun really-make-environment (parent names vals mutable?) (make-environment :parent parent :variables names :values vals :mutable? mutable?)) (define-scheme-nontrivial (*make-environment parent code) (let ((env (really-make-environment parent '() '() t))) (scheme::eval code env) env)) (defun *the-environment (parent names vals) (really-make-environment parent names vals nil)) (define-method (*access (env environment) name) (iterate loop ((vars (environment-variables env)) (vals (environment-values env))) (cond ((atom? vars) (if (eq? name vars) vals (*access (environment-parent env) name))) ((eq? name (car vars)) (car vals)) (else (loop (cdr vars) (cdr vals)))))) (define-method (*assign! (env environment) name val) (iterate loop ((vars (environment-variables env)) (vals (environment-values env))) (cond ((endp vars) (*assign! (environment-parent env) name val)) ((eq? name (car vars)) (setf (car vals) val) name) (else (loop (cdr vars) (cdr vals)))))) (define-method (prepare-to-define! (env environment) names) (mapc (lambda (name) (process-definition! env name)) names)) ;;; Shadowing loses, but you don't need it (define-method (translate-reference (env environment) var) (cond ((member var (environment-variables env)) `(*access %%the-environment%% ',var)) (else (translate-reference (environment-parent env) var)))) (define-method (translate-assignment (env environment) var code) (cond ((member var (environment-variables env)) `(*assign! %%the-environment%% ',var ,code)) (else (translate-assignment (environment-parent env) var code)))) (define-method (process-definition! (env environment) var) (cond ((not (environment-mutable? env)) (cerror "will treat the DEFINE as a SET!" "ouch! This environment isn't mutable! - (DEFINE ~s ...)" var)) ((not (member var (environment-variables env))) (push var (environment-variables env)) (push '*undefined* (environment-values env))))) (define-method (translate-environment (env environment)) (ignore env) '%%the-environment%%) ;;;----- LOAD/COMPILE subsystem (defvar *scheme-object-file-type* "SBIN") (defvar *scheme-source-file-type* "SCM") ;;; READ-SCHEME-FILE returns two values: ;;; 1. A list of source expressions ;;; 2. The truename of the file it openend (defun read-scheme-file (name) (with-open-file (s (scheme-source-pathname name) :direction :input) (let ((true (truename s))) (format t "~&Reading ~S~%" (namestring true)) (values (read-scheme-forms s) true)))) (defun read-scheme-forms (s) (with-package-and-readtable scheme-package scheme-readtable (iterate loop ((z '())) (let ((form (read s nil '*eof-object*))) (cond ((eq form '*eof-object*) (nreverse z)) (t (loop (cons form z)))))))) ;;; Like READ-SCHEME-FILE, but translates as well. (defun translate-scheme-file (name) (multiple-value-bind (code true-name) (read-scheme-file name) (format t "~&Translating ~S~%" (namestring true-name)) (values (translate-scheme-forms code) true-name))) ;;; Turn a list of scheme forms (the contents of a file) into an expression ;;; suitable for a top level expression in a common lisp file (something ;;; that LOAD or COMPILE-FILE might like to see). (defun translate-scheme-forms (forms) `(progn 'compile (defun run-top-level-forms (%%the-environment%%) %%the-environment%% ;; Avoid function redefinition message. (fmakunbound 'run-top-level-forms) ;; Flush any information compiler or LOAD might have stored. (setf (symbol-plist 'run-top-level-forms) '()) ,(translate-top-level-sequence forms *current-environment*)) (run-top-level-forms *current-environment*))) ;;; Eric Benson's clever kludge. ;;; It won't work on the 3600, but might be worth trying elsewhere. (defvar *use-roadblock-method?* nil) (defvar roadblock-readtable (copy-readtable nil)) (defun roadblock-read-macro (stream ch) (unread-char ch stream) (translate-scheme-forms (read-scheme-forms stream))) (let ((*readtable* roadblock-readtable)) (do ((i 0 (1+ i)) (stop (min 256. char-code-limit))) ((>= i stop) 'done) (let ((c (code-char i))) (if (or (graphic-char-p c) (member c '(#\page #\tab #\newline #\linefeed #\return))) (set-macro-character c #'roadblock-read-macro nil roadblock-readtable))))) ;;; Smart LOAD routine. (define-scheme-nontrivial (load name &optional (compile? nil)) ;E (let ((name (pathname name))) (cond ((scheme-object-file? name) (load-scheme-object-file name)) ((member (pathname-type name) '(nil :unspecific)) (let* ((src-name (scheme-source-pathname name)) (obj-name (scheme-object-pathname name)) (src (probe-file src-name)) (obj (probe-file obj-name))) (cond ((not obj) (load-scheme-source-file (or src src-name) compile?)) ((not src) (load-scheme-object-file obj)) (t (let ((src-version (pathname-version src)) (obj-version (pathname-version obj))) (cond ((or (not (numberp obj-version)) (not (numberp src-version)) (>= obj-version src-version)) (load-scheme-object-file obj)) (t (format t "~&Source file is newer, loading source file~%") (load-scheme-source-file src compile?)))))))) (t (load-scheme-source-file name compile?))))) (defun scheme-object-file? (name) (let ((type (pathname-type name))) (and (or (stringp type) #+Symbolics (zl:stringp type)) (string-equal type *scheme-object-file-type*)))) (defun unspecify (name) (make-pathname :defaults name :type *scheme-source-file-type* ; - ?? loses on ITS :version nil)) (defun load-scheme-object-file (name) (with-package-and-readtable scheme-package scheme-readtable (load name)) name) (defun load-scheme-source-file (name compile?) (cond (*use-roadblock-method?* (with-package-and-readtable scheme-package roadblock-readtable (load name))) (t (let (#+Symbolics (si:fdefine-file-pathname (unspecify name))) ;JDR hack. (multiple-value-bind (code name) (translate-scheme-file name) (format t "~&Loading ~S~%" (namestring name)) (let ((fun `(lisp:lambda (%%the-environment%%) %%the-environment%% ,code))) (funcall (cond (compile? (let (#+Symbolics (compiler:suppress-compiler-warnings t)) (compile nil fun))) (t fun)) ;LAMBDA-expressions are funcallable *current-environment*) name)))))) ;;; File compiler. (define-scheme-nontrivial (compile-file scheme-pathname &optional keep-lisp-file?) (cond (*use-roadblock-method?* (let ((name (probe-file (scheme-source-pathname scheme-pathname)))) (cond (name #-Symbolics (let ((*readtable* roadblock-readtable)) (compile-file-aux name)) #+Symbolics (zl:standard-value-let ((zl:readtable roadblock-readtable)) (compile-file-aux name))) (t (cerror "skip compiling this file" "file not found - (COMPILE-FILE ~s)" scheme-pathname))))) (t (multiple-value-bind (form name) (translate-scheme-file scheme-pathname) (format t "~&Compiling ~S~%" (namestring name)) (let (#+Symbolics (form `(progn 'compile (setq si:fdefine-file-pathname ;; parse-pathname ?? (parse-namestring ',(namestring (unspecify name)))) ,form)) (lisp-pathname (make-pathname :type "TEMP" :defaults name))) (unwind-protect (with-package-and-readtable scheme-package cl-readtable (with-open-file (out lisp-pathname :direction :out) (format out ";-*-Package:SCHEME-*-~%") (print '(in-package "SCHEME") out) (if keep-lisp-file? (pp form out) (print form out))) (compile-file-aux lisp-pathname)) (if (not keep-lisp-file?) (delete-file lisp-pathname)))))))) (defun compile-file-aux (name) (let (#+Symbolics (compiler:suppress-compiler-warnings t) (fasl-pathname (scheme-object-pathname name))) (compile-file name :output-file fasl-pathname) fasl-pathname)) ;;; Pathname hacking auxiliaries. (defun scheme-source-pathname (name) (let* ((pathname (pathname name)) (type (pathname-type pathname)) (type (cond ((member (pathname-type pathname) '(nil :unspecific)) *scheme-source-file-type*) (t type))) (version (or (pathname-version pathname) :newest))) (merge-pathnames ;JDR hack (make-pathname :defaults pathname :type type :version version)))) (defun scheme-object-pathname (name) (merge-pathnames ;JDR hack (make-pathname :type *scheme-object-file-type* :defaults name))) #+Symbolics (let ((type (zl:string *scheme-source-file-type*))) (cond ((not (member type fs:*its-uninteresting-types* :test #'equal)) (push type fs:*its-uninteresting-types*)))) #+Symbolics (fs:define-canonical-type :scheme *scheme-source-file-type*) ;Scheme source #+Symbolics (fs:define-canonical-type :scheme-bin *scheme-object-file-type*) ;Scheme binary ;;; Default mode for scheme source is lisp. #+Symbolics (unless (assoc :scheme fs:*file-type-mode-alist*) (push (cons :scheme :lisp) fs:*file-type-mode-alist*)) #+Symbolics (push scheme-package si:*reasonable-packages*) ;pushnew ? #+Symbolics (push scheme-readtable si:*valid-readtables*) #+Symbolics (push roadblock-readtable si:*valid-readtables*) ;;; Hack to allow loading scheme code as lisp code. ;;; Also makes indenting work right in ZMACS. (defmacro scheme::define (pat &body body) `(,(translate-top-level `(scheme::define ,pat ,@body) *current-environment*) *current-environment*)) ;;;----- Definitions for various Scheme procedures ;;; In alphabetical order (define-scheme-nontrivial (apply proc . rest) ;E S (values nil (set-registers-from-list proc (apply #'list* rest)))) (define-scheme (assq obj list) ;E S (assoc obj list :test #'eq)) (define-scheme (assoc obj list) ;E (assoc obj list :test #'equal)) (define-scheme (boolean? obj) ;new (or (eq obj t) (eq obj nil))) ;;; Note that in a true Scheme, the escape procedure would have ;;; indefinite extent. (define-scheme-nontrivial (call-with-current-continuation proc) ;E (!!) (block cwcc (scheme-call proc (function->procedure (lambda (val) (return-from cwcc val)) 'scheme::continuation)))) (define-scheme-nontrivial (call-with-input-file string proc) ;E (with-open-file (port string :direction :input) (scheme-call proc port))) (define-scheme-nontrivial (call-with-output-file string proc) ;E (with-open-file (port string :direction :output) (scheme-call proc port))) (define-scheme (ceiling num) ; R (values (ceiling num 1))) (define-scheme (char-whitespace? char) ; R (or (char= char #\space) (not (graphic-char-p char)))) (define-scheme (current-input-port) ;E *standard-input*) (define-scheme (current-output-port) ;E *standard-output*) (define-scheme (display obj &optional (port *standard-output*)) (fluid-let ((*print-escape* nil)) (scheme::write obj port))) ;E (define-scheme (eof-object? obj) ;E (eq obj '*eof-object*)) (define-scheme-nontrivial (error . items) ; S (apply #'error (apply #'concatenate 'string "~a" (mapcar (lambda (item) (ignore item) "~%~10t~s") (cdr items))) items)) (define-scheme (exact? num) ;E (ignore num) nil) (define-scheme (exit . vals) ; T (throw 'exit-scheme (values-list vals))) (define-scheme (explode atom) ; S (map 'list (lambda (ch) (intern (string ch) scheme-package)) (if (symbol? atom) (symbol-name atom) (write-to-string atom :escape nil)))) (define-scheme (floor num) ; R (values (floor num 1))) (define-scheme-nontrivial (for-each proc . lists) ;E (apply #'mapc (procedure->function proc) lists)) (define-scheme mapc ; S (locally (declare (special for-each)) for-each)) (define-type delay (forced-yet? nil) thunk-or-value) (define-method (print-instance (obj delay) stream guk) (ignore guk) (if (delay-forced-yet? obj) (print-with-braces obj stream "Forced" (delay-thunk-or-value obj)) (print-with-braces obj stream "Delayed"))) (define-scheme-nontrivial (force obj) ; S (cond ((delay? obj) (let ((tv (delay-thunk-or-value obj))) (cond ((delay-forced-yet? obj) tv) (t (let ((val (funcall tv))) (setf (delay-thunk-or-value obj) val) (setf (delay-forced-yet? obj) t) val))))) (t obj))) (define-scheme (implode list) ; S (values (intern (map 'string #'character list) scheme-package))) (define-scheme (inexact? num) ;E (ignore num) t) (define-scheme (input-port? obj) ;E (and (streamp obj) (input-stream-p obj))) (define-scheme (lisp) (with-package-and-readtable clsch-package cl-readtable (break "Lisp"))) (define-scheme (list->string list) ;E (coerce list 'string)) (define-scheme (list->vector list) ;E (coerce list 'vector)) (define-scheme (list-ref list n) ; R (nth n list)) (define-scheme (list-tail list n) ; R (nthcdr n list)) (define-scheme (make-table &optional (name nil)) ; T (ignore name) (values (make-hash-table))) (define-scheme (make-vector size ;E S &optional (fill '*uninitialized-vector-element*)) (make-sequence 'vector size :initial-element fill)) (define-scheme-nontrivial (map proc . lists) ;E (apply #'mapcar (procedure->function proc) lists)) (define-scheme mapcar ; S (locally (declare (special scheme::map)) scheme::map)) (define-scheme (memq obj list) ;E S (member obj list :test #'eq)) (define-scheme (member obj list) ;E (member obj list :test #'equal)) (define-scheme nil nil) ; RS (define-scheme (number->string num format) (if (not (equal? format '(scheme::heur))) (cerror "act as if the format was (HEUR)" "unimplemented format: (NUMBER->STRING '~s '~s)" num format)) (write-to-string num)) (define-scheme (output-port? obj) ;E (and (streamp obj) (output-stream-p obj))) (define-scheme (quotient n1 n2) ;E S (values (truncate n1 n2))) (define-scheme (read &optional (port *standard-input*)) ;E (read port nil '*eof-object*)) (define-scheme (read-char &optional (port *standard-input*)) ;E (read-char port nil '*eof-object*)) (define-scheme (real? obj) ;E (and (numberp obj) (not (complexp obj)))) (define-scheme (reset) ; feature (throw 'reset-scheme nil)) (define-scheme (round num) ; R (values (round num 1))) (define-scheme (set-car! pair obj) ;E (setf (car pair) obj)) (define-scheme (set-cdr! pair obj) ;E (setf (cdr pair) obj)) (define-scheme (string->list string) ;E (coerce string 'list)) (define-scheme (string->symbol string) ;E (values (intern string scheme-package))) (define-scheme (string-null? string) ;E (= (length string) 0)) ;;; Query system uses this (define-scheme (pp obj &optional (port *standard-output*)) ; M (fluid-let ((*print-pretty* t)) (print obj port) t)) (define-scheme (put sym ind val) ; S ! (setf (get sym ind) val)) (define-scheme (set-table-entry! table key val) ; T (sort of) (setf (gethash key table) val)) (define-scheme (string-append . strings) ;E (apply #'concatenate 'string strings)) (define-scheme t t) ; RS (define-scheme (table-entry table key) ; T (gethash key table nil)) (define-scheme-nontrivial (tail stream) ; S (let ((d (force (cdr stream)))) (setf (cdr stream) d) d)) (define-scheme the-empty-stream '()) ; S (define-scheme (truncate num) ; R (values (truncate num 1))) (define-scheme (vector? obj) ;E (and (simple-vector-p obj) ;; Strings are simple vectors in CLISP (this is a bug) #+tops-20 (not (stringp obj)) ;; Structures are vectors in CLISP and bolixlisp #+(or tops-20 symbolics) (not (typep obj 'structure)))) (define-scheme (vector->list vec) ;E (coerce vec 'list)) (define-scheme (vector-set! vec k obj) ;E S (setf (svref vec k) obj)) (define-scheme-nontrivial (with-input-from-file string thunk) ; R (with-open-file (*standard-input* string :direction :input) (scheme-call thunk))) (define-scheme-nontrivial (with-output-to-file string thunk) ; R (with-open-file (*standard-output* string :direction :output) (scheme-call thunk))) (define-scheme user-initial-environment ; S (make-package-environment :package scheme-package :name 'user-initial-environment)) (define-scheme clsch-environment ; feature (make-package-environment :package clsch-package :name 'clsch-environment)) (define-scheme (write obj &optional (port *standard-output*)) ;E (fluid-let ((*standard-output* port)) (really-write obj))) (define (really-write obj) (cond ((null? obj) (format *standard-output* "()")) ((pair? obj) (write-list obj)) ((eq? obj t) (format *standard-output* "#!TRUE")) ((eq? obj quasiquote-marker) (format *standard-output* "#!QUASIQUOTE")) ((eq? obj unquote-marker) (format *standard-output* "#!UNQUOTE")) ((eq? obj splice-marker) (format *standard-output* "#!SPLICE")) ((vector? obj) (write-vector obj)) (else (write obj)))) (define (write-list obj) (cond ((and *print-level* (<= *print-level* 0)) (write-string "#")) ((and (symbol? (car obj)) (get (car obj) 'printer)) (funcall (get (car obj) 'printer) obj)) (else (write-list-normally obj)))) (define (write-list-normally obj) (write-char #\() (fluid-let ((*print-level* (and *print-level* (1- *print-level*)))) (really-write (car obj)) (iterate loop ((l (cdr obj)) (n 1)) (cond ((atom? l) (cond ((not (null? l)) (write-char #\space) (write-char #\.) (write-char #\space) (really-write l)))) (else (write-char #\space) (cond ((and *print-length* (>= n *print-length*)) (write-string "...")) (else (really-write (car l)) (loop (cdr l) (1+ n)))))))) (write-char #\)) t) (define (write-vector obj) (write-char #\#) (write-char #\() (let ((z (length obj))) (cond ((> z 0) (really-write (svref obj 0)) (iterate loop ((i 1)) (cond ((>= i z)) (else (write-char #\space) (cond ((and *print-length* (>= i *print-length*)) (write-string "...")) (else (really-write (svref obj i)) (loop (1+ i)))))))))) (write-char #\)) t) ;;; Stuff for making printers understand Scheme backquote (define (make-macro-char-printer prefix) (lambda (obj) (cond ((or (not (pair? (cdr obj))) (not (null? (cddr obj)))) (write-list-normally obj)) (else (write-string prefix) (really-write (cadr obj)))))) (setf (get 'quote 'printer) (make-macro-char-printer "'")) (setf (get quasiquote-marker 'printer) (make-macro-char-printer "`")) (setf (get unquote-marker 'printer) (make-macro-char-printer ",")) (setf (get splice-marker 'printer) (make-macro-char-printer ",@")) #+Symbolics (progn 'compile ;Until a pretty-printer exists (setf (get quasiquote-marker 'gprint::formatter) (lambda (x) (gprint::format-quote "`" x))) (setf (get unquote-marker 'gprint::formatter) (lambda (x) (gprint::format-quote "," x))) (setf (get splice-marker 'gprint::formatter) (lambda (x) (gprint::format-quote ",@" x)))) (defvar *trace-level* 0) (define-scheme (*trace proc name) (function->procedure (lambda args (fluid-let ((*trace-level* (1+ *trace-level*))) (format t "~&~vt~d Enter ~s" *trace-level* *trace-level* name) (mapc (lambda (arg) (princ #\space) (scheme::write arg)) args) (let ((val (apply #'scheme-call proc args))) (format t "~&~vt~d Exit ~s " *trace-level* *trace-level* name) (scheme::write val) val))) `(trace ,proc))) (define-scheme (*untrace proc) (let ((name (procedure-name proc))) (cond ((and (consp name) (eq (car name) 'trace)) (cadr name)) (t (format t "~&Wasn't traced in the first place.~%") proc)))) (define-scheme (%out) (declare (special *)) *) (define-scheme (%ge env) (check-type env (or environment package-environment) "an environment") (setq *current-environment* env)) ;;; Entries are in alphabetical order. (Any other order would seem ;;; arbitrary. This way it's easy to tell whether something exists.) (mapc (lambda (z) (multiple-value-bind (from to) (cond ((consp z) (values (cadr z) (car z))) (t (values z z))) (assert (not (eq (symbol-package from) clsch-package)) (from)) (let ((to (clsch-symbol to))) (clsch-export to) (setf (symbol-value to) (function->procedure (symbol-function from) to)) (setf (get to 'trivial) from) (setf (symbol-function to) (symbol-function from))))) '( 1+ ; RS (-1+ 1-) ; RS + - * / ;E S = < > ;E S <= >= ;E S ? (=? =) (? >) (<=? <=) (>=? >=) ;E abs ;E S acos ; R append ;E (append! nconc) ; R asin ; R atan ; R (assv assoc) ;E (atom? atom) ; S car cdr caar cadr cdar cddr ;E S caaar caadr cadar caddr ;E S cdaar cdadr cddar cdddr ;E S caaaar caaadr caadar caaddr ;E S cadaar cadadr caddar cadddr ;E S cdaaar cdaadr cdadar cdaddr ;E S cddaar cddadr cdddar cddddr ;E S (char->integer char-code) ;E (char-alphabetic? alpha-char-p) ; R (char-ci<=? char-not-greaterp) ; R (char-ci=? char-not-lessp) ; R (char-ci>? char-greaterp) ; R (char-numeric? digit-char-p) ; R (almost) char-downcase ; R (char-lower-case? lower-case-p) ; R char-upcase ; R (char-upper-case? upper-case-p) ; R (char<=? char<=) ;E (char=? char>=) ;E (char>? char>) ;E (char? characterp) ;E (cons* list*) ; T cos ; RS (complex? numberp) ;E cons ;E S (empty-stream? null) ; S (eq? eq) ;E S (equal? equal) ;E (eqv? eql) ;E (even? evenp) ;E exp ; RS expt ; RS gcd ; RS get ; S ? (head car) ; S (integer->char code-char) ;E (integer? integerp) ;E (last-pair last) ; R (length list-length) ;E S lcm ; R list ;E S log ; R max ;E S (memv member) ;E min ;E S (modulo mod) ; R (negative? minusp) ;E (newline terpri) ;E not ;E S nth ; M (null? null) ;E S (number? numberp) ;E S (odd? oddp) ;E (pair? consp) ;E (positive? plusp) ;E princ ; S print ; S random ; M (rational? rationalp) ;E (remainder rem) ;E S reverse ; R (reverse! nreverse) ; MT sin ; RS sqrt ; RS (string-length length) ;E (string-ref char) ;E (string-ci<=? string-not-greaterp) ; R (string-ci=? string-not-lessp) ; R (string-ci>? string-greaterp) ; R (string<=? string<=) ;E (string=? string>=) ;E (string>? string>) ;E (string? stringp) ;E (substring subseq) ;E (symbol->string symbol-name) ;E (symbol? symbolp) ;E S tan ; R vector ;E (vector-length length) ;E (vector-ref svref) ;E S write-char ;E (zero? zerop) ;E )) ;;; Another hack for editor interface #+Symbolics (progn 'compile (put 'si:xr-bq-list 'trivial 'si:xr-bq-list) (put 'si:xr-bq-list* 'trivial 'si:xr-bq-list*) (put 'si:xr-bq-cons 'trivial 'si:xr-bq-cons) (put 'si:xr-bq-append 'trivial 'si:xr-bq-append)) ;;; Missing inessential RRRS features: ;;; exact and complex numbers ;;; make-rectangular make-polar real-part imag-part magnitude angle ;;; exact->inexact inexact->exact ;;; string->number ;;; make-string string-set! string-fill! string-copy substring-fill! ;;; substring-move-right! substring-move-left! ;;; vector-fill! ;;; object-hash object-unhash ;;; open-input-file open-output-file ;;; close-input-port close-output-port ;;; char-ready? ;;; transcript-on transcript-off ;;; T, MacScheme, and CL all have peek-char; should add this? ;;; For more efficient execution on a Lisp Machine, see this file: ;;; z:>multilisp>emulator>emulator.lisp ;;;----- Top level (need a definition for running in a real CL!) ;;; Read-eval-print loop. (defvar *current-environment* (locally (declare (special user-initial-environment)) user-initial-environment)) (defun scheme-eval (exp) (scheme::eval exp *current-environment*)) #-Symbolics (defun scheme () (catch 'exit-scheme (with-package-and-readtable scheme-package scheme-readtable (progv *proc+arg-regs* '() ;Make them all be unbound. CLtL p. 112 (iterate loop () (catch 'reset-scheme (format *terminal-io* "~&==> ") (let ((form (read *terminal-io* nil '*eof-object*))) (cond ((eq form '*eof-object*) (return-from scheme t)) (t (setq * (scheme-eval form)) (format *terminal-io* "~&") (scheme::write * *terminal-io*))))) (loop)))))) #+Symbolics (defun scheme () (catch 'exit-scheme (fluid-let ((si:*command-loop-eval-function* #'scheme-eval) (si:*command-loop-print-function* (lambda (values) (zl:send zl:standard-output :fresh-line) (cond ((or (null values) (not (null (cdr values)))) (format t "~&[Strange, there should have been exactly one value.]~&") (scheme::write values)) (t (scheme::write (car values)))))) (si:*read-form-edit-trivial-errors-p* nil) (si:*cp-prompt* "Scheme: ")) (with-package-and-readtable scheme-package scheme-readtable (progv *proc+arg-regs* '() (block done (iterate loop () (catch 'reset-scheme (si:lisp-command-loop *terminal-io* :name "Scheme Command Loop") (return-from done (values))) (loop)))))))) ;;; Local Modes: ;;; Lisp block Indent:1 ;;; Lisp with-open-file Indent:1 ;;; Lisp with-package-and-readtable Indent:2 ;;; Lisp zl:standard-value-let Indent:1 ;;; Lisp lisp:lambda Indent:1 ;;; Lisp catch Indent:1 ;;; Lisp iterate Indent:2 ;;; End: