;;;; MACROEXPAND and friends

;;;; This software is part of the SBCL system. See the README file for
;;;; more information.
;;;;
;;;; This software is derived from the CMU CL system, which was
;;;; written at Carnegie Mellon University and released into the
;;;; public domain. The software is in the public domain and is
;;;; provided with absolutely no warranty. See the COPYING and CREDITS
;;;; files for more information.

(in-package "SB!IMPL")

;;;; syntactic environment access

(defun sb!xc:special-operator-p (symbol)
  #!+sb-doc
  "If the symbol globally names a special form, return T, otherwise NIL."
  (declare (symbol symbol))
  (eq (info :function :kind symbol) :special-form))

(defvar sb!xc:*macroexpand-hook* 'funcall
  #!+sb-doc
  "The value of this variable must be a designator for a function that can
  take three arguments, a macro expander function, the macro form to be
  expanded, and the lexical environment to expand in. The function should
  return the expanded form. This function is called by MACROEXPAND-1
  whenever a runtime expansion is needed. Initially this is set to
  FUNCALL.")

(defun sb!xc:macroexpand-1 (form &optional env)
  #!+sb-doc
  "If form is a macro (or symbol macro), expand it once. Return two values,
   the expanded form and a T-or-NIL flag indicating whether the form was, in
   fact, a macro. ENV is the lexical environment to expand in, which defaults
   to the null environment."
  (cond ((and (consp form) (symbolp (car form)))
         (let ((def (sb!xc:macro-function (car form) env)))
           (if def
               (values (funcall sb!xc:*macroexpand-hook*
                                def
                                form
                                ;; As far as I can tell, it's not clear from
                                ;; the ANSI spec whether a MACRO-FUNCTION
                                ;; function needs to be prepared to handle
                                ;; NIL as a lexical environment. CMU CL
                                ;; passed NIL through to the MACRO-FUNCTION
                                ;; function, but I prefer SBCL "be conservative
                                ;; in what it sends and liberal in what it
                                ;; accepts" by doing the defaulting itself.
                                ;; -- WHN 19991128
                                (coerce-to-lexenv env))
                       t)
               (values form nil))))
        ((symbolp form)
         (flet ((perform-symbol-expansion (symbol expansion)
                  ;; CLHS 3.1.2.1.1 specifies that symbol-macros are expanded
                  ;; via the macroexpand hook, too.
                  (funcall sb!xc:*macroexpand-hook*
                           (constantly expansion)
                           symbol
                           env)))
           (let* ((venv (when env (sb!c::lexenv-vars env)))
                  (local-def (cdr (assoc form venv))))
             (cond ((and (consp local-def)
                         (eq (car local-def) 'macro))
                    (values (perform-symbol-expansion form (cdr local-def)) t))
                   (local-def
                    (values form nil))
                   ((eq (info :variable :kind form) :macro)
                    (let ((expansion (info :variable :macro-expansion form)))
                      (values (perform-symbol-expansion form expansion) t)))
                   (t
                    (values form nil))))))
        (t
         (values form nil))))

(defun sb!xc:macroexpand (form &optional env)
  #!+sb-doc
  "Repetitively call MACROEXPAND-1 until the form can no longer be expanded.
   Returns the final resultant form, and T if it was expanded. ENV is the
   lexical environment to expand in, or NIL (the default) for the null
   environment."
  (labels ((frob (form expanded)
             (multiple-value-bind (new-form newly-expanded-p)
                 (sb!xc:macroexpand-1 form env)
               (if newly-expanded-p
                   (frob new-form t)
                   (values new-form expanded)))))
    (frob form nil)))

;;; Like MACROEXPAND-1, but takes care not to expand special forms.
(defun %macroexpand-1 (form &optional env)
  (if (or (atom form)
          (let ((op (car form)))
            (not (and (symbolp op) (sb!xc:special-operator-p op)))))
      (sb!xc:macroexpand-1 form env)
      (values form nil)))

;;; Like MACROEXPAND, but takes care not to expand special forms.
(defun %macroexpand (form &optional env)
  (labels ((frob (form expanded)
             (multiple-value-bind (new-form newly-expanded-p)
                 (%macroexpand-1 form env)
               (if newly-expanded-p
                   (frob new-form t)
                   (values new-form expanded)))))
    (frob form nil)))
