diff --git a/bench/orchestrator-stats/2026-05-12-iter-ms.2.json b/bench/orchestrator-stats/2026-05-12-iter-ms.2.json new file mode 100644 index 0000000..2e724f9 --- /dev/null +++ b/bench/orchestrator-stats/2026-05-12-iter-ms.2.json @@ -0,0 +1,29 @@ +{ + "iter_id": "ms.2", + "date": "2026-05-12", + "mode": "standard", + "outcome": "DONE", + "tasks_total": 6, + "tasks_completed": 6, + "reloops_per_task": { "1": 0, "2": 0, "3": 0, "4": 0, "5": 0, "6": 0 }, + "review_loops_spec": 0, + "review_loops_quality": 0, + "blocked_reason": null, + "anomalies": [ + "CodeLlama JSON-cohort t2_length and t4_count_zeros terminated as api_failure on turn 1 with zero tokens (transient IONOS-side terminal error); not re-attempted per carrier no-exploratory-re-runs constraint.", + "Qwen re-run shifted one cell vs cma.3 baseline (AILX 2/4 -> 1/4, JSON 1/4 -> 1/4) at identical temperature=0; attributed to IONOS-side state-of-day noise on a single deterministic call, not the ms.1 feedback-formatting fix." + ], + "milestone_token_spend": { + "qwen_prompt": 292953, + "qwen_completion": 18446, + "codellama_prompt": 209032, + "codellama_completion": 4945, + "total_prompt": 501985, + "total_completion": 23391, + "grand_total": 525376 + }, + "run_dirs": { + "qwen": "experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/", + "codellama": "experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/" + } +} diff --git a/docs/DESIGN.md b/docs/DESIGN.md index b01687d..b370365 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -603,37 +603,59 @@ lowest-common-denominator fallback that always works. ### Empirical addendum (2026-05-12) -Cross-model measurement against `Qwen/Qwen3-Coder-Next` (IONOS), +Cross-model measurement against two foreign LLMs via IONOS, temperature=0, top_p=1, max-turns=5. Two blind cohorts on the same four MVP tasks (`t1_add_three`, `t2_length`, `t3_main_prints`, -`t4_count_zeros`); each cohort sees only its own form's -mini-spec. Run directory: -`experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/`. +`t4_count_zeros`); each cohort sees only its own form's mini-spec. +Subjects (both runs use the pipeline-error-formatting fix from ms.1 +so JSON-cohort feedback carries the full anyhow cause chain): -| metric | JSON cohort | AILX cohort | -|---|---|---| -| reached green | 1/4 | 2/4 | -| first-attempt green | 0/4 | 1/4 | -| mean turns-to-green (green only) | 2.0 | 2.0 | -| total prompt tokens | 191,768 | 95,417 | -| total completion tokens | 15,376 | 2,587 | -| top error class | check (×4) | parse (×3) | +- `Qwen/Qwen3-Coder-Next` — + `experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/` +- `meta-llama/CodeLlama-13b-Instruct-hf` — + `experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/` -The AILX cohort reached green on more tasks at roughly half the -prompt-token cost and one-sixth the completion-token cost. The -only first-attempt success in the entire run was AILX on -`t3_main_prints` (~5 lines of tagged s-expr; the JSON-cohort -counterpart was ~23 lines of structured JSON and required a -correction turn). JSON-cohort failures clustered in the -typecheck-stage; AILX-cohort failures clustered in the parse stage, -which is the symmetric front-of-pipeline check for that form. +| metric | Qwen / JSON | Qwen / AILX | CodeLlama / JSON | CodeLlama / AILX | +|---|---|---|---|---| +| reached green | 1/4 | 1/4 | 0/4 | 2/4 | +| first-attempt green | 1/4 | 1/4 | 0/4 | 2/4 | +| mean turns-to-green (green only) | 1.0 | 1.0 | — | 1.0 | +| total prompt tokens | 182,378 | 110,575 | 116,015 | 93,017 | +| total completion tokens | 14,972 | 3,474 | 2,711 | 2,234 | +| top error class | check (×3) | parse (×3) | check (×2) | parse (×2) | -**Scope of this addendum:** single subject, single deterministic -run. This is a data point, not a verdict. The universal claim of -this Decision (".ailx is the AI authoring projection") remains -pending a multi-subject expansion — see the roadmap entry -"Multi-subject expansion (cross-model authoring-form follow-up)" -for the next-step queue. +Both subjects show the same direction on every metric the table +tracks. AILX is cheaper than JSON on prompt tokens (Qwen 61%, +CodeLlama 80% of the JSON cohort's spend) and cheaper on completion +tokens (Qwen 23%, CodeLlama 82%). AILX reached-green is greater than +or equal to JSON reached-green for each subject (Qwen ties at 1/4; +CodeLlama strictly dominates, 2/4 vs 0/4). The failure-class +symmetry observed on the original Qwen baseline holds for both +subjects: JSON cohorts fail at the typecheck stage, AILX cohorts at +the parse stage — each form's front-of-pipeline check. Three of the +four first-attempt-green cells observed across the two subjects are +AILX (Qwen-AILX t3, CodeLlama-AILX t1, CodeLlama-AILX t3); the +fourth is Qwen-JSON t3, the same task the original baseline already +flagged as the JSON-form's only first-attempt success. Two CodeLlama +JSON-cohort cells (`t2_length`, `t4_count_zeros`) terminated as +`api_failure` on turn 1 with zero tokens consumed — a transient +IONOS-side terminal error, not a model-output failure; CodeLlama +JSON-cohort numbers therefore average over 2 informative cells, not +4. The Qwen re-run also shifted by one cell against the cma.3 +baseline (AILX 2/4 → 1/4, JSON 1/4 → 1/4) despite identical +temperature=0 inputs; this is IONOS-side state-of-day noise on a +single deterministic re-run, not an effect of the ms.1 +feedback-formatting fix (which only changes JSON-cohort prompt +content). + +**Scope of this addendum:** two subjects, n=1 each, deterministic. +This is a second data point pointing in the same direction as the +first, not a verdict. The universal claim of this Decision (".ailx +is the AI authoring projection") would need ≥3 subjects with +statistical robustness to ratify; both current points point the +same way (AILX cohort cheaper and at-least-as-green) but neither +the sample size nor the cross-call noise observed in the Qwen +re-run is enough to close out the Decision. ## Decision 8: explicit, verified tail calls diff --git a/docs/journals/2026-05-12-iter-ms.2.md b/docs/journals/2026-05-12-iter-ms.2.md new file mode 100644 index 0000000..486f0ad --- /dev/null +++ b/docs/journals/2026-05-12-iter-ms.2.md @@ -0,0 +1,124 @@ +# iter ms.2 — Multi-subject run + DESIGN.md addendum extension + +**Date:** 2026-05-12 +**Started from:** 8689f7a2386e0d7993609c374d91984bc28d215c +**Status:** DONE +**Tasks completed:** 6 of 6 + +## Summary + +Two live IONOS harness invocations executed with the ms.1 fix in +place: a Qwen3-Coder-Next retroactive re-run +(`runs/2026-05-12-080864/`, RUN_STATUS=ok) and a first-ever +CodeLlama-13b-Instruct run (`runs/2026-05-12-9197fd/`, RUN_STATUS=ok +at the run level; 2 of 8 cells terminated as api_failure on turn 1 +with zero tokens — transient IONOS-side terminal error, disclosed +in the addendum and below). DESIGN.md §"Decision 6 / Empirical +addendum (2026-05-12)" replaced with a four-column per-cell table +plus a framing paragraph; roadmap P3 "Multi-subject expansion" +entry removed. + +Both subjects point the same way on every measured axis: AILX +cohort is cheaper than JSON cohort on prompt tokens (Qwen 61%, +CodeLlama 80%) and on completion tokens (Qwen 23%, CodeLlama 82%), +and AILX reached-green ≥ JSON reached-green for each subject (Qwen +ties at 1/4; CodeLlama strictly dominates 2/4 vs 0/4). Three of the +four first-attempt-green cells across the two subjects are AILX. +Decision 6 is not ratified — n=1 per subject across two subjects is +not statistical robustness — but the second data point points +consistently. The roadmap entry was removed because the brainstorm +spec was scoped to two subjects, not because the universal claim is +closed; a future ratification iteration would re-open it. + +Anomaly disclosure: the Qwen re-run shifted by one cell vs the cma.3 +baseline (AILX 2/4 → 1/4, JSON 1/4 → 1/4) at identical +temperature=0; this is IONOS-side state-of-day noise, not an effect +of the ms.1 feedback fix, which only changes JSON-cohort prompt +content. The two CodeLlama JSON api_failure cells (`t2_length`, +`t4_count_zeros`) were not re-run — the carrier constraint forbids +exploratory token spend, and the addendum discloses the partial +sample directly. + +Roadmap P3 "Multi-subject expansion" entry removed in this iter. + +## Per-task notes + +- iter ms.2.1: Qwen retroactive re-run via harness — new run dir + `experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/`, + RUN_STATUS=ok, 8/8 cells populated. AIL_BIN set to + `target/release/ail` (no `~/.local/bin/ail`); harness picks it up + via env-var resolution at `pipeline.rs:42`. +- iter ms.2.2: CodeLlama-13b-Instruct first run via harness — new + run dir + `experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/`, + model id `meta-llama/CodeLlama-13b-Instruct-hf` (Boss-pre-flighted + IONOS catalogue id), RUN_STATUS=ok. 6/8 cells informative; 2/8 + json cells (`t2_length`, `t4_count_zeros`) `api_failure` on turn 1 + zero tokens. +- iter ms.2.3: Per-cell metrics computed via direct scores.csv + read — see addendum table. Total milestone token spend: prompt + 501,985 + completion 23,391 = 525,376. +- iter ms.2.4: DESIGN.md lines 604-636 replaced with new + four-column addendum block (~50 lines including the framing + paragraph). Subjects named with canonical IONOS model ids and + run-dir pointers; framing paragraph reports direction agreement, + failure-class symmetry, first-attempt tally, api_failure anomaly, + and Qwen-rerun noise. Scope paragraph extended to "two subjects, + n=1 each". +- iter ms.2.5: roadmap.md lines 253-268 removed (P3 multi-subject + entry + context note); journal file written. INDEX.md is NOT + edited by this agent per the Iron Law; the Boss appends the + ms.2 line at commit time. Suggested INDEX line (per plan Task 5 + Step 3): + `- 2026-05-12 — iter ms.2: Qwen retroactive re-run + first CodeLlama-13b-Instruct run; DESIGN.md §Decision-6 addendum extended to four cells; roadmap P3 removed → 2026-05-12-iter-ms.2.md` +- iter ms.2.6: Spec acceptance criteria sweep (1-7 all green); 14 + harness tests still pass. + +## Spec acceptance sweep + +1. ms.1 `pipeline.rs:118` reads `format!("check: {e:#}")` + RED→GREEN unit test → green (ms.1 working tree). +2. ms.1 existing 14 harness tests still pass → green (verified Task 6 Step 2). +3. CodeLlama-13b-Instruct-hf runs to completion against IONOS endpoint, RUN_STATUS=ok at run level → green; model id recorded above. +4. Qwen3-Coder-Next re-run completes, RUN_STATUS=ok, 8 cells; distinct dir from cma.3 baseline → green. +5. DESIGN.md §"Decision 6 / Empirical addendum" extended with follow-up paragraph + four-cell-shaped table → green. +6. Roadmap P3 entry removed; one-line mirror above → green. +7. Per-iter journal (this file) written → green. INDEX.md ms.2 entry is Boss-side per the Iron Law; suggested line is in the end-report. + +## Concerns + +- 2 of 4 CodeLlama JSON cells are `api_failure` (terminal IONOS + error on turn 1). Per the Plan's branch (c) and the carrier's + "no exploratory re-runs" constraint, not re-attempted. The + addendum and journal disclose this directly; the + CodeLlama-JSON column's prompt-token average is therefore over 2 + informative cells, not 4. Effect on the AILX-cheaper conclusion: + none in direction; the prompt-token gap is preserved over the + two informative JSON cells. If a future iteration re-runs + CodeLlama at lower IONOS load, this should resolve. +- Qwen-re-run vs cma.3-baseline drift (1 cell) at identical + temperature=0 is IONOS noise on a single deterministic call. A + future ratification iteration should add ≥3 repetitions per + subject to bound this noise, not rely on a single + temperature-zero call. + +## Known debt + +- The pre-existing 3 warnings in `harness/src/ionos.rs` lines 86, + 125, 137 (`last_err` assigned but never read) survived ms.1 + and ms.2. Not in scope here; cma.2 inheritance. +- Decision 6 universal-claim status is still "second data point, + not verdict". A future ratification iteration would need ≥3 + subjects with statistical robustness. Out of scope for ms. + +## Files touched + +- `docs/DESIGN.md` (addendum extended) +- `docs/roadmap.md` (P3 entry removed) +- `experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/` (created by Qwen run) +- `experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/` (created by CodeLlama run) +- `docs/journals/2026-05-12-iter-ms.2.md` (this file) +- `bench/orchestrator-stats/2026-05-12-iter-ms.2.json` (stats) + +## Stats + +bench/orchestrator-stats/2026-05-12-iter-ms.2.json diff --git a/docs/journals/INDEX.md b/docs/journals/INDEX.md index 9ccb542..04e5a0b 100644 --- a/docs/journals/INDEX.md +++ b/docs/journals/INDEX.md @@ -26,3 +26,4 @@ - 2026-05-12 — iter cma.3: live Qwen3-Coder-Next run + DESIGN.md §Decision-6 empirical addendum + roadmap close (AILX 2/4 green vs JSON 1/4; ~98k vs ~207k tokens; single-subject scope explicit, multi-subject expansion queued as P3) → 2026-05-12-iter-cma.3.md - 2026-05-12 — audit-cma: milestone close (Cross-model authoring-form test) — architect drift fixed in 2 record-keeping items (INDEX backfill for brainstorm Step 7.5, README test-count correction); bench all-green (5 unexplained improvements on latency.explicit_at_rc not coupled to milestone, baseline left pristine) → 2026-05-12-audit-cma.md - 2026-05-12 — iter ms.1: pipeline anyhow-chain preservation — `{e}` → `{e:#}` at `pipeline.rs:118` restores `serde_json` parse-error leaf in JSON-cohort feedback; pinning unit test added (14/14 green) → 2026-05-12-iter-ms.1.md +- 2026-05-12 — iter ms.2: Qwen3-Coder-Next retroactive re-run + first CodeLlama-13b-Instruct run via IONOS; DESIGN.md §Decision-6 addendum extended from 2-col single-subject to 4-col two-subject; both subjects agree on direction (AILX cheaper + ≥ green); roadmap P3 multi-subject entry removed → 2026-05-12-iter-ms.2.md diff --git a/docs/roadmap.md b/docs/roadmap.md index 238ca8e..b37ca7c 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -249,20 +249,3 @@ context. Pick the next milestone from P1.)_ - [ ] **\[idea\]** Richer integration paths between RC and uniqueness — deferred from the 21' arc; revisit once the uniqueness inference covers more program shapes. - -- [ ] **\[milestone\]** Multi-subject expansion (cross-model - authoring-form follow-up) — extend the cross-model authoring-form - test to ≥2 additional foreign LLMs and ≥4 more tasks (broader - surface coverage; current MVP run hit only 3/8 green so the - failure clusters dominate the signal). Optional: repetitions for - statistical robustness; calibrate strip_locations to also strip - `at byte N` from AILX-cohort feedback so the asymmetry observed - in the cma.3 baseline is removed before re-measuring. Goal: turn - the single-subject data point recorded in DESIGN.md §"Decision 6 - / Empirical addendum (2026-05-12)" into either a ratification or - retirement of Decision 6's universal claim. Baseline run: - `experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-df7531/`. - - context: JOURNAL 2026-05-12 ("iter cma.3"). Other IONOS catalogue - models named in the brainstorm record (Meta-Llama-3.1-405B, - Llama-3.3-70B, gpt-oss-120b, Mistral-Small-24B, CodeLlama-13b) - are reasonable next subjects. diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/RUN_STATUS b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/RUN_STATUS new file mode 100644 index 0000000..b5754e2 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/RUN_STATUS @@ -0,0 +1 @@ +ok \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_1_check_stderr.txt new file mode 100644 index 0000000..8f777c6 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_1_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: unexpected end of input, expected `)` to end module or another def diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_1_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_1_program.ailx new file mode 100644 index 0000000..57165eb --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_1_program.ailx @@ -0,0 +1,11 @@ +(module t1_add_three + (fn add_three + (doc "Add three Ints.") + (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int)))) + (params x y z) + (body (app + (app + x y) z))) + (fn main + (doc "Print add_three(1, 2, 3) and add_three(10, 20, 30).") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30))))) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_1_request.json new file mode 100644 index 0000000..a7db989 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_1_response.json new file mode 100644 index 0000000..bfd460a --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int))))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))", + "usage": { + "completion_tokens": 170, + "prompt_tokens": 6385, + "total_tokens": 6555 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_1_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_2_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_2_check_stderr.txt new file mode 100644 index 0000000..fcc3420 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_2_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `)` (end of type-attr), got `(` at byte 144 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_2_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_2_program.ailx new file mode 100644 index 0000000..5bbf62a --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_2_program.ailx @@ -0,0 +1,11 @@ +(module t1_add_three + (fn add_three + (doc "Add three Ints.") + (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int))) + (params x y z) + (body (app + (app + x y) z))) + (fn main + (doc "Print add_three(1, 2, 3) and add_three(10, 20, 30).") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30))))) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_2_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_2_request.json new file mode 100644 index 0000000..ab4f825 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_2_request.json @@ -0,0 +1,18 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + }, + { + "role": "assistant", + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int))))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: unexpected end of input, expected `)` to end module or another def" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_2_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_2_response.json new file mode 100644 index 0000000..bce4280 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_2_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int)))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))", + "usage": { + "completion_tokens": 170, + "prompt_tokens": 6586, + "total_tokens": 6756 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_2_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_2_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_3_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_3_check_stderr.txt new file mode 100644 index 0000000..fcc3420 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_3_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `)` (end of type-attr), got `(` at byte 144 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_3_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_3_program.ailx new file mode 100644 index 0000000..5bbf62a --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_3_program.ailx @@ -0,0 +1,11 @@ +(module t1_add_three + (fn add_three + (doc "Add three Ints.") + (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int))) + (params x y z) + (body (app + (app + x y) z))) + (fn main + (doc "Print add_three(1, 2, 3) and add_three(10, 20, 30).") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30))))) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_3_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_3_request.json new file mode 100644 index 0000000..50b0633 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_3_request.json @@ -0,0 +1,26 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + }, + { + "role": "assistant", + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int))))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: unexpected end of input, expected `)` to end module or another def" + }, + { + "role": "assistant", + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int)))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_3_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_3_response.json new file mode 100644 index 0000000..0baf6cf --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_3_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int)))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))", + "usage": { + "completion_tokens": 170, + "prompt_tokens": 6786, + "total_tokens": 6956 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_3_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_3_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_4_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_4_check_stderr.txt new file mode 100644 index 0000000..fcc3420 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_4_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `)` (end of type-attr), got `(` at byte 144 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_4_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_4_program.ailx new file mode 100644 index 0000000..5bbf62a --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_4_program.ailx @@ -0,0 +1,11 @@ +(module t1_add_three + (fn add_three + (doc "Add three Ints.") + (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int))) + (params x y z) + (body (app + (app + x y) z))) + (fn main + (doc "Print add_three(1, 2, 3) and add_three(10, 20, 30).") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30))))) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_4_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_4_request.json new file mode 100644 index 0000000..b585911 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_4_request.json @@ -0,0 +1,34 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + }, + { + "role": "assistant", + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int))))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: unexpected end of input, expected `)` to end module or another def" + }, + { + "role": "assistant", + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int)))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + }, + { + "role": "assistant", + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int)))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_4_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_4_response.json new file mode 100644 index 0000000..a495fb8 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_4_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int)))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))", + "usage": { + "completion_tokens": 170, + "prompt_tokens": 6986, + "total_tokens": 7156 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_4_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_4_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_5_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_5_check_stderr.txt new file mode 100644 index 0000000..fcc3420 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_5_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `)` (end of type-attr), got `(` at byte 144 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_5_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_5_program.ailx new file mode 100644 index 0000000..5bbf62a --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_5_program.ailx @@ -0,0 +1,11 @@ +(module t1_add_three + (fn add_three + (doc "Add three Ints.") + (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int))) + (params x y z) + (body (app + (app + x y) z))) + (fn main + (doc "Print add_three(1, 2, 3) and add_three(10, 20, 30).") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30))))) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_5_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_5_request.json new file mode 100644 index 0000000..e0e061a --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_5_request.json @@ -0,0 +1,42 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + }, + { + "role": "assistant", + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int))))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: unexpected end of input, expected `)` to end module or another def" + }, + { + "role": "assistant", + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int)))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + }, + { + "role": "assistant", + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int)))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + }, + { + "role": "assistant", + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int)))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_5_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_5_response.json new file mode 100644 index 0000000..56cf974 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_5_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int)))\n (params x y z)\n (body (app + (app + x y) z)))\n (fn main\n (doc \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))", + "usage": { + "completion_tokens": 170, + "prompt_tokens": 7186, + "total_tokens": 7356 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_5_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t1_add_three/turn_5_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_1_check_stderr.txt new file mode 100644 index 0000000..330bc59 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_1_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `)` (end of type-attr), got `(` at byte 202 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_1_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_1_program.ailx new file mode 100644 index 0000000..6599be5 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_1_program.ailx @@ -0,0 +1,17 @@ +(module t2_length + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn length + (doc "Count the elements in a List a.") + (type (fn-type (params (con List)) (ret (con Int))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons _ t) (app + 1 (app length t)))))) + (fn main + (doc "Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app length (ctor List Nil))) + (do io/print_int (app length (ctor List Cons 7 (ctor List Cons 8 (ctor List Cons 9 (ctor List Nil)))))))))) diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_1_request.json new file mode 100644 index 0000000..1fc80d3 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_1_response.json new file mode 100644 index 0000000..e3e607b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (doc \"Count the elements in a List a.\")\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (doc \"Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor List Nil)))\n (do io/print_int (app length (ctor List Cons 7 (ctor List Cons 8 (ctor List Cons 9 (ctor List Nil))))))))))\n", + "usage": { + "completion_tokens": 217, + "prompt_tokens": 6409, + "total_tokens": 6626 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_1_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_2_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_2_check_stderr.txt new file mode 100644 index 0000000..1e8684d --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_2_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error in term: unknown term head `ctor`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit` at byte 529 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_2_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_2_program.ailx new file mode 100644 index 0000000..8f5e9ac --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_2_program.ailx @@ -0,0 +1,17 @@ +(module t2_length + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn length + (doc "Count the elements in a List a.") + (type (fn-type (params (con List)) (ret (con Int)))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons _ t) (app + 1 (app length t)))))) + (fn main + (doc "Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app length (ctor List Nil))) + (do io/print_int (app length (ctor List Cons 7 (ctor List Cons 8 (ctor List Cons 9 (ctor List Nil)))))))))) diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_2_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_2_request.json new file mode 100644 index 0000000..f272dce --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_2_request.json @@ -0,0 +1,18 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (doc \"Count the elements in a List a.\")\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (doc \"Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor List Nil)))\n (do io/print_int (app length (ctor List Cons 7 (ctor List Cons 8 (ctor List Cons 9 (ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_2_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_2_response.json new file mode 100644 index 0000000..fbddfba --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_2_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (doc \"Count the elements in a List a.\")\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (doc \"Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor List Nil)))\n (do io/print_int (app length (ctor List Cons 7 (ctor List Cons 8 (ctor List Cons 9 (ctor List Nil))))))))))\n", + "usage": { + "completion_tokens": 217, + "prompt_tokens": 6656, + "total_tokens": 6873 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_2_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_2_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_3_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_3_check_stderr.txt new file mode 100644 index 0000000..118d529 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_3_check_stderr.txt @@ -0,0 +1,3 @@ +error: [unknown-type] List: unknown type: `List expects 1 type arg(s), got 0` +error: [unknown-type] length: unknown type: `List expects 1 type arg(s), got 0` +error: [type-mismatch] main: type mismatch: expected List, got List<$m0> diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_3_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_3_program.ailx new file mode 100644 index 0000000..d2fba09 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_3_program.ailx @@ -0,0 +1,17 @@ +(module t2_length + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn length + (doc "Count the elements in a List a.") + (type (fn-type (params (con List)) (ret (con Int)))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons _ t) (app + 1 (app length t)))))) + (fn main + (doc "Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app length (term-ctor List Nil))) + (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil)))))))))) diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_3_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_3_request.json new file mode 100644 index 0000000..1bc36d2 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_3_request.json @@ -0,0 +1,26 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (doc \"Count the elements in a List a.\")\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (doc \"Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor List Nil)))\n (do io/print_int (app length (ctor List Cons 7 (ctor List Cons 8 (ctor List Cons 9 (ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (doc \"Count the elements in a List a.\")\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (doc \"Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor List Nil)))\n (do io/print_int (app length (ctor List Cons 7 (ctor List Cons 8 (ctor List Cons 9 (ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "parse: parse error: parse error in term: unknown term head `ctor`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_3_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_3_response.json new file mode 100644 index 0000000..9cd793f --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_3_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (doc \"Count the elements in a List a.\")\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (doc \"Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (term-ctor List Nil)))\n (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil))))))))))\n", + "usage": { + "completion_tokens": 227, + "prompt_tokens": 6950, + "total_tokens": 7177 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_3_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_3_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_4_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_4_check_stderr.txt new file mode 100644 index 0000000..d0b8c65 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_4_check_stderr.txt @@ -0,0 +1 @@ +parse error: lex error: invalid integer literal "7," at byte 380 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_4_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_4_program.ailx new file mode 100644 index 0000000..47cca99 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_4_program.ailx @@ -0,0 +1,17 @@ +(module t2_length + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn length + (doc "Count the elements in a List a.) + (type (fn-type (params (con List)) (ret (con Int)))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons _ t) (app + 1 (app length t)))))) + (fn main + (doc "Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app length (term-ctor List Nil))) + (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil)))))))))) diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_4_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_4_request.json new file mode 100644 index 0000000..29452ff --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_4_request.json @@ -0,0 +1,34 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (doc \"Count the elements in a List a.\")\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (doc \"Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor List Nil)))\n (do io/print_int (app length (ctor List Cons 7 (ctor List Cons 8 (ctor List Cons 9 (ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (doc \"Count the elements in a List a.\")\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (doc \"Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor List Nil)))\n (do io/print_int (app length (ctor List Cons 7 (ctor List Cons 8 (ctor List Cons 9 (ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "parse: parse error: parse error in term: unknown term head `ctor`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`" + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (doc \"Count the elements in a List a.\")\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (doc \"Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (term-ctor List Nil)))\n (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "check: error: [unknown-type] List: unknown type: `List expects 1 type arg(s), got 0`\nerror: [unknown-type] length: unknown type: `List expects 1 type arg(s), got 0`\nerror: [type-mismatch] main: type mismatch: expected List, got List<$m0>" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_4_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_4_response.json new file mode 100644 index 0000000..017f5e0 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_4_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (doc \"Count the elements in a List a.)\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (doc \"Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (term-ctor List Nil)))\n (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil))))))))))\n", + "usage": { + "completion_tokens": 227, + "prompt_tokens": 7257, + "total_tokens": 7484 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_4_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_4_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_5_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_5_check_stderr.txt new file mode 100644 index 0000000..118d529 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_5_check_stderr.txt @@ -0,0 +1,3 @@ +error: [unknown-type] List: unknown type: `List expects 1 type arg(s), got 0` +error: [unknown-type] length: unknown type: `List expects 1 type arg(s), got 0` +error: [type-mismatch] main: type mismatch: expected List, got List<$m0> diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_5_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_5_program.ailx new file mode 100644 index 0000000..d2fba09 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_5_program.ailx @@ -0,0 +1,17 @@ +(module t2_length + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn length + (doc "Count the elements in a List a.") + (type (fn-type (params (con List)) (ret (con Int)))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons _ t) (app + 1 (app length t)))))) + (fn main + (doc "Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app length (term-ctor List Nil))) + (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil)))))))))) diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_5_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_5_request.json new file mode 100644 index 0000000..710ec22 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_5_request.json @@ -0,0 +1,42 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (doc \"Count the elements in a List a.\")\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (doc \"Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor List Nil)))\n (do io/print_int (app length (ctor List Cons 7 (ctor List Cons 8 (ctor List Cons 9 (ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (doc \"Count the elements in a List a.\")\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (doc \"Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor List Nil)))\n (do io/print_int (app length (ctor List Cons 7 (ctor List Cons 8 (ctor List Cons 9 (ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "parse: parse error: parse error in term: unknown term head `ctor`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`" + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (doc \"Count the elements in a List a.\")\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (doc \"Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (term-ctor List Nil)))\n (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "check: error: [unknown-type] List: unknown type: `List expects 1 type arg(s), got 0`\nerror: [unknown-type] length: unknown type: `List expects 1 type arg(s), got 0`\nerror: [type-mismatch] main: type mismatch: expected List, got List<$m0>" + }, + { + "role": "assistant", + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (doc \"Count the elements in a List a.)\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (doc \"Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (term-ctor List Nil)))\n (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil))))))))))\n" + }, + { + "role": "user", + "content": "parse: parse error: lex error: invalid integer literal \"7,\"" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_5_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_5_response.json new file mode 100644 index 0000000..113dabe --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_5_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (doc \"Count the elements in a List a.\")\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (doc \"Print length(Nil) and length(Cons(7, Cons(8, Cons(9, Nil))))).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (term-ctor List Nil)))\n (do io/print_int (app length (term-ctor List Cons 7 (term-ctor List Cons 8 (term-ctor List Cons 9 (term-ctor List Nil))))))))))\n", + "usage": { + "completion_tokens": 227, + "prompt_tokens": 7507, + "total_tokens": 7734 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_5_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t2_length/turn_5_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t3_main_prints/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t3_main_prints/turn_1_check_stderr.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t3_main_prints/turn_1_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t3_main_prints/turn_1_program.ailx new file mode 100644 index 0000000..d0f2ede --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t3_main_prints/turn_1_program.ailx @@ -0,0 +1,6 @@ +(module t3_main_prints + (fn main + (doc "Print 42 then 1337, each on its own line.") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int 42) (do io/print_int 1337))))) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t3_main_prints/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t3_main_prints/turn_1_request.json new file mode 100644 index 0000000..ae106f6 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t3_main_prints/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t3_main_prints. It must export main : () -> () !IO that prints 42 and then 1337, each on its own line. No other definitions are required." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t3_main_prints/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t3_main_prints/turn_1_response.json new file mode 100644 index 0000000..d0c241d --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t3_main_prints/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t3_main_prints\n (fn main\n (doc \"Print 42 then 1337, each on its own line.\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 42) (do io/print_int 1337)))))", + "usage": { + "completion_tokens": 83, + "prompt_tokens": 6356, + "total_tokens": 6439 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t3_main_prints/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t3_main_prints/turn_1_run_stdout.txt new file mode 100644 index 0000000..f12daf8 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t3_main_prints/turn_1_run_stdout.txt @@ -0,0 +1,2 @@ +42 +1337 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_1_check_stderr.txt new file mode 100644 index 0000000..1415acc --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_1_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `)` (end of type-attr), got `(` at byte 242 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_1_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_1_program.ailx new file mode 100644 index 0000000..4c6d911 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_1_program.ailx @@ -0,0 +1,18 @@ +(module t4_count_zeros + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn count_zeros + (doc "Count zeros in an Int list using the prelude Eq Int instance.") + (type (fn-type (params (con List)) (ret (con Int))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t)))))) + (fn main + (doc "Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app count_zeros (ctor List Nil))) + (seq (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil)))))) (unit)))))) +) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_1_request.json new file mode 100644 index 0000000..e24ac63 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_1_response.json new file mode 100644 index 0000000..0420a58 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (doc \"Count zeros in an Int list using the prelude Eq Int instance.\")\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (doc \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (ctor List Nil)))\n (seq (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil)))))) (unit))))))\n)", + "usage": { + "completion_tokens": 276, + "prompt_tokens": 6436, + "total_tokens": 6712 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_1_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_2_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_2_check_stderr.txt new file mode 100644 index 0000000..9042d00 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_2_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error in term: unknown term head `ctor`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit` at byte 665 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_2_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_2_program.ailx new file mode 100644 index 0000000..da76f50 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_2_program.ailx @@ -0,0 +1,18 @@ +(module t4_count_zeros + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn count_zeros + (doc "Count zeros in an Int list using the prelude Eq Int instance.") + (type (fn-type (params (con List)) (ret (con Int)))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t)))))) + (fn main + (doc "Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app count_zeros (ctor List Nil))) + (seq (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil)))))) (unit)))))) +) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_2_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_2_request.json new file mode 100644 index 0000000..622becb --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_2_request.json @@ -0,0 +1,18 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (doc \"Count zeros in an Int list using the prelude Eq Int instance.\")\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (doc \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (ctor List Nil)))\n (seq (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil)))))) (unit))))))\n)" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_2_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_2_response.json new file mode 100644 index 0000000..b069b4b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_2_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (doc \"Count zeros in an Int list using the prelude Eq Int instance.\")\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (doc \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (ctor List Nil)))\n (seq (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil)))))) (unit))))))\n)", + "usage": { + "completion_tokens": 276, + "prompt_tokens": 6742, + "total_tokens": 7018 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_2_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_2_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_3_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_3_check_stderr.txt new file mode 100644 index 0000000..80bb9f3 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_3_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error in term: unknown term head `unit`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit` at byte 874 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_3_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_3_program.ailx new file mode 100644 index 0000000..ce3278b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_3_program.ailx @@ -0,0 +1,18 @@ +(module t4_count_zeros + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn count_zeros + (doc "Count zeros in an Int list using the prelude Eq Int instance.") + (type (fn-type (params (con List)) (ret (con Int)))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t)))))) + (fn main + (doc "Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app count_zeros (term-ctor List Nil))) + (seq (do io/print_int (app count_zeros (term-ctor List Cons 0 (term-ctor List Cons 5 (term-ctor List Cons 0 (term-ctor List Cons 3 (term-ctor List Cons 0 (term-ctor List Nil)))))) (unit)))))) +) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_3_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_3_request.json new file mode 100644 index 0000000..29afae3 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_3_request.json @@ -0,0 +1,26 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (doc \"Count zeros in an Int list using the prelude Eq Int instance.\")\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (doc \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (ctor List Nil)))\n (seq (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil)))))) (unit))))))\n)" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (doc \"Count zeros in an Int list using the prelude Eq Int instance.\")\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (doc \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (ctor List Nil)))\n (seq (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil)))))) (unit))))))\n)" + }, + { + "role": "user", + "content": "parse: parse error: parse error in term: unknown term head `ctor`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_3_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_3_response.json new file mode 100644 index 0000000..9a65a57 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_3_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (doc \"Count zeros in an Int list using the prelude Eq Int instance.\")\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (doc \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (term-ctor List Nil)))\n (seq (do io/print_int (app count_zeros (term-ctor List Cons 0 (term-ctor List Cons 5 (term-ctor List Cons 0 (term-ctor List Cons 3 (term-ctor List Cons 0 (term-ctor List Nil)))))) (unit))))))\n)", + "usage": { + "completion_tokens": 290, + "prompt_tokens": 7095, + "total_tokens": 7385 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_3_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_3_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_4_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_4_check_stderr.txt new file mode 100644 index 0000000..3482d65 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_4_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected term, got `)` at byte 886 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_4_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_4_program.ailx new file mode 100644 index 0000000..d1cb250 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_4_program.ailx @@ -0,0 +1,18 @@ +(module t4_count_zeros + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn count_zeros + (doc "Count zeros in an Int list using the prelude Eq Int instance.") + (type (fn-type (params (con List)) (ret (con Int)))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t)))))) + (fn main + (doc "Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app count_zeros (term-ctor List Nil))) + (seq (do io/print_int (app count_zeros (term-ctor List Cons 0 (term-ctor List Cons 5 (term-ctor List Cons 0 (term-ctor List Cons 3 (term-ctor List Cons 0 (term-ctor List Nil)))))) (lit-unit)))))) +) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_4_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_4_request.json new file mode 100644 index 0000000..df80b9b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_4_request.json @@ -0,0 +1,34 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (doc \"Count zeros in an Int list using the prelude Eq Int instance.\")\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (doc \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (ctor List Nil)))\n (seq (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil)))))) (unit))))))\n)" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (doc \"Count zeros in an Int list using the prelude Eq Int instance.\")\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (doc \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (ctor List Nil)))\n (seq (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil)))))) (unit))))))\n)" + }, + { + "role": "user", + "content": "parse: parse error: parse error in term: unknown term head `ctor`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`" + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (doc \"Count zeros in an Int list using the prelude Eq Int instance.\")\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (doc \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (term-ctor List Nil)))\n (seq (do io/print_int (app count_zeros (term-ctor List Cons 0 (term-ctor List Cons 5 (term-ctor List Cons 0 (term-ctor List Cons 3 (term-ctor List Cons 0 (term-ctor List Nil)))))) (unit))))))\n)" + }, + { + "role": "user", + "content": "parse: parse error: parse error in term: unknown term head `unit`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_4_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_4_response.json new file mode 100644 index 0000000..3b61011 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_4_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (doc \"Count zeros in an Int list using the prelude Eq Int instance.\")\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (doc \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (term-ctor List Nil)))\n (seq (do io/print_int (app count_zeros (term-ctor List Cons 0 (term-ctor List Cons 5 (term-ctor List Cons 0 (term-ctor List Cons 3 (term-ctor List Cons 0 (term-ctor List Nil)))))) (lit-unit))))))\n)", + "usage": { + "completion_tokens": 291, + "prompt_tokens": 7462, + "total_tokens": 7753 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_4_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_4_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_5_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_5_check_stderr.txt new file mode 100644 index 0000000..fc51874 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_5_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error in term: unknown term head `term-lit-unit`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit` at byte 874 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_5_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_5_program.ailx new file mode 100644 index 0000000..ed6207a --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_5_program.ailx @@ -0,0 +1,18 @@ +(module t4_count_zeros + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn count_zeros + (doc "Count zeros in an Int list using the prelude Eq Int instance.") + (type (fn-type (params (con List)) (ret (con Int)))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t)))))) + (fn main + (doc "Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app count_zeros (term-ctor List Nil))) + (seq (do io/print_int (app count_zeros (term-ctor List Cons 0 (term-ctor List Cons 5 (term-ctor List Cons 0 (term-ctor List Cons 3 (term-ctor List Cons 0 (term-ctor List Nil)))))) (term-lit-unit)))))) +) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_5_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_5_request.json new file mode 100644 index 0000000..5534c9f --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_5_request.json @@ -0,0 +1,42 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (doc \"Count zeros in an Int list using the prelude Eq Int instance.\")\n (type (fn-type (params (con List)) (ret (con Int)))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (doc \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (ctor List Nil)))\n (seq (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil)))))) (unit))))))\n)" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `)` (end of type-attr), got `(`" + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (doc \"Count zeros in an Int list using the prelude Eq Int instance.\")\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (doc \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (ctor List Nil)))\n (seq (do io/print_int (app count_zeros (ctor List Cons 0 (ctor List Cons 5 (ctor List Cons 0 (ctor List Cons 3 (ctor List Cons 0 (ctor List Nil)))))) (unit))))))\n)" + }, + { + "role": "user", + "content": "parse: parse error: parse error in term: unknown term head `ctor`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`" + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (doc \"Count zeros in an Int list using the prelude Eq Int instance.\")\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (doc \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (term-ctor List Nil)))\n (seq (do io/print_int (app count_zeros (term-ctor List Cons 0 (term-ctor List Cons 5 (term-ctor List Cons 0 (term-ctor List Cons 3 (term-ctor List Cons 0 (term-ctor List Nil)))))) (unit))))))\n)" + }, + { + "role": "user", + "content": "parse: parse error: parse error in term: unknown term head `unit`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`" + }, + { + "role": "assistant", + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (doc \"Count zeros in an Int list using the prelude Eq Int instance.\")\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (doc \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (term-ctor List Nil)))\n (seq (do io/print_int (app count_zeros (term-ctor List Cons 0 (term-ctor List Cons 5 (term-ctor List Cons 0 (term-ctor List Cons 3 (term-ctor List Cons 0 (term-ctor List Nil)))))) (lit-unit))))))\n)" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected term, got `)`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_5_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_5_response.json new file mode 100644 index 0000000..3bb0823 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_5_response.json @@ -0,0 +1,8 @@ +{ + "content": "(module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (doc \"Count zeros in an Int list using the prelude Eq Int instance.\")\n (type (fn-type (params (con List)) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (doc \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (term-ctor List Nil)))\n (seq (do io/print_int (app count_zeros (term-ctor List Cons 0 (term-ctor List Cons 5 (term-ctor List Cons 0 (term-ctor List Cons 3 (term-ctor List Cons 0 (term-ctor List Nil)))))) (term-lit-unit))))))\n)", + "usage": { + "completion_tokens": 293, + "prompt_tokens": 7776, + "total_tokens": 8069 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_5_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/ailx/t4_count_zeros/turn_5_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_1_check_stderr.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_1_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_1_program.ail.json new file mode 100644 index 0000000..e2f4fd7 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_1_program.ail.json @@ -0,0 +1,101 @@ +{ + "schema": "ailang/v0", + "name": "t1_add_three", + "imports": [], + "defs": [ + { + "kind": "fn", + "name": "add_three", + "doc": "Add three Ints.", + "type": { + "k": "fn", + "params": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" } + ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["a", "b", "c"], + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "var", "name": "a" }, + { "t": "var", "name": "b" } + ] + }, + { "t": "var", "name": "c" } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { "t": "lit", "lit": { "kind": "int", "value": 2 } }, + { "t": "lit", "lit": { "kind": "int", "value": 3 } } + ] + } + ] + }, + "rhs": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 10 } }, + { "t": "lit", "lit": { "kind": "int", "value": 20 } }, + { "t": "lit", "lit": { "kind": "int", "value": 30 } } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { "t": "lit", "lit": { "kind": "int", "value": 2 } }, + { "t": "lit", "lit": { "kind": "int", "value": 3 } } + ] + } + ] + } + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_1_request.json new file mode 100644 index 0000000..b1d186f --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_1_response.json new file mode 100644 index 0000000..58b1f60 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n }\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 842, + "prompt_tokens": 9435, + "total_tokens": 10277 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_1_run_stdout.txt new file mode 100644 index 0000000..65d5b7a --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_1_run_stdout.txt @@ -0,0 +1,3 @@ +6 +60 +6 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_2_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_2_check_stderr.txt new file mode 100644 index 0000000..aab4f67 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_2_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-tk0Cm9/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_2_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_2_program.ail.json new file mode 100644 index 0000000..7b99df2 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_2_program.ail.json @@ -0,0 +1,99 @@ +{ + "schema": "ailang/v0", + "name": "t1_add_three", + "imports": [], + "defs": [ + { + "kind": "fn", + "name": "add_three", + "doc": "Add three Ints.", + "type": { + "k": "fn", + "params": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" } + ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["a", "b", "c"], + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "var", "name": "a" }, + { "t": "var", "name": "b" } + ] + }, + { "t": "var", "name": "c" } + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { "t": "lit", "lit": { "kind": "int", "value": 2 } }, + { "t": "lit", "lit": { "kind": "int", "value": 3 } } + ] + } + } + }, + "rhs": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 10 } }, + { "t": "lit", "lit": { "kind": "int", "value": 20 } }, + { "t": "lit", "lit": { "kind": "int", "value": 30 } } + ] + } + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { "t": "lit", "lit": { "kind": "int", "value": 2 } }, + { "t": "lit": { "kind": "int", "value": 3 } } + ] + } + ] + } + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_2_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_2_request.json new file mode 100644 index 0000000..d226c97 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_2_request.json @@ -0,0 +1,18 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n }\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "output: expected \"6\\n60\\n\", got \"6\\n60\\n6\\n\"" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_2_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_2_response.json new file mode 100644 index 0000000..fb7cc5b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_2_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n }\n },\n \"rhs\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n }\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 835, + "prompt_tokens": 10306, + "total_tokens": 11141 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_2_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_2_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_3_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_3_check_stderr.txt new file mode 100644 index 0000000..a6e2ccd --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_3_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-3tQmlG/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_3_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_3_program.ail.json new file mode 100644 index 0000000..6f15266 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_3_program.ail.json @@ -0,0 +1,83 @@ +{ + "schema": "ailang/v0", + "name": "t1_add_three", + "imports": [], + "defs": [ + { + "kind": "fn", + "name": "add_three", + "doc": "Add three Ints.", + "type": { + "k": "fn", + "params": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" } + ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["a", "b", "c"], + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "var", "name": "a" }, + { "t": "var", "name": "b" } + ] + }, + { "t": "var", "name": "c" } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { "t": "lit", "lit": { "kind": "int", "value": 2 } }, + { "t": "lit", "lit": { "kind": "int", "value": 3 } } + ] + } + } + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 10 } }, + { "t": "lit", "lit": { "kind": "int", "value": 20 } }, + { "t": "lit", "lit": { "kind": "int", "value": 30 } } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_3_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_3_request.json new file mode 100644 index 0000000..7878592 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_3_request.json @@ -0,0 +1,26 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n }\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "output: expected \"6\\n60\\n\", got \"6\\n60\\n6\\n\"" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n }\n },\n \"rhs\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n }\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-tk0Cm9/prog.ail.json: expected `,` or `]` at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_3_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_3_response.json new file mode 100644 index 0000000..05c7214 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_3_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n }\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 686, + "prompt_tokens": 11181, + "total_tokens": 11867 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_3_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_3_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_4_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_4_check_stderr.txt new file mode 100644 index 0000000..b920418 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_4_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-GqvKXs/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_4_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_4_program.ail.json new file mode 100644 index 0000000..6f15266 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_4_program.ail.json @@ -0,0 +1,83 @@ +{ + "schema": "ailang/v0", + "name": "t1_add_three", + "imports": [], + "defs": [ + { + "kind": "fn", + "name": "add_three", + "doc": "Add three Ints.", + "type": { + "k": "fn", + "params": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" } + ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["a", "b", "c"], + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "var", "name": "a" }, + { "t": "var", "name": "b" } + ] + }, + { "t": "var", "name": "c" } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { "t": "lit", "lit": { "kind": "int", "value": 2 } }, + { "t": "lit", "lit": { "kind": "int", "value": 3 } } + ] + } + } + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 10 } }, + { "t": "lit", "lit": { "kind": "int", "value": 20 } }, + { "t": "lit", "lit": { "kind": "int", "value": 30 } } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_4_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_4_request.json new file mode 100644 index 0000000..6234f00 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_4_request.json @@ -0,0 +1,34 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n }\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "output: expected \"6\\n60\\n\", got \"6\\n60\\n6\\n\"" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n }\n },\n \"rhs\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n }\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-tk0Cm9/prog.ail.json: expected `,` or `]` at" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n }\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-3tQmlG/prog.ail.json: expected `,` or `]` at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_4_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_4_response.json new file mode 100644 index 0000000..cd54fd6 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_4_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n }\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 686, + "prompt_tokens": 11907, + "total_tokens": 12593 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_4_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_4_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_5_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_5_check_stderr.txt new file mode 100644 index 0000000..eba6b3f --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_5_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-eDa0Ey/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_5_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_5_program.ail.json new file mode 100644 index 0000000..6f15266 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_5_program.ail.json @@ -0,0 +1,83 @@ +{ + "schema": "ailang/v0", + "name": "t1_add_three", + "imports": [], + "defs": [ + { + "kind": "fn", + "name": "add_three", + "doc": "Add three Ints.", + "type": { + "k": "fn", + "params": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" } + ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["a", "b", "c"], + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "var", "name": "a" }, + { "t": "var", "name": "b" } + ] + }, + { "t": "var", "name": "c" } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { "t": "lit", "lit": { "kind": "int", "value": 2 } }, + { "t": "lit", "lit": { "kind": "int", "value": 3 } } + ] + } + } + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 10 } }, + { "t": "lit", "lit": { "kind": "int", "value": 20 } }, + { "t": "lit", "lit": { "kind": "int", "value": 30 } } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_5_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_5_request.json new file mode 100644 index 0000000..077c2f6 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_5_request.json @@ -0,0 +1,42 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n }\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "output: expected \"6\\n60\\n\", got \"6\\n60\\n6\\n\"" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n }\n },\n \"rhs\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n }\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-tk0Cm9/prog.ail.json: expected `,` or `]` at" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n }\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-3tQmlG/prog.ail.json: expected `,` or `]` at" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n }\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-GqvKXs/prog.ail.json: expected `,` or `]` at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_5_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_5_response.json new file mode 100644 index 0000000..bb0f122 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_5_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"a\", \"b\", \"c\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"a\" },\n { \"t\": \"var\", \"name\": \"b\" }\n ]\n },\n { \"t\": \"var\", \"name\": \"c\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30) on separate lines.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n }\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 686, + "prompt_tokens": 12632, + "total_tokens": 13318 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_5_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t1_add_three/turn_5_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_1_check_stderr.txt new file mode 100644 index 0000000..74a7c45 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_1_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-33djaP/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_1_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_1_program.ail.json new file mode 100644 index 0000000..51b8e7f --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_1_program.ail.json @@ -0,0 +1,145 @@ +{ + "schema": "ailang/v0", + "name": "t2_length", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "A simple singly-linked list type.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "var", "name": "a" }, + { "k": "var", "name": "List" } + ] + } + ] + }, + { + "kind": "fn", + "name": "length", + "doc": "Returns the number of elements in the list.", + "type": { + "k": "fn", + "params": [ + { + "k": "con", + "name": "List", + "args": [ + { + "k": "var", + "name": "a" + } + } + } + ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "xs" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "wild" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + ], + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { "t": "var", "name": "t" } + ] + } + ] + } + } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Prints the length of Nil and then the length of [7,8,9].", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { "t": "var", "name": "Nil" } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 7 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 8 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 9 } }, + { "t": "var", "name": "Nil" } + ] + } + ] + } + ] + } + } + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_1_request.json new file mode 100644 index 0000000..9168099 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_1_response.json new file mode 100644 index 0000000..8d61ff1 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"var\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n {\n \"k\": \"con\",\n \"name\": \"List\",\n \"args\": [\n {\n \"k\": \"var\",\n \"name\": \"a\"\n }\n }\n }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n ],\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints the length of Nil and then the length of [7,8,9].\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 970, + "prompt_tokens": 9459, + "total_tokens": 10429 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_1_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_2_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_2_check_stderr.txt new file mode 100644 index 0000000..cf7f331 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_2_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-hXpYnT/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_2_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_2_program.ail.json new file mode 100644 index 0000000..2aadbf0 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_2_program.ail.json @@ -0,0 +1,145 @@ +{ + "schema": "ailang/v0", + "name": "t2_length", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "A simple singly-linked list type.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "var", "name": "a" }, + { "k": "con", "name": "List", "args": [{ "k": "var", "name": "a" }] } + ] + } + ] + }, + { + "kind": "fn", + "name": "length", + "doc": "Returns the number of elements in the list.", + "type": { + "k": "fn", + "params": [ + { + "k": "con", + "name": "List", + "args": [ + { + "k": "var", + "name": "a" + } + } + } + ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "xs" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "wild" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { "t": "var", "name": "t" } + ] + } + ] + } + } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Prints the length of Nil and then the length of [7,8,9].", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { "t": "var", "name": "Nil" } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 7 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 8 } }, + ] + }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 9 } }, + ] + }, + { "t": "var", "name": "Nil" } + ] + } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_2_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_2_request.json new file mode 100644 index 0000000..6dd12bb --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_2_request.json @@ -0,0 +1,18 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"var\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n {\n \"k\": \"con\",\n \"name\": \"List\",\n \"args\": [\n {\n \"k\": \"var\",\n \"name\": \"a\"\n }\n }\n }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n ],\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints the length of Nil and then the length of [7,8,9].\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-33djaP/prog.ail.json: expected `,` or `]` at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_2_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_2_response.json new file mode 100644 index 0000000..091c392 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_2_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [{ \"k\": \"var\", \"name\": \"a\" }] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n {\n \"k\": \"con\",\n \"name\": \"List\",\n \"args\": [\n {\n \"k\": \"var\",\n \"name\": \"a\"\n }\n }\n }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints the length of Nil and then the length of [7,8,9].\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n ]\n },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 984, + "prompt_tokens": 10469, + "total_tokens": 11453 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_2_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_2_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_3_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_3_check_stderr.txt new file mode 100644 index 0000000..5b35048 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_3_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-FYnW4m/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_3_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_3_program.ail.json new file mode 100644 index 0000000..45d5487 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_3_program.ail.json @@ -0,0 +1,141 @@ +{ + "schema": "ailang/v0", + "name": "t2_length", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "A simple singly-linked list type.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "var", "name": "a" }, + { "k": "con", "name": "List", "args": [{ "k": "var", "name": "a" }] } + ] + } + ] + }, + { + "kind": "fn", + "name": "length", + "doc": "Returns the number of elements in the list.", + "type": { + "k": "fn", + "params": [ + { + "k": "con", + "name": "List", + "args": [ + { + "k": "var", + "name": "a" + } + } + } + ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "xs" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "wild" }, + { "p": "var", "name": "t" } + } + }, + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { "t": "var", "name": "t" } + } + } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Prints the length of Nil and then the length of [7,8,9].", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { "t": "var", "name": "Nil" } + } + } + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 7 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 8 } }, + ] + }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 9 } }, + ] + }, + { "t": "var", "name": "Nil" } + ] + } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_3_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_3_request.json new file mode 100644 index 0000000..ec0cb77 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_3_request.json @@ -0,0 +1,26 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"var\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n {\n \"k\": \"con\",\n \"name\": \"List\",\n \"args\": [\n {\n \"k\": \"var\",\n \"name\": \"a\"\n }\n }\n }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n ],\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints the length of Nil and then the length of [7,8,9].\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-33djaP/prog.ail.json: expected `,` or `]` at" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [{ \"k\": \"var\", \"name\": \"a\" }] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n {\n \"k\": \"con\",\n \"name\": \"List\",\n \"args\": [\n {\n \"k\": \"var\",\n \"name\": \"a\"\n }\n }\n }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints the length of Nil and then the length of [7,8,9].\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n ]\n },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-hXpYnT/prog.ail.json: expected `,` or `]` at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_3_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_3_response.json new file mode 100644 index 0000000..08b9c94 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_3_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [{ \"k\": \"var\", \"name\": \"a\" }] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n {\n \"k\": \"con\",\n \"name\": \"List\",\n \"args\": [\n {\n \"k\": \"var\",\n \"name\": \"a\"\n }\n }\n }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n }\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"t\" }\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints the length of Nil and then the length of [7,8,9].\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"Nil\" }\n }\n }\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n ]\n },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 976, + "prompt_tokens": 11493, + "total_tokens": 12469 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_3_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_3_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_4_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_4_check_stderr.txt new file mode 100644 index 0000000..10f8e12 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_4_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-tdKyTv/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_4_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_4_program.ail.json new file mode 100644 index 0000000..6fd5d27 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_4_program.ail.json @@ -0,0 +1,144 @@ +{ + "schema": "ailang/v0", + "name": "t2_length", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "A simple singly-linked list type.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "var", "name": "a" }, + { "k": "con", "name": "List", "args": [{ "k": "var", "name": "a" }] } + ] + } + ] + }, + { + "kind": "fn", + "name": "length", + "doc": "Returns the number of elements in the list.", + "type": { + "k": "fn", + "params": [ + { + "k": "con", + "name": "List", + "args": [ + { + "k": "var", + "name": "a" + } + } + } + ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "xs" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "wild" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { "t": "var", "name": "t" } + ] + } + ] + } + } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Prints the length of Nil and then the length of [7,8,9].", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { "t": "var", "name": "Nil" } + ] + } + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 7 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 8 } }, + ] + }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 9 } }, + ] + }, + { "t": "var", "name": "Nil" } + ] + } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_4_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_4_request.json new file mode 100644 index 0000000..18f25e1 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_4_request.json @@ -0,0 +1,34 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"var\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n {\n \"k\": \"con\",\n \"name\": \"List\",\n \"args\": [\n {\n \"k\": \"var\",\n \"name\": \"a\"\n }\n }\n }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n ],\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints the length of Nil and then the length of [7,8,9].\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-33djaP/prog.ail.json: expected `,` or `]` at" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [{ \"k\": \"var\", \"name\": \"a\" }] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n {\n \"k\": \"con\",\n \"name\": \"List\",\n \"args\": [\n {\n \"k\": \"var\",\n \"name\": \"a\"\n }\n }\n }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints the length of Nil and then the length of [7,8,9].\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n ]\n },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-hXpYnT/prog.ail.json: expected `,` or `]` at" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [{ \"k\": \"var\", \"name\": \"a\" }] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n {\n \"k\": \"con\",\n \"name\": \"List\",\n \"args\": [\n {\n \"k\": \"var\",\n \"name\": \"a\"\n }\n }\n }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n }\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"t\" }\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints the length of Nil and then the length of [7,8,9].\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"Nil\" }\n }\n }\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n ]\n },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-FYnW4m/prog.ail.json: expected `,` or `]` at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_4_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_4_response.json new file mode 100644 index 0000000..9b4b66f --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_4_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [{ \"k\": \"var\", \"name\": \"a\" }] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n {\n \"k\": \"con\",\n \"name\": \"List\",\n \"args\": [\n {\n \"k\": \"var\",\n \"name\": \"a\"\n }\n }\n }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints the length of Nil and then the length of [7,8,9].\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n ]\n },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 982, + "prompt_tokens": 12509, + "total_tokens": 13491 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_4_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_4_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_5_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_5_check_stderr.txt new file mode 100644 index 0000000..c4d0193 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_5_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-aAV3p2/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_5_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_5_program.ail.json new file mode 100644 index 0000000..781ccbc --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_5_program.ail.json @@ -0,0 +1,135 @@ +{ + "schema": "ailang/v0", + "name": "t2_length", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "A simple singly-linked list type.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "var", "name": "a" }, + { "k": "con", "name": "List", "args": [{ "k": "var", "name": "a" }] } + ] + } + ] + }, + { + "kind": "fn", + "name": "length", + "doc": "Returns the number of elements in the list.", + "type": { + "k": "fn", + "params": [ + { + "k": "con", + "name": "List", + "args": [ + { + "k": "var", + "name": "a" + } + } + } + ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "xs" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "wild" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { "t": "var", "name": "t" } + ] + } + ] + } + } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Prints the length of Nil and then the length of [7,8,9].", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { "t": "var", "name": "Nil" } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "length" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 7 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 8 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 9 } }, + { "t": "var", "name": "Nil" } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_5_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_5_request.json new file mode 100644 index 0000000..a81ace4 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_5_request.json @@ -0,0 +1,42 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"var\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n {\n \"k\": \"con\",\n \"name\": \"List\",\n \"args\": [\n {\n \"k\": \"var\",\n \"name\": \"a\"\n }\n }\n }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n ],\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints the length of Nil and then the length of [7,8,9].\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-33djaP/prog.ail.json: expected `,` or `]` at" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [{ \"k\": \"var\", \"name\": \"a\" }] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n {\n \"k\": \"con\",\n \"name\": \"List\",\n \"args\": [\n {\n \"k\": \"var\",\n \"name\": \"a\"\n }\n }\n }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints the length of Nil and then the length of [7,8,9].\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n ]\n },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-hXpYnT/prog.ail.json: expected `,` or `]` at" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [{ \"k\": \"var\", \"name\": \"a\" }] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n {\n \"k\": \"con\",\n \"name\": \"List\",\n \"args\": [\n {\n \"k\": \"var\",\n \"name\": \"a\"\n }\n }\n }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n }\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"t\" }\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints the length of Nil and then the length of [7,8,9].\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"Nil\" }\n }\n }\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n ]\n },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-FYnW4m/prog.ail.json: expected `,` or `]` at" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [{ \"k\": \"var\", \"name\": \"a\" }] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n {\n \"k\": \"con\",\n \"name\": \"List\",\n \"args\": [\n {\n \"k\": \"var\",\n \"name\": \"a\"\n }\n }\n }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints the length of Nil and then the length of [7,8,9].\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n ]\n },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-tdKyTv/prog.ail.json: expected `,` or `]` at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_5_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_5_response.json new file mode 100644 index 0000000..74dded9 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_5_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t2_length\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"A simple singly-linked list type.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"var\", \"name\": \"a\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [{ \"k\": \"var\", \"name\": \"a\" }] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"length\",\n \"doc\": \"Returns the number of elements in the list.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n {\n \"k\": \"con\",\n \"name\": \"List\",\n \"args\": [\n {\n \"k\": \"var\",\n \"name\": \"a\"\n }\n }\n }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"t\" }\n ]\n }\n ]\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Prints the length of Nil and then the length of [7,8,9].\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"length\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 7 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 8 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 9 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n ]\n}", + "usage": { + "completion_tokens": 964, + "prompt_tokens": 13529, + "total_tokens": 14493 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_5_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t2_length/turn_5_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t3_main_prints/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t3_main_prints/turn_1_check_stderr.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t3_main_prints/turn_1_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t3_main_prints/turn_1_program.ail.json new file mode 100644 index 0000000..6c626de --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t3_main_prints/turn_1_program.ail.json @@ -0,0 +1,32 @@ +{ + "schema": "ailang/v0", + "name": "t3_main_prints", + "imports": [], + "defs": [ + { + "kind": "fn", + "name": "main", + "doc": "Print 42 and 1337, each on its own line.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ { "t": "lit", "lit": { "kind": "int", "value": 42 } } ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ { "t": "lit", "lit": { "kind": "int", "value": 1337 } } ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t3_main_prints/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t3_main_prints/turn_1_request.json new file mode 100644 index 0000000..a892330 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t3_main_prints/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t3_main_prints. It must export main : () -> () !IO that prints 42 and then 1337, each on its own line. No other definitions are required." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t3_main_prints/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t3_main_prints/turn_1_response.json new file mode 100644 index 0000000..aa56c14 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t3_main_prints/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t3_main_prints\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print 42 and 1337, each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1337 } } ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 249, + "prompt_tokens": 9406, + "total_tokens": 9655 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t3_main_prints/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t3_main_prints/turn_1_run_stdout.txt new file mode 100644 index 0000000..f12daf8 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t3_main_prints/turn_1_run_stdout.txt @@ -0,0 +1,2 @@ +42 +1337 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_1_check_stderr.txt new file mode 100644 index 0000000..25c3bff --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_1_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-8FWVGm/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_1_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_1_program.ail.json new file mode 100644 index 0000000..c43e926 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_1_program.ail.json @@ -0,0 +1,156 @@ +{ + "schema": "ailang/v0", + "name": "t4_count_zeros", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "Monomorphic Int list for this task.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "List", "args": [ { "k": "var", "name": "a" } ] } + ] + } + ] + }, + { + "kind": "fn", + "name": "count_zeros", + "doc": "Count how many elements equal 0 using the prelude Eq Int instance.", + "type": { + "k": "fn", + "params": [ { "k": "con", "name": "List", "args": [ { "k": "con", "name": "Int" } ] } ], + "param_modes": ["borrow"], + "ret": { "k": "con", "name": "Int" }, + "ret_mode": "own", + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "xs" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "var", "name": "h" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "if", + "cond": { + "t": "app", + "fn": { "t": "var", "name": "==" }, + "args": [ + { "t": "var", "name": "h" }, + { "t": "lit", "lit": { "kind": "int", "value": 0 } } + ] + }, + "then": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ { "t": "var", "name": "t" } ] + } + ] + }, + "else": { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ { "t": "var", "name": "t" } ] + } + } + } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ { "t": "var", "name": "Nil" } ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 5 } }, + ] + } + ] + }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 3 } }, + ] + }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + ] + }, + { "t": "var", "name": "Nil" } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_1_request.json new file mode 100644 index 0000000..b0efd69 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_1_response.json new file mode 100644 index 0000000..560521a --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count how many elements equal 0 using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"Nil\" } ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n ]\n }\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n ]\n },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 1227, + "prompt_tokens": 9486, + "total_tokens": 10713 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_1_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_2_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_2_check_stderr.txt new file mode 100644 index 0000000..1fa42be --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_2_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-FaAm0B/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_2_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_2_program.ail.json new file mode 100644 index 0000000..8a9c2d6 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_2_program.ail.json @@ -0,0 +1,155 @@ +{ + "schema": "ailang/v0", + "name": "t4_count_zeros", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "Monomorphic Int list for this task.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "List", "args": [ { "k": "var", "name": "a" } ] } + ] + } + ] + }, + { + "kind": "fn", + "name": "count_zeros", + "doc": "Count how many elements equal 0 using the prelude Eq Int instance.", + "type": { + "k": "fn", + "params": [ { "k": "con", "name": "List", "args": [ { "k": "con", "name": "Int" } ] }, + "param_modes": ["borrow"], + "ret": { "k": "con", "name": "Int" }, + "ret_mode": "own", + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "xs" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "var", "name": "h" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "if", + "cond": { + "t": "app", + "fn": { "t": "var", "name": "==" }, + "args": [ + { "t": "var", "name": "h" }, + { "t": "lit", "lit": { "kind": "int", "value": 0 } } + ] + }, + "then": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ { "t": "var", "name": "t" } ] + } + ] + }, + "else": { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ { "t": "var", "name": "t" } ] + } + } + } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ { "t": "var", "name": "Nil" } ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 5 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 3 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { "t": "var", "name": "Nil" } + ] + } + ] + } + } + ] + } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_2_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_2_request.json new file mode 100644 index 0000000..8ae3526 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_2_request.json @@ -0,0 +1,18 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count how many elements equal 0 using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"Nil\" } ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n ]\n }\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n ]\n },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-8FWVGm/prog.ail.json: trailing comma at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_2_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_2_response.json new file mode 100644 index 0000000..98663b8 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_2_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count how many elements equal 0 using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"Nil\" } ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 1224, + "prompt_tokens": 10747, + "total_tokens": 11971 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_2_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_2_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_3_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_3_check_stderr.txt new file mode 100644 index 0000000..7a4c41c --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_3_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-bFKJyB/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_3_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_3_program.ail.json new file mode 100644 index 0000000..1157f00 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_3_program.ail.json @@ -0,0 +1,158 @@ +{ + "schema": "ailang/v0", + "name": "t4_count_zeros", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "Monomorphic Int list for this task.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "List", "args": [ { "k": "var", "name": "a" } ] } + ] + } + ] + }, + { + "kind": "fn", + "name": "count_zeros", + "doc": "Count how many elements equal 0 using the prelude Eq Int instance.", + "type": { + "k": "fn", + "params": [ + { "k": "con", "name": "List", "args": [ { "k": "con", "name": "Int" } ] } + ], + "param_modes": ["borrow"], + "ret": { "k": "con", "name": "Int" }, + "ret_mode": "own", + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "xs" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "var", "name": "h" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "if", + "cond": { + "t": "app", + "fn": { "t": "var", "name": "==" }, + "args": [ + { "t": "var", "name": "h" }, + { "t": "lit", "lit": { "kind": "int", "value": 0 } } + ] + }, + "then": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ { "t": "var", "name": "t" } ] + } + ] + }, + "else": { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ { "t": "var", "name": "t" } ] + } + } + } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ { "t": "var", "name": "Nil" } ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 5 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 3 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { "t": "var", "name": "Nil" } + ] + } + ] + } + ] + } + } + } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_3_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_3_request.json new file mode 100644 index 0000000..eb4495d --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_3_request.json @@ -0,0 +1,26 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count how many elements equal 0 using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"Nil\" } ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n ]\n }\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n ]\n },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-8FWVGm/prog.ail.json: trailing comma at" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count how many elements equal 0 using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"Nil\" } ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-FaAm0B/prog.ail.json: expected `,` or `]` at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_3_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_3_response.json new file mode 100644 index 0000000..2483407 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_3_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count how many elements equal 0 using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"Nil\" } ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 1229, + "prompt_tokens": 12010, + "total_tokens": 13239 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_3_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_3_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_4_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_4_check_stderr.txt new file mode 100644 index 0000000..9f88339 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_4_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-J6zZaw/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_4_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_4_program.ail.json new file mode 100644 index 0000000..8346db4 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_4_program.ail.json @@ -0,0 +1,154 @@ +{ + "schema": "ailang/v0", + "name": "t4_count_zeros", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "Monomorphic Int list for this task.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "List", "args": [ { "k": "var", "name": "a" } ] } + ] + } + ] + }, + { + "kind": "fn", + "name": "count_zeros", + "doc": "Count how many elements equal 0 using the prelude Eq Int instance.", + "type": { + "k": "fn", + "params": [ + { "k": "con", "name": "List", "args": [ { "k": "con", "name": "Int" } ] + ], + "param_modes": ["borrow"], + "ret": { "k": "con", "name": "Int" }, + "ret_mode": "own", + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "xs" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "var", "name": "h" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "if", + "cond": { + "t": "app", + "fn": { "t": "var", "name": "==" }, + "args": [ + { "t": "var", "name": "h" }, + { "t": "lit", "lit": { "kind": "int", "value": 0 } + } + }, + "then": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ { "t": "var", "name": "t" } + } + ] + }, + "else": { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ { "t": "var", "name": "t" } + } + } + } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ { "t": "var", "name": "Nil" } + } + } + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 5 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 3 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { "t": "var", "name": "Nil" } + ] + } + } + } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_4_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_4_request.json new file mode 100644 index 0000000..ca65e2f --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_4_request.json @@ -0,0 +1,34 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count how many elements equal 0 using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"Nil\" } ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n ]\n }\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n ]\n },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-8FWVGm/prog.ail.json: trailing comma at" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count how many elements equal 0 using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"Nil\" } ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-FaAm0B/prog.ail.json: expected `,` or `]` at" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count how many elements equal 0 using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"Nil\" } ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-bFKJyB/prog.ail.json: expected `,` or `]` at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_4_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_4_response.json new file mode 100644 index 0000000..33e43b5 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_4_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count how many elements equal 0 using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ]\n ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 }\n }\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" }\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" }\n }\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"Nil\" }\n }\n }\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 1215, + "prompt_tokens": 13278, + "total_tokens": 14493 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_4_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_4_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_5_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_5_check_stderr.txt new file mode 100644 index 0000000..b2e4f25 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_5_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-YHI42X/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_5_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_5_program.ail.json new file mode 100644 index 0000000..1e6ac7b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_5_program.ail.json @@ -0,0 +1,155 @@ +{ + "schema": "ailang/v0", + "name": "t4_count_zeros", + "imports": [], + "defs": [ + { + "kind": "type", + "name": "List", + "vars": ["a"], + "doc": "Monomorphic Int list for this task.", + "ctors": [ + { "name": "Nil", "fields": [] }, + { + "name": "Cons", + "fields": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "List", "args": [ { "k": "var", "name": "a" } ] } + ] + } + ] + }, + { + "kind": "fn", + "name": "count_zeros", + "doc": "Count how many elements equal 0 using the prelude Eq Int instance.", + "type": { + "k": "fn", + "params": [ + { "k": "con", "name": "List", "args": [ { "k": "con", "name": "Int" } ] + ], + "param_modes": ["borrow"], + "ret": { "k": "con", "name": "Int" }, + "ret_mode": "own", + "effects": [] + }, + "params": ["xs"], + "body": { + "t": "match", + "scrutinee": { "t": "var", "name": "xs" }, + "arms": [ + { + "pat": { "p": "ctor", "ctor": "Nil", "fields": [] }, + "body": { "t": "lit", "lit": { "kind": "int", "value": 0 } + }, + { + "pat": { + "p": "ctor", + "ctor": "Cons", + "fields": [ + { "p": "var", "name": "h" }, + { "p": "var", "name": "t" } + ] + }, + "body": { + "t": "if", + "cond": { + "t": "app", + "fn": { "t": "var", "name": "==" }, + "args": [ + { "t": "var", "name": "h" }, + { "t": "lit", "lit": { "kind": "int", "value": 0 } + } + }, + "then": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ { "t": "var", "name": "t" } + } + ] + }, + "else": { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ { "t": "var", "name": "t" } + } + } + } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ { "t": "var", "name": "Nil" } + } + } + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "count_zeros" }, + "args": [ + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 5 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 3 } }, + { + "t": "ctor", + "type": "List", + "ctor": "Cons", + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 0 } }, + { "t": "var", "name": "Nil" } + ] + } + } + } + } + ] + } + ] + } + } + } + ] +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_5_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_5_request.json new file mode 100644 index 0000000..f3e0e9a --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_5_request.json @@ -0,0 +1,42 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count how many elements equal 0 using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"Nil\" } ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n ]\n }\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n ]\n },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n ]\n },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-8FWVGm/prog.ail.json: trailing comma at" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count how many elements equal 0 using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"Nil\" } ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-FaAm0B/prog.ail.json: expected `,` or `]` at" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count how many elements equal 0 using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"Nil\" } ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n ]\n }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-bFKJyB/prog.ail.json: expected `,` or `]` at" + }, + { + "role": "assistant", + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count how many elements equal 0 using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ]\n ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 }\n }\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" }\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" }\n }\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"Nil\" }\n }\n }\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-J6zZaw/prog.ail.json: expected `,` or `}` at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_5_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_5_response.json new file mode 100644 index 0000000..2d758f6 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_5_response.json @@ -0,0 +1,8 @@ +{ + "content": "{\n \"schema\": \"ailang/v0\",\n \"name\": \"t4_count_zeros\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"vars\": [\"a\"],\n \"doc\": \"Monomorphic Int list for this task.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"var\", \"name\": \"a\" } ] }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_zeros\",\n \"doc\": \"Count how many elements equal 0 using the prelude Eq Int instance.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"List\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ]\n ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"h\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 }\n }\n },\n \"then\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" }\n }\n ]\n },\n \"else\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" }\n }\n }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print count_zeros(Nil) and count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))) each on its own line.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"Nil\" }\n }\n }\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"count_zeros\" },\n \"args\": [\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 5 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } },\n {\n \"t\": \"ctor\",\n \"type\": \"List\",\n \"ctor\": \"Cons\",\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n { \"t\": \"var\", \"name\": \"Nil\" }\n ]\n }\n }\n }\n }\n ]\n }\n ]\n }\n }\n }\n ]\n}", + "usage": { + "completion_tokens": 1217, + "prompt_tokens": 14531, + "total_tokens": 15748 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_5_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/per_cohort/json/t4_count_zeros/turn_5_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/scores.csv b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/scores.csv new file mode 100644 index 0000000..7c079e5 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/scores.csv @@ -0,0 +1,9 @@ +cohort,task_id,first_attempt_green,turns_to_green,prompt_tokens,completion_tokens,error_classes,final_status +json,t1_add_three,false,INF,55461,3735,check;output,turn_limit +json,t2_length,false,INF,57459,4876,check,turn_limit +json,t3_main_prints,true,1,9406,249,,green +json,t4_count_zeros,false,INF,60052,6112,check,turn_limit +ailx,t1_add_three,false,INF,33929,850,parse,turn_limit +ailx,t2_length,false,INF,34779,1115,check;parse,turn_limit +ailx,t3_main_prints,true,1,6356,83,,green +ailx,t4_count_zeros,false,INF,35511,1426,parse,turn_limit diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/summary.md b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/summary.md new file mode 100644 index 0000000..47d8e41 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-080864/summary.md @@ -0,0 +1,22 @@ +# Cross-model authoring-form test — run summary + +## Cohort: json + +- tasks: 4 +- reached green: 1 +- first-attempt green: 1 +- mean turns-to-green (green-only): 1.00 +- total prompt tokens: 182378 +- total completion tokens: 14972 +- most common error class: check (x3) + +## Cohort: ailx + +- tasks: 4 +- reached green: 1 +- first-attempt green: 1 +- mean turns-to-green (green-only): 1.00 +- total prompt tokens: 110575 +- total completion tokens: 3474 +- most common error class: parse (x3) + diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/RUN_STATUS b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/RUN_STATUS new file mode 100644 index 0000000..b5754e2 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/RUN_STATUS @@ -0,0 +1 @@ +ok \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t1_add_three/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t1_add_three/turn_1_check_stderr.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t1_add_three/turn_1_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t1_add_three/turn_1_program.ailx new file mode 100644 index 0000000..81c7060 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t1_add_three/turn_1_program.ailx @@ -0,0 +1,12 @@ + (module t1_add_three + (fn add_three + (doc "Add three Ints.") + (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int)))) + (params x y z) + (body (app + x (app + y z)))) + (fn main + (doc "Print the sum of three Ints.") + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30))))) +) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t1_add_three/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t1_add_three/turn_1_request.json new file mode 100644 index 0000000..a7db989 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t1_add_three/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t1_add_three/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t1_add_three/turn_1_response.json new file mode 100644 index 0000000..9eb4888 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t1_add_three/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": " (module t1_add_three\n (fn add_three\n (doc \"Add three Ints.\")\n (type (fn-type (params (con Int) (con Int) (con Int)) (ret (con Int))))\n (params x y z)\n (body (app + x (app + y z))))\n (fn main\n (doc \"Print the sum of three Ints.\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app add_three 1 2 3)) (do io/print_int (app add_three 10 20 30)))))\n)", + "usage": { + "completion_tokens": 172, + "prompt_tokens": 7296, + "total_tokens": 7468 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t1_add_three/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t1_add_three/turn_1_run_stdout.txt new file mode 100644 index 0000000..f7ac172 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t1_add_three/turn_1_run_stdout.txt @@ -0,0 +1,2 @@ +6 +60 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_1_check_stderr.txt new file mode 100644 index 0000000..99a74f6 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_1_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `vars`, got ident `a` at byte 119 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_1_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_1_program.ailx new file mode 100644 index 0000000..d001dc5 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_1_program.ailx @@ -0,0 +1,15 @@ + (module t2_length + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn length + (type (forall (a) (fn-type (params (con List)) (ret (con Int))))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons _ t) (app + 1 (app length t)))))) + (fn main + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app length (ctor Nil))) + (do io/print_int (app length (ctor Cons 7 (ctor Cons 8 (ctor Cons 9 (ctor Nil))))))))) diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_1_request.json new file mode 100644 index 0000000..1fc80d3 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_1_response.json new file mode 100644 index 0000000..75fbccd --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": " (module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (a) (fn-type (params (con List)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor Nil)))\n (do io/print_int (app length (ctor Cons 7 (ctor Cons 8 (ctor Cons 9 (ctor Nil)))))))))\n", + "usage": { + "completion_tokens": 199, + "prompt_tokens": 7317, + "total_tokens": 7516 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_1_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_2_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_2_check_stderr.txt new file mode 100644 index 0000000..e0c0b11 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_2_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `(` (start of module), got ident `The` at byte 2 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_2_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_2_program.ailx new file mode 100644 index 0000000..8114175 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_2_program.ailx @@ -0,0 +1,21 @@ + The error message "expected `vars`, got ident `a`" suggests that the parser is expecting a `vars` keyword, but it found an identifier `a` instead. + +The `vars` keyword is used to declare a list of variables in a `data` declaration. For example: +``` +data MyData = MyData { + a :: Int, + b :: String +} +``` +In this example, `a` and `b` are declared as variables in the `MyData` data type. + +It's possible that the parser is confused by the `a` identifier in the `data` declaration, and is expecting a `vars` keyword instead. + +To fix this error, you can try adding a `vars` keyword before the `a` identifier, like this: +``` +data MyData = MyData { + vars a :: Int, + b :: String +} +``` +This should tell the parser that `a` is a variable in the `MyData` data type, and it should be able to parse the rest of the code correctly. \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_2_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_2_request.json new file mode 100644 index 0000000..de8997a --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_2_request.json @@ -0,0 +1,18 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": " (module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (a) (fn-type (params (con List)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor Nil)))\n (do io/print_int (app length (ctor Cons 7 (ctor Cons 8 (ctor Cons 9 (ctor Nil)))))))))\n" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `vars`, got ident `a`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_2_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_2_response.json new file mode 100644 index 0000000..59daae5 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_2_response.json @@ -0,0 +1,8 @@ +{ + "content": " The error message \"expected `vars`, got ident `a`\" suggests that the parser is expecting a `vars` keyword, but it found an identifier `a` instead.\n\nThe `vars` keyword is used to declare a list of variables in a `data` declaration. For example:\n```\ndata MyData = MyData {\n a :: Int,\n b :: String\n}\n```\nIn this example, `a` and `b` are declared as variables in the `MyData` data type.\n\nIt's possible that the parser is confused by the `a` identifier in the `data` declaration, and is expecting a `vars` keyword instead.\n\nTo fix this error, you can try adding a `vars` keyword before the `a` identifier, like this:\n```\ndata MyData = MyData {\n vars a :: Int,\n b :: String\n}\n```\nThis should tell the parser that `a` is a variable in the `MyData` data type, and it should be able to parse the rest of the code correctly.", + "usage": { + "completion_tokens": 235, + "prompt_tokens": 7540, + "total_tokens": 7775 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_2_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_2_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_3_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_3_check_stderr.txt new file mode 100644 index 0000000..e0c0b11 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_3_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `(` (start of module), got ident `The` at byte 2 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_3_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_3_program.ailx new file mode 100644 index 0000000..5ea2acb --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_3_program.ailx @@ -0,0 +1,9 @@ + The error message "expected `(` (start of module), got ident `The`" suggests that the parser is expecting the start of a module, but it found the identifier `The` instead. + +The `The` identifier is not a valid start of a module, and the parser is expecting a `(` character to indicate the start of a module. + +To fix this error, you can try adding a `(` character before the `The` identifier, like this: +``` +(The) +``` +This should tell the parser that the `The` identifier is the start of a module, and it should be able to parse the rest of the code correctly. \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_3_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_3_request.json new file mode 100644 index 0000000..9c74a6d --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_3_request.json @@ -0,0 +1,26 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": " (module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (a) (fn-type (params (con List)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor Nil)))\n (do io/print_int (app length (ctor Cons 7 (ctor Cons 8 (ctor Cons 9 (ctor Nil)))))))))\n" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `vars`, got ident `a`" + }, + { + "role": "assistant", + "content": " The error message \"expected `vars`, got ident `a`\" suggests that the parser is expecting a `vars` keyword, but it found an identifier `a` instead.\n\nThe `vars` keyword is used to declare a list of variables in a `data` declaration. For example:\n```\ndata MyData = MyData {\n a :: Int,\n b :: String\n}\n```\nIn this example, `a` and `b` are declared as variables in the `MyData` data type.\n\nIt's possible that the parser is confused by the `a` identifier in the `data` declaration, and is expecting a `vars` keyword instead.\n\nTo fix this error, you can try adding a `vars` keyword before the `a` identifier, like this:\n```\ndata MyData = MyData {\n vars a :: Int,\n b :: String\n}\n```\nThis should tell the parser that `a` is a variable in the `MyData` data type, and it should be able to parse the rest of the code correctly." + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `(` (start of module), got ident `The`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_3_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_3_response.json new file mode 100644 index 0000000..227b991 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_3_response.json @@ -0,0 +1,8 @@ +{ + "content": " The error message \"expected `(` (start of module), got ident `The`\" suggests that the parser is expecting the start of a module, but it found the identifier `The` instead.\n\nThe `The` identifier is not a valid start of a module, and the parser is expecting a `(` character to indicate the start of a module.\n\nTo fix this error, you can try adding a `(` character before the `The` identifier, like this:\n```\n(The)\n```\nThis should tell the parser that the `The` identifier is the start of a module, and it should be able to parse the rest of the code correctly.", + "usage": { + "completion_tokens": 141, + "prompt_tokens": 7804, + "total_tokens": 7945 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_3_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_3_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_4_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_4_check_stderr.txt new file mode 100644 index 0000000..e0c0b11 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_4_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `(` (start of module), got ident `The` at byte 2 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_4_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_4_program.ailx new file mode 100644 index 0000000..5ea2acb --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_4_program.ailx @@ -0,0 +1,9 @@ + The error message "expected `(` (start of module), got ident `The`" suggests that the parser is expecting the start of a module, but it found the identifier `The` instead. + +The `The` identifier is not a valid start of a module, and the parser is expecting a `(` character to indicate the start of a module. + +To fix this error, you can try adding a `(` character before the `The` identifier, like this: +``` +(The) +``` +This should tell the parser that the `The` identifier is the start of a module, and it should be able to parse the rest of the code correctly. \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_4_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_4_request.json new file mode 100644 index 0000000..b5b6a38 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_4_request.json @@ -0,0 +1,34 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": " (module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (a) (fn-type (params (con List)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor Nil)))\n (do io/print_int (app length (ctor Cons 7 (ctor Cons 8 (ctor Cons 9 (ctor Nil)))))))))\n" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `vars`, got ident `a`" + }, + { + "role": "assistant", + "content": " The error message \"expected `vars`, got ident `a`\" suggests that the parser is expecting a `vars` keyword, but it found an identifier `a` instead.\n\nThe `vars` keyword is used to declare a list of variables in a `data` declaration. For example:\n```\ndata MyData = MyData {\n a :: Int,\n b :: String\n}\n```\nIn this example, `a` and `b` are declared as variables in the `MyData` data type.\n\nIt's possible that the parser is confused by the `a` identifier in the `data` declaration, and is expecting a `vars` keyword instead.\n\nTo fix this error, you can try adding a `vars` keyword before the `a` identifier, like this:\n```\ndata MyData = MyData {\n vars a :: Int,\n b :: String\n}\n```\nThis should tell the parser that `a` is a variable in the `MyData` data type, and it should be able to parse the rest of the code correctly." + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `(` (start of module), got ident `The`" + }, + { + "role": "assistant", + "content": " The error message \"expected `(` (start of module), got ident `The`\" suggests that the parser is expecting the start of a module, but it found the identifier `The` instead.\n\nThe `The` identifier is not a valid start of a module, and the parser is expecting a `(` character to indicate the start of a module.\n\nTo fix this error, you can try adding a `(` character before the `The` identifier, like this:\n```\n(The)\n```\nThis should tell the parser that the `The` identifier is the start of a module, and it should be able to parse the rest of the code correctly." + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `(` (start of module), got ident `The`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_4_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_4_response.json new file mode 100644 index 0000000..a86d90e --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_4_response.json @@ -0,0 +1,8 @@ +{ + "content": " The error message \"expected `(` (start of module), got ident `The`\" suggests that the parser is expecting the start of a module, but it found the identifier `The` instead.\n\nThe `The` identifier is not a valid start of a module, and the parser is expecting a `(` character to indicate the start of a module.\n\nTo fix this error, you can try adding a `(` character before the `The` identifier, like this:\n```\n(The)\n```\nThis should tell the parser that the `The` identifier is the start of a module, and it should be able to parse the rest of the code correctly.", + "usage": { + "completion_tokens": 141, + "prompt_tokens": 7974, + "total_tokens": 8115 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_4_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_4_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_5_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_5_check_stderr.txt new file mode 100644 index 0000000..e0c0b11 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_5_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `(` (start of module), got ident `The` at byte 2 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_5_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_5_program.ailx new file mode 100644 index 0000000..5ea2acb --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_5_program.ailx @@ -0,0 +1,9 @@ + The error message "expected `(` (start of module), got ident `The`" suggests that the parser is expecting the start of a module, but it found the identifier `The` instead. + +The `The` identifier is not a valid start of a module, and the parser is expecting a `(` character to indicate the start of a module. + +To fix this error, you can try adding a `(` character before the `The` identifier, like this: +``` +(The) +``` +This should tell the parser that the `The` identifier is the start of a module, and it should be able to parse the rest of the code correctly. \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_5_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_5_request.json new file mode 100644 index 0000000..df690ac --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_5_request.json @@ -0,0 +1,42 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t2_length. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function length : forall a. (List a) -> Int that returns the number of elements in the list. It must also export main : () -> () !IO that prints length(Nil) and then length(Cons(7, Cons(8, Cons(9, Nil)))), each on its own line." + }, + { + "role": "assistant", + "content": " (module t2_length\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn length\n (type (forall (a) (fn-type (params (con List)) (ret (con Int)))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app length t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app length (ctor Nil)))\n (do io/print_int (app length (ctor Cons 7 (ctor Cons 8 (ctor Cons 9 (ctor Nil)))))))))\n" + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `vars`, got ident `a`" + }, + { + "role": "assistant", + "content": " The error message \"expected `vars`, got ident `a`\" suggests that the parser is expecting a `vars` keyword, but it found an identifier `a` instead.\n\nThe `vars` keyword is used to declare a list of variables in a `data` declaration. For example:\n```\ndata MyData = MyData {\n a :: Int,\n b :: String\n}\n```\nIn this example, `a` and `b` are declared as variables in the `MyData` data type.\n\nIt's possible that the parser is confused by the `a` identifier in the `data` declaration, and is expecting a `vars` keyword instead.\n\nTo fix this error, you can try adding a `vars` keyword before the `a` identifier, like this:\n```\ndata MyData = MyData {\n vars a :: Int,\n b :: String\n}\n```\nThis should tell the parser that `a` is a variable in the `MyData` data type, and it should be able to parse the rest of the code correctly." + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `(` (start of module), got ident `The`" + }, + { + "role": "assistant", + "content": " The error message \"expected `(` (start of module), got ident `The`\" suggests that the parser is expecting the start of a module, but it found the identifier `The` instead.\n\nThe `The` identifier is not a valid start of a module, and the parser is expecting a `(` character to indicate the start of a module.\n\nTo fix this error, you can try adding a `(` character before the `The` identifier, like this:\n```\n(The)\n```\nThis should tell the parser that the `The` identifier is the start of a module, and it should be able to parse the rest of the code correctly." + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `(` (start of module), got ident `The`" + }, + { + "role": "assistant", + "content": " The error message \"expected `(` (start of module), got ident `The`\" suggests that the parser is expecting the start of a module, but it found the identifier `The` instead.\n\nThe `The` identifier is not a valid start of a module, and the parser is expecting a `(` character to indicate the start of a module.\n\nTo fix this error, you can try adding a `(` character before the `The` identifier, like this:\n```\n(The)\n```\nThis should tell the parser that the `The` identifier is the start of a module, and it should be able to parse the rest of the code correctly." + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `(` (start of module), got ident `The`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_5_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_5_response.json new file mode 100644 index 0000000..b735411 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_5_response.json @@ -0,0 +1,8 @@ +{ + "content": " The error message \"expected `(` (start of module), got ident `The`\" suggests that the parser is expecting the start of a module, but it found the identifier `The` instead.\n\nThe `The` identifier is not a valid start of a module, and the parser is expecting a `(` character to indicate the start of a module.\n\nTo fix this error, you can try adding a `(` character before the `The` identifier, like this:\n```\n(The)\n```\nThis should tell the parser that the `The` identifier is the start of a module, and it should be able to parse the rest of the code correctly.", + "usage": { + "completion_tokens": 141, + "prompt_tokens": 8144, + "total_tokens": 8285 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_5_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t2_length/turn_5_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t3_main_prints/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t3_main_prints/turn_1_check_stderr.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t3_main_prints/turn_1_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t3_main_prints/turn_1_program.ailx new file mode 100644 index 0000000..a2b7e17 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t3_main_prints/turn_1_program.ailx @@ -0,0 +1,5 @@ + (module t3_main_prints + (fn main + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int 42) (do io/print_int 1337))))) \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t3_main_prints/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t3_main_prints/turn_1_request.json new file mode 100644 index 0000000..ae106f6 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t3_main_prints/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t3_main_prints. It must export main : () -> () !IO that prints 42 and then 1337, each on its own line. No other definitions are required." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t3_main_prints/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t3_main_prints/turn_1_response.json new file mode 100644 index 0000000..8f2716e --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t3_main_prints/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": " (module t3_main_prints\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 42) (do io/print_int 1337)))))", + "usage": { + "completion_tokens": 74, + "prompt_tokens": 7263, + "total_tokens": 7337 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t3_main_prints/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t3_main_prints/turn_1_run_stdout.txt new file mode 100644 index 0000000..f12daf8 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t3_main_prints/turn_1_run_stdout.txt @@ -0,0 +1,2 @@ +42 +1337 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_1_check_stderr.txt new file mode 100644 index 0000000..ff33388 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_1_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error in term: unknown term head `ctor`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit` at byte 489 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_1_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_1_program.ailx new file mode 100644 index 0000000..c3cdc01 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_1_program.ailx @@ -0,0 +1,15 @@ + (module t4_count_zeros + (data List (vars a) + (ctor Nil) + (ctor Cons a (con List))) + (fn count_zeros + (type (fn-type (params (borrow (con List (con Int)))) (ret (con Int)))) + (params xs) + (body (match xs + (case (pat-ctor Nil) 0) + (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t)))))) + (fn main + (type (fn-type (params) (ret (con Unit)) (effects IO))) + (params) + (body (seq (do io/print_int (app count_zeros (ctor Nil))) (do io/print_int (app count_zeros (ctor Cons 0 (ctor Cons 5 (ctor Cons 0 (ctor Cons 3 (ctor Cons 0 (ctor Nil))))))))))) +``` \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_1_request.json new file mode 100644 index 0000000..e24ac63 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_1_response.json new file mode 100644 index 0000000..d307089 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": " (module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (borrow (con List (con Int)))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (ctor Nil))) (do io/print_int (app count_zeros (ctor Cons 0 (ctor Cons 5 (ctor Cons 0 (ctor Cons 3 (ctor Cons 0 (ctor Nil)))))))))))\n```", + "usage": { + "completion_tokens": 240, + "prompt_tokens": 7353, + "total_tokens": 7593 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_1_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_2_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_2_check_stderr.txt new file mode 100644 index 0000000..b2fde1f --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_2_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `(` (start of module), got ident `It` at byte 2 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_2_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_2_program.ailx new file mode 100644 index 0000000..e1f40a2 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_2_program.ailx @@ -0,0 +1,5 @@ + It looks like you are trying to parse a term using the `parse` function, but the term you are trying to parse contains an unknown term head `ctor`. + +The `parse` function expects the term to be in a specific format, which is described in the error message. The term head `ctor` is not recognized by the `parse` function, and it expects one of the following term heads: `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`. + +To fix the error, you need to make sure that the term you are trying to parse is in the correct format. You can do this by checking the documentation for the `parse` function and making sure that your term is in the correct format. If you are still having trouble, you can try using the `parse-term` function instead, which is a more flexible function that can parse terms in a wider range of formats. \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_2_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_2_request.json new file mode 100644 index 0000000..b522b33 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_2_request.json @@ -0,0 +1,18 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": " (module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (borrow (con List (con Int)))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (ctor Nil))) (do io/print_int (app count_zeros (ctor Cons 0 (ctor Cons 5 (ctor Cons 0 (ctor Cons 3 (ctor Cons 0 (ctor Nil)))))))))))\n```" + }, + { + "role": "user", + "content": "parse: parse error: parse error in term: unknown term head `ctor`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_2_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_2_response.json new file mode 100644 index 0000000..5548d3e --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_2_response.json @@ -0,0 +1,8 @@ +{ + "content": " It looks like you are trying to parse a term using the `parse` function, but the term you are trying to parse contains an unknown term head `ctor`.\n\nThe `parse` function expects the term to be in a specific format, which is described in the error message. The term head `ctor` is not recognized by the `parse` function, and it expects one of the following term heads: `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`.\n\nTo fix the error, you need to make sure that the term you are trying to parse is in the correct format. You can do this by checking the documentation for the `parse` function and making sure that your term is in the correct format. If you are still having trouble, you can try using the `parse-term` function instead, which is a more flexible function that can parse terms in a wider range of formats.", + "usage": { + "completion_tokens": 230, + "prompt_tokens": 7675, + "total_tokens": 7905 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_2_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_2_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_3_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_3_check_stderr.txt new file mode 100644 index 0000000..dde9c04 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_3_check_stderr.txt @@ -0,0 +1 @@ +parse error: lex error: invalid float literal "10." at byte 982 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_3_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_3_program.ailx new file mode 100644 index 0000000..fa750a6 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_3_program.ailx @@ -0,0 +1,15 @@ + It looks like you are trying to parse a module using the `parse` function, but the module you are trying to parse is not in the correct format. The error message indicates that the parser is expecting to see the start of a module, which is indicated by the `(` character, but instead it is seeing the identifier `It`. + +To fix this error, you need to make sure that the module you are trying to parse is in the correct format. The module should start with the `(` character, followed by the module name, and then the module body. The module body should contain the definitions of the functions and variables that make up the module. + +Here is an example of a valid module: +``` +(module my-module + (import (scheme base)) + (define (my-function x) + (+ x 1)) + (define my-variable 10)) +``` +In this example, the module is named `my-module`, and it imports the `scheme base` module. It defines two functions, `my-function` and `my-variable`, and it sets the value of `my-variable` to 10. + +If you are still having trouble, you can try using the `parse-module` function instead of the `parse` function. This function is more flexible and can parse modules in a wider range of formats. \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_3_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_3_request.json new file mode 100644 index 0000000..8649f74 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_3_request.json @@ -0,0 +1,26 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": " (module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (borrow (con List (con Int)))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (ctor Nil))) (do io/print_int (app count_zeros (ctor Cons 0 (ctor Cons 5 (ctor Cons 0 (ctor Cons 3 (ctor Cons 0 (ctor Nil)))))))))))\n```" + }, + { + "role": "user", + "content": "parse: parse error: parse error in term: unknown term head `ctor`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`" + }, + { + "role": "assistant", + "content": " It looks like you are trying to parse a term using the `parse` function, but the term you are trying to parse contains an unknown term head `ctor`.\n\nThe `parse` function expects the term to be in a specific format, which is described in the error message. The term head `ctor` is not recognized by the `parse` function, and it expects one of the following term heads: `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`.\n\nTo fix the error, you need to make sure that the term you are trying to parse is in the correct format. You can do this by checking the documentation for the `parse` function and making sure that your term is in the correct format. If you are still having trouble, you can try using the `parse-term` function instead, which is a more flexible function that can parse terms in a wider range of formats." + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `(` (start of module), got ident `It`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_3_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_3_response.json new file mode 100644 index 0000000..22daf50 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_3_response.json @@ -0,0 +1,8 @@ +{ + "content": " It looks like you are trying to parse a module using the `parse` function, but the module you are trying to parse is not in the correct format. The error message indicates that the parser is expecting to see the start of a module, which is indicated by the `(` character, but instead it is seeing the identifier `It`.\n\nTo fix this error, you need to make sure that the module you are trying to parse is in the correct format. The module should start with the `(` character, followed by the module name, and then the module body. The module body should contain the definitions of the functions and variables that make up the module.\n\nHere is an example of a valid module:\n```\n(module my-module\n (import (scheme base))\n (define (my-function x)\n (+ x 1))\n (define my-variable 10))\n```\nIn this example, the module is named `my-module`, and it imports the `scheme base` module. It defines two functions, `my-function` and `my-variable`, and it sets the value of `my-variable` to 10.\n\nIf you are still having trouble, you can try using the `parse-module` function instead of the `parse` function. This function is more flexible and can parse modules in a wider range of formats.", + "usage": { + "completion_tokens": 295, + "prompt_tokens": 7934, + "total_tokens": 8229 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_3_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_3_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_4_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_4_check_stderr.txt new file mode 100644 index 0000000..b2fde1f --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_4_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `(` (start of module), got ident `It` at byte 2 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_4_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_4_program.ailx new file mode 100644 index 0000000..d7e4b07 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_4_program.ailx @@ -0,0 +1,13 @@ + It looks like you are trying to parse a float literal, but the value you are trying to parse is not a valid float literal. + +In Rust, a float literal is a number that is followed by a decimal point and one or more digits. For example, `10.0` is a valid float literal, but `10.` is not. + +To fix this error, you can add a digit after the decimal point. For example: +``` +let x = 10.0; +``` +Alternatively, you can use the `parse` function to convert a string to a float. For example: +``` +let x = "10.0".parse::().unwrap(); +``` +This will parse the string `"10.0"` as a float and store the result in the variable `x`. \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_4_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_4_request.json new file mode 100644 index 0000000..ccf2b9a --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_4_request.json @@ -0,0 +1,34 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": " (module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (borrow (con List (con Int)))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (ctor Nil))) (do io/print_int (app count_zeros (ctor Cons 0 (ctor Cons 5 (ctor Cons 0 (ctor Cons 3 (ctor Cons 0 (ctor Nil)))))))))))\n```" + }, + { + "role": "user", + "content": "parse: parse error: parse error in term: unknown term head `ctor`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`" + }, + { + "role": "assistant", + "content": " It looks like you are trying to parse a term using the `parse` function, but the term you are trying to parse contains an unknown term head `ctor`.\n\nThe `parse` function expects the term to be in a specific format, which is described in the error message. The term head `ctor` is not recognized by the `parse` function, and it expects one of the following term heads: `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`.\n\nTo fix the error, you need to make sure that the term you are trying to parse is in the correct format. You can do this by checking the documentation for the `parse` function and making sure that your term is in the correct format. If you are still having trouble, you can try using the `parse-term` function instead, which is a more flexible function that can parse terms in a wider range of formats." + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `(` (start of module), got ident `It`" + }, + { + "role": "assistant", + "content": " It looks like you are trying to parse a module using the `parse` function, but the module you are trying to parse is not in the correct format. The error message indicates that the parser is expecting to see the start of a module, which is indicated by the `(` character, but instead it is seeing the identifier `It`.\n\nTo fix this error, you need to make sure that the module you are trying to parse is in the correct format. The module should start with the `(` character, followed by the module name, and then the module body. The module body should contain the definitions of the functions and variables that make up the module.\n\nHere is an example of a valid module:\n```\n(module my-module\n (import (scheme base))\n (define (my-function x)\n (+ x 1))\n (define my-variable 10))\n```\nIn this example, the module is named `my-module`, and it imports the `scheme base` module. It defines two functions, `my-function` and `my-variable`, and it sets the value of `my-variable` to 10.\n\nIf you are still having trouble, you can try using the `parse-module` function instead of the `parse` function. This function is more flexible and can parse modules in a wider range of formats." + }, + { + "role": "user", + "content": "parse: parse error: lex error: invalid float literal \"10.\"" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_4_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_4_response.json new file mode 100644 index 0000000..912e22b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_4_response.json @@ -0,0 +1,8 @@ +{ + "content": " It looks like you are trying to parse a float literal, but the value you are trying to parse is not a valid float literal.\n\nIn Rust, a float literal is a number that is followed by a decimal point and one or more digits. For example, `10.0` is a valid float literal, but `10.` is not.\n\nTo fix this error, you can add a digit after the decimal point. For example:\n```\nlet x = 10.0;\n```\nAlternatively, you can use the `parse` function to convert a string to a float. For example:\n```\nlet x = \"10.0\".parse::().unwrap();\n```\nThis will parse the string `\"10.0\"` as a float and store the result in the variable `x`.", + "usage": { + "completion_tokens": 184, + "prompt_tokens": 8252, + "total_tokens": 8436 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_4_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_4_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_5_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_5_check_stderr.txt new file mode 100644 index 0000000..b2fde1f --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_5_check_stderr.txt @@ -0,0 +1 @@ +parse error: parse error: expected `(` (start of module), got ident `It` at byte 2 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_5_program.ailx b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_5_program.ailx new file mode 100644 index 0000000..f5ad0a7 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_5_program.ailx @@ -0,0 +1,19 @@ + It looks like you are trying to parse a module, but the module name is not in the correct format. + +In Rust, a module is a collection of related functions and variables that are defined together. The name of a module is typically a capitalized identifier, and it should be followed by a colon and the module body. + +Here is an example of a valid module: +``` +mod MyModule { + // module body +} +``` +In this example, the module name is `MyModule`, and the module body is the code that is defined inside the curly braces. + +To fix the error, you can try adding a colon after the module name, like this: +``` +mod MyModule: { + // module body +} +``` +This will define a module named `MyModule` with the specified module body. \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_5_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_5_request.json new file mode 100644 index 0000000..bf33aa5 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_5_request.json @@ -0,0 +1,42 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\n\nThe AILX surface is parenthesised tagged-head Lisp-style notation.\nEvery form begins with `(` followed by a tag identifier; positional\narguments follow; the form closes with `)`. There are no infix\noperators, no operator precedence, no implicit conversions.\n\nA module starts with `(module NAME ...)` and contains import forms\nfollowed by definition forms. Whitespace and `;`-prefixed line\ncomments are insignificant; indentation is for readability only.\n\nIdentifier characters allowed: letters, digits, `_`, `-`, `+`, `*`,\n`/`, `<`, `>`, `=`, `!`, `?`, `%`. An identifier may not start with a\ndigit. String literals are double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t`\nescapes; integer literals are signed decimal; the `(unit)` keyword\ndenotes the unit value.\n\nThe AILX form is the printed dual of the JSON form: `ail parse\nfile.ailx` produces the same canonical bytes as the JSON form would,\nand `ail render file.ail.json` produces the AILX text. Round-trip\nthrough this pair is gated by an integration test.\n\n```ailx\n(module fn_returns_int\n (fn answer\n (doc \"The simplest possible fn — no params, returns a fixed Int.\")\n (type (fn-type (params) (ret (con Int))))\n (params)\n (body 42)))\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\n\nType::Con: `Int`, `Bool`, `Str`, `Float`, `Unit`, or a user type\nname. For parameterised types: `(con List Int)` reads \"List of Int\";\nnested: `(con Map (con Str) (con Int))`.\n\nType::Var: a bare identifier alone in type position (no wrapper),\ne.g. `a` inside a `(forall (a) ...)`. Type variables are\nsyntactically distinguished from type constructors by being declared\nin the enclosing `forall`.\n\nType::Fn: `(fn-type (params T1 T2) (ret RET) (effects E1 E2))`. The\nparameter and effect lists may be empty. Modes wrap the relevant\ntype: `(own T)` or `(borrow T)` — see section 4.\n\nType::Forall: `(forall (a b) BODY-TYPE)` quantifies over `a` and\n`b`. Class constraints attach as `(forall (a) (constraints (Eq a))\nBODY-TYPE)`.\n\n```ailx\n(module forall_polymorphic\n (fn id\n (doc \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\")\n (type (forall (vars a) (fn-type (params a) (ret a))))\n (params x)\n (body x)))\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\n\nModes are surface wrappers around the type in parameter and return\nposition. Inside a `(fn-type ...)`:\n\n- `(params (own T1) (borrow T2))` — `T1` is owned, `T2` is borrowed.\n- `(ret (own T))` — the function returns an owned value.\n- A bare type with no mode wrapper is implicit (legacy default).\n\nWhen all parameters and the return are implicit, the surface omits\nthe mode wrappers entirely; this is the canonical form for fns that\ndo not yet need explicit annotations. Adding modes is a forward\nchange: an existing fn keeps the same canonical bytes as long as no\nannotation is added.\n\nWriting `(borrow T)` on a parameter the typechecker wants `own`\nfor surfaces as the `over-strict-mode` diagnostic — advisory, and\nsuppressable via the fn def's `suppress` clause. The mode is part\nof the fn's signature and contributes to its content hash, so\nmaking the choice explicit at authoring time avoids surprise\nhash changes when the typechecker's defaults shift.\n\n```ailx\n(module param_modes_all\n (fn f_implicit\n (doc \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params x)\n (body x))\n (fn f_own\n (doc \"(own T) — caller transfers ownership; callee consumes.\")\n (type (fn-type (params (own (con Int))) (ret (con Int))))\n (params x)\n (body x))\n (fn f_borrow\n (doc \"(borrow T) — caller retains ownership; callee may not consume.\")\n (type (fn-type (params (borrow (con Int))) (ret (con Int))))\n (params x)\n (body x)))\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\n\nFunction definition: `(fn NAME (doc STRING)? (type TYPE) (params\nNAME*) (body TERM))`. The `doc` clause is optional but recommended.\n\nTerm forms relevant here:\n\n- Variable reference: a bare identifier, e.g. `x` or `+`.\n- Application: `(app CALLEE ARG*)` — head is `app`, then the callee\n term, then positional arguments.\n- Lambda: `(lam (params NAME*) (paramTypes TYPE*) (ret TYPE)\n (effects EFFECT*) BODY)`.\n- Let: `(let (NAME VALUE) BODY)` — binds `NAME` to `VALUE` in\n `BODY`.\n- Letrec: `(letrec NAME (type FN-TYPE) (params NAME*) BODY IN-BODY)`\n — `NAME` is recursively visible inside `BODY`.\n\nInside any term position, an integer literal is a bare decimal\nnumber, a string is a double-quoted string, a bool is `true` or\n`false`, unit is `(unit)`.\n\n```ailx\n(module fn_calls_prelude\n (fn add\n (doc \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\")\n (type (fn-type (params (con Int) (con Int)) (ret (con Int))))\n (params x y)\n (body (let s (app + x y) (clone s)))))\n```\n\n```ailx\n(module fn_with_lambda\n (fn make_adder\n (doc \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\")\n (type (fn-type (params (con Int)) (ret (fn-type (params (con Int)) (ret (con Int))))))\n (params x)\n (body (lam (params (typed y (con Int))) (ret (con Int)) (body (app + x y))))))\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\n\nType definition: `(data NAME (vars TYVAR*)? (doc STRING)? (ctor\nCTOR-NAME ARG-TYPE*)*)`. The `vars` clause is optional; nullary\nconstructors have an empty argument list.\n\nExample: `(data List (ctor Nil) (ctor Cons Int (con List)))` is a\nmonomorphic Int list with a Nil and a Cons constructor.\n\nConstructor invocation: `(ctor TYPE-NAME CTOR-NAME ARG*)`. The\n`TYPE-NAME` precedes the constructor name so the parser does not\nneed a separate registry to resolve overlapping constructor names\nacross ADTs. Example: `(ctor List Cons 1 (ctor List Nil))` builds a\nsingle-element list.\n\n```ailx\n(module data_simple\n (data Box (vars a)\n (doc \"A unary box around a polymorphic value.\")\n (ctor MkBox a))\n (fn wrap_one\n (doc \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\")\n (type (fn-type (params (own (con Box (con Int)))) (ret (own (con Box (con Int))))))\n (params src)\n (body (reuse-as src (term-ctor Box MkBox 1)))))\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\n\nMatch expression: `(match SCRUT (case PAT BODY) (case PAT BODY)\n...)`. Arms are introduced by `(case ...)`; the order matters (top-\nto-bottom).\n\nPattern shapes:\n- Wildcard: `_` — bare underscore is the wildcard pattern.\n- Variable: a bare identifier alone, e.g. `h`, binds the scrutinee.\n- Literal: a literal value alone, e.g. `0`, `true`, `\"abc\"`,\n `(unit)`, matches bit-equal scrutinees.\n- Constructor: `(ctor CTOR-NAME SUBPAT*)` — note no `type` slot\n here, because the constructor name is resolved against the\n scrutinee's type.\n\nPattern variables introduced by a `(case PAT BODY)` are in scope\ninside `BODY` and bind there only. They do not leak into sibling\narms or into code outside the `match`.\n\n```ailx\n(module data_with_match\n (data List\n (doc \"Monomorphic singly-linked Int list — boxed, recursive.\")\n (ctor Nil)\n (ctor Cons (con Int) (con List)))\n (fn head_or_zero\n (doc \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h _) h))))\n (fn count_via_letrec\n (doc \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\")\n (type (fn-type (params (borrow (con List))) (ret (con Int))))\n (params xs)\n (body (let-rec go (params ys) (type (fn-type (params (borrow (con List))) (ret (con Int)))) (body (match ys\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons _ t) (app + 1 (app go t))))) (in (app go xs))))))\n```\n\n```ailx\n(module match_literal_pattern\n (fn classify\n (doc \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (match n\n (case (pat-lit 0) 100)\n (case _ 200))))\n (fn sign_if\n (doc \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\")\n (type (fn-type (params (con Int)) (ret (con Int))))\n (params n)\n (body (if (app < n 0) -1 (if (app == n 0) 0 1)))))\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\n\nEffect row on a fn type: `(effects E1 E2 ...)` inside the\n`(fn-type ...)`. Empty row: omit the `(effects)` clause or write\n`(effects)`. Common effectful row: `(effects IO)`. Order is sorted\nin canonical output.\n\nEffect operation: `(do OP-NAME ARG*)` — the leading `do` head is\nthe parser's signal that the next identifier is an effect op, not a\nfunction. Example: `(do io/print_int 42)` prints the integer 42\nand raises the `IO` effect.\n\nSequencing: `(seq LHS RHS)`. The value of `(seq X Y)` is `Y`'s\nvalue; `X`'s value is discarded. Chains of sequencing are written\nnested: `(seq A (seq B C))` runs `A`, then `B`, then yields `C`'s\nvalue.\n\n```ailx\n(module fn_with_do_seq\n (fn main\n (doc \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\")\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int 1) (do io/print_int 2)))))\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\n\nLiteral surface forms:\n\n- Int: bare signed decimal, e.g. `42`, `-7`, `0`.\n- Bool: `true` or `false` as bare keywords.\n- Str: double-quoted with `\\\"`, `\\\\`, `\\n`, `\\t` escapes, e.g.\n `\"hello\"`. Bytes outside ASCII printable are rejected by the\n authoring rule (Decision 6 Constraint 3).\n- Unit: the keyword `(unit)`.\n- Float: bare decimal with at least one digit on each side of the\n point, e.g. `3.14`, `0.5`, `-2.0`. The parser converts to the\n bit-pattern hex string when emitting JSON; you never author the\n hex form.\n\nThere is no separate syntax for exponent-form floats in the v1\nauthoring surface; for non-finite values use the prelude constants\n`nan`, `inf`, `neg_inf`.\n\n```ailx\n(module floats\n (const pi\n (doc \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\")\n (type (con Float))\n (body 3.14)))\n```\n\n```ailx\n(module bool_str\n (fn is_true\n (doc \"Trivial Bool literal. Exercises Literal::Bool.\")\n (type (fn-type (params) (ret (con Bool))))\n (params)\n (body true))\n (fn greeting\n (doc \"Trivial Str literal. Exercises Literal::Str.\")\n (type (fn-type (params) (ret (con Str))))\n (params)\n (body \"hi\"))\n (fn unit_value\n (doc \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\")\n (type (fn-type (params) (ret (con Unit))))\n (params)\n (body (lit-unit))))\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\n\nClass declaration: `(class NAME (param TYVAR) (method NAME (type\nMETHOD-TYPE) (default BODY)?)*)`. The `default` clause is\noptional; methods without a default must be supplied by every\ninstance.\n\nInstance declaration: `(instance NAME (type CONCRETE-TYPE) (method\nNAME BODY)*)`. The method bodies must match the class's method\nsignatures with the class parameter substituted to the concrete\ntype.\n\nConstraints inside a forall: `(forall (a) (constraints (Eq a) (Ord\na)) BODY-TYPE)`. Each constraint is `(CLASS-NAME TYPE)` — usually\nthe type is a single bound type variable but a concrete type is\nalso legal (typically a typecheck error unless an instance exists).\n\n```ailx\n(module class_def\n (class MyShow\n (param a)\n (doc \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\")\n (method show\n (type (fn-type (params a) (ret (con Str)))))))\n```\n\n```ailx\n(module instance_def\n (class MyShow\n (param a)\n (doc \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\")\n (method show\n (type (fn-type (params a) (ret (con Str))))))\n (instance\n (class MyShow)\n (type (con Int))\n (doc \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\")\n (method show\n (body \"int\"))))\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t4_count_zeros. It must define a local algebraic data type List a with constructors Nil and Cons a (List a). It must export a top-level function count_zeros : (List Int) -> Int that returns the number of elements in the list that equal 0, using the prelude Eq Int instance to compare against 0. It must also export main : () -> () !IO that prints count_zeros(Nil) and then count_zeros(Cons(0, Cons(5, Cons(0, Cons(3, Cons(0, Nil)))))), each on its own line." + }, + { + "role": "assistant", + "content": " (module t4_count_zeros\n (data List (vars a)\n (ctor Nil)\n (ctor Cons a (con List)))\n (fn count_zeros\n (type (fn-type (params (borrow (con List (con Int)))) (ret (con Int))))\n (params xs)\n (body (match xs\n (case (pat-ctor Nil) 0)\n (case (pat-ctor Cons h t) (if (app == h 0) (app + 1 (app count_zeros t)) (app count_zeros t))))))\n (fn main\n (type (fn-type (params) (ret (con Unit)) (effects IO)))\n (params)\n (body (seq (do io/print_int (app count_zeros (ctor Nil))) (do io/print_int (app count_zeros (ctor Cons 0 (ctor Cons 5 (ctor Cons 0 (ctor Cons 3 (ctor Cons 0 (ctor Nil)))))))))))\n```" + }, + { + "role": "user", + "content": "parse: parse error: parse error in term: unknown term head `ctor`; expected one of `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`" + }, + { + "role": "assistant", + "content": " It looks like you are trying to parse a term using the `parse` function, but the term you are trying to parse contains an unknown term head `ctor`.\n\nThe `parse` function expects the term to be in a specific format, which is described in the error message. The term head `ctor` is not recognized by the `parse` function, and it expects one of the following term heads: `app`, `tail-app`, `lam`, `let`, `let-rec`, `if`, `match`, `do`, `tail-do`, `seq`, `term-ctor`, `clone`, `reuse-as`, `lit-unit`.\n\nTo fix the error, you need to make sure that the term you are trying to parse is in the correct format. You can do this by checking the documentation for the `parse` function and making sure that your term is in the correct format. If you are still having trouble, you can try using the `parse-term` function instead, which is a more flexible function that can parse terms in a wider range of formats." + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `(` (start of module), got ident `It`" + }, + { + "role": "assistant", + "content": " It looks like you are trying to parse a module using the `parse` function, but the module you are trying to parse is not in the correct format. The error message indicates that the parser is expecting to see the start of a module, which is indicated by the `(` character, but instead it is seeing the identifier `It`.\n\nTo fix this error, you need to make sure that the module you are trying to parse is in the correct format. The module should start with the `(` character, followed by the module name, and then the module body. The module body should contain the definitions of the functions and variables that make up the module.\n\nHere is an example of a valid module:\n```\n(module my-module\n (import (scheme base))\n (define (my-function x)\n (+ x 1))\n (define my-variable 10))\n```\nIn this example, the module is named `my-module`, and it imports the `scheme base` module. It defines two functions, `my-function` and `my-variable`, and it sets the value of `my-variable` to 10.\n\nIf you are still having trouble, you can try using the `parse-module` function instead of the `parse` function. This function is more flexible and can parse modules in a wider range of formats." + }, + { + "role": "user", + "content": "parse: parse error: lex error: invalid float literal \"10.\"" + }, + { + "role": "assistant", + "content": " It looks like you are trying to parse a float literal, but the value you are trying to parse is not a valid float literal.\n\nIn Rust, a float literal is a number that is followed by a decimal point and one or more digits. For example, `10.0` is a valid float literal, but `10.` is not.\n\nTo fix this error, you can add a digit after the decimal point. For example:\n```\nlet x = 10.0;\n```\nAlternatively, you can use the `parse` function to convert a string to a float. For example:\n```\nlet x = \"10.0\".parse::().unwrap();\n```\nThis will parse the string `\"10.0\"` as a float and store the result in the variable `x`." + }, + { + "role": "user", + "content": "parse: parse error: parse error: expected `(` (start of module), got ident `It`" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_5_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_5_response.json new file mode 100644 index 0000000..10e0fc1 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_5_response.json @@ -0,0 +1,8 @@ +{ + "content": " It looks like you are trying to parse a module, but the module name is not in the correct format.\n\nIn Rust, a module is a collection of related functions and variables that are defined together. The name of a module is typically a capitalized identifier, and it should be followed by a colon and the module body.\n\nHere is an example of a valid module:\n```\nmod MyModule {\n // module body\n}\n```\nIn this example, the module name is `MyModule`, and the module body is the code that is defined inside the curly braces.\n\nTo fix the error, you can try adding a colon after the module name, like this:\n```\nmod MyModule: {\n // module body\n}\n```\nThis will define a module named `MyModule` with the specified module body.", + "usage": { + "completion_tokens": 182, + "prompt_tokens": 8465, + "total_tokens": 8647 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_5_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/ailx/t4_count_zeros/turn_5_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_1_check_stderr.txt new file mode 100644 index 0000000..b787020 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_1_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-vXB5Kc/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_1_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_1_program.ail.json new file mode 100644 index 0000000..4fd4c47 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_1_program.ail.json @@ -0,0 +1,78 @@ + ```json +{ + "schema": "ailang/v0", + "name": "t1_add_three", + "imports": [], + "defs": [ + { + "kind": "fn", + "name": "add_three", + "doc": "Add three Ints.", + "type": { + "k": "fn", + "params": [ + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" }, + { "k": "con", "name": "Int" } + ], + "ret": { "k": "con", "name": "Int" }, + "effects": [] + }, + "params": ["x", "y", "z"], + "body": { + "t": "app", + "fn": { "t": "var", "name": "+" }, + "args": [ + { "t": "app", "fn": { "t": "var", "name": "+" }, "args": [{ "t": "var", "name": "x" }, { "t": "var", "name": "y" }] }, + { "t": "var", "name": "z" } + ] + } + }, + { + "kind": "fn", + "name": "main", + "doc": "Print add_three(1, 2, 3) and add_three(10, 20, 30).", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 1 } }, + { "t": "lit", "lit": { "kind": "int", "value": 2 } }, + { "t": "lit", "lit": { "kind": "int", "value": 3 } } + ] + } + ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ + { + "t": "app", + "fn": { "t": "var", "name": "add_three" }, + "args": [ + { "t": "lit", "lit": { "kind": "int", "value": 10 } }, + { "t": "lit", "lit": { "kind": "int", "value": 20 } }, + { "t": "lit", "lit": { "kind": "int", "value": 30 } } + ] + } + ] + } + } + } + ] +} +``` \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_1_request.json new file mode 100644 index 0000000..b1d186f --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_1_response.json new file mode 100644 index 0000000..8072aef --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": " ```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\", \"z\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"app\", \"fn\": { \"t\": \"var\", \"name\": \"+\" }, \"args\": [{ \"t\": \"var\", \"name\": \"x\" }, { \"t\": \"var\", \"name\": \"y\" }] },\n { \"t\": \"var\", \"name\": \"z\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n ]\n }\n }\n }\n ]\n}\n```", + "usage": { + "completion_tokens": 776, + "prompt_tokens": 10863, + "total_tokens": 11639 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_1_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_2_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_2_check_stderr.txt new file mode 100644 index 0000000..aa4f9c7 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_2_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-PmMIqa/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_2_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_2_program.ail.json new file mode 100644 index 0000000..1ad1147 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_2_program.ail.json @@ -0,0 +1,15 @@ + The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one. + +Here are a few possible reasons why the JSON file may be invalid: + +1. The file may be missing a closing bracket or quote. +2. The file may have an extra comma or semicolon. +3. The file may have a syntax error in the JSON structure. + +To fix the issue, you can try the following: + +1. Check the JSON file for any missing or extra characters. +2. Check the JSON structure to ensure that it is valid. +3. Use a JSON validator tool to check the file for any syntax errors. + +Once you have fixed the issue, you can try parsing the JSON file again. \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_2_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_2_request.json new file mode 100644 index 0000000..064d6a1 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_2_request.json @@ -0,0 +1,18 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + }, + { + "role": "assistant", + "content": " ```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\", \"z\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"app\", \"fn\": { \"t\": \"var\", \"name\": \"+\" }, \"args\": [{ \"t\": \"var\", \"name\": \"x\" }, { \"t\": \"var\", \"name\": \"y\" }] },\n { \"t\": \"var\", \"name\": \"z\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n ]\n }\n }\n }\n ]\n}\n```" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-vXB5Kc/prog.ail.json: expected value at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_2_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_2_response.json new file mode 100644 index 0000000..ff3203d --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_2_response.json @@ -0,0 +1,8 @@ +{ + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible reasons why the JSON file may be invalid:\n\n1. The file may be missing a closing bracket or quote.\n2. The file may have an extra comma or semicolon.\n3. The file may have a syntax error in the JSON structure.\n\nTo fix the issue, you can try the following:\n\n1. Check the JSON file for any missing or extra characters.\n2. Check the JSON structure to ensure that it is valid.\n3. Use a JSON validator tool to check the file for any syntax errors.\n\nOnce you have fixed the issue, you can try parsing the JSON file again.", + "usage": { + "completion_tokens": 188, + "prompt_tokens": 11677, + "total_tokens": 11865 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_2_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_2_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_3_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_3_check_stderr.txt new file mode 100644 index 0000000..499d54a --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_3_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-9iI2uV/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_3_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_3_program.ail.json new file mode 100644 index 0000000..1ad1147 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_3_program.ail.json @@ -0,0 +1,15 @@ + The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one. + +Here are a few possible reasons why the JSON file may be invalid: + +1. The file may be missing a closing bracket or quote. +2. The file may have an extra comma or semicolon. +3. The file may have a syntax error in the JSON structure. + +To fix the issue, you can try the following: + +1. Check the JSON file for any missing or extra characters. +2. Check the JSON structure to ensure that it is valid. +3. Use a JSON validator tool to check the file for any syntax errors. + +Once you have fixed the issue, you can try parsing the JSON file again. \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_3_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_3_request.json new file mode 100644 index 0000000..104af1f --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_3_request.json @@ -0,0 +1,26 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + }, + { + "role": "assistant", + "content": " ```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\", \"z\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"app\", \"fn\": { \"t\": \"var\", \"name\": \"+\" }, \"args\": [{ \"t\": \"var\", \"name\": \"x\" }, { \"t\": \"var\", \"name\": \"y\" }] },\n { \"t\": \"var\", \"name\": \"z\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n ]\n }\n }\n }\n ]\n}\n```" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-vXB5Kc/prog.ail.json: expected value at" + }, + { + "role": "assistant", + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible reasons why the JSON file may be invalid:\n\n1. The file may be missing a closing bracket or quote.\n2. The file may have an extra comma or semicolon.\n3. The file may have a syntax error in the JSON structure.\n\nTo fix the issue, you can try the following:\n\n1. Check the JSON file for any missing or extra characters.\n2. Check the JSON structure to ensure that it is valid.\n3. Use a JSON validator tool to check the file for any syntax errors.\n\nOnce you have fixed the issue, you can try parsing the JSON file again." + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-PmMIqa/prog.ail.json: expected value at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_3_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_3_response.json new file mode 100644 index 0000000..0494acd --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_3_response.json @@ -0,0 +1,8 @@ +{ + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible reasons why the JSON file may be invalid:\n\n1. The file may be missing a closing bracket or quote.\n2. The file may have an extra comma or semicolon.\n3. The file may have a syntax error in the JSON structure.\n\nTo fix the issue, you can try the following:\n\n1. Check the JSON file for any missing or extra characters.\n2. Check the JSON structure to ensure that it is valid.\n3. Use a JSON validator tool to check the file for any syntax errors.\n\nOnce you have fixed the issue, you can try parsing the JSON file again.", + "usage": { + "completion_tokens": 188, + "prompt_tokens": 11901, + "total_tokens": 12089 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_3_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_3_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_4_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_4_check_stderr.txt new file mode 100644 index 0000000..fb6e672 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_4_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-GYAZkX/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_4_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_4_program.ail.json new file mode 100644 index 0000000..1ad1147 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_4_program.ail.json @@ -0,0 +1,15 @@ + The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one. + +Here are a few possible reasons why the JSON file may be invalid: + +1. The file may be missing a closing bracket or quote. +2. The file may have an extra comma or semicolon. +3. The file may have a syntax error in the JSON structure. + +To fix the issue, you can try the following: + +1. Check the JSON file for any missing or extra characters. +2. Check the JSON structure to ensure that it is valid. +3. Use a JSON validator tool to check the file for any syntax errors. + +Once you have fixed the issue, you can try parsing the JSON file again. \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_4_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_4_request.json new file mode 100644 index 0000000..aa4edc9 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_4_request.json @@ -0,0 +1,34 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + }, + { + "role": "assistant", + "content": " ```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\", \"z\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"app\", \"fn\": { \"t\": \"var\", \"name\": \"+\" }, \"args\": [{ \"t\": \"var\", \"name\": \"x\" }, { \"t\": \"var\", \"name\": \"y\" }] },\n { \"t\": \"var\", \"name\": \"z\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n ]\n }\n }\n }\n ]\n}\n```" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-vXB5Kc/prog.ail.json: expected value at" + }, + { + "role": "assistant", + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible reasons why the JSON file may be invalid:\n\n1. The file may be missing a closing bracket or quote.\n2. The file may have an extra comma or semicolon.\n3. The file may have a syntax error in the JSON structure.\n\nTo fix the issue, you can try the following:\n\n1. Check the JSON file for any missing or extra characters.\n2. Check the JSON structure to ensure that it is valid.\n3. Use a JSON validator tool to check the file for any syntax errors.\n\nOnce you have fixed the issue, you can try parsing the JSON file again." + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-PmMIqa/prog.ail.json: expected value at" + }, + { + "role": "assistant", + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible reasons why the JSON file may be invalid:\n\n1. The file may be missing a closing bracket or quote.\n2. The file may have an extra comma or semicolon.\n3. The file may have a syntax error in the JSON structure.\n\nTo fix the issue, you can try the following:\n\n1. Check the JSON file for any missing or extra characters.\n2. Check the JSON structure to ensure that it is valid.\n3. Use a JSON validator tool to check the file for any syntax errors.\n\nOnce you have fixed the issue, you can try parsing the JSON file again." + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-9iI2uV/prog.ail.json: expected value at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_4_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_4_response.json new file mode 100644 index 0000000..559f889 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_4_response.json @@ -0,0 +1,8 @@ +{ + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible reasons why the JSON file may be invalid:\n\n1. The file may be missing a closing bracket or quote.\n2. The file may have an extra comma or semicolon.\n3. The file may have a syntax error in the JSON structure.\n\nTo fix the issue, you can try the following:\n\n1. Check the JSON file for any missing or extra characters.\n2. Check the JSON structure to ensure that it is valid.\n3. Use a JSON validator tool to check the file for any syntax errors.\n\nOnce you have fixed the issue, you can try parsing the JSON file again.", + "usage": { + "completion_tokens": 188, + "prompt_tokens": 12127, + "total_tokens": 12315 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_4_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_4_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_5_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_5_check_stderr.txt new file mode 100644 index 0000000..7e0b9f5 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_5_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-7Udtgl/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_5_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_5_program.ail.json new file mode 100644 index 0000000..1ad1147 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_5_program.ail.json @@ -0,0 +1,15 @@ + The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one. + +Here are a few possible reasons why the JSON file may be invalid: + +1. The file may be missing a closing bracket or quote. +2. The file may have an extra comma or semicolon. +3. The file may have a syntax error in the JSON structure. + +To fix the issue, you can try the following: + +1. Check the JSON file for any missing or extra characters. +2. Check the JSON structure to ensure that it is valid. +3. Use a JSON validator tool to check the file for any syntax errors. + +Once you have fixed the issue, you can try parsing the JSON file again. \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_5_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_5_request.json new file mode 100644 index 0000000..5a66ae0 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_5_request.json @@ -0,0 +1,42 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t1_add_three. It must export a top-level function add_three that takes three Int parameters and returns their sum. It must also export main : () -> () !IO that prints add_three(1, 2, 3) and then add_three(10, 20, 30), each on its own line." + }, + { + "role": "assistant", + "content": " ```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"t1_add_three\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add_three\",\n \"doc\": \"Add three Ints.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\", \"z\"],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"app\", \"fn\": { \"t\": \"var\", \"name\": \"+\" }, \"args\": [{ \"t\": \"var\", \"name\": \"x\" }, { \"t\": \"var\", \"name\": \"y\" }] },\n { \"t\": \"var\", \"name\": \"z\" }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print add_three(1, 2, 3) and add_three(10, 20, 30).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 3 } }\n ]\n }\n ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"add_three\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 10 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 20 } },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 30 } }\n ]\n }\n ]\n }\n }\n }\n ]\n}\n```" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-vXB5Kc/prog.ail.json: expected value at" + }, + { + "role": "assistant", + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible reasons why the JSON file may be invalid:\n\n1. The file may be missing a closing bracket or quote.\n2. The file may have an extra comma or semicolon.\n3. The file may have a syntax error in the JSON structure.\n\nTo fix the issue, you can try the following:\n\n1. Check the JSON file for any missing or extra characters.\n2. Check the JSON structure to ensure that it is valid.\n3. Use a JSON validator tool to check the file for any syntax errors.\n\nOnce you have fixed the issue, you can try parsing the JSON file again." + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-PmMIqa/prog.ail.json: expected value at" + }, + { + "role": "assistant", + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible reasons why the JSON file may be invalid:\n\n1. The file may be missing a closing bracket or quote.\n2. The file may have an extra comma or semicolon.\n3. The file may have a syntax error in the JSON structure.\n\nTo fix the issue, you can try the following:\n\n1. Check the JSON file for any missing or extra characters.\n2. Check the JSON structure to ensure that it is valid.\n3. Use a JSON validator tool to check the file for any syntax errors.\n\nOnce you have fixed the issue, you can try parsing the JSON file again." + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-9iI2uV/prog.ail.json: expected value at" + }, + { + "role": "assistant", + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible reasons why the JSON file may be invalid:\n\n1. The file may be missing a closing bracket or quote.\n2. The file may have an extra comma or semicolon.\n3. The file may have a syntax error in the JSON structure.\n\nTo fix the issue, you can try the following:\n\n1. Check the JSON file for any missing or extra characters.\n2. Check the JSON structure to ensure that it is valid.\n3. Use a JSON validator tool to check the file for any syntax errors.\n\nOnce you have fixed the issue, you can try parsing the JSON file again." + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-GYAZkX/prog.ail.json: expected value at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_5_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_5_response.json new file mode 100644 index 0000000..6b8782d --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_5_response.json @@ -0,0 +1,8 @@ +{ + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible reasons why the JSON file may be invalid:\n\n1. The file may be missing a closing bracket or quote.\n2. The file may have an extra comma or semicolon.\n3. The file may have a syntax error in the JSON structure.\n\nTo fix the issue, you can try the following:\n\n1. Check the JSON file for any missing or extra characters.\n2. Check the JSON structure to ensure that it is valid.\n3. Use a JSON validator tool to check the file for any syntax errors.\n\nOnce you have fixed the issue, you can try parsing the JSON file again.", + "usage": { + "completion_tokens": 188, + "prompt_tokens": 12353, + "total_tokens": 12541 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_5_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t1_add_three/turn_5_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_1_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_1_check_stderr.txt new file mode 100644 index 0000000..5628661 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_1_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-EoxPyG/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_1_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_1_program.ail.json new file mode 100644 index 0000000..16d9b3c --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_1_program.ail.json @@ -0,0 +1,34 @@ + ```json +{ + "schema": "ailang/v0", + "name": "t3_main_prints", + "imports": [], + "defs": [ + { + "kind": "fn", + "name": "main", + "doc": "Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).", + "type": { + "k": "fn", + "params": [], + "ret": { "k": "con", "name": "Unit" }, + "effects": ["IO"] + }, + "params": [], + "body": { + "t": "seq", + "lhs": { + "t": "do", + "op": "io/print_int", + "args": [ { "t": "lit", "lit": { "kind": "int", "value": 42 } } ] + }, + "rhs": { + "t": "do", + "op": "io/print_int", + "args": [ { "t": "lit", "lit": { "kind": "int", "value": 1337 } } ] + } + } + } + ] +} +``` \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_1_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_1_request.json new file mode 100644 index 0000000..a892330 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_1_request.json @@ -0,0 +1,10 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t3_main_prints. It must export main : () -> () !IO that prints 42 and then 1337, each on its own line. No other definitions are required." + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_1_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_1_response.json new file mode 100644 index 0000000..7e7655c --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_1_response.json @@ -0,0 +1,8 @@ +{ + "content": " ```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"t3_main_prints\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1337 } } ]\n }\n }\n }\n ]\n}\n```", + "usage": { + "completion_tokens": 323, + "prompt_tokens": 10830, + "total_tokens": 11153 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_1_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_1_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_2_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_2_check_stderr.txt new file mode 100644 index 0000000..a8a163b --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_2_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-ZgLEef/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_2_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_2_program.ail.json new file mode 100644 index 0000000..65d4a87 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_2_program.ail.json @@ -0,0 +1,9 @@ + The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one. + +Here are a few possible causes for this error: + +1. The JSON file is not properly formatted. Make sure that the file is valid JSON and that all of the curly braces, square brackets, and colons are properly matched. +2. The JSON file is missing a value at the specified location. Check the file to make sure that there is a value at the location where the parser is expecting one. +3. The JSON file contains a syntax error. Check the file for any syntax errors, such as missing commas or mismatched quotes. + +To fix the error, you will need to identify the cause of the problem and then correct it. If you are not sure how to do this, you may want to try using a JSON validator tool to help you identify the problem. \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_2_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_2_request.json new file mode 100644 index 0000000..eb07f8c --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_2_request.json @@ -0,0 +1,18 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t3_main_prints. It must export main : () -> () !IO that prints 42 and then 1337, each on its own line. No other definitions are required." + }, + { + "role": "assistant", + "content": " ```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"t3_main_prints\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1337 } } ]\n }\n }\n }\n ]\n}\n```" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-EoxPyG/prog.ail.json: expected value at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_2_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_2_response.json new file mode 100644 index 0000000..a61b977 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_2_response.json @@ -0,0 +1,8 @@ +{ + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible causes for this error:\n\n1. The JSON file is not properly formatted. Make sure that the file is valid JSON and that all of the curly braces, square brackets, and colons are properly matched.\n2. The JSON file is missing a value at the specified location. Check the file to make sure that there is a value at the location where the parser is expecting one.\n3. The JSON file contains a syntax error. Check the file for any syntax errors, such as missing commas or mismatched quotes.\n\nTo fix the error, you will need to identify the cause of the problem and then correct it. If you are not sure how to do this, you may want to try using a JSON validator tool to help you identify the problem.", + "usage": { + "completion_tokens": 215, + "prompt_tokens": 11189, + "total_tokens": 11404 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_2_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_2_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_3_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_3_check_stderr.txt new file mode 100644 index 0000000..e186c8c --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_3_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-qgrEEu/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_3_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_3_program.ail.json new file mode 100644 index 0000000..65d4a87 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_3_program.ail.json @@ -0,0 +1,9 @@ + The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one. + +Here are a few possible causes for this error: + +1. The JSON file is not properly formatted. Make sure that the file is valid JSON and that all of the curly braces, square brackets, and colons are properly matched. +2. The JSON file is missing a value at the specified location. Check the file to make sure that there is a value at the location where the parser is expecting one. +3. The JSON file contains a syntax error. Check the file for any syntax errors, such as missing commas or mismatched quotes. + +To fix the error, you will need to identify the cause of the problem and then correct it. If you are not sure how to do this, you may want to try using a JSON validator tool to help you identify the problem. \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_3_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_3_request.json new file mode 100644 index 0000000..8bc3ad1 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_3_request.json @@ -0,0 +1,26 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t3_main_prints. It must export main : () -> () !IO that prints 42 and then 1337, each on its own line. No other definitions are required." + }, + { + "role": "assistant", + "content": " ```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"t3_main_prints\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1337 } } ]\n }\n }\n }\n ]\n}\n```" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-EoxPyG/prog.ail.json: expected value at" + }, + { + "role": "assistant", + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible causes for this error:\n\n1. The JSON file is not properly formatted. Make sure that the file is valid JSON and that all of the curly braces, square brackets, and colons are properly matched.\n2. The JSON file is missing a value at the specified location. Check the file to make sure that there is a value at the location where the parser is expecting one.\n3. The JSON file contains a syntax error. Check the file for any syntax errors, such as missing commas or mismatched quotes.\n\nTo fix the error, you will need to identify the cause of the problem and then correct it. If you are not sure how to do this, you may want to try using a JSON validator tool to help you identify the problem." + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-ZgLEef/prog.ail.json: expected value at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_3_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_3_response.json new file mode 100644 index 0000000..37def45 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_3_response.json @@ -0,0 +1,8 @@ +{ + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible causes for this error:\n\n1. The JSON file is not properly formatted. Make sure that the file is valid JSON and that all of the curly braces, square brackets, and colons are properly matched.\n2. The JSON file is missing a value at the specified location. Check the file to make sure that there is a value at the location where the parser is expecting one.\n3. The JSON file contains a syntax error. Check the file for any syntax errors, such as missing commas or mismatched quotes.\n\nTo fix the error, you will need to identify the cause of the problem and then correct it. If you are not sure how to do this, you may want to try using a JSON validator tool to help you identify the problem.", + "usage": { + "completion_tokens": 215, + "prompt_tokens": 11440, + "total_tokens": 11655 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_3_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_3_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_4_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_4_check_stderr.txt new file mode 100644 index 0000000..2af6bcc --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_4_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-pC7zF4/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_4_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_4_program.ail.json new file mode 100644 index 0000000..65d4a87 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_4_program.ail.json @@ -0,0 +1,9 @@ + The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one. + +Here are a few possible causes for this error: + +1. The JSON file is not properly formatted. Make sure that the file is valid JSON and that all of the curly braces, square brackets, and colons are properly matched. +2. The JSON file is missing a value at the specified location. Check the file to make sure that there is a value at the location where the parser is expecting one. +3. The JSON file contains a syntax error. Check the file for any syntax errors, such as missing commas or mismatched quotes. + +To fix the error, you will need to identify the cause of the problem and then correct it. If you are not sure how to do this, you may want to try using a JSON validator tool to help you identify the problem. \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_4_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_4_request.json new file mode 100644 index 0000000..a02609f --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_4_request.json @@ -0,0 +1,34 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t3_main_prints. It must export main : () -> () !IO that prints 42 and then 1337, each on its own line. No other definitions are required." + }, + { + "role": "assistant", + "content": " ```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"t3_main_prints\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1337 } } ]\n }\n }\n }\n ]\n}\n```" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-EoxPyG/prog.ail.json: expected value at" + }, + { + "role": "assistant", + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible causes for this error:\n\n1. The JSON file is not properly formatted. Make sure that the file is valid JSON and that all of the curly braces, square brackets, and colons are properly matched.\n2. The JSON file is missing a value at the specified location. Check the file to make sure that there is a value at the location where the parser is expecting one.\n3. The JSON file contains a syntax error. Check the file for any syntax errors, such as missing commas or mismatched quotes.\n\nTo fix the error, you will need to identify the cause of the problem and then correct it. If you are not sure how to do this, you may want to try using a JSON validator tool to help you identify the problem." + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-ZgLEef/prog.ail.json: expected value at" + }, + { + "role": "assistant", + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible causes for this error:\n\n1. The JSON file is not properly formatted. Make sure that the file is valid JSON and that all of the curly braces, square brackets, and colons are properly matched.\n2. The JSON file is missing a value at the specified location. Check the file to make sure that there is a value at the location where the parser is expecting one.\n3. The JSON file contains a syntax error. Check the file for any syntax errors, such as missing commas or mismatched quotes.\n\nTo fix the error, you will need to identify the cause of the problem and then correct it. If you are not sure how to do this, you may want to try using a JSON validator tool to help you identify the problem." + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-qgrEEu/prog.ail.json: expected value at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_4_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_4_response.json new file mode 100644 index 0000000..8394c7f --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_4_response.json @@ -0,0 +1,8 @@ +{ + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible causes for this error:\n\n1. The JSON file is not properly formatted. Make sure that the file is valid JSON and that all of the curly braces, square brackets, and colons are properly matched.\n2. The JSON file is missing a value at the specified location. Check the file to make sure that there is a value at the location where the parser is expecting one.\n3. The JSON file contains a syntax error. Check the file for any syntax errors, such as missing commas or mismatched quotes.\n\nTo fix the error, you will need to identify the cause of the problem and then correct it. If you are not sure how to do this, you may want to try using a JSON validator tool to help you identify the problem.", + "usage": { + "completion_tokens": 215, + "prompt_tokens": 11691, + "total_tokens": 11906 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_4_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_4_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_5_check_stderr.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_5_check_stderr.txt new file mode 100644 index 0000000..fed27af --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_5_check_stderr.txt @@ -0,0 +1 @@ +parsing JSON in /tmp/cma2-run-Xy2thw/prog.ail.json \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_5_program.ail.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_5_program.ail.json new file mode 100644 index 0000000..65d4a87 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_5_program.ail.json @@ -0,0 +1,9 @@ + The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one. + +Here are a few possible causes for this error: + +1. The JSON file is not properly formatted. Make sure that the file is valid JSON and that all of the curly braces, square brackets, and colons are properly matched. +2. The JSON file is missing a value at the specified location. Check the file to make sure that there is a value at the location where the parser is expecting one. +3. The JSON file contains a syntax error. Check the file for any syntax errors, such as missing commas or mismatched quotes. + +To fix the error, you will need to identify the cause of the problem and then correct it. If you are not sure how to do this, you may want to try using a JSON validator tool to help you identify the problem. \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_5_request.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_5_request.json new file mode 100644 index 0000000..318b24a --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_5_request.json @@ -0,0 +1,42 @@ +[ + { + "role": "system", + "content": "# AILang authoring reference\n\n## 1. Header\n\nAILang is a small data-as-source functional language designed for an\nLLM author. This document is the complete authoring reference. Every\nwell-formed program is a module. Read the whole thing once before you\nwrite code; the order of sections is the order of dependencies.\n\n## 2. Modules and the on-disk form\n\nA module is the top-level container. It has a name (matching the file\nstem), an optional list of imports, and a flat list of definitions.\nEvery program you write is exactly one module. Cross-module imports\nare out of scope for the tasks in this experiment — every task is a\nsingle self-contained module with an empty `imports` array.\n\nA definition is the unit that gets a content hash. There are five\nkinds of definition: function, constant, data type, class, and\ninstance. Their order within the module is preserved on disk but is\nnot load-bearing semantically — forward references are legal.\n\nThe schema of the on-disk form is fixed. The toolchain rejects any\nmodule that does not conform.\n\nThe on-disk form is a single JSON object with the following required\ntop-level fields, in canonical key order:\n\n- `schema`: the string `\"ailang/v0\"`. Any other value is rejected at\n load time.\n- `name`: the module name (matches the file stem on disk).\n- `imports`: array of import objects. May be empty.\n- `defs`: array of definition objects. Order is the declared order;\n the hash is computed over the canonical-keys-sorted byte form so\n inserting fields in source order is safe.\n\nCanonical key order means object keys are emitted in sorted order\nwhen the toolchain computes the canonical bytes for hashing; for\nauthoring you can write keys in whatever order is clearest. Numeric\nliterals are bare JSON numbers; strings are JSON strings. Every\ndefinition object carries a `kind` discriminator (`fn`, `const`,\n`type`, `class`, `instance`). Every term object carries a `t`\ndiscriminator. Every type object carries a `k` discriminator. Every\npattern object carries a `p` discriminator. Every literal object\ncarries a `kind` discriminator.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_returns_int\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"answer\",\n \"doc\": \"The simplest possible fn — no params, returns a fixed Int.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } }\n }\n ]\n}\n```\n\n## 3. Types\n\nA type is one of four shapes. Every type position in a function\nsignature must be filled in; there is no implicit type inference at\nthe binding form. Constructor types may carry type arguments.\nFunctions are first-class — function types appear as parameter and\nreturn types.\n\nA type constructor names a base type or a user-defined ADT. The\nprelude provides `Int`, `Bool`, `Str`, `Float`, `Unit`. User ADTs are\nintroduced with the `data` form (see section 6); after declaration\ntheir name is usable wherever a type constructor is expected.\n\nA type variable is a placeholder introduced by `forall` at the top\nof a function signature. Within the signature the variable behaves\nas an opaque type; the toolchain instantiates it at each call site.\n\nA function type names parameter types, parameter modes (see section\n4), a return type, a return mode, and an effect row (see section 8).\n\nA `forall` is universal quantification: it lists the type variables\nbound and the body type they appear in. Forall is only valid at the\ntop of a function or constant signature.\n\nType::Con: `{\"k\": \"con\", \"name\": \"Int\"}` for a base type; for a\nparameterised type, `{\"k\": \"con\", \"name\": \"List\", \"args\": [...]}`.\nThe `args` field is omitted when empty (canonical-bytes stable).\n\nType::Var: `{\"k\": \"var\", \"name\": \"a\"}`. Variable names are bare\nidentifiers; conventionally lower-case single letters.\n\nType::Fn: `{\"k\": \"fn\", \"params\": [...], \"ret\": ..., \"effects\": [...]}`\nwith optional `param_modes` and `ret_mode` fields. The `effects`\narray is a set; sort and dedup at authoring time for readability.\n\nType::Forall: `{\"k\": \"forall\", \"vars\": [...], \"body\": ...}` with an\noptional `constraints` array for class constraints (see section 10).\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"forall_polymorphic\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"id\",\n \"doc\": \"The polymorphic identity function. Exercises Type::Forall and Type::Var.\",\n \"type\": {\n \"k\": \"forall\",\n \"vars\": [\"a\"],\n \"body\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"var\", \"name\": \"a\" },\n \"effects\": []\n }\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 4. Mode annotations\n\nEvery function parameter carries a mode. The mode says how the\ncallee may treat the value: `implicit` is the legacy default\n(treated as `own`), `own` means the caller transfers ownership and\nthe callee consumes the value, `borrow` means the caller retains\nownership and the callee may inspect but not consume.\n\nThe mode also applies to the return value: `ret_mode` says whether\nthe function transfers ownership to the caller (`own`) or returns a\nborrow (`borrow`). For most functions the return mode is `own` or\nimplicit; `borrow` returns are rare and used only for accessor-style\nfunctions.\n\nModes are mandatory on every parameter and on the return position.\nThe schema rejects unannotated fn signatures only when annotations\nare explicitly requested; for compatibility the legacy form (no\nmode field at all) is treated as all-implicit.\n\nOn `Type::Fn`, two optional fields carry modes:\n\n- `param_modes`: array of strings `\"implicit\"`, `\"own\"`, or\n `\"borrow\"`, one per parameter, in the same order as `params`. The\n array is omitted from the canonical bytes when every entry is\n `\"implicit\"`; this is purely a serialisation optimisation. Author\n the field explicitly when at least one parameter is `own` or\n `borrow`.\n- `ret_mode`: a single string of the same vocabulary. Omitted when\n it would be `\"implicit\"`.\n\nExample: a fn that borrows two ints and returns an int:\n`\"param_modes\": [\"borrow\", \"borrow\"]`, `ret_mode` omitted (implicit).\nA fn that consumes a list and returns a new list:\n`\"param_modes\": [\"own\"]`, `\"ret_mode\": \"own\"`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"param_modes_all\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"f_implicit\",\n \"doc\": \"Implicit mode is the legacy default — `param_modes` is omitted from canonical JSON when every entry is Implicit, which is the case here. The visitor still observes ParamMode::Implicit via the (defaulted) ret_mode field on Type::Fn.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_own\",\n \"doc\": \"(own T) — caller transfers ownership; callee consumes.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"f_borrow\",\n \"doc\": \"(borrow T) — caller retains ownership; callee may not consume.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": { \"t\": \"var\", \"name\": \"x\" }\n }\n ]\n}\n```\n\n## 5. Functions\n\nA function definition binds a name to a body of code. The signature\ndeclares the parameter types, the return type, and the effect set\n(if any). The body is an expression — there is no statement form.\nFunctions are first-class values: they may be passed to other\nfunctions, returned, stored in data structures.\n\nInside a function body, four term forms appear most often: a\nvariable reference (the name of a parameter, a local binding, or a\ntop-level definition); a function application (a callee plus\npositional arguments); a lambda (an anonymous function with its\nown signature); a literal value (see section 9).\n\nLambdas carry their own parameter list, parameter types, return\ntype, and effect set. Lambdas may close over names from the\nenclosing lexical scope; the closure is captured by value (the\ncaptured values are reference-counted under `--alloc=rc`).\n\nA `let` binds a name to the value of one expression for the\nduration of another expression. A `letrec` is the recursive variant:\nthe bound name is visible inside its own body. Both are\nexpressions, not statements — they have a value.\n\nFunction definition: `{\"kind\": \"fn\", \"name\": ..., \"type\": ...,\n\"params\": [...], \"body\": ...}` plus optional `doc`.\n\nTerm variants relevant here:\n\n- Variable reference: `{\"t\": \"var\", \"name\": \"x\"}`.\n- Application: `{\"t\": \"app\", \"fn\": ..., \"args\": [...]}`.\n- Lambda: `{\"t\": \"lam\", \"params\": [...], \"paramTypes\": [...],\n \"retType\": ..., \"effects\": [...], \"body\": ...}`.\n- Let: `{\"t\": \"let\", \"name\": \"x\", \"value\": ..., \"body\": ...}`.\n- Letrec: `{\"t\": \"letrec\", \"name\": \"go\", \"type\": ..., \"params\":\n [...], \"body\": ..., \"in\": ...}`.\n\nThe `paramTypes` and `retType` keys in `lam` use camelCase; the rest\nuse snake_case. This is historical and the canonical bytes preserve\nthe difference.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_calls_prelude\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"add\",\n \"doc\": \"Add two Ints via the polymorphic prelude `+`. Also exercises TermLet by binding the sum to a local name before returning it, and TermClone by re-using the let-bound value.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"Int\" }\n ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"x\", \"y\"],\n \"body\": {\n \"t\": \"let\",\n \"name\": \"s\",\n \"value\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n },\n \"body\": { \"t\": \"clone\", \"value\": { \"t\": \"var\", \"name\": \"s\" } }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_lambda\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"make_adder\",\n \"doc\": \"Curried adder: takes an Int and returns an Int -> Int closure. Exercises TermLam and Type::Fn appearing as a return type.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"effects\": []\n },\n \"params\": [\"x\"],\n \"body\": {\n \"t\": \"lam\",\n \"params\": [\"y\"],\n \"paramTypes\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"retType\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": [],\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"x\" },\n { \"t\": \"var\", \"name\": \"y\" }\n ]\n }\n }\n }\n ]\n}\n```\n\n## 6. Algebraic data types\n\nA data type declaration introduces a new named type with one or\nmore constructors. Each constructor takes zero or more positional\nargument types and produces a value of the declared type. ADTs may\nbe parameterised by type variables; the variables are listed before\nthe constructors and may appear in the constructor argument types.\n\nA constructor is invoked at the term level by naming the type and\nthe constructor plus the positional arguments. The runtime\nrepresentation under `--alloc=rc` is a tagged heap cell with one\nslot per constructor field; the tag identifies which constructor\nwas used.\n\nConstructor names live in their own namespace, separate from\nfunction names. The same identifier may be a function and a\nconstructor without conflict, though for readability the convention\nis constructors are capitalised and functions are lower-case.\n\nType definition: `{\"kind\": \"type\", \"name\": ..., \"vars\": [...],\n\"doc\": ..., \"ctors\": [...]}`. The `vars` array is the list of type\nparameter names; omitted when empty.\n\nEach ctor: `{\"name\": \"Cons\", \"fields\": [TYPE, TYPE, ...]}`.\n`fields` is a positional list of field types.\n\nConstructor invocation: `{\"t\": \"ctor\", \"type\": \"List\", \"ctor\":\n\"Cons\", \"args\": [...]}`. The `type` field disambiguates which ADT\nthe constructor belongs to (necessary because constructor names are\nnot globally unique). `args` is positional and matches the\nconstructor's declared `fields` in order; nullary constructors\ncarry `\"args\": []`.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_simple\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"Box\",\n \"vars\": [\"a\"],\n \"doc\": \"A unary box around a polymorphic value.\",\n \"ctors\": [\n {\n \"name\": \"MkBox\",\n \"fields\": [ { \"k\": \"var\", \"name\": \"a\" } ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"wrap_one\",\n \"doc\": \"Construct a Box Int via MkBox. The body uses (reuse-as src body) — a hint that the freshly-allocated Box should reuse the source's memory slot under --alloc=rc. Exercises TermCtor, ReuseAs, and Type::Var inside a ctor field position.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [\n { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] }\n ],\n \"param_modes\": [\"own\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Box\", \"args\": [ { \"k\": \"con\", \"name\": \"Int\" } ] },\n \"ret_mode\": \"own\",\n \"effects\": []\n },\n \"params\": [\"src\"],\n \"body\": {\n \"t\": \"reuse-as\",\n \"source\": { \"t\": \"var\", \"name\": \"src\" },\n \"body\": {\n \"t\": \"ctor\",\n \"type\": \"Box\",\n \"ctor\": \"MkBox\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 7. Pattern matching\n\nPattern matching is the way to inspect an ADT value. A `match`\nform takes a scrutinee expression plus one or more arms; each arm\npairs a pattern with a body. Arms are tried top-to-bottom; the\nfirst matching arm's body is evaluated. The toolchain checks\nexhaustiveness: every constructor of the scrutinee's type must be\nreachable through some arm, otherwise the typechecker errors.\n\nThere are four pattern shapes. A wildcard matches anything and\nbinds nothing. A variable pattern matches anything and binds the\nmatched value to the named identifier for use inside the arm body.\nA literal pattern matches only when the scrutinee is bit-equal to\nthe literal value; literal patterns work on Int, Bool, Str, and\nUnit (Float patterns are rejected at typecheck — see section 9).\nA constructor pattern matches when the scrutinee is built by the\nnamed constructor and recursively matches the constructor's\nfields.\n\nMatch expression: `{\"t\": \"match\", \"scrutinee\": ..., \"arms\": [...]}`.\nEach arm: `{\"pat\": PATTERN, \"body\": TERM}`.\n\nPattern variants:\n- Wildcard: `{\"p\": \"wild\"}` — no other fields.\n- Variable: `{\"p\": \"var\", \"name\": \"h\"}` — binds the scrutinee to\n `h`.\n- Literal: `{\"p\": \"lit\", \"lit\": LITERAL}` — same `lit` shape as a\n term literal.\n- Constructor: `{\"p\": \"ctor\", \"ctor\": \"Cons\", \"fields\": [PATTERN,\n PATTERN, ...]}` — positional sub-patterns matching the\n constructor's declared field types.\n\nPattern variables are linear: each name appears at most once in a\nsingle pattern. Repeating a name is a typecheck error.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"data_with_match\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"type\",\n \"name\": \"List\",\n \"doc\": \"Monomorphic singly-linked Int list — boxed, recursive.\",\n \"ctors\": [\n { \"name\": \"Nil\", \"fields\": [] },\n {\n \"name\": \"Cons\",\n \"fields\": [\n { \"k\": \"con\", \"name\": \"Int\" },\n { \"k\": \"con\", \"name\": \"List\" }\n ]\n }\n ]\n },\n {\n \"kind\": \"fn\",\n \"name\": \"head_or_zero\",\n \"doc\": \"Return the head of xs, or 0 if empty. Exercises TermMatch with two arms, PatternCtor (both nullary Nil and binary Cons), and PatternWild on the tail field.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"xs\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"var\", \"name\": \"h\" },\n { \"p\": \"wild\" }\n ]\n },\n \"body\": { \"t\": \"var\", \"name\": \"h\" }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"count_via_letrec\",\n \"doc\": \"Walk xs counting nodes using a local recursive let. Exercises TermLetRec and TermVar; the recursive `go` is bound locally.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"xs\"],\n \"body\": {\n \"t\": \"letrec\",\n \"name\": \"go\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"List\" } ],\n \"param_modes\": [\"borrow\"],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"ys\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"ys\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"ctor\", \"ctor\": \"Nil\", \"fields\": [] },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n },\n {\n \"pat\": {\n \"p\": \"ctor\",\n \"ctor\": \"Cons\",\n \"fields\": [\n { \"p\": \"wild\" },\n { \"p\": \"var\", \"name\": \"t\" }\n ]\n },\n \"body\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"+\" },\n \"args\": [\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } },\n {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"t\" } ]\n }\n ]\n }\n }\n ]\n },\n \"in\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"go\" },\n \"args\": [ { \"t\": \"var\", \"name\": \"xs\" } ]\n }\n }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"match_literal_pattern\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"classify\",\n \"doc\": \"Classify an Int via literal patterns plus a wildcard fallback. Exercises PatternLit and PatternWild.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"match\",\n \"scrutinee\": { \"t\": \"var\", \"name\": \"n\" },\n \"arms\": [\n {\n \"pat\": { \"p\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 100 } }\n },\n {\n \"pat\": { \"p\": \"wild\" },\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 200 } }\n }\n ]\n }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"sign_if\",\n \"doc\": \"Return -1 / 0 / 1 for the sign of n using nested TermIf. Exercises TermIf in both then/else positions plus TermApp on the polymorphic comparison.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"con\", \"name\": \"Int\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Int\" },\n \"effects\": []\n },\n \"params\": [\"n\"],\n \"body\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"<\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": -1 } },\n \"else\": {\n \"t\": \"if\",\n \"cond\": {\n \"t\": \"app\",\n \"fn\": { \"t\": \"var\", \"name\": \"==\" },\n \"args\": [\n { \"t\": \"var\", \"name\": \"n\" },\n { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } }\n ]\n },\n \"then\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 0 } },\n \"else\": { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } }\n }\n }\n }\n ]\n}\n```\n\n## 8. Effects\n\nEvery function type carries an effect row — a set of effect labels\nthat the function may raise. The two currently wired effects are\n`IO` (required to call effect operations like `io/print_int`) and\n`Diverge` (used for functions that may not terminate). An empty\neffect row marks the function as pure.\n\nEffect operations are reached through the `do` term, not through a\nregular function application. The `do` form names the operation\n(e.g. `io/print_int`) and lists the operation's arguments; the\ntypechecker resolves the operation against the prelude's effect-op\ntable, checks the arguments, and accumulates the operation's\neffect label into the enclosing function's effect row.\n\nA `seq` term sequences two effectful expressions: the left-hand\nside is evaluated for its effects and its result discarded, then\nthe right-hand side is evaluated and its value is the value of the\nwhole expression. `seq` is the canonical way to perform multiple\nIO operations one after the other inside a function body.\n\nEffect row on `Type::Fn`: the `effects` field is a JSON array of\nstrings (the effect labels). `[]` means pure. `[\"IO\"]` is the\ncommon effectful case. The toolchain compares effect rows modulo\norder; for readability sort the array alphabetically.\n\nEffect operation invocation: `{\"t\": \"do\", \"op\": \"io/print_int\",\n\"args\": [INT-TERM]}`. The `op` is a string naming the prelude\noperation. The `args` list matches the operation's declared\nparameter types.\n\nSequencing: `{\"t\": \"seq\", \"lhs\": EFFECT-TERM, \"rhs\": EFFECT-TERM}`.\nThe value of the `seq` is the value of `rhs`; `lhs`'s value is\ndiscarded but its effects count toward the enclosing fn's row.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"fn_with_do_seq\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 2 } } ]\n }\n }\n }\n ]\n}\n```\n\n## 9. Literals\n\nThere are five literal shapes. An integer literal is a signed\n64-bit decimal. A boolean literal is `true` or `false`. A string\nliteral is a UTF-8 sequence in double quotes; AILang restricts the\nauthored byte set to ASCII printable (Decision 6 Constraint 3) — non-\nASCII bytes are an authoring error. A unit literal is the value of\ntype Unit, written `(unit)` in AILX and `{\"kind\": \"unit\"}` in JSON.\n\nA Float literal is an IEEE-754 binary64 value. When you author\nFloats, write the decimal form (`3.14`) — the AILX parser converts\nto the 64-bit bit pattern and emits the canonical 16-character\nlowercase hex string into the JSON. You never author the hex\nencoding by hand; the toolchain owns that representation. NaN and\n±Inf are representable through the prelude constants `nan`, `inf`,\nand `neg_inf` (see section 11).\n\nFloat pattern matching is rejected at typecheck. IEEE-`==` makes\nFloat patterns semantically dubious (NaN never matches; equality\nis bit-exact, not approximate), so the language hard-rejects them\nto push authors toward comparison-based dispatch.\n\nLiteral variants and their JSON shapes:\n\n- Int: `{\"kind\": \"int\", \"value\": 42}` — value is a JSON number.\n- Bool: `{\"kind\": \"bool\", \"value\": true}` — value is a JSON bool.\n- Str: `{\"kind\": \"str\", \"value\": \"hi\"}` — value is a JSON string.\n- Unit: `{\"kind\": \"unit\"}` — no value payload.\n- Float: `{\"kind\": \"float\", \"bits\": \"400921fb54442d18\"}` — the\n `bits` field is a 16-character lowercase hex string of the IEEE-\n 754 binary64 bit pattern. The hex-string path keeps NaN and ±Inf\n representable (JSON numbers cannot encode them) and avoids\n serialisation drift across `serde_json` versions.\n\nFloat bit patterns are computed by the toolchain on `ail parse`,\nnot by the author. When you write `3.14` in AILX, the parser emits\n`\"bits\": \"40091eb851eb851f\"` in the JSON form.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"floats\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"const\",\n \"name\": \"pi\",\n \"doc\": \"Pi as a Float constant. Exercises Def::Const and Literal::Float — the bit pattern below is f64::to_bits(3.14).\",\n \"type\": { \"k\": \"con\", \"name\": \"Float\" },\n \"value\": { \"t\": \"lit\", \"lit\": { \"kind\": \"float\", \"bits\": \"40091eb851eb851f\" } }\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"bool_str\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"is_true\",\n \"doc\": \"Trivial Bool literal. Exercises Literal::Bool.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Bool\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"bool\", \"value\": true } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"greeting\",\n \"doc\": \"Trivial Str literal. Exercises Literal::Str.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"hi\" } }\n },\n {\n \"kind\": \"fn\",\n \"name\": \"unit_value\",\n \"doc\": \"Trivial Unit literal — needed so Literal::Unit appears at least once in the corpus.\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": []\n },\n \"params\": [],\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"unit\" } }\n }\n ]\n}\n```\n\n## 10. Typeclasses\n\nA typeclass is a named bundle of operations that may be\nimplemented for multiple types. The class declaration lists the\nmethods; each instance declaration provides the bodies for a\nspecific type. The compiler resolves method calls at the call site\nand monomorphises the implementation: there is no runtime dispatch.\n\nA class is parameterised by a single type variable (multi-parameter\nclasses are not supported in v1). Methods are declared with their\nfull type signature; the class parameter appears as a type variable\ninside the method type. Methods may carry an optional default body\nthat instances inherit unless they explicitly override.\n\nClass constraints attach to polymorphic function types: a function\n`forall a. (Eq a) => (a, a) -> Bool` requires its callers to\nprovide an `Eq` instance for `a` at the call site. The constraint\nappears inside the `forall` quantifier, in the `constraints` slot.\n\nThe prelude provides four built-in classes: `Eq` (equality), `Ord`\n(ordering with `<`, `<=`, `>`, `>=`), `Num` (arithmetic — `+`, `-`,\n`*`, `/`, `neg`), and `Bounded`. Their instances for Int, Bool,\nStr, Float, and Unit are built in; you do not declare them.\n\nClass declaration: `{\"kind\": \"class\", \"name\": \"Show\", \"param\": \"a\",\n\"methods\": [...]}`. Each method: `{\"name\": \"show\", \"type\": ...,\n\"default\": ...}` — `type` is the method's full signature with the\nclass parameter appearing as a `Type::Var`; `default` is an\noptional body (omit when the method is abstract-required).\n\nInstance declaration: `{\"kind\": \"instance\", \"class\": \"Show\",\n\"type\": ..., \"methods\": [...]}`. The `type` field is the concrete\ntype the class is applied to. Each method: `{\"name\": \"show\",\n\"body\": TERM}`.\n\nConstraint on a polymorphic fn: inside `Type::Forall`, the\n`constraints` field is an array of constraint objects: `{\"class\":\n\"Eq\", \"type\": TYPE}`. Empty when the polymorphic fn has no class\nconstraints; omitted from canonical bytes when empty.\n\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"class_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"A toy single-method class. The instance in instance_def.ail.json provides a body for `show` at type Int.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n }\n ]\n}\n```\n\n```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"instance_def\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"class\",\n \"name\": \"MyShow\",\n \"param\": \"a\",\n \"doc\": \"Re-declared here so this fixture is self-contained; class_def.ail.json declares the same class verbatim.\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [ { \"k\": \"var\", \"name\": \"a\" } ],\n \"ret\": { \"k\": \"con\", \"name\": \"Str\" },\n \"effects\": []\n }\n }\n ]\n },\n {\n \"kind\": \"instance\",\n \"class\": \"MyShow\",\n \"type\": { \"k\": \"con\", \"name\": \"Int\" },\n \"doc\": \"Trivial MyShow Int — show always returns the fixed string \\\"int\\\".\",\n \"methods\": [\n {\n \"name\": \"show\",\n \"body\": { \"t\": \"lit\", \"lit\": { \"kind\": \"str\", \"value\": \"int\" } }\n }\n ]\n }\n ]\n}\n```\n\n## 11. The prelude\n\nThe prelude provides a fixed set of operators, conversion functions,\nand effect operations that are in scope without any import. The\noperators are polymorphic over a small set of types and resolve at\nthe call site by the resolved argument type.\n\n| Name | Type | Notes |\n|------|------|------|\n| `+` `-` `*` `/` | `forall a. (a, a) -> a` | Int and Float; codegen filters at use site |\n| `%` | `(Int, Int) -> Int` | Int only |\n| `==` `!=` `<` `<=` `>` `>=` | `forall a. (a, a) -> Bool` | Int, Bool, Str, Float, Unit |\n| `not` | `(Bool) -> Bool` | Boolean negation |\n| `neg` | `forall a. (a) -> a` | Int and Float negation |\n| `int_to_float` | `(Int) -> Float` | Widening; codegen uses `sitofp` |\n| `float_to_int_truncate` | `(Float) -> Int` | Saturating truncation to zero |\n| `float_to_str` | `(Float) -> Str` | Formatted decimal |\n| `is_nan` | `(Float) -> Bool` | True only for NaN bit patterns |\n| `nan` `inf` `neg_inf` | `Float` | Bare-value constants |\n| `__unreachable__` | `forall a. a` | Polymorphic bottom; LLVM `unreachable` |\n\nEffect operations (reached through `do`, not `app`):\n\n| Op | Signature | Effect |\n|----|----------|-------|\n| `io/print_int` | `(Int) -> Unit` | `IO` |\n| `io/print_bool` | `(Bool) -> Unit` | `IO` |\n| `io/print_str` | `(Str) -> Unit` | `IO` |\n| `io/print_float` | `(Float) -> Unit` | `IO` |\n\n`int_to_str` is intentionally NOT in the prelude — it is type-\ninstalled in a future milestone but codegen-deferred pending the\nheap-Str ABI work. Do not call it.\n\n## 12. Content addressing\n\nAILang has content-addressed identity: every top-level definition\nhas a 16-character hash derived from its canonical bytes. The\ntoolchain computes hashes on `ail parse` and `ail check`; the\nauthor writes no hash literal in either form, and the JSON form\nhas no `hash` field. This is symmetric across forms and removes\nhash as a form-distinguishing factor. You author code; the\ntoolchain owns the identity.\n\n## 13. Out of scope, and closing directive\n\nThis reference does NOT teach: cross-module imports (every task is\na single self-contained module with an empty `imports` array),\nrefinement types (reserved in the schema but pass-through in this\nversion), point-free style (cut by Decision 6), operator\noverloading (cut), syntactic sugar of any kind (cut). The four\ntasks you will be given do not need any of these features.\n\nWhen asked to write a module, return the complete module text and\nnothing else. No markdown fences. No prose explanation. No\npreamble. The harness parses your response verbatim; any wrapping\ntext is treated as a parse error.\n" + }, + { + "role": "user", + "content": "Write a complete AILang module named t3_main_prints. It must export main : () -> () !IO that prints 42 and then 1337, each on its own line. No other definitions are required." + }, + { + "role": "assistant", + "content": " ```json\n{\n \"schema\": \"ailang/v0\",\n \"name\": \"t3_main_prints\",\n \"imports\": [],\n \"defs\": [\n {\n \"kind\": \"fn\",\n \"name\": \"main\",\n \"doc\": \"Print two Ints in sequence. Exercises TermDo, TermSeq, and the IO effect on a fn type. The trailing unit return is implicit in the last Do (op returns Unit).\",\n \"type\": {\n \"k\": \"fn\",\n \"params\": [],\n \"ret\": { \"k\": \"con\", \"name\": \"Unit\" },\n \"effects\": [\"IO\"]\n },\n \"params\": [],\n \"body\": {\n \"t\": \"seq\",\n \"lhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 42 } } ]\n },\n \"rhs\": {\n \"t\": \"do\",\n \"op\": \"io/print_int\",\n \"args\": [ { \"t\": \"lit\", \"lit\": { \"kind\": \"int\", \"value\": 1337 } } ]\n }\n }\n }\n ]\n}\n```" + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-EoxPyG/prog.ail.json: expected value at" + }, + { + "role": "assistant", + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible causes for this error:\n\n1. The JSON file is not properly formatted. Make sure that the file is valid JSON and that all of the curly braces, square brackets, and colons are properly matched.\n2. The JSON file is missing a value at the specified location. Check the file to make sure that there is a value at the location where the parser is expecting one.\n3. The JSON file contains a syntax error. Check the file for any syntax errors, such as missing commas or mismatched quotes.\n\nTo fix the error, you will need to identify the cause of the problem and then correct it. If you are not sure how to do this, you may want to try using a JSON validator tool to help you identify the problem." + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-ZgLEef/prog.ail.json: expected value at" + }, + { + "role": "assistant", + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible causes for this error:\n\n1. The JSON file is not properly formatted. Make sure that the file is valid JSON and that all of the curly braces, square brackets, and colons are properly matched.\n2. The JSON file is missing a value at the specified location. Check the file to make sure that there is a value at the location where the parser is expecting one.\n3. The JSON file contains a syntax error. Check the file for any syntax errors, such as missing commas or mismatched quotes.\n\nTo fix the error, you will need to identify the cause of the problem and then correct it. If you are not sure how to do this, you may want to try using a JSON validator tool to help you identify the problem." + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-qgrEEu/prog.ail.json: expected value at" + }, + { + "role": "assistant", + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible causes for this error:\n\n1. The JSON file is not properly formatted. Make sure that the file is valid JSON and that all of the curly braces, square brackets, and colons are properly matched.\n2. The JSON file is missing a value at the specified location. Check the file to make sure that there is a value at the location where the parser is expecting one.\n3. The JSON file contains a syntax error. Check the file for any syntax errors, such as missing commas or mismatched quotes.\n\nTo fix the error, you will need to identify the cause of the problem and then correct it. If you are not sure how to do this, you may want to try using a JSON validator tool to help you identify the problem." + }, + { + "role": "user", + "content": "check: parsing JSON in /tmp/cma2-run-pC7zF4/prog.ail.json: expected value at" + } +] \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_5_response.json b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_5_response.json new file mode 100644 index 0000000..8ffe776 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_5_response.json @@ -0,0 +1,8 @@ +{ + "content": " The error message you provided suggests that the JSON file you are trying to parse is not valid. The error message is indicating that the parser is expecting a value at a specific location in the file, but it is not finding one.\n\nHere are a few possible causes for this error:\n\n1. The JSON file is not properly formatted. Make sure that the file is valid JSON and that all of the curly braces, square brackets, and colons are properly matched.\n2. The JSON file is missing a value at the specified location. Check the file to make sure that there is a value at the location where the parser is expecting one.\n3. The JSON file contains a syntax error. Check the file for any syntax errors, such as missing commas or mismatched quotes.\n\nTo fix the error, you will need to identify the cause of the problem and then correct it. If you are not sure how to do this, you may want to try using a JSON validator tool to help you identify the problem.", + "usage": { + "completion_tokens": 215, + "prompt_tokens": 11944, + "total_tokens": 12159 + } +} \ No newline at end of file diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_5_run_stdout.txt b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/per_cohort/json/t3_main_prints/turn_5_run_stdout.txt new file mode 100644 index 0000000..e69de29 diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/scores.csv b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/scores.csv new file mode 100644 index 0000000..c301fe6 --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/scores.csv @@ -0,0 +1,9 @@ +cohort,task_id,first_attempt_green,turns_to_green,prompt_tokens,completion_tokens,error_classes,final_status +json,t1_add_three,false,INF,58921,1528,check,turn_limit +json,t2_length,false,INF,0,0,,api_failure +json,t3_main_prints,false,INF,57094,1183,check,turn_limit +json,t4_count_zeros,false,INF,0,0,,api_failure +ailx,t1_add_three,true,1,7296,172,,green +ailx,t2_length,false,INF,38779,857,parse,turn_limit +ailx,t3_main_prints,true,1,7263,74,,green +ailx,t4_count_zeros,false,INF,39679,1131,parse,turn_limit diff --git a/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/summary.md b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/summary.md new file mode 100644 index 0000000..5a63afb --- /dev/null +++ b/experiments/2026-05-12-cross-model-authoring/runs/2026-05-12-9197fd/summary.md @@ -0,0 +1,22 @@ +# Cross-model authoring-form test — run summary + +## Cohort: json + +- tasks: 4 +- reached green: 0 +- first-attempt green: 0 +- mean turns-to-green (green-only): NaN +- total prompt tokens: 116015 +- total completion tokens: 2711 +- most common error class: check (x2) + +## Cohort: ailx + +- tasks: 4 +- reached green: 2 +- first-attempt green: 2 +- mean turns-to-green (green-only): 1.00 +- total prompt tokens: 93017 +- total completion tokens: 2234 +- most common error class: parse (x2) +