# AILang — Designentscheidungen Dieses Dokument hält die Kernentscheidungen für AILang fest. Es ist mein Vertrag mit mir selbst über künftige Iterationen. Kürzungen statt Wachstum bevorzugen. ## Zielsetzung AILang ist eine Programmiersprache für LLM-Autoren. Sie wird zu LLVM IR kompiliert. Performance: nativ, ohne GC für den MVP. Optimiert für: - **Maschinenlesbarkeit** statt menschlicher Ergonomie. Quelle ist strukturiert. - **Lokales Reasoning.** Jede Definition trägt ihren vollständigen Typ und ihre Effekte. - **Beweisbarkeit.** Reine Kernsprache, explizite Effekte, optionale Refinements. - **Robustheit gegen Halluzinationen.** Symbole sind hashbar; Tools können Existenz verifizieren, ohne Kontextfenster zu verbrauchen. ## Entscheidung 1: Quelle = Daten, nicht Text Ein Modul ist ein JSON-Objekt mit einem festen Schema. Es gibt keinen Parser für Freitext. Tippfehler in Bezeichnern werden zu Hash-Lookup-Fehlern, die der Compiler direkt vorschlägt zu fixen. Eine Textform existiert (`.ail`, S-Expression-artig), aber nur als bidirektionale Projektion der JSON-Form. Sie ist für Menschen-Reviews und Diffs gedacht. **Kanonisches Format:** `.ail.json` mit deterministischer Schlüsselreihenfolge. ## Entscheidung 2: Content-addressed Definitionen Jede Top-Level-Definition hat einen `hash`-Wert (BLAKE3 über kanonisches JSON ohne das `hash`-Feld selbst). Verweise zwischen Definitionen erfolgen primär per Name — Namen sind für Lesbarkeit. Der Hash ist die kanonische Identität. Vorteile: - Refactoring durch Hinzufügen neuer Defs, nicht durch In-place-Änderung. Alte Versionen bleiben aufrufbar, bis manuell entfernt. - Caching von Typcheck-Ergebnissen und Codegen pro Hash. - Diffs zeigen exakt, welche Def sich geändert hat. ## Entscheidung 3: Reine Kernsprache + algebraische Effekte Default sind totale, reine Funktionen. Effekte werden als Set im Funktionstyp deklariert: `(Int) -> Int ![IO]`. Die Effektmenge ist row-polymorph (`![IO | r]`). Im MVP sind nur die Effekte `IO` und `Diverge` (für Endlosschleifen) verbaut. Dies ist die wichtigste LLM-Eigenschaft: Wenn ich eine Funktion lese, kann ich ihrer Signatur trauen, ohne den Body zu lesen. ## Entscheidung 4: Hindley-Milner + optionale Refinements MVP: HM mit Let-Polymorphismus. Alle Typen sind inferierbar, müssen aber im Top-Level immer explizit annotiert sein (für lokales Reasoning). Später: Refinement-Annotationen, die zu SMT escalieren. `(i: Int | i >= 0)`. Vom Anfang an im AST vorgesehen, aber im MVP einfach als opake Strings durchgereicht. ## Entscheidung 5: LLVM IR als Text emittieren Statt `inkwell` oder `llvm-sys`: AILang erzeugt `.ll`-Dateien als Strings und übergibt an `clang` zum Linken. Begründung: - LLVM-IR-Textsyntax ist über Versionen weitgehend stabil. - Keine Build-Abhängigkeit von einer bestimmten libllvm-Version. - Generierter Code ist trivial inspizierbar, was Debugging massiv vereinfacht. - LLM kann generierten IR direkt lesen, was bei opaken Bibliothekscalls schwerer ist. Trade-off: keine Inline-Optimierungen über die LLVM-API. Wir setzen auf `clang -O2` als Standard-Pipeline. ## Datenmodell (MVP) ### Module ```jsonc { "schema": "ailang/v0", "name": "", "imports": [{ "module": "", "as": "" }], "defs": [Def...] } ``` ### Def `kind ∈ { "fn", "type", "effect", "const" }`. Im MVP nur `fn` und `const`. ```jsonc { "kind": "fn", "name": "", "type": Type, "params": [""...], "body": Term, "doc": "" } ``` ### Term (Expression) ```jsonc { "t": "lit", "lit": { "kind": "int" | "bool" | "unit", "value": ... } } { "t": "var", "name": "" } { "t": "app", "fn": Term, "args": [Term...] } { "t": "let", "name": "", "value": Term, "body": Term } { "t": "if", "cond": Term, "then": Term, "else": Term } { "t": "do", "op": "/", "args": [Term...] } ``` `do` ist im MVP nur ein direkter Aufruf eines Built-in-Effekt-Ops (kein Handler). ### Type ```jsonc { "k": "con", "name": "Int" } { "k": "con", "name": "Bool" } { "k": "con", "name": "Unit" } { "k": "fn", "params": [Type...], "ret": Type, "effects": ["IO"...] } { "k": "var", "name": "a" } { "k": "forall", "vars": ["a"...], "body": Type } ``` ## Pipeline ``` .ail.json ─┐ ├─ load + validate schema ├─ resolve names + assign hashes ├─ typecheck (HM, effect rows) ├─ lower to MIR (SSA-ähnlich, named SSA-Werte) ├─ emit LLVM IR (.ll) └─ clang -O2 *.ll -o binary ``` ## CLI ``` ail check — Lädt, validiert, typecheckt ail manifest — Tabelle: name :: type !effects [hash] ail describe — Detail einer Definition ail render — JSON → Pretty-Text ail parse — Pretty-Text → JSON (für Bootstrapping) ail emit-ir — schreibt .ll ail build — komplette Pipeline → Binary ``` ## Verifikation und Korrektheit (über Zyklen) 1. **Snapshot-Tests** für Pretty-Printer und IR-Emit. Diff macht Regressions sofort sichtbar. 2. **Property-Tests** für Roundtrip JSON ↔ Pretty. 3. **End-to-End-Tests** für `examples/` mit erwartetem Programmoutput. 4. **Hash-Stabilität**: Test stellt sicher, dass dieselbe Def stets denselben Hash produziert. 5. **CI-Pin** der Outputs in `tests/expected/`. ## Was MVP NICHT ist - Keine ADTs / Pattern Matching (Phase 2). - Keine Closures / höhere Funktionen (Phase 2). - Keine Effekt-Handler (Phase 3). - Keine Refinements / SMT (Phase 4). - Kein Modulsystem über Imports hinaus (Phase 2). - Keine Strings als first-class. Nur ints + bools + unit. Der MVP ist erfolgreich, wenn `examples/sum.ail.json` ein Binary erzeugt, das die Summe 1..10 = 55 druckt.