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>
This commit is contained in:
2026-05-07 15:57:24 +02:00
parent 747b7cd05c
commit 2bce825b69
5 changed files with 445 additions and 0 deletions
+36
View File
@@ -0,0 +1,36 @@
; Iter 14b design exhibit — Decision 6 form (A).
; This file is NOT yet parseable by `ail`. The parser lands in
; Iter 14c. Hash-equivalence with `box.ail.json` is the round-trip
; test that gates that iter.
(module box
(data Box (vars a)
(doc "A unary box around a polymorphic value.")
(ctor MkBox a))
(fn unbox
(doc "Polymorphic projection out of a Box.")
(type
(forall (vars a)
(fn-type
(params (con Box a))
(ret a))))
(params b)
(body
(match b
(case (pat-ctor MkBox x)
x))))
(fn main
(doc "Round-trip 42 through MkBox + unbox.")
(type
(fn-type
(params)
(ret (con Unit))
(effects IO)))
(params)
(body
(do io/print_int
(app unbox
(term-ctor Box MkBox 42))))))
+8
View File
@@ -0,0 +1,8 @@
; Iter 14b design exhibit — Decision 6 form (A).
; Trivial-program reference: shows the form's overhead floor.
(module hello
(fn main
(type (fn-type (params) (ret (con Unit)) (effects IO)))
(params)
(body (do io/print_str "Hello, AILang."))))
+59
View File
@@ -0,0 +1,59 @@
; Iter 14b design exhibit — Decision 6 form (A).
; Companion to box.ailx. Same hash-equivalence gate in Iter 14c.
(module list_map_poly
(data List (vars a)
(doc "Singly-linked polymorphic list.")
(ctor Nil)
(ctor Cons a (con List a)))
(fn inc
(type (fn-type (params (con Int)) (ret (con Int))))
(params x)
(body (app + x 1)))
(fn map
(doc "Apply f to every element. Recursive on the tail.")
(type
(forall (vars a b)
(fn-type
(params (fn-type (params a) (ret b))
(con List a))
(ret (con List b)))))
(params f xs)
(body
(match xs
(case (pat-ctor Nil)
(term-ctor List Nil))
(case (pat-ctor Cons h t)
(term-ctor List Cons
(app f h)
(app map f t))))))
(fn print_list
(type
(fn-type
(params (con List (con Int)))
(ret (con Unit))
(effects IO)))
(params xs)
(body
(match xs
(case (pat-ctor Nil)
(lit-unit))
(case (pat-ctor Cons h t)
(seq
(do io/print_int h)
(app print_list t))))))
(fn main
(type (fn-type (params) (ret (con Unit)) (effects IO)))
(params)
(body
(app print_list
(app map inc
(term-ctor List Cons 1
(term-ctor List Cons 2
(term-ctor List Cons 3
(term-ctor List Nil)))))))))