prose: human-readable projection renderer (iter 20a)

New crate ailang-prose with one public fn module_to_prose(&Module)
-> String. Rust-flavour with braces, =>-match-arms, mode keywords
(own/borrow), effects as trailing 'with IO', Cons(1, Nil) ctor
form, /// doc strings.

Lossy projection where the LLM can re-derive: (con T) wrap,
(fn-type ...) wrap, (term-ctor ...) wrap. Load-bearing semantic
detail stays visible (modes, effects, clone, reuse-as, doc strings,
type annotations, tail flag).

CLI: 'ail prose <file.ail.json>' prints the projection.

3 snapshot fixtures + 28 unit tests. 215-line .ail.json reduces
to ~18 lines of legible source.

20b queued: infix arithmetic, paren elision, let-inlining, do
prettify.
This commit is contained in:
2026-05-08 18:06:16 +02:00
parent d8e80cb9c5
commit a9d57c5c81
11 changed files with 1171 additions and 0 deletions
+14
View File
@@ -200,6 +200,13 @@ enum Cmd {
#[arg(short, long)]
output: Option<PathBuf>,
},
/// Iter 20a: prints the module as human-readable prose (form B).
///
/// One-way projection: the renderer is deterministic, but no
/// parser exists for the prose surface. The canonical authoring
/// surface remains form (A) (`render` / `parse`) and the canonical
/// hashable artefact remains the JSON-AST.
Prose { path: PathBuf },
}
fn main() -> Result<()> {
@@ -298,6 +305,13 @@ fn main() -> Result<()> {
let m = ailang_core::load_module(&path)?;
print!("{}", ailang_surface::print(&m));
}
Cmd::Prose { path } => {
// Iter 20a: load via `load_module` (single-module mode,
// matching how `render` / `parse` work). Workspace-wide
// prose is not in scope for 20a.
let m = ailang_core::load_module(&path)?;
print!("{}", ailang_prose::module_to_prose(&m));
}
Cmd::Describe { path, name, json, workspace } => {
if workspace {
let ws = ailang_core::load_workspace(&path)?;