From a311664a08e648f172f8775332ad42b0a7f40239 Mon Sep 17 00:00:00 2001 From: Joseph Ferano Date: Fri, 11 Sep 2026 20:20:24 +0700 Subject: [PATCH] The delimiter half of the backtick fix was observed by nothing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Every sigil in the corpus test sat in leading position, where read_form handles it before is_delimiter is ever consulted — so reverting the is_delimiter line alone left every case green. a`b now has the case a~b already had, and the corpus carries both, which is what makes the class guard cover the delimiter change rather than only the read branches. The handoff note hedged on the one thing it exists to decide: expansion runs over Form before Parse, not over Ast. There is no Ast.Defmacro, so an Ast pass would have nothing to read. Says milestone 6 for union values because that is the number check.ml itself gives. --- NEXT.md | 17 +++++++++-------- test/test_flan.ml | 3 ++- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/NEXT.md b/NEXT.md index 64e53d6..f2e0393 100644 --- a/NEXT.md +++ b/NEXT.md @@ -1621,14 +1621,15 @@ The shape it wants: accessors. That is the real work, and it is bigger than the expander itself: the compiler and the compiled macro have to agree on the *layout* of a `Form`, not merely its shape, so whatever the checker does for unions has to - be exact here. Until unions are values this cannot start; that is milestone 6 - work landing before milestone 5's. -2. **Expansion is a pass between `Parse` and `Check`**, over `Ast`, not over - `Form` — or, better, over `Form` before `Parse` ever runs, which is why - `Parse` refusing `defmacro` rather than storing it is not a dead end: the - expander runs first and `Parse` never sees a macro call at all. That is the - Clojure ordering and the one to prefer, because a macro expanding to a - special form is then ordinary rather than a special case. + be exact here. Until unions are values this cannot start — `check.ml` puts + union values and `match` on a union at **milestone 6**, so that is milestone + 6 work landing before milestone 5's. +2. **Expansion runs over `Form`, before `Parse`.** Not a pass over `Ast`: + there is no `Ast.Defmacro` and `Parse` refuses `defmacro` outright, so an + `Ast`-level pass would have nothing to work with. That refusal is not a dead + end, it is the ordering — the expander runs first and `Parse` never sees a + macro call at all. It is also the Clojure ordering, and the reason a macro + expanding to a special form is ordinary rather than a special case. 3. **Order matters and files do not have one.** Top-level names in a package are order-independent everywhere else (`declared_types`, the constant fixpoint in `check.ml`). Macros cannot be: a macro must be compiled and diff --git a/test/test_flan.ml b/test/test_flan.ml index 54a7edc..7b1c6d3 100644 --- a/test/test_flan.ml +++ b/test/test_flan.ml @@ -113,6 +113,7 @@ let () = reads "quote in quasi" "`(a 'b)" "(quasiquote (a (quote b)))"; (* The delimiter half of the fix: without it ~x is one symbol named "~x". *) reads "tilde ends a name" "(f a~b)" "(f a (unquote b))"; + reads "backtick ends a name" "(f a`b)" "(f a (quasiquote b))"; reads "backtick in vec" "[`a ~b]" "[(quasiquote a) (unquote b)]"; (* The whole class: no reader-significant character may end up inside a name. *) @@ -127,7 +128,7 @@ let () = in let corpus = "(invoke-restart 'skip-form) (a 'b [c 'd] {:e 'f}) '(g 'h) \ - `(i ~j ~@k) `(l `(m ~n)) [`o ~p] {:q `r}" + `(i ~j ~@k) `(l `(m ~n)) [`o ~p] {:q `r} (f a~b x`y)" in check "no sigils leak into names" (bad_names (Form.make (Form.List (Reader.read_all ~file:"" corpus))