Commit Graph

11 Commits

Author SHA1 Message Date
Brummel 51011511b4 iter 22-tidy.6.2: write_fn_def renders forall class constraints 2026-05-10 04:58:01 +02:00
Brummel 788c9808dd iter 22-tidy.6.1 fixup: cover abstract-method + instance-override branches; doc + fallback comments 2026-05-10 04:54:22 +02:00
Brummel d1c992d49a iter 22-tidy.6.1: write_class_def + write_instance_def — full Form-B projection 2026-05-10 04:47:50 +02:00
Brummel c8fc33fb86 iter 22-tidy.3: prose-lib — drop 22b.4 reference from class/instance placeholder 2026-05-09 22:52:17 +02:00
Brummel b636b883a3 iter 22b.2.1: add Constraint struct, Type::Forall.constraints 2026-05-09 17:34:39 +02:00
Brummel f25d7b6cd6 iter 22b.1.1: ClassDef/InstanceDef AST + downstream match arms
Adds Def::Class(ClassDef) and Def::Instance(InstanceDef) variants per
Decision 11 §"Form-A schema". All optional fields (superclass, doc,
default body) carry skip_serializing_if so canonical-JSON bytes of
pre-22b fixtures stay bit-identical.

Downstream match-on-Def sites are extended with explicit Class/Instance
arms. Behaviour for 22b.1 is placeholder (skip in typecheck/codegen,
one-line summary in pretty/manifest, placeholder marker in prose/print)
with TODO comments naming the deferred sub-iters (22b.2 typecheck,
22b.3 codegen, 22b.4 prose-projection arms).
2026-05-09 12:31:23 +02:00
Brummel 8375eb81ed prose: split reuse-as keyword + inline single-use lets (iter 20b polish)
reuse-as: render `(reuse-as src body)` as `reuse src as body` with
adaptive braces — inline for single-line bodies (the 99% Ctor case),
braced for multi-line (Let, Seq, nested Match). Reads as English
subject-verb-object instead of compound keyword + always-block.

let-inlining: `let x = rhs; <body using x once>` collapses to
`<body[x := rhs]>` when (a) rhs is not a `Term::Do` (keep effect
sequencing visible), (b) x is used exactly once (no work duplication),
(c) rhs renders single-line AND ≤ 40 chars (no line smearing). Lossy
projection — round-trip mediator sees the original .ail.json and can
re-introduce a let when the mode model demands it.

Helpers `count_free_var` and `subst_var_with_term` are render-local;
both respect inner shadowing (let, lam, match arm pattern binders,
let rec). Pre-render value at current level + check for newline + len
budget; on inline, write_term_prec gets passed parent_prec so the
substituted term still wraps correctly inside binops.

Snapshot drift: rc_app_let_partial_drop_leak.prose.txt — boilerplate
`let p = build_pair(1)` collapsed into the match scrutinee. Improves
readability. Cons-tower in rc_own_param_drop kept its let thanks to
the length budget (52 chars > 40).

Tests: 59 unit (+ 9 new) + 4 snapshot, all green. Coverage:
inline-single-use, two-uses-keeps, zero-uses-keeps, Do-rhs-keeps,
multiline-rhs-keeps, length-budget-keeps, shadowing-not-counted,
reuse-as inline + braces.
2026-05-08 23:03:10 +02:00
Brummel 50b68267fe check: mode-strict-because suppression (iter 19b)
Closes the 19a/19a.1/19b arc. Corpus signal from 19a.1 (5/65
fixtures fire over-strict-mode, all deliberate RC codegen-test
fixtures) justified shipping the suppress mechanism end-to-end.

Schema: Suppress { code, because } on FnDef. Pre-19b hashes
bit-identical via skip_serializing_if. Typechecker drops matching
diagnostics; empty 'because' is Error severity; wrong codes are
silent no-ops (open-set registry).

Form-A: (suppress (code "...") (because "...")) clause, parser
+ printer round-trip clean. Form-B: '// @suppress <code>: <reason>'
above the doc string, lossless contract metadata.

5 RC fixtures migrated (.ail.json + .ailx + .prose.txt). Corpus
signal: 5/65 -> 0/65. Lint still fires on any future fn that's
accidentally over-strict without an authored reason.

Test counts: ailang-check 55->61, ailang-core 26->28, ailang-surface
21->26, ailang-prose 49->52, e2e 70 unchanged.

Known debt: .ailx comment headers lost on regen (ail render's
contract excludes comments); parse_suppress_attr accepts
duplicate code/because attrs without diagnose (bounded by canonical
print order).
2026-05-08 19:36:04 +02:00
Brummel b9e942b99d prose: doc-wrap widow control for 1-word orphans
When greedy word-wrap leaves a 1-word last line, pull the
penultimate line's tail word down so the orphan has at least
2 words. Skipped if the combined length would exceed budget
(invariant takes priority over cosmetics).

Visible on nested_let_rec.prose.txt:
  before: '...captures outer's param\n/// i.'
  after:  '...captures outer's\n/// param i.'

Two new unit tests; existing 4 snapshots unaffected (none had
a 1-word orphan).
2026-05-08 19:18:37 +02:00
Brummel 9cf0e3e81c 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.
2026-05-08 18:13:43 +02:00
Brummel a9d57c5c81 prose: human-readable projection renderer (iter 20a)
New crate ailang-prose with one public fn module_to_prose(&Module)
-> String. Rust-flavour with braces, =>-match-arms, mode keywords
(own/borrow), effects as trailing 'with IO', Cons(1, Nil) ctor
form, /// doc strings.

Lossy projection where the LLM can re-derive: (con T) wrap,
(fn-type ...) wrap, (term-ctor ...) wrap. Load-bearing semantic
detail stays visible (modes, effects, clone, reuse-as, doc strings,
type annotations, tail flag).

CLI: 'ail prose <file.ail.json>' prints the projection.

3 snapshot fixtures + 28 unit tests. 215-line .ail.json reduces
to ~18 lines of legible source.

20b queued: infix arithmetic, paren elision, let-inlining, do
prettify.
2026-05-08 18:06:16 +02:00