The Term::Loop arm of write_term rendered binders as bare
`name = init;` statements inside the `loop { … }` body block, which
reads as C/Rust re-init-every-iteration semantics — a projection
lying about a body the prose surface exists to let the reader trust.
Rewrite to a parenthesised init-list on the keyword line,
`loop(acc = 0, i = 1) { … }`, positionally isomorphic to the
unchanged Term::Recur arm. Projection-only: loop/recur AST, Form-A,
JSON-AST, typecheck, codegen, and the Form-A↔JSON round-trip
invariant untouched. RED-first via two new committed
examples/*.prose.txt byte-equality snapshots + two snapshot_loop_*
tests; full ailang-prose suite 10/0; both ail-prose CLI byte-matches
green. Single-iteration milestone, brainstorm→plan→implement.
Spec: docs/specs/2026-05-18-prose-loop-binders.md
Plan: docs/plans/prose-loop-binders.1.md
2.9 KiB
iter prose-loop-binders.1 — Form-B loop renders binders as a parenthesised init-list on the keyword
Date: 2026-05-18
Started from: 6533134dab
Status: DONE
Tasks completed: 2 of 2
Summary
Projection-only single-iteration milestone in ailang-prose. The
Term::Loop arm of write_term (crates/ailang-prose/src/lib.rs)
now renders loop binders as a parenthesised init-list on the keyword
line — loop(acc = 0, i = 1) { … } — instead of bare
name = init; statements inside the body block. The old shape
implied C/Rust re-init-every-iteration semantics; the new shape
mirrors the adjacent (untouched) Term::Recur arm's
enumerate() + ", "-separator idiom and renders init expressions
at level (they sit on the keyword line, not the indented block).
RED-first: two committed examples/*.prose.txt snapshot fixtures
plus two check_snapshot-calling #[test] fns were added and
verified failing against the old renderer before the arm rewrite
made them pass. Loop/recur AST, Form-A, JSON-AST, typecheck,
codegen and the Form-A↔JSON round-trip invariant are untouched by
construction (single render-arm body rewrite, no signature change,
no caller touched).
Per-task notes
- iter prose-loop-binders.1.1: RED — created
examples/loop_sum_to_run.prose.txt(multi-binder + non-recur exit) andexamples/loop_forever_build.prose.txt(single-binder recur-only) with the exact Option-A bytes from the plan; appendedsnapshot_loop_sum_to_run+snapshot_loop_forever_buildaftersnapshot_ordering_matchintests/snapshot.rs.cargo test -p ailang-prose snapshot_loop→0 passed; 2 failed; 8 filtered out(filter resolved to exactly the two new tests; both panic with the expectedprose snapshot mismatchmessage). RED confirmed. - iter prose-loop-binders.1.2: GREEN — rewrote the
Term::Looparm atlib.rs:938-955verbatim per the plan (parenthesised init-list, init atlevel, stale "not yet designed" comment removed).cargo test -p ailang-prose snapshot_loop→2 passed; 0 failed; fullailang-prosesuite →10 passed; 0 failed(no pre-existing snapshot regressed); bothail prose | diffCLI byte-matches →MATCH; post-cleanupgit status --porcelainis EXACTLY the four expected paths.
Concerns
(none)
Known debt
(none — projection-only single-arm rewrite, fully gated by whole-file byte-equality snapshots + live-CLI diff)
Files touched
crates/ailang-prose/src/lib.rs—Term::Looparm ofwrite_termrewritten (single hunk, lines 938-953)crates/ailang-prose/tests/snapshot.rs— two#[test]fns appended aftersnapshot_ordering_matchexamples/loop_sum_to_run.prose.txt— new committed snapshotexamples/loop_forever_build.prose.txt— new committed snapshot
Blocked detail
(n/a — Status DONE)
Stats
bench/orchestrator-stats/2026-05-18-iter-prose-loop-binders.1.json