//! cordial source decomposer -- produces self-contained Rust from Cordial. use acord_core::interp::{parse_program, Expr, Op, Stmt}; /// extension point for external projects adding custom decomposition rules. pub trait DecomposeHook { /// extra Rust source appended after the value prelude, before generated code. fn preamble(&self) -> Option { None } /// intercepts a function call by name before the default decomposition. fn call(&self, _name: &str, _args: &[Expr]) -> Option { None } /// intercepts an expression before the default decomposition. fn expr(&self, _expr: &Expr) -> Option { None } /// intercepts a statement before the default decomposition. fn stmt(&self, _stmt: &Stmt) -> Option { None } } struct NoHook; impl DecomposeHook for NoHook {} /// external module dependency discovered during decomposition. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Dependency { /// module name (note filename stem, hyphen-form). pub module: String, /// specific item imported, or None / Some("*") for the whole module. pub item: Option, } /// result of decomposing a single cordial source. pub struct Decomposed { /// self-contained rust source for this module. pub code: String, /// external modules referenced by use declarations. pub deps: Vec, } /// decomposes cordial source into standalone rust + a dependency list. pub fn decompose(source: &str) -> Result { decompose_with(source, &NoHook) } /// decomposes with a custom hook for external extensions. pub fn decompose_with(source: &str, hook: &dyn DecomposeHook) -> Result { let stmts = parse_program(source)?; let mut deps = Vec::new(); let mut out = String::new(); out.push_str(GENERATED_PRELUDE); if let Some(extra) = hook.preamble() { out.push_str(&extra); out.push('\n'); } emit_program(&mut out, &stmts, &mut deps, hook)?; Ok(Decomposed { code: out, deps }) } fn emit_program(out: &mut String, stmts: &[Stmt], deps: &mut Vec, hook: &dyn DecomposeHook) -> Result<(), String> { let mut uses = Vec::new(); let mut fns = Vec::new(); let mut rest = Vec::new(); for s in stmts { match s { Stmt::Use(..) => uses.push(s), Stmt::FnDef { .. } => fns.push(s), _ => rest.push(s), } } for s in &uses { emit_stmt(out, s, 0, deps, hook)?; } if !uses.is_empty() { out.push('\n'); } for s in &fns { emit_stmt(out, s, 0, deps, hook)?; out.push('\n'); } indent(out, 0, "pub fn run() -> V {"); for s in &rest { emit_stmt(out, s, 1, deps, hook)?; } indent(out, 1, "V::Void"); indent(out, 0, "}"); Ok(()) } fn emit_stmt(out: &mut String, stmt: &Stmt, depth: usize, deps: &mut Vec, hook: &dyn DecomposeHook) -> Result<(), String> { if let Some(custom) = hook.stmt(stmt) { indent(out, depth, &custom); return Ok(()); } match stmt { Stmt::Let(name, _ann, expr) => { let rhs = emit_expr(expr, hook)?; indent(out, depth, &format!("let mut {} = {};", ident(name), rhs)); } Stmt::Assign(name, expr) => { let rhs = emit_expr(expr, hook)?; indent(out, depth, &format!("{} = {};", ident(name), rhs)); } Stmt::PathAssign(lhs, expr) => { let lhs_s = emit_expr(lhs, hook)?; let rhs = emit_expr(expr, hook)?; indent(out, depth, &format!("v_path_assign(&mut {}, &{});", lhs_s, rhs)); } Stmt::Return(expr) => { let rhs = emit_expr(expr, hook)?; indent(out, depth, &format!("return {};", rhs)); } Stmt::ExprStmt(expr) => { let rendered = emit_expr(expr, hook)?; indent(out, depth, &format!("let _ = {};", rendered)); } Stmt::FnDef { name, params, body, .. } => { let param_list: String = params.iter() .map(|(p, _)| format!("mut {}: V", ident(p))) .collect::>() .join(", "); indent(out, depth, &format!("pub fn {}({}) -> V {{", ident(name), param_list)); for s in body { emit_stmt(out, s, depth + 1, deps, hook)?; } indent(out, depth + 1, "V::Void"); indent(out, depth, "}"); } Stmt::While(cond, body) => { let c = emit_expr(cond, hook)?; indent(out, depth, &format!("while v_truthy(&{}) {{", c)); for s in body { emit_stmt(out, s, depth + 1, deps, hook)?; } indent(out, depth, "}"); } Stmt::IfElse(cond, then_body, else_body) => { let c = emit_expr(cond, hook)?; indent(out, depth, &format!("if v_truthy(&{}) {{", c)); for s in then_body { emit_stmt(out, s, depth + 1, deps, hook)?; } if let Some(els) = else_body { indent(out, depth, "} else {"); for s in els { emit_stmt(out, s, depth + 1, deps, hook)?; } } indent(out, depth, "}"); } Stmt::ForLoop(var, iter_expr, body) => { let iter_s = emit_expr(iter_expr, hook)?; indent(out, depth, &format!("for {} in v_iter(&{}) {{", ident(var), iter_s)); for s in body { emit_stmt(out, s, depth + 1, deps, hook)?; } indent(out, depth, "}"); } Stmt::Use(module, item) => { let mod_ident = module.replace('-', "_"); deps.push(Dependency { module: module.clone(), item: item.clone() }); indent(out, depth, &format!("mod {};", mod_ident)); match item.as_deref() { None | Some("*") => indent(out, depth, &format!("use {}::*;", mod_ident)), Some(name) => indent(out, depth, &format!("use {}::{};", mod_ident, ident(name))), } } Stmt::CellAssign { table, cell, value, .. } => { let v = emit_expr(value, hook)?; indent(out, depth, &format!( "v_cell_set({:?}, {}, {}, &{});", table, cell.0, cell.1, v )); } Stmt::SolveDef { name, params, target_var, source_fn, .. } => { let param_list: String = params.iter() .map(|p| format!("mut {}: V", ident(p))) .collect::>() .join(", "); indent(out, depth, &format!("pub fn {}({}) -> V {{", ident(name), param_list)); indent(out, depth + 1, &format!( "v_solve_newton({:?}, {:?})", target_var, source_fn )); indent(out, depth, "}"); } Stmt::TraitDef { name, methods } => { indent(out, depth, &format!("pub trait {} {{", ident(name))); for m in methods { indent(out, depth + 1, &format!("fn {}(&self) -> V;", ident(m))); } indent(out, depth, "}"); } Stmt::ImplBlock { type_name, trait_name, methods } => { let header = match trait_name { Some(t) => format!("impl {} for {} {{", ident(t), ident(type_name)), None => format!("impl {} {{", ident(type_name)), }; indent(out, depth, &header); for m in methods { emit_stmt(out, m, depth + 1, deps, hook)?; } indent(out, depth, "}"); } } Ok(()) } fn emit_expr(expr: &Expr, hook: &dyn DecomposeHook) -> Result { if let Some(custom) = hook.expr(expr) { return Ok(custom); } Ok(match expr { Expr::Num(n) => format!("V::Num({})", fmt_num(*n)), Expr::Bool(b) => format!("V::Bool({})", b), Expr::Str(s) => format!("V::Str({:?}.into())", s), Expr::Ident(name) => format!("{}.clone()", ident(name)), Expr::UnaryOp(op, inner) => { let inner_s = emit_expr(inner, hook)?; match op { Op::Neg => format!("v_neg(&{})", inner_s), Op::Not => format!("v_not(&{})", inner_s), Op::Strip => format!("v_strip(&{})", inner_s), _ => format!("v_neg(&{})", inner_s), } } Expr::BinOp(op, l, r) => { let ls = emit_expr(l, hook)?; let rs = emit_expr(r, hook)?; let func = match op { Op::Add => "v_add", Op::Sub => "v_sub", Op::Mul => "v_mul", Op::Div => "v_div", Op::Mod => "v_rem", Op::Pow => "v_pow", Op::Eq => "v_eq", Op::Neq => "v_neq", Op::Lt => "v_lt", Op::Gt => "v_gt", Op::Lte => "v_lte", Op::Gte => "v_gte", Op::And => "v_and", Op::Or => "v_or", _ => "v_add", }; format!("{}(&{}, &{})", func, ls, rs) } Expr::Call(name, args) => { if let Some(custom) = hook.call(name, args) { custom } else { let arg_list: Vec = args.iter() .map(|a| emit_expr(a, hook)) .collect::>()?; format!("{}({})", ident(name), arg_list.join(", ")) } } Expr::Array(items) => { let parts: Vec = items.iter() .map(|e| emit_expr(e, hook)) .collect::>()?; format!("V::Array(vec![{}])", parts.join(", ")) } Expr::Index(base, idx) => { let b = emit_expr(base, hook)?; let i = emit_expr(idx, hook)?; format!("v_index(&{}, &{})", b, i) } Expr::Range(start, end) => { let s = emit_expr(start, hook)?; let e = emit_expr(end, hook)?; format!("v_range(&{}, &{})", s, e) } Expr::IsCheck(inner, type_name) => { let i = emit_expr(inner, hook)?; format!("v_is(&{}, {:?})", i, type_name) } Expr::CellRef { table, target, .. } => { let t = table.as_deref().unwrap_or("_default"); match target { acord_core::interp::CellRefTarget::Cell(col, row) => { format!("v_cell_get({:?}, {}, {})", t, col, row) } acord_core::interp::CellRefTarget::Range(c1, r1, c2, r2) => { format!("v_cell_range({:?}, {}, {}, {}, {})", t, c1, r1, c2, r2) } acord_core::interp::CellRefTarget::Whole => { format!("v_cell_table({:?})", t) } } } Expr::SolveMacro { var, source_fn } => { format!("v_solve_call({:?}, {:?})", var, source_fn) } Expr::Struct(fields) => { let entries: Vec = fields.iter() .map(|(k, v)| { let val = emit_expr(v, hook)?; Ok(format!("({:?}.into(), {})", k, val)) }) .collect::, String>>()?; format!("V::Struct(vec![{}].into_iter().collect())", entries.join(", ")) } Expr::Field(base, field) => { let b = emit_expr(base, hook)?; format!("v_field(&{}, {:?})", b, field) } Expr::MethodCall(recv, method, args) => { let r = emit_expr(recv, hook)?; let arg_list: Vec = args.iter() .map(|a| emit_expr(a, hook)) .collect::>()?; if arg_list.is_empty() { format!("v_method_call(&{}, {:?}, &[])", r, method) } else { format!("v_method_call(&{}, {:?}, &[{}])", r, method, arg_list.join(", ")) } } Expr::StaticCall(type_name, method, args) => { let arg_list: Vec = args.iter() .map(|a| emit_expr(a, hook)) .collect::>()?; format!("{}__{}({})", ident(type_name), ident(method), arg_list.join(", ")) } }) } fn indent(out: &mut String, depth: usize, line: &str) { for _ in 0..depth { out.push_str(" "); } out.push_str(line); out.push('\n'); } fn ident(name: &str) -> String { if RESERVED.contains(&name) { format!("r#{}", name) } else { name.replace('-', "_") } } fn fmt_num(n: f64) -> String { if n.is_nan() { return "f64::NAN".into(); } if n.is_infinite() { return if n > 0.0 { "f64::INFINITY".into() } else { "f64::NEG_INFINITY".into() }; } let s = format!("{}", n); if s.contains('.') || s.contains('e') || s.contains('E') { s } else { format!("{}.0", s) } } const RESERVED: &[&str] = &[ "as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref", "return", "self", "Self", "static", "struct", "super", "trait", "true", "type", "unsafe", "use", "where", "while", "async", "await", "dyn", ]; const GENERATED_PRELUDE: &str = r#"#![allow(unused_variables, unused_mut, dead_code, unused_imports, non_snake_case)] use std::collections::BTreeMap; #[derive(Clone, Debug, PartialEq)] pub enum V { Num(f64), Str(String), Bool(bool), Array(Vec), Struct(BTreeMap), Void, } impl std::fmt::Display for V { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { V::Num(n) => write!(f, "{}", n), V::Str(s) => write!(f, "{}", s), V::Bool(b) => write!(f, "{}", b), V::Array(a) => { write!(f, "[")?; for (i, v) in a.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "{}", v)?; } write!(f, "]") } V::Struct(m) => { write!(f, "{{")?; for (i, (k, v)) in m.iter().enumerate() { if i > 0 { write!(f, ", ")?; } write!(f, "{}: {}", k, v)?; } write!(f, "}}") } V::Void => write!(f, "()"), } } } fn v_num(v: &V) -> f64 { match v { V::Num(n) => *n, V::Bool(b) => if *b { 1.0 } else { 0.0 }, _ => 0.0 } } fn v_truthy(v: &V) -> bool { match v { V::Bool(b) => *b, V::Num(n) => *n != 0.0, V::Str(s) => !s.is_empty(), V::Array(a) => !a.is_empty(), _ => false } } fn v_neg(a: &V) -> V { V::Num(-v_num(a)) } fn v_not(a: &V) -> V { V::Bool(!v_truthy(a)) } fn v_strip(a: &V) -> V { match a { V::Str(s) => V::Str(s.trim().into()), _ => a.clone() } } fn v_add(a: &V, b: &V) -> V { match (a, b) { (V::Str(s1), V::Str(s2)) => V::Str(format!("{}{}", s1, s2)), (V::Array(a1), V::Array(a2)) => V::Array([a1.as_slice(), a2.as_slice()].concat()), _ => V::Num(v_num(a) + v_num(b)) } } fn v_sub(a: &V, b: &V) -> V { V::Num(v_num(a) - v_num(b)) } fn v_mul(a: &V, b: &V) -> V { V::Num(v_num(a) * v_num(b)) } fn v_div(a: &V, b: &V) -> V { let d = v_num(b); V::Num(if d == 0.0 { f64::NAN } else { v_num(a) / d }) } fn v_rem(a: &V, b: &V) -> V { V::Num(v_num(a) % v_num(b)) } fn v_pow(a: &V, b: &V) -> V { V::Num(v_num(a).powf(v_num(b))) } fn v_eq(a: &V, b: &V) -> V { V::Bool(a == b) } fn v_neq(a: &V, b: &V) -> V { V::Bool(a != b) } fn v_lt(a: &V, b: &V) -> V { V::Bool(v_num(a) < v_num(b)) } fn v_gt(a: &V, b: &V) -> V { V::Bool(v_num(a) > v_num(b)) } fn v_lte(a: &V, b: &V) -> V { V::Bool(v_num(a) <= v_num(b)) } fn v_gte(a: &V, b: &V) -> V { V::Bool(v_num(a) >= v_num(b)) } fn v_and(a: &V, b: &V) -> V { V::Bool(v_truthy(a) && v_truthy(b)) } fn v_or(a: &V, b: &V) -> V { V::Bool(v_truthy(a) || v_truthy(b)) } fn v_index(base: &V, idx: &V) -> V { match (base, idx) { (V::Array(arr), V::Num(n)) => arr.get(*n as usize).cloned().unwrap_or(V::Void), (V::Struct(m), V::Str(k)) => m.get(k).cloned().unwrap_or(V::Void), _ => V::Void, } } fn v_range(start: &V, end: &V) -> V { let s = v_num(start) as i64; let e = v_num(end) as i64; V::Array((s..=e).map(|i| V::Num(i as f64)).collect()) } fn v_is(val: &V, type_name: &str) -> V { let matches = match (val, type_name) { (V::Num(_), "number") => true, (V::Str(_), "string") => true, (V::Bool(_), "bool") => true, (V::Array(_), "array") => true, (V::Struct(_), "struct") => true, (V::Void, "void") => true, _ => false, }; V::Bool(matches) } fn v_field(base: &V, name: &str) -> V { match base { V::Struct(m) => m.get(name).cloned().unwrap_or(V::Void), V::Array(a) => match name { "len" => V::Num(a.len() as f64), _ => V::Void, }, _ => V::Void, } } fn v_field_set(base: &mut V, _val: &V) { let _ = base; // path assignment placeholder } fn v_iter(val: &V) -> Vec { match val { V::Array(a) => a.clone(), V::Num(n) => (0..(*n as i64).max(0)).map(|i| V::Num(i as f64)).collect(), _ => Vec::new(), } } fn v_path_assign(target: &mut V, val: &V) { *target = val.clone(); } fn v_cell_get(_table: &str, _col: u32, _row: u32) -> V { V::Void } fn v_cell_range(_table: &str, _c1: u32, _r1: u32, _c2: u32, _r2: u32) -> V { V::Array(Vec::new()) } fn v_cell_table(_table: &str) -> V { V::Array(Vec::new()) } fn v_cell_set(_table: &str, _col: u32, _row: u32, _val: &V) {} fn v_solve_newton(_target_var: &str, _source_fn: &str) -> V { V::Void } fn v_solve_call(_var: &str, _source_fn: &str) -> V { V::Void } fn v_method_call(_recv: &V, _method: &str, _args: &[V]) -> V { V::Void } // --- generated code below --- "#; #[cfg(test)] mod tests { use super::*; fn dec(src: &str) -> String { decompose(src).expect("decompose failed").code } fn dec_full(src: &str) -> Decomposed { decompose(src).expect("decompose failed") } #[test] fn empty_produces_run() { let out = dec(""); assert!(out.contains("pub fn run() -> V")); } #[test] fn let_binding() { let out = dec("let x = 5"); assert!(out.contains("let mut x = V::Num(5.0)")); } #[test] fn binop() { let out = dec("let x = 1 + 2"); assert!(out.contains("v_add(")); } #[test] fn fn_def() { let out = dec("fn add(a, b) {\n return a + b\n}"); assert!(out.contains("pub fn add(mut a: V, mut b: V) -> V")); } #[test] fn if_else() { let out = dec("if true {\n let x = 1\n} else {\n let x = 2\n}"); assert!(out.contains("if v_truthy(")); assert!(out.contains("} else {")); } #[test] fn while_loop() { let out = dec("let i = 0\nwhile i < 10 {\n i = i + 1\n}"); assert!(out.contains("while v_truthy(")); } #[test] fn for_loop() { let out = dec("for x in [1, 2, 3] {\n let _ = x\n}"); assert!(out.contains("for x in v_iter(")); } #[test] fn array_literal() { let out = dec("let a = [1, 2, 3]"); assert!(out.contains("V::Array(vec![")); } #[test] fn index_access() { let out = dec("let a = [1, 2]\nlet b = a[0]"); assert!(out.contains("v_index(")); } #[test] fn struct_literal() { let out = dec("let p = {x: 1, y: 2}"); assert!(out.contains("V::Struct(")); } #[test] fn field_access() { let out = dec("let p = {x: 1}\nlet v = p.x"); assert!(out.contains("v_field(")); } #[test] fn reserved_word_raw_ident() { let out = dec("let type = 1"); assert!(out.contains("r#type")); } #[test] fn self_contained_compiles() { let out = dec("fn double(x) {\n return x * 2\n}\nlet r = double(5)"); assert!(out.contains("pub fn double(")); assert!(out.contains("pub fn run()")); assert!(!out.contains("acord")); } #[test] fn use_emits_mod_and_reports_dep() { let r = dec_full("use math"); assert!(r.code.contains("mod math;")); assert!(r.code.contains("use math::*;")); assert_eq!(r.deps.len(), 1); assert_eq!(r.deps[0].module, "math"); assert_eq!(r.deps[0].item, None); } #[test] fn use_specific_item() { let r = dec_full("use utils::double"); assert!(r.code.contains("mod utils;")); assert!(r.code.contains("use utils::double;")); assert_eq!(r.deps[0].item.as_deref(), Some("double")); } #[test] fn use_with_underscored_name() { let r = dec_full("use my_lib"); assert!(r.code.contains("mod my_lib;")); assert!(r.code.contains("use my_lib::*;")); assert_eq!(r.deps[0].module, "my_lib"); } }