258 lines
		
	
	
		
			No EOL
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
			
		
		
	
	
			258 lines
		
	
	
		
			No EOL
		
	
	
		
			7.8 KiB
		
	
	
	
		
			Markdown
		
	
	
	
	
	
| 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. |