Compare commits
2 Commits
9f2e2ff687
...
c18ee2c226
| Author | SHA1 | Date |
|---|---|---|
|
|
c18ee2c226 | |
|
|
6c3066fa1d |
|
|
@ -49,6 +49,11 @@ pub fn decompose(source: &str) -> Result<Decomposed, String> {
|
||||||
/// decomposes with a custom hook for external extensions.
|
/// decomposes with a custom hook for external extensions.
|
||||||
pub fn decompose_with(source: &str, hook: &dyn DecomposeHook) -> Result<Decomposed, String> {
|
pub fn decompose_with(source: &str, hook: &dyn DecomposeHook) -> Result<Decomposed, String> {
|
||||||
let stmts = parse_program(source)?;
|
let stmts = parse_program(source)?;
|
||||||
|
decompose_stmts(&stmts, hook)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// decomposes a pre-parsed statement list.
|
||||||
|
pub fn decompose_stmts(stmts: &[Stmt], hook: &dyn DecomposeHook) -> Result<Decomposed, String> {
|
||||||
let mut deps = Vec::new();
|
let mut deps = Vec::new();
|
||||||
let mut out = String::new();
|
let mut out = String::new();
|
||||||
out.push_str(GENERATED_PRELUDE);
|
out.push_str(GENERATED_PRELUDE);
|
||||||
|
|
@ -56,18 +61,28 @@ pub fn decompose_with(source: &str, hook: &dyn DecomposeHook) -> Result<Decompos
|
||||||
out.push_str(&extra);
|
out.push_str(&extra);
|
||||||
out.push('\n');
|
out.push('\n');
|
||||||
}
|
}
|
||||||
emit_program(&mut out, &stmts, &mut deps, hook)?;
|
emit_program(&mut out, stmts, &mut deps, hook)?;
|
||||||
Ok(Decomposed { code: out, deps })
|
Ok(Decomposed { code: out, deps })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// dispatch-table entry: type, method, fn name, arity.
|
||||||
|
struct MethodReg {
|
||||||
|
ty: String,
|
||||||
|
method: String,
|
||||||
|
fn_name: String,
|
||||||
|
arity: usize,
|
||||||
|
}
|
||||||
|
|
||||||
fn emit_program(out: &mut String, stmts: &[Stmt], deps: &mut Vec<Dependency>, hook: &dyn DecomposeHook) -> Result<(), String> {
|
fn emit_program(out: &mut String, stmts: &[Stmt], deps: &mut Vec<Dependency>, hook: &dyn DecomposeHook) -> Result<(), String> {
|
||||||
let mut uses = Vec::new();
|
let mut uses = Vec::new();
|
||||||
let mut fns = Vec::new();
|
let mut fns = Vec::new();
|
||||||
|
let mut impls = Vec::new();
|
||||||
let mut rest = Vec::new();
|
let mut rest = Vec::new();
|
||||||
for s in stmts {
|
for s in stmts {
|
||||||
match s {
|
match s {
|
||||||
Stmt::Use(..) => uses.push(s),
|
Stmt::Use(..) => uses.push(s),
|
||||||
Stmt::FnDef { .. } => fns.push(s),
|
Stmt::FnDef { .. } => fns.push(s),
|
||||||
|
Stmt::ImplBlock { .. } | Stmt::TraitDef { .. } => impls.push(s),
|
||||||
_ => rest.push(s),
|
_ => rest.push(s),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -81,15 +96,77 @@ fn emit_program(out: &mut String, stmts: &[Stmt], deps: &mut Vec<Dependency>, ho
|
||||||
emit_stmt(out, s, 0, deps, hook)?;
|
emit_stmt(out, s, 0, deps, hook)?;
|
||||||
out.push('\n');
|
out.push('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut regs: Vec<MethodReg> = Vec::new();
|
||||||
|
for s in &impls {
|
||||||
|
if let Stmt::ImplBlock { type_name, methods, .. } = s {
|
||||||
|
emit_impl(out, type_name, methods, &mut regs, deps, hook)?;
|
||||||
|
out.push('\n');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
indent(out, 0, "pub fn run() -> V {");
|
indent(out, 0, "pub fn run() -> V {");
|
||||||
for s in &rest {
|
for s in &rest {
|
||||||
emit_stmt(out, s, 1, deps, hook)?;
|
emit_stmt(out, s, 1, deps, hook)?;
|
||||||
}
|
}
|
||||||
indent(out, 1, "V::Void");
|
indent(out, 1, "V::Void");
|
||||||
indent(out, 0, "}");
|
indent(out, 0, "}");
|
||||||
|
out.push('\n');
|
||||||
|
|
||||||
|
emit_dispatch(out, ®s);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// emits each impl method as a dispatch-registered free function.
|
||||||
|
fn emit_impl(
|
||||||
|
out: &mut String,
|
||||||
|
type_name: &str,
|
||||||
|
methods: &[Stmt],
|
||||||
|
regs: &mut Vec<MethodReg>,
|
||||||
|
deps: &mut Vec<Dependency>,
|
||||||
|
hook: &dyn DecomposeHook,
|
||||||
|
) -> Result<(), String> {
|
||||||
|
for m in methods {
|
||||||
|
let Stmt::FnDef { name, params, body, .. } = m else { continue };
|
||||||
|
let fn_name = format!("{}__{}", ident(type_name), ident(name));
|
||||||
|
let param_list: String = params.iter()
|
||||||
|
.map(|(p, _)| format!("mut {}: V", ident(p)))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
indent(out, 0, &format!("pub fn {}({}) -> V {{", fn_name, param_list));
|
||||||
|
for s in body {
|
||||||
|
emit_stmt(out, s, 1, deps, hook)?;
|
||||||
|
}
|
||||||
|
indent(out, 1, "V::Void");
|
||||||
|
indent(out, 0, "}");
|
||||||
|
regs.push(MethodReg {
|
||||||
|
ty: type_name.to_string(),
|
||||||
|
method: name.clone(),
|
||||||
|
fn_name,
|
||||||
|
arity: params.len(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// emits the runtime dispatch table keyed on the struct __type tag.
|
||||||
|
fn emit_dispatch(out: &mut String, regs: &[MethodReg]) {
|
||||||
|
out.push_str("fn v_dispatch(ty: &str, method: &str, args: &[V]) -> Option<V> {\n");
|
||||||
|
out.push_str(" match (ty, method) {\n");
|
||||||
|
for r in regs {
|
||||||
|
let call_args: String = (0..r.arity)
|
||||||
|
.map(|i| format!("args.get({}).cloned().unwrap_or(V::Void)", i))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
out.push_str(&format!(
|
||||||
|
" ({:?}, {:?}) => Some({}({})),\n",
|
||||||
|
r.ty, r.method, r.fn_name, call_args
|
||||||
|
));
|
||||||
|
}
|
||||||
|
out.push_str(" _ => None,\n");
|
||||||
|
out.push_str(" }\n}\n");
|
||||||
|
}
|
||||||
|
|
||||||
fn emit_stmt(out: &mut String, stmt: &Stmt, depth: usize, deps: &mut Vec<Dependency>, hook: &dyn DecomposeHook) -> Result<(), String> {
|
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) {
|
if let Some(custom) = hook.stmt(stmt) {
|
||||||
indent(out, depth, &custom);
|
indent(out, depth, &custom);
|
||||||
|
|
@ -105,9 +182,13 @@ fn emit_stmt(out: &mut String, stmt: &Stmt, depth: usize, deps: &mut Vec<Depende
|
||||||
indent(out, depth, &format!("{} = {};", ident(name), rhs));
|
indent(out, depth, &format!("{} = {};", ident(name), rhs));
|
||||||
}
|
}
|
||||||
Stmt::PathAssign(lhs, expr) => {
|
Stmt::PathAssign(lhs, expr) => {
|
||||||
let lhs_s = emit_expr(lhs, hook)?;
|
let mut steps = Vec::new();
|
||||||
|
let root = collect_lvalue_path(lhs, &mut steps, hook)?;
|
||||||
let rhs = emit_expr(expr, hook)?;
|
let rhs = emit_expr(expr, hook)?;
|
||||||
indent(out, depth, &format!("v_path_assign(&mut {}, &{});", lhs_s, rhs));
|
indent(out, depth, &format!(
|
||||||
|
"v_assign_path(&mut {}, &[{}], {});",
|
||||||
|
root, steps.join(", "), rhs
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Stmt::Return(expr) => {
|
Stmt::Return(expr) => {
|
||||||
let rhs = emit_expr(expr, hook)?;
|
let rhs = emit_expr(expr, hook)?;
|
||||||
|
|
@ -194,28 +275,31 @@ fn emit_stmt(out: &mut String, stmt: &Stmt, depth: usize, deps: &mut Vec<Depende
|
||||||
));
|
));
|
||||||
indent(out, depth, "}");
|
indent(out, depth, "}");
|
||||||
}
|
}
|
||||||
Stmt::TraitDef { name, methods } => {
|
// hoisted to free functions in emit_program.
|
||||||
indent(out, depth, &format!("pub trait {} {{", ident(name)));
|
Stmt::TraitDef { .. } | Stmt::ImplBlock { .. } => {}
|
||||||
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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// walks a Field/Index lvalue chain into a root identifier plus emitted Step nodes.
|
||||||
|
fn collect_lvalue_path(expr: &Expr, steps: &mut Vec<String>, hook: &dyn DecomposeHook) -> Result<String, String> {
|
||||||
|
match expr {
|
||||||
|
Expr::Ident(name) => Ok(ident(name)),
|
||||||
|
Expr::Field(inner, field) => {
|
||||||
|
let root = collect_lvalue_path(inner, steps, hook)?;
|
||||||
|
steps.push(format!("Step::Field({:?}.into())", field));
|
||||||
|
Ok(root)
|
||||||
|
}
|
||||||
|
Expr::Index(inner, idx) => {
|
||||||
|
let root = collect_lvalue_path(inner, steps, hook)?;
|
||||||
|
let i = emit_expr(idx, hook)?;
|
||||||
|
steps.push(format!("Step::Index(v_num(&{}) as i64)", i));
|
||||||
|
Ok(root)
|
||||||
|
}
|
||||||
|
_ => Err("assignment target must root at a variable".into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn emit_expr(expr: &Expr, hook: &dyn DecomposeHook) -> Result<String, String> {
|
fn emit_expr(expr: &Expr, hook: &dyn DecomposeHook) -> Result<String, String> {
|
||||||
if let Some(custom) = hook.expr(expr) {
|
if let Some(custom) = hook.expr(expr) {
|
||||||
return Ok(custom);
|
return Ok(custom);
|
||||||
|
|
@ -344,6 +428,7 @@ fn indent(out: &mut String, depth: usize, line: &str) {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ident(name: &str) -> String {
|
fn ident(name: &str) -> String {
|
||||||
|
if name == "self" { return "self_".into(); }
|
||||||
if RESERVED.contains(&name) { format!("r#{}", name) } else { name.replace('-', "_") }
|
if RESERVED.contains(&name) { format!("r#{}", name) } else { name.replace('-', "_") }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -405,25 +490,41 @@ impl std::fmt::Display for V {
|
||||||
}
|
}
|
||||||
|
|
||||||
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_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_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(), V::Struct(m) => !m.is_empty(), _ => false } }
|
||||||
|
|
||||||
fn v_neg(a: &V) -> V { V::Num(-v_num(a)) }
|
fn v_struct_type(v: &V) -> Option<String> {
|
||||||
fn v_not(a: &V) -> V { V::Bool(!v_truthy(a)) }
|
if let V::Struct(m) = v {
|
||||||
fn v_strip(a: &V) -> V { match a { V::Str(s) => V::Str(s.trim().into()), _ => a.clone() } }
|
if let Some(V::Str(t)) = m.get("__type") { return Some(t.clone()); }
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
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_binop_overload(method: &str, a: &V, b: &V) -> Option<V> {
|
||||||
fn v_sub(a: &V, b: &V) -> V { V::Num(v_num(a) - v_num(b)) }
|
if let Some(t) = v_struct_type(a) { return v_dispatch(&t, method, &[a.clone(), b.clone()]); }
|
||||||
fn v_mul(a: &V, b: &V) -> V { V::Num(v_num(a) * v_num(b)) }
|
None
|
||||||
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_unop_overload(method: &str, a: &V) -> Option<V> {
|
||||||
fn v_pow(a: &V, b: &V) -> V { V::Num(v_num(a).powf(v_num(b))) }
|
if let Some(t) = v_struct_type(a) { return v_dispatch(&t, method, &[a.clone()]); }
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
fn v_eq(a: &V, b: &V) -> V { V::Bool(a == b) }
|
fn v_neg(a: &V) -> V { if let Some(r) = v_unop_overload("neg", a) { return r; } V::Num(-v_num(a)) }
|
||||||
fn v_neq(a: &V, b: &V) -> V { V::Bool(a != b) }
|
fn v_not(a: &V) -> V { if let Some(r) = v_unop_overload("not", a) { return r; } V::Bool(!v_truthy(a)) }
|
||||||
fn v_lt(a: &V, b: &V) -> V { V::Bool(v_num(a) < v_num(b)) }
|
fn v_strip(a: &V) -> V { if let Some(r) = v_unop_overload("strip", a) { return r; } match a { V::Str(s) => V::Str(s.trim().into()), V::Bool(b) => V::Num(if *b { 1.0 } else { 0.0 }), _ => a.clone() } }
|
||||||
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_add(a: &V, b: &V) -> V { if let Some(r) = v_binop_overload("add", a, b) { return r; } match (a, b) { (V::Str(s1), V::Str(s2)) => V::Str(format!("{}{}", s1, s2)), (V::Str(s), o) => V::Str(format!("{}{}", s, o)), (o, V::Str(s)) => V::Str(format!("{}{}", o, s)), (V::Array(a1), V::Array(a2)) => V::Array([a1.as_slice(), a2.as_slice()].concat()), (V::Array(a1), o) => { let mut v = a1.clone(); v.push(o.clone()); V::Array(v) }, (o, V::Array(a2)) => { let mut v = vec![o.clone()]; v.extend(a2.iter().cloned()); V::Array(v) }, _ => V::Num(v_num(a) + v_num(b)) } }
|
||||||
fn v_gte(a: &V, b: &V) -> V { V::Bool(v_num(a) >= v_num(b)) }
|
fn v_sub(a: &V, b: &V) -> V { if let Some(r) = v_binop_overload("sub", a, b) { return r; } V::Num(v_num(a) - v_num(b)) }
|
||||||
|
fn v_mul(a: &V, b: &V) -> V { if let Some(r) = v_binop_overload("mul", a, b) { return r; } V::Num(v_num(a) * v_num(b)) }
|
||||||
|
fn v_div(a: &V, b: &V) -> V { if let Some(r) = v_binop_overload("div", a, b) { return r; } 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 { if let Some(r) = v_binop_overload("rem", a, b) { return r; } V::Num(v_num(a) % v_num(b)) }
|
||||||
|
fn v_pow(a: &V, b: &V) -> V { if let Some(r) = v_binop_overload("pow", a, b) { return r; } V::Num(v_num(a).powf(v_num(b))) }
|
||||||
|
|
||||||
|
fn v_eq(a: &V, b: &V) -> V { if let Some(r) = v_binop_overload("eq", a, b) { return r; } V::Bool(a == b) }
|
||||||
|
fn v_neq(a: &V, b: &V) -> V { if let Some(r) = v_binop_overload("ne", a, b) { return r; } V::Bool(a != b) }
|
||||||
|
fn v_lt(a: &V, b: &V) -> V { if let Some(r) = v_binop_overload("lt", a, b) { return r; } V::Bool(v_num(a) < v_num(b)) }
|
||||||
|
fn v_gt(a: &V, b: &V) -> V { if let Some(r) = v_binop_overload("gt", a, b) { return r; } V::Bool(v_num(a) > v_num(b)) }
|
||||||
|
fn v_lte(a: &V, b: &V) -> V { if let Some(r) = v_binop_overload("le", a, b) { return r; } V::Bool(v_num(a) <= v_num(b)) }
|
||||||
|
fn v_gte(a: &V, b: &V) -> V { if let Some(r) = v_binop_overload("ge", a, b) { return r; } 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_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_or(a: &V, b: &V) -> V { V::Bool(v_truthy(a) || v_truthy(b)) }
|
||||||
|
|
||||||
|
|
@ -465,8 +566,31 @@ fn v_field(base: &V, name: &str) -> V {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn v_field_set(base: &mut V, _val: &V) {
|
enum Step { Field(String), Index(i64) }
|
||||||
let _ = base; // path assignment placeholder
|
|
||||||
|
fn v_assign_path(root: &mut V, steps: &[Step], val: V) {
|
||||||
|
let mut cur = root;
|
||||||
|
for (i, step) in steps.iter().enumerate() {
|
||||||
|
let last = i + 1 == steps.len();
|
||||||
|
match step {
|
||||||
|
Step::Field(k) => match cur {
|
||||||
|
V::Struct(m) => {
|
||||||
|
if last { m.insert(k.clone(), val); return; }
|
||||||
|
cur = m.entry(k.clone()).or_insert(V::Void);
|
||||||
|
}
|
||||||
|
_ => return,
|
||||||
|
},
|
||||||
|
Step::Index(idx) => match cur {
|
||||||
|
V::Array(a) => {
|
||||||
|
let n = if *idx < 0 { (a.len() as i64 + idx) as usize } else { *idx as usize };
|
||||||
|
if n >= a.len() { return; }
|
||||||
|
if last { a[n] = val; return; }
|
||||||
|
cur = &mut a[n];
|
||||||
|
}
|
||||||
|
_ => return,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn v_iter(val: &V) -> Vec<V> {
|
fn v_iter(val: &V) -> Vec<V> {
|
||||||
|
|
@ -477,14 +601,20 @@ fn v_iter(val: &V) -> Vec<V> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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_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_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_table(_table: &str) -> V { V::Array(Vec::new()) }
|
||||||
fn v_cell_set(_table: &str, _col: u32, _row: u32, _val: &V) {}
|
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_newton(_target_var: &str, _source_fn: &str) -> V { V::Void }
|
||||||
fn v_solve_call(_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 }
|
fn v_method_call(recv: &V, method: &str, args: &[V]) -> V {
|
||||||
|
if let Some(t) = v_struct_type(recv) {
|
||||||
|
let mut full = vec![recv.clone()];
|
||||||
|
full.extend_from_slice(args);
|
||||||
|
if let Some(r) = v_dispatch(&t, method, &full) { return r; }
|
||||||
|
}
|
||||||
|
V::Void
|
||||||
|
}
|
||||||
|
|
||||||
// --- generated code below ---
|
// --- generated code below ---
|
||||||
|
|
||||||
|
|
@ -616,4 +746,101 @@ mod tests {
|
||||||
assert!(r.code.contains("use sitter::scene::primitives;"));
|
assert!(r.code.contains("use sitter::scene::primitives;"));
|
||||||
assert_eq!(r.deps[0].segments, vec!["sitter", "scene", "primitives"]);
|
assert_eq!(r.deps[0].segments, vec!["sitter", "scene", "primitives"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn impl_method_emits_free_fn_and_dispatch() {
|
||||||
|
let out = dec("impl Vec2 {\n fn new(x, y) {\n return {__type: \"Vec2\", x: x, y: y}\n }\n fn add(self, other) {\n return Vec2::new(self.x + other.x, self.y + other.y)\n }\n}");
|
||||||
|
assert!(out.contains("pub fn Vec2__new("));
|
||||||
|
assert!(out.contains("pub fn Vec2__add(mut self_: V, mut other: V)"));
|
||||||
|
assert!(out.contains("(\"Vec2\", \"add\") => Some(Vec2__add("));
|
||||||
|
assert!(!out.contains("impl Vec2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// rustc compile-and-run check of operator overloading and method dispatch.
|
||||||
|
#[test]
|
||||||
|
fn decomposed_binary_runs_with_correct_results() {
|
||||||
|
let src = "\
|
||||||
|
impl Vec2 {
|
||||||
|
fn new(x, y) {
|
||||||
|
return {__type: \"Vec2\", x: x, y: y}
|
||||||
|
}
|
||||||
|
fn add(self, other) {
|
||||||
|
return Vec2::new(self.x + other.x, self.y + other.y)
|
||||||
|
}
|
||||||
|
fn len2(self) {
|
||||||
|
return self.x * self.x + self.y * self.y
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let a = Vec2::new(1, 2)
|
||||||
|
let b = Vec2::new(3, 4)
|
||||||
|
let c = a + b
|
||||||
|
let p = {x: 1, y: 2}
|
||||||
|
p.x = 10
|
||||||
|
let arr = [1, 2, 3]
|
||||||
|
arr[1] = 20
|
||||||
|
let node = {pose: {pos: {x: 0}}}
|
||||||
|
node.pose.pos.x = 5
|
||||||
|
";
|
||||||
|
let mut code = dec(src);
|
||||||
|
assert!(code.contains("v_assign_path(&mut p"));
|
||||||
|
assert!(code.contains("v_assign_path(&mut arr"));
|
||||||
|
assert!(code.contains("v_assign_path(&mut node"));
|
||||||
|
code.push_str(r#"
|
||||||
|
fn main() {
|
||||||
|
let a = Vec2__new(V::Num(1.0), V::Num(2.0));
|
||||||
|
let b = Vec2__new(V::Num(3.0), V::Num(4.0));
|
||||||
|
let c = v_add(&a, &b);
|
||||||
|
assert_eq!(v_field(&c, "x"), V::Num(4.0));
|
||||||
|
assert_eq!(v_field(&c, "y"), V::Num(6.0));
|
||||||
|
let l = v_method_call(&a, "len2", &[]);
|
||||||
|
assert_eq!(l, V::Num(5.0));
|
||||||
|
|
||||||
|
let mut p = V::Struct([("x".to_string(), V::Num(1.0))].into_iter().collect());
|
||||||
|
v_assign_path(&mut p, &[Step::Field("x".into())], V::Num(10.0));
|
||||||
|
assert_eq!(v_field(&p, "x"), V::Num(10.0));
|
||||||
|
|
||||||
|
let mut arr = V::Array(vec![V::Num(1.0), V::Num(2.0), V::Num(3.0)]);
|
||||||
|
v_assign_path(&mut arr, &[Step::Index(1)], V::Num(20.0));
|
||||||
|
assert_eq!(v_index(&arr, &V::Num(1.0)), V::Num(20.0));
|
||||||
|
|
||||||
|
let mut node = V::Struct([("pose".to_string(), V::Struct([("pos".to_string(), V::Struct([("x".to_string(), V::Num(0.0))].into_iter().collect()))].into_iter().collect()))].into_iter().collect());
|
||||||
|
v_assign_path(&mut node, &[Step::Field("pose".into()), Step::Field("pos".into()), Step::Field("x".into())], V::Num(5.0));
|
||||||
|
assert_eq!(v_field(&v_field(&v_field(&node, "pose"), "pos"), "x"), V::Num(5.0));
|
||||||
|
|
||||||
|
let _ = run();
|
||||||
|
println!("OK");
|
||||||
|
}
|
||||||
|
"#);
|
||||||
|
|
||||||
|
let dir = std::env::temp_dir().join(format!("acord-dec-compile-{}", std::process::id()));
|
||||||
|
let _ = std::fs::remove_dir_all(&dir);
|
||||||
|
std::fs::create_dir_all(&dir).unwrap();
|
||||||
|
let src_path = dir.join("prog.rs");
|
||||||
|
let bin_path = dir.join("prog");
|
||||||
|
std::fs::write(&src_path, &code).unwrap();
|
||||||
|
|
||||||
|
let compile = std::process::Command::new("rustc")
|
||||||
|
.arg("--edition").arg("2021")
|
||||||
|
.arg("-O")
|
||||||
|
.arg(&src_path)
|
||||||
|
.arg("-o").arg(&bin_path)
|
||||||
|
.output();
|
||||||
|
|
||||||
|
let compile = match compile {
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(_) => { std::fs::remove_dir_all(&dir).ok(); return; }
|
||||||
|
};
|
||||||
|
assert!(
|
||||||
|
compile.status.success(),
|
||||||
|
"rustc failed:\n{}\n--- source ---\n{}",
|
||||||
|
String::from_utf8_lossy(&compile.stderr),
|
||||||
|
code
|
||||||
|
);
|
||||||
|
|
||||||
|
let run = std::process::Command::new(&bin_path).output().unwrap();
|
||||||
|
assert!(run.status.success(), "binary panicked:\n{}", String::from_utf8_lossy(&run.stderr));
|
||||||
|
assert_eq!(String::from_utf8_lossy(&run.stdout).trim(), "OK");
|
||||||
|
|
||||||
|
std::fs::remove_dir_all(&dir).ok();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Op {
|
pub enum Op {
|
||||||
Add, Sub, Mul, Div, Mod, Pow,
|
Add, Sub, Mul, Div, Mod, Pow,
|
||||||
Eq, Neq, Lt, Gt, Lte, Gte,
|
Eq, Neq, Lt, Gt, Lte, Gte,
|
||||||
|
|
@ -6,6 +6,30 @@ pub enum Op {
|
||||||
Strip,
|
Strip,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Op {
|
||||||
|
/// maps an operator to its overload method name.
|
||||||
|
pub fn overload_method(self) -> Option<&'static str> {
|
||||||
|
Some(match self {
|
||||||
|
Op::Add => "add",
|
||||||
|
Op::Sub => "sub",
|
||||||
|
Op::Mul => "mul",
|
||||||
|
Op::Div => "div",
|
||||||
|
Op::Mod => "rem",
|
||||||
|
Op::Pow => "pow",
|
||||||
|
Op::Eq => "eq",
|
||||||
|
Op::Neq => "ne",
|
||||||
|
Op::Lt => "lt",
|
||||||
|
Op::Gt => "gt",
|
||||||
|
Op::Lte => "le",
|
||||||
|
Op::Gte => "ge",
|
||||||
|
Op::Neg => "neg",
|
||||||
|
Op::Not => "not",
|
||||||
|
Op::Strip => "strip",
|
||||||
|
Op::And | Op::Or => return None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Stmt {
|
pub enum Stmt {
|
||||||
Let(String, Option<String>, Expr),
|
Let(String, Option<String>, Expr),
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,11 @@ impl Interpreter {
|
||||||
|
|
||||||
let l_raw = self.eval_expr(lhs, depth)?;
|
let l_raw = self.eval_expr(lhs, depth)?;
|
||||||
let r_raw = self.eval_expr(rhs, depth)?;
|
let r_raw = self.eval_expr(rhs, depth)?;
|
||||||
|
|
||||||
|
if let Some(res) = self.try_binop_overload(*op, &l_raw, &r_raw, depth) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
let (l, l_unit) = unwrap_spice(&l_raw);
|
let (l, l_unit) = unwrap_spice(&l_raw);
|
||||||
let (r, r_unit) = unwrap_spice(&r_raw);
|
let (r, r_unit) = unwrap_spice(&r_raw);
|
||||||
let had_unit = l_unit.is_some() || r_unit.is_some();
|
let had_unit = l_unit.is_some() || r_unit.is_some();
|
||||||
|
|
@ -99,4 +104,56 @@ impl Interpreter {
|
||||||
};
|
};
|
||||||
Ok(retag_spice(result?, unit_after))
|
Ok(retag_spice(result?, unit_after))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// dispatches a binary operator to a typed operand's impl method or an extern hook.
|
||||||
|
fn try_binop_overload(&mut self, op: Op, left: &Value, right: &Value, depth: u32)
|
||||||
|
-> Option<Result<Value, String>>
|
||||||
|
{
|
||||||
|
if let Value::Struct(s) = left {
|
||||||
|
let tag = s.borrow().get("__type").and_then(|v| match v {
|
||||||
|
Value::Str(s) => Some(s.clone()),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
if let (Some(tag), Some(method)) = (tag, op.overload_method()) {
|
||||||
|
if let Some(fndef) = self.methods.get(&(tag, method.to_string())).cloned() {
|
||||||
|
return Some(self.call_fndef(&fndef, &[left.clone(), right.clone()], depth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matches!(left, Value::Extern(_)) || matches!(right, Value::Extern(_)) {
|
||||||
|
let hooks = self.hooks.hooks.clone();
|
||||||
|
for h in &hooks {
|
||||||
|
if let Some(res) = h.extern_binop(self, op, left, right, depth) {
|
||||||
|
return Some(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// dispatches a unary operator to a typed operand's impl method or an extern hook.
|
||||||
|
pub(crate) fn try_unop_overload(&mut self, op: Op, operand: &Value, depth: u32)
|
||||||
|
-> Option<Result<Value, String>>
|
||||||
|
{
|
||||||
|
if let Value::Struct(s) = operand {
|
||||||
|
let tag = s.borrow().get("__type").and_then(|v| match v {
|
||||||
|
Value::Str(s) => Some(s.clone()),
|
||||||
|
_ => None,
|
||||||
|
});
|
||||||
|
if let (Some(tag), Some(method)) = (tag, op.overload_method()) {
|
||||||
|
if let Some(fndef) = self.methods.get(&(tag, method.to_string())).cloned() {
|
||||||
|
return Some(self.call_fndef(&fndef, &[operand.clone()], depth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if matches!(operand, Value::Extern(_)) {
|
||||||
|
let hooks = self.hooks.hooks.clone();
|
||||||
|
for h in &hooks {
|
||||||
|
if let Some(res) = h.extern_unop(self, op, operand, depth) {
|
||||||
|
return Some(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -58,6 +58,9 @@ impl Interpreter {
|
||||||
}
|
}
|
||||||
Expr::UnaryOp(Op::Neg, inner) => {
|
Expr::UnaryOp(Op::Neg, inner) => {
|
||||||
let v = self.eval_expr(inner, depth)?;
|
let v = self.eval_expr(inner, depth)?;
|
||||||
|
if let Some(res) = self.try_unop_overload(Op::Neg, &v, depth) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
match v {
|
match v {
|
||||||
Value::Number(n) => Ok(Value::Number(-n)),
|
Value::Number(n) => Ok(Value::Number(-n)),
|
||||||
_ => Err("cannot negate non-number".into()),
|
_ => Err("cannot negate non-number".into()),
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
|
|
||||||
use super::ast::{Expr, Stmt};
|
use super::ast::{Expr, Op, Stmt};
|
||||||
use super::parse::Parser;
|
use super::parse::Parser;
|
||||||
use super::token::Token;
|
use super::token::Token;
|
||||||
use super::value::{ExternHandle, Value};
|
use super::value::{ExternHandle, Value};
|
||||||
|
|
@ -34,6 +34,11 @@ pub trait InterpreterHook {
|
||||||
fn extern_method_call(&self, _i: &mut Interpreter, _h: &ExternHandle,
|
fn extern_method_call(&self, _i: &mut Interpreter, _h: &ExternHandle,
|
||||||
_method: &str, _args: &[Value], _depth: u32) -> Option<Result<Value, String>> { None }
|
_method: &str, _args: &[Value], _depth: u32) -> Option<Result<Value, String>> { None }
|
||||||
fn extern_eq(&self, _a: &ExternHandle, _b: &ExternHandle) -> Option<bool> { None }
|
fn extern_eq(&self, _a: &ExternHandle, _b: &ExternHandle) -> Option<bool> { None }
|
||||||
|
|
||||||
|
fn extern_binop(&self, _i: &mut Interpreter, _op: Op, _left: &Value, _right: &Value, _depth: u32)
|
||||||
|
-> Option<Result<Value, String>> { None }
|
||||||
|
fn extern_unop(&self, _i: &mut Interpreter, _op: Op, _operand: &Value, _depth: u32)
|
||||||
|
-> Option<Result<Value, String>> { None }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct HookList {
|
pub(crate) struct HookList {
|
||||||
|
|
|
||||||
|
|
@ -28,13 +28,12 @@ pub use modules::{extract_use_declarations, ModuleExports, UseDecl};
|
||||||
pub use parse::{parse_program, Parser};
|
pub use parse::{parse_program, Parser};
|
||||||
pub use ring::RingBuf;
|
pub use ring::RingBuf;
|
||||||
pub use tables::{display_addr, parse_cell_address};
|
pub use tables::{display_addr, parse_cell_address};
|
||||||
pub use token::Token;
|
pub use token::{tokenize, Token};
|
||||||
pub use value::{display_value_with_type, ExternHandle, Value};
|
pub use value::{display_value_with_type, ExternHandle, Value};
|
||||||
|
|
||||||
use hooks::HookList;
|
use hooks::HookList;
|
||||||
use spice::source_enables_spice;
|
use spice::source_enables_spice;
|
||||||
use tables::{coerce_cell_value, rows_to_value};
|
use tables::{coerce_cell_value, rows_to_value};
|
||||||
use token::tokenize;
|
|
||||||
|
|
||||||
pub(crate) const MAX_ITERATIONS: usize = 10_000;
|
pub(crate) const MAX_ITERATIONS: usize = 10_000;
|
||||||
pub(crate) const MAX_CALL_DEPTH: u32 = 256;
|
pub(crate) const MAX_CALL_DEPTH: u32 = 256;
|
||||||
|
|
|
||||||
|
|
@ -110,7 +110,6 @@ fn use_module_colon_submodule_loads_sibling_cord_file() {
|
||||||
assert!(matches!(i.get_var("standard_outer"), Some(Value::Number(n)) if (n - 9.8).abs() < 1e-9));
|
assert!(matches!(i.get_var("standard_outer"), Some(Value::Number(n)) if (n - 9.8).abs() < 1e-9));
|
||||||
let v = i.eval("make_toroid()").unwrap();
|
let v = i.eval("make_toroid()").unwrap();
|
||||||
assert!(matches!(v, Value::Number(n) if n == 42.0));
|
assert!(matches!(v, Value::Number(n) if n == 42.0));
|
||||||
// coils.cord should NOT be loaded by `use sitter::primitives`
|
|
||||||
assert!(i.get_var("standard_turns").is_none());
|
assert!(i.get_var("standard_turns").is_none());
|
||||||
|
|
||||||
fs::remove_dir_all(&dir).ok();
|
fs::remove_dir_all(&dir).ok();
|
||||||
|
|
|
||||||
|
|
@ -24,3 +24,4 @@ mod embedding;
|
||||||
mod hooks;
|
mod hooks;
|
||||||
mod loader;
|
mod loader;
|
||||||
mod module_paths;
|
mod module_paths;
|
||||||
|
mod operators;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,145 @@
|
||||||
|
use std::rc::Rc;
|
||||||
|
|
||||||
|
use crate::interp::*;
|
||||||
|
|
||||||
|
#[allow(unused_imports)]
|
||||||
|
use super::helpers::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn struct_add_overload_dispatches_to_impl() {
|
||||||
|
let mut i = Interpreter::new();
|
||||||
|
i.exec("impl Vec2 { fn new(x, y) { return {__type: \"Vec2\", x: x, y: y} } fn add(self, other) { return Vec2::new(self.x + other.x, self.y + other.y) } }").unwrap();
|
||||||
|
i.exec("let a = Vec2::new(1, 2)").unwrap();
|
||||||
|
i.exec("let b = Vec2::new(3, 4)").unwrap();
|
||||||
|
let v = i.eval("(a + b).x").unwrap();
|
||||||
|
assert!(matches!(v, Value::Number(n) if n == 4.0));
|
||||||
|
let v = i.eval("(a + b).y").unwrap();
|
||||||
|
assert!(matches!(v, Value::Number(n) if n == 6.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn struct_mul_overload_dispatches() {
|
||||||
|
let mut i = Interpreter::new();
|
||||||
|
i.exec("impl Scale { fn new(f) { return {__type: \"Scale\", f: f} } fn mul(self, other) { return Scale::new(self.f * other.f) } }").unwrap();
|
||||||
|
i.exec("let a = Scale::new(3)").unwrap();
|
||||||
|
i.exec("let b = Scale::new(4)").unwrap();
|
||||||
|
let v = i.eval("(a * b).f").unwrap();
|
||||||
|
assert!(matches!(v, Value::Number(n) if n == 12.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn struct_eq_overload_overrides_default_false() {
|
||||||
|
let mut i = Interpreter::new();
|
||||||
|
i.exec("impl Tag { fn new(id) { return {__type: \"Tag\", id: id} } fn eq(self, other) { return self.id == other.id } }").unwrap();
|
||||||
|
i.exec("let a = Tag::new(7)").unwrap();
|
||||||
|
i.exec("let b = Tag::new(7)").unwrap();
|
||||||
|
let v = i.eval("a == b").unwrap();
|
||||||
|
assert!(matches!(v, Value::Bool(true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn struct_neg_overload_dispatches() {
|
||||||
|
let mut i = Interpreter::new();
|
||||||
|
i.exec("impl Money { fn new(c) { return {__type: \"Money\", c: c} } fn neg(self) { return Money::new(-self.c) } }").unwrap();
|
||||||
|
i.exec("let m = Money::new(5)").unwrap();
|
||||||
|
let v = i.eval("(-m).c").unwrap();
|
||||||
|
assert!(matches!(v, Value::Number(n) if n == -5.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn untyped_struct_without_overload_keeps_default_behavior() {
|
||||||
|
let mut i = Interpreter::new();
|
||||||
|
i.exec("let a = {x: 1}").unwrap();
|
||||||
|
i.exec("let b = {x: 1}").unwrap();
|
||||||
|
let v = i.eval("a == b").unwrap();
|
||||||
|
assert!(matches!(v, Value::Bool(false)));
|
||||||
|
}
|
||||||
|
|
||||||
|
struct VecExtern;
|
||||||
|
|
||||||
|
impl InterpreterHook for VecExtern {
|
||||||
|
fn name(&self) -> &str { "vec_extern" }
|
||||||
|
fn extern_binop(&self, _i: &mut Interpreter, op: Op, left: &Value, right: &Value, _depth: u32)
|
||||||
|
-> Option<Result<Value, String>>
|
||||||
|
{
|
||||||
|
let (a, b) = match (left, right) {
|
||||||
|
(Value::Extern(a), Value::Extern(b)) if a.kind == "vec" && b.kind == "vec" => (a.id, b.id),
|
||||||
|
_ => return None,
|
||||||
|
};
|
||||||
|
match op {
|
||||||
|
Op::Add => Some(Ok(Value::Number((a + b) as f64))),
|
||||||
|
Op::Eq => Some(Ok(Value::Bool(a == b))),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn extern_unop(&self, _i: &mut Interpreter, op: Op, operand: &Value, _depth: u32)
|
||||||
|
-> Option<Result<Value, String>>
|
||||||
|
{
|
||||||
|
let id = match operand { Value::Extern(h) if h.kind == "vec" => h.id, _ => return None };
|
||||||
|
match op {
|
||||||
|
Op::Neg => Some(Ok(Value::Number(-(id as f64)))),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extern_binop_hook_handles_addition() {
|
||||||
|
let mut i = Interpreter::new();
|
||||||
|
i.register_hook(Rc::new(VecExtern)).unwrap();
|
||||||
|
i.set_var("u", Value::Extern(ExternHandle { kind: "vec".into(), id: 3 }));
|
||||||
|
i.set_var("v", Value::Extern(ExternHandle { kind: "vec".into(), id: 4 }));
|
||||||
|
let r = i.eval("u + v").unwrap();
|
||||||
|
assert!(matches!(r, Value::Number(n) if n == 7.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extern_binop_hook_handles_equality() {
|
||||||
|
let mut i = Interpreter::new();
|
||||||
|
i.register_hook(Rc::new(VecExtern)).unwrap();
|
||||||
|
i.set_var("u", Value::Extern(ExternHandle { kind: "vec".into(), id: 5 }));
|
||||||
|
i.set_var("v", Value::Extern(ExternHandle { kind: "vec".into(), id: 5 }));
|
||||||
|
let r = i.eval("u == v").unwrap();
|
||||||
|
assert!(matches!(r, Value::Bool(true)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extern_unop_hook_handles_negation() {
|
||||||
|
let mut i = Interpreter::new();
|
||||||
|
i.register_hook(Rc::new(VecExtern)).unwrap();
|
||||||
|
i.set_var("u", Value::Extern(ExternHandle { kind: "vec".into(), id: 9 }));
|
||||||
|
let r = i.eval("-u").unwrap();
|
||||||
|
assert!(matches!(r, Value::Number(n) if n == -9.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extern_binop_falls_through_when_hook_declines() {
|
||||||
|
let mut i = Interpreter::new();
|
||||||
|
i.register_hook(Rc::new(VecExtern)).unwrap();
|
||||||
|
i.set_var("u", Value::Extern(ExternHandle { kind: "other".into(), id: 1 }));
|
||||||
|
i.set_var("v", Value::Extern(ExternHandle { kind: "other".into(), id: 2 }));
|
||||||
|
let r = i.eval("u + v");
|
||||||
|
assert!(r.is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn module_reload_picks_up_changed_file_without_restart() {
|
||||||
|
use std::fs;
|
||||||
|
let dir = std::env::temp_dir().join(format!("acord-reload-{}", std::process::id()));
|
||||||
|
let _ = fs::remove_dir_all(&dir);
|
||||||
|
fs::create_dir_all(&dir).unwrap();
|
||||||
|
let file = dir.join("conf.cord");
|
||||||
|
|
||||||
|
let mut i = Interpreter::new();
|
||||||
|
i.add_module_path("conf", &file);
|
||||||
|
|
||||||
|
fs::write(&file, "let setting = 1").unwrap();
|
||||||
|
i.exec("use conf").unwrap();
|
||||||
|
assert!(matches!(i.get_var("setting"), Some(Value::Number(n)) if n == 1.0));
|
||||||
|
|
||||||
|
fs::write(&file, "let setting = 2").unwrap();
|
||||||
|
i.exec("use conf").unwrap();
|
||||||
|
assert!(matches!(i.get_var("setting"), Some(Value::Number(n)) if n == 2.0));
|
||||||
|
|
||||||
|
fs::remove_dir_all(&dir).ok();
|
||||||
|
}
|
||||||
|
|
@ -111,7 +111,7 @@ fn finalize_number(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn tokenize(input: &str, spice: bool) -> Result<Vec<Token>, String> {
|
pub fn tokenize(input: &str, spice: bool) -> Result<Vec<Token>, String> {
|
||||||
let mut tokens = Vec::new();
|
let mut tokens = Vec::new();
|
||||||
let chars: Vec<char> = input.chars().collect();
|
let chars: Vec<char> = input.chars().collect();
|
||||||
let len = chars.len();
|
let len = chars.len();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue