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>
This commit is contained in:
@@ -23,6 +23,7 @@ pub struct Import {
|
||||
pub enum Def {
|
||||
Fn(FnDef),
|
||||
Const(ConstDef),
|
||||
Type(TypeDef),
|
||||
}
|
||||
|
||||
impl Def {
|
||||
@@ -30,10 +31,26 @@ impl Def {
|
||||
match self {
|
||||
Def::Fn(f) => &f.name,
|
||||
Def::Const(c) => &c.name,
|
||||
Def::Type(t) => &t.name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TypeDef {
|
||||
pub name: String,
|
||||
pub ctors: Vec<Ctor>,
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub doc: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Ctor {
|
||||
pub name: String,
|
||||
#[serde(default)]
|
||||
pub fields: Vec<Type>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FnDef {
|
||||
pub name: String,
|
||||
@@ -80,6 +97,44 @@ pub enum Term {
|
||||
op: String,
|
||||
args: Vec<Term>,
|
||||
},
|
||||
/// Konstruktor-Anwendung. `type_name` bindet die ADT an, `ctor` den Variant.
|
||||
/// Beispiel: `Some(42)` -> `{ "t": "ctor", "type": "Option", "ctor": "Some",
|
||||
/// "args": [{"t":"lit","lit":{"kind":"int","value":42}}] }`.
|
||||
Ctor {
|
||||
#[serde(rename = "type")]
|
||||
type_name: String,
|
||||
ctor: String,
|
||||
#[serde(default)]
|
||||
args: Vec<Term>,
|
||||
},
|
||||
/// Pattern matching über einen Wert.
|
||||
Match {
|
||||
scrutinee: Box<Term>,
|
||||
arms: Vec<Arm>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Arm {
|
||||
pub pat: Pattern,
|
||||
pub body: Term,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "p", rename_all = "lowercase")]
|
||||
pub enum Pattern {
|
||||
/// `_` — bindet nichts, matcht alles.
|
||||
Wild,
|
||||
/// `x` — bindet den Wert an einen Namen.
|
||||
Var { name: String },
|
||||
/// Match auf ein Literal.
|
||||
Lit { lit: Literal },
|
||||
/// Match auf einen Konstruktor mit Sub-Patterns für seine Felder.
|
||||
Ctor {
|
||||
ctor: String,
|
||||
#[serde(default)]
|
||||
fields: Vec<Pattern>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
@@ -40,6 +40,29 @@ pub fn manifest(m: &Module) -> String {
|
||||
let (kw, ty) = match def {
|
||||
Def::Fn(f) => ("fn", type_to_string(&f.ty)),
|
||||
Def::Const(c) => ("const", type_to_string(&c.ty)),
|
||||
Def::Type(t) => {
|
||||
let ctors = t
|
||||
.ctors
|
||||
.iter()
|
||||
.map(|c| {
|
||||
if c.fields.is_empty() {
|
||||
c.name.clone()
|
||||
} else {
|
||||
format!(
|
||||
"{}({})",
|
||||
c.name,
|
||||
c.fields
|
||||
.iter()
|
||||
.map(type_to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
.join(" | ");
|
||||
("type", ctors)
|
||||
}
|
||||
};
|
||||
writeln!(
|
||||
s,
|
||||
@@ -86,6 +109,26 @@ fn def_block(def: &Def, indent: usize) -> String {
|
||||
s.push(')');
|
||||
s
|
||||
}
|
||||
Def::Type(t) => {
|
||||
let mut s = format!("{pad}(type {name}\n", pad = pad, name = t.name);
|
||||
let inner = " ".repeat(indent + 2);
|
||||
for ctor in &t.ctors {
|
||||
if ctor.fields.is_empty() {
|
||||
s.push_str(&format!("{inner}(| {})\n", ctor.name));
|
||||
} else {
|
||||
let fs = ctor
|
||||
.fields
|
||||
.iter()
|
||||
.map(type_to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
s.push_str(&format!("{inner}(| {name} {fs})\n", name = ctor.name));
|
||||
}
|
||||
}
|
||||
s.push_str(&pad);
|
||||
s.push(')');
|
||||
s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -131,6 +174,50 @@ fn term_block(t: &Term, indent: usize) -> String {
|
||||
s.push(')');
|
||||
s
|
||||
}
|
||||
Term::Ctor { type_name, ctor, args } => {
|
||||
let mut s = format!("{pad}({type_name}/{ctor}");
|
||||
for a in args {
|
||||
s.push(' ');
|
||||
s.push_str(&term_inline(a));
|
||||
}
|
||||
s.push(')');
|
||||
s
|
||||
}
|
||||
Term::Match { scrutinee, arms } => {
|
||||
let mut s = format!("{pad}(match {}\n", term_inline(scrutinee));
|
||||
let inner = " ".repeat(indent + 2);
|
||||
for arm in arms {
|
||||
s.push_str(&format!(
|
||||
"{inner}(case {} ->\n",
|
||||
pattern_to_string(&arm.pat)
|
||||
));
|
||||
s.push_str(&term_block(&arm.body, indent + 4));
|
||||
s.push_str(")\n");
|
||||
}
|
||||
s.push_str(&pad);
|
||||
s.push(')');
|
||||
s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pattern_to_string(p: &Pattern) -> String {
|
||||
match p {
|
||||
Pattern::Wild => "_".into(),
|
||||
Pattern::Var { name } => name.clone(),
|
||||
Pattern::Lit { lit } => lit_to_string(lit),
|
||||
Pattern::Ctor { ctor, fields } => {
|
||||
if fields.is_empty() {
|
||||
ctor.clone()
|
||||
} else {
|
||||
let fs = fields
|
||||
.iter()
|
||||
.map(pattern_to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ");
|
||||
format!("({ctor} {fs})")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,6 +244,19 @@ fn term_inline(t: &Term) -> String {
|
||||
s.push(')');
|
||||
s
|
||||
}
|
||||
Term::Ctor { type_name, ctor, args } => {
|
||||
let mut s = format!("({type_name}/{ctor}");
|
||||
for a in args {
|
||||
s.push(' ');
|
||||
s.push_str(&term_inline(a));
|
||||
}
|
||||
s.push(')');
|
||||
s
|
||||
}
|
||||
Term::Match { scrutinee, .. } => {
|
||||
// Match nicht inline darstellen — mit Marker.
|
||||
format!("(match {} ...)", term_inline(scrutinee))
|
||||
}
|
||||
// Strukturelle Terms in Inline-Form rekursiv schwer; fallback:
|
||||
Term::Let { name, value, body } => {
|
||||
format!(
|
||||
|
||||
Reference in New Issue
Block a user