;      -*- Mode: Lisp -*-			      Filename:  pcomp.s

;--------------------------------------------------------------------------;
;									   ;
;			  TI SCHEME -- PCS Compiler			   ;
;		   Copyright 1985 (c) Texas Instruments 		   ;
;									   ;
;			       Terry Caudill				   ;
;									   ;
;		   Compiler Specific runtime routines   		   ;
;									   ;
;--------------------------------------------------------------------------;

; Revision history:
;  6/01/87 tc - This file was created from several of the old compiler
;               files (pstd, pio, pdebug, pchreq, and pstl) in order to
;		collect all the compiler-specific code.
;  6/01/87 tc - added PCS-INTEGRATE-DEFINE variable so that MIT style
;		defines don't expand into named-lambda unless #T. This
;		is a requirement for the R^3 Report.
;  6/01/87 tc - added string->number as autoload from PNUM2S
;  6/01/87 tc - make compiler re-entrant
;  6/01/87 rb - added more PGR functions to autoload;
;		toplevel reworked so RESET doesn't affect the fluids
;		INPUT-PORT and OUTPUT-PORT (this allows the system toplevel
;		to run in windows other than 'CONSOLE);
;		revamped PCS-INITIAL-ARGUMENTS per 3.0 changes to cmd line
;  6/01/87 tc - added MAKE-STRING as autoload for PFUNARG

;;;
;;; The following functions are related in that they all envoke the
;;; compiler in some form or fashion
;;;
(define load						; LOAD
       (lambda (filename)
	 (let ((i-port (open-input-file filename)))
           (if (null? i-port)
               (error "Unable to load file" filename)
               (letrec
                 ((loop
                    (lambda (form)
                      (cond ((eof-object? form)
                             (close-input-port i-port)
                             'ok)
                            (else
                              (eval form)
                              (loop (read i-port)))))))
                 (let ((form (read i-port)))
                   (if (eq? form '#!fast-load)
                       (begin
                         (close-input-port i-port)
                         (fast-load filename))
                       (loop form))))))))

(define compile-file					; COMPILE-FILE
       (lambda (filename1 filename2)
	 (if (or (not (string? filename1))
		 (not (string? filename2))
		 (equal? filename1 filename2))
	     (%error-invalid-operand-list 'COMPILE-FILE
		    filename1
		    filename2)
	     (let ((i-port (open-input-file filename1)))
	       (let ((o-port (open-output-file filename2)))
		 (set-line-length! 74 o-port)
		 (letrec
		   ((loop
			(lambda (form)
			  (if (eof-object? form)
			      (begin (close-input-port i-port)
				     (close-output-port o-port)
				     'ok)
			      (begin			; no COMPILE-FORMS
				 (compile-to-file form)
				 (set! form '())     ; for GC
				 (loop (read i-port))))))
		    (compile-to-file
			(lambda (form)
			  (let ((cform (compile form)))
			    (write (list '%execute (list 'quote cform))
				   o-port)
			    (newline o-port)
			    (%execute cform)))))
		 (loop (read i-port))))))))

(define %compile-timings '())

(define %compile					; %COMPILE
  (lambda (exp . time?)
    (when time? (gc))
    (let ((time '())
          (t0 (runtime)))
      (set! pcs-local-var-count 0)
      (set! pcs-error-flag #!false)
      (set! pcs-verbose-flag (not time?))
      (set! pcs-binary-output #!false)
      (set! pme= (pcs-macro-expand exp))
      (if pcs-error-flag
          (error "[Compilation terminated because of errors]")
          (begin
            (set! time (cons (- (runtime) t0) time))
            (set! psimp= (pcs-simplify pme=))
            (set! time (cons (- (runtime) t0) time))
            (pcs-closure-analysis psimp=)
            (set! time (cons (- (runtime) t0) time))
            (set! pcg= (pcs-gencode psimp=))
            (set! time (cons (- (runtime) t0) time))
            (set! ppeep= (pcs-postgen pcg=))
            (set! time (cons (- (runtime) t0) time))
            (set! pasm= (pcs-assembler ppeep=))
            (set! time (cons (- (runtime) t0) time))
            (set! pcs-verbose-flag #!false)
            (when time?
                  (set! %compile-timings
                        (cons (reverse! time) %compile-timings)))
            pasm=)))))

;
; Make compiler re-entrant (or more so, at any rate). The problem arises
; when a macro evokes EVAL and thus COMPILE during macro expansion i9n PME
;
(define compile '())                                    ; COMPILE

(let ((ge (%set-global-environment user-global-environment)))
      (set! compile
	(lambda (exp)
	  (let* ((vc pcs-local-var-count)	; save
		 (vf pcs-verbose-flag)
		 (ef pcs-error-flag)
		 (bo pcs-binary-output)
		 (gensym-string (access string (procedure-environment gensym)))
		 (gensym-counter (access counter (procedure-environment gensym)))
		 (result (pcs-assembler (pcs-compile-to-AL exp))))
	    (set! pcs-local-var-count vc)	; restore
	    (set! pcs-verbose-flag vf)
	    (set! pcs-error-flag ef)
	    (set! pcs-binary-output bo)
	    (set! (access string (procedure-environment gensym)) gensym-string)
	    (set! (access counter (procedure-environment gensym)) gensym-counter)
	    (pcs-clear-registers)
	    result)))
      (%set-global-environment ge))

(define pcs-compile-to-AL				; PCS-COMPILE-TO-AL
  (lambda (exp)
    (set! pcs-local-var-count 0)
    (set! pcs-error-flag #!false)
    (set! pcs-binary-output #!true)
    (set! pcs-verbose-flag #!false)
    (let ((t1 (pcs-macro-expand exp)))
      (if pcs-error-flag
          (error "[Compilation terminated because of errors]")
          (begin
            (set! exp '())                          ; for GC
            (pcs-clear-registers)
            (let ((t2 (pcs-simplify t1)))
              (pcs-closure-analysis t2)
              (let ((t3 (pcs-gencode t2)))
                (set! t2 '())                               ; for GC
                (pcs-clear-registers)
                (let ((t4 (pcs-postgen t3)))
                  (pcs-clear-registers)
                  t4))))))))

(define pcs-execute-AL					; PCS-EXECUTE-AL
  (lambda (al)
    (let ((t1 (pcs-assembler al)))
      (pcs-clear-registers)
      (%execute t1))))

(define optimize!					; OPTIMIZE!
  (lambda args
    (let ((flag (or (null? args)(car args))))
      (set! pcs-permit-peep-1 flag)
      (set! pcs-permit-peep-2 flag))))


;;;; Syntax Checking Functions
;;;
;;; These functions may be used by macros and other syntax transformers
;;; to help find violations of Scheme syntax rules.  Note that these
;;; check only the syntax, not semantics, of the program fragments they
;;; are defined for.  It is the caller's responsibility, for example, to
;;; verify that all of the identifiers bound in a LETREC are distinct.
;;; PCS-CHK-PAIRS can't do so, because it is called to verify pairs for
;;; both LETREC and LET*.

(define pcs-chk-id					; PCS-CHK-ID
  (lambda (e y)
    (when (not (symbol? y))
          (syntax-error "Invalid identifier in expression" y e))))

(define (pcs-chk-length= e y n)				; PCS-CHK-LENGTH=
  (cond ((and (null? y)(zero? n))
         '())
        ((null? y)
         (syntax-error "Expression has too few subexpressions" e))
        ((atom? y)
         (syntax-error (if (atom? e)
                           "List expected"
                           "Expression ends with `dotted' atom")
                       e))
        ((zero? n)
         (syntax-error "Expression has too many subexpressions" e))
        (else
          (pcs-chk-length= e (cdr y) (sub1 n)))))

(define (pcs-chk-length>= e y n)			; PCS-CHK-LENGTH>=
  (cond ((and (null? y)( < n 1))
         '())
        ((atom? y)
         (pcs-chk-length= e y -1))
        (else
          (pcs-chk-length>= e (cdr y) (sub1 n)))))

(define (pcs-chk-bvl e bvl dot-ok?) 		; PCS-CHK-BVL
  (letrec ((oops
             (lambda () (syntax-error "Invalid identifier list" e))))
    (cond ((atom? bvl)
           (or (null? bvl)(and dot-ok? (pcs-chk-bvar bvl))
               (oops)))
          ((pcs-chk-bvar (car bvl))
           (pcs-chk-bvl e (cdr bvl) dot-ok?))
          (else
            (oops)))))

(define (pcs-chk-pairs e pairs)				; PCS-CHK-PAIRS
  (letrec ((oops
             (lambda () (syntax-error "Invalid pair binding list" e))))
    (if (atom? pairs)
        (or (null? pairs)
            (oops))
        (let ((pr (car pairs)))
          (if (or (atom? pr)
                  (not (pcs-chk-bvar (car pr)))
                  (atom? (cdr pr))
                  (not (null? (cddr pr))))
              (oops)
              (pcs-chk-pairs e (cdr pairs)))))))


(define pcs-chk-bvar					; PCS-CHK-BVAR
  (lambda (id)
    (if (or (not (symbol? id))
            (getprop id 'PCS*MACRO)
            (memq id '(QUOTE LAMBDA IF SET!
                             BEGIN LETREC DEFINE))
            (and (memq id '(T NIL))
                 pcs-integrate-t-and-nil))
        (syntax-error "Invalid bound variable name" id)
        #!true)))

;;; EXPAND, EXPAND-MACRO and EXPAND-MACRO-1 expand macro calls. EXPAND-MACRO
;;; and EXPAND-MACRO-1 only expand the outer-level form and leave sub-forms 
;;; alone.  EXPAND-MACRO-1 does so only once, while EXPAND-MACRO does so 
;;; repeatedly until there is no change. EXPAND expands form and all subforms
;;; completely.

(define expand-macro					; EXPAND-MACRO
  (lambda (exp)
    (let ((expansion (expand-macro-1 exp)))
      (if (or (atom? exp) (equal? expansion exp))
	  expansion
	  (expand-macro expansion)))))

(define expand-macro-1					; EXPAND-MACRO-1
  (lambda (x)
    (cond ((symbol? x)
	   (let ((entry (getprop x 'PCS*MACRO)))
	     (if (null? entry)
                 x
                 (if (pair? entry)
                     (if (eq? (car entry) 'ALIAS)
                         (cdr entry))
                     (syntax-error "Macro or special form name used as a variable"
                                   x)))))
	  ((pair? x)
	   (let* ((f  (car x))
		  (ef (if (pair? f) (expand-macro f) f))
		  (a  (cdr x)))
	     (if (symbol? ef)
                 (let ((macfun (getprop ef 'PCS*MACRO)))
                   (cond ((null? macfun)
                          (cons ef a))
                         ((pair? macfun)
                          (cons (cdr macfun) a))
                         (else
                           (macfun (cons ef a)))))
                 (cons ef a))))
	  (else x))))

(define expand						; EXPAND
  (letrec ((expand-item
             (lambda (item)
               (if (pair? item) (expand item) item))))
    (lambda (exp)
      (let ((expansion (expand-macro exp)))
        (map expand-item expansion)))))

;;;
;;; Set up EDWIN so that it may be loaded into its own environment
;;;

(define initiate-edwin					; INITIATE-EDWIN
  (lambda ()
    (unbind 'edwin user-global-environment)
    (set! (access edwin-environment user-global-environment)
	  (make-hashed-environment))
    (%reify! edwin-environment 0 user-initial-environment)
    (autoload-from-file (%system-file-name "edwin0.fsl")
      '(edwin)
      edwin-environment)
    (edwin)))

(define edwin initiate-edwin)				; EDWIN

;;;
;;; Set up compiler-related global variables
;;;

(BEGIN
 (define %pcs-stl-debug-flag #!false)
 (define %pcs-stl-history    '(%PCS-STL-HISTORY))  ; getprop tag
 (define pcs-local-var-count	   0)
 (define pcs-integrate-integrables  #!true)
 (define pcs-integrate-primitives   #!true)
 (define pcs-integrate-T-and-NIL    #!true)
 (define pcs-integrate-define	   #!true)
 (define pcs-debug-mode		   #!false)	; debug mode OFF
 (define pcs-permit-peep-1	   #!true)	; optimization ON
 (define pcs-permit-peep-2	   #!true)
 (define pcs-verbose-flag	   #!false)
 (define pcs-display-warnings	   #!true)
 (define pme=   '())
 (define psimp= '())
 (define pcg=   '())
 (define ppeep= '())
 (define pasm=  '())
)

;;; Evaluation

;;; EVAL is part interpreter, but calls the compiler for complicated
;;; expressions.  In particular, it does not do any bindings
;;; interpretively, since they would have to be first-class
;;; environments and the compiler might be able to do better.

(define eval
  (letrec
    ((eval-exp
       (lambda (xx env)
         (let ((x (expand-macro xx)))
           (if (pair? x)
               (case (car x)
                 ((QUOTE)		(eval-quote x env))
                 ((IF) 		(eval-if x env))
                 ((SET!)		(eval-set! x env))
                 ((DEFINE)		(eval-define x env))
                 ((BEGIN)		(eval-begin x env))
                 ((LET
		    LET*
		    LETREC
		    LAMBDA )		(eval-compile x env))
                 ((%%GET-FLUID%%)	(eval-fluid x env))
                 ((%%SET-FLUID%%)	(eval-set-fluid! x env))
                 ((THE-ENVIRONMENT)	env)
                 ((PCS-CODE-BLOCK)	(eval-execute x env))
                 (else 		(eval-application x env)))
               (eval-atom x env)))))
     
     (lookup-binding					; LOOKUP-BINDING
       (lambda (sym)
         ; The following is the object code to lookup/fetch
         ; the binding of sym. It must be passed to %execute with
         ; the desired environment.
         (list 'pcs-code-block 1 4 (list sym)
               '( 7 4 0       ; Ld-global r1,sym
                    59))))       ; exit
     
     (eval-atom					; EVAL-ATOM
       (lambda (x env)
         (cond ((not (symbol? x))			  x)
               ((memq x '(#!TRUE #!FALSE #!UNASSIGNED))  x)
               (else
		 (let ((entry (and PCS-INTEGRATE-T-AND-NIL
				   (assq x '((T  #T) (NIL  #F))))))
		   (if entry
                       (cadr entry)
                       ;else
                       (or (lookup-integrable x env)
                           (eval-execute (lookup-binding x) env))))))))
     
     (lookup-integrable
       (lambda (x env)
         (let ((info (getprop x 'PCS*PRIMOP-HANDLER)))
           (and info
                (pair? info)
                (eval-exp (cdr info) env)))))
     
     (eval-quote					; EVAL-QUOTE
       (lambda (x env)
         (pcs-chk-length= x x 2)
         (cadr x)))
     
     (eval-id-error
       (lambda (err caller env)
         (syntax-error
           (string-append "Invalid identifier for " caller ": ") err)))
     
     
     (eval-if 					; EVAL-IF
       (lambda (x env)
         (if (or (atom? (cdr x))	; No Pred
                 (atom? (cddr x))	; No Then
                 (pair? (cdddr x)))	; has ELSE
	     (pcs-chk-length= x x 4)
	     (pcs-chk-length= x x 3))
         (cond ((eval-exp (cadr x) env)
                (eval-exp (caddr x) env))
               ((pair? (cdddr x))
                (eval-exp (cadddr x) env))
               (else
		 #!FALSE))))
     
     
     (set-var-value					; SET-VAR-VALUE
       (lambda (sym value)
         ; The following is the object code code to set the value
         ; of a variable. It must be passed to %execute with the
         ; desired environment.
         (list 'pcs-code-block 2 7 (list sym value)
               '( 1 4 1        ; Load r1, value
                    15 4 0	; St-glob-env r1,sym
                    59))))	; exit
     
     (eval-set!					; EVAL-SET!
       (lambda (x env)
         (pcs-chk-length= x x 3)
         (let* ((id	(cadr x))
                (var	(expand-macro id))
                (value (eval-exp (caddr x) env)))
           (cond ((not (pair? var))
                  (cond ((or (not (symbol? var))
                             (not (eq? var (expand-macro var))))
                         (eval-id-error var "SET!" env))
                        ((getprop var 'PCS*PRIMOP-HANDLER)
                         ; this is for primitives and define-integrables
                         (eval-compile x env))
                        (else
			  (eval-execute (SET-VAR-VALUE var value) env))))
                 (else
		   (eval-id-error var "SET!" env))))))
     
     (def-var   					; DEF-VAR
       (lambda (sym value)
         ; The following is the object code code to define a variable
         ; in a given environment. It must be passed to %execute with the
         ; desired environment.
         (list 'pcs-code-block 2 7 (list sym value)
               '( 1  4 1        ; Load r1, value
                  31 4 0	; define!  value,sym
                  59))))	; exit
     
     (eval-define					; EVAL-DEFINE
       (lambda (x env)
         (pcs-chk-length>= x x 3)
         (if (and (pair? (caddr x))
                  (memq (caaddr x) '(LAMBDA NAMED-LAMBDA)))
             (eval-compile x env)
             ;else
             (let* ((id	  (cadr x))
                    (var   (expand-macro id))
                    (value (eval-exp (caddr x) env)))
               (cond ((not (pair? var))
                      (cond ((or (not (symbol? var))
                                 (not (eq? var (expand-macro var))))
                             (eval-id-error var "DEFINE" env))
                            ((getprop var 'PCS*PRIMOP-HANDLER)
                             ; this is for primitives and define-integrables
                             (eval-compile x env))
                            (else
			      (eval-execute (DEF-VAR var value) env)
                              id)))
                     (else
                       (eval-id-error var "DEFINE" env)))))))
     
     
     (eval-begin					; EVAL-BEGIN
       (lambda (x env)
         (pcs-chk-length>= x x 1)
         (let loop ((x (cdr x)))
           (if (null? (cdr x))
               (eval-exp (car x) env)
               (begin
                 (eval-exp (car x) env)
                 (loop (cdr x)))))))
     
     (lookup-fluid					; LOOKUP-FLUID
       (lambda (sym)
         ; The following is the object code to lookup/fetch the
         ; fluid binding of sym. It must be passed to %execute with
         ; the desired environment.
         (list 'pcs-code-block 1 4 (list sym)
               '( 8 4 0       ; Ld_fl r1,sym
                    59))))       ; exit
     
     (eval-fluid					; EVAL-FLUID
       (lambda (x env)
         (pcs-chk-length= x x 2)
         (eval-execute (lookup-fluid (eval-exp (cadr x) env)) env)))
     
     (set-fluid-var					; SET-FLUID-VAR
       (lambda (sym value)
         ; The following is the object code to set the value of a
         ; fluid variable. It must be passed to %execute with the
         ; desired environment.
         (list 'pcs-code-block 2 7 (list sym value)
               '( 1 4 1        ; Load  r1, value
                    16 4 0	; St-fl r1,sym
                    59))))	; exit
     
     (eval-set-fluid! 				; EVAL-SET-FLUID!
       (lambda (x env)
         (pcs-chk-length>= x x 2)
         (let ((sym  (eval-exp (cadr x) env))
               (val (eval-exp (caddr x) env)))
           (pcs-chk-id x sym)
           (eval-execute (set-fluid-var sym val) env))))
     
     (eval-application				; EVAL-APPLICATION
       (lambda (x env)
         (pcs-chk-length>= x x 1)
         (let ((proc (eval-exp (car x) env)))
           (when (not (or (procedure? proc)
                          (and (pair? proc)
                               (eq? (car proc) 'LAMBDA))))
                 (error-procedure "Attempt to call a non-procedural object"
                                  (cons proc (cdr x))
                                  env))
           (let ((args (eval-args (cdr x) env)))
             (let* ((saved-env (%set-global-environment env))
                    (result (apply proc args)))
               (%set-global-environment saved-env)
               result)))))
     
     (eval-args					; EVAL-ARGS
       (lambda (x env)
         (if (null? x)
             '()
             (cons (eval-exp  (car x) env)
                   (eval-args (cdr x) env)))))
     
     (eval-compile					; EVAL-COMPILE
       (lambda (x env)
         (eval-execute (compile x) env)))
     
     (eval-execute					; EVAL-EXECUTE
       (lambda (x env)
         (let* ((saved-env (%set-global-environment env))
                (result (%execute x)))
           (%set-global-environment saved-env)
           result)))
     
     ) ; letrec vars
    
    (lambda (exp . rest)
      (let* ((env (cond ((null? rest)
                         (let ((e (%set-global-environment
                                    user-initial-environment)))
                           (%set-global-environment e)
                           e))
			((not (environment? (car rest)))
			 (%error-invalid-operand 'EVAL (car rest)))
			(else
                          (car rest))))
	     (result (eval-exp exp env)))
        result))))