;; Copyright (c) 1991-2002, The Numerical ALgorithms Group Ltd.
;; All rights reserved.
;;
;; Redistribution and use in source and binary forms, with or without
;; modification, are permitted provided that the following conditions are
;; met:
;;
;;     - Redistributions of source code must retain the above copyright
;;       notice, this list of conditions and the following disclaimer.
;;
;;     - Redistributions in binary form must reproduce the above copyright
;;       notice, this list of conditions and the following disclaimer in
;;       the documentation and/or other materials provided with the
;;       distribution.
;;
;;     - Neither the name of The Numerical ALgorithms Group Ltd. nor the
;;       names of its contributors may be used to endorse or promote products
;;       derived from this software without specific prior written permission.
;;
;; THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
;; IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
;; TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
;; PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
;; OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
;; EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
;; PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
;; PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
;; LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
;; NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
;; SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


; NAME:    Compiler Utilities Package

; PURPOSE: Comp is a modified version of Compile which is a preprocessor for
;          calls to Lisp Compile.  It searches for variable assignments that use
;          (SPADLET a b). It allows you to create local variables without
;          declaring them local by moving them into a PROG variable list.
;          This is not an ordinary SPADLET.  It looks and is used like a SETQ.
;          This preprocessor then collects the uses and creates the PROG.
;
;          SPADLET is defined in Macro.Lisp.
;
;          Comp recognizes as new lambda types the forms ILAM, SPADSLAM, SLAM,
;          and entries on $clamList.  These cache results.  ("Saving LAMbda".)
;          If the function is called with EQUAL arguments, returns the previous
;          result computed.
;
;          The package also causes traced things which are recompiled to
;          become untraced.

(in-package "BOOT")

(export '(Comp FluidVars LocVars OptionList SLAM SPADSLAM ILAM FLUID))

;;; Common Block section

(defparameter FluidVars nil)
(defparameter LocVars nil)
; (defparameter OptionList nil) defined in nlib.lisp
(defparameter SpecialVars nil)

(defun |compAndDefine| (L)
  (let ((*comp370-apply* (function print-and-eval-defun)))
    (declare (special *comp370-apply*))
    (COMP L)))

(defun COMP (L) (MAPCAR #'COMP-2 (MAPCAN #'COMP-1 L)))

;;(defun |compQuietly| (L)
;;  (let (U CUROUTSTREAM)
;;    (declare (special CUROUTSTREAM))
;;    (ADDOPTIONS 'LISTING NULLOUTSTREAM)                     
;;    (SETQ CUROUTSTREAM NULLOUTSTREAM)                       
;;    (setq U (COMP L))
;;    (setq OPTIONLIST (CDDR OPTIONLIST))
;;    U))

(defun |compQuietly| (fn)
  (let ((*comp370-apply*
         (if |$InteractiveMode|
             (if |$compileDontDefineFunctions| #'compile-defun #'eval-defun)
           #'print-defun))
     ;; following creates a null outputstream if $InteractiveMode
        (*standard-output*
         (if |$InteractiveMode| (make-broadcast-stream)
           *standard-output*)))
    (COMP fn)))

#-:CCL
(defun |compileFileQuietly| (fn) 
  (let (
     ;; following creates a null outputstream if $InteractiveMode
        (*standard-output*
         (if |$InteractiveMode| (make-broadcast-stream)
           *standard-output*)))
    (COMPILE-FILE fn)))

#+:CCL
(defun |compileFileQuietly| (fn)
  (let (
     ;; following creates a null outputstream if $InteractiveMode
     (*standard-output*
       (if |$InteractiveMode| (make-broadcast-stream) *standard-output*)))
     ;; The output-library is not opened before use unless set explicitly
     (if (null output-library)
         (|openOutputLibrary| 
           (setq |$outputLibraryName|
            (if (null |$outputLibraryName|)
                (make-pathname :directory (get-current-directory)
                               :name "user.lib")
                (if (filep |$outputLibraryName|) (truename |$outputLibraryName|)
                                                 |$outputLibraryName|)))))
     (compile-lib-file fn)))

;; The following are used mainly in setvars.boot
(defun notEqualLibs (u v)
  (if (string= u (library-name v)) (seq (close-library v) t) nil))

(defun |dropInputLibrary| (lib) 
  ;; Close any existing copies of this library on the input path
 (setq input-libraries
  (delete lib input-libraries :test #'notEqualLibs )))

(defun |openOutputLibrary| (lib)
  (|dropInputLibrary| lib)
  (setq output-library (open-library lib 't))
  (setq input-libraries (cons output-library input-libraries)) )

(defun |addInputLibrary| (lib)
  (|dropInputLibrary| lib)
   (setq input-libraries (cons (open-library lib) input-libraries)) )



;;(defun |compileQuietly| (L) (PROG (U CUROUTSTREAM)
;;  ;; calls lisp system COMPILE or DEFINE                  
;;  (ADDOPTIONS 'QUIET 'T)                                  
;;  (ADDOPTIONS 'LISTING NULLOUTSTREAM)                     
;;  (SETQ CUROUTSTREAM NULLOUTSTREAM)                       
;;  (SETQ U (COND                                           
;;    (|$compileDontDefineFunctions| (COMPILE L))           
;;    ('T (DEFINE L))))                                     
;;  (SETQ OPTIONLIST (CDDR OPTIONLIST))                     
;;  (RETURN U)   ))                                       

(defun |compileQuietly| (fn)
  (let ((*comp370-apply*
         (if |$InteractiveMode|
             (if |$compileDontDefineFunctions| #'compile-defun #'eval-defun)
           #'print-defun))
     ;; following creates a null outputstream if $InteractiveMode
        (*standard-output*
         (if |$InteractiveMode| (make-broadcast-stream)
           *standard-output*)))
    (COMP370 fn)))

(defun COMP-1 (X)
  (let* ((FNAME (car X))
         ($FUNNAME FNAME)
         ($FUNNAME_TAIL (LIST FNAME))
         (LAMEX (second X))
         ($closedfns nil))
    (declare (special $FUNNAME $FUNNAME_TAIL $CLOSEDFNS))
    (setq LAMEX (COMP-TRAN LAMEX))
    (COMP-NEWNAM LAMEX)
    (if (fboundp FNAME)
        (format t "~&~%;;;     ***       ~S REDEFINED~%" FNAME))
    (CONS (LIST FNAME LAMEX) $CLOSEDFNS)))

(defun Comp-2 (args &aux name type argl bodyl junk)
    (dsetq (NAME (TYPE ARGL . BODYL) . JUNK) args)
    (cond (JUNK (MOAN (format nil "******pren error in (~S (~S ...) ...)" NAME TYPE)))
          ((eq TYPE 'SLAM) (COMP-SLAM NAME ARGL BODYL))
          ((LASSQ NAME |$clamList|) (|compClam| NAME ARGL BODYL |$clamList|))
          ((eq TYPE 'SPADSLAM) (COMP-SPADSLAM NAME ARGL BODYL))
          ((eq TYPE 'ILAM) (COMP-ILAM NAME ARGL BODYL))
          ((setq BODYL (LIST NAME (CONS TYPE (CONS ARGL BODYL))))
           (if |$PrettyPrint| (pprint bodyl))
           (if (null $COMPILE) (SAY "No Compilation")
               (COMP370 (LIST BODYL)))
           NAME)))

;; used to be called POSN - but that interfered with a CCL function
(DEFUN POSN1 (X L) (position x l :test #'equal))

(DEFUN COMP-ILAM (NAME ARGL BODYL)
  (let* ((FARGL (NLIST (LENGTH ARGL) '(GENSYM)))
         (BODYLP (SUBLISLIS FARGL ARGL BODYL)))
        (MAKEPROP NAME 'ILAM T)
        (SET NAME (CONS 'LAMBDA (CONS FARGL BODYLP)))
        NAME))

(DEFUN COMP-SPADSLAM (NAME ARGL BODYL)
  (let* ((AL (INTERNL NAME ";AL"))
         (AUXFN (INTERNL NAME ";"))
         (G1 (GENSYM))
         (G2 (GENSYM))
         (U (COND ((NOT ARGL) (LIST NIL NIL (LIST AUXFN)))
                  ((NOT (CDR ARGL))
                   (LIST (LIST G1) (LIST '|devaluate| G1) (LIST AUXFN G1)))
                  ((LIST G1
                         (LIST '|devaluateList| G1)
                         (LIST 'APPLY (LIST 'FUNCTION AUXFN) G1)))))
         (ARG (first U))
         (ARGTRAN (second U))
         (APP (third U))
         (LAMEX  `(lam ,ARG
                       (let (,g2)
                         (cond ,(COND (ARGL `((setq ,g2 (|assoc| ,argtran ,al))
                                              (cdr ,g2)))
                                      ((LIST AL)))
                               ,(COND (ARGL
                                       `(t(setq ,al(|cons5|(cons ,argtran
                                                                 (setq ,g2 ,app))
                                                           ,al))
                                          ,g2))
                                      (`(t (setq ,al ,app)))))))))
    (setandfile AL NIL)
    (setq U (LIST NAME LAMEX))
    (if |$PrettyPrint| (PRETTYPRINT U))
    (COMP370 (LIST U))
    (setq U (LIST AUXFN (CONS 'LAMBDA (CONS ARGL BODYL))))
    (COND (|$PrettyPrint| (PRETTYPRINT U)))
    (COMP370 (LIST U))
    NAME))

(DEFUN COMP-SLAM (NAME ARGL BODYL)
  (let* ((AL (INTERNL NAME ";AL"))
         (AUXFN (INTERNL NAME ";"))
         (G1 (GENSYM))
         (G2 (GENSYM))
         (U (COND ((NOT ARGL) `(nil (,auxfn)))
                  ((NOT (CDR ARGL)) `((,g1)(,auxfn ,g1)))
                  (`(,g1 (applx (function ,auxfn) ,g1)))))
         (ARG (CAR U))
         (APP (CADR U))
         (LAMEX
           (LIST 'LAM ARG
                 (LIST 'PROG (LIST G2)
                       (LIST 'RETURN
                             (LIST 'COND
                                   (COND (ARGL
                                          `((setq ,G2 (|assoc| ,G1 ,AL))
                                            (CDR ,G2)))
                                         ((LIST AL)))
                                   (COND (ARGL (LIST ''T `(setq ,G2 ,APP)
                                                     (LIST 'SETQ AL
                                                           `(CONS
                                                              (CONS ,G1 ,G2) ,AL))
                                                     G2))
                                         ((LIST ''T `(setq ,AL ,APP))))))))))
    (set AL NIL)
    (setq U (LIST NAME LAMEX))
    (if |$PrettyPrint| (PRETTYPRINT U))
    (COMP370 (LIST U))
    (setq U (LIST AUXFN (CONS 'LAMBDA (CONS ARGL BODYL))))
    (if |$PrettyPrint| (PRETTYPRINT U))
    (COMP370 (LIST U))
    NAME))

(DEFUN COMP-NEWNAM (X)
  (let (y u)
    (cond ((ATOM X) NIL)
          ((ATOM (setq Y (CAR X)))
          ;; (AND (IDENTP Y) (setq U (GET Y 'NEWNAM)) (RPLACA X U))
           (AND (NOT (eq Y 'QUOTE)) (COMP-NEWNAM (CDR X)))
           (WHEN (and (EQ Y 'CLOSEDFN) (boundp '$closedfns))
                 (SETQ U (MAKE-CLOSEDFN-NAME))
                 (PUSH (list U (CADR X)) $closedfns)
                 (rplaca x 'FUNCTION)
                 (rplaca (cdr x) u)))
          (t (COMP-NEWNAM (CAR X)) (COMP-NEWNAM (CDR X))))))

(defun make-closedfn-name ()
  (internl $FUNNAME "!" (STRINGIMAGE (LENGTH $CLOSEDFNS))))

(DEFUN COMP-TRAN (X)
  "SEXPR<FN. BODY> -> SEXPR"
  (let ((X (COMP-EXPAND X)) FluidVars LocVars SpecialVars)
    (COMP-TRAN-1 (CDDR X))
    (setq X (list (first x) (second x)
                  (if (and (null (cdddr x))
                           (or (atom (third x))
                               (eq (car (third x)) 'SEQ)
                               (not (contained 'EXIT (third x)))))
                      (caddr x)
                      (cons 'SEQ (cddr x))))) ;catch naked EXITs
    (let* ((FluidVars (REMDUP (NREVERSE FLUIDVARS)))
           (LOCVARS (S- (S- (REMDUP (NREVERSE LOCVARS)) FLUIDVARS)
                        (LISTOFATOMS (CADR X))))
           (LVARS (append fluidvars LOCVARS)))
      (let ((fluids (S+ fluidvars SpecialVars)))
        (setq x
              (if fluids
                  `(,(first x) ,(second x)
                    (prog ,lvars (declare (special . ,fluids))
                      (return ,(third x))))
                  (list (first x) (second x)
                     (if (or lvars (contained 'RETURN (third x)))
                         `(prog ,lvars (return ,(third x)))
                         (third x)) )))))
    (let ((fluids (S+ (comp-fluidize (second x)) SpecialVars)))
      (if fluids
          `(,(first x) ,(second x) (declare (special . ,fluids)) . ,(cddr x))
          `(,(first x) ,(second x) . ,(cddr x))))))

; Fluidize: Returns a list of fluid variables in X

(DEFUN COMP-FLUIDIZE (X)
  (COND ((AND (symbolp X)
              (NE X '$)
              (NE X '$$)
              (char= #\$ (ELT (PNAME X) 0))
              (NOT (DIGITP (ELT (PNAME X) 1))))
         x)
        ((atom x) nil)
        ((eq (first X) 'FLUID) (second X))
        ((let ((a (comp-fluidize (first x)))
               (b (comp-fluidize (rest x))))
           (if a (cons a b) b)))))

(DEFUN COMP\,FLUIDIZE  (X) (COND
  ((AND (IDENTP X)
        (NE X '$)
        (NE X '$$)
        (char= #\$ (ELT (PNAME X) 0)) (NULL (DIGITP (ELT (PNAME X) 1))))
    (LIST 'FLUID X))
  ((ATOM X) X)
  ((EQ (QCAR X) 'FLUID) X)
  ('T (PROG (A B)
      (SETQ A (COMP\,FLUIDIZE (QCAR X)))
      (SETQ B (COMP\,FLUIDIZE (QCDR X)))
      (COND ((AND (EQ A (QCAR X)) (EQ B (QCDR X)))
              (RETURN X))
            ('T (RETURN (CONS A B)) )) )    )))

; NOTE: It is potentially dangerous to assume every occurrence of element of
; $COMP-MACROLIST is actually a macro call

(defparameter $COMP-MACROLIST
  '(COLLECT REPEAT SUCHTHATCLAUSE THETA COLLECTV COLLECTVEC
            THETA1 SPADREDUCE SPADDO)
  "???")

(DEFUN COMP-EXPAND (X)
  (COND ((atom x) x)
        ((eq (CAR X) 'QUOTE) X)
        ((memq (CAR X) $COMP-MACROLIST)
         (comp-expand (macroexpand-1 x)))
        ((let ((a (comp-expand (car x)))
               (b (comp-expand (cdr x))))
           (if (AND (eq A (CAR X)) (eq B (CDR X)))
               x
               (CONS A B))))))

(DEFUN COMP-TRAN-1 (X)
  (let (u)
    (cond ((ATOM X) NIL)
          ((eq (setq U (CAR X)) 'QUOTE) NIL)
          ((AND (eq U 'MAKEPROP) $TRACELETFLAG (RPLAC (CAR X) 'MAKEPROP-SAY) NIL)
           NIL)
           ; temporarily make TRACELET cause MAKEPROPs to be reported
          ((MEMQ U '(DCQ RELET PRELET SPADLET SETQ LET) )
           (COND ((NOT (eq U 'DCQ))
                  (COND ((OR (AND (eq $NEWSPAD T) (NOT $BOOT))
                             (MEMQ $FUNNAME |$traceletFunctions|))
                         (NCONC X $FUNNAME_TAIL)
                         (RPLACA X 'LETT))
                        ; this devious trick (due to RDJ) is needed since the compile
                        ; looks only at global variables in top-level environment;
                        ; thus SPADLET cannot itself test for such flags (7/83).
                        ($TRACELETFLAG (RPLACA X '/TRACE-LET))
                        ((eq U 'LET) (RPLACA X 'SPADLET)))))
           (COMP-TRAN-1 (CDDR X))
           (AND (NOT (MEMQ U '(setq RELET)))
                (COND ((IDENTP (CADR X)) (PUSHLOCVAR (CADR X)))
                      ((EQCAR (CADR X) 'FLUID)
                       (PUSH (CADADR X) FLUIDVARS)
                       (RPLAC (CADR X) (CADADR X)))
                      ((mapc #'pushlocvar (listofatoms (cadr x))) nil))))
          ((and (symbolp u) (GET U 'ILAM))
           (RPLACA X (EVAL U)) (COMP-TRAN-1 X))
          ((MEMQ U '(PROG LAMBDA))
           (PROG (NEWBINDINGS RES)
                 (setq NEWBINDINGS NIL)
                 (mapcar #'(lambda (Y)
                             (COND ((NOT (MEMQ Y LOCVARS))
                                    (setq LOCVARS (CONS Y LOCVARS))
                                    (setq NEWBINDINGS (CONS Y NEWBINDINGS)))))
                         (second x))
                 (setq RES (COMP-TRAN-1 (CDDR X)))
                 (setq locvars (remove-if #'(lambda (y) (memq y newbindings))
                                          locvars))
                 (RETURN (CONS U (CONS (CADR X) RES)) )) )
          ((PROGN (COMP-TRAN-1 U) (COMP-TRAN-1 (CDR X)))))))

(DEFUN PUSHLOCVAR (X)
  (let (p)
    (cond ((AND (NE X '$)
                (char= #\$ (ELT (setq P (PNAME X)) 0))
                (NOT (char= #\, (ELT P 1)))
                (NOT (DIGITP (ELT P 1)))) NIL)
          ((PUSH X LOCVARS)))))

(defmacro PRELET (L) `(spadlet . ,L))
(defmacro RELET (L) `(spadlet . ,L))
(defmacro PRESET (L) `(spadlet . ,L))
(defmacro RESET (L) `(spadlet . ,L))