;;; -*- Mode:LISP; Package:(NC LISP); Base:10; Readtable:CL -*-

;;;; COMPILE-FILE and Top Level Forms

(export 'nlisp:compile-file 'nlisp)

(defun nca (lambda  &optional (env (make-compiler-env)))
  (really-compile lambda env))

(defun really-compile (exp env)
  (declare (values code local-refs refs immediates entry-points))
  (let ((tree (make-code-tree exp env)))
    (analyze tree)
    (multiple-value-bind (instructions entry-points)
	(generate tree)
      (assemble-instruction-list instructions entry-points))))


(defun c (defun)
  (if (eq (car defun) 'DEFUN)
      (nca `(NAMED-LAMBDA ,(second defun) ,(third defun)
			  (BLOCK ,(second defun)
			    ,@(cdddr defun))))
    (let ((new-defun (nlisp::macroexpand-1 defun)))
      (if (eq new-defun defun)
	  (error "~%This is not a defun:~%~a" defun)
	(c new-defun)))))


(defvar *env* nil "Compile time environment")

(defvar *kbin-output-stream*)


;;; this doesn't belong here
(DEFUN PATHNAME-DEFAULT-BINARY-FILE-TYPE (PATHNAME)
  "Given a pathname, return the default binary file type (possibly canonical) to use with it.
This is computed from the SYSTEM which the pathname belongs to."
  (OR (zl:SEND (zl:SEND PATHNAME :GENERIC-PATHNAME) :GET :DEFAULT-BINARY-FILE-TYPE)
      "KFASL"))

(defun nlisp:compile-file (input-file &optional output-file)
  (let ((inpath (fs:merge-pathname-defaults input-file fs:load-pathname-defaults)))
    (let ((outpath
	    (cond ((typep output-file 'pathname)
		   (if (zl:send output-file :version)
		       output-file
		     (zl:send output-file :new-pathname
			      :version :newest)))
		  (output-file
		   (fs:merge-pathname-defaults
		     output-file inpath
		     (pathname-default-binary-file-type
		       (zl:send inpath :generic-pathname))
		     :newest))
		  (t
		   (zl:send inpath :new-pathname
			    :type (pathname-default-binary-file-type
				    (zl:send inpath :generic-pathname))
			    :version :newest)))))
      (with-open-file (input-stream inpath)
	(with-open-file (*kbin-output-stream* outpath :direction :output)
	  (let ((*env* (make-compiler-env))
		(*package* (let ((package-spec (progn (fs:read-attribute-list inpath input-stream)
						      (zl:send inpath :get :package))))
			     (or (if package-spec
				     (find-package package-spec))
				 *package*)))
		(*readtable* (si:find-readtable-named
				 (or (zl:send inpath :get :readtable)
				     :cl))))
	    (do* ((eof (list ()))
		  (form (read input-stream nil eof)
			(read input-stream nil eof)))
		 ((eq form eof))
	      (process-toplevel-form form)))
	  (fasdump:fasd-eof *kbin-output-stream*))))))

;;; For each top level form that the compiler specially recognizes, there will probably
;;; be a corresponding fasl op.

(defvar *top-level-form-handler-table* (make-table '*top-level-form-handler-table*))

(defmacro define-toplevel-form-handler (name pattern &body body)
  (let ((sym (concatenate-symbol 'toplevel/ name))
        (form (gensym 'form)))
    `(progn
       (defun ,sym (,form)
	 (destructure ((,pattern (cdr ,form)))
		      ,@body))
       (setf (table-entry *top-level-form-handler-table* ',name)
	     #',sym))))
	 
(defun process-toplevel-form (form)
  (if (not (consp form))
      (warn "The atom ~s was found at toplevel, this would do nothing" form)
    (let ((handler (table-entry *top-level-form-handler-table* (car form))))
      (if handler
	  (funcall handler form)
	(let ((new-form (nlisp::macroexpand-1 form *env*)))
	  (if (eq new-form form)
	      (arrange-for-eval form)
	    (process-toplevel-form new-form)))))))

(defun arrange-for-eval (form)
  form
  (fasdump:fasd-eval *kbin-output-stream* form))

(define-toplevel-form-handler DEFUN (name lambda-list . body)
  (multiple-value-bind (code local-refs refs immediates entry-points)
      (nca `(named-lambda ,name ,lambda-list (block ,name . ,body)) *env*)
    (fasdump:fasd-defun *kbin-output-stream*
      name
      local-refs
      refs
      immediates
      entry-points
      code)))

(define-toplevel-form-handler PRIMS:DEFSUBST (name lambda-list . body)
  (eval `(GLOBAL::DEFSUBST ,name ,lambda-list . ,body))   ;crock 
  (let ((source `(named-lambda ,name ,lambda-list . ,body)))
    (multiple-value-bind (code local-refs refs immediates entry-points)
	(nca source *env*)
    (fasdump:fasd-defsubst *kbin-output-stream*
      name
      source
      local-refs
      refs
      immediates
      entry-points
      code))))

(define-toplevel-form-handler DEFAFUN (name lambda-list . body)
  (multiple-value-bind (code local-refs refs immediates entry-points)
      (assemble-instruction-list (cons name body) `((,(length lambda-list) . ,name)))
    (fasdump:fasd-defafun *kbin-output-stream*
      name
      local-refs
      refs
      immediates
      entry-points
      code)))
  
;;; value optional
;;; doc string (also optional)
(define-toplevel-form-handler DEFCONSTANT (name value)
  (eval `(DEFCONSTANT ,name ,value)) ;crock
  (fasdump:fasd-defconstant *kbin-output-stream* name value ""))

;;; I don't think we need this - JRM
;(define-toplevel-form-handler DEFSETF rest
;  (eval `(DEFSETF . ,rest))
;  (format t "~&FasDump DEFSETF"))
	    
(define-toplevel-form-handler PRIMS:DEFMACRO macrobody
  (let ((macrofun (si::expand-defmacro macrobody nil)))	;** env
    (multiple-value-bind (code local-refs refs immediates entry-points)
	(nca `(named-lambda . ,macrobody) *env*)
      (fasdump:fasd-defmacro *kbin-output-stream*
	(car macrobody)
	local-refs
	refs
	immediates
	entry-points
	code))
    (fbind (car macrobody) (cons 'MACRO macrofun) *env*)
    ))

(define-toplevel-form-handler PROGN body
  (dolist (form body)
    (process-toplevel-form form)))

(define-toplevel-form-handler IN-PACKAGE rest
;  (eval (cons 'IN-PACKAGE rest))
  (fasdump:fasd-in-package *kbin-output-stream* rest))

(define-toplevel-form-handler EVAL-WHEN (when . body)
  (if (member 'compile when)
      (eval `(progn . ,body)))
  (if (member 'load when)
      (arrange-for-eval `(progn . ,body))))



;----------------------------------------------------------------
;;; Editor Interface


;;; more here
(defun compile-region-for-new-processor (form)
  (cc form))

(zwei::DEFCOM com-compile-region-for-new-processor "" ()
  (zwei::COMPILE-DEFUN-INTERNAL #'compile-region-for-new-processor "Hacking" "hacked.")
  zwei::DIS-NONE)


(zwei::COMMAND-STORE 'com-compile-region-for-new-processor #\s-sh-C zwei::*ZMACS-COMTAB*)



