prose: infix + paren elision + doc-wrap + nested-match (iter 20b)
Eleven binary builtins (+ - * / % == != < <= > >=) render as lhs op rhs when called with two args (tail-flagged keeps prefix). Standard Rust-aligned 4-level precedence table elides parens. not(x) renders as !x. Long /// doc strings wrap at 80 cols on word boundaries. bench_list_sum: 'if ==(n, 0)' becomes 'if n == 0', '+(acc, h)' becomes 'acc + h', tail keyword still visible on tail calls. 19 new unit tests (47 total). 4 snapshots (3 re-rendered + 1 new bench fixture). Public API unchanged.
This commit is contained in:
@@ -8964,3 +8964,96 @@ All explicitly out of 20a's scope; pinned for 20b.
|
||||
- **Iter 20d** — the round-trip mediator (prose-edit → updated
|
||||
AIL via LLM call). Specification + prompt template, not a
|
||||
compiler pass.
|
||||
|
||||
## 2026-05-08 — Iter 20b: prose formatting polish shipped
|
||||
|
||||
The polish pass on top of 20a's renderer skeleton. Four polishes,
|
||||
no public-API change.
|
||||
|
||||
### Polishes
|
||||
|
||||
1. **Infix binary operators.** Eleven canonical builtins
|
||||
(`+ - * / % == != < <= > >=`) when called via
|
||||
`Term::App { args.len() == 2, tail: false }` render as
|
||||
`lhs op rhs`. Tail-flagged binary ops keep prefix form so the
|
||||
`tail` keyword stays visible.
|
||||
2. **Paren elision by precedence.** Standard 4-level Rust-aligned
|
||||
table: `* / %` (mul) > `+ -` (add) > `< <= > >=` (cmp,
|
||||
non-assoc) > `== !=` (eq, non-assoc). Atomic terms (Var,
|
||||
Lit, Ctor, App-non-binary, etc.) bind tightest. Same-level
|
||||
left-associative left side: no parens; same-level right
|
||||
side: keeps parens. Non-assoc ops always keep parens at
|
||||
same level.
|
||||
3. **Unary `not`.** `App { callee: Var "not", args: [x] }`
|
||||
renders as `!x`. Atomic operand binds at level 5 (tightest);
|
||||
`!(a == b)` keeps parens because the operand is a level-1
|
||||
binary op.
|
||||
4. **Long doc-string wrap.** `///` lines exceeding 80 columns
|
||||
wrap at word boundaries. Explicit newlines split first, then
|
||||
each piece word-wraps independently.
|
||||
5. **Nested-match formatting** (already structurally correct in
|
||||
20a; locked in by a 3-deep test).
|
||||
|
||||
### Snapshot impressions
|
||||
|
||||
`bench_list_sum.prose.txt` after 20b:
|
||||
|
||||
```
|
||||
fn cons_n_acc(n: Int, acc: IntList) -> IntList {
|
||||
if n == 0 {
|
||||
acc
|
||||
} else {
|
||||
tail cons_n_acc(n - 1, ICons(n - 1, acc))
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
vs. pre-20b `if ==(n, 0) { ... -(n, 1) ... ICons(-(n, 1), acc) }`.
|
||||
The infix conversion is the headline win — arithmetic now reads
|
||||
as arithmetic.
|
||||
|
||||
`rc_own_param_drop.prose.txt` doc string now wraps:
|
||||
|
||||
```
|
||||
/// Take ownership of an IntList; return its head if Cons, else 0. The tail is
|
||||
/// loaded as a pattern binder but never consumed — iter A dec's it at arm
|
||||
/// close. The outer cell is dec'd at fn return via iter B's Own-param emission.
|
||||
```
|
||||
|
||||
### Tests
|
||||
|
||||
Test count went from 28 → 47 (19 new unit tests in `lib.rs`),
|
||||
plus a fourth snapshot (`examples/bench_list_sum.prose.txt`) that
|
||||
exercises infix on a real benchmark fixture. Existing snapshots
|
||||
re-rendered to incorporate the wrapping.
|
||||
|
||||
### Files
|
||||
|
||||
- `crates/ailang-prose/src/lib.rs` — `write_doc` rewrite,
|
||||
`wrap_words` helper, `PREC_*` constants, `binop_info`,
|
||||
`as_binop`, `as_unary_not` helpers, `write_term_prec`
|
||||
threading `parent_prec` through term recursion.
|
||||
- `crates/ailang-prose/tests/snapshot.rs` — new
|
||||
`snapshot_bench_list_sum`.
|
||||
- `examples/bench_list_sum.prose.txt` — new.
|
||||
- `examples/rc_own_param_drop.prose.txt` — re-rendered.
|
||||
|
||||
### Build/test status
|
||||
|
||||
- `cargo build --workspace` — green, no warnings.
|
||||
- `cargo test --workspace` — 47 prose-unit + 4 prose-snapshot
|
||||
pass; everything else unchanged-green.
|
||||
|
||||
### What stays queued
|
||||
|
||||
- **Iter 20d** — round-trip mediator. Specification + prompt
|
||||
template (the LLM bundles original .ail.json + edited prose
|
||||
and emits the updated .ail.json). Likely a `ail merge-prose`
|
||||
subcommand that prints a shaped prompt, the user mediates
|
||||
through their LLM, then `ail parse` (or `ail check`) on the
|
||||
re-emitted artefact. Possibly: skip the CLI shim, ship as a
|
||||
documented prompt template under `docs/`.
|
||||
- **Let-inlining** — questionable polish; deferred until the
|
||||
corpus shows it's needed.
|
||||
- **`do io/print_int(x)` → `print(x)`** — needs type context
|
||||
to be safe; deferred.
|
||||
|
||||
Reference in New Issue
Block a user