(in-package "XLISP")

(export '(compile compile-file *compile-verbose* *compile-print*))

(defpackage "XLSCMP"
  (:use "XLISP")
  (:import-from "XLISP" "*CMP-SETF*" "*CMP-STRUCTS*" "*CMP-GLOBAL-MACROS*"
		"*CMP-MACROS*" "*CMP-SPECIALS*"	#+xlisp-stat "ADD-METHOD"))

(in-package "XLSCMP")

#|
put in package stuff
move macros to cmpmacr.lsp
move cps stuff to convert.lsp
|#

(require "backquot")

(require "cmpmacro")
(require "convert")
(require "cells")
(require "simplify")
(require "lift")
(require "gencode")
(require "peephole")
(require "assemble")

#|
Macros currently defined (**** need to be checked over ***):

DEFSETF PUSH PUSHNEW STEP WITH-INPUT-FROM-STRING WITH-OPEN-FILE
WITH-OUTPUT-TO-STRING


Macros needed (check which are really needed):

assert call-method ccase check-type ctypecase decf declaim defclass
defgeneric define-compiler-macro define-condition define-declaration
define-method-combination define-modify-macro define-setf-method
defmethod defpackage deftype destructuring-bind do-all-symbols
do-external-symbols do-symbols ecase encapsulated etypecase formatter
gathering generic-function handler-bind handler-case ignore-errors
in-package incf iterate loop-finish mapping multiple-value-bind
multiple-value-list multiple-value-setq next-in nth-value pop
pprint-exit-if-exhausted pprint-logical-block pprint-pop
print-unreadable-object producing psetf remf restart-bind restart-case
rotatef shiftf terminate-producing typecase with-accessors
with-compilation-unit with-condition-restarts with-hash-table-iterator
with-open-stream with-package-iterator with-simple-restarts with-slots
with-standard-io-syntax
|#

(defun pscvt (e)
  (progv '(*cmp-env*   *cmp-fenv*  *cmp-denv*   *cmp-tenv* *cmp-specials*
	   *cmp-gvars* *cmp-gfuns* *cmp-consts*
	   *cmp-specials* *cmp-macros* *cmp-setf* *cmp-structs*)
	 '(nil nil nil nil nil nil nil nil nil nil nil nil)
	 (let ((n (if (and (consp e) (eq (first e) 'lambda))
		      (convert-lambda e)
		      (convert-lambda `(lambda () ,e)))))
	   (pp-cps (merge-tests (simplify-tree (insert-cells n)))))))

		     
;;;;
;;;; Simple compiler front end
;;;;

(defun cmp (e &optional name)
  (progv '(*cmp-env* *cmp-denv* *cmp-tenv*
	   *cmp-gvars* *cmp-gfuns* *cmp-consts*)
	 '(nil nil nil nil nil nil)
	 (let ((n (if (and (consp e) (eq (first e) 'lambda))
		      (if name
			  (convert-named-lambda (cons name (rest e)))
			  (convert-lambda e))
		      (convert-lambda `(lambda () ,e)))))
	   (insert-cells n)
	   (substitute-all-variables n)
	   (collapse-null-lambda-calls n)
	   (simplify-tree n)
	   (merge-tests n)
	   (let ((pieces (lift-lambdas n)))
	     (dolist (p pieces)
	       (let ((c (second p)))
		 (extract-constants c)
		 (remove-unused-cells c)
		 ;;(mapcar #'pp-cps p)
		 ))
	     (generate-code pieces)))))

(defun pcmp (e) (pprint (cmp e)))

(defun cmp-reset ()
  (mapcar #'set
	  '(*cmp-env* *cmp-fenv* *cmp-denv* *cmp-tenv*
	    *cmp-specials* *cmp-macros* *cmp-setf* *cmp-structs*
	    *cmp-gvars* *cmp-gfuns* *cmp-consts*)
	  '(nil nil nil nil nil nil nil nil nil nil nil)))


(defparameter *cmp-files*
  '("backquot" "cmpmacro" "convert" "cells" "simplify"
    "lift" "gencode" "peephole" "assemble" "cmpfront"))

(defun compile-file-list (list &optional load)
  (progv '(*features*)
         #+unix '((WINDOWS DIALOGS COLOR UNIX X11 XLISP))
         #+macintosh '((WINDOWS DIALOGS MACINTOSH XLISP))
	 (dolist (f list)
	   (format t "~%Compiling file ~s ... ~%" f)
	   (compile-file f)
	   (format t "finished compiling file ~s~%~%" f)
	   (if load (load f)))))

(defun compile-cmp () (compile-file-list *cmp-files* t))

;;;;;
;;;;;
;;;;; New File Compiler
;;;;;
;;;;;

(defun print-compiled-form (form stream sp)
  (let ((*print-readably* t)
	(*print-symbol-package* sp))
    (format stream "~s~%" form)))

(defun accumulate-compiled-form (form outstuff)
  (rplacd (last outstuff) (list form)))

;;**** change default later?
(defvar *compile-verbose* t)
(defvar *compile-print* t)

(defvar *compile-print-symbol-package* nil)

(defvar *compile-warn-specials* nil)

(defvar *cmp-specials* nil)
(defvar *cmp-macros* nil)
(defvar *cmp-setf* nil)
(defvar *cmp-structs* nil)

#|
(defun file-name-needs-extension (fname)
  (not (eql #\.
	    (find-if-not #'(lambda (x) (or (digit-char-p x) (alpha-char-p x)))
			 fname
			 :from-end t))))
				 
(defun lsp-file-name (f)
  (if (file-name-needs-extension f)
      (concatenate 'string f ".lsp")
      f))

(defun fsl-file-name (f)
  (let ((n (length f)))
    (unless (and (<= 4 (length f)) (string= (subseq f (- n 4) n) ".lsp"))
	    (error "not a valid lisp file name -- ~s" f))
    (concatenate 'string (subseq f 0 (- n 4)) ".fsl")))
|#

(defun cmp-print-start-message (form)
  (when *compile-print*
	(let ((*print-level* 2)
	      (*print-length* 3))
	  (format t "~&; compiling ~s ... " form)
	  (force-output))))

(defun cmp-print-end-message ()
  (when *compile-print*
	(format t "done~%")
	(force-output)))

(defun expand-one (e)
  (if (consp e)
      (loop
       (if (member (first e) '(progn macrolet eval-when)) (return e))
       (multiple-value-bind (ee flag) (cmp-macroexpand-1 e)
         (if flag
	     (setq e ee)
	     (return e))))))

;;**** %set-cmp-macro???
(defun compile-one (e compile-time-too stream)
  (handler-case
   (progn
     (setq e (expand-one e))
     (case (first e)
       (progn (dolist (e (rest e)) (compile-one e compile-time-too stream)))
       (macrolet
	(let ((macs (second e))
	      (env (list nil *cmp-fenv* *cmp-macros* *cmp-global-macros*))
	      (frame nil)
	      (body (rest (rest e)))
	      (*cmp-fenv* *cmp-fenv*))
	  (dolist (m macs)
	    (push
	     (cons (first m)
		   (coerce-to-macro
		    (parse-macro (first m) (second m) (rest (rest m)) env)))
	     frame))
	  (dolist (x frame) (push x *cmp-fenv*))
	  (compile-one `(progn ,@body) compile-time-too stream)))
       (eval-when
	(let* ((sits (second e))
	       (body `(progn ,@(rest (rest e))))
	       (lt (or (member 'load sits) (member :load-toplevel sits)))
	       (ct (or (member 'compile sits) (member :compile-toplevel sits)))
	       (ex (or (member 'eval sits) (member :execute sits))))
	  (cond
	   ((or (and lt ct)
		(and lt (not ct) ex compile-time-too))
	    (compile-one body t stream))
	   ((or (and lt (not ct) (not compile-time-too))
		(and lt (not ct) (not ex)))
	    (compile-one body nil stream))
	   ((or (and (not lt) ct)
		(and (not lt) (not ct) ex compile-time-too))
	    (eval body)))))
       (t (if compile-time-too (eval e))
	  (compile-form e stream))))
   (error (c)
	  (format *error-output* "~&Compiler error: ~a~%" c)
	  ;;****(format stream "~&;;**** Error compiling exression:~%")
	  (compile-form `(eval ',e) stream))))

;;**** could ignore top level atoms, (function ...) expressions
(defun compile-form (e outstuff)
  (labels ((cmparg (e)
	     (cond
	      ((and (consp e)
		    (eq (first e) 'function)
		    (consp (second e))
		    (eq (first (second e)) 'lambda))
	       ;;**** should be put in quote?
	       `(byte-code-close (quote ,(assemble (cmp (second e))))))
	      ((consp e) (rcmp e))
	      (t e)))
	   (rcmp (e)
	     (if (consp e)
		 (let ((f (first e)))
		   (if (and (symbolp f)
			    (not (assoc f *cmp-fenv*))
			    (not (assoc f *cmp-macros*))
			    (not (assoc f *cmp-global-macros*))
			    (fboundp f)
			    (functionp (symbol-function f)))
		       `(,(first e) ,@(mapcar #'cmparg (rest e)))
		       e))
	         e)))
    (handler-case
     (let ((ce (rcmp e)))
       ;;**** don't need the test -- top level (function ...) 's are not done
       (accumulate-compiled-form
	(if (typep ce 'byte-code) ce (assemble (cmp ce)))
	;(if (typep ce 'byte-code) ce (assemble (cmp `(eval ',ce))))
	outstuff))
     (error (c)
	    (format *error-output* "~&Compiler error: ~a~%" c)
	    (accumulate-compiled-form (assemble (cmp `(eval ',e)))
				      outstuff)))))


#|
(defun compile-form (e outstuff)
    (accumulate-compiled-form (assemble (cmp e)) outstuff))
|#

(defun print-fsl-version-check (out sp)
  (let ((major xlisp::*fsl-major-version*)
	(minor xlisp::*fsl-minor-version*))
    (print-compiled-form `(xlisp::check-fsl-version ,major ,minor) out sp)))

;;**** need to avoid creating bad .fsl file
(defun compile-file (file &key
			  (output-file (merge-pathnames ".fsl" file))
			  (temporary-file (merge-pathnames "cmptmp.fsl" file))
			  (load nil)
			  ((:print *compile-print*) *compile-print*)
			  ((:verbose *compile-verbose*) *compile-verbose*)
			  ((:print-symbol-package sp) ;;**** do this cleaner
			   *compile-print-symbol-package*))
  (let* ((*package* *package*)
	 (*readtable* *readtable*)
	 (*cmp-specials* nil)
	 (*cmp-macros* nil)
	 (*cmp-setf* nil)
	 (*cmp-structs* nil)
	 (iname (merge-pathnames ".lsp" file)))
    (with-open-file (in iname)
      (with-open-file (out temporary-file :direction :output)
        (when *compile-verbose*
	      (format t "~&; compiling file ~s~%" iname)
	      (force-output))
	(print-fsl-version-check out sp)
        (let ((eof (list 'eof)))
	  (do ((e (read in nil eof) (read in nil eof)))
	      ((eq e eof))
	      (let ((outstuff (list 'progn)))
		(cmp-print-start-message e)
		(let ((*cmp-fenv* nil))
		  (compile-one e nil outstuff))
		(cmp-print-end-message)
		;; this insures that a single expression is printed for
		;; each expression read -- so common literals can be
		;; handled by circle printing/reading.
		(case (length outstuff)
		  (1 nil)
		  (2 (print-compiled-form (second outstuff) out sp))
		  (t (print-compiled-form (assemble (cmp `(eval ',outstuff)))
					  out
					  sp))))))))
    (rename-file temporary-file output-file)
    (if load (load output-file))))

;;**** this doesn't currently work for compiling macros
(defun compile-lambda-expression (fun name env macrop)
  (when env (error "COMPILE can only compile top level definitions"))
  (let ((*cmp-specials* nil)
	(*cmp-macros* nil)
	(*cmp-fenv* nil)
	(*cmp-setf* nil)
	(*cmp-structs* nil))
    (let* ((fun (if macrop (cons 'lambda (rest fun)) fun))
	   (cfun (byte-code-close (assemble (cmp fun name)))))
      (if macrop (coerce-to-macro cfun) cfun))))

(defun compile-return (f cfun)
  (if f
      (xlisp::install-function f cfun)
    cfun))
  
(defun compile (f &optional (fun (symbol-function f)))
  (cond
   ((or (typep fun 'subr) (typep fun 'fsubr) (compiled-function-p fun))
    (compile-return f fun))
   ((typep fun 'closure)
    (multiple-value-bind (flam top fname) (function-lambda-expression fun)
      (let ((name (if f f fname))
	    (macrop (eq (first flam) 'macro)))
	(compile-return f (compile-lambda-expression flam name top macrop)))))
   ((and (consp fun) (eq (first fun) 'lambda))
    (compile-return f (compile-lambda-expression fun f nil nil)))
   (t (error "bad argument type -- ~s" fun))))

(provide "cmpload")


syntax highlighted by Code2HTML, v. 0.9.1