//! `ail` — CLI für AILang. //! //! Subcommands sind so geschnitten, dass jedes einzelne Tool dem LLM einen //! kleinen, fokussierten Kontext liefert (manifest = Übersicht; describe = //! Detail; emit-ir = exakte Maschinensicht; build = Pipeline-Validierung). use anyhow::{Context, Result}; use clap::{Parser, Subcommand}; use std::path::{Path, PathBuf}; #[derive(Parser)] #[command(name = "ail", version, about = "AILang toolchain")] struct Cli { #[command(subcommand)] cmd: Cmd, } #[derive(Subcommand)] enum Cmd { /// Lädt ein Modul und gibt eine kompakte Symboltabelle aus. 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. Describe { path: PathBuf, name: String, #[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, #[arg(long)] json: bool, }, /// Typprüft ein Modul. Check { path: PathBuf }, /// Schreibt LLVM IR (.ll) für das Modul. EmitIr { path: PathBuf, #[arg(short, long)] out: Option, }, /// Komplette Pipeline: check + emit-ir + clang -> Binary. Build { path: PathBuf, #[arg(short, long)] out: Option, /// Optimierung (z. B. `-O2`); default `-O0` für Debugbarkeit. #[arg(long, default_value = "-O0")] opt: String, }, /// Listet eingebaute Operationen mit ihren Signaturen. Builtins { #[arg(long)] json: bool, }, } fn main() -> Result<()> { let cli = Cli::parse(); match cli.cmd { Cmd::Manifest { path, json } => { let m = ailang_core::load_module(&path)?; 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![], ), ailang_core::Def::Type(t) => { let s = t .ctors .iter() .map(|c| { if c.fields.is_empty() { c.name.clone() } else { format!( "{}({})", c.name, c.fields .iter() .map(ailang_core::pretty::type_to_string) .collect::>() .join(", ") ) } }) .collect::>() .join(" | "); ("type", s, 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)?; print!("{}", ailang_core::pretty::module(&m)); } Cmd::Describe { path, name, json } => { let m = ailang_core::load_module(&path)?; let def = m .defs .iter() .find(|d| d.name() == name) .with_context(|| format!("no def `{name}` in module `{}`", m.name))?; if json { let s = serde_json::to_string_pretty(def)?; println!("{s}"); } else { // Pretty-form: render module mit nur dieser Def. let one = ailang_core::Module { schema: m.schema.clone(), name: m.name.clone(), imports: vec![], defs: vec![def.clone()], }; let h = ailang_core::def_hash(def); println!("hash: {h}"); print!("{}", ailang_core::pretty::module(&one)); } } Cmd::Check { path } => { let m = ailang_core::load_module(&path)?; let r = ailang_check::check(&m)?; println!("ok ({} symbols)", r.symbols.len()); } Cmd::EmitIr { path, out } => { let m = ailang_core::load_module(&path)?; ailang_check::check(&m)?; let ir = ailang_codegen::emit_ir(&m)?; match out { Some(p) => { std::fs::write(&p, ir)?; eprintln!("wrote {}", p.display()); } None => print!("{ir}"), } } Cmd::Build { path, out, opt } => { let m = ailang_core::load_module(&path)?; ailang_check::check(&m)?; let ir = ailang_codegen::emit_ir(&m)?; let tmpdir = std::env::temp_dir().join(format!("ailang-{}", std::process::id())); std::fs::create_dir_all(&tmpdir)?; let ll_path = tmpdir.join(format!("{}.ll", m.name)); std::fs::write(&ll_path, &ir)?; let out_bin = out.unwrap_or_else(|| { Path::new(".").join(&m.name).with_extension("") }); let status = std::process::Command::new("clang") .arg(&opt) .arg("-o") .arg(&out_bin) .arg(&ll_path) .status() .context("running clang")?; if !status.success() { anyhow::bail!( "clang failed (status {}); ll at {}", status, ll_path.display() ); } eprintln!("built {}", out_bin.display()); } 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 = 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 { let mut out = std::collections::BTreeSet::new(); match def { ailang_core::Def::Fn(f) => walk_term(&f.body, &mut out), ailang_core::Def::Const(c) => walk_term(&c.value, &mut out), ailang_core::Def::Type(td) => { // Eine Typedef referenziert die Typen ihrer Felder. for c in &td.ctors { for ft in &c.fields { if let ailang_core::Type::Con { name } = ft { out.insert(format!("type:{name}")); } } } } } out } fn walk_term(t: &ailang_core::Term, out: &mut std::collections::BTreeSet) { 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); } } Term::Ctor { type_name, ctor, args } => { out.insert(format!("ctor:{type_name}/{ctor}")); for a in args { walk_term(a, out); } } Term::Match { scrutinee, arms } => { walk_term(scrutinee, out); for arm in arms { if let ailang_core::ast::Pattern::Ctor { ctor, .. } = &arm.pat { out.insert(format!("ctor:{ctor}")); } walk_term(&arm.body, out); } } } }