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>
This commit is contained in:
2026-05-07 10:18:32 +02:00
commit 2fbcdba0b1
21 changed files with 2633 additions and 0 deletions
+151
View File
@@ -0,0 +1,151 @@
//! AST-Knoten. Serde-Layout entspricht dem JSON-Schema in `docs/DESIGN.md`.
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Module {
pub schema: String,
pub name: String,
#[serde(default)]
pub imports: Vec<Import>,
pub defs: Vec<Def>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Import {
pub module: String,
#[serde(rename = "as", default, skip_serializing_if = "Option::is_none")]
pub alias: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
pub enum Def {
Fn(FnDef),
Const(ConstDef),
}
impl Def {
pub fn name(&self) -> &str {
match self {
Def::Fn(f) => &f.name,
Def::Const(c) => &c.name,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FnDef {
pub name: String,
#[serde(rename = "type")]
pub ty: Type,
pub params: Vec<String>,
pub body: Term,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub doc: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConstDef {
pub name: String,
#[serde(rename = "type")]
pub ty: Type,
pub value: Term,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub doc: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "t", rename_all = "lowercase")]
pub enum Term {
Lit { lit: Literal },
Var { name: String },
App {
#[serde(rename = "fn")]
callee: Box<Term>,
args: Vec<Term>,
},
Let {
name: String,
value: Box<Term>,
body: Box<Term>,
},
If {
cond: Box<Term>,
then: Box<Term>,
#[serde(rename = "else")]
else_: Box<Term>,
},
Do {
op: String,
args: Vec<Term>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "kind", rename_all = "lowercase")]
pub enum Literal {
Int { value: i64 },
Bool { value: bool },
Unit,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "k", rename_all = "lowercase")]
pub enum Type {
Con {
name: String,
},
Fn {
params: Vec<Type>,
ret: Box<Type>,
#[serde(default)]
effects: Vec<String>,
},
Var {
name: String,
},
Forall {
vars: Vec<String>,
body: Box<Type>,
},
}
impl Type {
pub fn int() -> Type {
Type::Con { name: "Int".into() }
}
pub fn bool_() -> Type {
Type::Con { name: "Bool".into() }
}
pub fn unit() -> Type {
Type::Con { name: "Unit".into() }
}
}
impl PartialEq for Type {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Type::Con { name: a }, Type::Con { name: b }) => a == b,
(
Type::Fn { params: ap, ret: ar, effects: ae },
Type::Fn { params: bp, ret: br, effects: be },
) => {
ap == bp && ar == br && {
let mut a = ae.clone();
let mut b = be.clone();
a.sort();
b.sort();
a == b
}
}
(Type::Var { name: a }, Type::Var { name: b }) => a == b,
(
Type::Forall { vars: a, body: ab },
Type::Forall { vars: b, body: bb },
) => a == b && ab == bb,
_ => false,
}
}
}
impl Eq for Type {}