c747cdf932
A read-only coherence skim across the project (the contract edits
themselves verified code-true, INDEX bijection exact, all pins green)
surfaced two pre-existing drifts — both predating this audit, neither
from the contract pass. Fixed in the same conservative style.
1. Model 0008 (ownership-totality) §1+§2 narrated the `Implicit`
leak in the PRESENT tense, contradicting the file's own STATUS
header (Implicit deleted via #55, 76b21c0), contract 0008 ("There
is no `Implicit`"), and the live fixture. §1 claimed "This is
documented intentional behaviour today ... the fixture asserts
`live = 1`"; the `rc_let_implicit_returning_app.ail` fixture now
asserts `live = 0` (its own comment marks the `live = 1` lane as
"Pre-0062"). §2 claimed "the typechecker already treats
`Implicit ≡ Own` (`ParamMode::mode_eq`)"; that variant and fn no
longer exist. Rewrote both to past tense (the leak the cutover
fixed). The STATUS header had been updated at cutover; these two
bodies had not. The design argument (§2-§8) is untouched — the
header frames it as the whitepaper's reasoning and points readers
to the contract for current state.
2. Contract 0010 (scope-boundaries) referenced 18 example fixtures as
`examples/*.ail.json` — files that exist only as `.ail` since the
form-A-default migration (JSON is derived in-process; only the
twelve carve-outs remain `.ail.json` on disk). All 18 → `.ail`
(every target verified present). Same single stale file-path ref in
model 0001 §3 (`list_map_poly`) corrected; model 0001's other two
`.ail.json` mentions are intentional references to the canonical
JSON *form* (the whitepaper's subject) and were left.
Honesty sweep clean; design_index_pin / docs_honesty_pin /
effect_doc_honesty_pin green; no dangling example path remains.
165 lines
6.0 KiB
Markdown
165 lines
6.0 KiB
Markdown
# Authoring surface — notation rationale whitepaper
|
||
|
||
## Candidate notations (same `map` encoded in each)
|
||
|
||
The reference target — the polymorphic `map` from `examples/list_map_poly.ail`:
|
||
|
||
```
|
||
data List a where Nil | Cons a (List a)
|
||
fn map : forall a b. ((a) -> b, List a) -> List b
|
||
= \f xs. match xs of Nil -> Nil
|
||
| Cons h t -> Cons(f(h), map(f, t))
|
||
```
|
||
|
||
#### (A) S-expression with fully-tagged AST nodes
|
||
|
||
```
|
||
(module list_map_poly
|
||
|
||
(data List (vars a)
|
||
(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
|
||
(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)))))))
|
||
```
|
||
|
||
Grammar core (3-rule lexical layer + ~25 named-form productions):
|
||
```
|
||
sexpr ::= atom | "(" sexpr* ")"
|
||
atom ::= integer | string | ident
|
||
ident ::= any maximal non-whitespace, non-paren run that is not
|
||
a recognised integer or string literal.
|
||
```
|
||
|
||
The lexer recognises one delimiter (`(` / `)`) and whitespace.
|
||
Every other maximal token is classified post-hoc:
|
||
- All-digit run with optional leading `-` → integer atom.
|
||
- `"`-delimited run → string atom.
|
||
- Otherwise → ident.
|
||
|
||
Consequence: arithmetic operators like `+` / `-` / `*` / `/` / `%`,
|
||
qualified names like `io/print_str`, and cross-module references
|
||
like `std_list.map` are all single ident tokens with no special
|
||
lex rule. The only token *delimiters* are `(`, `)`, and whitespace.
|
||
|
||
One character is *reserved within* a token: `$`. Any identifier
|
||
token containing `$` is rejected by the lexer
|
||
(`LexError::ReservedDollar` in `crates/ailang-surface/src/lex.rs`),
|
||
because `$` is reserved for the compiler's synthetic namespace
|
||
(match-scrutinee, lifted-let-rec, and shadow-rename binders).
|
||
Making `$` unauthorable keeps that synthetic namespace
|
||
collision-free by construction. The reservation is on the
|
||
Form-(A) authoring surface only — `$` inside a string literal or
|
||
a comment is unaffected, and the canonical `.ail.json`
|
||
deserialization path is intentionally not guarded.
|
||
Bool literals (`true`, `false`) and unit (`(lit-unit)`) are
|
||
disambiguated by parser context, not by lex. Comparison and
|
||
equality are class methods (`eq` / `compare`) and named fns
|
||
(`float_eq` / `float_lt` / etc.), not operators — see
|
||
[Prelude classes](../contracts/0017-prelude-classes.md).
|
||
|
||
Every AST node form has a unique head keyword (`module`, `data`,
|
||
`fn`, `forall`, `fn-type`, `con`, `var`, `app`, `lam`, `match`,
|
||
`case`, `pat-ctor`, `term-ctor`, `do`, `seq`, ...). A bare atom in
|
||
a positional slot (e.g. inside `(con List a)` second position) is
|
||
a name reference whose **sort** is determined by the parent slot:
|
||
|
||
- inside `(con NAME args...)` second-and-later positions → type
|
||
expression. Bare atom there ⇒ `Type::Var { name }`.
|
||
- inside `(app HEAD args...)` first position ⇒ `Term::Var`.
|
||
- inside `(pat-ctor CTOR fields...)` field positions ⇒
|
||
`Pattern::Var`.
|
||
- inside `(case PAT BODY)` second position ⇒ term.
|
||
|
||
There is **no lexical case rule**. To construct a value with a
|
||
ctor, write `(term-ctor TypeName CtorName args...)`. To match
|
||
against one, write `(pat-ctor CtorName fields...)`. Capitalised
|
||
identifiers carry no special meaning to the parser. This rules
|
||
out a class of silent errors ("I forgot to capitalise `Cons` and
|
||
it parsed as a function call").
|
||
|
||
**Pros:** smallest formal grammar of any candidate (the lexical
|
||
core is 3 rules; the named-form productions are uniform — every
|
||
node a tagged list). Foreign-LLM bar lowest. Round-trip with the
|
||
existing pretty-printer is a refactor of `pretty.rs` to emit this
|
||
tagged form, plus a new parser.
|
||
|
||
**Cons:** paren density is high. `(forall (vars a b) (fn-type
|
||
...))` has more visual nesting than the current pretty-printer's
|
||
`forall a. (...) -> ...`. Verbosity is ~2× JSON for the same node
|
||
when measured in characters, but ~8× shorter in lines (the
|
||
existing JSON `box.ail.json` of 160 lines becomes ~20 lines in
|
||
this form).
|
||
|
||
#### (B) Indented record-style with explicit terminators
|
||
|
||
```
|
||
module std_list
|
||
|
||
data List(a):
|
||
Nil
|
||
Cons(a, List(a))
|
||
end
|
||
|
||
fn map:
|
||
type: forall a b. fn(fn(a) -> b, List(a)) -> List(b)
|
||
params: f, xs
|
||
body:
|
||
match xs:
|
||
Nil => Nil
|
||
Cons(h, t) => Cons(f(h), map(f, t))
|
||
end
|
||
end
|
||
```
|
||
|
||
Grammar core (~20–30 productions): module-level (def/data/end), type
|
||
sub-grammar (forall, fn, con, var), term sub-grammar (lam, match,
|
||
ctor, app, lit, var, seq), pattern sub-grammar.
|
||
|
||
**Pros:** higher information density per line, closer to mainstream
|
||
ML/Haskell shape. **Cons:** four sub-grammars instead of one.
|
||
`forall a b. fn(...)` keeps a pseudo-precedence (`->` binds tighter
|
||
than the outer `fn(...)` wrapper). Foreign-LLM bar higher.
|
||
|
||
#### (C) Pretty-printer-as-source
|
||
|
||
Use exactly the format `pretty::module` already emits, plus a parser
|
||
that accepts it. The existing pretty-printer's quirks (`::` for
|
||
type-of, `[params]` for fn-params, `<a>` for type-args, `forall a. ...`,
|
||
`!IO`, `()` ambiguous between unit-arg-list and empty-form) become
|
||
the spec.
|
||
|
||
**Pros:** zero churn — the existing pretty-printer is already the
|
||
spec; only the inverse is missing. Round-trip is the identity by
|
||
construction. **Cons:** the existing format mixes four mini-dialects
|
||
(s-expr at term level, ML-shape at type level, square brackets for
|
||
params, `<>` for type args). Formalising it crisply is harder than
|
||
designing a uniform form from scratch.
|
||
|
||
The chosen form is (A), shipped as the `ailang-surface` crate (see
|
||
[authoring surface](../contracts/0001-authoring-surface.md)); the
|
||
canonical JSON-AST it round-trips against is documented in
|
||
[Data model](../contracts/0002-data-model.md), and the round-trip
|
||
property itself in [Roundtrip Invariant](../contracts/0009-roundtrip-invariant.md).
|
||
|
||
For the prose projection used for human review (Form B), see
|
||
[prose projection](0006-prose-projection.md).
|