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:
@@ -0,0 +1,255 @@
|
||||
//! Pretty-Printer: AST → menschenlesbare Textform.
|
||||
//!
|
||||
//! Die Textform ist als Diff- und Review-Werkzeug gedacht. Die
|
||||
//! kanonische Quelle bleibt die JSON-Form. Jede pretty-Ausgabe ist
|
||||
//! deterministisch.
|
||||
|
||||
use crate::ast::*;
|
||||
use std::fmt::Write;
|
||||
|
||||
pub fn module(m: &Module) -> String {
|
||||
let mut s = String::new();
|
||||
writeln!(s, "(module {}", m.name).unwrap();
|
||||
if !m.imports.is_empty() {
|
||||
for imp in &m.imports {
|
||||
match &imp.alias {
|
||||
Some(a) => writeln!(s, " (import {} as {})", imp.module, a).unwrap(),
|
||||
None => writeln!(s, " (import {})", imp.module).unwrap(),
|
||||
}
|
||||
}
|
||||
}
|
||||
for (i, def) in m.defs.iter().enumerate() {
|
||||
if i > 0 {
|
||||
s.push('\n');
|
||||
}
|
||||
let body = def_block(def, 2);
|
||||
s.push_str(&body);
|
||||
s.push('\n');
|
||||
}
|
||||
s.push(')');
|
||||
s.push('\n');
|
||||
s
|
||||
}
|
||||
|
||||
pub fn manifest(m: &Module) -> String {
|
||||
let mut s = String::new();
|
||||
writeln!(s, "module {}", m.name).unwrap();
|
||||
let max_name = m.defs.iter().map(|d| d.name().len()).max().unwrap_or(0);
|
||||
for def in &m.defs {
|
||||
let h = crate::hash::def_hash(def);
|
||||
let (kw, ty) = match def {
|
||||
Def::Fn(f) => ("fn", type_to_string(&f.ty)),
|
||||
Def::Const(c) => ("const", type_to_string(&c.ty)),
|
||||
};
|
||||
writeln!(
|
||||
s,
|
||||
" {kw:5} {name:<width$} :: {ty} [{h}]",
|
||||
kw = kw,
|
||||
name = def.name(),
|
||||
width = max_name,
|
||||
ty = ty,
|
||||
h = h,
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
fn def_block(def: &Def, indent: usize) -> String {
|
||||
let pad = " ".repeat(indent);
|
||||
match def {
|
||||
Def::Fn(f) => {
|
||||
let params = if f.params.is_empty() {
|
||||
"[]".to_string()
|
||||
} else {
|
||||
format!("[{}]", f.params.join(" "))
|
||||
};
|
||||
let mut s = format!(
|
||||
"{pad}(fn {name} :: {ty} {params}\n",
|
||||
pad = pad,
|
||||
name = f.name,
|
||||
ty = type_to_string(&f.ty),
|
||||
params = params,
|
||||
);
|
||||
s.push_str(&term_block(&f.body, indent + 2));
|
||||
s.push(')');
|
||||
s
|
||||
}
|
||||
Def::Const(c) => {
|
||||
let mut s = format!(
|
||||
"{pad}(const {name} :: {ty}\n",
|
||||
pad = pad,
|
||||
name = c.name,
|
||||
ty = type_to_string(&c.ty),
|
||||
);
|
||||
s.push_str(&term_block(&c.value, indent + 2));
|
||||
s.push(')');
|
||||
s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn term_block(t: &Term, indent: usize) -> String {
|
||||
let pad = " ".repeat(indent);
|
||||
match t {
|
||||
Term::Lit { lit } => format!("{pad}{}", lit_to_string(lit)),
|
||||
Term::Var { name } => format!("{pad}{name}"),
|
||||
Term::App { callee, args } => {
|
||||
let mut s = format!("{pad}(");
|
||||
s.push_str(&term_inline(callee));
|
||||
for a in args {
|
||||
s.push(' ');
|
||||
s.push_str(&term_inline(a));
|
||||
}
|
||||
s.push(')');
|
||||
s
|
||||
}
|
||||
Term::Let { name, value, body } => {
|
||||
let mut s = format!("{pad}(let {name}\n");
|
||||
s.push_str(&term_block(value, indent + 2));
|
||||
s.push('\n');
|
||||
s.push_str(&term_block(body, indent + 2));
|
||||
s.push(')');
|
||||
s
|
||||
}
|
||||
Term::If { cond, then, else_ } => {
|
||||
let mut s = format!("{pad}(if\n");
|
||||
s.push_str(&term_block(cond, indent + 2));
|
||||
s.push('\n');
|
||||
s.push_str(&term_block(then, indent + 2));
|
||||
s.push('\n');
|
||||
s.push_str(&term_block(else_, indent + 2));
|
||||
s.push(')');
|
||||
s
|
||||
}
|
||||
Term::Do { op, args } => {
|
||||
let mut s = format!("{pad}(do {op}");
|
||||
for a in args {
|
||||
s.push(' ');
|
||||
s.push_str(&term_inline(a));
|
||||
}
|
||||
s.push(')');
|
||||
s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn term_inline(t: &Term) -> String {
|
||||
match t {
|
||||
Term::Lit { lit } => lit_to_string(lit),
|
||||
Term::Var { name } => name.clone(),
|
||||
Term::App { callee, args } => {
|
||||
let mut s = String::from("(");
|
||||
s.push_str(&term_inline(callee));
|
||||
for a in args {
|
||||
s.push(' ');
|
||||
s.push_str(&term_inline(a));
|
||||
}
|
||||
s.push(')');
|
||||
s
|
||||
}
|
||||
Term::Do { op, args } => {
|
||||
let mut s = format!("(do {op}");
|
||||
for a in args {
|
||||
s.push(' ');
|
||||
s.push_str(&term_inline(a));
|
||||
}
|
||||
s.push(')');
|
||||
s
|
||||
}
|
||||
// Strukturelle Terms in Inline-Form rekursiv schwer; fallback:
|
||||
Term::Let { name, value, body } => {
|
||||
format!(
|
||||
"(let {name} {} {})",
|
||||
term_inline(value),
|
||||
term_inline(body)
|
||||
)
|
||||
}
|
||||
Term::If { cond, then, else_ } => {
|
||||
format!(
|
||||
"(if {} {} {})",
|
||||
term_inline(cond),
|
||||
term_inline(then),
|
||||
term_inline(else_)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn lit_to_string(l: &Literal) -> String {
|
||||
match l {
|
||||
Literal::Int { value } => value.to_string(),
|
||||
Literal::Bool { value } => value.to_string(),
|
||||
Literal::Unit => "()".to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn type_to_string(t: &Type) -> String {
|
||||
match t {
|
||||
Type::Con { name } => name.clone(),
|
||||
Type::Var { name } => name.clone(),
|
||||
Type::Fn { params, ret, effects } => {
|
||||
let p = params
|
||||
.iter()
|
||||
.map(type_to_string)
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let eff = if effects.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(" !{}", effects.join(","))
|
||||
};
|
||||
format!("({p}) -> {ret}{eff}", ret = type_to_string(ret))
|
||||
}
|
||||
Type::Forall { vars, body } => {
|
||||
format!("forall {}. {}", vars.join(" "), type_to_string(body))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn sample_module() -> Module {
|
||||
Module {
|
||||
schema: crate::SCHEMA.into(),
|
||||
name: "sample".into(),
|
||||
imports: vec![],
|
||||
defs: vec![
|
||||
Def::Fn(FnDef {
|
||||
name: "add".into(),
|
||||
ty: Type::Fn {
|
||||
params: vec![Type::int(), Type::int()],
|
||||
ret: Box::new(Type::int()),
|
||||
effects: vec![],
|
||||
},
|
||||
params: vec!["a".into(), "b".into()],
|
||||
body: Term::App {
|
||||
callee: Box::new(Term::Var { name: "+".into() }),
|
||||
args: vec![
|
||||
Term::Var { name: "a".into() },
|
||||
Term::Var { name: "b".into() },
|
||||
],
|
||||
},
|
||||
doc: None,
|
||||
}),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pretty_print_does_not_panic() {
|
||||
let s = module(&sample_module());
|
||||
assert!(s.contains("(module sample"));
|
||||
assert!(s.contains("(fn add"));
|
||||
assert!(s.contains("(+ a b)"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn manifest_contains_type_and_hash() {
|
||||
let s = manifest(&sample_module());
|
||||
assert!(s.contains("add"));
|
||||
assert!(s.contains("(Int, Int) -> Int"));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user