# Authoring surface ## Authoring surface Form (A) is implemented as the `ailang-surface` crate (parser + printer). Form-A is gated against drift by `ailang-surface/tests/round_trip.rs`, which parses every `.ail` fixture, prints it back, re-parses, and demands canonical-byte equality. `ail render` and both branches of `ail describe` were rewired to use `ailang_surface::print`, making form (A) the **sole** text projection of a module — the legacy non-round-tripping pretty-printer code in `pretty.rs` was deleted at the same time, leaving only diagnostic helpers (`type_to_string`, `pattern_to_string`, `manifest`) public. ## Architectural pin: data structure is the source of truth The textual surface is **not** a replacement for the JSON-AST. It is one projection among potentially many. Concretely: - The [JSON-AST](0002-data-model.md) keeps its role as the canonical, hashable, content- addressed representation of a module. All hashing, content- addressing, cross-module references, and typecheck/codegen input flow through the JSON-AST. **No new hashable form is introduced.** - The textual surface (form A, this contract) is the **AI authoring projection**: optimised for me producing programs token-efficiently and for foreign LLMs producing programs from a spec. It is not optimised for human authors and does not need to be human-pleasant. - Future projections are explicitly anticipated: a visual / graphical front-end is a plausible second projection for human review and inspection (display being the one case where non-AI eyes matter). The architecture leaves room: any producer of well-formed `ailang-core::ast::Module` values is a valid front-end. - **No human is expected to author AILang seriously.** Authoring is AI work. Display and verification, by contrast, are concerns where human-facing alternatives may be useful — and which can therefore layer their own projections on top of the same AST without touching the surface or the core. In code terms: `ailang-core` owns the AST. `ailang-surface` is one producer/consumer pair: text-form-A → AST → text-form-A. A hypothetical `ailang-visual` would be a different producer of the same AST. `ailang-check` and `ailang-codegen` consume only the AST and remain projection-agnostic. ## Constraints (hard, in priority order) 1. **Formalizable for a foreign LLM.** The grammar must fit in an EBNF/PEG spec of ≤ 30 productions. A model that has never seen AILang must be able to read the spec and produce conforming source zero-shot. Rules out: precedence between binary operators, semantic indentation, maximal-munch lexing, context-sensitive reductions. 2. **AST-isomorphic.** Every surface form maps to exactly one AST shape. The full bijection between `.ail.json` and `.ail` (both directions, BLAKE3-stable hashing, Float-bits-hex encoding, workspace-CI enforcement points) is anchored as the top-level [Roundtrip Invariant](0009-roundtrip-invariant.md) — this constraint records that the surface-design choice must satisfy that invariant; the invariant itself lives at top level because the property is load-bearing on the language identity, not on this file's surface-design rationale. 3. **No external symbols.** ASCII only. No Greek (`∀`), no arrows (`→`), no subscripts. Reasoning: I substitute mojibake for non-ASCII characters under context pressure; foreign LLMs vary in how they tokenize Unicode. 4. **No precedence.** Either everything is parenthesized, or there are no infix operators. Prefer the latter — `add(x, 1)` over `x + 1`. Removes a fail mode for both me and foreign LLMs. 5. **No semantic indentation.** Block structure expressed by paired delimiters or terminator tokens. Indentation is informational only; the parser ignores it. 6. **One construct per token-list.** Every AST node corresponds to exactly one parenthesized form (or atom). No "sometimes you can omit the parens" rules. 7. **AST surface stays frozen.** The surface adapts to the AST, not the other way around. We do not change the JSON schema or invalidate hashes to make the surface prettier. Ratified by: `crates/ailang-surface/tests/round_trip.rs`.