- The standard provides constantp, which may take an environment. But eval takes no environment, always using the global environment.
- The value of a constant variable isn't necessarily available at the time constantp returns true (such as in the compilation environment).
- If we are passing information via symbol macros (e.g. doing compiler-let-like tricks) then we'd like an easy way to get those values.
(defun quoted-form-p (form)
"Return true if FORM is a QUOTE special form."
(typep form '(cons (eql quote) (cons t null))))
(defun self-evaluating-p (form)
"Return true if FORM evaluates to itself (in any environment)."
(typep form '(and atom (or null (eql t) keyword (not symbol)))))
(defun constant-form-p (form &optional env)
"Return true if FORM is a (bound) constant variable."
(and (symbolp form) (constantp form env) (boundp form)))
(defun macro-eval (form &optional env)
(prog ((expansion form) expanded-p)
macro-eval-1
(when (self-evaluating-p expansion)
(return (values expansion t)))
(when (quoted-form-p expansion)
(return (values (cadr expansion) t)))
(multiple-value-setq (expansion expanded-p)
(macroexpand-1 expansion env))
(when expanded-p (go macro-eval-1))
(return (if (constant-form-p expansion env)
;; (1) We have a constant variable, and
;; (2) the value is actually available.
(values (symbol-value expansion) t)
;; No evaluation.
(values form nil)))))
Note that constantp is "not permitted" to expand compiler macros, but macro-eval could be extended to do this.
Related reading
- The symbol-macrolet trick
- Issue COMPILER-LET-CONFUSION:ELIMINATE
- Issue CONSTANTP-DEFINITION:INTENTIONAL
- constantp values always available at macro expansion time?
metaprogramming