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:
2026-05-07 10:28:16 +02:00
parent 2fbcdba0b1
commit 6e6b6a14fb
10 changed files with 431 additions and 49 deletions
+145 -7
View File
@@ -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);
}
}
}
}