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>
This commit is contained in:
+145
-7
@@ -18,7 +18,11 @@ struct Cli {
|
||||
#[derive(Subcommand)]
|
||||
enum Cmd {
|
||||
/// Lädt ein Modul und gibt eine kompakte Symboltabelle aus.
|
||||
Manifest { path: PathBuf },
|
||||
Manifest {
|
||||
path: PathBuf,
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
/// Gibt das Modul in Textform aus (Pretty-Printer).
|
||||
Render { path: PathBuf },
|
||||
/// Gibt eine einzelne Definition als JSON oder Pretty-Text aus.
|
||||
@@ -28,6 +32,15 @@ enum Cmd {
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
/// Listet, welche Symbole jede Definition aufruft (statisch).
|
||||
Deps {
|
||||
path: PathBuf,
|
||||
/// Nur für ein Symbol; ohne Argument: für alle.
|
||||
#[arg(long)]
|
||||
of: Option<String>,
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
/// Typprüft ein Modul.
|
||||
Check { path: PathBuf },
|
||||
/// Schreibt LLVM IR (.ll) für das Modul.
|
||||
@@ -46,15 +59,59 @@ enum Cmd {
|
||||
opt: String,
|
||||
},
|
||||
/// Listet eingebaute Operationen mit ihren Signaturen.
|
||||
Builtins,
|
||||
Builtins {
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
},
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
match cli.cmd {
|
||||
Cmd::Manifest { path } => {
|
||||
Cmd::Manifest { path, json } => {
|
||||
let m = ailang_core::load_module(&path)?;
|
||||
print!("{}", ailang_core::pretty::manifest(&m));
|
||||
if json {
|
||||
let entries: Vec<_> = m
|
||||
.defs
|
||||
.iter()
|
||||
.map(|d| {
|
||||
let h = ailang_core::def_hash(d);
|
||||
let (kind, ty, effects) = match d {
|
||||
ailang_core::Def::Fn(f) => {
|
||||
let effects = match &f.ty {
|
||||
ailang_core::Type::Fn { effects, .. } => effects.clone(),
|
||||
_ => vec![],
|
||||
};
|
||||
(
|
||||
"fn",
|
||||
ailang_core::pretty::type_to_string(&f.ty),
|
||||
effects,
|
||||
)
|
||||
}
|
||||
ailang_core::Def::Const(c) => (
|
||||
"const",
|
||||
ailang_core::pretty::type_to_string(&c.ty),
|
||||
vec![],
|
||||
),
|
||||
};
|
||||
serde_json::json!({
|
||||
"name": d.name(),
|
||||
"kind": kind,
|
||||
"type": ty,
|
||||
"effects": effects,
|
||||
"hash": h,
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
let out = serde_json::json!({
|
||||
"module": m.name,
|
||||
"schema": m.schema,
|
||||
"symbols": entries,
|
||||
});
|
||||
println!("{}", serde_json::to_string_pretty(&out)?);
|
||||
} else {
|
||||
print!("{}", ailang_core::pretty::manifest(&m));
|
||||
}
|
||||
}
|
||||
Cmd::Render { path } => {
|
||||
let m = ailang_core::load_module(&path)?;
|
||||
@@ -127,11 +184,92 @@ fn main() -> Result<()> {
|
||||
}
|
||||
eprintln!("built {}", out_bin.display());
|
||||
}
|
||||
Cmd::Builtins => {
|
||||
for (n, sig) in ailang_check::builtins::list() {
|
||||
println!("{n:<16} {sig}");
|
||||
Cmd::Builtins { json } => {
|
||||
let list = ailang_check::builtins::list();
|
||||
if json {
|
||||
let arr: Vec<_> = list
|
||||
.iter()
|
||||
.map(|(n, s)| serde_json::json!({ "name": n, "sig": s }))
|
||||
.collect();
|
||||
println!("{}", serde_json::to_string_pretty(&arr)?);
|
||||
} else {
|
||||
for (n, sig) in list {
|
||||
println!("{n:<16} {sig}");
|
||||
}
|
||||
}
|
||||
}
|
||||
Cmd::Deps { path, of, json } => {
|
||||
let m = ailang_core::load_module(&path)?;
|
||||
let mut entries = Vec::new();
|
||||
for d in &m.defs {
|
||||
if let Some(filter) = &of {
|
||||
if d.name() != filter {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let mut refs: Vec<String> = collect_refs(d).into_iter().collect();
|
||||
refs.sort();
|
||||
entries.push((d.name().to_string(), refs));
|
||||
}
|
||||
if json {
|
||||
let arr: Vec<_> = entries
|
||||
.iter()
|
||||
.map(|(n, r)| serde_json::json!({ "name": n, "refs": r }))
|
||||
.collect();
|
||||
println!("{}", serde_json::to_string_pretty(&arr)?);
|
||||
} else {
|
||||
for (n, refs) in entries {
|
||||
if refs.is_empty() {
|
||||
println!("{n:>20} -");
|
||||
} else {
|
||||
println!("{n:>20} -> {}", refs.join(", "));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_refs(def: &ailang_core::Def) -> std::collections::BTreeSet<String> {
|
||||
let mut out = std::collections::BTreeSet::new();
|
||||
let body = match def {
|
||||
ailang_core::Def::Fn(f) => &f.body,
|
||||
ailang_core::Def::Const(c) => &c.value,
|
||||
};
|
||||
walk_term(body, &mut out);
|
||||
out
|
||||
}
|
||||
|
||||
fn walk_term(t: &ailang_core::Term, out: &mut std::collections::BTreeSet<String>) {
|
||||
use ailang_core::Term;
|
||||
match t {
|
||||
Term::Lit { .. } => {}
|
||||
Term::Var { name } => {
|
||||
out.insert(name.clone());
|
||||
}
|
||||
Term::App { callee, args } => {
|
||||
walk_term(callee, out);
|
||||
for a in args {
|
||||
walk_term(a, out);
|
||||
}
|
||||
}
|
||||
Term::Let { value, body, .. } => {
|
||||
walk_term(value, out);
|
||||
walk_term(body, out);
|
||||
}
|
||||
Term::If { cond, then, else_ } => {
|
||||
walk_term(cond, out);
|
||||
walk_term(then, out);
|
||||
walk_term(else_, out);
|
||||
}
|
||||
Term::Do { op, args } => {
|
||||
// Effekt-Ops als `effect:io/print_int` markieren, damit man sie
|
||||
// von normalen Funktionsaufrufen trennen kann.
|
||||
out.insert(format!("effect:{op}"));
|
||||
for a in args {
|
||||
walk_term(a, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,3 +42,17 @@ fn sum_1_to_10_is_55() {
|
||||
let stdout = build_and_run("sum.ail.json");
|
||||
assert_eq!(stdout.trim(), "55");
|
||||
}
|
||||
|
||||
/// Schützt das Block-Tracking im Codegen: max3 hat verschachtelte `if`s,
|
||||
/// und falsches phi-Block-Tracking würde hier zu falschem Ergebnis führen.
|
||||
#[test]
|
||||
fn max3_picks_largest() {
|
||||
let stdout = build_and_run("max3.ail.json");
|
||||
assert_eq!(stdout.trim(), "17");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hello_world_str_lit() {
|
||||
let stdout = build_and_run("hello.ail.json");
|
||||
assert_eq!(stdout.trim(), "Hallo, AILang.");
|
||||
}
|
||||
|
||||
@@ -51,6 +51,14 @@ pub fn install(env: &mut crate::Env) {
|
||||
ret: Type::unit(),
|
||||
},
|
||||
);
|
||||
env.effect_ops.insert(
|
||||
"io/print_str".into(),
|
||||
EffectOpSig {
|
||||
effect: "IO".into(),
|
||||
params: vec![Type::str_()],
|
||||
ret: Type::unit(),
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/// Liefert die Liste aller registrierten Built-ins. Praktisch für CLI-Subcommand
|
||||
@@ -71,5 +79,6 @@ pub fn list() -> Vec<(&'static str, &'static str)> {
|
||||
("not", "(Bool) -> Bool"),
|
||||
("io/print_int", "(Int) -> Unit !IO [effect op]"),
|
||||
("io/print_bool", "(Bool) -> Unit !IO [effect op]"),
|
||||
("io/print_str", "(Str) -> Unit !IO [effect op]"),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -166,6 +166,7 @@ fn synth(
|
||||
Term::Lit { lit } => Ok(match lit {
|
||||
Literal::Int { .. } => Type::int(),
|
||||
Literal::Bool { .. } => Type::bool_(),
|
||||
Literal::Str { .. } => Type::str_(),
|
||||
Literal::Unit => Type::unit(),
|
||||
}),
|
||||
Term::Var { name } => {
|
||||
|
||||
@@ -56,6 +56,9 @@ struct Emitter<'a> {
|
||||
str_counter: u64,
|
||||
/// Liste aller user-definierten Top-Level-Funktionen (für call-resolution).
|
||||
user_fns: BTreeMap<String, FnSig>,
|
||||
/// Aktuelles Basic-Block-Label. Wird von `start_block` gesetzt und ist
|
||||
/// die einzige Quelle der Wahrheit für `phi`-Operanden.
|
||||
current_block: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -88,9 +91,16 @@ impl<'a> Emitter<'a> {
|
||||
counter: 0,
|
||||
str_counter: 0,
|
||||
user_fns,
|
||||
current_block: String::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn start_block(&mut self, label: &str) {
|
||||
self.body.push_str(label);
|
||||
self.body.push_str(":\n");
|
||||
self.current_block = label.to_string();
|
||||
}
|
||||
|
||||
fn finish(self) -> String {
|
||||
let mut out = String::new();
|
||||
out.push_str("; AILang generated module: ");
|
||||
@@ -115,7 +125,8 @@ impl<'a> Emitter<'a> {
|
||||
out.push('\n');
|
||||
}
|
||||
|
||||
out.push_str("declare i32 @printf(ptr, ...)\n\n");
|
||||
out.push_str("declare i32 @printf(ptr, ...)\n");
|
||||
out.push_str("declare i32 @puts(ptr)\n\n");
|
||||
out.push_str(&self.header);
|
||||
out.push_str(&self.body);
|
||||
out
|
||||
@@ -159,20 +170,26 @@ impl<'a> Emitter<'a> {
|
||||
|
||||
fn emit_const(&mut self, c: &ConstDef) -> Result<()> {
|
||||
let lty = llvm_type(&c.ty)?;
|
||||
let (val_ty, val) = match &c.value {
|
||||
Term::Lit { lit } => match lit {
|
||||
Literal::Int { value } => ("i64".to_string(), value.to_string()),
|
||||
Literal::Bool { value } => {
|
||||
("i1".to_string(), if *value { "true".into() } else { "false".into() })
|
||||
}
|
||||
Literal::Unit => ("i8".to_string(), "0".to_string()),
|
||||
},
|
||||
let lit = match &c.value {
|
||||
Term::Lit { lit } => lit,
|
||||
_ => {
|
||||
return Err(CodegenError::Internal(
|
||||
"MVP: const muss Literal sein".into(),
|
||||
));
|
||||
}
|
||||
};
|
||||
let (val_ty, val) = match lit {
|
||||
Literal::Int { value } => ("i64".to_string(), value.to_string()),
|
||||
Literal::Bool { value } => (
|
||||
"i1".to_string(),
|
||||
if *value { "true".into() } else { "false".into() },
|
||||
),
|
||||
Literal::Unit => ("i8".to_string(), "0".to_string()),
|
||||
Literal::Str { value } => {
|
||||
let g = self.intern_string("str", value);
|
||||
("ptr".to_string(), format!("@{g}"))
|
||||
}
|
||||
};
|
||||
if val_ty != lty {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"const type mismatch: {} vs {}",
|
||||
@@ -220,7 +237,7 @@ impl<'a> Emitter<'a> {
|
||||
}
|
||||
sig.push_str(") {\n");
|
||||
self.body.push_str(&sig);
|
||||
self.body.push_str("entry:\n");
|
||||
self.start_block("entry");
|
||||
|
||||
let (val, val_ty) = self.lower_term(&f.body)?;
|
||||
if val_ty != llvm_ret {
|
||||
@@ -243,6 +260,12 @@ impl<'a> Emitter<'a> {
|
||||
if *value { "true".into() } else { "false".into() },
|
||||
"i1".into(),
|
||||
),
|
||||
Literal::Str { value } => {
|
||||
// Globale Konstante anlegen; in opaque-pointer-LLVM
|
||||
// ist `@name` direkt ein gültiger `ptr`.
|
||||
let g = self.intern_string("str", value);
|
||||
(format!("@{g}"), "ptr".into())
|
||||
}
|
||||
Literal::Unit => ("0".into(), "i8".into()),
|
||||
}),
|
||||
Term::Var { name } => {
|
||||
@@ -275,24 +298,24 @@ impl<'a> Emitter<'a> {
|
||||
" br i1 {cond_v}, label %{then_lbl}, label %{else_lbl}\n"
|
||||
));
|
||||
|
||||
self.body.push_str(&format!("{then_lbl}:\n"));
|
||||
self.start_block(&then_lbl);
|
||||
let (then_v, then_ty) = self.lower_term(then)?;
|
||||
let then_block_end = self.current_block_label_for_phi(&then_lbl);
|
||||
self.body
|
||||
.push_str(&format!(" br label %{join_lbl}\n"));
|
||||
// Verschachtelter Code im `then`-Body kann das Block-Label
|
||||
// verändert haben — phi muss den letzten tatsächlichen Block sehen.
|
||||
let then_block_end = self.current_block.clone();
|
||||
self.body.push_str(&format!(" br label %{join_lbl}\n"));
|
||||
|
||||
self.body.push_str(&format!("{else_lbl}:\n"));
|
||||
self.start_block(&else_lbl);
|
||||
let (else_v, else_ty) = self.lower_term(else_)?;
|
||||
if then_ty != else_ty {
|
||||
return Err(CodegenError::Internal(format!(
|
||||
"if branches type mismatch: {then_ty} vs {else_ty}"
|
||||
)));
|
||||
}
|
||||
let else_block_end = self.current_block_label_for_phi(&else_lbl);
|
||||
self.body
|
||||
.push_str(&format!(" br label %{join_lbl}\n"));
|
||||
let else_block_end = self.current_block.clone();
|
||||
self.body.push_str(&format!(" br label %{join_lbl}\n"));
|
||||
|
||||
self.body.push_str(&format!("{join_lbl}:\n"));
|
||||
self.start_block(&join_lbl);
|
||||
let phi = self.fresh_ssa();
|
||||
self.body.push_str(&format!(
|
||||
" {phi} = phi {ty} [ {tv}, %{tlbl} ], [ {ev}, %{elbl} ]\n",
|
||||
@@ -396,6 +419,22 @@ impl<'a> Emitter<'a> {
|
||||
));
|
||||
Ok(("0".into(), "i8".into()))
|
||||
}
|
||||
"io/print_str" => {
|
||||
if args.len() != 1 {
|
||||
return Err(CodegenError::Internal(
|
||||
"io/print_str arity".into(),
|
||||
));
|
||||
}
|
||||
let (v, vty) = self.lower_term(&args[0])?;
|
||||
if vty != "ptr" {
|
||||
return Err(CodegenError::Internal(
|
||||
"io/print_str needs ptr".into(),
|
||||
));
|
||||
}
|
||||
self.body
|
||||
.push_str(&format!(" call i32 @puts(ptr {v})\n"));
|
||||
Ok(("0".into(), "i8".into()))
|
||||
}
|
||||
"io/print_bool" => {
|
||||
if args.len() != 1 {
|
||||
return Err(CodegenError::Internal(
|
||||
@@ -418,17 +457,17 @@ impl<'a> Emitter<'a> {
|
||||
self.body.push_str(&format!(
|
||||
" br i1 {v}, label %{then_lbl}, label %{else_lbl}\n"
|
||||
));
|
||||
self.body.push_str(&format!("{then_lbl}:\n"));
|
||||
self.start_block(&then_lbl);
|
||||
self.body.push_str(&format!(
|
||||
" call i32 (ptr, ...) @printf(ptr @{fmt_t})\n"
|
||||
));
|
||||
self.body.push_str(&format!(" br label %{join_lbl}\n"));
|
||||
self.body.push_str(&format!("{else_lbl}:\n"));
|
||||
self.start_block(&else_lbl);
|
||||
self.body.push_str(&format!(
|
||||
" call i32 (ptr, ...) @printf(ptr @{fmt_f})\n"
|
||||
));
|
||||
self.body.push_str(&format!(" br label %{join_lbl}\n"));
|
||||
self.body.push_str(&format!("{join_lbl}:\n"));
|
||||
self.start_block(&join_lbl);
|
||||
Ok(("0".into(), "i8".into()))
|
||||
}
|
||||
other => Err(CodegenError::Internal(format!(
|
||||
@@ -446,25 +485,6 @@ impl<'a> Emitter<'a> {
|
||||
self.counter
|
||||
}
|
||||
|
||||
/// Im MVP haben wir keinen verschachtelten Control-Flow innerhalb von
|
||||
/// `then`/`else` von `if`, also entspricht das End-Label dem Block-Anfang.
|
||||
/// Das ändert sich, sobald `if` rekursiv andere `if`s enthält — dann muss
|
||||
/// das tatsächlich aktuelle Label am Phi-Punkt verwendet werden.
|
||||
fn current_block_label_for_phi(&self, fallback: &str) -> String {
|
||||
// Heuristik: scanne self.body rückwärts nach dem letzten Label-Header.
|
||||
// Das ist robuster als anzunehmen, dass der ursprüngliche Block-Header
|
||||
// noch der aktuelle ist.
|
||||
for line in self.body.lines().rev() {
|
||||
let line = line.trim_end();
|
||||
if let Some(s) = line.strip_suffix(':') {
|
||||
if !s.starts_with(' ') && !s.is_empty() && !s.contains(' ') {
|
||||
return s.to_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
fallback.to_string()
|
||||
}
|
||||
|
||||
fn intern_string(&mut self, hint: &str, content: &str) -> String {
|
||||
if let Some((name, _)) = self.strings.get(content) {
|
||||
return name.clone();
|
||||
@@ -484,6 +504,7 @@ fn llvm_type(t: &Type) -> Result<String> {
|
||||
"Int" => Ok("i64".into()),
|
||||
"Bool" => Ok("i1".into()),
|
||||
"Unit" => Ok("i8".into()),
|
||||
"Str" => Ok("ptr".into()),
|
||||
other => Err(CodegenError::UnsupportedType(other.into())),
|
||||
},
|
||||
other => Err(CodegenError::UnsupportedType(
|
||||
|
||||
@@ -87,6 +87,7 @@ pub enum Term {
|
||||
pub enum Literal {
|
||||
Int { value: i64 },
|
||||
Bool { value: bool },
|
||||
Str { value: String },
|
||||
Unit,
|
||||
}
|
||||
|
||||
@@ -121,6 +122,9 @@ impl Type {
|
||||
pub fn unit() -> Type {
|
||||
Type::Con { name: "Unit".into() }
|
||||
}
|
||||
pub fn str_() -> Type {
|
||||
Type::Con { name: "Str".into() }
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Type {
|
||||
|
||||
@@ -180,6 +180,11 @@ fn lit_to_string(l: &Literal) -> String {
|
||||
match l {
|
||||
Literal::Int { value } => value.to_string(),
|
||||
Literal::Bool { value } => value.to_string(),
|
||||
Literal::Str { value } => {
|
||||
// serde_json escapt für uns; das Ergebnis ist ein gültiges
|
||||
// JSON-String-Literal, was für uns als kanonische Form ausreicht.
|
||||
serde_json::to_string(value).unwrap()
|
||||
}
|
||||
Literal::Unit => "()".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
+53
-1
@@ -14,4 +14,56 @@ Hindernisse, Beobachtungen, die zukünftige Iterationen brauchen.
|
||||
- `crates/ailang-check` — Typchecker (kommt später)
|
||||
- `crates/ailang-codegen` — Lowering + LLVM IR Emit
|
||||
- `crates/ail` — CLI
|
||||
- MVP-Ziel: `examples/sum.ail.json` → Binary, das 55 druckt.
|
||||
- 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:**
|
||||
|
||||
1. **`current_block_label_for_phi` ist eine Heuristik** (siehe codegen). Bei
|
||||
verschachtelten `if`-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.
|
||||
2. **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).
|
||||
3. **`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):**
|
||||
|
||||
1. Block-Label-Tracking sauber machen, verschachteltes-if-Test.
|
||||
2. Strings als Literal + `io/print_str`.
|
||||
3. Hello-world-Beispiel als zweiten E2E-Test.
|
||||
4. 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 `if`s funktionieren). Test
|
||||
`max3_picks_largest` schützt das.
|
||||
- Strings als `Lit::Str { value }`, Type `Str` -> LLVM `ptr`, mit
|
||||
`io/print_str` Effekt-Op. `examples/hello.ail.json` druckt einen String.
|
||||
- CLI: `manifest --json`, `builtins --json` für Tool-Konsumenten.
|
||||
- `ail deps [--of NAME] [--json]` listet Aufruf-Edges. Effekt-Ops sind als
|
||||
`effect:NAME` markiert, 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).
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"schema": "ailang/v0",
|
||||
"name": "hello",
|
||||
"imports": [],
|
||||
"defs": [
|
||||
{
|
||||
"kind": "fn",
|
||||
"name": "main",
|
||||
"type": {
|
||||
"k": "fn",
|
||||
"params": [],
|
||||
"ret": { "k": "con", "name": "Unit" },
|
||||
"effects": ["IO"]
|
||||
},
|
||||
"params": [],
|
||||
"body": {
|
||||
"t": "do",
|
||||
"op": "io/print_str",
|
||||
"args": [
|
||||
{ "t": "lit", "lit": { "kind": "str", "value": "Hallo, AILang." } }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"schema": "ailang/v0",
|
||||
"name": "max3",
|
||||
"imports": [],
|
||||
"defs": [
|
||||
{
|
||||
"kind": "fn",
|
||||
"name": "max",
|
||||
"type": {
|
||||
"k": "fn",
|
||||
"params": [
|
||||
{ "k": "con", "name": "Int" },
|
||||
{ "k": "con", "name": "Int" }
|
||||
],
|
||||
"ret": { "k": "con", "name": "Int" },
|
||||
"effects": []
|
||||
},
|
||||
"params": ["a", "b"],
|
||||
"body": {
|
||||
"t": "if",
|
||||
"cond": {
|
||||
"t": "app",
|
||||
"fn": { "t": "var", "name": ">" },
|
||||
"args": [
|
||||
{ "t": "var", "name": "a" },
|
||||
{ "t": "var", "name": "b" }
|
||||
]
|
||||
},
|
||||
"then": { "t": "var", "name": "a" },
|
||||
"else": { "t": "var", "name": "b" }
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "fn",
|
||||
"name": "max3",
|
||||
"type": {
|
||||
"k": "fn",
|
||||
"params": [
|
||||
{ "k": "con", "name": "Int" },
|
||||
{ "k": "con", "name": "Int" },
|
||||
{ "k": "con", "name": "Int" }
|
||||
],
|
||||
"ret": { "k": "con", "name": "Int" },
|
||||
"effects": []
|
||||
},
|
||||
"params": ["a", "b", "c"],
|
||||
"doc": "Demonstriert verschachteltes if (statt max-call) zum Test des Block-Trackings.",
|
||||
"body": {
|
||||
"t": "if",
|
||||
"cond": {
|
||||
"t": "app",
|
||||
"fn": { "t": "var", "name": ">" },
|
||||
"args": [
|
||||
{ "t": "var", "name": "a" },
|
||||
{ "t": "var", "name": "b" }
|
||||
]
|
||||
},
|
||||
"then": {
|
||||
"t": "if",
|
||||
"cond": {
|
||||
"t": "app",
|
||||
"fn": { "t": "var", "name": ">" },
|
||||
"args": [
|
||||
{ "t": "var", "name": "a" },
|
||||
{ "t": "var", "name": "c" }
|
||||
]
|
||||
},
|
||||
"then": { "t": "var", "name": "a" },
|
||||
"else": { "t": "var", "name": "c" }
|
||||
},
|
||||
"else": {
|
||||
"t": "if",
|
||||
"cond": {
|
||||
"t": "app",
|
||||
"fn": { "t": "var", "name": ">" },
|
||||
"args": [
|
||||
{ "t": "var", "name": "b" },
|
||||
{ "t": "var", "name": "c" }
|
||||
]
|
||||
},
|
||||
"then": { "t": "var", "name": "b" },
|
||||
"else": { "t": "var", "name": "c" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"kind": "fn",
|
||||
"name": "main",
|
||||
"type": {
|
||||
"k": "fn",
|
||||
"params": [],
|
||||
"ret": { "k": "con", "name": "Unit" },
|
||||
"effects": ["IO"]
|
||||
},
|
||||
"params": [],
|
||||
"body": {
|
||||
"t": "do",
|
||||
"op": "io/print_int",
|
||||
"args": [
|
||||
{
|
||||
"t": "app",
|
||||
"fn": { "t": "var", "name": "max3" },
|
||||
"args": [
|
||||
{ "t": "lit", "lit": { "kind": "int", "value": 3 } },
|
||||
{ "t": "lit", "lit": { "kind": "int", "value": 17 } },
|
||||
{ "t": "lit", "lit": { "kind": "int", "value": 9 } }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user