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.
14 KiB
JOURNAL
Chronologische Notizen für mich. Nicht jede Änderung; nur Entscheidungen, Hindernisse, Beobachtungen, die zukünftige Iterationen brauchen.
2026-05-07 — Tag 0
- Repo initialisiert. Auftrag in
CLAUDE.md: LLM-native Sprache, LLVM-Backend. - Designentscheidungen festgehalten in
docs/DESIGN.md. - Toolchain:
rustc 1.94,llvm-config 22.1.3,clangvorhanden. - Entschieden gegen
inkwellzugunsten LLVM-IR-Text-Emit. Begründung im DESIGN.md. - Workspace-Layout:
crates/ailang-core— AST, Type, Hash, JSON-Schemacrates/ailang-check— Typchecker (kommt später)crates/ailang-codegen— Lowering + LLVM IR Emitcrates/ail— CLI
- MVP-Ziel:
examples/sum.ail.json→ Binary, das 55 druckt. Erreicht.
2026-05-07 — Architektur-Review nach MVP
Wieder am Pfad? Im Großen ja. Konkrete Beobachtungen:
Was hält:
- JSON-AST + canonical form + content-hash sind alle Lego-Steine, auf denen
spätere Tools ohne Refactor aufsetzen können (
ail deps,ail diff). - LLVM-IR-Text-Pipeline arbeitet wie geplant. Keine libllvm-Versionsschmerzen.
- Effekt-Set ist im Typsystem von Anfang an verbaut. Erweiterbar zu row-poly, ohne den Kern zu ändern.
Schulden, die Zinsen tragen:
current_block_label_for_phiist eine Heuristik (siehe codegen). Bei verschachteltenif-Termen wird sie das falsche Block-Label zurückgeben, weil sie rückwärts den Body scannt. Tickend, weil keine Test-Fälle das bisher triggern. Muss als nächstes fixiert werden, bevor neue Sprachfeatures dazu kommen.- Kein typed-AST. Codegen liest direkt das Quell-AST und verlässt sich darauf, dass der Typchecker vorher lief. Für MVP OK; sobald ADTs oder Closures dazukommen, brauche ich eine getrennte typisierte IR-Stufe (TIR).
hash-Feld ist nicht im AST. Aktuell hashen wir das Def-Objekt direkt. Wenn ich Hashes später als Felder mitserialisiere (Caching), muss der Hash das Feld vor der Berechnung ausschließen.
Plan Iteration 2 (jetzt):
- Block-Label-Tracking sauber machen, verschachteltes-if-Test.
- Strings als Literal +
io/print_str. - Hello-world-Beispiel als zweiten E2E-Test.
- CLI:
--json-Output für maschinelle Konsumenten überall, wo es passt.
Plan Iteration 3:
ADTs + Pattern Matching. Das ist der nächste große Sprung. Erfordert typisierte IR-Stufe (TIR), weil Pattern Matching zu Decision-Trees lowered wird, was eine andere Form ist als der AST.
2026-05-07 — Iteration 2 fertig
- Block-Label-Tracking robust (verschachtelte
ifs funktionieren). Testmax3_picks_largestschützt das. - Strings als
Lit::Str { value }, TypeStr-> LLVMptr, mitio/print_strEffekt-Op.examples/hello.ail.jsondruckt einen String. - CLI:
manifest --json,builtins --jsonfür Tool-Konsumenten. ail deps [--of NAME] [--json]listet Aufruf-Edges. Effekt-Ops sind alseffect:NAMEmarkiert, damit ein Konsument sie filtern kann.
Architektur-Check: Keine strukturellen Abweichungen. Codegen liest noch direkt das Quell-AST (TIR-Stufe wird mit ADTs in Iteration 3 nötig).
2026-05-07 — Iteration 3 fertig: ADTs
- TypeDef im AST mit Ctors. Ein Ctor hat
nameundfields: [Type...]. - Term::Ctor (Konstruktion) und Term::Match (Pattern Matching).
- Patterns:
Wild,Var,Lit,Ctor { ctor, fields }. Im MVP sind nested Ctor-Patterns NICHT erlaubt — Sub-Patterns müssenVaroderWildsein. - Typchecker mit Type-Registry und
ctor_index(ctor-name → ADT). Im Match wird Exhaustiveness gegen die volle Konstruktormenge geprüft. Negativ-Test schützt das. - Codegen: Boxed-Heap-Layout. Pro Ctor-Anwendung
malloc(8 + 8*n)Bytes; Tag in offset 0, Felder ab offset 8 (8-Byte-Slots, native typed load/store). Match: load tag + switch + arm-blocks + phi am Join. examples/list.ail.json(Cons/Nil-Liste, sum_list über match) liefert 42.
Erstaunlich problemlos. Die Architekturentscheidungen aus Tag 0 haben sich ausgezahlt: opaque ptr in LLVM 22 macht Boxed-Layout fast ohne Glue-Code möglich; das Effekt-Tracking blieb von ADTs unberührt; der JSON-AST nimmt neue Knoten-Typen sauber auf.
Gewachsene Schulden:
- Codegen liest immer noch direkt den Quell-AST. Die Versuchung war stark, ohne TIR weiterzumachen — und hat funktioniert, weil meine Match-Restriktionen flach sind (keine nested Patterns). Sobald nested Patterns kommen, braucht es ein Decision-Tree-Lowering, das ohne TIR nicht sauber wird. Schulden anerkannt; nicht jetzt fällig.
- Kein GC. Heap leakt. Akzeptabel für Demo-Programme; muss vor jedem längerläufigen Programm angegangen werden. Optionen für Phase 4: Refcount, Boehm-GC-Linkage, Region-Inference.
- Pretty-Printer für ADT-Werte zur Laufzeit fehlt.
io/print_intreicht für Demos, aber ein generischesshow :: a -> Strfür ADTs wäre wertvoll. Erfordert dispatch über tag — machbar, aber nicht jetzt.
Plan Iteration 4:
Die nächsten Schritte sind weniger eindeutig. Drei Kandidaten in Prioritätsreihenfolge:
- Modulsystem (Imports). Aktuell ist alles in einem Modul. Mit mehreren Modulen + Cross-Module-Hashing wird die Sprache erst praktikabel für mehrere Defs.
- Strukturierte Fehlerausgabe (
ail check --json). Damit Tools auf Typfehler reagieren können, ohne Text zu parsen. - Closures / höherwertige Funktionen. Erfordert Closure-Konvertierung und ist ein größerer Schritt.
Iteration 4 wird (1) + (2) — beides macht das LLM-Tooling stärker und hat moderates Risiko.
2026-05-07 — Workflow-Wechsel: Orchestrator + Agent-Repo
Auf Anregung des Auftraggebers Wechsel auf Orchestrator-Modus: ich delegiere abgrenzte Implementierungs-Brocken an Sub-Agenten und behalte nur Architektur-Entscheidungen, Reviews und Commit-Disziplin. Vier spezialisierte Agenten formuliert: implementer, architect, tester, debugger.
Wichtige Korrektur: Auftraggeber hat verlangt, dass die Agenten nicht
in .claude/agents/ versteckt liegen, sondern als sichtbarer Bestandteil
des Projekts unter agents/ versioniert werden. DESIGN.md hat einen neuen
Abschnitt "Projekt-Ökosystem" bekommen, der das festhält: AILang ist nicht
nur eine Sprache, sondern Sprachkern + CLI + Examples + Agents + Doku +
Tests, alle gleichwertig.
Aufruf-Schema: System-Prompt-Body aus agents/<name>.md als Präfix vor
die konkrete Aufgabe + an general-purpose-Agent. Funktional identisch
zum Subagent-Loading aus .claude/agents/, aber sichtbar im Repo.
Plan Iteration 4 (überarbeitet):
Modulsystem ist aufwändiger als erwartet (Cross-Module-Hashing, Import-Resolution). Erst die kleineren Tooling-Wins ziehen, dann das Modulsystem als Iteration 5:
- Strukturierte Fehlerausgabe (
ail check --jsonmit Diagnostic- Struct, stabile Codes wieunbound-var,type-mismatch). ail diff <a> <b>— semantischer Modul-Diff via Hash-Vergleich pro Def.- IR-Snapshot-Tests — Regressions-Schutz für die Codegen-Pipeline.
2026-05-07 — Iteration 4 fertig: LLM-Tooling-Konsolidierung
Drei Sub-Commits, jeder von einem ailang-implementer-Aufruf erzeugt
und stichprobenartig vom Orchestrator verifiziert:
93fe723Iter 4a:ail check --jsonmitDiagnostic-Struct (severity,code,message,def,ctx). Stabile Codes:unbound-var,type-mismatch,arity-mismatch,non-exhaustive-match,unknown-ctor,unknown-ctor-in-pattern,nested-ctor-pattern-not-allowed,duplicate-def,unknown-effect-op,unknown-type,schema-mismatch. API:check_module(&Module) -> Vec<Diagnostic>.c652b12Iter 4b:ail diff <a> <b> [--json]als struktureller Top- Level-Def-Diff via BLAKE3-Hash. Vier Kategorien (added/removed/ changed/unchanged), alphabetisch sortiert, Exit-Code 1 bei Diff.74a2005Iter 4c: IR-Snapshot-Tests incrates/ail/tests/snapshots/{sum,max3,hello,list}.ll. Normalisierung vontarget triple. Update viaUPDATE_SNAPSHOTS=1 cargo test ir_snapshot_. Mismatch erzeugt.actual-Datei.
Test-Stand: 28 (vorher 19). 7 E2E + 4 IR-Snapshot + 9 ailang-check + 1 ailang-codegen + 7 ailang-core.
Erledigt aus dem Schulden-Register:
- Block-Tracking im Codegen ist seit Iter 2 kein Heuristik-Risiko mehr;
die explizite
current_block: String-Spur wurde durch Iter 4c jetzt zusätzlich von Snapshot-Tests gegen Regression geschützt. Schuld geschlossen.
Neue / präzisierte Schulden:
check_moduleist single-shot — der erste Fehler bricht ab, kein Multi-Diagnose-Sammeln. Spec war so, aber das Format suggeriert Vec-Semantik. Echter Multi-Diagnose-Refactor wird billiger, sobald TIR existiert (zentraler Fehler-Akku durch separate Stufe). Nicht jetzt fällig.source_filenameim IR ist hartkodiert auf"<module>.ail". Solange es nur ein Top-Level-Modul gibt, plattform-stabil. Mit Iter 5 (Modulsystem + Imports) wird der Pfad relevant — dort gleich beim Bau pfad-unabhängig halten, sonst kippen die Snapshots.
Plan Iteration 5: Modulsystem mit Imports. Cross-Module-Hashing,
Import-Auflösung, mehrere .ail.json-Files in einem Build. Multi-
Diagnose-Refactor erst danach.
Sub-Schritte:
- 5a — Workspace-Loader.
ailang_core::Workspace { modules: BTreeMap<String, Module> }plusload_workspace(entry: &Path), dasimportsrekursiv vom Eintrittsmodul folgt. Konvention:import { module: "foo" }löst auf<dir>/foo.ail.jsonneben dem Entry. Zyklus- Erkennung. CLI: bestehende Subkommandos arbeiten weiter auf einzelnem Modul; ein neuesail workspace <entry>listet alle erreichbaren Module mit Hash. Tests: zwei kleine Beispielmodule mit Import-Beziehung; Zyklus-Test. - 5b — Cross-Module-Typcheck. Typchecker bekommt
&Workspacestatt&Module. Imports werden im Env als Namespace eingehängt (alias.defoder bei fehlendem Aliasmodule.def). Neue Diagnostic- Codes:unknown-module,unknown-import,import-cycle,ambiguous-name. Tests pro Code. - 5c — Cross-Module-Codegen. Emitter erzeugt IR für alle Module im
Workspace, prefix-mangelt mit
@ail_<module>_<def>. E2E-Test: Programm, das eine Funktion aus Modul B in Modul A nutzt, liefert korrektes Ergebnis im Binary. - 5d — Tooling-Anpassungen.
manifest,describe,deps,diffbekommen einen--workspace-Modus (rekursiv). Single-Modus bleibt Default für Rückwärtskompatibilität.
Während Iter 5 gleich beim Bau source_filename pfad-unabhängig
halten (nur Modulname, kein Verzeichnis-Prefix), sonst kippen die
IR-Snapshots.
2026-05-07 — Iter 5b fertig: Cross-Module-Typcheck
check_workspace(&Workspace) -> Vec<Diagnostic>als Top-Level-API.check_modulebleibt erhalten und hebt das Modul intern in einen Trivial-Workspace.- Konvention für qualifizierte Verweise (in DESIGN.md festgehalten):
Term::Var { name }mit genau einem Punkt =<prefix>.<def>. Prefix ist Import-Alias oder Modulname. Kein neuer AST-Knoten, keine umbenannten Felder ⇒ Hashes bleiben stabil; alleir_snapshot_*weiterhin grün. - Drei neue Diagnostic-Codes:
unknown-module,unknown-import,invalid-def-name(mitctx.reason: "contains-dot"). - CLI:
ail check <entry>lädt jetzt immer überload_workspace. Workspace-Lade-Fehler werden im JSON-Modus zu strukturierten Diagnostics mit Codesmodule-not-found,module-cycle,module-name-mismatch,module-hash-mismatch,schema-mismatch.ail buildundail emit-irbleiben pro Einzelmodul (Codegen cross-module ist 5c). - Beispiele:
ws_main.ail.jsonruft jetztws_lib.addauf (beobachtbar). Neu:ws_broken.ail.json(unknown-import),ws_unknown_module.ail.json(unknown-module). - Tests: 37 grün (vorher 32). 4 neue Workspace-Integrationstests in
crates/ailang-check/tests/workspace.rs, ein neuer e2e-Testcheck_workspace_resolves_import. - Schulden: Single-Shot-Diagnostics weiterhin (Multi-Diagnose nach 5c).
Punkt-Konvention deckt nur einen Punkt ab — verschachtelte Modulpfade
(
a.b.c) gibt es nicht; das wäre erst mit Hierarchie-Modulen ein Thema und fällt aktuell alsunbound-vardurch.
2026-05-07 — Iter 5c fertig: Cross-Module-Codegen
- Mangling-Bruch (mit Bedacht). Alle AILang-Funktionen heißen jetzt
@ail_<modul>_<def>, auch in Single-Modul-Programmen. Die alte Form@ail_<def>ist weg. Strings/Const-Globals analog (@.str_<modul>_<hint>_<idx>,@ail_<modul>_<const>). Eintrittspunkt bleibtmainals C-ABI: eindefine i32 @main()-Trampoline ruft@ail_<entry-modul>_main(). Fehlt im Eintrittsmodul einmain : () -> Unit !IO, fällt der Build mitMissingEntryMain. - Workspace-Lowering. Neue Top-Level-API
ailang_codegen::lower_workspace(ws: &Workspace) -> Result<String>produziert eine einzelne.llfür den ganzen Workspace. Module alphabetisch (BTreeMap-Order); Defs in AST-Reihenfolge. Cross-Module- Calls werden im Codegen über die Import-Map des aufrufenden Moduls aufgelöst — gleiche Logik wie im Typchecker, lokal dupliziert mit Verweis (kein gemeinsames Helper-Modul, weil die Typ-Welten unterschiedlich sind: Typchecker hantiert mitType, Codegen mitFnSigaus llvm-Typen). - CLI.
ail buildundail emit-irladen jetzt immer den Workspace und checken/lowern ihn vollständig. Single-Modul-Programme funktionieren weiter (Trivial-Workspace mit einem Modul).emit_ir(m)bleibt im Codegen-Crate als Bequemlichkeits-API erhalten und wrapt intern in einen Trivial-Workspace. - Snapshots regeneriert.
sum.ll,max3.ll,hello.ll,list.llzeigen das neue Mangling. Neuerws_main.ll-Snapshot dokumentiert den Cross-Module-Build:@ail_ws_main_mainruft@ail_ws_lib_addauf. - Tests. 40 grün (vorher 37). Neu:
workspace_build_runs_imported_fn(e2e: druckt 5),ir_snapshot_ws_main,missing_entry_main_is_error(codegen-unit). Bestehende Verhaltens-Tests (sum_1_to_10_is_55,max3_picks_largest,hello_world_str_lit,list_sum_via_match) bleiben grün — Verhalten unverändert, nur das Mangling ist neu.
Schulden geschlossen:
- #19 (
source_filenamehärten). In der Workspace-Welt istsource_filenamejetzt einheitlich<entry-modul>.ail, einmal pro Workspace. Der bisherige hartkodierte Pfad-Punkt entfällt damit.
Stand: Modulsystem ist Ende-zu-Ende geschlossen — Loader + Typcheck
- Codegen + Build sehen den Workspace als kohärente Einheit. Multi- Diagnose-Refactor und ggf. ADT-Cross-Module bleiben für später.