about summary refs log tree commit diff
path: root/third_party/lisp/quasiquote_2/README.md
blob: 2d590a0564aea33fe3abcb32e79b375a0c49ffe3 (plain) (blame)
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
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
quasiquote-2.0
==============

Why should it be hard to write macros that write other macros?
Well, it shouldn't!

quasiquote-2.0 defines slightly different rules for quasiquotation,
that make writing macro-writing macros very smooth experience.

NOTE: quasiquote-2.0 does horrible things to shared structure!!!
(it does a lot of COPY-TREE's, so shared-ness is destroyed).
So, it's indeed a tool to construct code (where it does not matter much if the
structure is shared or not) and not the data (or, at least, not the data with shared structure)


```lisp
(quasiquote-2.0:enable-quasiquote-2.0)

(defmacro define-my-macro (name args &body body)
  `(defmacro ,name ,args
     `(sample-thing-to-expand-to
        ,,@body))) ; note the difference from usual way

(define-my-macro foo (x y)
  ,x ; now here injections of quotation constructs work
  ,y)

(define-my-macro bar (&body body)
  ,@body) ; splicing is also easy
```

The "injections" in macros FOO and BAR work as naively expected, as if I had written
```lisp
(defmacro foo (x y)
  `(sample-thing-to-expand-to ,x ,y))

(defmacro bar (&body body)
  `(sample-thing-to-expand-to ,@body))

(macroexpand-1 '(foo a b))

  '(SAMPLE-THING-TO-EXPAND-TO A B)

(macroexpand-1 '(bar a b c))

  '(SAMPLE-THING-TO-EXPAND-TO A B C)
```


So, how is this effect achieved?


DIG, INJECT and SPLICE
-------------------------

The transformations of backquote occur at macroexpansion-time and not at read-time.
It is totally possible not to use any special reader syntax, but just
underlying macros directly!

At the core is a macro DIG, which expands to the code that generates the
expression according to the rules, which are roughly these:
  * each DIG increases "depth" by one (hence the name)
  * each INJECT or SPLICE decreases "depth" by one
  * if depth is 0, evaluation is turned on
  * if depth if not zero (even if it's negative!) evaluation is off
  * SPLICE splices the form, similarly to ordinary `,@`, INJECT simply injects, same as `,`

```lisp
;; The example using macros, without special reader syntax

(dig ; depth is 1 here
  (a b
     (dig ; depth is 2 here
       ((inject c) ; this inject is not evaluated, because depth is nonzero
        (inject (d ;depth becomes 1 here again
                (inject e) ; and this inject is evaluated, because depth becomes zero
                ))
        (inject 2 f) ; this inject with level specification is evaluated, because it
                     ; decreases depth by 2
        ))))


;; the same example using ENABLE-QUASIQUOTE-2.0 syntax is written as
`(a b `(,c ,(d ,e) ,,f)) ; note double comma acts different than usually
```


The ENABLE-QUASIQUOTE-2.0 macro just installs reader that reads
`FORM as (DIG FORM), ,FORM as (INJECT FORM) and ,@FORM as (SPLICE FORM).
You can just as well type DIG's, INJECT's and SPLICE's directly, 
(in particular, when writing utility functions that generate macro-generating code)
or roll your own convenient reader syntax (pull requests are welcome).

So, these two lines (with ENABLE-QUASIQUOTE-2.0) read the same
```lisp
`(a (,b `,,c) d)

(dig (a ((inject b) (dig (inject 2 c))) d))
```

You may notice the (INJECT 2 ...) form appearing, which is described below.


At "level 1", i.e. when only \` , and ,@ are used, and not, say \`\` ,, ,', ,,@ ,',@
this behaves exactly as usual quasiquotation.


The optional N argument
--------------

All quasiquote-2.0 operators accept optional "depth" argument,
which goes before the form for human readability.

Namely, (DIG N FORM) increases depth by N instead of one and
(INJECT N FORM) decreases depth by N instead of one.

```lisp
(DIG 2 (INJECT 2 A))

; gives the same result as

(DIG (INJECT A))
```


In fact, with ENABLE-QUASIQUOTE-2.0, say, ,,,,,FORM (5 quotes) reads as (INJECT 5 FORM)
and ,,,,,@FORM as (SPLICE 5 FORM)


More examples
-------------

For fairly complicated example, which uses ,,,@ and OINJECT (see below),
 see DEFINE-BINOP-DEFINER macro
in CG-LLVM (https://github.com/mabragor/cg-llvm/src/basics.lisp),
desire to write which was the initial impulse for this project.


For macro, that is not a macro-writing macro, yet benefits from
ability to inject using `,` and `,@`, consider JOINING-WITH-COMMA-SPACE macro
(also from CG-LLVM)

```lisp
(defmacro joining-with-comma-space (&body body)
  ;; joinl just joins strings in the list with specified string
  `(joinl ", " (mapcar #'emit-text-repr
		       (remove-if-not #'identity  `(,,@body)))))

;; the macro can be then used uniformly over strings and lists of strings
(defun foo (x y &rest z)
  (joining-with-comma-space ,x ,y ,@z))

(foo "a" "b" "c" "d")
  ;; produces
  "a, b, c, d"
```


ODIG and OINJECT and OSPLICE
----------------------------

Sometimes you don't want DIG's macroexpansion to look further into the structure of
some INJECT or SPLICE or DIG in its subform,
if the depth does not match. In these cases you need "opaque" versions of
DIG, INJECT and SPLICE, named, respectively, ODIG, OINJECT and OSPLICE.

```lisp
;; here injection of B would occur
(defun foo (b)
  (dig (dig (inject (a (inject b))))))

;; and here not, because macroexpansion does not look into OINJECT form
(defun bar (b)
  (dig (dig (oinject (a (inject b))))))

(foo 1)

  '(DIG (INJECT (A 1)))

(bar 1)

  '(DIG (OINJECT (A (INJECT B))))
```

MACRO-INJECT and MACRO-SPLICE
-----------------------------

Sometimes you just want to abstract-out some common injection patterns...
That is, you want macros, that expand into common injection patterns.
However, you want this only sometimes, and only in special circumstances.
So it won't do, if INJECT and SPLICE just expanded something, whenever it
turned out to be macro. For that, use MACRO-INJECT and MACRO-SPLICE.

```lisp
;; with quasiquote-2.0 syntax turned on
(defmacro inject-n-times (form n)
  (make-list n :initial-element `(inject ,form)))

(let (x 0)
  `(dig (a (macro-inject (inject-n-times (incf x) 3)))))
;; yields
'(a (1 2 3))

;;and same with MACRO-SPLICE
(let (x 0)
  `(dig (a (macro-splice (inject-n-times (incf x) 3)))))
;; yields
'(a 1 2 3)
```

OMACRO-INJECT and OMACRO-SPLICE are, as usual, opaque variants of MACRO-INJECT and MACRO-SPLICE.

Both MACRO-INJECT and MACRO-SPLICE expand their subform exactly once (using MACROEXPAND-1),
before plugging it into list.
If you want to expand as much as it's possible, use MACRO-INJECT-ALL and MACRO-SPLICE-ALL,
which expand using MACROEXPAND before injecting/splicing, respectively.
That implies, that while subform of MACRO-INJECT and MACRO-SPLICE is checked to be
macro-form, the subform of MACRO-INJECT-ALL is not.


Terse syntax of the ENABLE-QUASIQUOTE-2.0
-----------------------------------------

Of course, typing all those MACRO-INJECT-ALL, or OMACRO-SPLICE-ALL or whatever explicitly
every time you want this special things is kind of clumsy. For that, default reader
of quasiquote-2.0 provides extended syntax

```lisp
',,,,!oma@x

;; reads as
'(OMACRO-SPLICE-ALL 4 X)
```

That is, the regexp of the syntax is
[,]+![o][m][a][@]<whatever>

As usual, number of commas determine the anti-depth of the injector, exclamation mark
turns on the syntax, if `o` is present, opaque version of injector will be used,
if `m` is present, macro-expanding version of injector will be used and if
`a` is present, macro-all version of injector will be used.

Note: it's possible to write ,!ax, which will read as (INJECT-ALL X), but
this will not correspond to the actual macro name.

Note: it was necessary to introduce special escape-char for extended syntax,
since usual idioms like `,args` would otherwise be completely screwed.


TODO
----

* WITH-QUASIQUOTE-2.0 read-macro-token for local enabling of ` and , overloading
* wrappers for convenient definition of custom overloading schemes
* some syntax for opaque operations

P.S. Name "quasiquote-2.0" comes from "patronus 2.0" spell from www.hpmor.com
     and has nothing to do with being "the 2.0" version of quasiquote.