Acord/compile/src/lib.rs

600 lines
21 KiB
Rust

//! 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<String> { None }
/// intercepts a function call by name before the default decomposition.
fn call(&self, _name: &str, _args: &[Expr]) -> Option<String> { None }
/// intercepts an expression before the default decomposition.
fn expr(&self, _expr: &Expr) -> Option<String> { None }
/// intercepts a statement before the default decomposition.
fn stmt(&self, _stmt: &Stmt) -> Option<String> { 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<String>,
}
/// 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<Dependency>,
}
/// decomposes cordial source into standalone rust + a dependency list.
pub fn decompose(source: &str) -> Result<Decomposed, String> {
decompose_with(source, &NoHook)
}
/// decomposes with a custom hook for external extensions.
pub fn decompose_with(source: &str, hook: &dyn DecomposeHook) -> Result<Decomposed, String> {
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<Dependency>, 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<Dependency>, 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::<Vec<_>>()
.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::<Vec<_>>()
.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<String, String> {
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<String> = args.iter()
.map(|a| emit_expr(a, hook))
.collect::<Result<_, _>>()?;
format!("{}({})", ident(name), arg_list.join(", "))
}
}
Expr::Array(items) => {
let parts: Vec<String> = items.iter()
.map(|e| emit_expr(e, hook))
.collect::<Result<_, _>>()?;
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<String> = fields.iter()
.map(|(k, v)| {
let val = emit_expr(v, hook)?;
Ok(format!("({:?}.into(), {})", k, val))
})
.collect::<Result<Vec<_>, 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<String> = args.iter()
.map(|a| emit_expr(a, hook))
.collect::<Result<_, _>>()?;
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<String> = args.iter()
.map(|a| emit_expr(a, hook))
.collect::<Result<_, _>>()?;
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<V>),
Struct(BTreeMap<String, V>),
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<V> {
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");
}
}