;;;-*- Mode:LISP; Package:(WALKER LISP 1000); Base:10; Syntax:Common-lisp -*-
;;;
;;; *************************************************************************
;;; Copyright (c) 1985 Xerox Corporation.  All rights reserved.
;;;
;;; Use and copying of this software and preparation of derivative works
;;; based upon this software are permitted.  Any distribution of this
;;; software or derivative works must comply with all applicable United
;;; States export control laws.
;;; 
;;; This software is made available AS IS, and Xerox Corporation makes no
;;; warranty about the software, its performance or its conformity to any
;;; specification.
;;; 
;;; Any person obtaining a copy of this software is requested to send their
;;; name and post office or electronic mail address to:
;;;   CommonLoops Coordinator
;;;   Xerox Artifical Intelligence Systems
;;;   2400 Hanover St.
;;;   Palo Alto, CA 94303
;;; (or send Arpanet mail to CommonLoops-Coordinator.pa@Xerox.arpa)
;;;
;;; Suggestions, comments and requests for improvements are also welcome.
;;; *************************************************************************
;;; 
;;; A simple code walker, based IN PART on: (roll the credits)
;;;   Larry Masinter's Masterscope
;;;   Moon's Common Lisp code walker
;;;   Gary Drescher's code walker
;;;   Larry Masinter's simple code walker
;;;   .
;;;   .
;;;   boy, thats fair (I hope).
;;;
;;; For now at least, this code walker really only does what PCL needs it to
;;; do.  Maybe it will grow up someday.
;;;

(in-package 'walker)

(export '(define-walker-template
	  walk-form
	  variable-lexical-p
	  variable-special-p
	  ))

;;; *walk-function* is the function being called on each sub-form as we walk.
;;; Normally it is supplied using the :walk-function keyword argument to
;;; walk-form, but it is OK to bind it around a call to walk-form-internal.
(defvar *walk-function*)

;;; *walk-form* is used by the IF template.  When the first argument to the
;;; if template is a list it will be evaluated with *walk-form* bound to the 
;;; form currently being walked.
(defvar *walk-form*)

;;; *declarations* is a list of the declarations currently in effect.
(defvar *declarations*)
	
;;; *lexical-variables* is a list of the variables bound in the current
;;; contour. In *lexical-variables* the cons whose car is the variable is
;;; meaningful in the sense that the cons whose car is the variable can be
;;; used to keep track of which contour the variable is bound in.
;;;
;;; Now isn't that just the cats pajamas.
;;;
(defvar *lexical-variables*)

;;; An environment of the kind that macroexpand-1 gets as its second
;;; argument.  In fact, that is exactly where it comes from.  This is kind of
;;; kludgy since Common Lisp is somewhat screwed up in this respect.
;;; Hopefully Common Lisp will fix this soon.  For more info see:
;;; MAKE-LEXICAL-ENVIRONMENT
(defvar *environment*)

;;;
;;; With new contour is used to enter a new lexical binding contour which
;;; inherits from the exisiting one.  I admit that using with-new-contour is
;;; often overkill.  It would suffice for the the walker to rebind
;;; *lexical-variables* and *declarations* when walking LET and rebind
;;; *environment* and *declarations* when walking MACROLET etc.
;;; WITH-NEW-CONTOUR is much more convenient and just as correct.
;;; 
(defmacro with-new-contour (&body body)
  `(let ((*declarations* ())			;If Common Lisp got an
						;unspecial declaration
						;this would need to be
						;re-worked.
         (*lexical-variables* *lexical-variables*)
         (*environment* *environment*))
     . ,body))

(defmacro note-lexical-binding (thing)
  `(push ,thing *lexical-variables*))

(defmacro note-declaration (declaration)
  `(push ,declaration *declarations*))


(defun variable-lexically-boundp (var)
  (if (not (boundp '*walk-function*))
      :unsure
      (values (member var *lexical-variables* :test (function eq))
	      (variable-special-p var) 't)))

(defun variable-lexical-p (var)
  (if (not (boundp '*walk-function*))
      :unsure
      (and (not (eq (variable-special-p var) 't))
	   (member var *lexical-variables* :test (function eq)))))

(defun variable-special-p (var)
  (if (not (boundp '*walk-function*))
      (or (variable-globally-special-p var) :unsure)
      (or (dolist (decl *declarations*)
	    (and (eq (car decl) 'special)
		 (member var (cdr decl) :test #'eq)
		 (return t)))
	  (variable-globally-special-p var))))

;;;
;;; VARIABLE-GLOBALLY-SPECIAL-P is used to ask if a variable has been
;;; declared globally special.  Any particular CommonLisp implementation
;;; should customize this function accordingly and send their customization
;;; back.
;;;
;;; The default version of variable-globally-special-p is probably pretty
;;; slow, so it uses *globally-special-variables* as a cache to remember
;;; variables that it has already figured out are globally special.
;;;
;;; This would need to be reworked if an unspecial declaration got added to
;;; Common Lisp.
;;;
;;; Common Lisp nit:
;;;   variable-globally-special-p should be defined in Common Lisp.
;;;
#-(or Symbolics Lucid Xerox Excl KCL (and dec vax common) :CMU HP GCLisp TI
      pyramid)
(defvar *globally-special-variables* ())

(defun variable-globally-special-p (symbol)
  #+Symbolics                   (si:special-variable-p symbol)
  #+Lucid                       (lucid::proclaimed-special-p symbol)
  #+TI                          (get symbol 'special)
  #+Xerox                       (il:variable-globally-special-p symbol)
  #+(and dec vax common)        (get symbol 'system::globally-special)
  #+KCL			        (si:specialp symbol)
  #+excl                        (get symbol 'excl::.globally-special.)
  #+:CMU			(or (get symbol 'lisp::globally-special)
				    (get symbol
					 'clc::globally-special-in-compiler))
  #+HP                          (member (get symbol 'impl:vartype)
					'(impl:fluid impl:global)
					:test #'eq)
  #+:GCLISP                     (gclisp::special-p symbol)
  #+pyramid			(or (get symbol 'lisp::globally-special)
				    (get symbol
					 'clc::globally-special-in-compiler))
  #-(or Symbolics Lucid Xerox Excl KCL (and dec vax common) :CMU HP GCLisp TI
	pyramid)
  (or (not (null (member symbol *globally-special-variables* :test #'eq)))
      (when (eval `(flet ((ref () ,symbol))
		     (let ((,symbol '#,(list nil)))
		       (and (boundp ',symbol) (eq ,symbol (ref))))))
	(push symbol *globally-special-variables*)
	t)))


  ;;   
;;;;;; Handling of special forms (the infamous 24).
  ;;
;;;
;;; and I quote...
;;; 
;;;     The set of special forms is purposely kept very small because
;;;     any program analyzing program (read code walker) must have
;;;     special knowledge about every type of special form. Such a
;;;     program needs no special knowledge about macros...
;;;
;;; So all we have to do here is a define a way to store and retrieve
;;; templates which describe how to walk the 24 special forms and we are all
;;; set...
;;;
;;; Well, its a nice concept, and I have to admit to being naive enough that
;;; I believed it for a while, but not everyone takes having only 24 special
;;; forms as seriously as might be nice.  There are (at least) 3 ways to
;;; lose:
;;
;;;   1 - Implementation x implements a Common Lisp special form as a macro
;;;       which expands into a special form which:
;;;         - Is a common lisp special form (not likely)
;;;         - Is not a common lisp special form (on the 3600 IF --> COND).
;;;
;;;     * We can safe ourselves from this case (second subcase really) by
;;;       checking to see if there is a template defined for something
;;;       before we check to see if we we can macroexpand it.
;;;
;;;   2 - Implementation x implements a Common Lisp macro as a special form.
;;;
;;;     * This is a screw, but not so bad, we save ourselves from it by
;;;       defining extra templates for the macros which are *likely* to
;;;       be implemented as special forms.  (DO, DO* ...)
;;;
;;;   3 - Implementation x has a special form which is not on the list of
;;;       Common Lisp special forms.
;;;
;;;     * This is a bad sort of a screw and happens more than I would like
;;;       to think, especially in the implementations which provide more
;;;       than just Common Lisp (3600, Xerox etc.).
;;;       The fix is not terribly staisfactory, but will have to do for
;;;       now.  There is a hook in get walker-template which can get a
;;;       template from the implementation's own walker.  That template
;;;       has to be converted, and so it may be that the right way to do
;;;       this would actually be for that implementation to provide an
;;;       interface to its walker which looks like the interface to this
;;;       walker.
;;;

(eval-when (compile load eval)

(defmacro get-walker-template-internal (x) ;Has to be inside eval-when because
  `(get ,x 'walker-template))		   ;Golden Common Lisp doesn't hack
					   ;compile time definition of macros
					   ;right for setf.

(defmacro define-walker-template (name template)
  `(eval-when (load eval)
     (setf (get-walker-template-internal ',name) ',template)))
)

(defun get-walker-template (x)
  (cond ((symbolp x)
	 (or (get-walker-template-internal x)
	     (get-implementation-dependent-walker-template x)))
	((and (listp x) (eq (car x) 'lambda))
	 '(lambda repeat (eval)))
	((and (listp x) (eq (car x) 'lambda))
	 '(call repeat (eval)))))

(defun get-implementation-dependent-walker-template (x)
  (declare (ignore x))
  ())


  ;;   
;;;;;; The actual templates
  ;;   

(define-walker-template BLOCK                (NIL NIL REPEAT (EVAL)))
(define-walker-template CATCH                (NIL EVAL REPEAT (EVAL)))
(define-walker-template COMPILER-LET         walk-compiler-let)
(define-walker-template DECLARE              walk-unexpected-declare)
(define-walker-template EVAL-WHEN            (NIL QUOTE REPEAT (EVAL)))
(define-walker-template FLET                 walk-flet/labels)
(define-walker-template FUNCTION             (NIL CALL))
(define-walker-template GO                   (NIL QUOTE))
(define-walker-template IF                   (NIL TEST RETURN RETURN))
(define-walker-template LABELS               walk-flet/labels)
(define-walker-template LAMBDA               walk-lambda)
(define-walker-template LET                  walk-let)
(define-walker-template LET*                 walk-let*)
(define-walker-template MACROLET             walk-macrolet)
(define-walker-template MULTIPLE-VALUE-CALL  (NIL EVAL REPEAT (EVAL)))
(define-walker-template MULTIPLE-VALUE-PROG1 (NIL RETURN REPEAT (EVAL)))
(define-walker-template MULTIPLE-VALUE-SETQ  (NIL (REPEAT (SET)) EVAL))
(define-walker-template MULTIPLE-VALUE-BIND  walk-multiple-value-bind)
(define-walker-template PROGN                (NIL REPEAT (EVAL)))
(define-walker-template PROGV                (NIL EVAL EVAL REPEAT (EVAL)))
(define-walker-template QUOTE                (NIL QUOTE))
(define-walker-template RETURN-FROM          (NIL QUOTE REPEAT (RETURN)))
(define-walker-template SETQ                 (NIL REPEAT (SET EVAL)))
(define-walker-template TAGBODY              walk-tagbody)
(define-walker-template THE                  (NIL QUOTE EVAL))
(define-walker-template THROW                (NIL EVAL EVAL))
(define-walker-template UNWIND-PROTECT       (NIL RETURN REPEAT (EVAL)))

;;; The new special form.
;(define-walker-template pcl::LOAD-TIME-EVAL       (NIL EVAL))

;;;
;;; And the extra templates...
;;;
(define-walker-template DO      walk-do)
(define-walker-template DO*     walk-do*)
(define-walker-template PROG    walk-prog)
(define-walker-template PROG*   walk-prog*)
(define-walker-template COND    (NIL REPEAT ((TEST REPEAT (EVAL)))))


  ;;   
;;;;;; WALK-FORM
  ;;   
;;;
;;; The main entry-point is walk-form, calls back in should use walk-form-internal.
;;; 

(defun walk-form (form &key ((:declarations *declarations*) ())
			    ((:lexical-variables *lexical-variables*) ())
			    ((:environment *environment*) ())
			    ((:walk-function *walk-function*)
			     #'(lambda (x y) (declare (ignore y)) x)))
  (walk-form-internal form 'eval))

;;;
;;; WALK-FORM-INTERNAL is the main driving function for the code walker. It
;;; takes a form and the current context and walks the form calling itself or
;;; the appropriate template recursively.
;;;
;;;   "It is recommended that a program-analyzing-program process a form
;;;    that is a list whose car is a symbol as follows:
;;;
;;;     1. If the program has particular knowledge about the symbol,
;;;        process the form using special-purpose code.  All of the
;;;        standard special forms should fall into this category.
;;;     2. Otherwise, if macro-function is true of the symbol apply
;;;        either macroexpand or macroexpand-1 and start over.
;;;     3. Otherwise, assume it is a function call. "
;;;     

(defun walk-form-internal (form context
			   &aux newform newnewform
				walk-no-more-p macrop
				fn template)
  ;; First apply the *walk-function* to perform whatever translation
  ;; the user wants to to this form.  If the second value returned
  ;; by *walk-function* is T then we don't recurse...
  (multiple-value-setq (newform walk-no-more-p)
    (funcall *walk-function* form context))
  (cond (walk-no-more-p newform)
	((not (eq form newform)) (walk-form-internal newform context))
	((not (consp newform)) newform)
	((setq template (get-walker-template (setq fn (car newform))))
         (if (symbolp template)
             (funcall template newform context)
             (walk-template newform template context)))
	((progn (multiple-value-setq (newnewform macrop)
		  (macroexpand-1 newform *environment*))
		macrop)
	 (walk-form-internal newnewform context))
	((and (symbolp fn)
	      (not (fboundp fn))
	      (special-form-p fn))
	 (error
	   "~S is a special form, not defined in the CommonLisp manual.~%~
            This code walker doesn't know how to walk it.  Please define a~%~
            template for this special form and try again."
	   fn))
	(t
         ;; Otherwise, walk the form as if its just a standard function
         ;; call using a template for standard function call.
         (walk-template newform '(call repeat (eval)) context))))

(defun walk-template (form template context)
  (if (atom template)
      (ecase template
        ((QUOTE NIL) form)
        ((EVAL FUNCTION TEST EFFECT RETURN)
         (walk-form-internal form :EVAL))
        (SET
          (walk-form-internal form :SET))
        ((LAMBDA CALL)
	 (if (symbolp form)
	     form
	     (walk-lambda form context))))
      (case (car template)
        (IF
          (let ((*walk-form* form))
            (walk-template form
			   (if (if (listp (cadr template))
				   (eval (cadr template))
				   (funcall (cadr template) form))
			       (caddr template)
			       (cadddr template))
			   context)))
        (REPEAT
          (walk-template-handle-repeat form
                                       (cdr template)
				       ;; For the case where nothing happens
				       ;; after the repeat optimize out the
				       ;; call to length.
				       (if (null (cddr template))
					   ()
					   (nthcdr (- (length form)
						      (length
							(cddr template)))
						   form))
                                       context))
        (REMOTE
          (walk-template form (cadr template) context))
        (otherwise
          (cond ((atom form) form)
                (t (recons form
                           (walk-template
			     (car form) (car template) context)
                           (walk-template
			     (cdr form) (cdr template) context))))))))

(defun walk-template-handle-repeat (form template stop-form context)
  (if (eq form stop-form)
      (walk-template form (cdr template) context)
      (walk-template-handle-repeat-1 form
				     template
				     (car template)
				     stop-form
				     context)))

(defun walk-template-handle-repeat-1 (form template repeat-template
					   stop-form context)
  (cond ((null form) ())
        ((eq form stop-form)
         (if (null repeat-template)
             (walk-template stop-form (cdr template) context)       
             (error "While handling repeat:
                     ~%~Ran into stop while still in repeat template.")))
        ((null repeat-template)
         (walk-template-handle-repeat-1
	   form template (car template) stop-form context))
        (t
         (recons form
                 (walk-template (car form) (car repeat-template) context)
                 (walk-template-handle-repeat-1 (cdr form)
						template
						(cdr repeat-template)
						stop-form
						context)))))

(defun recons (x car cdr)
  (if (or (not (eq (car x) car))
          (not (eq (cdr x) cdr)))
      (cons car cdr)
      x))

(defun relist* (x &rest args)
  (relist*-internal x args))

(defun relist*-internal (x args)
  (if (null (cdr args))
      (car args)
      (recons x (car args) (relist*-internal (cdr x) (cdr args)))))


  ;;   
;;;;;; Special walkers
  ;;

(defun walk-declarations (body fn
			       &optional doc-string-p declarations old-body
			       &aux (form (car body)) macrop new-form)
  (cond ((and (stringp form)			;might be a doc string
              (cdr body)			;isn't the returned value
              (null doc-string-p)		;no doc string yet
              (null declarations))		;no declarations yet
         (recons body
                 form
                 (walk-declarations (cdr body) fn t)))
        ((and (listp form) (eq (car form) 'declare))
         ;; Got ourselves a real live declaration.  Record it, look for more.
         (dolist (declaration (cdr form))
           (note-declaration declaration)
           (push declaration declarations))
         (recons body
                 form
                 (walk-declarations
		   (cdr body) fn doc-string-p declarations)))
        ((and form
	      (listp form)
	      (null (get-walker-template (car form)))
	      (progn
		(multiple-value-setq (new-form macrop)
				     (macroexpand-1 (car form) *environment*))
		macrop))
	 ;; This form was a call to a macro.  Maybe it expanded
	 ;; into a declare?  Recurse to find out.
	 (walk-declarations (recons body new-form (cdr body))
			    fn doc-string-p declarations
			    (or old-body body)))
	(t
	 ;; Now that we have walked and recorded the declarations,
	 ;; call the function our caller provided to expand the body.
	 ;; We call that function rather than passing the real-body
	 ;; back, because we are RECONSING up the new body.
	 (funcall fn (or old-body body)))))


(defun walk-unexpected-declare (form context)
  (declare (ignore context))
  (warn "Encountered declare ~S in a place where a declare was not expected."
	form)
  form)

(defun walk-arglist (arglist context &optional (destructuringp nil) &aux arg)
  (cond ((null arglist) ())
        ((symbolp (setq arg (car arglist)))
         (or (member arg lambda-list-keywords :test #'eq)
             (note-lexical-binding arg))
         (recons arglist
                 arg
                 (walk-arglist (cdr arglist)
                               context
                               (and destructuringp
				    (not (member arg lambda-list-keywords
						 :test #'eq))))))
        ((consp arg)
         (prog1 (if destructuringp
                    (walk-arglist arg context destructuringp)
                    (recons arglist
                            (relist* arg
                                     (car arg)
                                     (walk-form-internal (cadr arg) 'eval)
                                     (cddr arg))
                            (walk-arglist (cdr arglist) context nil)))
                (if (symbolp (car arg))
                    (note-lexical-binding (car arg))
                    (note-lexical-binding (cadar arg)))
                (or (null (cddr arg))
                    (not (symbolp (caddr arg)))
                    (note-lexical-binding arg))))
          (t
	   (error "Can't understand something in the arglist ~S" arglist))))

(defun walk-let (form context)
  (walk-let/let* form context nil))

(defun walk-let* (form context)
  (walk-let/let* form context t))

(defun walk-prog (form context)
  (walk-let/let* form context nil))

(defun walk-prog* (form context)
  (walk-let/let* form context t))

(defun walk-do (form context)
  (walk-do/do* form context nil))

(defun walk-do* (form context)
  (walk-do/do* form context t))

(defun walk-let/let* (form context sequentialp)
  (let ((old-declarations *declarations*)
	(old-lexical-variables *lexical-variables*))
    (with-new-contour
      (let* ((let/let* (car form))
             (bindings (cadr form))
             (body (cddr form))
             walked-bindings
             (walked-body
               (walk-declarations 
                 body
                 #'(lambda (real-body)
                     (setq walked-bindings
                           (walk-bindings-1 bindings
					    old-declarations
					    old-lexical-variables
					    context
					    sequentialp))
                     (walk-template real-body '(repeat (eval)) context)))))
        (relist*
	  form let/let* walked-bindings walked-body)))))

(defun walk-do/do* (form context sequentialp)
  (let ((old-declarations *declarations*)
	(old-lexical-variables *lexical-variables*))
    (with-new-contour
      (let* ((do/do* (car form))
             (bindings (cadr form))
             (end-test (caddr form))
             (body (cdddr form))
             walked-bindings
             (walked-body
               (walk-declarations
                 body
                 #'(lambda (real-body)
                     (setq walked-bindings
                           (walk-bindings-1 bindings
					    old-declarations
					    old-lexical-variables
					    context
					    sequentialp))
                     (walk-template real-body '(repeat (eval)) context)))))
        (relist* form
                 do/do*
                 (walk-bindings-2 bindings walked-bindings context)
                 (walk-template end-test '(test repeat (eval)) context)
                 walked-body)))))

(defun walk-multiple-value-bind (form context)
  (let ((old-declarations *declarations*)
	(old-lexical-variables *lexical-variables*))
    (with-new-contour
      (let* ((mvb (car form))
             (bindings (cadr form))
	     (mv-form (walk-template (caddr form) 'eval context))
             (body (cdddr form))
             walked-bindings
             (walked-body
               (walk-declarations 
                 body
                 #'(lambda (real-body)
                     (setq walked-bindings
                           (walk-bindings-1 bindings
					    old-declarations
					    old-lexical-variables
					    context
					    nil))
                     (walk-template real-body '(repeat (eval)) context)))))
        (relist* form mvb walked-bindings mv-form walked-body)))))
                            
(defun walk-bindings-1 (bindings old-declarations old-lexical-variables
				 context sequentialp)
  (and bindings
       (let ((binding (car bindings)))
         (recons bindings
                 (if (symbolp binding)
                     (prog1 binding
                            (note-lexical-binding binding))
                     (prog1 (let ((*declarations* old-declarations)
				  (*lexical-variables*
				    (if sequentialp
					*lexical-variables*
					old-lexical-variables)))
                              (relist* binding
                                       (car binding)
                                       (walk-form-internal (cadr binding)
							   context)
                                       (cddr binding)))	;save cddr for DO/DO*
						        ;it is the next value
						        ;form. Don't walk it
						        ;now though.
                            (note-lexical-binding (car binding))))
                 (walk-bindings-1 (cdr bindings)
				  old-declarations old-lexical-variables
				  context sequentialp)))))

(defun walk-bindings-2 (bindings walked-bindings context)
  (and bindings
       (let ((binding (car bindings))
             (walked-binding (car walked-bindings)))
         (recons bindings
		 (if (symbolp binding)
		     binding
		     (relist* binding
			      (car walked-binding)
			      (cadr walked-binding)
			      (walk-template (cddr binding) '(eval) context)))		 
                 (walk-bindings-2 (cdr bindings)
				  (cdr walked-bindings)
				  context)))))

(defun walk-lambda (form context)
  (with-new-contour    
    (let* ((arglist (cadr form))
           (body (cddr form))
           (walked-arglist nil)
           (walked-body
             (walk-declarations body
	       #'(lambda (real-body)
		   (setq walked-arglist (walk-arglist arglist context))
		   (walk-template real-body '(repeat (eval)) context)))))
      (relist* form
               (car form)
	       walked-arglist
               walked-body))))

(defun walk-tagbody (form context)
  (recons form (car form) (walk-tagbody-1 (cdr form) context)))

(defun walk-tagbody-1 (form context)
  (and form
       (recons form
               (walk-form-internal (car form)
				   (if (symbolp (car form)) 'quote context))
               (walk-tagbody-1 (cdr form) context))))

(defun walk-compiler-let (form context)
  (with-new-contour
    (let ((vars ())
	  (vals ()))
      (dolist (binding (cadr form))
	(cond ((symbolp binding) (push binding vars) (push nil vals))
	      (t
	       (push (car binding) vars)
	       (push (eval (cadr binding)) vals))))
      (relist* form
               (car form)
               (cadr form)
               (progv vars vals
                 (note-declaration (cons 'special vars))
                 (walk-template (cddr form) '(repeat (eval)) context))))))

(defun walk-macrolet (form context)
  (labels ((walk-definitions (definitions)
             (and (not (null definitions))
                  (let ((definition (car definitions)))
                    (recons definitions
                            (with-new-contour
                              (relist* definition
                                       (car definition)
                                       (walk-arglist (cadr definition)
						     context t)
                                       (walk-declarations (cddr definition)
					 #'(lambda (real-body)
					     (walk-template
					       real-body
					       '(repeat (eval))
					       context)))))
                            (walk-definitions (cdr definitions)))))))
    (with-new-contour
      (relist* form
               (car form)
               (walk-definitions (cadr form))
               (progn (setq *environment*
			    (make-lexical-environment form *environment*))
                      (walk-declarations (cddr form)
			#'(lambda (real-body)
			    (walk-template real-body
						    '(repeat (eval))
						    context))))))))

(defun walk-flet/labels (form context)
  (with-new-contour
    (labels ((walk-definitions (definitions)
               (if (null definitions)
                   ()
                   (recons definitions
                           (walk-lambda (car definitions) context)
                           (walk-definitions (cdr definitions)))))
             (update-environment ()
               (setq *environment*
		     (make-lexical-environment form *environment*))))
      (relist* form
               (car form)
               (ecase (car form)
                 (flet
                   (prog1 (walk-definitions (cadr form))
                          (update-environment)))
                 (labels
                   (update-environment)
                   (walk-definitions (cadr form))))
               (walk-declarations (cddr form)
		 #'(lambda (real-body)
		     (walk-template real-body '(repeat (eval)) context)))))))

;;; make-lexical-environemnt is kind of gross.  It would be less gross if
;;; EVAL took an environment argument.
;;;
;;; Common Lisp nit:
;;;    if Common Lisp should provide mechanisms for playing with
;;;    environments explicitly.  making them, finding out what
;;;    functions are bound in them etc.  Maybe compile should
;;;    take an environment argument too?
;;;    

#-(or GCLisp)
(defun make-lexical-environment (macrolet/flet/labels-form environment)
  (if (null (cadr macrolet/flet/labels-form))
      environment
      (evalhook (list (car macrolet/flet/labels-form)
		      (cadr macrolet/flet/labels-form)
		      ()			;Fake out macroexpansion which
						;may be looking for declares.
						;Actually, this should be a
						;no-op since declarations are
						;not allowed to appear here,
						;but...
		      (list 'make-lexical-environment-2))
		'make-lexical-environment-1
		()
		environment)))

(defun make-lexical-environment-1 (form env)
  (setq form (macroexpand form #-excl env
			       #+excl (cadr env)))
  (evalhook form  'make-lexical-environment-1 nil env))

(defmacro make-lexical-environment-2 (&environment env)
  (list 'quote (copy-tree env)))

#+GCLisp
(defun make-lexical-environment (mfl-form env)
  (do ((new-entries nil)
       (new-funcs (second mfl-form) (cdr new-funcs)))
      ((null new-funcs)
       (if (eq :compiler-menv (car env))
	   `(:compiler-menv ,@new-entries ,@(cdr env))
	   (append new-entries env)))
    (let ((lex-fn-entry
	    (ecase (car mfl-form)
	      ((flet labels) (cons (caar new-funcs) nil))
	      (macrolet
		(let* ((lisp::*defmacro-unrestricted-p* t)
		       (expander (cdr (lisp::defmac-trans
					(cadr (car new-funcs))
					(cddr (car new-funcs))))))
		  (declare (special lisp::*defmacro-unrestricted-p*))
		  (cons (caar new-funcs)
			(if (eq :compiler-menv (car env))
			    `(,(gensym) ,expander)
			    `(MACRO . ,expander))))))))
      (push lex-fn-entry new-entries))))

  ;;   
;;;;;; Tests tests tests
  ;;

#|

(defmacro take-it-out-for-a-test-walk (form)
  `(progn 
     (terpri)
     (terpri)
     (let ((copy-of-form (copy-tree ',form))
           (result (walk-form ',form :walk-function
                              '(lambda (x y)
                                 (format t "~&Form: ~S ~3T Context: ~A" x y)
                                 (when (symbolp x)
				   (multiple-value-bind (lexical special)
				       (variable-lexically-boundp x)
                                     (when lexical
                                       (format t ";~3T")
                                       (format t "lexically bound"))
                                     (when special
                                       (format t ";~3T")
                                       (format t "declared special"))
                                     (when (boundp x)
                                       (format t ";~3T")
                                       (format t "bound: ~S " (eval x)))))
                                 x))))
       (cond ((not (equal result copy-of-form))
              (format t "~%Warning: Result not EQUAL to copy of start."))
             ((not (eq result ',form))
              (format t "~%Warning: Result not EQ to copy of start.")))
       (#+Symbolics zl:grind-top-level
        #-Symbolics print
                                  result)
       result)))

(defun foo (&rest ignore) ())

(defmacro bar (x) `'(global-bar-expanded ,x))

(defun baz (&rest ignore) ())

(take-it-out-for-a-test-walk (foo arg1 arg2 arg3))
(take-it-out-for-a-test-walk (foo (baz 1 2) (baz 3 4 5)))

(take-it-out-for-a-test-walk (block block-name a b c))
(take-it-out-for-a-test-walk (block block-name (foo a) b c))

(take-it-out-for-a-test-walk (catch catch-tag (foo a) b c))
(take-it-out-for-a-test-walk (compiler-let ((a 1) (b 2)) (foo a) b))
(take-it-out-for-a-test-walk (prog () (declare (special a b))))
(take-it-out-for-a-test-walk (let (a b c)
                               (declare (special a b))
                               (foo a) b c))
(take-it-out-for-a-test-walk (let (a b c)
                               (declare (special a) (special b))
                               (foo a) b c))
(take-it-out-for-a-test-walk (let (a b c)
                               (declare (special a))
                               (declare (special b))
                               (foo a) b c))
(take-it-out-for-a-test-walk (let (a b c)
                               (declare (special a))
                               (declare (special b))
                               (let ((a 1))
                                 (foo a) b c)))
(take-it-out-for-a-test-walk (eval-when ()
                               a
                               (foo a)))
(take-it-out-for-a-test-walk (eval-when (eval when load)
                               a
                               (foo a)))

(take-it-out-for-a-test-walk (multiple-value-bind (a b) (foo a b) (list a b)))
(take-it-out-for-a-test-walk (multiple-value-bind (a b)
				 (foo a b)
			       (declare (special a))
			       (list a b)))
(take-it-out-for-a-test-walk (progn (function foo)))
(take-it-out-for-a-test-walk (progn a b (go a)))
(take-it-out-for-a-test-walk (if a b c))
(take-it-out-for-a-test-walk (if a b))
(take-it-out-for-a-test-walk ((lambda (a b) (list a b)) 1 2))
(take-it-out-for-a-test-walk ((lambda (a b) (declare (special a)) (list a b))
			      1 2))
(take-it-out-for-a-test-walk (let ((a a) (b a) (c b)) (list a b c)))
(take-it-out-for-a-test-walk (let* ((a a) (b a) (c b)) (list a b c)))
(take-it-out-for-a-test-walk (let ((a a) (b a) (c b))
                               (declare (special a b))
                               (list a b c)))
(take-it-out-for-a-test-walk (let* ((a a) (b a) (c b))
                               (declare (special a b))
                               (list a b c)))
(take-it-out-for-a-test-walk (let ((a 1) (b 2))
                               (foo bar)
                               (declare (special a))
                               (foo a b)))
(take-it-out-for-a-test-walk (multiple-value-call #'foo a b c))
(take-it-out-for-a-test-walk (multiple-value-prog1 a b c))
(take-it-out-for-a-test-walk (progn a b c))
(take-it-out-for-a-test-walk (progv vars vals a b c))
(take-it-out-for-a-test-walk (quote a))
(take-it-out-for-a-test-walk (return-from block-name a b c))
(take-it-out-for-a-test-walk (setq a 1))
(take-it-out-for-a-test-walk (setq a (foo 1) b (bar 2) c 3))
(take-it-out-for-a-test-walk (tagbody a b c (go a)))
(take-it-out-for-a-test-walk (the foo (foo-form a b c)))
(take-it-out-for-a-test-walk (throw tag-form a))
(take-it-out-for-a-test-walk (unwind-protect (foo a b) d e f))


(take-it-out-for-a-test-walk (flet ((flet-1 (a b) (list a b)))
                               (flet-1 1 2)
                               (foo 1 2)))
(take-it-out-for-a-test-walk (labels ((label-1 (a b) (list a b)))
                               (label-1 1 2)
                               (foo 1 2)))
(take-it-out-for-a-test-walk (macrolet ((macrolet-1 (a b) (list a b)))
                               (macrolet-1 a b)
                               (foo 1 2)))

(take-it-out-for-a-test-walk (macrolet ((foo (a) `(inner-foo-expanded ,a)))
                               (foo 1)))

(take-it-out-for-a-test-walk (progn (bar 1)
                                    (macrolet ((bar (a)
						 `(inner-bar-expanded ,a)))
                                      (bar 1))))

(take-it-out-for-a-test-walk (progn (bar 1)
                                    (macrolet ((bar (s)
						 (bar s)
						 `(inner-bar-expanded ,s)))
                                      (bar 2))))

(take-it-out-for-a-test-walk (cond (a b)
                                   ((foo bar) a (foo a))))


(let ((the-lexical-variables ()))
  (walk-form '(let ((a 1) (b 2))
		#'(lambda (x) (list a b x y)))
	     :walk-function #'(lambda (form context)
				(when (and (symbolp form)
					   (variable-lexical-p form))
				  (push form the-lexical-variables))
				form))
  (or (and (= (length the-lexical-variables) 3)
	   (member 'a the-lexical-variables)
	   (member 'b the-lexical-variables)
	   (member 'x the-lexical-variables))
      (error "Walker didn't do lexical variables of a closure properly.")))

|#

()