summaryrefslogtreecommitdiff
path: root/pure-expansion.lisp
blob: 3b1faf7732509a6aaa2c649f3fa7d53a97e26611 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
(in-package :agnostic-lizard)

; In this file we pretend we only want a macroexpand-all function

(defgeneric metaenv-macroexpand-1 (form env)
            (:documentation
              "Do a single step of macroexpansion on the top level of form in the environment env"))

(defmethod metaenv-macroexpand-1 (x env)
  (cond
    ((symbolp x)
     (let*
       ((local-match
          (find x (metaenv-variable-like-entries env) :key 'first))
        (local-content (second local-match))
        (local-payload (second local-content)))
       (ecase (first local-content)
         ((:plain) x)
         ((:macro) local-payload)
         ((:macro-from) (macroexpand-1 x local-payload))
         ((nil) (macroexpand-1 x (metaenv-fallback-env env))))))
    ((consp x)
     (let*
       ((local-match
          (find (first x) (metaenv-function-like-entries env) :key 'first))
        (local-content (second local-match)))
       (ecase (first local-content)
         ((:plain) x)
         ((:macro :macro-from :macro-function-code :macro-code)
          (with-metaenv-built-env env 'lisp-env `((macroexpand-1 ',x lisp-env))))
         ((nil)
          (cond
            ; the fallback is the global environment anyway
            ((null (metaenv-fallback-env env))
             (with-metaenv-built-env env 'lisp-env `((macroexpand-1 ',x lisp-env))))
            ; no macro defined or an flet shadows the macro
            ((null (macro-function (first x) (metaenv-fallback-env env))) x)
            ; metaenv does not contain anything meaningful
            ((metaenv-irrelevant-for-macroexpand env)
             (macroexpand-1 x (metaenv-fallback-env env)))
            ; the macro is from the global environment or defined inside
            ; the walked fragment
            ((eq (macro-function (first x) nil)
                 (macro-function (first x) (metaenv-fallback-env env)))
             (with-metaenv-built-env env 'lisp-env `((macroexpand-1 ',x lisp-env))))
            ; The macro is from a macrolet reflected in the fallback environment
            ; Assume macroexpand calls inside the macro function expect some other
            ; outside macrolet to be in effect
            (t (macroexpand-1 x (metaenv-fallback-env env))))))))
    (t x)))

(defun metaenv-macroexpand (form env)
  "Perform full macroexpansion of the top level of the form in the environment env"
  (let*
    ((expansion (metaenv-macroexpand-1 form env)))
    (if (eq expansion form) form
      (metaenv-macroexpand expansion env))))

(defgeneric metaenv-macroexpand-all (form env)
            (:documentation
              "Expand all macros on all levels of form in the environment env"))
(defgeneric metaenv-macroexpand-all-special-form (operator form env)
            (:documentation
              "A handler for dispatching macroexpand-all handling of special forms and some macros"))

; When not passing an environment, initialize a metaenv

(defmethod metaenv-macroexpand-all (form (env (eql nil)))
  (metaenv-macroexpand-all form (make-instance 'metaenv)))

(defmethod metaenv-macroexpand-all-special-form (operator form (env (eql nil)))
  (metaenv-macroexpand-all-special-form operator form (make-instance 'metaenv)))

(eval-when (:compile-toplevel :load-toplevel :execute)
  (defparameter *hardwired-operators*
    `(
      ; named-lambda expansions are complicated, better just to walk
      defun defmacro
      ; these are said to expand to something non-portable in some
      ; implementations
      cond multiple-value-bind handler-bind)
    "The list of hardwired macros; they are code-walked without macroexpansion"))

(defmethod metaenv-macroexpand-all (form (env metaenv))
  (let* ((initial-operator (and (consp form) (car form)))
         (hardwiredp (find initial-operator *hardwired-operators*))
         (expanded (if hardwiredp form (metaenv-macroexpand form env))))
    (and
      expanded
      (if (not (consp expanded)) expanded
        (let* ((operator (car expanded)))
          (if (not
                (or
                  (special-operator-p operator)
                  (find operator *hardwired-operators*)))
            (cons operator
                  (loop
                    for arg in (cdr expanded)
                    collect (metaenv-macroexpand-all arg env)))
            (metaenv-macroexpand-all-special-form operator expanded env)))))))

(defun metaenv-map-macroexpand-all (forms env)
  "Apply metaenv-macroexpand-all to all the forms using the same env"
  (loop for form in forms collect (metaenv-macroexpand-all form env)))

(defun metaenv-map-macroexpand-all-after-declarations (entries env enable-docstring)
  "Apply metaenv-map-macroexpand-all to all the forms among the entries, but use the declarations as-is"
  (let*
    ((declarations-and-forms (separate-declarations entries :enable-docstring enable-docstring)))
    (append
      (first declarations-and-forms)
      (metaenv-map-macroexpand-all (second declarations-and-forms) env))))

(defun macroexpand-all (form &optional env)
  "Recursively expand all macro calls in form with initial environment described by env"
  (cond
    ((null env) (metaenv-macroexpand-all form nil))
    ((typep env 'metaenv) (metaenv-macroexpand-all form env))
    (t (metaenv-macroexpand-all form (make-instance 'metaenv :fallback-env env)))))