flan/test/programs/macro-params.flan
Joseph Ferano 69646e534e A macro's parameter list, and one grammar for it
(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.
2026-09-20 18:18:24 +07:00

72 lines
2.8 KiB
Plaintext

;;;; A macro's parameter list: positional names, [ ] patterns, and &.
;;;;
;;;; macros.flan is the other half of this and is deliberately not merged with
;;;; it: everything there is written [& args] and picks its arguments apart by
;;;; hand, which is what every macro in the tree looked like before this. Here
;;;; the parameter list does the picking, and the two files together are the
;;;; claim that both spellings are the same grammar rather than two.
;;;;
;;;; Nothing below checks its own arity. It cannot be reached with the wrong
;;;; one: lib/expand.ml's check_call runs over the call *before* the macro is
;;;; expanded, so a miscount is refused at the call with the call's own
;;;; location — see macro-arity.flan and the three beside it.
;; The shape the feature was asked for (DISCUSS.org): a binding vector
;; destructured in the signature, and & for the body. Without a parameter list
;; this is (at args 0), a match on Form.Vec to unwrap it, four more (at ...)
;; inside that, and (form-rest args 1) for the body.
(defmacro do-grid [[r rows c cols] & body]
`(dotimes [~r ~rows]
(dotimes [~c ~cols]
~@body)))
;; One positional parameter, which is where the grammar changed: [x] used to
;; bind the whole argument list and now binds the first argument. The gensym is
;; the ordinary reason it is there — expansion is not hygienic — and not
;; anything to do with the parameter list.
(defmacro doubled [x]
(let [v (gensym)]
`(let [~v ~x] (+ ~v ~v))))
;; Patterns nest, because a pattern's elements are patterns. And & is not only
;; the top level's: the tail of a pattern is the tail of that vector.
(defmacro nested [[a [b c]] & body]
`(do (print ~a) (print ~b) (print ~c) ~@body))
;; & inside a pattern, which is the same & and means the same thing one level
;; down: the tail of the vector written at the call.
(defmacro first-of [[a & more]]
`(do (print ~a) ~@more))
;; & with nothing after it at the call: the rest is an empty slice, ~@ splices
;; nothing, and the expansion is the wrapper alone. The arity check says "at
;; least 1" and one is what this is given.
(defmacro shout [label & body]
`(do (print ~label) ~@body (println "!")))
;; The whole argument list, which is what [args] used to mean and is now spelled
;; [& args]. Every macro in the tree was migrated to this line, so it is the
;; one that has to keep working unchanged.
(defmacro all-of [& args]
(if (= (len args) 0)
`true
`(if ~(at args 0) (all-of ~@(form-rest args 1)) false)))
(defn main [] i32
(do-grid [i 2 j 3]
(print i) (print j))
(println "")
(print (doubled 21)) (println "")
(nested [1 [2 3]] (println " nested"))
(first-of [4 (print " and") (println " more")])
(shout "alone")
(shout "with" (print " body"))
(print (all-of)) (print " ")
(print (all-of true true true)) (print " ")
(print (all-of true false true)) (println "")
0)