Spec M9 — #rule (surface piece C) + lib/prelude.mc
Prerequisite: M7 closed (fixed point, golden recorded). This milestone changes stage0 and
src/ (the mechanism costs twice), and ends with the fixed point re-verified.
Syntax (final; see docs/plan.md § Surface) #
#rule stmt: while ( expr $c ) block $b => loop { if (!$c) break; $b }
#rule stmt: for ( stmt $init expr $cond ; expr $step ) block $b
=> { $init loop { if (!$cond) break; $b $step; } }
#rule stmt: ident $x += expr $e ; => $x = $x + $e;
#rule stmt: ident $x ++ ; => $x = $x + 1;
#rule stmt:is the only category in M9 (expr:stays reserved; a clear error if used).- Pattern: a flat sequence of items; an item is either a literal token (any lexeme, including ones
created by
#token—+=and++need a#tokenfirst) ornt $namewithnt ∈ {expr, stmt, block, ident, type}. The first item is always a literal token; if it's an identifier (while), it becomes a reserved keyword (registered viatok_addas a word) — from then on it can no longer be used as a name. - Template: a single statement (or block) parsed at definition time with the normal parser,
where
$namebecomesN_HOLE(the binding's index) and$$namebecomes a gensymN_HOLE. - No alternation/optional/repetition in the pattern.
Semantics #
- Indexed by opening token: a linear table
{tok, nitems, items[], tmpl}; when a statement starts, if the current token opens some rule, that rule is tried (the last one defined for the same token wins). Matching is deterministic and backtracking-free: every literal item must match, and everynt $xcalls the matching sub-parser (parse_expr,parse_stmt,parse_block, ident, type). Failure = a syntax error pointing at the item. - Expansion:
node_copy_substwith the bindings;$$nameproduces one fresh local per expansion ($g<N>, a deterministic global counter;$does not lex as part of an identifier, so no capture is possible) — the template may declare it (i64 $$t = ...;). - Re-expansion: does the result go back through the statement parser? No — the template was already parsed; any rules used inside the template were already expanded at definition time. Rules that use other rules work naturally. Infinite recursion is impossible by construction (there is no textual re-expansion). Limit of 64 levels of rule nesting at definition time.
- Frame computed after expansion: already the case (codegen walks the expanded AST).
ident $xmatches onlyT_IDENTand producesN_IDENT.type $tproduces a newN_TYPEnode (kind reserved) used only as a hole in declarationstype $t ident $x = expr $e;— out of scope for M9 if it would cost more than 20 lines; document that.--dump-astshows the expanded AST; add--dump-rules(lists rules: opening token, items, template size) for diagnostics.
lib/prelude.mc #
#token "+=", "-=", "++", "--"; rules while, for, +=, -=, ++, --, until?
(no), unless (no). Only what mc.mc itself will use. No struct in M9 — deferred to a
possible M9.5 after measuring the cost; the plan called for it, but M6 showed that #define plus
accessors work fine, and a real struct needs type $t and a layout, which is more than #rule
delivers.
Stage0 (C) and src/ (.mc) #
Implement in stage0/parse.c (+ ast.c for gensym) and transliterate into src/parse.mc
(+ ast.mc). Budget: ≤ 3000 lines in C (today 2500; target ≤ 2900). If it doesn't fit, cut
--dump-rules first, then type $t.
Tests #
050-while.mc,051-for.mc(withcontinue— the prelude'sformust increment before looping back: use$$for the step? No:continueinside the body skips$step. Decision: the prelude'sfordocuments thatcontinueskips the step (same asloop); the test covers this),052-compound.mc(+=,++),053-gensym.mc(a rule that declares$$tand is used twice in the same block),054-rule-in-rule.mc(a rule whose template useswhile),055-keyword.mc(usewhileas a variable name after the rule is defined → error; report it).- Plan acceptance: a program using
whileproduces a.oidentical undermc0andmc1(check-obj.shstays at 100%),check-asmidentical,make bootstrapgreen with the golden updated (src/changed), and then migrate one leaf module (src/macho.mc) to use the prelude'swhile/for/+=and re-verify the fixed point (golden updated again, with the--dump-asmdiff reviewed: it must be empty, since the expansion produces the same AST).