;;; -*- Mode:LISP; Package:(CLSCH LISP); Syntax:COMMON-LISP; Base:10 -*-

;;; An implementation of Scheme, less continuations, in Common Lisp.

;;; ***** Initial version - not fully debugged *****

;;; The dialect here is a union of the features needed for
;;;  (a) the examples in Abelson & Sussman's book "Structure and
;;;      Interpretation of Computer Programs"
;;;  (b) the problem sets for the 6.001 course
;;;  (c) the "essential" subset of the Revised Revised Report on Scheme
;;;      Scheme

;;; The only thing from (a) that's missing is MAKE-ENVIRONMENT.
;;; The only thing from (c) that's missing is CALL-WITH-CURRENT-CONTINUATION.
;;; Some things from (b) might be missing; I haven't checked thoroughly.

;;; A small number of important nonstandard features are present also:
;;; RANDOM, tables, "named LET", FLUID-LET, and DEFINE-MACRO.

;;; Implementing MAKE-ENVIRONMENT and CALL-WITH-CURRENT-CONTINUATION
;;; would be a major headache.  Fortunately, Scheme is still fairly
;;; useful without them.

;;; *** RECENT CHANGES ***
;;; Version 156 (11/22/85)
;;;  - JDR's latest set of fixes
;;; Version 154 (11/21/85)
;;;  - Flushed idea of exporting things in Scheme package.
;;;  - Translator passes an ENV argument around (doesn't use it yet).
;;; Version 149 (11/16/85)
;;;  - Winning quasiquote macro.
;;;  - Scheme's very own printer.
;;; Version 146 (11/12/85)
;;;  - Eric Benson's colon read macro.
;;;  - Fixed a bug in WRITE.
;;;  - John Ramsdell's meta-. hacks.
;;; Version 134 (11/10/85)
;;;  - Entry function S renamed to be SCHEME.
;;;  - Object file extension is now SBIN istead of SFASL.
;;;  - Improvements in package structure; SCHEME now inherits from CLSCH.
;;;     E.g., Scheme's QUOTE is now the same as Lisp's.
;;;  - Definition of a DEFINE macro in the SCHEME package, so that it
;;;     will now sort of work to use Common Lisp LOAD and EVAL and editor
;;;	commands to cause Scheme values to get defined.
;;;  - No more call to MACROEXPAND (avoid Symbolics DEFSUBST screw).
;;;  - Elimination of REC in DEFINE expansion (for speed and debugging).
;;;  - Some nonstandard features: tables, FLUID-LET, and DEFINE-MACRO.
;;;  - Scheme DEFINE also sets the function cell.
;;;  - Trivial definition of NUMBER->STRING.

;;; A minor flaw:
;;;  (define (foo x) y) is the same as
;;;  (define foo (lambda (x) y)), NOT
;;;  (define foo (rec foo (lambda (x) y))), as the RRRS would dictate.
;;; Fortunately, this delinquency leads to faster execution, and makes
;;; TRACE work besides.

;;; To do:
;;;  - It would be nice if there was an evaluator.  Code runs impossibly
;;;    slow if it's not compiled; and debugging is a pain.
;;;  - CAR and CDR of () should be error.
;;;  - Error reporting is bad; e.g., no procedure names.
;;;  - Need equivalent of GRINDEF.
;;;  - Want optimization of LETREC.
;;;  - Variants of the compiler: (1) real continuations, (2) CL's which
;;;    already have tail recursion, (3) CL's which can kludge tail recursion
;;;    (e.g. 3600's)

;;; Thanks to John Ramsdell of Mitre and Eric Benson of Lucid for various
;;; contributions.

;;; Jonathan Rees  Aug-Sept 1985

(in-package 'clsch)				;Common Lisp SCHeme

(shadow '(help lambda))

(export '(scheme help))

(defvar help

"How to use Scheme:
 - To start a scheme read-eval-print loop, evaluate (CLSCH:SCHEME).
 - To exit scheme once in it, do (EXIT).
 - To get back into the scheme read-eval-print loop, e.g. when an
   error occurs, do (RESET).
 - Use MIT AI memo 848, the Revised Revised Report on Scheme, as a
   reference manual.  All features marked 'essential' are here,
   along with many of the non-essential features.
 - CALL-WITH-CURRENT-CONTINUATION exists, but continuations will have
   indefinite extent only if the host Common Lisp's BLOCK tags do.
 - Additional features used in S&ICP are also defined, with the
   exception of COLLECT and MAKE-ENVIRONMENT.
 - The expression ## evaluates to the value of the read-eval-print
   loop's last evaluation.
 - To compile a scheme file, use scheme's COMPILE-FILE procedure.")

;;; Utility for avoiding compiler warnings
(defmacro ignorable (&rest vars)
  `(progn ,@vars))

;;;----- Scheme-like macros for Common Lisp code

;;; The main reason the following things exist is so that the translator
;;; itself can be written in Scheme instead of in Common Lisp.  This is
;;; mostly just an exercise.

(eval-when (global:compile global:eval global:load)

(defun ampersandify (bvl)
  (do ((l bvl (cdr l))
       (z '() (cons (car l) z)))
      ((atom l)
       (cond ((null l)
	      (nreverse z))
	     (t
	      (nreconc z `(&rest ,l)))))))

(defun concatenate-symbol (&rest things)	;   T
  (values (intern (apply #'concatenate 'string (mapcar #'string things)))))
)

;;; Same as SETQ but inhibits "not declared special" warning.

(defmacro setq-global-value (var val)
  (let ((g (gensym)))
    `(let ((,g ,val))
       (declare (special ,var))
       (setq ,var ,g)
       ',var)))

(defmacro define (pat &body body)
  (cond ((consp pat)
	 `(defun ,(car pat) ,(ampersandify (cdr pat)) ,@body))
	(t
	 `(setq-global-value ,pat ,@body))))

;;; LAMBDA is shadowed in the CLSCH package, to avoid a conflict here with
;;; any possible LAMBDA macro defined in the host Common Lisp.

(defmacro lambda (vars &body body)
  `#'(lisp:lambda ,(ampersandify vars) ,@body))

(defmacro iterate (tag specs &body body)
  (let ((vars (mapcar #'car specs))
	(id (gensym)))
    `(block ,id
       (let ,specs
	 (tagbody
	   ,id (macrolet ((,tag ,vars
			   `(progn (psetq ,@(list ,@(mapcan #'(lisp:lambda (var)
								`(',var ,var))
							    vars)))
				   (go ,',id))))
		 (return-from ,id (progn ,@body))))))))

#|The following version is better, but isn't efficiently compiled by most CL's.
(defmacro iterate (tag specs &body body)
  `(labels ((,tag ,(mapcar #'car specs)
	     ,@body))
     (,tag ,@(mapcar #'cadr specs))))|#

(defmacro defsynonym (dst src)
  `(defmacro ,dst (&rest x) `(,',src ,@x)))

(defsynonym eq?      eq)
(defsynonym equal?   equal)
(defsynonym null?    null)
(defsynonym pair?    consp)
(defsynonym atom?    atom)
(defsynonym symbol?  symbolp)
(defsynonym append!  nconc)
(defsynonym reverse! nreverse)
(defsynonym vector?  simple-vector-p)

(defconstant else t)

;;; Pretty useless, but the existence of the definitions may help
;;; indentation work on the 3600.

(defmacro letrec (specs &body body)
  `(labels ,(mapcar #'(lisp:lambda (spec)
			(let ((var (car spec)) (val (cadr spec)))
			  (cond ((and (pair? val)
				      (eq? (car val) 'lambda))
				 `(,var ,(ampersandify (cadr val))
					,@(cddr val)))
				(t
				 (error "losing LETREC spec - ~s" spec)))))
		    specs)
     ,@body))

(defmacro fluid-let (specs &body body)
  `(let ,specs
     (declare (special ,@(mapcar #'car specs)))
     ,@body))

(defmacro define-macro ((name . bvl) &body body)
  `(progn 'compile
	  (defmacro ,name ,(ampersandify bvl) ,@body)
	  (*define-scheme-macro ',name ',name)))

(defmacro set! (var val)
  `(setq ,var ,val))


;;;----- Basic things which need to be defined before everything else

;;; Scheme lives within its own package, but happens to inherit the
;;; symbols that CLSCH exports.  It does NOT inherit from LISP!

(eval-when (eval load compile)
  (defvar clsch-package (find-package "CLSCH"))
  (defvar scheme-package (make-package 'scheme :use (list clsch-package))))

(eval-when (eval load compile)
  
  (export 'else clsch-package)

  (defun clsch-symbol (symbol)
    (cond ((eq (symbol-package symbol) clsch-package)
	   symbol)
	  (t
	   (let ((symbol (intern (symbol-name symbol) scheme-package)))
	     ;; (export symbol scheme-package) - necessary??
	     symbol))))
  (defun clsch-export (symbol)
    (cond ((eq (symbol-package symbol) clsch-package)
	   (export symbol clsch-package))
	  ((not (eq (symbol-package symbol) scheme-package))
	   (error "bogus symbol: (CLSCH-EXPORT '~s)" symbol)))
    symbol))


;;; Registers for passing arguments

(defvar *argument-registers*
  (list '**a1** '**a2** '**a3** '**a4** '**a5**))

(defvar *proc+arg-regs*
  (cons '**p** *argument-registers*))

(defvar *number-of-arg-regs*
  (length *argument-registers*))

(defvar **p**)
(defvar **a1**)
(defvar **a2**)
(defvar **a3**)
(defvar **a4**)
(defvar **a5**)

(define (ensure-enough-arg-regs n)
  (cond ((> n *number-of-arg-regs*)
	 (iterate loop ((i (1+ *number-of-arg-regs*))
			(l '()))
	   (cond ((> i n)
		  (setf (cdr (last *argument-registers*))
			(reverse! l))
		  (setq *number-of-arg-regs* n))
		 (else
		  (loop (1+ i)
			(cons (intern (format nil "**A~D**" i)
				      (find-package "CLSCH"))
			      l))))))))

;;; The Scheme readtable isn't needed until run time, but this seems
;;; like a good place to put it.

(defvar cl-readtable *readtable*)
(defvar scheme-readtable (copy-readtable nil))

(defconstant quasiquote-marker 'quasiquote-marker)
(defconstant unquote-marker    'unquote-marker)
(defconstant splice-marker     'splice-marker)

(defun sharp-excl-read-macro (stream subchar arg)
  (ignorable subchar arg)
  (let ((name (read stream t nil t)))
    (ccase name
      ((scheme::true) t)
      ((scheme::false) nil)
      ((scheme::null) '())
      ((scheme::quasiquote) quasiquote-marker)
      ((scheme::unquote) unquote-marker)
      ((scheme::splice) splice-marker)
      )))

(defun sharp-sharp-read-macro (stream subchar arg)
  (cond (arg (funcall (get-dispatch-macro-character #\# #\# cl-readtable)
		      stream subchar arg))
	(t '*)))

;;; Thanks to Eric Benson for the following hack.
;;; Small bug: a symbol beginning ":#" can't be read.

(defun read-alphabetic (stream char)
  (let ((following-char (peek-char nil stream nil stream t)))
    ;; EOF is OK, STREAM is EOF-VALUE.
    (if (or (eq following-char stream)
	    (not (constituentp following-char)))
        (values (intern (string char)))
	(let ((subsymbol (read stream t nil t)))
	  (check-type subsymbol symbol)
	  (values (intern (concatenate 'string
				       (string char)
				       (symbol-name subsymbol))))))))

(defun constituentp (char)
  (multiple-value-bind (fun n-t-p)
      (get-macro-character char)
    (cond (fun (not n-t-p))
	  (t (and (graphic-char-p char)
		  (not (find char " ',;`()[]{}")))))))

(defun quasiquote-read-macro (stream ignore)
  (list quasiquote-marker (read stream t nil t)))

(defun unquote-read-macro (stream ignore)
  (list (let ((following-char
		(peek-char nil stream nil stream t)))
	  (cond ((char= following-char #\@)
		 (read-char stream)
		 splice-marker)
		(t unquote-marker)))
	(read stream t nil t)))

(defun illegal-read-macro (ignore char)
  (cerror "treat the character as whitespace"
	  "illegal character read - ~s"
	  char)
  (values))

(let ((*readtable* scheme-readtable))
  (set-macro-character #\` #'quasiquote-read-macro)
  (set-macro-character #\, #'unquote-read-macro)
  #-Symbolics
  (set-macro-character #\: #'read-alphabetic t)
  #+Symbolics   ;Symbolics Common Lisp has many bugs.
  (set-syntax-from-char #\: #\!)
  (set-macro-character #\| #'illegal-read-macro)
  (set-macro-character #\\ #'illegal-read-macro)
  (set-macro-character #\[ #'illegal-read-macro)
  (set-macro-character #\] #'illegal-read-macro)
  (set-macro-character #\{ #'illegal-read-macro)
  (set-macro-character #\} #'illegal-read-macro)
  (set-dispatch-macro-character #\# #\! #'sharp-excl-read-macro)
  (set-dispatch-macro-character #\# #\# #'sharp-sharp-read-macro))


;;;----- The Scheme-to-Common-Lisp Compiler

;;; Each definition of a Scheme special form or procedure is annotated
;;; with one or more letters indicating why that feature is included in
;;; this Scheme implementation.
;;;   E = essential feature of RRRSS
;;;   R = inessential feature of RRRSS
;;;   S = something used in the book S&ICP
;;;   M = MIT Scheme feature needed to run 6.001 course software
;;;   T = random T feature which JAR likes to use

;;; Brief description of compilation strategy:
;;; Scheme code is translated into Common Lisp code, which can then be
;;; either interpreted or compiled.  Scheme parasitizes Common Lisp's
;;; data types.  Also, the compiler doesn't need to keep track of
;;; lexical variable references, since Common Lisp's lexical scoping can
;;; be used.  The main thing the compiler worries about is tail recursion.
;;; If a Scheme procedure returns a non-null second value, that means that
;;; it wants to do a tail-recursive call.  A driver loop is expected to
;;; call the procedure in the **P** register.  Procedures always expect
;;; to receive their arguments in registers **A1**, **A2**, etc.

;;; Currently the ENV argument is ignored.

;;; TRANSLATE returns two values: the output expression, and a flag
;;; saying whether or not the expression was trivial (i.e. made
;;; assignments to the argument registers).

(define (translate exp env ret?)
  (cond ((not (pair? exp))
	 (translate-return exp t ret?))
	((special-form? exp)
	 (translate-special-form exp env ret?))
	(else
	 (translate-call exp env ret?))))

(define (translate-return exp trivial? ret?)
  (ignorable ret?)
  (values exp trivial?))  ;!

;;; Procedure call

(define (translate-call proc+arg-exps env ret?)
  (let ((proc-exp (car proc+arg-exps))
	(arg-exps (cdr proc+arg-exps)))
    (cond ((and (symbol? proc-exp)
		(get proc-exp 'trivial))
	   (translate-trivial-call (get proc-exp 'trivial)
				   arg-exps env t ret?))
	  ((and (pair? proc-exp) (eq? (car proc-exp) 'lambda))
	   ;; LAMBDA-combinations are trivial.
	   (multiple-value-bind (code trivial?)
	       (translate-lambda-body (cddr proc-exp) env ret?)
	     (let ((new-bvl (ampersandify (cadr proc-exp))))
	       (mapc (lambda (b) (warn-if-redefining b "binding")) new-bvl)
	       (translate-trivial-call `(lisp:lambda ,new-bvl
					  ,code)
				       arg-exps
				       env
				       trivial?
				       ret?))))
	  (else
	   (translate-nontrivial-call proc-exp arg-exps env ret?)))))

(define (translate-trivial-call proc-code arg-exps env trivial? ret?)
  (iterate loop ((a arg-exps)
		 (z '())
		 (trivial? trivial?))
    (cond ((null? a)
	   (translate-return `(,proc-code ,@(reverse! z))
			     trivial?
			     ret?))
	  (else
	   (multiple-value-bind (code arg-trivial?)
	       (translate (car a) env nil)
	     (loop (cdr a) (cons code z) (and trivial? arg-trivial?)))))))

(define (translate-nontrivial-call proc-exp arg-exps env ret?)
  (let ((nargs (length arg-exps)))
    (ensure-enough-arg-regs nargs)
    (let ((setup-code (psetqify-args proc-exp arg-exps env)))
      (cond (ret? (values `(progn ,@setup-code
				  (values nil ,nargs))
			  nil))
	    (else (values `(progn ,@setup-code
				  (driver-loop ,nargs))
			  nil))))))

;;; Tries to do SETQ's in preference to PSETQ's, where possible.

(define (psetqify-args proc-exp arg-exps env)
  (iterate loop ((a (cons proc-exp arg-exps))
		 (r *proc+arg-regs*)
		 (setqs '())
		 (psetqs '()))
    (cond ((null? a)
	   (append (if (null? psetqs) '() `((psetq ,@(reverse! psetqs))))
		   (if (null?  setqs) '() `(( setq ,@(reverse!  setqs))))))
	  (else
	   (multiple-value-bind (code trivial?)
	       (translate (car a) env nil)
	     (let ((reg (car r)))
	       (cond (trivial?
		      (loop (cdr a) (cdr r) (list* code reg setqs) psetqs))
		     (else
		      (loop (cdr a) (cdr r) setqs (list* code reg psetqs))
		      ))))))))

;;; Generates adequate, but potentially inferior, code.

(define (psetqify-args-ez proc-exp arg-exps env)
  `((psetq ,@(mapcan (lambda (reg exp)
		       (list reg (translate exp env nil)))
		     *proc+arg-regs*
		     (cons proc-exp arg-exps)))))

;;; Scheme special forms

(defvar special-form-table)
(setq special-form-table (make-hash-table :test #'eq))

(define (special-form? exp)
  (and (pair? exp) (gethash (car exp) special-form-table nil)))

(define (translate-special-form exp env ret?)
  (funcall (gethash (car exp) special-form-table) exp env ret?))

(defmacro define-scheme-special-form (name args &body body)
  ;; Symbolics CL sucks.  Anonymous functions don't get compiled!
  (let ((internal-name (concatenate-symbol name '/scheme-special-form)))
    `(progn 'compile
	    (export ',name clsch-package)
	    (defun ,internal-name ,args ,@args ,@body)
	    (setf (gethash ',name special-form-table) #',internal-name))))

;;; (Definitions of special forms appear in alphabetical order.)

;;; BEGIN, SEQUENCE

(define-scheme-special-form begin (exp env ret?)	;E
  (translate-sequence (cdr exp) env ret?))

(define-scheme-special-form sequence (exp env ret?)	; RS
  (translate-sequence (cdr exp) env ret?))

(define (translate-sequence exp-list env ret?)
  (cond ((null? (cdr exp-list))
	 (translate (car exp-list) env ret?))
	(else
	 (iterate loop ((l exp-list)
			(z '())
			(trivial? t))
	   (multiple-value-bind (code arg-trivial?)
	       (translate (car l) env (and (null? (cdr l)) ret?))
	     (let ((trivial? (and trivial? arg-trivial?)))
	       (cond ((null? (cdr l))
		      (values `(progn ,@(reverse! (cons code z))) trivial?))
		     (else
		      (loop (cdr l)
			    (cons code z)
			    trivial?)))))))))

;;; Top-level DEFINE

(defvar top-level-env '())

(define (translate-top-level exp)
  (cond ((definition? exp)
	 (translate-top-level-define exp))
	(t
	 (translate exp top-level-env nil))))

(define (translate-top-level-define exp)
  (let ((pat (cadr exp))
	(body (cddr exp)))
    (multiple-value-bind (name val)
	(parse-define pat body)
      (warn-if-redefining name "defining")
      (multiple-value-bind (code trivial?)
	  (translate val top-level-env nil)
	(translate-return
	  `(*define ',name ,code)
	  trivial?
	  nil)))))

(define (parse-define pat body)
  (cond ((pair? pat)
	 (values (car pat)
		 ;; `(rec ,(car pat) ...)
		 `(lambda		;Want NAMED-LAMBDA
		    ,(cdr pat)
		    ,@body)))
	(else
	 (values pat (car body)))))

(define (definition? exp)
  (and (pair? exp) (eq? (car exp) 'scheme::define)))

(define (warn-if-redefining name what)
  (cond ((get name 'trivial)
	 (in-scheme-package
	  (lambda ()
	    (format *error-output*
		    "~&Warning: ~A ~S, which is a built-in procedure.~&"
		    what
		    name)))
	 (remprop name 'trivial))))

;;; IF

(define-scheme-special-form if (exp env ret?)	;E
  (multiple-value-bind (test-code test-trivial?)
      (translate (cadr exp) env nil)
    (multiple-value-bind (con-code con-trivial?)
	(translate (caddr exp) env ret?)
      (multiple-value-bind (alt-code alt-trivial?)
	  (translate (cadddr exp) env ret?)
	(values `(if ,test-code
		     ,con-code
		     ,alt-code)
		(and test-trivial? con-trivial? alt-trivial?))))))

;;; LAMBDA

(define-scheme-special-form lambda (exp env ret?)	;E S
  (multiple-value-bind (code trivial?)
      (translate-lambda-body (cddr exp) (cons (cadr exp) env) t)
    (ignorable trivial?)
    (translate-return `(create-procedure
			 (lambda (nargs)
			   ;; Eventually, check number of args.
			   (ignorable nargs)
			   (let ,(bind-arguments (cadr exp))
			     ,code))
			 nil)
		      t				;yes, closures are trivial
		      ret?)))

(define (bind-arguments bvl)
  (ensure-enough-arg-regs (improper-length bvl))
  (do ((l bvl (cdr l))
       (a *argument-registers* (cdr a))
       (n 0 (+ n 1))
       (z '() (cons `(,(car l) ,(car a)) z)))
      ((not (pair? l))
       (reverse! (cond ((null? l) z)
		       (else (cons `(,l (get-n-ary-arg nargs ,n)) z)))))
    (assert (not (null? a)))
    (warn-if-redefining (car l) "binding")))

(define (improper-length l)
  (do ((l l (cdr l))
       (n 0 (1+ n)))
      ((atom? l) n)))

(define (translate-lambda-body body env ret?)
  (iterate loop ((b body)
		 (vars '())
		 (z '()))
    (cond ((null? (cdr b))
	   (multiple-value-bind (code trivial?)
	       (translate-sequence (reverse! (cons (car b) z))
				   (cons vars env)
				   ret?)
	     (values (if (null? vars)
			 code
			 `(let ,(mapcar (lambda (var) `(,var '*undefined*))
					vars)
			    ,code))
		     trivial?)))
	  (else
	   (let ((exp (car b)))
	     (cond ((definition? exp)
		    (multiple-value-bind (name val)
			(parse-define (cadr exp) (cddr exp))
		      (loop (cdr b)
			    (cons name vars)
			    (cons `(set! ,name ,val)
				  z))))
		   (else
		    (loop (cdr b)
			  vars
			  (cons (car b) z)))))))))

(define-scheme-special-form delay (exp env ret?)	;  S
  (translate-return `(make-delay :thunk-or-value
				 #'(lisp:lambda ()
				     ,(translate (cadr exp) env nil)))
		    t ret?))

(setf (get 'make-delay 'trivial) 'make-delay)

;;; QUOTE

(define-scheme-special-form quote (exp env ret?)	;E S
  (translate-return `(quote ,(cadr exp)) t ret?))

;;; SET!

(define-scheme-special-form set! (exp env ret?)	;E S
  (let ((var (cadr exp))
	(val-exp (caddr exp)))
    (multiple-value-bind (code trivial?)
	(translate val-exp env nil)
      (warn-if-redefining var "assigning")
      (translate-return `(setq ,var ,code)
			trivial?
			ret?))))

;;; Renegade DEFINE-MACRO - sort of compatible with MIT Scheme

(define-scheme-special-form define-macro (exp env ret?)	;   M
  (let ((name (caadr exp))
	(bvl  (cdadr exp))
	(body (cddr exp)))
    (let ((internal-name (concatenate-symbol name '/scheme-macro)))
      (multiple-value-bind (code trivial?)
	  (translate-sequence body env nil)
	(ignorable trivial?)
	(translate-return `(progn 'compile
				  (defmacro ,internal-name ,(ampersandify bvl)
				    ,code)
				  (*define-scheme-macro ',name
							',internal-name))
			  t
			  ret?)))))

;;; Renegade FLUID-LET - like MIT Scheme, but only works on top level
;;; variables!  (For obvious reasons.)

(define-scheme-special-form fluid-let (exp env ret?)
  (let ((specs (cadr exp))
	(body (cddr exp)))
    (multiple-value-bind (code trivial?)
	(translate-sequence body env ret?)
      (ignorable trivial?)
      (translate-return `(let ,(mapcar (lambda (spec)
					 `(,(car spec)
					   ,(translate (cadr spec) env nil)))
				       specs)
			   (declare (special ,@(mapcar #'car specs)))
			   ,code)
			nil
			ret?))))

;;; Test routines for translator

(define (tst exp)
  (pretty-print (translate-top-level (schemify exp)))
  '*)

(define (schemify sexpr)      ;don't try to use this with backquote forms!
  (cond ((null? sexpr) sexpr)
	((symbol? sexpr) (intern (symbol-name sexpr) scheme-package))
	((pair? sexpr) (cons (schemify (car sexpr)) (schemify (cdr sexpr))))
	(else sexpr)))

#|(define (show obj)
    (check-type obj function)
    (pretty-print (si::undigest (sys::%p-contents-offset obj 1)))
    '*)|#

;;;----- Scheme macros

(defmacro define-scheme-macro ((name . args) &body body)
  (let ((internal-name (concatenate-symbol name '/scheme-macro)))
    `(progn 'compile
	    (export ',name clsch-package)
	    (defmacro ,internal-name ,(ampersandify args) ,@body)
	    (*define-scheme-macro ',name ',internal-name))))

(defun *define-scheme-macro (sym fun)
  (setf (gethash sym special-form-table)
	(lambda (exp env ret?)
	  (translate (macroexpand-1 (cons fun (cdr exp)))
		     env
		     ret?)))
  sym)

;;; (Definitions of macros appear in alphabetical order.)

(define-scheme-macro (and . forms)		; RS
  (labels ((expand-and (forms)
            (cond ((atom? forms) 't)
                  ((atom? (cdr forms)) (car forms))
                  (else `(if ,(car forms)
			     ,(expand-and (cdr forms))
			     nil)))))
    (expand-and forms)))

;;; case					; R
;;; collect					;  S (!)

(define-scheme-macro (cond . clauses)	;E S
  (labels ((expand-cond (clauses)
            (cond ((atom? clauses) ''**no-more-cond-clauses**)
                  ((atom? (car clauses))
                   (cerror "ignore it" "atomic COND clause: ~s" (car clauses))
		   (expand-cond (cdr clauses)))
                  ((atom? (cdar clauses))
                   `(or ,(caar clauses)
			,(expand-cond (cdr clauses))))
		  ((eq? (caar clauses) 'else)
		   (if (not (atom? (cdr clauses)))
		       (cerror "ignore clauses following ELSE clause"
			       "ELSE clause not last in COND: ~s"
			       `(cond ,@clauses)))
		   `(begin ,@(cdar clauses)))
                  (else `(if ,(caar clauses)
			     (begin ,@(cdar clauses))
			     ,(expand-cond (cdr clauses)))))))
    (expand-cond clauses)))

(define-scheme-macro (cons-stream head tail)	;  S
  `(scheme::cons ,head (delay ,tail)))

(define-scheme-macro (do specs end &body body)	; R
  (let ((loop (gensym "DO")))
    `(letrec ((,loop (lambda ,(mapcar #'car specs)
                       (cond ,end
                             (else ,@body
				   (,loop
				    ,@(mapcar (lambda (y)
						(if (and (cdr y) (cddr y))
						    (caddr y)
						    (car y)))
					      specs)))))))
       (,loop ,@(mapcar (lambda (y) (if (cdr y) (cadr y) 'nil))
			specs)))))

(define-scheme-macro (let specs &body body)	;E S
  (cond ((and (not (pair? specs))   ;Weird MIT extension
	      (not (null? specs)))
	 (let ((tag specs)
	       (specs (car body))
	       (body (cdr body)))
	   `(letrec ((,tag (lambda ,(mapcar #'car specs) ,@body)))
	      (,tag ,@(mapcar #'cadr specs)))))
	(else
	 `((lambda ,(mapcar #'car specs) ,@body)
	   ,@(mapcar (lambda (x)
		       (cond ((atom? (cdr x)) ''**let-missing-initializer**)
			     (else (cadr x))))
		     specs)))))

;;; let*					; R

(define-scheme-macro (letrec specs &body body)	;E
  (iterate loop ((s specs)
		 (vars '())
		 (inits '()))
    (cond ((null? s)
           `((lambda ,vars
	       ,@(reverse! inits)
	       . ,body)
             ,@(mapcar (lambda (var) (ignorable var) ''**unbound-label**)
		       vars)))
          (else
           (let ((spec (car s)))
             (cond ((atom? spec)
                    (cerror "ignore it"
			    "bad spec - (LETREC (... ~S ...) ...)"
			    spec)
		    (loop (cdr s) vars inits))
                   ((atom? (car spec))
                    (loop (cdr s)
                          (cons (car spec) vars)
                          (cons `(set! ,@spec)
                                inits)))
                   (else
                    (loop (cdr s)
                          (cons (caar spec) vars)
                          (cons `(set! ,(caar spec)
				       (lambda ,(cdar spec)
					 ,@(cdr spec)))
                                inits)))))))))

;;; make-environment				;  S (!)

(define-scheme-macro (or . args)		; RS
  (labels ((expand-or (args)
	     (cond ((atom? args) ''nil)
		   ((atom? (cdr args)) (car args))
		   (else `((lambda (p)
			     (if p p ,(expand-or (cdr args))))
			   ,(car args))))))
    (expand-or args)))

(define-scheme-macro (rec var exp)		;E S
  `(letrec ((,var ,exp)) ,var))

(define-scheme-macro (trace var)
  `(set! ,var (*trace ,var ',var)))

(define-scheme-macro (untrace var)
  `(set! ,var (*untrace ,var)))

;;; Quasiquote

(*define-scheme-macro quasiquote-marker quasiquote-marker)
(defmacro quasiquote-marker (x)
  (expand-quasiquote x 0))

(*define-scheme-macro unquote-marker unquote-marker)
(defmacro unquote-marker (x)
  (cerror "as as if the comma wasn't there at all"
	  "comma not inside backquote form - ,~S" x)
  x)

(*define-scheme-macro splice-marker splice-marker)
(defmacro splice-marker (x)
  (cerror "act as if the ,@ wasn't there at all"
	  "\",@\" not inside backquote form - ,@~S" x)
  x)

(define (expand-quasiquote x level)
  (multiple-value-bind (mode arg)
      (descend-quasiquote x level)
    (finalize-quasiquote mode arg)))

(define (finalize-quasiquote mode arg)
  (cond ((eq? mode 'quote) `',arg)
	((eq? mode 'unquote) arg)
	((eq? mode 'splice)
	 (cerror "act as if () had been seen instead of ,@<form>"
		 ",@ 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 ((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))))))))

(define (interesting-to-quasiquote? x marker)
  (and (pair? x)
       (eq? (car x) marker)
       (pair? (cdr x))
       (null? (cddr x))))

;;;----- Runtime system

(defstruct (procedure (:predicate procedure?)
		      (:print-function (lisp:lambda (struct stream ignore)
					 (scheme::write struct stream))))
  function
  name)

(defvar *procedure-uid-counter* 0)
(defun create-procedure (fun name)
  (make-procedure :function fun :name (or name (incf *procedure-uid-counter*))))

(defun *define (name value)
  (set-name value name)
  (setf (symbol-value name) value)
  ;; 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.
  name)

(defun set-name (obj name)
  (if (and (procedure? obj) (numberp (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)))

(defun scheme-eval (exp)
  (eval (translate-top-level exp)))

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


;;;----- 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)
    (in-scheme-package
      (lambda ()
	(iterate loop ((z '()))
	  (let ((form (read s nil '*eof-object*)))
	    (cond ((eq form '*eof-object*)
		   (values (nreverse z) (truename s)))
		  (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)
    (values (mapcar #'translate-top-level code) true-name)))

;;; Smart LOAD routine.

(define-scheme-nontrivial (load name &optional (compile? nil))  ;E
  (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 (stringp type) (string-equal type *scheme-object-file-type*))))

(defun load-scheme-source-file (name compile?)
  (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))
      (cond (compile?
	     (funcall (let (#+Symbolics (compiler:suppress-compiler-warnings t))
			(compile nil `(lisp:lambda () ,@code)))))
	    (t
	     (mapc #'eval code)))
      name)))

(defun unspecify (name)
  (make-pathname :defaults name
		 :type nil  ;*scheme-source-file-type*  - ??
		 :version nil))

(defun load-scheme-object-file (name)
  (in-scheme-package
   (lambda ()
     (let ((*readtable* cl-readtable))
       (load name))))
  ;; Use FUNCALL ' to suppress undefined function warning.
  (funcall 'run-top-level-forms)
  ;; 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) '())
  name)

;;; File compiler.

(define-scheme-nontrivial (compile-file scheme-pathname
					&optional keep-lisp-file?)
  (multiple-value-bind (code name)
      (translate-scheme-file scheme-pathname)
    (format t "~&Compiling ~S~%" (namestring name))
    (let ((form `(defun run-top-level-forms ()
		   (let (#+Symbolics			;JDR hack
			 (si:fdefine-file-pathname
			   ;; parse-pathname ??
			   (parse-namestring ',(namestring (unspecify name)))))
		     ,@code)))
	  (lisp-pathname (make-pathname :type "TEMP" :defaults name))
	  (fasl-pathname (scheme-object-pathname name)))
      (unwind-protect (in-scheme-package
			(lambda ()
			  (let ((*readtable* cl-readtable))
			    (with-open-file (out lisp-pathname :direction :out)
			      (format out ";-*-Package:SCHEME-*-~%")
			      (print '(in-package "SCHEME") out)
			      (if keep-lisp-file?
				  (pretty-print form out)
				  (print form out)))
			    (let (#+Symbolics
				  (compiler:suppress-compiler-warnings t))
			      (compile-file lisp-pathname
					    :output-file fasl-pathname)
			      fasl-pathname))))
	(if (not keep-lisp-file?) (delete-file lisp-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*))

;;; Utility for getting the package bound properly.  Nontrivial on a
;;; Lisp Machine!

(defun in-scheme-package (thunk)
  (let ((*readtable* scheme-readtable))
    #-Symbolics
    (let ((*package* scheme-package))
      (funcall thunk))
    #+Symbolics
    (zl:standard-value-let ((zl:package scheme-package))
      (funcall thunk))))

#+Symbolics
(push scheme-package si:*reasonable-packages*)

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

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

;;; 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) (ignorable item) "~%~10t~s")
			(cdr items)))
	 items))

(define-scheme-nontrivial (eval exp env)	;  S
  (declare (special user-initial-environment))  ;inhibit warning
  (if (not (eq? env user-initial-environment))
      (cerror "use the USER-INITIAL-ENVIRONMENT instead"
	      "illegal environment argument - (EVAL '~s '~s)"
	      exp env))
  (scheme-eval exp))

(define-scheme (exact? num)			;E
  (ignorable 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))
       (symbol-name atom)))

(define-scheme (floor num)			; R
  (values (floor num 1)))

(define-scheme (for-each proc . lists)		;E
  (apply #'mapc (procedure->function proc) lists))

(define-scheme mapc for-each)			;  S

(defstruct delay (forced-yet? nil) thunk-or-value)

(define-scheme-nontrivial (force obj)		;  S
  (cond ((delay-p 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
  (intern (map 'string #'character list) scheme-package))

(define-scheme (inexact? num)			;E
  (ignorable num) t)

(define-scheme (input-port? obj)		;E
  (and (streamp obj) (input-stream-p obj)))

(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
  (ignorable 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 (map proc . lists)		;E
  (apply #'mapcar (procedure->function proc) lists))

(define-scheme mapcar scheme::map)		;  S

(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)
  (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))

(define-scheme (pretty-print obj &optional (port *standard-output*))
  (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->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-input-from-file string thunk)	; R
  (with-open-file (*standard-output* string :direction :output)
    (scheme-call thunk)))

(define-scheme user-initial-environment		;  S
  'user-initial-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"))
	((procedure? obj)
	 (format *standard-output* "#{Procedure ")
	 (really-write (procedure-name obj))
	 (format *standard-output* "}"))
	((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))))

;;; 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-lessp)			; R
	(char-ci=?  char-equal)			; 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>=? 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-lessp)		; R
	(string-ci=?  string-equal)		; R
	(string-ci>=? string-not-lessp)		; R
	(string-ci>?  string-greaterp)		; R
	(string<=? string<=)			;E
	(string<?  string<)			;E
	(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
	(vector? simple-vector-p)		;E
	write-char				;E
	(zero? zerop)				;E
	))

;;; 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.

#-Symbolics
(defun scheme ()
  (catch 'exit-scheme
    (in-scheme-package
     (lambda ()
       (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 s 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: "))
      (in-scheme-package
	(lambda ()
	  (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 lisp:lambda Indent:1
;;; Lisp zl:standard-value-let Indent:1
;;; Lisp catch Indent:1
;;; Lisp iterate Indent:2
;;; End:

