(defmacro do-grid [[r rows c cols] & body] ...) — positional names, a [ ] pattern wherever an argument is a vector, and & for the tail. The reading of the list lives in Expand, below both sides that need it: Parse turns it into the bindings a macro body opens with, and Macro checks a call against the same reading before expanding it, so arity and shape are refused with the call's own location rather than with the Loc.from_macro stamp every node of an expansion carries. The breaking half: [args] used to bind the whole argument list and now binds the first argument. The whole list is [& args], and every defmacro in the tree — prelude, vendor, tests, the elisp fixtures — was migrated to it. One grammar, not a legacy mode.
83 lines
3.3 KiB
Plaintext
83 lines
3.3 KiB
Plaintext
;;;; Macros: a defmacro in the file, called from the file.
|
|
;;;;
|
|
;;;; There is no interpreter, so every macro below was compiled into a shared
|
|
;;;; object and dlopened into the compiler before this file's first line was
|
|
;;;; parsed. What arrives here is the expansion; nothing at run time knows a
|
|
;;;; macro was involved.
|
|
;;;;
|
|
;;;; A macro takes one parameter, the slice of forms written at its call site,
|
|
;;;; and answers one form. That is where variadics come from in a language with
|
|
;;;; no &rest: (len args) is how many were written.
|
|
|
|
;; The simplest one there is: two forms, in order. It proves the call site's
|
|
;; arguments arrive as forms and come back as code.
|
|
(defmacro both [& args]
|
|
`(do ~(at args 0) ~(at args 1)))
|
|
|
|
;; Splicing, which is the only reason ~@ exists: the body is however many forms
|
|
;; were written, and they go where a list is expected.
|
|
(defmacro when2 [& args]
|
|
`(if ~(at args 0) (do ~@(form-rest args 1))))
|
|
|
|
;; Expansion is not hygienic -- Common Lisp's rule and Clojure's, settled in
|
|
;; plan.org's open decision 2 -- so a macro that needs a name of its own asks
|
|
;; for one. gensym is a prelude function the loaded module runs while it runs,
|
|
;; and the name it answers starts with ~, which is a delimiter, so no symbol
|
|
;; the reader can produce is able to collide with it.
|
|
;;
|
|
;; Without this, `twice` would bind `tmp` and the caller's own `tmp` would be
|
|
;; shadowed inside it. The two calls below are the difference.
|
|
(defmacro twice [& args]
|
|
(let [v (gensym)]
|
|
`(let [~v ~(at args 0)]
|
|
(+ ~v ~v))))
|
|
|
|
;; A macro that answers a call to another macro. This costs the pre-pass
|
|
;; nothing: `both` is inside the quasiquote, so it is part of what this macro
|
|
;; *returns* and is expanded again after it returns, and `announce` can be
|
|
;; compiled without `both` existing.
|
|
(defmacro announce [& args]
|
|
`(both (print "-> ") ~(at args 0)))
|
|
|
|
;; This is the one that makes the pre-pass a fixpoint rather than a sweep. The
|
|
;; call to `id` is not inside a quasiquote, so it runs while *this macro is
|
|
;; being compiled* -- which means `id` has to be compiled and dlopened first,
|
|
;; and until it is, `id` is a name nothing defines and this body will not
|
|
;; compile at all. So round 0 takes `id`, round 1 expands this against it, and
|
|
;; the module that finally answers a call holds both.
|
|
(defmacro id [& args]
|
|
(at args 0))
|
|
|
|
(defmacro quiet [& args]
|
|
(id `(println "a macro that called a macro")))
|
|
|
|
;; And a macro that expands into a call to itself, which is what every
|
|
;; conditional macro in every Lisp is. It gets smaller each time and stops at
|
|
;; the empty case, so the expander's fuel never comes into it.
|
|
(defmacro all-of [& args]
|
|
(if (= (len args) 0)
|
|
`true
|
|
`(if ~(at args 0) (all-of ~@(form-rest args 1)) false)))
|
|
|
|
(defn main [] i32
|
|
(both (print "a") (println "b"))
|
|
|
|
(when2 true (print "c") (println "d"))
|
|
(when2 false (println "not printed"))
|
|
|
|
;; 21 + 21. The argument is evaluated once, into the gensym'd binding.
|
|
(print (twice 21)) (println "")
|
|
|
|
;; The caller's own `tmp` is untouched by the one the macro bound, because
|
|
;; the macro did not bind `tmp`.
|
|
(let [tmp 5]
|
|
(print (twice tmp)) (print " ") (print tmp) (println ""))
|
|
|
|
(announce (println "announced"))
|
|
(quiet)
|
|
|
|
(print (all-of)) (println "")
|
|
(print (all-of true true true)) (println "")
|
|
(print (all-of true false true)) (println "")
|
|
0)
|