Unit 6c: remove the macro system, add assert, and, or as fixed forms - #54
Merged
Conversation
The v0 language drops defmacro, qq, and unquote, and gains assert, and, and or as fixed compiler forms beside if and list, with the semantics Chialisp's utility_macros.clib macros give the same spellings: assert nests lazy ifs over a raise, and yields 1 or nil short-circuiting at the first falsy operand, or yields 1 at the first truthy operand. docs/lang/macros.md is deleted and the deviations section now records the removal and its rationale. Decision by Evan, 2026-08-16, reversing unit 6 (PR 52) on production evidence: across Chia's canonical deployed puzzle corpus (91 puzzles), the corpora vendored in references/, and the chia-gaming state-channel codebase, no production puzzle defines a novel macro, and the only macros in production use are short-circuit assert (17 of 91 canonical puzzles), and, and or (about 5 files). chia-gaming's production referee uses built-in destructuring where a macro-based alternative beside it sits unbuilt. The fixed forms cover the complete observed vocabulary while removing the expansion machinery's audit surface. The reserved-word set change is a deliberate source and symbol-file compatibility break in both directions: assert, and, and or become reserved, defmacro, qq, and unquote become ordinary names.
The compiler drops defmacro, qq, unquote, and everything that existed to serve them: the expansion pre-pass with its depth, count, and cost guards, the read-back lift with its one-hop typo evidence, the macro world in Definitions, and the macro arms in resolution and emission. _expanded_bodies collapses back to plain reachability over source bodies, and compile_expression loses its session_names parameter, whose only purpose was shadowing macro output. The unknown-operator spelling hint goes too: its rationale was macro-computed head bytes, which can no longer arise. The three new forms compile through _lazy_if, the apply-a-quoted- branch idiom extracted from if, so laziness composes by construction: assert folds conditions over a raise, and folds to 1 or nil short-circuiting at the first falsy operand, or yields 1 at the first truthy operand. Semantics match Chialisp's utility_macros.clib, per the language doc's deviations section. The tests and vectors that pinned the removed behavior go in this commit so every commit in the PR stays green: the macro sections of test_compiler.py and test_repl.py, the macro error-table rows, the old reserved-word loader pin (its flipped replacement lands with the new pins), and vectors/vm/macro-programs.json, which pinned compiled representatives of programs that can no longer be compiled from source. New pins for assert, and, and or follow in the next commit. Upstream reference note: the semantics were established against the classic clvm_tools utility_macros.clib macros and verified by compiled-shape probes, not copied from any source.
Codegen pins for every shape of the three forms, empty and single-operand cases included: single-operand and and or emit the identical boolean tree, pinned so a value-returning regression fails loudly. Compile-and-run cases prove the boolean value semantics and the short circuit with guarded raises an eager reading would trip. Error-table rows pin the empty assert rejection and the reserved-word claims in parameter, defun, and defconstant position. A hypothesis property holds and and or to Python's all and any over integer lists, empty included. The symbol-file loader test returns flipped: assert, and, and or are rejected as function names, and qq, reserved before this unit, loads again, pinning the compatibility break in both directions. REPL tests cover the raw-first retry reaching the new forms, a failed assert printing as invalid: user_raise, def rejecting the new reserved words, and def qq binding again. vectors/vm/compiled-programs.json grows from 8 to 13 cases, the compiled representatives replaying the laziness and the raise on the consensus VM: empty forms folding to constants, and and or short-circuiting before a guarded raise, and assert passing its value or raising user_raise.
The unit 6 bullet gains its reversal note, unit 6c enters the plan as landed with the three-survey evidence summarized, unit 6b's includes are deferred behind unit 7 with the design target reframed to program-and-hash import on the chia-gaming evidence, and the unit 7 style constraint and the typed v1 ledger note track the cut.
Review fold-in: the and section said each level's untaken branch is the remaining chain, but the chain is the taken branch, and the section showed no compiled form to correct the misreading. It now shows the two-operand tree exactly as the vectors pin it, so the doc alone predicts the emission.
Restores the unknown-operator spelling hint: the removal commit claimed name-spelling head bytes could no longer arise, but an authored string or hex atom in head position still produces them, so the hint returns with its rationale corrected in place. DECLARATION_KEYWORDS becomes the one tuple both the compiler's declaration dispatch and the REPL's line dispatch read, closing the drift seam the macro removal had to hand-edit across five sites. _reachable_bodies collapses to _reachable_names, a single-pass worklist returning the reachable set: the parallel name-to-body dict and the restart sweep existed for expansion-error attribution, which left with the macros, and _compile now unpacks params and body from the one authoritative store. The branching forms share module-level _TRUE and _RAISE_CALL nodes, making the doc-pinned byte identity of single-operand and and or structural rather than coincidental.
The self-containment pin returns: the deleted macro test was the only test holding a pasted program form to ignore session definitions, a guarantee that survived the removal, so it returns with a defun in the macro's place. A new pin transcribes the reserved-word set literally, condition-table style, and checks every reserved word means something in head position, so a fixed form added to dispatch but not the reserved set fails here. The assert run test reuses the program _run_program already compiled instead of recompiling a duplicated literal, and the loader test's comment no longer points at a documentation file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What changed
Reverses unit 6 (PR 52). The v0 language drops
defmacro,qq, andunquotealong with the whole expansion machinery, and gainsassert,and, andoras fixed compiler forms besideifandlist, with the semantics Chialisp's utility_macros.clib gives the same spellings. Decision by Evan, 2026-08-16, both sides steelmanned under ground rule 3.Why
Three usage surveys ran before this decision, and they agree:
assert(17 of 91 puzzles, 6 of them hand-copying the same macro), plusand/orin about 5 files.assertor list-building sugar. Included libraries are 84 percent constants and plain functions.The three fixed forms cover the complete observed vocabulary. What leaves with the cut is the most subtle code in the compiler: the expansion guards, the one-hop read-back rule that took four review rounds in PR 52, and the documented capture and stale-template sharp edges.
The surveys also reframed the deferred includes unit (6b): chia-gaming abandoned textual include for importing a compiled sibling program plus its tree hash as a compile-time constant. That reframing is recorded in the plan, and the unit 7 puzzles decide whether the mechanism is needed.
Compatibility break
Deliberate, in both directions, pinned by the loader test:
assert,and, andorbecome reserved words (old sources and symbol files using them as names now fail), anddefmacro,qq, andunquotebecome ordinary claimable names (old macro sources fail as plain unknown names).Commit read order
docs:the language surface.language.mdstates the three forms with their exact emission and error strings and records the removal in the deviations section,macros.mdis deleted,repl.md, the docs index, and the glossary follow.tools:the implementation. All three forms compile through_lazy_if, the apply-a-quoted-branch idiom extracted fromif, so laziness composes by construction. This commit also deletes the tests andvectors/vm/macro-programs.jsonthat pinned the removed behavior, a deviation from the usual implementation/vector split so every commit stays green under a removal. Fold differently if you prefer.tests+vectors:the new pins. Codegen shapes (empty and single-operand cases included), boolean-and-lazy run semantics with guarded raises, error-table rows, the flipped loader test, REPL round trips, a hypothesis property holdingand/orto Python'sall/any, and 5 new cases incompiled-programs.json.docs:the execution-plan record (unit 6 reversal note, unit 6c bullet, unit 6b deferral reframing, unit 7 and ledger updates).Verify independently
Boundary probes worth repeating by hand:
(assert)rejects at compile time,(and)compiles to(q . 1)and(or)to(),(and X (x))with a falsy X returns nil without raising,def anderrors in the REPL whiledef qqbinds, and thelanguage.mdworked session pastes with identical output. The docs' compiled examples were verified byte for byte againstbitlisp-compile.