a33d76c67b93658ee93729db0d49ffd1ef6a8008
3 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
d64031c234 |
Iter 14e: explicit, verified tail calls
Decision 8 ships. Term::App and Term::Do gain tail: bool with serde-default false and skip-when-false serialisation. New typecheck pass verify_tail_positions enforces tail-position rules (Scheme-style propagation through match arms, seq.rhs, let body, lam body). Codegen emits musttail call for marked App calls. Hash invariance verified: only the two migrated print_list defs (list_map_poly.print_list, sort.print_list) changed hashes; all other defs across all 18 fixtures kept bit-identical hashes — confirms the skip-when-false serialisation rule works. Tests 76 -> 79: tail_call_in_non_tail_position_is_rejected, tail_call_in_tail_position_is_accepted, plus an IR-grep e2e test asserting that print_list's recursive call site emits musttail in the lowered IR. Existing 25 e2e tests unchanged in behaviour (map -> [2,3,4], sort -> sorted list). IR evidence at the recursive site: %v7 = musttail call i8 @ail_list_map_poly_print_list(ptr %v6) ret i8 %v7 Two deviations called out in the implementer report and JOURNAL: 1. tail-do uses tail call, not musttail. Cross-type return (runtime helpers return i32, AILang Unit is i8) would have LLVM reject musttail. Path is implemented but not exercised by any current fixture; proper fix is runtime-helper signature change, punted. 2. block_terminated flag in codegen so tail-call emit (musttail call + ret) doesn't get a duplicate trailing ret from surrounding code (match-arm phi, fn-body, lambda thunk). Internal plumbing; required for IR well-formedness. Form (A) productions now at ~30, exactly the constraint-1 budget. Future surface additions need to retire something or explicit-budget-rebalance in DESIGN.md. GC notes from implementer survey land in JOURNAL: - Allocations cluster in lower_ctor; every term-ctor does malloc(8+8n). - Tail recursion does not reduce alloc pressure, only stack. For map-style ctor-blocked recursions, allocation IS the bottleneck. - Per-fn arena is sound only when fn return type contains no boxed ADT. Most current fixtures violate this. Plan 14f: Boehm conservative GC (GC_malloc, -lgc) as a first cut. Single-iter integration, no AST/schema change. Stress test: build a 100k Cons list, observe RSS doesn't blow up. After 14f the language is feature-complete enough for stdlib work (15a). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
706f90bacd |
Iter 14c: ailang-surface ships — form (A) parser + pretty-printer
Strictly additive new crate per Decision 6 architectural pin. JSON-AST stays the source of truth; ailang-surface is one producer/consumer of ailang_core::ast::Module values. ailang-check and ailang-codegen unchanged. Crate contents: - src/lex.rs (~264 LOC): 3-rule lexical core. Whitespace and parens delimit tokens; semicolon to EOL is comment; first-character classifier (digit -> int, " -> string, else -> ident). - src/parse.rs (~1041 LOC): hand-written recursive descent. One Rust fn per EBNF production. No parser-combinator dep. - src/print.rs (~371 LOC): deterministic pretty-printer. Round-trip contract with parse() is the surface's correctness gate. - tests/round_trip.rs (~128 LOC): integration test runs every examples/*.ail.json fixture through print -> parse -> canonical JSON, asserts canonical-byte equality with the original. Two AST-driven form widenings beyond the 14b sketch (both folded into DESIGN.md Decision 6): - lam-term carries (typed name type) params, ret type, and optional effects (Term::Lam has parallel param_tys/ret_ty/ effects fields). - import-clause admits (import name (as alias)?) (Import.alias is Option<String>). Production count ~28, under 30-rule budget. Constraint 1 (formalisable for foreign LLM) intact. Verification: - cargo build --workspace green. - cargo test --workspace: 76 tests green (was 64; +9 surface unit, +2 round-trip integration). All 17 fixtures round-trip byte-identical at canonical level. 3 hand-written .ailx exhibits parse to canonical JSON identical to their .ail.json siblings. - cargo doc --no-deps zero warnings (DESIGN.md item 6 invariant). Manual smoke test (ail parse → ail run): hello, box, list_map_poly all produce expected output through the form-(A) authoring lane end-to-end. CLI: ail parse <file.ailx> [-o <file.ail.json>]. .ail.json remains a first-class input to every existing subcommand. Plan 14d: stdlib (std_list.ailx with length/filter/fold/concat/ reverse/head/tail), authored in form (A) from day one. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
2bce825b69 |
Iter 14b: design pass for the authoring surface
User redirected at iter boundary: writing a stdlib in JSON was the wrong move. The language is supposed to be the one I program *best* in, and JSON-AST authoring is rationalisation, not strength. DESIGN.md Decision 6 captures the constraints that fall out of the "formalisable for a foreign LLM" hard requirement (no precedence, no semantic indentation, ASCII only, every AST node a uniquely-tagged form), sketches three candidate notations with the same `map` encoded in each, and picks form (A) — fully-tagged S-expressions — as the first attempt with explicit rollback path to form (C) if (A) hurts authoring. Form (A) shape: - 3-rule lexical core: sexpr / atom / token-classified-by- first-character. - Every AST node has a unique head keyword. No case-rule (no "capitalised head means ctor"); ctors are explicit via `(term-ctor TypeName CtorName args)` and `(pat-ctor CtorName fields)`. - Bare atoms get their sort from the parent slot (type-var inside `(con NAME args)`, term-var inside `(app HEAD args)`, pat-var inside `(pat-ctor CTOR fields)`, integer literal in term position, etc.). Empirical exhibits, hand-encoded: - examples/hello.ailx 5 LOC (JSON was 36 pretty / 21 canonical) - examples/box.ailx 25 LOC (JSON was 160 / 88) - examples/list_map_poly.ailx 50 LOC (JSON was 394 / 230) 4-8x line reduction, ~4x character reduction. Bigger gains on bigger programs since overhead is proportional to AST depth. None are parseable yet — header comments say "Iter 14b design exhibit, parser lands in 14c". Two small spec issues caught while writing the exhibits and folded back into DESIGN.md before committing: - Operator idents (`+`, `==`) need the token-by-first-char classification rule, not a word-shaped regex. - Bool literals (`true`/`false`) reserved in term context; unit is explicit `(lit-unit)`. Tests unchanged (this iter is paper). 25/25 e2e green. cargo doc --no-deps zero warnings. Plan 14c: new crate `ailang-surface` with PEG parser, round-trip hash-equivalence gate against every existing `examples/*.ail.json`, CLI subcommand `ail parse`. If round-trip holds, stdlib starts in `.ailx` form (Iter 14d). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |