;;**** allow simple lambdas t have used rest arguments?
;;**** rethink substitution of multiple value continuations

(in-package "XLSCMP")

;;;;;
;;;;; Macro expansion, alphatization, and CPS Conversion
;;;;;
#|

Special forms done (sort of):

BLOCK CASE CATCH ERRSET FLET FUNCTION GO IF LABELS LAMBDA LET LET*
LOOP PROGN PROGV QUOTE RETURN-FROM SETQ TAGBODY THROW UNWIND-PROTECT
LOCALLY MULTIPLE-VALUE-CALL MULTIPLE-VALUE-PROG1 DECLARE


Special forms needed later:

GENERIC-FLET GENERIC-LABELS LOAD-TIME-VALUE WITH-ADDED-METHODS
|#


;;;;
;;;; Internal CPS node representation
;;;;

(setf (symbol-function 'leaf-node-p) #'cps-leaf-node-p)
(setf (symbol-function 'lambda-node-p) #'cps-lambda-node-p)
(setf (symbol-function 'call-node-p) #'cps-call-node-p)

(setf (symbol-function 'node-children) #'cps-node-children)
(setf (symbol-function 'node-parent) #'cps-node-parent)
(setf (symbol-function 'node-simplified-p) #'cps-node-simplified-p)
(setf (symbol-function 'node-note) #'cps-node-note)

(setf (symbol-function 'set-node-children) #'cps-set-node-children)
(setf (symbol-function 'set-node-parent) #'cps-set-node-parent)
(setf (symbol-function 'set-node-simplified) #'cps-set-node-simplified)
(setf (symbol-function 'set-node-note) #'cps-set-node-note)

(setf (symbol-function 'leaf-node-value) #'cps-leaf-node-value)
(setf (symbol-function 'leaf-node-count) #'cps-leaf-node-count)

(setf (symbol-function 'set-leaf-node-value) #'cps-set-leaf-node-value)
(setf (symbol-function 'set-leaf-node-count) #'cps-set-leaf-node-count)

(setf (symbol-function 'lambda-node-arglist) #'cps-lambda-node-arglist)
(setf (symbol-function 'lambda-node-lambda-list) #'cps-lambda-node-lambda-list)
(setf (symbol-function 'lambda-node-name) #'cps-lambda-node-name)

(setf (symbol-function 'set-lambda-node-arglist) #'cps-set-lambda-node-arglist)
(setf (symbol-function 'set-lambda-node-lambda-list) #'cps-set-lambda-node-lambda-list)
(setf (symbol-function 'set-lambda-node-name) #'cps-set-lambda-node-name)

(setf (symbol-function 'lambda-node-body) #'cps-lambda-node-body)
(setf (symbol-function 'call-node-function) #'cps-call-node-function)
(setf (symbol-function 'call-node-args) #'cps-call-node-args)

(setf (symbol-function 'any-references-p) #'cps-any-references-p)
(setf (symbol-function 'find-references) #'cps-find-references)


;;;;;
;;;;; Additions to internal CPS node representation
;;;;;

;; general nodes
(defun make-node () (make-cps-node nil))

(defun node-expression (e)
  (cond
   ((leaf-node-p e) (leaf-node-value e))
   ((lambda-node-p e)
    `(,(if (continuation-node-p e) 'continuation 'lambda)
      ,(mapcar #'leaf-node-value (lambda-node-arglist e))
      ,(node-expression (first (node-children e)))))
   ((call-node-p e) (mapcar #'node-expression (node-children e)))))

;; leaf nodes
(defun make-leaf-node (v)
  (let ((n (make-cps-node 'leaf)))
    (set-node-simplified n t)
    (set-leaf-node-value n v)
    n))

;; lambda nodes
(defun make-lambda-node (arglist body &optional lambda-list)
  (let ((n (make-cps-node 'lambda)))
    (set-lambda-node-arglist n arglist)
    (set-node-children n (list body))
    (dolist (a arglist) (set-symbol-node-owner a n))
    (set-node-parent body n)
    (set-lambda-node-lambda-list
     n
     (if lambda-list lambda-list (lambda-list-default arglist)))
    n))
 
(defun lambda-node-delete-arg (n v)
  (set-lambda-node-arglist n (delete v (lambda-node-arglist n)))
  (set-lambda-node-lambda-list n
			       (lambda-list-default (lambda-node-arglist n))))
;;**** check use of this
(defun set-lambda-node-arglist-fix (n a)
  (set-lambda-node-arglist n a)
  (set-lambda-node-lambda-list n (lambda-list-default a)))
  
;; call nodes
(defun make-call-node (&rest args)
  (let ((n (make-cps-node 'call)))
    (set-node-children n args)
    (dolist (c args) (set-node-parent c n))
    (if (lambda-node-p (first args)) (fixup-lambda-call-node n))
    n))

(defun call-node-add-arg (n a)
  (set-node-children n (append (node-children n) (list a)))
  n)

(defun call-node-arg (n k) (nth k (call-node-args n)))

(defun call-node-arg-count (n) (length (call-node-args n)))

;;**** this needs to be replaced completely by the index based one
(defun call-node-delete-arg (n v)
  (set-node-children n (delete v (node-children n)))
  (set-node-parent v nil))

;;**** this is a hack -- it should be done more efficiently
(defun call-node-delete-arg-index (n i)
  (let* ((c (node-children n))
	 (v (nth (+ i 1) c))
	 (x (cons nil nil))) ;;**** a unique marker
    (setf (nth (+ i 1) c) x)
    (set-node-children n (delete x c))
    (set-node-parent v nil)))

(defun set-call-node-args (n a)
  (dolist (c a) (unless (leaf-node-p c) (set-node-parent c n)))
  (set-node-children n (cons (first (node-children n)) a))
  (fixup-lambda-call-node n))

(defun set-call-node-function (n f)
  (unless (leaf-node-p f) (set-node-parent f n))
  (set-node-children n (cons f (rest (node-children n))))
  (fixup-lambda-call-node n))

(defun set-call-node-arg (n i a)
  (unless (leaf-node-p a) (set-node-parent a n))
  (setf (nth (+ i 1) (node-children n)) a)
  (fixup-lambda-call-node n))

;; node moving and copying
(defun copy-node-internals (b a)
  (if (leaf-node-p b) (error "can't copy a leaf node"))
  (let ((pa (node-parent a)))
    (cps-node-transform a b)
    (set-node-parent a pa)))

(defun move-node-tree (b a)
  (copy-node-internals b a)
  (dolist (c (node-children a)) (set-node-parent c a))
  (do ((p (node-parent a) (node-parent p)))
      ((not (and p (node-simplified-p p))))
      (set-node-simplified p nil)))

(defun internal-insert-node-tree (tree form pos)
  (setf (nth pos (node-children form)) tree)
  (unless (leaf-node-p tree) (set-node-parent tree form)))

(defun insert-node-tree (tree form pos)
  (internal-insert-node-tree tree form pos)
  (do ((p form (node-parent p)))
      ((not (and p (node-simplified-p p))))
      (set-node-simplified p nil)))

;;****** this may be dangerous as it does not replace variable nodes
;;****** variable node replacement is needed for multiple inlining
(defun copy-node-tree (tree)
  (if (leaf-node-p tree)
      tree
      (let ((cp (make-node)))
	(copy-node-internals tree cp)
	(if (lambda-node-p cp)
	    (dolist (a (lambda-node-arglist cp))
	      (set-symbol-node-owner a cp)))
	(let ((ch (copy-list (node-children cp))))
	  (set-node-children cp ch)
	  (dotimes (i (length ch))
	    (internal-insert-node-tree (copy-node-tree (nth i ch)) cp i))
	  cp))))

;; continuation nodes
(defun continuation-node-p (n)
  (and (lambda-node-p n) (eq (node-note n) 'continuation)))

(defun make-continuation-node (arglist body)
  (let ((n (make-lambda-node arglist body)))
    (set-node-note n 'continuation)
    n))

#|
(setf count 0)
(defun mark-tree-unsimplified (n)
  (incf count)
  (unless (leaf-node-p n) (set-node-simplified n nil))
  (dolist (c (node-children n)) (mark-tree-unsimplified c)))
|#

;;;;
;;;; Representation-independent part
;;;;

(defun pp-cps (e) (pprint (node-expression e)))

;;;
;;; Conversion rules management
;;;
(defun set-cps-converter (s c) (setf (get s 'cps-converter) c))

;;***** check for local function binding
(defun get-cps-converter (s)
  (if (and (symbolp s) (null (assoc s *cmp-fenv*)))
      (get s 'cps-converter)))

(defmacro define-cps-converter (sym &rest body)
  `(set-cps-converter ',sym #'(lambda ,@body)))

;;;
;;; CPS converter
;;;

(defvar *cmp-env* nil)
(defvar *cmp-fenv* nil)
(defvar *cmp-denv* nil)
(defvar *cmp-tenv* nil)
(defvar *cmp-specials* nil)
(defvar *cmp-macros* nil)
(defvar *cmp-setf* nil)
(defvar *cmp-structs* nil)
(defvar *cmp-gvars* nil)
(defvar *cmp-gfuns* nil)
(defvar *cmp-consts* nil)
(defvar *cmp-base-continuation-symbol* (gensym "K0-"))

(defun lfun-node-p (n) (lambda-node-p (find-lambda-binding n)))

(defun gfun-node-p (n) (and (leaf-node-p n) (eq (symbol-node-owner n) 'gfun)))
(defun gvar-node-p (n) (and (leaf-node-p n) (eq (symbol-node-owner n) 'gvar)))
(defun constant-node-p (n) (and (leaf-node-p n) (eq (node-note n) 'constant)))

(defun gfun-eq (n s) (and (gfun-node-p n) (eq (gfun-symbol n) s)))
(defun gfun-member (n sl) (and (gfun-node-p n) (member (gfun-symbol n) sl)))
(defun gfun-symbol (n) (leaf-node-value n))

(defun local-symbol-node-p (n)
  (and (leaf-node-p n) (lambda-node-p (symbol-node-owner n))))

(defun make-variable-node (e) (make-leaf-node e))
(defun make-function-node (e) (make-leaf-node e))

(defun make-constant-node (e)
  (let ((n (make-leaf-node e)))
    (set-node-note n 'constant)
    n))

(defun symbol-node-owner (n) (node-note n))
(defun set-symbol-node-owner (n o) (set-node-note n o))

(defun new-env-node (sym)
  (let ((n (make-variable-node sym)))
    (push (list sym n) *cmp-env*)
    n))

(defun new-fenv-node (sym)
  (let ((n (make-function-node sym)))
    (push (list sym n) *cmp-fenv*)
    n))

(defun make-gvar-node (sym)
  (let ((n (make-variable-node sym)))
    (set-symbol-node-owner n 'gvar)
    n))

(defun make-gfun-node (sym)
  (let ((n (make-function-node sym)))
    (set-symbol-node-owner n 'gfun)
    n))

(defun new-gvar-node (sym)
  (let ((n (make-gvar-node sym)))
    (push (list sym n) *cmp-gvars*)
    n))

(defun new-gfun-node (sym)
  (let ((n (make-gfun-node sym)))
    (push (list sym n) *cmp-gfuns*)
    n))

(defun new-constant-node (e)
  (let ((n (make-constant-node e)))
    (push (list e n) *cmp-consts*)
    n))

(defun get-gvar-node (sym)
  (let ((gnode (second (assoc sym *cmp-gvars*))))
    (if gnode gnode (new-gvar-node sym))))

(defun get-gfun-node (sym)
  (let ((gnode (second (assoc sym *cmp-gfuns*))))
    (if gnode gnode (new-gfun-node sym))))

(defun get-variable-node (sym)
  (if (is-special-variable sym)
      (get-gvar-node sym)
      (let ((lnode (second (assoc sym *cmp-env*))))
	(if lnode lnode (get-gvar-node sym)))))
  
(defun get-function-node (sym)
  (let ((lnode (second (assoc sym *cmp-fenv*))))
    (if lnode lnode (get-gfun-node sym))))

(defun get-constant-node (e)
  (let ((cnode (second (assoc e *cmp-consts* :test #'equal))))
    (if cnode cnode (new-constant-node e))))

(defun is-special-variable (sym)
  (or (specialp sym) (member sym *cmp-specials*)))

(defmacro with-saved-cmp-environments (&rest body)
  `(progv '(*cmp-env* *cmp-fenv* *cmp-denv* *cmp-tenv* *cmp-specials*)
	  (list *cmp-env* *cmp-fenv* *cmp-denv* *cmp-tenv* *cmp-specials*)
	  ,@body))

(defun cvt (e)
  (progv '(*cmp-env*   *cmp-fenv*  *cmp-denv*   *cmp-tenv* *cmp-specials*
	   *cmp-gvars* *cmp-gfuns* *cmp-consts*)
	 '(nil nil nil nil nil nil nil nil)
	 (let ((k0 (get-function-node *cmp-base-continuation-symbol*)))
	   (convert e k0))))

;;**** fix to use intrnal constant status
(defun constant-symbol-p (e) (member e '(t nil)))

(defun constant-value-form (e)
  (let ((v (if e (symbol-value e) nil)))
     (if (or (null v) (eq t v) (characterp v) (numberp v)) v `(quote ,v))))

(defun make-continuation-call-node (k &rest args)
  (if (multiple-value-continuation-node-p k)
      (apply #'make-call-node (get-gfun-node 'values) k args)
      (apply #'make-call-node k args)))

#|
(defun convert (e k)
  (setf e (cmp-macroexpand e))
  (cond 
   ((or (atom e) (eq (first e) 'quote))
    (if (symbolp e)
	(if (constant-symbol-p e)
	    (make-continuation-call-node
	     k
	     (get-constant-node (constant-value-form e)))
	    (let ((v (get-variable-node e)))
	      (if (gvar-node-p v)
		  (convert `(%symval ',e) k)
		  (make-continuation-call-node k v))))
        (make-continuation-call-node k (get-constant-node e))))
   (t (check-arg-count e)
      (let ((c (get-cps-converter (first e))))
	(if c
	    (funcall c e k) 
	    (convert-funarg
	     (first e) 
	     (with-saved-cmp-environments
	      (let ((p (new-env-node (gensym "P"))))
		(make-continuation-node
		 (list p)
		 (convert-arguments (rest e)
				    (make-call-node p k)))))))))))
|#
;;***** includes initial variable substitution
;;***** make sure this initial substitution is OK
(defun pre-simplify-tree (n)
  (when (and (call-node-p n) (lambda-node-p (call-node-function n)))
	(lambda-call-substitute-variables n nil t)
	(let ((f (call-node-function n)))
	  (if (null (lambda-node-arglist f))
	      (move-node-tree (lambda-node-body f) n)))))

(defun convert (e k)
  (flet ((convert1 (e k)
           (setf e (cmp-macroexpand e))
	   (cond 
	    ((or (atom e) (eq (first e) 'quote))
	     (if (symbolp e)
		 (if (constant-symbol-p e)
		     (make-continuation-call-node
		      k
		      (get-constant-node (constant-value-form e)))
		   (let ((v (get-variable-node e)))
		     (cond
		      ((gvar-node-p v)
		       (unless (or (is-special-variable e)
				   (not *compile-warn-specials*))
			       (warn "variable ~s assumed special" e))
		       (convert `(%symval ',e) k));;////// warn if not special
		      (t (make-continuation-call-node k v)))))
	       (make-continuation-call-node k (get-constant-node e))))
	    (t (check-arg-count e)
	       (let ((c (get-cps-converter (first e))))
		 (if c
		     (funcall c e k) 
		   (convert-funarg
		    (first e) 
		    (with-saved-cmp-environments
		     (let ((p (new-env-node (gensym "P"))))
		       (make-continuation-node
			(list p)
			(convert-arguments (rest e)
					   (make-call-node p k))))))))))))
    (let ((n (convert1 e k)))
      (pre-simplify-tree n)
      n)))

;;**** simplify symbol case?
(defun convert-funarg (f k)
  (cond
   ((symbolp f)
    (make-call-node k (get-function-node f)))
   ((and (consp f) (eq (first f) 'lambda)) (convert f k))
   (t (error "bad function - ~s" f))))

#|
;****//// this causes deep recursions
(defun convert-arguments (args final-call)
  (if (null args)
      final-call
      (with-saved-cmp-environments
        (let ((k (new-env-node (gensym "K"))))
	  (convert (car args)
		   (make-continuation-node
		    (list k)
		    (convert-arguments (rest args)
				       (call-node-add-arg final-call k))))))))
|#
;;****** iterative version
(defun convert-arguments (args final-call)
  (let ((vars nil))
    (with-saved-cmp-environments
     (dolist (a args) (push (new-env-node (gensym "V")) vars)))
    ;; because of substitution, adding the arguments MUST come first
    (set-node-children final-call
		       (append (node-children final-call) (reverse vars)))
    (do ((form final-call)
	 (rargs (reverse args) (rest rargs))
	 (vars vars (rest vars)))
	((null rargs) form)
	(let ((a (first rargs))
	      (v (first vars)))
	  (setf form (convert a (make-continuation-node (list v) form)))))))


;;;
;;; Specific conversion rules
;;;

(defun fixup-optional-argument (x)
  (cond
   ((symbolp x) (list x nil nil))
   ((and (consp x) (symbolp (first x)))
    (check-supplied-p-argument (third x))
    (list (first x) (cmp-macroexpand (second x)) (third x)))
   (t (error "bad optional argument entry -- ~s" x))))

(defun make-corresponding-keyword (s) (intern (symbol-name s) "KEYWORD"))

(defun check-supplied-p-argument (s)
  (if s (unless (symbolp s) (error "bad supplied-p argument -- ~s" s))))

(defun fixup-keyword-argument (x)
  (cond
   ((symbolp x) (list (make-corresponding-keyword x) x nil nil))
   ((and (consp x) (symbolp (first x)))
    (check-supplied-p-argument (third x))
    (list (make-corresponding-keyword (first x))
	  (first x)
	  (cmp-macroexpand (second x))
	  (third x)))
   ((and (consp x)
	 (consp (first x))
	 (symbolp (first (first x)))
	 (symbolp (second (first x)))
	 (null (rest (rest (first x)))))
    (check-supplied-p-argument (third x))
    (list (first (first x))
	  (second (first x)) 
	  (cmp-macroexpand (second x))
	  (third x)))
   (t (error "bad keyword argument entry -- ~s" x))))

(defun fixup-aux-argument (x)
  (cond
   ((symbolp x) (list x nil))
   ((and (consp x) (symbolp (first x))) (list (first x) (second x)))
   (t (error "bad aux argument entry -- ~s" x))))

(defun is-lambda-key (s) (member s lambda-list-keywords))

;;**** put in better error checking here
(defun split-lambda-list (ll)
  (let ((env (second (member '&environment ll)))
	(req nil)
	(opt nil)
	(rest nil)
	(allow-keys nil)
	(key nil)
	(allow-other-keys nil)
	(aux nil))
    (if env (setf ll (remove '&environment (remove env ll))))
    (when ll
	  (loop
	   (let ((x (first ll)))
	     (if (is-lambda-key x) (return))
	     ;;(assert (symbolp x))
	     (push x req)
	     (setf ll (rest ll))
	     (if (null ll) (return)))))
    (when (eq (first ll) '&optional)
	  (setf ll (rest ll))
	  (loop
	   (let ((x (first ll)))
	     (if (or (null ll) (is-lambda-key x)) (return))
	     (push (fixup-optional-argument x) opt)
	     (setf ll (rest ll)))))
    (when (eq (first ll) '&rest)
	  (setf ll (rest ll))
	  ;;(assert (and (consp ll) (symbolp (first ll))))
	  (setf rest (first ll))
	  (setf ll (rest ll)))
    (when (eq (first ll) '&key)
	  (setf ll (rest ll))
	  (setf allow-keys t)
	  (loop
	   (let ((x (first ll)))
	     (if (or (null ll) (is-lambda-key x)) (return))
	     (push (fixup-keyword-argument x) key)
	     (setf ll (rest ll)))))
    (when (eq (first ll) '&allow-other-keys)
	  (setf ll (rest ll))
	  (setf allow-other-keys t))
    (when (eq (first ll) '&aux)
	  (setf ll (rest ll))
	  (loop
	   (let ((x (first ll)))
	     (if (or (null ll) (is-lambda-key x)) (return))
	     (push (fixup-aux-argument x) aux)
	     (setf ll (rest ll)))))
    (unless (null ll) (error "bad formal argument list"))
    (list (reverse req)
	  (reverse opt)
	  rest
	  allow-keys
	  (reverse key)
	  (reverse aux)
	  allow-other-keys
	  env)))

(defun rewrite-lambda (lambda-list body)
  (let* ((sl (split-lambda-list lambda-list))
	 (req (nth 0 sl))
	 (opt (nth 1 sl))
	 (rest (nth 2 sl))
	 (allow-keys (nth 3 sl))
	 (key (nth 4 sl))
	 (aux (nth 5 sl))
	 (allow-other-keys (nth 6 sl))
	 (env (nth 7 sl))
	 (db (split-declarations body))
	 (pdcl (parse-declarations (first db)))
	 (rdcl (mapcar #'(lambda (x) (assoc x (first pdcl))) req))
	 (odcl (set-difference (first pdcl) rdcl))
	 (body (second db))
	 (key-defaults nil)
	 (opt-defaults nil)
	 (variables nil)
	 (bindings aux))
    (if env
	(let ((ee (gensym "E")))
	  (push `(,env ,ee) bindings)
	  (push ee variables)))
    (dolist (v (reverse key))
      (let ((s (second v))
	    (ss (gensym "K"))
	    (dflt (third v))
	    (sp (fourth v)))
	(push ss variables)
	(cond
	 ((and (constant-expression-p dflt) (null sp))
	  (push (strip-quote dflt) key-defaults)
	  (push `(,s ,ss) bindings))
	 (t
	  (push '%not-supplied key-defaults)
	  (push `(,s (if (%supplied-p ,ss) ,ss ,dflt)) bindings)
	  (if sp (push `(,sp (%supplied-p ,ss)) bindings))))))
    (if rest
	(let ((rr (gensym "R")))
	  (push `(,rest ,rr) bindings)
	  (push rr variables)))
    (dolist (v (reverse opt))
      (let ((s (first v))
	    (ss (gensym "O"))
	    (dflt (second v))
	    (sp (third v)))
	(push ss variables)
	(cond
	 ((and (constant-expression-p dflt) (null sp))
	  (push (strip-quote dflt) opt-defaults)
	  (push `(,s ,ss) bindings))
	 (t
	  (push '%not-supplied opt-defaults)
	  (push `(,s (if (%supplied-p ,ss) ,ss ,dflt)) bindings)
	  (if sp (push `(,sp (%supplied-p ,ss)) bindings))))))
    (dolist (v (reverse req)) (push v variables))
    (list variables
	  (if bindings
	      `(,@(apply #'append
			 (mapcar #'unparse-variable-declarations rdcl))
		(let* ,bindings
		  ,@(apply #'append 
			   (mapcar #'unparse-variable-declarations odcl))
		  ,@(unparse-function-declarations (second pdcl))
		  ,@(unparse-compiler-declarations (third pdcl))
		  ,@body))
	      `(,@(apply #'append
			 (mapcar #'unparse-variable-declarations rdcl))
		,@(apply #'append 
			 (mapcar #'unparse-variable-declarations odcl))
		,@(unparse-function-declarations (second pdcl))
		,@(unparse-compiler-declarations (third pdcl))
		,@body))
	  (length req)
	  (length opt)
	  opt-defaults
	  allow-keys
	  key-defaults
	  rest
	  allow-other-keys
	  (mapcar #'first key)
	  env)))

(defun lambda-list-default (args)
  (append (list (length args) 0) (make-list 6)))

(defun lambda-list-data (args)
  (let* ((sl (split-lambda-list args))
	 (req (nth 0 sl))
	 (opt (nth 1 sl))
	 (rest (nth 2 sl))
	 (allow-keys (nth 3 sl))
	 (key (nth 4 sl))
	 (aux (nth 5 sl))
	 (allow-other-keys (nth 6 sl))
	 (env (nth 7 sl)))
    (list (length req)
	  (length opt)
	  (mapcar #'second opt)
	  allow-keys
	  (mapcar #'third key)
	  rest
	  allow-other-keys
	  (mapcar #'first key)
	  env)))

(defun find-lambda-list-data (f)
  (if (and (consp f) (eq (first f) 'lambda))
      (lambda-list-data (second f))
      (let ((lf (second (assoc f *cmp-fenv*))))
	(if lf
	    nil;;(lambda-node-lambda-list (find-lambda-binding lf));;////????
	    (get-lambda-list-data f)))))

(defmacro define-lambda-list (s ll)
  `(setf (get ',s 'cmp-lambda-list-data) (lambda-list-data ',ll)))

(defun get-lambda-list-data (s) (get s 'cmp-lambda-list-data))

;;****** need lots more of this
;;****** may also need for setf methods, eg
;;(define-lambda-list (setf aref) (a i &rest args))
(define-lambda-list car (x))
(define-lambda-list aref (a i &rest args))
(define-lambda-list %set-aref (x y z &rest args)) ;;**** should be (setf aref)
(define-lambda-list - (x &rest args))
(define-lambda-list / (x &rest args))
(define-lambda-list slot-value (x &optional y))

(define-lambda-list setq (var val &rest more))
(define-lambda-list lambda (args &rest body))
(define-lambda-list let (bindings &rest body))
(define-lambda-list let* (bindings &rest body))
(define-lambda-list flet (bindings &rest body))
(define-lambda-list labels (bindings &rest body))
(define-lambda-list macrolet (bindings &rest body))
(define-lambda-list if (test cons &optional alt))
(define-lambda-list progn (&rest body))
(define-lambda-list locally (&rest body))
(define-lambda-list the (a b))
(define-lambda-list block (tag &rest body))
(define-lambda-list function (fun))
(define-lambda-list catch (tag &rest forms))
(define-lambda-list throw (tag form))
(define-lambda-list errset (form &optional print))
(define-lambda-list unwind-protect (pform &rest cforms))
(define-lambda-list tagbody (&rest body))
(define-lambda-list go (tag))
(define-lambda-list return-from (tag &optional form))
(define-lambda-list progv (syms vals &rest body))
(define-lambda-list case (key &rest forms))
(define-lambda-list multiple-value-call (funcion &rest forms))
(define-lambda-list nth-value (n form))
(define-lambda-list multiple-value-prog1 (form &rest forms))


(defun check-arg-count (e)
  (let* ((f (first e))
	 (n (length (rest e)))
	 (ll (find-lambda-list-data f)))
    (if ll
	(let ((nr (nth 0 ll))
	      (no (nth 1 ll))
	      (allow-keys (nth 3 ll))
	      (rest (nth 5 ll)))
	  (if (< n nr) (error "too few arguments to ~s" f))
	  (unless (or rest allow-keys)
		  (if (> n (+ nr no))
		      (error "too many arguments to ~s" f)))))))

;;**** be more careful about non-self-evaluating forms
(defun constant-expression-p (e)
  (cond
   ((atom e) (or (not (symbolp e)) (constant-symbol-p e)))
   ((eq (first e) 'quote))))

(defun strip-quote (x) (if (consp x) (second x) x))

;;**** fix these to allow inlining lambdas with rest arguments
;;**** if rest is unused, just drop.
;;**** otherwise, add code to make the list
#|
(defun rest-arg-only-p (n)
  (let* ((ll (lambda-node-lambda-list n))
	 (r (nth 5 ll)))
    (and r (every #'null (remove r (rest (rest (rest ll))))))))
|#

(defun simple-lambda-node-p (n)
  (multiple-value-bind (nr no od ak kd rest aok)
		       (values-list (lambda-node-lambda-list n))
    (and (null ak)
	 (null aok)
	 (or (null rest)
	     (not (any-references-p (first (last (lambda-node-arglist n)))
				    (lambda-node-body n)))))))

(defun fixup-lambda-call-node (n)
  (let ((f (call-node-function n)))
    (if (lambda-node-p f)
	(let* ((ll (lambda-node-lambda-list f))
	       (na (call-node-arg-count n))
	       (nr (first ll))
	       (no (second ll))
	       (rest (sixth ll)))
	  (unless (simple-lambda-node-p n)
		  (error "can't inline a non-simple lambda"))
	  (if (and (not rest) (> na (+ nr no))) (error "too many arguments"))
	  (if (< na nr) (error "too few arguments"))
	  (if (< na (+ nr no))
	      (let ((extra (nthcdr (- na nr) (third ll))))
		(set-node-children n
				   (append
				    (node-children n)
				    (mapcar #'(lambda (x)
						(get-constant-node `',x))
					    extra)))))
	  (if (< (+ nr no) na)
	      (set-node-children n (butlast (node-children n) (- na nr no))))
	  (if rest
	      (lambda-node-delete-arg
	       f
	       (first (last (lambda-node-arglist f)))))))))

(defun lambda-list-num-variables (ll)
  (let ((nr (nth 0 ll))
	(no (nth 1 ll))
	(nk (length (nth 4 ll)))
	(rest (if (nth 5 ll) 1 0))
	(env (if (nth 8 ll) 1 0)))
    (+ nr no nk rest env)))

;;**** fix to use special declarations
(defun fix-lambda-specials (rl)
  (let ((vars (first rl))
	(db (split-declarations (second rl))))
    (when (some #'is-special-variable vars)
	  (let ((bds nil)
		(nv nil)
		(sv nil))
	    (dolist (v vars)
	      (if (is-special-variable v)
		  (let ((s (gensym "SV")))
		    (when (constantp v)
			  (error "can't bind to a constant - ~s" v))
		    (push v bds)
		    (push s bds)
		    (push s nv)
		    (push v sv))
		  (push v nv)))
	    (setf (first rl) (nreverse nv))
	    (setf (second rl)
		  `(,@(first db)
		      (%dynamic-bind ',sv 't
				     #'(lambda ()
					 (setq ,@(nreverse bds))
					 ,@(second db)))))))
    rl))

(defun convert-lambda (e)
  (with-saved-cmp-environments
    (let* ((ksym (gensym "K"))
	   (lambda-list (cons ksym (second e)))
	   (body (rest (rest e)))
	   (rl (fix-lambda-specials (rewrite-lambda lambda-list body)))
	   (vars (mapcar #'new-env-node (first rl)))
	   (k (first vars))
	   (db (split-declarations (second rl)))
	   (new-body `(progn ,@(second db))))
      (make-lambda-node vars (convert new-body k) (rest (rest rl))))))

(define-cps-converter lambda (e k)
  (make-continuation-call-node k (convert-lambda e)))

(define-cps-converter let (e k)
  (let ((bindings (second e))
	(body (rest (rest e))))
    (let ((vars (mapcar #'(lambda (x) (if (consp x) (first x) x)) bindings))
	  (vals (mapcar #'(lambda (x) (if (consp x) (second x))) bindings)))
      (convert `((lambda ,vars ,@body) ,@vals) k))))

(define-cps-converter if (e k)
  (let ((test (second e))
	(consequent (third e))
	(alternative (fourth e)))
    (convert test
	     (with-saved-cmp-environments
	       (let ((v (new-env-node (gensym "V"))))
		 (make-continuation-node
		  (list v)
		  (make-call-node
		   (with-saved-cmp-environments
		     (let ((j (new-env-node (gensym "J"))))
		       (make-lambda-node
			(list j)
			(make-call-node
			 (get-gfun-node '%test)
			 (make-continuation-node () (convert consequent j))
			 (make-continuation-node () (convert alternative j))
			 v))))
		   k)))))))

#|
;****//// this causes deep recursions
(define-cps-converter progn (e k)
  (let ((first (second e))
	(rest (rest (rest e))))
    (if rest
	(convert first
		 (with-saved-cmp-environments
		  (make-continuation-node
		   (list (new-env-node (gensym "V")))
		   (convert `(progn ,@rest) k))))
        (convert first k))))
|#
;;**** iterative version
(define-cps-converter progn (e k)
  (let* ((rbody (reverse (rest e)))
	 (last (first rbody))
	 (rest (rest rbody))
	 (ce (convert last k)))
    (dolist (ee rest ce)
      (setf ce
	    (convert ee
		     (with-saved-cmp-environments
		      (make-continuation-node
		       (list (new-env-node (gensym "V")))
		       ce)))))))

(define-cps-converter locally (e k)
  (convert (cons 'progn (rest e)) k))

(define-cps-converter the (e k)
  (convert (third e) k))

(defun rewrite-let* (e)
  (let* ((v (fixup-let-variables (second e)))
	 (db (split-declarations (rest (rest e))))
	 (body (second db))
	 (d (parse-declarations (first db)))
	 (vd (first d))
	 (fd (second d))
	 (cd (third d)))
    (labels ((rw (v d)
	       (if (consp (rest v))
		   (let* ((vi (first v))
			  (vsym (first vi))
			  (vd (assoc vsym d)))
		     `(let (,vi)
			,@(unparse-variable-declarations vd)
			,(rw (rest v) (remove vd d))))
		   `(let ,v
		      ,@(apply #'append
			       (mapcar #'unparse-variable-declarations d))
		      ,@(unparse-function-declarations fd)
		      ,@(unparse-compiler-declarations cd)
		      ,@body))))
      (rw v vd))))

(define-cps-converter let* (e k) (convert (rewrite-let* e) k))

(defun convert-named-lambda (f)
  (let* ((name (first f))
	 (args (second f))
	 (d (split-declarations (rest (rest f))))
	 (decls (first d))
	 (body (second d)))
    (convert-lambda `(lambda ,args ,@decls (block ,name ,@body)))))

;;**** should use named lambda's!!
;;**** deal with declarations
(define-cps-converter flet (e k)
  (let* ((funs (second e))
	 (db (split-declarations (rest (rest e))))
	 (body `(progn ,@(second db))))
    (flet ((cvtfun (f) (convert-named-lambda f))
	   (fname (f) (new-fenv-node (first f))))
      (let ((cfuns (mapcar #'cvtfun funs)))
	(with-saved-cmp-environments
	 (let* ((alist (mapcar #'fname funs))
		(cbody (make-lambda-node alist (convert body k))))
	   (apply #'make-call-node cbody cfuns)))))))

;;**** should use named lambda's!!
;;**** deal with declarations
(define-cps-converter labels (e k)
  (let* ((funs (second e))
	 (db (split-declarations (rest (rest e))))
	 (body `(progn ,@(second db))))
    (flet ((cvtfun (f) (convert-named-lambda f))
	   (fname (f) (new-fenv-node (first f))))
      (with-saved-cmp-environments
       (let* ((alist (mapcar #'fname funs))
	      (cfuns (mapcar #'cvtfun funs))
	      (cbody (convert body k)))
	 (make-call-node
	  (get-gfun-node '%y)
	  (make-continuation-node
	   alist
	   (apply #'make-call-node 
		  (get-gfun-node '%y-list)
		  (make-continuation-node nil cbody)
		  cfuns))))))))

(define-cps-converter block (e k)
  (let ((name (second e))
	(body (rest (rest e))))
    (with-saved-cmp-environments
     (let ((tag (new-env-node (gensym "T")))
	   (kk (new-env-node (gensym "K"))))
       (push (list 'block name tag) *cmp-tenv*)
       (make-call-node
	(get-gfun-node '%catch-block)
	  k
	  (get-constant-node `(quote ,name))
	  (make-lambda-node (list kk tag) (convert `(progn ,@body) kk)))))))

(defun find-block-tag (name)
  (dolist (a *cmp-tenv*)
    (if (and (eq 'block (first a)) (eq name (second a)))
	(return (first (rest (rest a)))))))
		
;;**** simplify this?
(define-cps-converter setq (e k)
  (let ((sym (second e))
	(val (third e))
	(rest (rest (rest (rest e)))))
    (if rest
	(convert `(progn (setq ,sym ,val) (setq ,@rest)) k)
        (convert val
		 (with-saved-cmp-environments
		  (let ((v (new-env-node (gensym "V")))
			(sn (get-variable-node sym)))
		    (if (gvar-node-p sn)
			(progn
			  (when (constantp sym)
				(error
				 "can't change the value of a constant - ~s"
				 sym))
			  (make-continuation-node
			   (list v)
			   (make-call-node
			    (get-gfun-node '%set-symval)
			    k
			    (get-constant-node `(quote ,(leaf-node-value sn)))
			    v)))
		        (make-continuation-node
			 (list v)
			 (make-call-node (get-gfun-node '%setq) k sn v)))))))))

(define-cps-converter function (e k)
  (let ((f (second e)))
    (cond
     ((symbolp f)
      (let ((fn (get-function-node f)))
	(if (gfun-node-p fn)
	    (make-call-node (get-gfun-node '%symfun) k fn)
	    (make-continuation-call-node k fn))))
     ((and (consp f) (eq (first f) 'lambda)) (convert f k))
     (t (error "bad argument for FUNCTION -- ~s" f)))))

(define-cps-converter catch (e k)
  (convert `(%catch ,(second e) (lambda () ,@(rest (rest e)))) k))

(define-cps-converter errset (e k)
  (let ((expr (second e))
	(flag (if (= (length e) 3) (third e) t)))
    (convert `(%errset (lambda () ,expr) ,flag) k)))

(define-cps-converter unwind-protect (e k)
  (convert `(%unwind-protect #'(lambda () ,(second e))
			     #'(lambda () ,@(rest (rest e))))
	   k))

(define-cps-converter progv (e k)
  (let ((vars (second e))
	(vals (third e))
	(body (rest (rest (rest e)))))
    (convert `(%progv ,vars ,vals #'(lambda () ,@body)) k)))

;;**** is this the best way?
(define-cps-converter case (e k)
  (let ((var (second e))
	(sels (mapcar #'first (rest (rest e))))
	(acts (mapcar #'rest (rest (rest e)))))
    (if (member (first (last sels)) '(t otherwise))
	(setf sels (butlast sels))
        (setf acts (append acts (list nil))))
    (convert var
	     (with-saved-cmp-environments
	      (let ((v (new-env-node (gensym "V"))))
		(make-continuation-node
		 (list v)
		 (make-call-node
		  (with-saved-cmp-environments
		   (let ((j (new-env-node (gensym "K"))))
		    (flet ((mkfun (a)
			     (make-continuation-node
			      ()
			      (convert `(progn ,@a) j))))
		      (make-continuation-node
		       (list j)
		       (apply #'make-call-node
			      (get-gfun-node '%case)
			      v
			      (get-constant-node
			       `(quote ,(coerce sels 'vector)))
			      (mapcar #'mkfun acts))))))
		 k)))))))

(defun split-tagbody-body (bd)
  (unless (symbolp (first bd)) (push (gensym "S") bd))
  (let ((pairs nil)
	(tag (first bd))
	(body nil))
    (dolist (f (rest bd))
      (cond
       ((consp f) (push f body))
       (t (push (list tag (reverse body)) pairs)
	  (setf tag f)
	  (setf body nil))))
    (push (list tag (reverse body)) pairs)
    (setf pairs (reverse pairs))
    (list (mapcar #'first pairs) (mapcar #'second pairs))))

(define-cps-converter tagbody (e k)
  (let ((bd (rest e)))
    (cond
     ((null bd) (convert nil k))
     ((every #'consp bd) (convert `(progn ,@bd nil) k))
     (t (let* ((sb (split-tagbody-body bd))
	       (gtags (first sb))
	       (gbd (reverse (second sb))))
	  (with-saved-cmp-environments
	   (let ((gnodes (mapcar #'(lambda (x) (new-fenv-node (gensym x)))
				 gtags))
		 (fnodes nil)
		 (kk (new-env-node (gensym "K")))
		 (tag (new-env-node (gensym "T"))))
	     (with-saved-cmp-environments
	      (push (list 'tagbody gtags gnodes tag) *cmp-tenv*)
	      (push (make-continuation-node
		     nil
		     (convert `(progn ,@(first gbd))
			      (make-continuation-node
			       (list (new-env-node (gensym "V")))
			       (make-continuation-call-node
				kk
				(get-constant-node nil)))))
		    fnodes))
	     (mapc #'(lambda (x y)
		       (with-saved-cmp-environments
			(push (list 'tagbody gtags gnodes tag) *cmp-tenv*)
			(push (make-continuation-node
			       nil
			       (convert `(progn ,@x)
					(make-continuation-node
					 (list (new-env-node (gensym "V")))
					 (make-call-node y))))
			      fnodes)))
		   (rest gbd)
		   (reverse (rest gnodes)))
	     (make-call-node
	      (get-gfun-node '%catch-tagbody)
	      k
	      (make-lambda-node
	       (list kk tag)
	       (make-call-node
		(get-gfun-node '%y)
		(make-continuation-node
		 gnodes
		 (apply #'make-call-node
			(get-gfun-node '%y-list)
			(make-continuation-node
			 nil
			 (make-call-node
			  (get-gfun-node '%do-catch-tagbody)
			  k
			  (first gnodes)
			  kk
			  tag))
			fnodes))))))))))))

(defun find-go-tag (tag)
  (dolist (a *cmp-tenv*)
    (if (and (eq 'tagbody (first a)) (member tag (second a)))
	(return (list (fourth a) (nth (position tag (second a)) (third a)))))))
		
(define-cps-converter go (e k)
  (let* ((tag-name (second e))
	 (tag-info (find-go-tag tag-name))
	 (tag (first tag-info))
	 (fun (second tag-info)))
    (if tag
	(make-call-node (get-gfun-node '%throw-go) tag fun)
        (error "no tag named ~s in current lexical context" tag-name))))

;;**** ought to push multiple values on stack to avoid consing for > 1 expr
(define-cps-converter multiple-value-call (e k)
  (case (length e)
    (2 (convert `(funcall ,(second e)) k))
    (3
     (let ((fun (second e))
	   (expr (third e)))
       (convert fun
		(with-saved-cmp-environments
		 (let ((v (new-env-node (gensym "F")))
		       (w (new-env-node (gensym "V"))))
		   (make-continuation-node
		    (list v)
		    (convert expr
			     (make-continuation-node
			      (list w)
			      (make-call-node
			       (get-gfun-node '%mv-collect)
			       (make-continuation-node
				(list (new-env-node (gensym "D")))
				(make-call-node (get-gfun-node '%mvc)
						k
						v))
			       w)))))))))
    (t
     (convert `(apply ,(second e)
		      (nconc ,@(mapcar #'(lambda (x) `(multiple-value-list ,x))
				       (rest (rest e)))))
	      k))))


;;****** uses multiple-value-continuation
(define-cps-converter nth-value (e k)
  (let ((count (second e))
	(expr (third e)))
    (convert count
	     (with-saved-cmp-environments
	      (let ((v (new-env-node (gensym "C")))
		    (w (new-env-node (gensym "V"))))
		(make-continuation-node
		 (list v)
		 (convert expr
			  (make-continuation-node
			   (list w)
			   (make-call-node
			    (get-gfun-node '%mv-collect)
			    (make-continuation-node
			     (list (new-env-node (gensym "D")))
			     (make-call-node (get-gfun-node '%nth-value)
					     k
					     v))
			    w)))))))))

(define-cps-converter throw (e k)
  (let ((tag (second e))
	(expr (third e)))
    (convert tag
	     (with-saved-cmp-environments
	      (let ((v (new-env-node (gensym "C")))
		    (w (new-env-node (gensym "V"))))
		(make-continuation-node
		 (list v)
		 (convert expr
			  (make-continuation-node
			   (list w)
			   (make-call-node
			    (get-gfun-node '%mv-collect)
			    (make-continuation-node
			     (list (new-env-node (gensym "D")))
			     (make-call-node (get-gfun-node '%throw) v))
			    w)))))))))

(define-cps-converter return-from (e k)
  (let* ((name (second e))
	 (expr (third e))
	 (tag (find-block-tag name)))
    (if tag
	(with-saved-cmp-environments
	 (let ((w (new-env-node (gensym "V"))))
	   (convert expr
		    (make-continuation-node
		     (list w)
		     (make-call-node
		      (get-gfun-node '%mv-collect)
		      (make-continuation-node
		       (list (new-env-node (gensym "D")))
		       (make-call-node (get-gfun-node '%throw-return-from)
				       tag))
		      w)))))
        (error "no block named ~s in current lexical context" name))))

(define-cps-converter multiple-value-prog1 (e k)
  (let* ((expr (second e))
	 (body (rest (rest e))))
    (with-saved-cmp-environments
     (let ((w (new-env-node (gensym "V")))
	   (n (new-env-node (gensym "N"))))
       (convert expr
		(make-continuation-node
		 (list w)
		 (make-call-node
		  (get-gfun-node '%mv-collect)
		  (make-continuation-node
		   (list (new-env-node (gensym "D")))
		   (make-call-node
		    (get-gfun-node '%push-values)
		    (make-continuation-node
		     (list n)
		     (convert `(progn ,@body)
			      (make-continuation-node
			       (list (new-env-node (gensym "D")))
			       (make-call-node (get-gfun-node '%pop-values)
					       k
					       n))))))
		  w)))))))

;;***** this could use some decent error checking
(define-cps-converter macrolet (e k)
  (let ((macs (second e))
	(env (list nil *cmp-fenv* *cmp-macros* *cmp-global-macros*))
	(frame nil)
	(body (rest (rest e))))
    (with-saved-cmp-environments
     (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*))
     (convert `(progn ,@body) k))))

(defun substitute-all-variables (n)
  (flet ((substitute1 (n)
	   (if (call-node-p n)
	       (let ((f (call-node-function n)))
		 (when (lambda-node-p f)
		       (lambda-call-substitute-variables n))))))
    (do () ((not (substitute1 n))))
    (dolist (c (node-children n)) (substitute-all-variables c))))

(defun collapse-null-lambda-calls (n)
  (flet ((collapse1 (n)
	   (if (call-node-p n)
	       (let ((f (call-node-function n)))
		 (when (and (lambda-node-p f) (null (lambda-node-arglist f)))
		       (move-node-tree (lambda-node-body f) n)
		       t)))))
    (do () ((not (collapse1 n))))
    (dolist (c (node-children n)) (collapse-null-lambda-calls c))))


syntax highlighted by Code2HTML, v. 0.9.1