Skip to content

Unit 6c: remove the macro system, add assert, and, or as fixed forms - #54

Merged
EvanWinget merged 7 commits into
mainfrom
unit-6c-macro-removal
Aug 16, 2026
Merged

Unit 6c: remove the macro system, add assert, and, or as fixed forms#54
EvanWinget merged 7 commits into
mainfrom
unit-6c-macro-removal

Conversation

@EvanWinget

Copy link
Copy Markdown
Owner

What changed

Reverses unit 6 (PR 52). The v0 language drops defmacro, qq, and unquote along with the whole expansion machinery, and gains assert, and, and or as fixed compiler forms beside if and list, 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:

  • Chia's canonical deployed corpus (Chia-Network/chia_puzzles, 91 puzzles): no puzzle defines a novel macro. The entire production macro story is short-circuit assert (17 of 91 puzzles, 6 of them hand-copying the same macro), plus and/or in about 5 files.
  • The corpora vendored in references/ (359 Chialisp files including the game-referee corpus): real code declaring its own macro rounds to 3 files, all assert or list-building sugar. Included libraries are 84 percent constants and plain functions.
  • chia-gaming (the most ambitious Chialisp codebase, ~3,300 production lines): every shipped on-chain puzzle is macro-free, and the production referee deliberately uses built-in destructuring while a structural-macro alternative sits unbuilt beside it in the same directory.

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, and or become reserved words (old sources and symbol files using them as names now fail), and defmacro, qq, and unquote become ordinary claimable names (old macro sources fail as plain unknown names).

Commit read order

  1. docs: the language surface. language.md states the three forms with their exact emission and error strings and records the removal in the deviations section, macros.md is deleted, repl.md, the docs index, and the glossary follow.
  2. tools: the implementation. All three forms compile through _lazy_if, the apply-a-quoted-branch idiom extracted from if, so laziness composes by construction. This commit also deletes the tests and vectors/vm/macro-programs.json that 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.
  3. 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 holding and/or to Python's all/any, and 5 new cases in compiled-programs.json.
  4. docs: the execution-plan record (unit 6 reversal note, unit 6c bullet, unit 6b deferral reframing, unit 7 and ledger updates).

Verify independently

.venv/bin/pytest python/tests                 # 706 passed
.venv/bin/python tools/run_vectors.py         # 33 files, 1002 cases, 0 failures
ci/lint/lint.sh                               # all checks passed

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 and errors in the REPL while def qq binds, and the language.md worked session pastes with identical output. The docs' compiled examples were verified byte for byte against bitlisp-compile.

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.
@EvanWinget
EvanWinget merged commit 58966be into main Aug 16, 2026
2 checks passed
@EvanWinget
EvanWinget deleted the unit-6c-macro-removal branch August 16, 2026 23:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant