diff --git a/docs/JOURNAL.md b/docs/JOURNAL.md index 49d979b..92b77f6 100644 --- a/docs/JOURNAL.md +++ b/docs/JOURNAL.md @@ -8741,3 +8741,141 @@ build + test green. common enough to warrant the schema field. - Precise sub-binder analysis for the lint: waits for evidence that the conservative cut misses real cases. + +## 2026-05-08 — Pinned: human-readable prose surface (Family 20 candidate) + +User feature-request, pinned for autonomous design pass: + +> *"AILang ist für Menschen tatsächlich ziemlich unleserlich. Ich +> hätte gern einen Menschen-lesbaren Text-Output, der semantisch +> exakt identisch zu ail ist, aber von Menschen gut gelesen werden +> kann. Dabei sind Formatierung, Inlining, klare Identifier (keine +> Klammerhölle!), vllt. Infix-Notation, etc. wichtig. Und eventuell +> das Weglassen von Spezifika, die nur der Sprachstringenz dienen. +> Der Zweck ist: 1. Menschen sollen den Code schneller begreifen. +> 2. Sie sollen den Code durch FREITEXT editieren können. Der +> editierte Code und der Original-Code gehen dann wieder an das +> LLM, das dann in AIL die Freitext-Ergänzungen einbaut. Damit +> beginnt der Zyklus von vorne."* + +### Read of the request + +Two artefacts are wanted: + + 1. **A renderer.** `Module → human-prose String`. Indented, + infix-flavoured, parens dropped where unambiguous, Lispy + `(con T)` / `(term-ctor ...)` machinery suppressed in favour + of conventional notation (`T`, `Cons(1, Nil)`). + 2. **A round-trip mediator.** Original .ail.json + edited prose + → updated .ail.json. The mediator is the LLM, not the + compiler. Renderer is deterministic; round-trip is not (LLM + interprets free-text intent). + +The user's word "semantisch exakt identisch" applies to the +**renderer's output as a projection of the source**: re-reading +the prose alongside the .ail.json gives a human all the semantic +information needed to reason about the program. The user's +"Weglassen von Spezifika, die nur der Sprachstringenz dienen" +explicitly authorises *lossy* projection — the prose may omit +machinery the LLM can re-derive (e.g. `consume_count` is already +inferred, so the prose definitely doesn't show it; redundant +parens can go; `(con Int)` becomes `Int`). The full .ail.json +remains the canonical source. + +### What MUST stay visible in prose + +Anything semantically load-bearing that the LLM cannot trivially +re-derive from prose alone: + + - `(own T)` / `(borrow T)` mode annotations on fn signatures — + these are contracts (Decision 10), not buchhaltung. Render as + `own T` / `borrow T` or a sigil (`&T` / `~T`?). To be decided. + - Effect annotations (`IO`, `Diverge`) on user fns. + - Explicit `clone` calls — they're an author commitment, not a + free choice. + - Doc strings. + +### What MAY go + + - Outer `(module …)` wrap (filename or top-line is enough). + - `(con T)` wrapping around base types — render as `T`. + - `(term-ctor T C f1 f2)` — render as `C(f1, f2)` (or just + `Nil` for nullary). + - Redundant parentheses around expressions whose precedence is + unambiguous. + - Fully explicit `(fn-type (params ...) (ret ...))` — + render as `(p1: T1, p2: T2) -> R`. + - `(let x v body)` — render as `let x = v` (newline) `body` or + `x := v; body`. + - Non-essential schema-rigour fields (e.g. `consume_count` if + the AST ever exposed it; today it's already inferred so this + is a forward-proofing note). + - The implicit `(do io/print_int x)` ceremony when `print` would + do. + +### Round-trip cycle + +For the editor cycle: + +``` +.ail.json ──[render]──→ .prose.txt + ↓ user edits freely + .prose.txt' (edited) + ↓ +.ail.json + .prose.txt' ──[LLM mediator]──→ .ail.json' +``` + +The LLM mediator is the contract-enforcer here: it integrates +free-text edits while preserving annotations the prose may have +omitted (e.g. carries forward the original `(own T)` annotation +unless the user's edit obviously contradicts it). This is *not* +a compiler pass — the renderer ships first, the mediator is +a separate piece (probably a `prompt_template` + a calling +convention, not a Rust crate). + +### Iter scope + +Probably a small family. First cut design: + + - **Iter 20a — renderer skeleton.** New crate `ailang-prose` + with `pub fn module_to_prose(&Module) -> String`. Covers the + full AST with a conservative formatting policy (no infix yet, + no inlining, just kill the `(con ...)` / `(term-ctor ...)` + noise and use proper indentation + commas). One snapshot test + per fixture in `examples/`. + + - **Iter 20b — formatting polish.** Infix for arithmetic + (`(i64-add a b)` → `a + b`), drop redundant parens, inline + short let-bindings, prettier match arms. Snapshot tests + update; behavioural surface unchanged. + + - **Iter 20c — `ail prose` CLI subcommand.** `ail prose ` + prints the prose to stdout. Symmetric to `ail parse`'s role. + + - **Iter 20d — round-trip ergonomics.** This is where the LLM + mediator's input format gets specified. May be a documented + prompt template under `docs/`, may grow into a `ail merge-prose` + subcommand that wraps an LLM call. Defer until 20a–c are real + code we're using. + +### Why this is its own family + +Decision 10 / Family 18 was the memory model. Family 20 is a +**presentation layer** — it touches no semantics, no codegen, no +typechecker. Lives entirely above `ailang-core` as a sibling of +`ailang-surface` (which is the form-A *parser* / printer; prose +is the form-B *projection*). + +### What's deferred / open questions for design pass + + - How to render `(own T)` / `(borrow T)` in prose. Sigil vs. + keyword. Sigils are denser; keywords align with the + explicit-intent philosophy. Lean one way and document + rationale. + - Module headers / imports — render or suppress? + - Whether prose carries hash/version annotations for the + round-trip (so the mediator can detect drift between prose + and the .ail.json the user *thinks* they're editing). + - Whether snapshot tests live in `crates/ailang-prose/tests/` + or `examples/` (as `.prose.txt` siblings to the .ail.json). +