Commit Graph

1022 Commits

Author SHA1 Message Date
Brummel 849eca4fcf Iter 9a/9b: dogfood (list_map) + ail run
Iter 9a — dogfood. examples/list_map.ail.json exercises everything
Iter 1-8 shipped in one program: ADTs (IntList = Nil | Cons Int IntList),
recursive fns over the ADT (map_int, print_list), pattern matching with
nested Var fields, a closure (`\\x. x * 2`, no captures), fn-typed
parameters, IO effects propagating through recursion, and `let`-
sequencing of an effectful sub-expression inside a match arm.

Result: nothing broke. The full pipeline (typecheck → IR emit →
clang → run) produces "2\\n4\\n6\\n". The language is now sufficient
for "small but real" programs.

Iter 9b — `ail run`. Convenience subcommand that builds into a
tempdir and executes, propagating the binary's exit code. Equivalent
to `ail build && ./bin` in one step. Build logic factored out of
`Cmd::Build` into a shared `build_to` helper used by both Build and
Run.

Tests: 50 green (was 49). New e2e `list_map_doubles_then_prints`.
No test for `ail run` itself — the existing build_and_run helper in
e2e.rs already exercises the same build+exec sequence path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:09:18 +02:00
Brummel 91b9bf0581 Iter 8c: docs (DESIGN.md + JOURNAL.md)
DESIGN.md:
- Term schema: add `ctor`, `match`, `lam` rows. The schema fragment is
  now exhaustive for the supported language; prior versions silently
  dropped Iter 3/Iter 8 additions.
- "What is not (yet) supported": closures-with-capture moves out of
  pending. New positive bullet for anonymous lambdas. Polymorphism
  added as an explicit pending bullet (Forall is parseable but not
  inferred). Smoke-test list extended with closure.ail.json.

JOURNAL: append Iter 8 entry covering both 8a (closure-pair ABI) and
8b (Term::Lam + capture). Includes the rationale for skipping TIR
(KISS won — `synth` already provides enough type info), the closure-
conversion sketch, hash stability check (existing examples produce
the same fn hashes pre vs post Iter 8), and the Iter 9 plan with
two candidates ranked.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:05:46 +02:00
Brummel ecde8fa7af Iter 8b: lambdas with capture (Term::Lam + closure conversion)
Adds anonymous functions to AILang. A lambda value is constructed by
malloc'ing an env struct that holds its captured locals, plus a
closure pair `{ thunk_ptr, env_ptr }` (Iter 8a's ABI). The lambda's
body lifts to a top-level thunk `@ail_<m>_<def>_lam<id>(ptr %env,
params...)` that unpacks captures back into named locals before
running.

AST: new Term::Lam { params, paramTypes, retType, effects, body }.
Serde tag is "lam"; existing modules (no Lam) serialize identically,
so all current hashes stay stable.

Pretty-printer: renders `(\\ (x: T ...) -> R . body)`.

Typecheck (ailang-check): a lambda's type is the declared Type::Fn.
Body's effect set must be a subset of declared effects (no row
polymorphism). Constructing a lambda is pure — only calling it picks
up the declared effects, via the existing App branch.

Codegen (ailang-codegen):
- collect_captures: free-var walk; excludes builtins, current-module
  top-level fns, and qualified names.
- lower_lambda: per-fn lam counter, save/restore emitter state, emit
  thunk into a deferred queue (flushed after the parent fn), pack env
  + closure pair on heap, register the resulting closure-pair SSA in
  the sidetable so subsequent indirect calls find it.
- Capture sigs propagate: a fn-typed capture keeps its FnSig in the
  thunk's sidetable so `f(args)` inside the body still indirect-calls.
- Env layout: 8 bytes per capture (typed load/store reads only the
  needed bytes; padding wasted but uniform).

Tests: 49 green (was 48). New examples/closure.ail.json + e2e
`closure_captures_let_n` exercises `let n = 3 in apply(\\x. x + n, 39)`
end-to-end and asserts the binary prints 42.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 13:03:20 +02:00
Brummel 99f68e89fa Iter 8a: closure-pair ABI for fn-values
Every fn-value is now a `ptr` to a closure pair `{ ptr thunk, ptr env }`,
not a raw fn-pointer. Top-level fns get an auto-generated adapter
`@ail_<m>_<f>_adapter(ptr %_env, params...)` that ignores env and
forwards to the real fn, plus a static closure constant
`@ail_<m>_<f>_clos = { @adapter, null }`. References to a fn as a
value return the closure-pair address.

Indirect calls now GEP the thunk + env slots, load both, and call
`thunk(env, args...)`. Direct calls to statically-known callees stay
on the original fast path (no adapter).

This is the ABI groundwork for Iter 8b lambdas: a captured-env closure
will reuse the same value shape, just with a non-null env produced by
malloc.

Tests: 48 still green. IR snapshots refreshed (every fn now carries an
adapter + static closure pair). Iter 7's hof.ail.json prints 42
unchanged — `apply(inc, 41)` now hands `@ail_hof_inc_clos` to apply,
which unpacks and indirect-calls as designed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:56:12 +02:00
Brummel c6c0a10788 Iter 7: first-class function references (no capture)
Top-level fn names are now usable as values, fn-typed parameters can
be called as `f(args)`. KISS slice of "closures + HOFs + TIR": no
TIR, no heap, no ABI shift — that bundle stays in Iter 8 where
closure-with-capture and lambdas land together.

Codegen:
- Type::Fn lowers to LLVM `ptr`; sig travels via a per-fn-body
  `ssa_fn_sigs` sidetable on `Emitter`.
- Term::Var falls through to `resolve_top_level_fn` when the name is
  not a local; emits `@ail_<m>_<def>` and registers the sig.
- Term::App splits: static callee (builtin / current-module / qualified)
  keeps the existing direct path; otherwise lower the callee, look up
  the sig, emit indirect `call <ret> (<param-tys>) %fn(args...)`.
- emit_fn registers fn-typed params in the sidetable on entry.
- If branches propagate a fn-pointer sig to their phi when both arms
  match.

Typechecker: unchanged. It already accepted `synth(callee)` against
Type::Fn; the only blocker was codegen rejecting non-Var callees.

Tests: 48 green (was 47). New `examples/hof.ail.json` and e2e
`higher_order_apply_inc` exercise `apply(inc, 41) == 42` end-to-end.

DESIGN.md: "What is not (yet) supported" rewritten — closures-with-
capture and lambdas remain pending, first-class fn-refs added as
positive bullet. Smoke-test list extended with `hof.ail.json`.

JOURNAL.md: Iter 7 entry with rationale, scope choice, architecture
self-check, Iter 8 plan.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:44:10 +02:00
Brummel 1a448309fa Iter 6: deps hardening, multi-diagnose, DESIGN audit
- ail deps now filters builtins, fn params, and let/match-pattern
  bindings. New value_names() in ailang-check::builtins is the single
  source of truth shared with the typechecker install path. walk_term
  threads a scope set; qualified `prefix.def` refs pass through.
- check_in_workspace returns Vec<CheckError>; check_workspace
  accumulates body diagnostics across defs and modules. Pass-1
  (top-level symbol table) and per-module type-def setup stay
  fail-fast — corrupt env would taint later diagnostics.
- DESIGN.md "What the MVP is NOT" was lying (ADTs, strings landed
  in Iter 2/3). Renamed to "What is not (yet) supported" and split
  into "not yet" + supported/smoke-tested. JOURNAL Iter 6 entry
  records the architecture self-check.

Tests: 47 green (was 44). +2 deps filter tests in e2e,
+1 multi-diagnose test in ailang-check workspace integration.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:31:20 +02:00
Brummel efd209779c Translate remaining German content to English
Finishes the project-wide English convention:
- CLAUDE.md (the user opted in to translate it too).
- examples/hello.ail.json: "Hallo, AILang." -> "Hello, AILang.".
- E2E test assertion and IR snapshot for hello regenerated to match.
- DESIGN.md "Project language: English" section: the previous
  CLAUDE.md exception is dropped.

After this commit, no German remains in any committed file
(grep -P '[äöüÄÖÜß]' is empty across .rs / .md / .json / .ll).

Verified: cargo test --workspace passes (44/44).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:20:18 +02:00
Brummel 7577ab8a90 Translate project content to English
Make English the project-wide language: all comments, string literals,
CLI help text, design docs, journal, agent prompts, and README. Only
CLAUDE.md (the user's own instruction file) stays German, and the live
conversation between user and Claude continues in German.

Adds a new "Project language: English" section to docs/DESIGN.md as
the durable convention. No logic changes — translation only. Four
internal error strings in the typechecker were retranslated; no
test asserts on their wording.

Verified: cargo test --workspace passes (44/44).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 12:17:48 +02:00
Brummel b1dbafc6f2 Doku-Drift fixen + Iter-5d-JOURNAL nachreichen
JOURNAL bekommt fehlenden Iter-5d-Eintrag und Iter-5-Architektur-
Review-Notiz mit Iter-6-Plan (Aufräumarbeiten). DESIGN.md korrigiert
zwei Drifts vom Architekt-Befund: @main ist i32 (C-/LLVM-ABI), nicht
i64; String-Globals tragen Hint @.str_<modul>_<hint>_<idx>.
2026-05-07 11:50:49 +02:00
Brummel 2114fb2f6a Iter 5d: --workspace-Modus für manifest, describe, deps, diff
Vier Tooling-Subkommandos können jetzt auf dem ganzen Workspace
arbeiten. Default bleibt single-modul (Rückwärtskompat). Manifest
listet alphabetisch nach (modul, name) mit module-Feld; describe
nimmt Punktnotation ws_lib.add an, ambig-Erkennung fallback;
deps gibt Cross-Module-Edges {from_module,from_def,to_module,to_def}
aus, Effekt-Ops separat; diff vergleicht workspace-übergreifend mit
added/removed/changed/unchanged_modules und nested Sub-Diff pro
changed_module. Helper diff_def_lists wird von Single- und
Workspace-Diff geteilt.
2026-05-07 11:46:28 +02:00
Brummel a20ab93c66 Iter 5c: Cross-Module-Codegen
Symbol-Mangling-Schema einheitlich auf @ail_<modul>_<def> umgestellt
(auch für Single-Modul-Programme), String-Globals als
@.str_<modul>_<idx>. main bleibt LLVM-/C-ABI-Eintrittspunkt und ist
ein Trampoline auf @ail_<entry>_main. lower_workspace emittiert eine
einzige .ll für den ganzen Workspace, alphabetisch nach Modulname,
Cross-Module-Calls über Import-Map aufgelöst. ail build / ail emit-ir
laufen jetzt durch den Workspace-Pfad. IR-Snapshots regeneriert,
neuer ws_main-Snapshot. E2E-Test workspace_build_runs_imported_fn
prüft, dass das Binary die importierte Funktion korrekt aufruft.
Schuld #19 (source_filename) durch einheitliches <entry>.ail-Schema
geschlossen.
2026-05-07 11:39:59 +02:00
Brummel b2878fe655 Iter 5b: Cross-Module-Typcheck mit qualifizierten Verweisen
Neue API check_workspace(&Workspace) -> Vec<Diagnostic>; check_module
hebt das Modul intern in einen Trivial-Workspace. Term::Var mit genau
einem Punkt im Namen ist ein qualifizierter Verweis <prefix>.<def>,
aufgelöst über Import-Map (alias-oder-modulname → echter Modulname).
Drei neue Diagnostic-Codes: unknown-module, unknown-import,
invalid-def-name. ail check lädt jetzt immer via load_workspace;
Loader-Fehler werden im JSON-Modus zu strukturierten Diagnostics
(module-not-found, module-cycle, …). DESIGN.md und JOURNAL.md
dokumentieren die Konvention. Hash-Stabilität: alle ir_snapshot_*-
Tests bitidentisch grün, kein neuer AST-Knoten.
2026-05-07 11:31:57 +02:00
Brummel 3451b5bd15 Iter 5a: Workspace-Loader mit Imports
ailang_core::Workspace + load_workspace folgt imports-Feld rekursiv
vom Eintrittsmodul. DFS mit Zyklus-Erkennung; Konvention: Modulname
== Dateiname (.ail.json). WorkspaceLoadError sammelt strukturiert:
Cycle, ModuleNotFound, ModuleNameMismatch, Schema, Io. Neues
ail workspace <entry> [--json] listet erreichbare Module mit
module_hash und Def-Count. Bestehende Subkommandos arbeiten weiter
pro Einzelmodul (Iter 5d).
2026-05-07 11:23:21 +02:00
Brummel 7619f20cd6 JOURNAL: Iter-5-Sub-Schritte 5a/5b/5c/5d präzisiert 2026-05-07 11:19:36 +02:00
Brummel 937c5211e8 JOURNAL: Iter 4 abgeschlossen, Schulden präzisiert
Architekt-Befund umgesetzt: Iter-4-Eintrag dokumentiert die drei
Sub-Commits (4a/4b/4c), den Test-Stand von 28, das Schließen der
Iter-2-Block-Tracking-Schuld und die zwei neuen Schulden
(single-shot check_module, hartkodiertes source_filename).
Iter-5-Plan steht: Modulsystem zuerst, Multi-Diagnose-Refactor danach.
2026-05-07 11:18:29 +02:00
Brummel 74a20055c3 Iter 4c: IR-Snapshot-Tests als Codegen-Regressions-Schutz
Vier Snapshots in crates/ail/tests/snapshots/ (sum, max3, hello, list)
sichern den erzeugten LLVM-IR. Test-Helper normalisiert target triple
und trailing whitespace, sonst byte-für-byte-Vergleich. Update via
UPDATE_SNAPSHOTS=1 cargo test ir_snapshot_. Mismatch erzeugt eine
.actual-Datei mit dem aktuellen Output für Diff-Inspektion.
2026-05-07 11:14:41 +02:00
Brummel c652b12582 Iter 4b: ail diff für semantischen Modul-Vergleich
ail diff <a> <b> [--json] vergleicht zwei Module strukturell per
Def-Hash. Vier Kategorien (added/removed/changed/unchanged),
alphabetisch sortiert, deterministisches JSON-Schema. Exit 1 bei
Unterschieden, Exit 0 bei Identität — skript-tauglich. Kein
Typcheck-Zwang, damit man auch kaputte Module diffen kann. Helper
def_name/def_kind in ailang-core für stabile Def-Identität.
2026-05-07 11:11:48 +02:00
Brummel 93fe7237e3 Iter 4a: ail check --json mit strukturierten Diagnostics
Neue Top-Level-API check_module(&Module) -> Vec<Diagnostic> in
ailang-check, plus stabile Codes (unbound-var, type-mismatch,
arity-mismatch, non-exhaustive-match, unknown-ctor-in-pattern,
duplicate-def, …). CLI bekommt --json-Flag für maschinenlesbares
Output, Exit 1 bei Errors. Text-Modus unverändert. E2E-Test
check_json_unbound_var sichert das Format ab.
2026-05-07 11:07:36 +02:00
Brummel 44243a515e Agenten als Toolchain-Bestandteil + Ökosystem-Notiz
Vier spezialisierte Agent-Definitionen (implementer, architect, tester,
debugger) im sichtbaren agents/-Verzeichnis. DESIGN.md erweitert um den
Abschnitt "Projekt-Ökosystem" — AILang ist Sprache + CLI + Examples +
Agents + Doku + Tests gleichermaßen. JOURNAL hält den Workflow-Wechsel
auf Orchestrator-Modus fest und ordnet Iteration 4 neu.
2026-05-07 11:02:24 +02:00
Brummel 21606c9340 Iter 3: ADTs + Pattern Matching
- AST: Def::Type mit Ctors; Term::Ctor (Konstruktion) und Term::Match
  mit Arm/Pattern. Patterns: Wild, Var, Lit, Ctor { ctor, fields } —
  Sub-Patterns im MVP auf Var/Wild beschränkt.
- Typchecker: Type-Registry, ctor_index für O(1)-Resolution, Pattern-
  Bindings, Exhaustiveness-Check gegen volle Konstruktormenge plus
  Negativ-Tests.
- Codegen: Boxed-Heap-Layout via malloc; Tag in Offset 0, Felder ab
  Offset 8 in 8-Byte-Slots. Match lowert zu load tag + switch + Phi
  am Join. Default-Block ist unreachable, wenn vom Typchecker geprüft.
- examples/list.ail.json: rekursive Int-Liste mit sum_list via match.
  E2E-Test + Exhaustiveness-Tests. 19/19 Tests grün.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 10:38:07 +02:00
Brummel 6e6b6a14fb Iter 2: verschachteltes if, Strings, JSON-Output, deps
- Codegen: current_block-Tracking ersetzt die Heuristik im phi-Lowering;
  verschachtelte if-Ausdrücke produzieren jetzt korrekte LLVM IR.
  examples/max3.ail.json + Test schützt gegen Regression.
- Strings: Lit::Str / Type Str / io/print_str Effekt-Op; Strings sind im
  MVP immutable Konstanten. examples/hello.ail.json als zweiter E2E-Test.
- CLI: --json für manifest und builtins; neuer deps-Subcommand listet
  statische Symbol-Referenzen pro Definition. Effekt-Ops mit Prefix
  effect: markiert.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 10:28:16 +02:00
Brummel 2fbcdba0b1 MVP: AILang-Sprache mit JSON-AST, Typchecker, LLVM-IR-Backend
Erste lauffähige Iteration. examples/sum.ail.json wird zu nativem Binary
kompiliert und druckt 55 (Summe 1..10) als End-to-End-Test.

Architektur:
- ailang-core: hashbares JSON-AST + canonical-form + pretty-printer
- ailang-check: monomorpher HM-Subset + Effekt-Set-Tracking
- ailang-codegen: LLVM-IR-Text-Emitter (kein libllvm-link)
- ail: CLI mit check/manifest/render/describe/emit-ir/build/builtins

Designentscheidungen sind in docs/DESIGN.md dokumentiert; der Verlauf
in docs/JOURNAL.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-07 10:18:32 +02:00