diff --git a/src/compile/analyze.rs b/src/compile/analyze.rs index 6336ee3..eaf8642 100644 --- a/src/compile/analyze.rs +++ b/src/compile/analyze.rs @@ -14,6 +14,7 @@ use std::collections::HashSet; use crate::runtime::Value; +use crate::runtime::nanvalue::NanView; // ── Classification types ──────────────────────────────────────────────────── @@ -132,21 +133,21 @@ pub fn analyze_operative(proc_val: &Value) -> Option { /// Analyze a bare vau's args: `[formals, body]` or `[formals, env_formal, body]`. pub fn analyze_vau(args: &Value) -> OperativePattern { - let arr = match args { - Value::Array(arr) => arr, + let ha = match args.view() { + NanView::Array(ha) => ha, _ => return OperativePattern::FullOperative { reason: "malformed vau args" }, }; - let (formals, env_name, body) = match arr.len() { + let (formals, env_name, body) = match ha.data.len() { // {"vau": [formals, body]} — no env formal (source form) - 2 => (&arr[0], None, &arr[1]), + 2 => (&ha.data[0], None, &ha.data[1]), // {"vau": [formals, env_formal, body]} — has env formal (source form) 3 => { - let env = match &arr[1] { - Value::String(s) => Some(s.to_string()), + let env = match ha.data[1].view() { + NanView::String(s) => Some(s.to_string()), _ => None, }; - (&arr[0], env, &arr[2]) + (&ha.data[0], env, &ha.data[2]) } // {"vau": [formals, body, scope_obj]} — closure value form (2-arg + captured scope) // {"vau": [formals, env_formal, body, scope_obj]} — closure value form (3-arg + captured scope) @@ -172,32 +173,32 @@ pub fn analyze_mu_from_args(mu_args: &Value) -> OperativePattern { } fn analyze_mu(mu_args: &Value) -> OperativePattern { - let arr = match mu_args { - Value::Array(arr) if arr.len() == 3 => arr, + let ha = match mu_args.view() { + NanView::Array(ha) if ha.data.len() == 3 => ha, _ => return OperativePattern::FullOperative { reason: "malformed mu args" }, }; - let defs_obj = &arr[0]; - let identity = match &arr[1] { - Value::String(s) => s.to_string(), + let defs_obj = &ha.data[0]; + let identity = match ha.data[1].view() { + NanView::String(s) => s.to_string(), _ => return OperativePattern::FullOperative { reason: "mu identity not a string" }, }; // Look up the identity entry in defs - if let Value::Object(obj) = defs_obj { - for (k, v) in obj.iter() { + if let NanView::Object(ho) = defs_obj.view() { + for (k, v) in ho.iter() { if &**k == identity.as_str() { // Entry is [kind, formals, body] - if let Value::Array(entry) = v - && entry.len() >= 3 - && let Value::String(kind) = &entry[0] { + if let NanView::Array(entry) = v.view() + && entry.data.len() >= 3 + && let NanView::String(kind) = entry.data[0].view() { if &**kind == "fn" { // fn-kind mu is applicative return OperativePattern::PureTransform; } // macro/vau-kind: analyze the body - let formals = &entry[1]; - let body = &entry[2]; + let formals = &entry.data[1]; + let body = &entry.data[2]; let bound = crate::runtime::pattern_bound_names(formals); let caps = scan_body(body, None, &bound); return classify_from_caps(&caps, false, body, None); @@ -264,27 +265,27 @@ fn scan_value( bound: &HashSet, caps: &mut BodyCapabilities, ) { - match val { + match val.view() { // Primitives — no capabilities - Value::Null | Value::Bool(_) | Value::Int(_) | Value::Float(_) | Value::BigNum(_) | Value::String(_) => {} + NanView::Null | NanView::Bool(_) | NanView::Int(_) | NanView::Float(_) | NanView::BigNum(_) | NanView::String(_) => {} // Arrays — recurse into elements - Value::Array(arr) => { - for elem in arr.iter() { + NanView::Array(ha) => { + for elem in ha.data.iter() { scan_value(elem, env_name, bound, caps); } } // Objects (call forms or data) - Value::Object(obj) => { - if obj.len() == 1 { + NanView::Object(ho) => { + if ho.len() == 1 { // Singleton = call form - let (key, arg) = obj.iter().next().unwrap(); + let (key, arg) = ho.iter().next().unwrap(); let key_str: &str = key; scan_call(key_str, arg, env_name, bound, caps); } else { // Multi-key object — recurse into values - for (_, v) in obj.iter() { + for (_, v) in ho.iter() { scan_value(v, env_name, bound, caps); } } @@ -302,20 +303,22 @@ fn scan_call( match key { // Variable reference — check if it's the env formal "var" => { - if let Value::String(name) = arg { - let name_str: &str = name; + if let NanView::String(name) = arg.view() { + let name_str: &str = &**name; if let Some(env) = env_name && name_str == env { caps.uses_env = true; } - } else if let Value::Array(arr) = arg { + } else if let NanView::Array(ha) = arg.view() { // {"var": ["obj", "key"]} — path access - if let Some(Value::String(name)) = arr.first() { - let name_str: &str = name; - if let Some(env) = env_name - && name_str == env { - caps.uses_env = true; - } + if let Some(first) = ha.data.first() { + if let NanView::String(name) = first.view() { + let name_str: &str = &**name; + if let Some(env) = env_name + && name_str == env { + caps.uses_env = true; + } + } } } } @@ -392,13 +395,13 @@ fn match_expansion_template(body: &Value, env_name: &str) -> Option= 2 - && let Value::String(tag) = &a_arr[1] + && let NanView::Array(arr) = eval_args.view() + && arr.data.len() == 2 + && is_env_ref(&arr.data[1], env_name) + && let Some(("assoc", assoc_args)) = arr.data[0].as_call() + && let NanView::Array(a_arr) = assoc_args.view() + && a_arr.data.len() >= 2 + && let NanView::String(tag) = a_arr.data[1].view() && &**tag == "def" { return Some(ExpansionTemplate::EvalDef); } @@ -412,21 +415,21 @@ fn match_eval_vau_template(val: &Value, env_name: &str) -> Option arr, + let ha = match eval_args.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return None, }; // Second arg must be env reference - if !is_env_ref(&arr[1], env_name) { + if !is_env_ref(&ha.data[1], env_name) { return None; } // First arg must be {"assoc": [{}, "vau", _]} - if let Some(("assoc", assoc_args)) = arr[0].as_call() - && let Value::Array(a_arr) = assoc_args - && a_arr.len() >= 2 - && let Value::String(tag) = &a_arr[1] + if let Some(("assoc", assoc_args)) = ha.data[0].as_call() + && let NanView::Array(a_arr) = assoc_args.view() + && a_arr.data.len() >= 2 + && let NanView::String(tag) = a_arr.data[1].view() && &**tag == "vau" { return Some(ExpansionTemplate::EvalVau); } @@ -437,7 +440,7 @@ fn match_eval_vau_template(val: &Value, env_name: &str) -> Option bool { if let Some(("var", name_val)) = val.as_call() - && let Value::String(s) = name_val { + && let NanView::String(s) = name_val.view() { return &**s == env_name; } false @@ -543,7 +546,7 @@ mod tests { Value::array( vec![ Value::call("count", var("args"), &b).unwrap(), - Value::Int(1), + Value::int(1), ], &b, ).unwrap(), @@ -666,7 +669,7 @@ mod tests { #[test] fn full_operative_malformed() { let b = budget(); - let op = Value::call("vau", Value::Null, &b).unwrap(); + let op = Value::call("vau", Value::null(), &b).unwrap(); assert_eq!( analyze_operative(&op), Some(OperativePattern::FullOperative { @@ -720,7 +723,7 @@ mod tests { // {"vau": [[{"var": "x"}], {"fn": [[{"var": "y"}], {"eval": [{"var": "y"}, null]}]}]} // The eval is inside a nested fn, so it doesn't taint the outer vau let b = budget(); - let inner_fn_body = eval_form(var("y"), Value::Null); + let inner_fn_body = eval_form(var("y"), Value::null()); let inner_fn = Value::call( "fn", Value::array(vec![single_formal("y"), inner_fn_body], &b).unwrap(), @@ -736,7 +739,7 @@ mod tests { let b = budget(); let body = Value::call( "if", - Value::array(vec![Value::Bool(true), var("_env"), Value::Null], &b).unwrap(), + Value::array(vec![Value::bool(true), var("_env"), Value::null()], &b).unwrap(), &b, ).unwrap(); let op = bare_vau_env(single_formal("x"), "_env", body); diff --git a/src/compile/anf.rs b/src/compile/anf.rs index 6698370..c35cc76 100644 --- a/src/compile/anf.rs +++ b/src/compile/anf.rs @@ -793,7 +793,7 @@ mod tests { } fn int_val(n: i64) -> Value { - Value::Int(n) + Value::int(n) } fn call(proc: IrNode, args: Vec) -> IrNode { @@ -857,8 +857,8 @@ mod tests { #[test] fn trivial_lit() { - assert!(is_trivial(&lit(Value::Null))); - assert!(is_trivial(&lit(Value::Bool(true)))); + assert!(is_trivial(&lit(Value::null()))); + assert!(is_trivial(&lit(Value::bool(true)))); assert!(is_trivial(&lit(int_val(42)))); } @@ -874,7 +874,7 @@ mod tests { IrKind::Closure { kind: FnKind::Fn, params: vec![], - body: Box::new(lit(Value::Null)), + body: Box::new(lit(Value::null())), captures: vec![], }, JestType::Top, @@ -884,14 +884,14 @@ mod tests { #[test] fn trivial_array_all_trivial() { - assert!(is_trivial(&array_lit(vec![lit(Value::Null), var(0)]))); + assert!(is_trivial(&array_lit(vec![lit(Value::null()), var(0)]))); } #[test] fn not_trivial_array_with_call() { let arr = array_lit(vec![ - lit(Value::Null), - call(var(0), vec![lit(Value::Null)]), + lit(Value::null()), + call(var(0), vec![lit(Value::null())]), ]); assert!(!is_trivial(&arr)); } @@ -899,28 +899,28 @@ mod tests { #[test] fn trivial_object_all_trivial() { assert!(is_trivial(&object_lit(vec![ - ("a", lit(Value::Null)), + ("a", lit(Value::null())), ("b", var(0)), ]))); } #[test] fn not_trivial_object_with_call() { - let obj = object_lit(vec![("a", call(var(0), vec![lit(Value::Null)]))]); + let obj = object_lit(vec![("a", call(var(0), vec![lit(Value::null())]))]); assert!(!is_trivial(&obj)); } #[test] fn not_trivial_call() { - assert!(!is_trivial(&call(var(0), vec![lit(Value::Null)]))); + assert!(!is_trivial(&call(var(0), vec![lit(Value::null())]))); } #[test] fn not_trivial_branch() { assert!(!is_trivial(&branch( - lit(Value::Bool(true)), - lit(Value::Null), - lit(Value::Null), + lit(Value::bool(true)), + lit(Value::null()), + lit(Value::null()), ))); } @@ -996,7 +996,7 @@ mod tests { #[test] fn normalize_branch_with_trivial_cond() { let mut ctx = make_ctx(); - let node = branch(lit(Value::Bool(true)), lit(int_val(1)), lit(int_val(2))); + let node = branch(lit(Value::bool(true)), lit(int_val(1)), lit(int_val(2))); let result = anf_normalize(node, &mut ctx); assert!(matches!(result.kind, IrKind::Branch { .. })); assert_anf(&result); @@ -1116,7 +1116,7 @@ mod tests { fn normalize_try() { let mut ctx = make_ctx(); let expr = call(var(0), vec![lit(int_val(1))]); - let handler = lit(Value::Null); + let handler = lit(Value::null()); let node = IrNode::new( IrKind::Try { expr: Box::new(expr), @@ -1165,7 +1165,7 @@ mod tests { let node = IrNode::new( IrKind::InterpCall { operative: "vau".to_string(), - raw_args: Value::Null, + raw_args: Value::null(), needs_env: false, }, JestType::Top, @@ -1250,7 +1250,7 @@ mod tests { #[test] #[should_panic(expected = "ANF violation: Branch condition is not trivial")] fn assert_anf_catches_non_trivial_branch_cond() { - let node = branch(call(var(0), vec![]), lit(Value::Null), lit(Value::Null)); + let node = branch(call(var(0), vec![]), lit(Value::null()), lit(Value::null())); assert_anf(&node); } @@ -1285,7 +1285,7 @@ mod tests { #[test] #[should_panic(expected = "ANF violation: Call proc is not trivial")] fn assert_anf_catches_non_trivial_call_proc() { - let node = call(call(var(0), vec![]), vec![lit(Value::Null)]); + let node = call(call(var(0), vec![]), vec![lit(Value::null())]); assert_anf(&node); } diff --git a/src/compile/bytecode.rs b/src/compile/bytecode.rs index cac7950..0a0d86c 100644 --- a/src/compile/bytecode.rs +++ b/src/compile/bytecode.rs @@ -4,6 +4,7 @@ //! and supporting tables for closures and interpreter fallback. use crate::runtime::{Value, Budget, String as RStr}; +use crate::runtime::nanvalue::NanView; use super::ir::{DecisionTree, IrDoStmt, IrFnDef, IrNode, TypeTag}; use super::FnKind; @@ -476,7 +477,7 @@ impl Chunk { .map(|(name, slot)| { Value::array(vec![ Value::string(name, budget)?, - Value::Int(*slot as i64), + Value::int(*slot as i64), ], budget) }) .collect::>>()?; @@ -487,7 +488,7 @@ impl Chunk { .map(|(name, slot)| { Value::array(vec![ Value::string(name, budget)?, - Value::Int(*slot as i64), + Value::int(*slot as i64), ], budget) }) .collect::>>()?; @@ -498,7 +499,7 @@ impl Chunk { .map(|(name, slot)| { Value::array(vec![ Value::string(name, budget)?, - Value::Int(*slot as i64), + Value::int(*slot as i64), ], budget) }) .collect::>>()?; @@ -508,8 +509,8 @@ impl Chunk { let capture_global_slots_vals: Vec = self.capture_global_slots.iter() .map(|(local_slot, global_slot)| { Value::array(vec![ - Value::Int(*local_slot as i64), - Value::Int(*global_slot as i64), + Value::int(*local_slot as i64), + Value::int(*global_slot as i64), ], budget) }) .collect::>>()?; @@ -526,7 +527,7 @@ impl Chunk { (RStr::new("interp_calls", budget)?, interp_calls_array), (RStr::new("letrec_templates", budget)?, letrec_templates_array), (RStr::new("match_templates", budget)?, match_templates_array), - (RStr::new("num_locals", budget)?, Value::Int(self.num_locals as i64)), + (RStr::new("num_locals", budget)?, Value::int(self.num_locals as i64)), (RStr::new("object_keys", budget)?, object_keys_array), (RStr::new("param_slots", budget)?, param_slots_array), (RStr::new("rec_slots", budget)?, rec_slots_array), @@ -539,78 +540,78 @@ impl Chunk { fn op_to_value(op: &Op, budget: &Budget) -> crate::Result { let (tag, payload) = match op { Op::LoadConst(dst, idx) => ("LoadConst", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*idx as i64), + Value::int(*dst as i64), Value::int(*idx as i64), ], budget)?), - Op::LoadNull(dst) => ("LoadNull", Value::Int(*dst as i64)), - Op::LoadTrue(dst) => ("LoadTrue", Value::Int(*dst as i64)), - Op::LoadFalse(dst) => ("LoadFalse", Value::Int(*dst as i64)), + Op::LoadNull(dst) => ("LoadNull", Value::int(*dst as i64)), + Op::LoadTrue(dst) => ("LoadTrue", Value::int(*dst as i64)), + Op::LoadFalse(dst) => ("LoadFalse", Value::int(*dst as i64)), Op::Move(dst, src) => ("Move", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*src as i64), + Value::int(*dst as i64), Value::int(*src as i64), ], budget)?), Op::LoadGlobal(dst, slot) => ("LoadGlobal", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*slot as i64), + Value::int(*dst as i64), Value::int(*slot as i64), ], budget)?), - Op::Jump(offset) => ("Jump", Value::Int(*offset as i64)), + Op::Jump(offset) => ("Jump", Value::int(*offset as i64)), Op::JumpIfFalse(cond, target) => ("JumpIfFalse", Value::array(vec![ - Value::Int(*cond as i64), Value::Int(*target as i64), + Value::int(*cond as i64), Value::int(*target as i64), ], budget)?), - Op::Return(src) => ("Return", Value::Int(*src as i64)), + Op::Return(src) => ("Return", Value::int(*src as i64)), Op::Call(dst, proc_reg, arg_base, arity) => ("Call", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*proc_reg as i64), - Value::Int(*arg_base as i64), Value::Int(*arity as i64), + Value::int(*dst as i64), Value::int(*proc_reg as i64), + Value::int(*arg_base as i64), Value::int(*arity as i64), ], budget)?), Op::TailCall(dst, proc_reg, arg_base, arity) => ("TailCall", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*proc_reg as i64), - Value::Int(*arg_base as i64), Value::Int(*arity as i64), + Value::int(*dst as i64), Value::int(*proc_reg as i64), + Value::int(*arg_base as i64), Value::int(*arity as i64), ], budget)?), Op::BuildArray(dst, base, count) => ("BuildArray", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*base as i64), Value::Int(*count as i64), + Value::int(*dst as i64), Value::int(*base as i64), Value::int(*count as i64), ], budget)?), Op::BuildObject(dst, base, count, keys_idx) => ("BuildObject", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*base as i64), - Value::Int(*count as i64), Value::Int(*keys_idx as i64), + Value::int(*dst as i64), Value::int(*base as i64), + Value::int(*count as i64), Value::int(*keys_idx as i64), ], budget)?), Op::TypeConstruct(dst, payload, name_idx) => ("TypeConstruct", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*payload as i64), Value::Int(*name_idx as i64), + Value::int(*dst as i64), Value::int(*payload as i64), Value::int(*name_idx as i64), ], budget)?), Op::MakeClosure(dst, base, idx) => ("MakeClosure", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*base as i64), Value::Int(*idx as i64), + Value::int(*dst as i64), Value::int(*base as i64), Value::int(*idx as i64), ], budget)?), Op::Count(dst, src) => ("Count", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*src as i64), + Value::int(*dst as i64), Value::int(*src as i64), ], budget)?), Op::First(dst, src) => ("First", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*src as i64), + Value::int(*dst as i64), Value::int(*src as i64), ], budget)?), Op::Rest(dst, src) => ("Rest", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*src as i64), + Value::int(*dst as i64), Value::int(*src as i64), ], budget)?), Op::Cons(dst, elem, arr) => ("Cons", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*elem as i64), Value::Int(*arr as i64), + Value::int(*dst as i64), Value::int(*elem as i64), Value::int(*arr as i64), ], budget)?), Op::Nth(dst, arr, idx) => ("Nth", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*arr as i64), Value::Int(*idx as i64), + Value::int(*dst as i64), Value::int(*arr as i64), Value::int(*idx as i64), ], budget)?), Op::NthUnchecked(dst, arr, idx) => ("NthUnchecked", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*arr as i64), Value::Int(*idx as i64), + Value::int(*dst as i64), Value::int(*arr as i64), Value::int(*idx as i64), ], budget)?), Op::InterpCall(dst, idx) => ("InterpCall", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*idx as i64), + Value::int(*dst as i64), Value::int(*idx as i64), ], budget)?), Op::EvalMatch(dst, idx) => ("EvalMatch", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*idx as i64), + Value::int(*dst as i64), Value::int(*idx as i64), ], budget)?), Op::EvalTry(dst, idx) => ("EvalTry", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*idx as i64), + Value::int(*dst as i64), Value::int(*idx as i64), ], budget)?), Op::EvalDispatch(dst, idx) => ("EvalDispatch", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*idx as i64), + Value::int(*dst as i64), Value::int(*idx as i64), ], budget)?), Op::EvalSeq(dst, idx) => ("EvalSeq", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*idx as i64), + Value::int(*dst as i64), Value::int(*idx as i64), ], budget)?), Op::EvalLetRec(dst, idx) => ("EvalLetRec", Value::array(vec![ - Value::Int(*dst as i64), Value::Int(*idx as i64), + Value::int(*dst as i64), Value::int(*idx as i64), ], budget)?), }; Value::object(vec![(RStr::new(tag, budget)?, payload)], budget) @@ -640,14 +641,14 @@ fn closure_template_to_value(ct: &ClosureTemplate, budget: &Budget) -> crate::Re (RStr::new("kind", budget)?, Value::string(kind_str, budget)?), (RStr::new("param_names", budget)?, param_names), (RStr::new("body_source", budget)?, ct.body_source.clone()), - (RStr::new("num_captures", budget)?, Value::Int(ct.num_captures as i64)), + (RStr::new("num_captures", budget)?, Value::int(ct.num_captures as i64)), (RStr::new("capture_names", budget)?, capture_names), ], budget) } fn match_template_to_value(mt: &MatchTemplate, budget: &Budget) -> crate::Result { Value::object(vec![ - (RStr::new("scrutinee_id", budget)?, Value::Int(mt.scrutinee_id as i64)), + (RStr::new("scrutinee_id", budget)?, Value::int(mt.scrutinee_id as i64)), (RStr::new("tree", budget)?, decision_tree_to_value(&mt.tree, budget)?), ], budget) } @@ -655,7 +656,7 @@ fn match_template_to_value(mt: &MatchTemplate, budget: &Budget) -> crate::Result fn try_template_to_value(tt: &TryTemplate, budget: &Budget) -> crate::Result { let budget_val = match &tt.budget_expr { Some(b) => ir_node_to_value(b, budget)?, - None => Value::Null, + None => Value::null(), }; Value::object(vec![ @@ -667,16 +668,16 @@ fn try_template_to_value(tt: &TryTemplate, budget: &Budget) -> crate::Result crate::Result { let arg_ids_vals: Vec = dt.arg_ids.iter() - .map(|id| Ok(Value::Int(*id as i64))) + .map(|id| Ok(Value::int(*id as i64))) .collect::>>()?; let resolved_val = match &dt.resolved { Some(r) => ir_node_to_value(r, budget)?, - None => Value::Null, + None => Value::null(), }; let table_val = if dt.dispatch_table.is_empty() { - Value::Null + Value::null() } else { let entry_vals: Vec = dt.dispatch_table.iter() .map(|e| dispatch_entry_to_value(e, budget)) @@ -702,7 +703,7 @@ fn dispatch_entry_to_value(entry: &DispatchEntry, budget: &Budget) -> crate::Res (RStr::new("builtin", budget)?, Value::string(name, budget)?), ], budget)?, MethodRef::Constant(idx) => Value::object(vec![ - (RStr::new("constant", budget)?, Value::Int(*idx as i64)), + (RStr::new("constant", budget)?, Value::int(*idx as i64)), ], budget)?, }; @@ -748,11 +749,11 @@ fn ir_node_to_value(node: &IrNode, budget: &Budget) -> crate::Result { Value::object(vec![(RStr::new("Lit", budget)?, v.clone())], budget)? } IrKind::Var(id) => { - Value::object(vec![(RStr::new("Var", budget)?, Value::Int(id.0 as i64))], budget)? + Value::object(vec![(RStr::new("Var", budget)?, Value::int(id.0 as i64))], budget)? } IrKind::Let { name, value, body } => { let fields = Value::array(vec![ - Value::Int(name.0 as i64), + Value::int(name.0 as i64), ir_node_to_value(value, budget)?, ir_node_to_value(body, budget)?, ], budget)?; @@ -816,7 +817,7 @@ fn ir_node_to_value(node: &IrNode, budget: &Budget) -> crate::Result { .collect::>>()?; let resolved_val = match resolved { Some(r) => ir_node_to_value(r, budget)?, - None => Value::Null, + None => Value::null(), }; let fields = Value::array(vec![ Value::string(generic_name, budget)?, @@ -829,7 +830,7 @@ fn ir_node_to_value(node: &IrNode, budget: &Budget) -> crate::Result { let fields = Value::array(vec![ Value::string(operative, budget)?, raw_args.clone(), - if *needs_env { Value::Int(1) } else { Value::Int(0) }, + if *needs_env { Value::int(1) } else { Value::int(0) }, ], budget)?; Value::object(vec![(RStr::new("InterpCall", budget)?, fields)], budget)? } @@ -843,7 +844,7 @@ fn ir_node_to_value(node: &IrNode, budget: &Budget) -> crate::Result { IrKind::Try { expr, handler, budget: try_budget } => { let budget_val = match try_budget { Some(b) => ir_node_to_value(b, budget)?, - None => Value::Null, + None => Value::null(), }; let fields = Value::array(vec![ ir_node_to_value(expr, budget)?, @@ -868,7 +869,7 @@ fn decision_tree_to_value(tree: &DecisionTree, budget: &Budget) -> crate::Result let bindings_vals: Vec = bindings.iter() .map(|(id, name, _ty)| { Value::array(vec![ - Value::Int(id.0 as i64), + Value::int(id.0 as i64), Value::string(name, budget)?, ], budget) }) @@ -890,11 +891,11 @@ fn decision_tree_to_value(tree: &DecisionTree, budget: &Budget) -> crate::Result .collect::>>()?; let default_val = match default { Some(d) => decision_tree_to_value(d, budget)?, - None => Value::Null, + None => Value::null(), }; Value::object(vec![ (RStr::new("tag", budget)?, Value::string("TypeSwitch", budget)?), - (RStr::new("scrutinee", budget)?, Value::Int(scrutinee.0 as i64)), + (RStr::new("scrutinee", budget)?, Value::int(scrutinee.0 as i64)), (RStr::new("cases", budget)?, Value::array(cases_vals, budget)?), (RStr::new("default", budget)?, default_val), ], budget) @@ -910,11 +911,11 @@ fn decision_tree_to_value(tree: &DecisionTree, budget: &Budget) -> crate::Result .collect::>>()?; let default_val = match default { Some(d) => decision_tree_to_value(d, budget)?, - None => Value::Null, + None => Value::null(), }; Value::object(vec![ (RStr::new("tag", budget)?, Value::string("LitSwitch", budget)?), - (RStr::new("scrutinee", budget)?, Value::Int(scrutinee.0 as i64)), + (RStr::new("scrutinee", budget)?, Value::int(scrutinee.0 as i64)), (RStr::new("cases", budget)?, Value::array(cases_vals, budget)?), (RStr::new("default", budget)?, default_val), ], budget) @@ -932,9 +933,9 @@ fn decision_tree_to_value(tree: &DecisionTree, budget: &Budget) -> crate::Result fn lit_value_to_value(lit: &super::ir::LitValue, budget: &Budget) -> crate::Result { use super::ir::LitValue; match lit { - LitValue::Null => Ok(Value::Null), - LitValue::Bool(b) => Ok(Value::Bool(*b)), - LitValue::Integer(i) => Ok(Value::Int(i.to_i64().unwrap_or(0))), + LitValue::Null => Ok(Value::null()), + LitValue::Bool(b) => Ok(Value::bool(*b)), + LitValue::Integer(i) => Ok(Value::int(i.to_i64().unwrap_or(0))), LitValue::Float(s) => { // Store as string for exact round-trip Value::string(s, budget) @@ -966,7 +967,7 @@ fn ir_do_stmt_to_value(stmt: &super::ir::IrDoStmt, budget: &Budget) -> crate::Re } IrDoStmt::Bind { name, original_name, value } => { let fields = Value::array(vec![ - Value::Int(name.0 as i64), + Value::int(name.0 as i64), Value::string(original_name, budget)?, ir_node_to_value(value, budget)?, ], budget)?; @@ -988,7 +989,7 @@ fn ir_do_stmt_to_value(stmt: &super::ir::IrDoStmt, budget: &Budget) -> crate::Re IrDoStmt::DefGeneric { name, arity } => { let fields = Value::array(vec![ Value::string(name, budget)?, - Value::Int(*arity as i64), + Value::int(*arity as i64), ], budget)?; Value::object(vec![(RStr::new("DefGeneric", budget)?, fields)], budget) } @@ -1019,11 +1020,11 @@ fn ir_fn_def_to_value(def: &super::ir::IrFnDef, budget: &Budget) -> crate::Resul .collect::>>()?; let captures_vals: Vec = def.captures.iter() - .map(|id| Ok(Value::Int(id.0 as i64))) + .map(|id| Ok(Value::int(id.0 as i64))) .collect::>>()?; Value::object(vec![ - (RStr::new("name", budget)?, Value::Int(def.name.0 as i64)), + (RStr::new("name", budget)?, Value::int(def.name.0 as i64)), (RStr::new("original_name", budget)?, Value::string(&def.original_name, budget)?), (RStr::new("kind", budget)?, Value::string(kind_str, budget)?), (RStr::new("params", budget)?, Value::array(params_vals, budget)?), @@ -1034,7 +1035,7 @@ fn ir_fn_def_to_value(def: &super::ir::IrFnDef, budget: &Budget) -> crate::Resul fn ir_param_to_value(param: &super::ir::IrParam, budget: &Budget) -> crate::Result { Value::object(vec![ - (RStr::new("id", budget)?, Value::Int(param.id.0 as i64)), + (RStr::new("id", budget)?, Value::int(param.id.0 as i64)), (RStr::new("name", budget)?, Value::string(¶m.name, budget)?), ], budget) } @@ -1044,8 +1045,8 @@ fn ir_param_to_value(param: &super::ir::IrParam, budget: &Budget) -> crate::Resu impl Chunk { /// Deserialize a chunk from a JSON Value. pub fn from_value(val: &Value) -> crate::Result { - let obj = match val { - Value::Object(o) => o, + let obj = match val.view() { + NanView::Object(o) => o, _ => return Err(crate::Error::Throw("chunk must be an object".into())), }; @@ -1055,8 +1056,8 @@ impl Chunk { let constants_val = obj.get("constants") .ok_or_else(|| crate::Error::Throw("missing 'constants' field".into()))?; - let constants = match constants_val { - Value::Array(a) => (&**a).to_vec(), + let constants = match constants_val.view() { + NanView::Array(ha) => ha.data.to_vec(), _ => return Err(crate::Error::Throw("'constants' must be an array".into())), }; @@ -1070,8 +1071,8 @@ impl Chunk { let interp_calls_val = obj.get("interp_calls") .ok_or_else(|| crate::Error::Throw("missing 'interp_calls' field".into()))?; - let interp_calls = match interp_calls_val { - Value::Array(a) => (&**a).iter() + let interp_calls = match interp_calls_val.view() { + NanView::Array(ha) => ha.data.iter() .map(|v| Ok(InterpCallInfo { source_form: v.clone() })) .collect::>>()?, _ => return Err(crate::Error::Throw("'interp_calls' must be an array".into())), @@ -1151,15 +1152,15 @@ impl Chunk { } fn value_to_code(val: &Value) -> crate::Result> { - let arr = match val { - Value::Array(a) => &**a, + let arr = match val.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw("code must be an array".into())), }; - arr.iter() + arr.data.iter() .map(|v| { - let obj = match v { - Value::Object(o) => o, + let obj = match v.view() { + NanView::Object(ho) => ho, _ => return Err(crate::Error::Throw("op must be an object".into())), }; @@ -1204,32 +1205,32 @@ fn value_to_code(val: &Value) -> crate::Result> { } fn arr2(val: &Value) -> crate::Result<&[Value]> { - match val { - Value::Array(a) if a.len() == 2 => Ok(&**a), + match val.view() { + NanView::Array(ha) if ha.data.len() == 2 => Ok(&ha.data), _ => Err(crate::Error::Throw("expected 2-element array".into())), } } fn arr3(val: &Value) -> crate::Result<&[Value]> { - match val { - Value::Array(a) if a.len() == 3 => Ok(&**a), + match val.view() { + NanView::Array(ha) if ha.data.len() == 3 => Ok(&ha.data), _ => Err(crate::Error::Throw("expected 3-element array".into())), } } fn arr4(val: &Value) -> crate::Result<&[Value]> { - match val { - Value::Array(a) if a.len() == 4 => Ok(&**a), + match val.view() { + NanView::Array(ha) if ha.data.len() == 4 => Ok(&ha.data), _ => Err(crate::Error::Throw("expected 4-element array".into())), } } fn value_to_u8(val: &Value) -> crate::Result { - match val { - Value::Int(i) => { - u8::try_from(*i).map_err(|_| crate::Error::Throw("integer out of u8 range".into())) + match val.view() { + NanView::Int(i) => { + u8::try_from(i).map_err(|_| crate::Error::Throw("integer out of u8 range".into())) }, - Value::BigNum(n) => match &**n { + NanView::BigNum(n) => match n { crate::runtime::Number::Integer(i) => { i.to_u8().ok_or_else(|| crate::Error::Throw("integer out of u8 range".into())) }, @@ -1240,11 +1241,11 @@ fn value_to_u8(val: &Value) -> crate::Result { } fn value_to_u16(val: &Value) -> crate::Result { - match val { - Value::Int(i) => { - u16::try_from(*i).map_err(|_| crate::Error::Throw("integer out of u16 range".into())) + match val.view() { + NanView::Int(i) => { + u16::try_from(i).map_err(|_| crate::Error::Throw("integer out of u16 range".into())) }, - Value::BigNum(n) => match &**n { + NanView::BigNum(n) => match n { crate::runtime::Number::Integer(i) => { i.to_u16().ok_or_else(|| crate::Error::Throw("integer out of u16 range".into())) }, @@ -1255,11 +1256,11 @@ fn value_to_u16(val: &Value) -> crate::Result { } fn value_to_u32(val: &Value) -> crate::Result { - match val { - Value::Int(i) => { - u32::try_from(*i).map_err(|_| crate::Error::Throw("integer out of u32 range".into())) + match val.view() { + NanView::Int(i) => { + u32::try_from(i).map_err(|_| crate::Error::Throw("integer out of u32 range".into())) }, - Value::BigNum(n) => match &**n { + NanView::BigNum(n) => match n { crate::runtime::Number::Integer(i) => { i.to_u32().ok_or_else(|| crate::Error::Throw("integer out of u32 range".into())) }, @@ -1270,57 +1271,57 @@ fn value_to_u32(val: &Value) -> crate::Result { } fn value_to_param_slots(val: &Value) -> crate::Result> { - let arr = match val { - Value::Array(a) => &**a, + let arr = match val.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw("param_slots must be an array".into())), }; - arr.iter().map(|entry| { - let pair = match entry { - Value::Array(a) if a.len() == 2 => &**a, + arr.data.iter().map(|entry| { + let pair = match entry.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return Err(crate::Error::Throw("param_slot entry must be [name, slot]".into())), }; - let name = match &pair[0] { - Value::String(s) => s.to_string(), + let name = match pair.data[0].view() { + NanView::String(s) => s.to_string(), _ => return Err(crate::Error::Throw("param_slot name must be a string".into())), }; - let slot = value_to_u32(&pair[1])?; + let slot = value_to_u32(&pair.data[1])?; Ok((name, slot)) }).collect() } fn value_to_u32_pairs(val: &Value) -> crate::Result> { - let arr = match val { - Value::Array(a) => &**a, + let arr = match val.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw("u32_pairs must be an array".into())), }; - arr.iter().map(|entry| { - let pair = match entry { - Value::Array(a) if a.len() == 2 => &**a, + arr.data.iter().map(|entry| { + let pair = match entry.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return Err(crate::Error::Throw("u32_pair entry must be [a, b]".into())), }; - let a = value_to_u32(&pair[0])?; - let b = value_to_u32(&pair[1])?; + let a = value_to_u32(&pair.data[0])?; + let b = value_to_u32(&pair.data[1])?; Ok((a, b)) }).collect() } fn value_to_object_keys(val: &Value) -> crate::Result>> { - let arr = match val { - Value::Array(a) => &**a, + let arr = match val.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw("object_keys must be an array".into())), }; - arr.iter() + arr.data.iter() .map(|keys_val| { - let keys_arr = match keys_val { - Value::Array(a) => &**a, + let keys_arr = match keys_val.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw("each object key set must be an array".into())), }; - keys_arr.iter() + keys_arr.data.iter() .map(|k| { - match k { - Value::String(s) => Ok((**s).to_string()), + match k.view() { + NanView::String(s) => Ok((&**s).to_string()), _ => Err(crate::Error::Throw("object key must be a string".into())), } }) @@ -1330,20 +1331,20 @@ fn value_to_object_keys(val: &Value) -> crate::Result crate::Result> { - let arr = match val { - Value::Array(a) => &**a, + let arr = match val.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw("closure_templates must be an array".into())), }; - arr.iter() + arr.data.iter() .map(|ct_val| { - let obj = match ct_val { - Value::Object(o) => o, + let obj = match ct_val.view() { + NanView::Object(ho) => ho, _ => return Err(crate::Error::Throw("closure template must be an object".into())), }; - let kind_str = match obj.get("kind") { - Some(Value::String(s)) => &**s, + let kind_str = match obj.get("kind").map(|v| v.view()) { + Some(NanView::String(s)) => s, _ => return Err(crate::Error::Throw("missing 'kind' field".into())), }; let kind = match &**kind_str { @@ -1354,14 +1355,14 @@ fn value_to_closure_templates(val: &Value) -> crate::Result let param_names_val = obj.get("param_names") .ok_or_else(|| crate::Error::Throw("missing 'param_names' field".into()))?; - let param_names = match param_names_val { - Value::Array(a) => &**a, + let param_names_arr = match param_names_val.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw("param_names must be an array".into())), - } - .iter() + }; + let param_names = param_names_arr.data.iter() .map(|v| { - match v { - Value::String(s) => Ok((**s).to_string()), + match v.view() { + NanView::String(s) => Ok((&**s).to_string()), _ => Err(crate::Error::Throw("param name must be a string".into())), } }) @@ -1377,14 +1378,14 @@ fn value_to_closure_templates(val: &Value) -> crate::Result let capture_names_val = obj.get("capture_names") .ok_or_else(|| crate::Error::Throw("missing 'capture_names' field".into()))?; - let capture_names = match capture_names_val { - Value::Array(a) => &**a, + let capture_names_arr = match capture_names_val.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw("capture_names must be an array".into())), - } - .iter() + }; + let capture_names = capture_names_arr.data.iter() .map(|v| { - match v { - Value::String(s) => Ok((**s).to_string()), + match v.view() { + NanView::String(s) => Ok((&**s).to_string()), _ => Err(crate::Error::Throw("capture name must be a string".into())), } }) @@ -1403,15 +1404,15 @@ fn value_to_closure_templates(val: &Value) -> crate::Result } fn value_to_match_templates(val: &Value) -> crate::Result> { - let arr = match val { - Value::Array(a) => &**a, + let arr = match val.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw("match_templates must be an array".into())), }; - arr.iter() + arr.data.iter() .map(|mt_val| { - let obj = match mt_val { - Value::Object(o) => o, + let obj = match mt_val.view() { + NanView::Object(ho) => ho, _ => return Err(crate::Error::Throw("match template must be an object".into())), }; @@ -1429,15 +1430,15 @@ fn value_to_match_templates(val: &Value) -> crate::Result> { } fn value_to_try_templates(val: &Value) -> crate::Result> { - let arr = match val { - Value::Array(a) => &**a, + let arr = match val.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw("try_templates must be an array".into())), }; - arr.iter() + arr.data.iter() .map(|tt_val| { - let obj = match tt_val { - Value::Object(o) => o, + let obj = match tt_val.view() { + NanView::Object(ho) => ho, _ => return Err(crate::Error::Throw("try template must be an object".into())), }; @@ -1451,7 +1452,7 @@ fn value_to_try_templates(val: &Value) -> crate::Result> { let budget_expr_val = obj.get("budget_expr") .ok_or_else(|| crate::Error::Throw("missing 'budget_expr' field".into()))?; - let budget_expr = if matches!(budget_expr_val, Value::Null) { + let budget_expr = if matches!(budget_expr_val.view(), NanView::Null) { None } else { Some(value_to_ir_node(budget_expr_val)?) @@ -1463,29 +1464,29 @@ fn value_to_try_templates(val: &Value) -> crate::Result> { } fn value_to_dispatch_templates(val: &Value) -> crate::Result> { - let arr = match val { - Value::Array(a) => &**a, + let arr = match val.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw("dispatch_templates must be an array".into())), }; - arr.iter() + arr.data.iter() .map(|dt_val| { - let obj = match dt_val { - Value::Object(o) => o, + let obj = match dt_val.view() { + NanView::Object(ho) => ho, _ => return Err(crate::Error::Throw("dispatch template must be an object".into())), }; let generic_name_val = obj.get("generic_name") .ok_or_else(|| crate::Error::Throw("missing 'generic_name' field".into()))?; - let generic_name = match generic_name_val { - Value::String(s) => (**s).to_string(), + let generic_name = match generic_name_val.view() { + NanView::String(s) => (&**s).to_string(), _ => return Err(crate::Error::Throw("generic_name must be a string".into())), }; let arg_ids_val = obj.get("arg_ids") .ok_or_else(|| crate::Error::Throw("missing 'arg_ids' field".into()))?; - let arg_ids = match arg_ids_val { - Value::Array(a) => (&**a).iter() + let arg_ids = match arg_ids_val.view() { + NanView::Array(ha) => ha.data.iter() .map(value_to_u32) .collect::>>()?, _ => return Err(crate::Error::Throw("arg_ids must be an array".into())), @@ -1493,15 +1494,15 @@ fn value_to_dispatch_templates(val: &Value) -> crate::Result { - entries.iter() + let dispatch_table = match obj.get("dispatch_table").map(|v| v.view()) { + Some(NanView::Array(entries)) => { + entries.data.iter() .map(value_to_dispatch_entry) .collect::>>()? } @@ -1514,15 +1515,15 @@ fn value_to_dispatch_templates(val: &Value) -> crate::Result crate::Result { - let obj = match val { - Value::Object(o) => o, + let obj = match val.view() { + NanView::Object(ho) => ho, _ => return Err(crate::Error::Throw("dispatch entry must be an object".into())), }; let tags_val = obj.get("tags") .ok_or_else(|| crate::Error::Throw("missing 'tags' field".into()))?; - let arg_tags = match tags_val { - Value::Array(a) => a.iter() + let arg_tags = match tags_val.view() { + NanView::Array(ha) => ha.data.iter() .map(value_to_type_tag) .collect::>>()?, _ => return Err(crate::Error::Throw("tags must be an array".into())), @@ -1530,16 +1531,16 @@ fn value_to_dispatch_entry(val: &Value) -> crate::Result { let method_val = obj.get("method") .ok_or_else(|| crate::Error::Throw("missing 'method' field".into()))?; - let method = match method_val { - Value::Object(m) => { + let method = match method_val.view() { + NanView::Object(m) => { if let Some(name) = m.get("builtin") { - match name { - Value::String(s) => MethodRef::Builtin((**s).to_string()), + match name.view() { + NanView::String(s) => MethodRef::Builtin((&**s).to_string()), _ => return Err(crate::Error::Throw("builtin name must be a string".into())), } } else if let Some(idx) = m.get("constant") { - match idx { - Value::Int(i) => MethodRef::Constant(*i as u16), + match idx.view() { + NanView::Int(i) => MethodRef::Constant(i as u16), _ => return Err(crate::Error::Throw("constant index must be an int".into())), } } else { @@ -1553,22 +1554,22 @@ fn value_to_dispatch_entry(val: &Value) -> crate::Result { } fn value_to_seq_templates(val: &Value) -> crate::Result> { - let arr = match val { - Value::Array(a) => &**a, + let arr = match val.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw("seq_templates must be an array".into())), }; - arr.iter() + arr.data.iter() .map(|st_val| { - let obj = match st_val { - Value::Object(o) => o, + let obj = match st_val.view() { + NanView::Object(ho) => ho, _ => return Err(crate::Error::Throw("seq template must be an object".into())), }; let stmts_val = obj.get("stmts") .ok_or_else(|| crate::Error::Throw("missing 'stmts' field".into()))?; - let stmts = match stmts_val { - Value::Array(a) => (&**a).iter() + let stmts = match stmts_val.view() { + NanView::Array(ha) => ha.data.iter() .map(value_to_ir_do_stmt) .collect::>>()?, _ => return Err(crate::Error::Throw("stmts must be an array".into())), @@ -1580,22 +1581,22 @@ fn value_to_seq_templates(val: &Value) -> crate::Result> { } fn value_to_letrec_templates(val: &Value) -> crate::Result> { - let arr = match val { - Value::Array(a) => &**a, + let arr = match val.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw("letrec_templates must be an array".into())), }; - arr.iter() + arr.data.iter() .map(|lt_val| { - let obj = match lt_val { - Value::Object(o) => o, + let obj = match lt_val.view() { + NanView::Object(ho) => ho, _ => return Err(crate::Error::Throw("letrec template must be an object".into())), }; let defs_val = obj.get("defs") .ok_or_else(|| crate::Error::Throw("missing 'defs' field".into()))?; - let defs = match defs_val { - Value::Array(a) => (&**a).iter() + let defs = match defs_val.view() { + NanView::Array(ha) => ha.data.iter() .map(value_to_ir_fn_def) .collect::>>()?, _ => return Err(crate::Error::Throw("defs must be an array".into())), @@ -1607,8 +1608,8 @@ fn value_to_letrec_templates(val: &Value) -> crate::Result> let body_chunks_val = obj.get("body_chunks") .ok_or_else(|| crate::Error::Throw("missing 'body_chunks' field".into()))?; - let body_chunks = match body_chunks_val { - Value::Array(a) => (&**a).iter() + let body_chunks = match body_chunks_val.view() { + NanView::Array(ha) => ha.data.iter() .map(Chunk::from_value) .collect::>>()?, _ => return Err(crate::Error::Throw("body_chunks must be an array".into())), @@ -1627,8 +1628,8 @@ fn value_to_ir_node(val: &Value) -> crate::Result { // Simplified deserialization: reconstruct kind from serialized format // Type is set to Top since we don't serialize it - let obj = match val { - Value::Object(o) => o, + let obj = match val.view() { + NanView::Object(ho) => ho, _ => return Err(crate::Error::Throw("IR node must be an object".into())), }; @@ -1645,159 +1646,159 @@ fn value_to_ir_node(val: &Value) -> crate::Result { IrKind::Var(IrId(id)) } "Let" => { - let arr = match payload { - Value::Array(a) if a.len() == 3 => &**a, + let arr = match payload.view() { + NanView::Array(ha) if ha.data.len() == 3 => ha, _ => return Err(crate::Error::Throw("Let needs 3 elements".into())), }; - let name = IrId(value_to_u32(&arr[0])?); - let value = Box::new(value_to_ir_node(&arr[1])?); - let body = Box::new(value_to_ir_node(&arr[2])?); + let name = IrId(value_to_u32(&arr.data[0])?); + let value = Box::new(value_to_ir_node(&arr.data[1])?); + let body = Box::new(value_to_ir_node(&arr.data[2])?); IrKind::Let { name, value, body } } "Branch" => { - let arr = match payload { - Value::Array(a) if a.len() == 3 => &**a, + let arr = match payload.view() { + NanView::Array(ha) if ha.data.len() == 3 => ha, _ => return Err(crate::Error::Throw("Branch needs 3 elements".into())), }; - let cond = Box::new(value_to_ir_node(&arr[0])?); - let then_branch = Box::new(value_to_ir_node(&arr[1])?); - let else_branch = Box::new(value_to_ir_node(&arr[2])?); + let cond = Box::new(value_to_ir_node(&arr.data[0])?); + let then_branch = Box::new(value_to_ir_node(&arr.data[1])?); + let else_branch = Box::new(value_to_ir_node(&arr.data[2])?); IrKind::Branch { cond, then_branch, else_branch } } "Call" => { - let arr = match payload { - Value::Array(a) if !a.is_empty() => &**a, + let arr = match payload.view() { + NanView::Array(ha) if !ha.data.is_empty() => ha, _ => return Err(crate::Error::Throw("Call needs at least 1 element".into())), }; - let proc = Box::new(value_to_ir_node(&arr[0])?); - let args = arr[1..].iter() + let proc = Box::new(value_to_ir_node(&arr.data[0])?); + let args = arr.data[1..].iter() .map(value_to_ir_node) .collect::>>()?; IrKind::Call { proc, args } } "ArrayLit" => { - let arr = match payload { - Value::Array(a) => &**a, + let arr = match payload.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw("ArrayLit payload must be array".into())), }; - let elems = arr.iter() + let elems = arr.data.iter() .map(value_to_ir_node) .collect::>>()?; IrKind::ArrayLit(elems) } "ObjectLit" => { - let arr = match payload { - Value::Array(a) => &**a, + let arr = match payload.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw("ObjectLit payload must be array".into())), }; - let pairs = arr.iter() + let pairs = arr.data.iter() .map(|pair_val| { - let pair_arr = match pair_val { - Value::Array(a) if a.len() == 2 => &**a, + let pair_arr = match pair_val.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return Err(crate::Error::Throw("ObjectLit pair must be 2-element array".into())), }; - let key = match &pair_arr[0] { - Value::String(s) => (**s).to_string(), + let key = match pair_arr.data[0].view() { + NanView::String(s) => (&**s).to_string(), _ => return Err(crate::Error::Throw("ObjectLit key must be string".into())), }; - let value = value_to_ir_node(&pair_arr[1])?; + let value = value_to_ir_node(&pair_arr.data[1])?; Ok((key, value)) }) .collect::>>()?; IrKind::ObjectLit(pairs) } "TypeConstruct" => { - let arr = match payload { - Value::Array(a) if a.len() == 2 => &**a, + let arr = match payload.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return Err(crate::Error::Throw("TypeConstruct needs 2 elements".into())), }; - let type_name = match &arr[0] { - Value::String(s) => (**s).to_string(), + let type_name = match arr.data[0].view() { + NanView::String(s) => (&**s).to_string(), _ => return Err(crate::Error::Throw("TypeConstruct name must be string".into())), }; - let payload_node = Box::new(value_to_ir_node(&arr[1])?); + let payload_node = Box::new(value_to_ir_node(&arr.data[1])?); IrKind::TypeConstruct { type_name, payload: payload_node } } "TailCall" => { - let arr = match payload { - Value::Array(a) if !a.is_empty() => &**a, + let arr = match payload.view() { + NanView::Array(ha) if !ha.data.is_empty() => ha, _ => return Err(crate::Error::Throw("TailCall needs at least 1 element".into())), }; - let proc = Box::new(value_to_ir_node(&arr[0])?); - let args = arr[1..].iter() + let proc = Box::new(value_to_ir_node(&arr.data[0])?); + let args = arr.data[1..].iter() .map(value_to_ir_node) .collect::>>()?; IrKind::TailCall { proc, args } } "Loop" => { - let arr = match payload { - Value::Array(a) => &**a, + let arr = match payload.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw("Loop payload must be array".into())), }; - let args = arr.iter() + let args = arr.data.iter() .map(value_to_ir_node) .collect::>>()?; IrKind::Loop { args } } "Dispatch" => { - let arr = match payload { - Value::Array(a) if a.len() == 3 => &**a, + let arr = match payload.view() { + NanView::Array(ha) if ha.data.len() == 3 => ha, _ => return Err(crate::Error::Throw("Dispatch needs 3 elements".into())), }; - let generic_name = match &arr[0] { - Value::String(s) => (**s).to_string(), + let generic_name = match arr.data[0].view() { + NanView::String(s) => (&**s).to_string(), _ => return Err(crate::Error::Throw("Dispatch name must be string".into())), }; - let args_arr = match &arr[1] { - Value::Array(a) => &**a, + let args_arr = match arr.data[1].view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw("Dispatch args must be array".into())), }; - let args = args_arr.iter() + let args = args_arr.data.iter() .map(value_to_ir_node) .collect::>>()?; - let resolved = if arr[2] == Value::Null { + let resolved = if matches!(arr.data[2].view(), NanView::Null) { None } else { - Some(Box::new(value_to_ir_node(&arr[2])?)) + Some(Box::new(value_to_ir_node(&arr.data[2])?)) }; IrKind::Dispatch { generic_name, args, resolved } } "InterpCall" => { - let arr = match payload { - Value::Array(a) if a.len() == 3 => &**a, + let arr = match payload.view() { + NanView::Array(ha) if ha.data.len() == 3 => ha, _ => return Err(crate::Error::Throw("InterpCall needs 3 elements".into())), }; - let operative = match &arr[0] { - Value::String(s) => (**s).to_string(), + let operative = match arr.data[0].view() { + NanView::String(s) => (&**s).to_string(), _ => return Err(crate::Error::Throw("InterpCall operative must be string".into())), }; - let raw_args = arr[1].clone(); - let needs_env = match &arr[2] { - Value::Int(n) => *n != 0, + let raw_args = arr.data[1].clone(); + let needs_env = match arr.data[2].view() { + NanView::Int(n) => n != 0, _ => false, }; IrKind::InterpCall { operative, raw_args, needs_env } } "Match" => { - let arr = match payload { - Value::Array(a) if a.len() == 2 => &**a, + let arr = match payload.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return Err(crate::Error::Throw("Match needs 2 elements".into())), }; - let scrutinee = Box::new(value_to_ir_node(&arr[0])?); - let tree = value_to_decision_tree(&arr[1])?; + let scrutinee = Box::new(value_to_ir_node(&arr.data[0])?); + let tree = value_to_decision_tree(&arr.data[1])?; IrKind::Match { scrutinee, tree } } "Try" => { - let arr = match payload { - Value::Array(a) if a.len() == 3 => &**a, + let arr = match payload.view() { + NanView::Array(ha) if ha.data.len() == 3 => ha, _ => return Err(crate::Error::Throw("Try needs 3 elements".into())), }; - let expr = Box::new(value_to_ir_node(&arr[0])?); - let handler = Box::new(value_to_ir_node(&arr[1])?); - let try_budget = if arr[2] == Value::Null { + let expr = Box::new(value_to_ir_node(&arr.data[0])?); + let handler = Box::new(value_to_ir_node(&arr.data[1])?); + let try_budget = if matches!(arr.data[2].view(), NanView::Null) { None } else { - Some(Box::new(value_to_ir_node(&arr[2])?)) + Some(Box::new(value_to_ir_node(&arr.data[2])?)) }; IrKind::Try { expr, handler, budget: try_budget } } @@ -1814,15 +1815,15 @@ fn value_to_ir_node(val: &Value) -> crate::Result { fn value_to_decision_tree(val: &Value) -> crate::Result { use super::ir::{DecisionTree, IrId}; - let obj = match val { - Value::Object(o) => o, + let obj = match val.view() { + NanView::Object(ho) => ho, _ => return Err(crate::Error::Throw("decision tree must be an object".into())), }; let tag_val = obj.get("tag") .ok_or_else(|| crate::Error::Throw("missing 'tag' field".into()))?; - let tag_str = match tag_val { - Value::String(s) => &**s, + let tag_str = match tag_val.view() { + NanView::String(s) => s, _ => return Err(crate::Error::Throw("tag must be a string".into())), }; @@ -1830,16 +1831,16 @@ fn value_to_decision_tree(val: &Value) -> crate::Result { "Leaf" => { let bindings_val = obj.get("bindings") .ok_or_else(|| crate::Error::Throw("missing 'bindings' field".into()))?; - let bindings = match bindings_val { - Value::Array(a) => (&**a).iter() + let bindings = match bindings_val.view() { + NanView::Array(ha) => ha.data.iter() .map(|binding_val| { - let arr = match binding_val { - Value::Array(a) if a.len() == 2 => &**a, + let arr = match binding_val.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return Err(crate::Error::Throw("binding must be 2-element array".into())), }; - let id = IrId(value_to_u32(&arr[0])?); - let name = match &arr[1] { - Value::String(s) => (**s).to_string(), + let id = IrId(value_to_u32(&arr.data[0])?); + let name = match arr.data[1].view() { + NanView::String(s) => (&**s).to_string(), _ => return Err(crate::Error::Throw("binding name must be string".into())), }; Ok((id, name, crate::typing::JestType::Top)) @@ -1861,15 +1862,15 @@ fn value_to_decision_tree(val: &Value) -> crate::Result { let cases_val = obj.get("cases") .ok_or_else(|| crate::Error::Throw("missing 'cases' field".into()))?; - let cases = match cases_val { - Value::Array(a) => (&**a).iter() + let cases = match cases_val.view() { + NanView::Array(ha) => ha.data.iter() .map(|case_val| { - let arr = match case_val { - Value::Array(a) if a.len() == 2 => &**a, + let arr = match case_val.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return Err(crate::Error::Throw("case must be 2-element array".into())), }; - let tag = value_to_type_tag(&arr[0])?; - let tree = value_to_decision_tree(&arr[1])?; + let tag = value_to_type_tag(&arr.data[0])?; + let tree = value_to_decision_tree(&arr.data[1])?; Ok((tag, tree)) }) .collect::>>()?, @@ -1878,7 +1879,7 @@ fn value_to_decision_tree(val: &Value) -> crate::Result { let default_val = obj.get("default") .ok_or_else(|| crate::Error::Throw("missing 'default' field".into()))?; - let default = if matches!(default_val, Value::Null) { + let default = if matches!(default_val.view(), NanView::Null) { None } else { Some(Box::new(value_to_decision_tree(default_val)?)) @@ -1893,15 +1894,15 @@ fn value_to_decision_tree(val: &Value) -> crate::Result { let cases_val = obj.get("cases") .ok_or_else(|| crate::Error::Throw("missing 'cases' field".into()))?; - let cases = match cases_val { - Value::Array(a) => (&**a).iter() + let cases = match cases_val.view() { + NanView::Array(ha) => ha.data.iter() .map(|case_val| { - let arr = match case_val { - Value::Array(a) if a.len() == 2 => &**a, + let arr = match case_val.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return Err(crate::Error::Throw("case must be 2-element array".into())), }; - let lit = value_to_lit_value(&arr[0])?; - let tree = value_to_decision_tree(&arr[1])?; + let lit = value_to_lit_value(&arr.data[0])?; + let tree = value_to_decision_tree(&arr.data[1])?; Ok((lit, tree)) }) .collect::>>()?, @@ -1910,7 +1911,7 @@ fn value_to_decision_tree(val: &Value) -> crate::Result { let default_val = obj.get("default") .ok_or_else(|| crate::Error::Throw("missing 'default' field".into()))?; - let default = if matches!(default_val, Value::Null) { + let default = if matches!(default_val.view(), NanView::Null) { None } else { Some(Box::new(value_to_decision_tree(default_val)?)) @@ -1925,16 +1926,16 @@ fn value_to_decision_tree(val: &Value) -> crate::Result { fn value_to_lit_value(val: &Value) -> crate::Result { use super::ir::LitValue; - match val { - Value::Null => Ok(LitValue::Null), - Value::Bool(b) => Ok(LitValue::Bool(*b)), - Value::Int(i) => Ok(LitValue::Integer(crate::runtime::IntegerValue::from_i64(*i))), - Value::Float(f) => Ok(LitValue::Float(f.to_string())), - Value::BigNum(n) => match &**n { + match val.view() { + NanView::Null => Ok(LitValue::Null), + NanView::Bool(b) => Ok(LitValue::Bool(b)), + NanView::Int(i) => Ok(LitValue::Integer(crate::runtime::IntegerValue::from_i64(i))), + NanView::Float(f) => Ok(LitValue::Float(f.to_string())), + NanView::BigNum(n) => match n { crate::runtime::Number::Integer(i) => Ok(LitValue::Integer(i.clone())), crate::runtime::Number::Float(f) => Ok(LitValue::Float(f.to_string())), }, - Value::String(s) => Ok(LitValue::String((**s).to_string())), + NanView::String(s) => Ok(LitValue::String((&**s).to_string())), _ => Err(crate::Error::Throw("invalid literal value in LitSwitch".into())), } } @@ -1942,8 +1943,8 @@ fn value_to_lit_value(val: &Value) -> crate::Result { fn value_to_type_tag(val: &Value) -> crate::Result { use super::ir::TypeTag; - let s = match val { - Value::String(s) => &**s, + let s = match val.view() { + NanView::String(s) => s, _ => return Err(crate::Error::Throw("type tag must be a string".into())), }; @@ -1962,8 +1963,8 @@ fn value_to_type_tag(val: &Value) -> crate::Result { fn value_to_ir_do_stmt(val: &Value) -> crate::Result { use super::ir::{IrDoStmt, IrId}; - let obj = match val { - Value::Object(o) => o, + let obj = match val.view() { + NanView::Object(ho) => ho, _ => return Err(crate::Error::Throw("IR do stmt must be an object".into())), }; @@ -1979,71 +1980,71 @@ fn value_to_ir_do_stmt(val: &Value) -> crate::Result { Ok(IrDoStmt::Expr(node)) } "Bind" => { - let arr = match payload { - Value::Array(a) if a.len() == 3 => &**a, + let arr = match payload.view() { + NanView::Array(ha) if ha.data.len() == 3 => ha, _ => return Err(crate::Error::Throw("Bind needs 3 elements".into())), }; - let name = IrId(value_to_u32(&arr[0])?); - let original_name = match &arr[1] { - Value::String(s) => (**s).to_string(), + let name = IrId(value_to_u32(&arr.data[0])?); + let original_name = match arr.data[1].view() { + NanView::String(s) => (&**s).to_string(), _ => return Err(crate::Error::Throw("original_name must be string".into())), }; - let value = value_to_ir_node(&arr[2])?; + let value = value_to_ir_node(&arr.data[2])?; Ok(IrDoStmt::Bind { name, original_name, value }) } "DefRec" => { - let arr = match payload { - Value::Array(a) => &**a, + let arr = match payload.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw("DefRec payload must be array".into())), }; - let defs = arr.iter() + let defs = arr.data.iter() .map(value_to_ir_fn_def) .collect::>>()?; Ok(IrDoStmt::DefRec(defs)) } "DefType" => { - let arr = match payload { - Value::Array(a) if a.len() == 2 => &**a, + let arr = match payload.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return Err(crate::Error::Throw("DefType needs 2 elements".into())), }; - let name = match &arr[0] { - Value::String(s) => (**s).to_string(), + let name = match arr.data[0].view() { + NanView::String(s) => (&**s).to_string(), _ => return Err(crate::Error::Throw("DefType name must be string".into())), }; - let raw_args = arr[1].clone(); + let raw_args = arr.data[1].clone(); Ok(IrDoStmt::DefType { name, raw_args }) } "DefGeneric" => { - let arr = match payload { - Value::Array(a) if a.len() == 2 => &**a, + let arr = match payload.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return Err(crate::Error::Throw("DefGeneric needs 2 elements".into())), }; - let name = match &arr[0] { - Value::String(s) => (**s).to_string(), + let name = match arr.data[0].view() { + NanView::String(s) => (&**s).to_string(), _ => return Err(crate::Error::Throw("DefGeneric name must be string".into())), }; - let arity = value_to_usize(&arr[1])?; + let arity = value_to_usize(&arr.data[1])?; Ok(IrDoStmt::DefGeneric { name, arity }) } "DefMethod" => { - let arr = match payload { - Value::Array(a) if a.len() == 3 => &**a, + let arr = match payload.view() { + NanView::Array(ha) if ha.data.len() == 3 => ha, _ => return Err(crate::Error::Throw("DefMethod needs 3 elements".into())), }; - let generic_name = match &arr[0] { - Value::String(s) => (**s).to_string(), + let generic_name = match arr.data[0].view() { + NanView::String(s) => (&**s).to_string(), _ => return Err(crate::Error::Throw("DefMethod generic_name must be string".into())), }; - let type_pattern = match &arr[1] { - Value::Array(a) => (&**a).iter() - .map(|v| match v { - Value::String(s) => Ok((**s).to_string()), + let type_pattern = match arr.data[1].view() { + NanView::Array(ha) => ha.data.iter() + .map(|v| match v.view() { + NanView::String(s) => Ok((&**s).to_string()), _ => Err(crate::Error::Throw("type pattern element must be string".into())), }) .collect::>>()?, _ => return Err(crate::Error::Throw("DefMethod type_pattern must be array".into())), }; - let body = value_to_ir_node(&arr[2])?; + let body = value_to_ir_node(&arr.data[2])?; Ok(IrDoStmt::DefMethod { generic_name, type_pattern, body }) } _ => Err(crate::Error::Throw(format!("unknown IR do stmt tag: {}", &**tag))), @@ -2053,8 +2054,8 @@ fn value_to_ir_do_stmt(val: &Value) -> crate::Result { fn value_to_ir_fn_def(val: &Value) -> crate::Result { use super::ir::{IrFnDef, IrId}; - let obj = match val { - Value::Object(o) => o, + let obj = match val.view() { + NanView::Object(ho) => ho, _ => return Err(crate::Error::Throw("IR fn def must be an object".into())), }; @@ -2064,15 +2065,15 @@ fn value_to_ir_fn_def(val: &Value) -> crate::Result { let original_name_val = obj.get("original_name") .ok_or_else(|| crate::Error::Throw("missing 'original_name' field".into()))?; - let original_name = match original_name_val { - Value::String(s) => (**s).to_string(), + let original_name = match original_name_val.view() { + NanView::String(s) => (&**s).to_string(), _ => return Err(crate::Error::Throw("original_name must be string".into())), }; let kind_val = obj.get("kind") .ok_or_else(|| crate::Error::Throw("missing 'kind' field".into()))?; - let kind_str = match kind_val { - Value::String(s) => &**s, + let kind_str = match kind_val.view() { + NanView::String(s) => s, _ => return Err(crate::Error::Throw("kind must be string".into())), }; let kind = match &**kind_str { @@ -2083,8 +2084,8 @@ fn value_to_ir_fn_def(val: &Value) -> crate::Result { let params_val = obj.get("params") .ok_or_else(|| crate::Error::Throw("missing 'params' field".into()))?; - let params = match params_val { - Value::Array(a) => (&**a).iter() + let params = match params_val.view() { + NanView::Array(ha) => ha.data.iter() .map(value_to_ir_param) .collect::>>()?, _ => return Err(crate::Error::Throw("params must be an array".into())), @@ -2096,8 +2097,8 @@ fn value_to_ir_fn_def(val: &Value) -> crate::Result { let captures_val = obj.get("captures") .ok_or_else(|| crate::Error::Throw("missing 'captures' field".into()))?; - let captures = match captures_val { - Value::Array(a) => (&**a).iter() + let captures = match captures_val.view() { + NanView::Array(ha) => ha.data.iter() .map(|v| Ok(IrId(value_to_u32(v)?))) .collect::>>()?, _ => return Err(crate::Error::Throw("captures must be an array".into())), @@ -2117,8 +2118,8 @@ fn value_to_ir_fn_def(val: &Value) -> crate::Result { fn value_to_ir_param(val: &Value) -> crate::Result { use super::ir::{IrId, IrParam}; - let obj = match val { - Value::Object(o) => o, + let obj = match val.view() { + NanView::Object(ho) => ho, _ => return Err(crate::Error::Throw("IR param must be an object".into())), }; @@ -2128,8 +2129,8 @@ fn value_to_ir_param(val: &Value) -> crate::Result { let name_val = obj.get("name") .ok_or_else(|| crate::Error::Throw("missing 'name' field".into()))?; - let name = match name_val { - Value::String(s) => (**s).to_string(), + let name = match name_val.view() { + NanView::String(s) => (&**s).to_string(), _ => return Err(crate::Error::Throw("param name must be string".into())), }; @@ -2141,11 +2142,11 @@ fn value_to_ir_param(val: &Value) -> crate::Result { } fn value_to_usize(val: &Value) -> crate::Result { - match val { - Value::Int(i) => { - usize::try_from(*i).map_err(|_| crate::Error::Throw("integer out of usize range".into())) + match val.view() { + NanView::Int(i) => { + usize::try_from(i).map_err(|_| crate::Error::Throw("integer out of usize range".into())) }, - Value::BigNum(n) => match &**n { + NanView::BigNum(n) => match n { crate::runtime::Number::Integer(i) => { i.to_usize().ok_or_else(|| crate::Error::Throw("integer out of usize range".into())) }, @@ -2166,8 +2167,8 @@ mod tests { fn chunk_add_constant_deduplicates() { let b = Budget::new(4096); let mut chunk = Chunk::new(); - let v1 = Value::Int(42); - let v2 = Value::Int(42); + let v1 = Value::int(42); + let v2 = Value::int(42); let v3 = Value::string("hello", &b).unwrap(); let idx1 = chunk.add_constant(v1); @@ -2203,7 +2204,7 @@ mod tests { let template = ClosureTemplate { kind: FnKind::Fn, param_names: vec!["x".into()], - body_source: Value::Null, + body_source: Value::null(), num_captures: 0, capture_names: vec![], body_chunk: None, @@ -2217,7 +2218,7 @@ mod tests { fn chunk_interp_call() { let mut chunk = Chunk::new(); let info = InterpCallInfo { - source_form: Value::Null, + source_form: Value::null(), }; let idx = chunk.add_interp_call(info); assert_eq!(idx, 0); @@ -2244,7 +2245,7 @@ mod tests { chunk.emit(Op::Return(0)); // Add a constant - let val = Value::Int(42); + let val = Value::int(42); chunk.add_constant(val); // Add object keys @@ -2254,7 +2255,7 @@ mod tests { let template = ClosureTemplate { kind: FnKind::Fn, param_names: vec!["x".into(), "y".into()], - body_source: Value::Null, + body_source: Value::null(), num_captures: 1, capture_names: vec!["z".into()], body_chunk: None, diff --git a/src/compile/classify.rs b/src/compile/classify.rs index 681c9db..5979d47 100644 --- a/src/compile/classify.rs +++ b/src/compile/classify.rs @@ -362,7 +362,7 @@ mod tests { let budget = Budget::new(4096); // Build {"wrap": {"vau": [[], null]}} let formals = Value::array(vec![], &budget).unwrap(); - let vau_args = Value::array(vec![formals, Value::Null], &budget).unwrap(); + let vau_args = Value::array(vec![formals, Value::null()], &budget).unwrap(); let vau_val = Value::call("vau", vau_args, &budget).unwrap(); let wrap_val = Value::call("wrap", vau_val, &budget).unwrap(); @@ -379,7 +379,7 @@ mod tests { use crate::runtime::Budget; let budget = Budget::new(4096); let formals = Value::array(vec![], &budget).unwrap(); - let vau_args = Value::array(vec![formals, Value::Null], &budget).unwrap(); + let vau_args = Value::array(vec![formals, Value::null()], &budget).unwrap(); let vau_val = Value::call("vau", vau_args, &budget).unwrap(); assert!(matches!( diff --git a/src/compile/decision.rs b/src/compile/decision.rs index f03dc16..f6f9a74 100644 --- a/src/compile/decision.rs +++ b/src/compile/decision.rs @@ -13,6 +13,7 @@ //! - Inverse evaluation (`{"+": [1, {"var": "x"}]}`) use crate::runtime::Value; +use crate::runtime::nanvalue::NanView; use crate::typing::JestType; use super::ir::*; @@ -322,7 +323,7 @@ fn compile_pattern( for (i, arg) in args.iter().enumerate() { if extract_var_name(arg).is_some() { var_index = Some(i); - ground_args.push(IrNode::new(IrKind::Lit(Value::Null), JestType::Null)); // placeholder + ground_args.push(IrNode::new(IrKind::Lit(Value::null()), JestType::Null)); // placeholder } else { ground_args.push(IrNode::new(IrKind::Lit(arg.clone()), JestType::Top)); } @@ -451,53 +452,53 @@ enum PatternKind { /// Classify a pattern value into its kind. fn classify_pattern(pat: &Value) -> PatternKind { - match pat { - Value::Null => PatternKind::LitNull, - Value::Bool(b) => PatternKind::LitBool(*b), - Value::Int(i) => PatternKind::LitNumber(crate::runtime::Number::int(*i)), - Value::Float(f) => PatternKind::LitNumber(crate::runtime::Number::Float(crate::runtime::FloatValue::from_f64(*f))), - Value::BigNum(n) => PatternKind::LitNumber((**n).clone()), - Value::String(s) => PatternKind::LitString(s.to_string()), - - Value::Array(arr) => { + match pat.view() { + NanView::Null => PatternKind::LitNull, + NanView::Bool(b) => PatternKind::LitBool(b), + NanView::Int(i) => PatternKind::LitNumber(crate::runtime::Number::int(i)), + NanView::Float(f) => PatternKind::LitNumber(crate::runtime::Number::Float(crate::runtime::FloatValue::from_f64(f))), + NanView::BigNum(n) => PatternKind::LitNumber(n.clone()), + NanView::String(s) => PatternKind::LitString(s.to_string()), + + NanView::Array(ha) => { // Array patterns are always fixed-length. // Variadic patterns use {"cons": [head, tail]} instead. - let elements = arr.iter().cloned().collect(); + let elements = ha.data.iter().cloned().collect(); PatternKind::Array { elements, rest_pat: None } } - Value::Object(obj) if obj.len() == 1 => { - let (key, val) = obj.iter().next().unwrap(); + NanView::Object(ho) if ho.len() == 1 => { + let (key, val) = ho.iter().next().unwrap(); let key_str = key.to_string(); match key_str.as_str() { "var" => { // {"var": "name"} → variable binding // {"var": null} → wildcard - match val { - Value::Null => PatternKind::Wildcard, - Value::String(s) => PatternKind::Var(s.to_string()), + match val.view() { + NanView::Null => PatternKind::Wildcard, + NanView::String(s) => PatternKind::Var(s.to_string()), _ => PatternKind::Wildcard, } } "and-pattern" => { - if let Value::Array(arr) = val - && arr.len() == 2 { - return PatternKind::AndPattern(arr[0].clone(), arr[1].clone()); + if let NanView::Array(ha) = val.view() + && ha.data.len() == 2 { + return PatternKind::AndPattern(ha.data[0].clone(), ha.data[1].clone()); } PatternKind::Wildcard } "or-pattern" => { - if let Value::Array(arr) = val - && arr.len() == 2 { - return PatternKind::OrPattern(arr[0].clone(), arr[1].clone()); + if let NanView::Array(ha) = val.view() + && ha.data.len() == 2 { + return PatternKind::OrPattern(ha.data[0].clone(), ha.data[1].clone()); } PatternKind::Wildcard } "guard-pattern" => { - if let Value::Array(arr) = val - && arr.len() == 2 { - return PatternKind::GuardPattern(arr[0].clone(), arr[1].clone()); + if let NanView::Array(ha) = val.view() + && ha.data.len() == 2 { + return PatternKind::GuardPattern(ha.data[0].clone(), ha.data[1].clone()); } PatternKind::Wildcard } @@ -505,12 +506,12 @@ fn classify_pattern(pat: &Value) -> PatternKind { // If the singleton has array args and one is a var, it's an inverse tag => { if is_invertible_op(tag) - && let Value::Array(args) = val { - let has_var = args.iter().any(|a| extract_var_name(a).is_some()); + && let NanView::Array(ha) = val.view() { + let has_var = ha.data.iter().any(|a| extract_var_name(a).is_some()); if has_var { return PatternKind::Inverse { op: tag.to_string(), - args: args.iter().cloned().collect(), + args: ha.data.iter().cloned().collect(), }; } } @@ -520,9 +521,9 @@ fn classify_pattern(pat: &Value) -> PatternKind { } } - Value::Object(obj) => { + NanView::Object(ho) => { // Multi-key object pattern: subset matching - let entries: Vec<(String, Value)> = obj + let entries: Vec<(String, Value)> = ho .iter() .map(|(k, v)| (k.to_string(), v.clone())) .collect(); @@ -541,9 +542,9 @@ fn is_invertible_op(op: &str) -> bool { /// Extract a variable name from `{"var": "name"}`, or `None`. fn extract_var_name(val: &Value) -> Option { if let Some(("var", inner)) = val.as_call() { - match inner { - Value::String(s) => Some(s.to_string()), - Value::Null => Some(String::new()), // wildcard + match inner.view() { + NanView::String(s) => Some(s.to_string()), + NanView::Null => Some(String::new()), // wildcard _ => None, } } else { @@ -574,13 +575,13 @@ fn extract_all_bindings(tree: &DecisionTree) -> Vec<(IrId, String, JestType)> { fn extract_body(tree: &DecisionTree) -> Box { match tree { DecisionTree::Leaf { body, .. } => body.clone(), - _ => Box::new(IrNode::new(IrKind::Lit(Value::Null), JestType::Null)), + _ => Box::new(IrNode::new(IrKind::Lit(Value::null()), JestType::Null)), } } /// Create a placeholder body for chaining and-patterns. fn make_placeholder_body(_tree: &DecisionTree) -> IrNode { - IrNode::new(IrKind::Lit(Value::Null), JestType::Null) + IrNode::new(IrKind::Lit(Value::null()), JestType::Null) } // ── Tests ─────────────────────────────────────────────────────────────────── @@ -599,11 +600,11 @@ mod tests { } fn wildcard_pat(b: &Budget) -> Value { - Value::call("var", Value::Null, b).unwrap() + Value::call("var", Value::null(), b).unwrap() } fn dummy_body() -> IrNode { - IrNode::new(IrKind::Lit(Value::Null), JestType::Null) + IrNode::new(IrKind::Lit(Value::null()), JestType::Null) } #[test] @@ -640,7 +641,7 @@ mod tests { #[test] fn null_literal() { let mut ctx = DecisionCtx::new(0); - let clauses = vec![(Value::Null, dummy_body())]; + let clauses = vec![(Value::null(), dummy_body())]; let tree = compile_clauses(&clauses, IrId(0), &JestType::Top, &mut ctx); assert!(matches!(tree, DecisionTree::LitSwitch { .. })); } @@ -648,7 +649,7 @@ mod tests { #[test] fn bool_literal() { let mut ctx = DecisionCtx::new(0); - let clauses = vec![(Value::Bool(true), dummy_body())]; + let clauses = vec![(Value::bool(true), dummy_body())]; let tree = compile_clauses(&clauses, IrId(0), &JestType::Top, &mut ctx); match &tree { DecisionTree::LitSwitch { cases, .. } => { @@ -699,15 +700,15 @@ mod tests { let b = budget(); let mut ctx = DecisionCtx::new(0); let body1 = IrNode::new( - IrKind::Lit(Value::Bool(true)), + IrKind::Lit(Value::bool(true)), JestType::Bool(Some(true)), ); let body2 = IrNode::new( - IrKind::Lit(Value::Bool(false)), + IrKind::Lit(Value::bool(false)), JestType::Bool(Some(false)), ); let clauses = vec![ - (Value::Null, body1), + (Value::null(), body1), (var_pat("x", &b), body2), ]; let tree = compile_clauses(&clauses, IrId(0), &JestType::Top, &mut ctx); @@ -724,7 +725,7 @@ mod tests { let b = budget(); let mut ctx = DecisionCtx::new(0); // {"+": [1, {"var": "x"}]} - let one = Value::Int(1); + let one = Value::int(1); let args = Value::array(vec![one, var_pat("x", &b)], &b).unwrap(); let pat = Value::call("+", args, &b).unwrap(); let clauses = vec![(pat, dummy_body())]; diff --git a/src/compile/emit.rs b/src/compile/emit.rs index 4284ca3..6cca1e9 100644 --- a/src/compile/emit.rs +++ b/src/compile/emit.rs @@ -11,7 +11,7 @@ use super::bytecode::{ use super::eval::ir_body_to_source; use super::ir::{IrKind, IrNode, IrParam}; use super::lower::IrContext; -use crate::runtime::{Budget, Value}; +use crate::runtime::{Budget, Value, nanvalue::NanView}; use crate::typing::JestType; use crate::typing::types::IntegerInfo; use crate::typing::intervals::Interval; @@ -235,7 +235,7 @@ pub(crate) fn emit_node( } let body_source = ir_body_to_source(body, ctx, budget) - .unwrap_or(Value::Null); + .unwrap_or(Value::null()); let capture_names: Vec = captures .iter() @@ -308,14 +308,14 @@ pub(crate) fn emit_node( r } }; - let name_val = Value::string(type_name, budget).unwrap_or(Value::Null); + let name_val = Value::string(type_name, budget).unwrap_or(Value::null()); let idx = chunk.add_constant(name_val); chunk.emit(Op::TypeConstruct(dst, payload_reg, idx)); } IrKind::InterpCall { operative, raw_args, .. } => { let source_form = Value::call(operative, raw_args.clone(), budget) - .unwrap_or(Value::Null); + .unwrap_or(Value::null()); let idx = chunk.add_interp_call(InterpCallInfo { source_form }); chunk.emit(Op::InterpCall(dst, idx)); } @@ -399,7 +399,7 @@ pub(crate) fn emit_node( } super::ir::IrDoStmt::DefRec(defs) => { let body_chunks = emit_defrec_chunks(defs, ctx, budget); - let body = IrNode::new(IrKind::Lit(Value::Null), crate::typing::JestType::Null); + let body = IrNode::new(IrKind::Lit(Value::null()), crate::typing::JestType::Null); let idx = chunk.add_letrec_template(LetRecTemplate { defs: defs.clone(), body, @@ -490,7 +490,7 @@ fn emit_call( // emit a constant instead of a runtime count op. let eliminated = if let JestType::Array(at) = &args[0].ty { if at.is_fixed() { - let count_val = Value::Int(at.head.len() as i64); + let count_val = Value::int(at.head.len() as i64); let idx = chunk.add_constant(count_val) as u16; chunk.emit(Op::LoadConst(dst, idx)); true @@ -620,10 +620,10 @@ fn emit_to_reg( // ── Literal emission ──────────────────────────────────────────────────────── fn emit_literal(val: &Value, dst: u32, chunk: &mut Chunk) { - match val { - Value::Null => { chunk.emit(Op::LoadNull(dst)); } - Value::Bool(true) => { chunk.emit(Op::LoadTrue(dst)); } - Value::Bool(false) => { chunk.emit(Op::LoadFalse(dst)); } + match val.view() { + NanView::Null => { chunk.emit(Op::LoadNull(dst)); } + NanView::Bool(true) => { chunk.emit(Op::LoadTrue(dst)); } + NanView::Bool(false) => { chunk.emit(Op::LoadFalse(dst)); } _ => { let idx = chunk.add_constant(val.clone()); chunk.emit(Op::LoadConst(dst, idx)); diff --git a/src/compile/eval.rs b/src/compile/eval.rs index 1d6c8e1..7ca7e01 100644 --- a/src/compile/eval.rs +++ b/src/compile/eval.rs @@ -17,6 +17,7 @@ use super::{ LitValue, TypeTag, }; use crate::runtime::convert::is_truthy; +use crate::runtime::nanvalue::NanView; use crate::runtime::procs::dispatch_call; use crate::runtime::{Budget, Number, Scope, Value}; use crate::runtime::eval_trampoline; @@ -307,7 +308,7 @@ fn eval_seq( eval_limit: &mut usize, scope: &Scope, ) -> crate::Result { - let mut last_val = Value::Null; + let mut last_val = Value::null(); let mut local_scope = scope.clone(); for stmt in stmts.iter() { @@ -322,11 +323,11 @@ fn eval_seq( let vars: HashMap = [(original_name.clone(), val)].into_iter().collect(); local_scope = local_scope.local(vars); - last_val = Value::Null; + last_val = Value::null(); } IrDoStmt::DefRec(defs) => { eval_defrec_in_seq(defs, env, ctx, budget, eval_limit, &mut local_scope)?; - last_val = Value::Null; + last_val = Value::null(); } // DefType, DefGeneric, DefMethod, Import → delegate via InterpCall-like mechanism IrDoStmt::DefType { raw_args, .. } => { @@ -335,20 +336,20 @@ fn eval_seq( let result = eval_trampoline(budget, eval_limit, call_val, local_scope.clone())?; // If the result is a binding-result, extend scope extend_scope_from_binding(&result, &mut local_scope, budget)?; - last_val = Value::Null; + last_val = Value::null(); } IrDoStmt::DefGeneric { name, arity } => { let args_val = Value::array( vec![ Value::string(name, budget)?, - Value::Int(*arity as i64), + Value::int(*arity as i64), ], budget, )?; let call_val = Value::call("defgeneric", args_val, budget)?; let result = eval_trampoline(budget, eval_limit, call_val, local_scope.clone())?; extend_scope_from_binding(&result, &mut local_scope, budget)?; - last_val = Value::Null; + last_val = Value::null(); } IrDoStmt::DefMethod { generic_name, type_pattern, body } => { // defmethod needs full runtime evaluation; delegate the body to IR @@ -369,13 +370,13 @@ fn eval_seq( let call_val = Value::call("defmethod", args_val, budget)?; let result = eval_trampoline(budget, eval_limit, call_val, local_scope.clone())?; extend_scope_from_binding(&result, &mut local_scope, budget)?; - last_val = Value::Null; + last_val = Value::null(); } IrDoStmt::Import { path, names, bindings } => { // Import: evaluate path, build import call, extend scope with bindings let path_val = ir_step(path, env, ctx, budget, eval_limit, &local_scope)?; let names_val = match names { - super::ImportSpec::Wildcard => Value::Null, + super::ImportSpec::Wildcard => Value::null(), super::ImportSpec::Namespace(ns) => Value::string(ns, budget)?, super::ImportSpec::Selective(sel) => { let items: Vec = sel @@ -403,10 +404,11 @@ fn eval_seq( // Extend scope with the imported bindings extend_scope_from_binding(&result, &mut local_scope, budget)?; // Also bind into IR env using the resolved IrIds - if let Value::Object(obj) = &result + if let NanView::Object(obj) = result.view() + && obj.len() == 1 && let Some((key, inner)) = obj.iter().next() && &**key == "binding" - && let Value::Object(bindings_obj) = inner { + && let NanView::Object(bindings_obj) = inner.view() { for (bnd_name, bnd_id) in bindings { for (k, v) in bindings_obj.iter() { if &**k == bnd_name.as_str() { @@ -415,7 +417,7 @@ fn eval_seq( } } } - last_val = Value::Null; + last_val = Value::null(); } } } @@ -428,11 +430,11 @@ fn extend_scope_from_binding( scope: &mut Scope, _budget: &Budget, ) -> crate::Result<()> { - if let Value::Object(obj) = val + if let NanView::Object(obj) = val.view() && obj.len() == 1 && let Some((key, inner)) = obj.iter().next() && &**key == "binding" - && let Value::Object(bindings) = inner { + && let NanView::Object(bindings) = inner.view() { let vars: HashMap = bindings .iter() .map(|(k, v)| ((**k).to_string(), v.clone())) @@ -520,7 +522,7 @@ fn eval_call( /// Check if a procedure value is an operative (vau/mu without wrap). fn is_operative(val: &Value) -> bool { - if let Value::Object(obj) = val + if let NanView::Object(obj) = val.view() && obj.len() == 1 && let Some((key, _)) = obj.iter().next() { @@ -729,7 +731,7 @@ fn ir_body_to_source_impl( } _ => { // For complex do-stmts, emit null as placeholder - parts.push(Value::Null); + parts.push(Value::null()); } } } @@ -752,8 +754,8 @@ fn ir_body_to_source_impl( let body_val = ir_body_to_source_impl(body, ctx, budget, None)?; let formals: Vec = params .iter() - .map(|p| Value::call("var", Value::string(&p.name, budget).unwrap_or(Value::Null), budget) - .unwrap_or(Value::Null)) + .map(|p| Value::call("var", Value::string(&p.name, budget).unwrap_or(Value::null()), budget) + .unwrap_or(Value::null())) .collect(); let formals_val = Value::array(formals, budget)?; let args = Value::array(vec![formals_val, body_val], budget)?; @@ -768,8 +770,8 @@ fn ir_body_to_source_impl( let fn_body = ir_body_to_source_impl(&def.body, ctx, budget, Some(&def.original_name))?; let formals: Vec = def.params .iter() - .map(|p| Value::call("var", Value::string(&p.name, budget).unwrap_or(Value::Null), budget) - .unwrap_or(Value::Null)) + .map(|p| Value::call("var", Value::string(&p.name, budget).unwrap_or(Value::null()), budget) + .unwrap_or(Value::null())) .collect(); let formals_val = Value::array(formals, budget)?; let kind_str = match def.kind { FnKind::Fn => "fn", FnKind::Macro => "macro" }; @@ -801,7 +803,7 @@ fn ir_body_to_source_impl( // Match trees are complex to reconstruct; fall through to null. // This shouldn't appear in closure bodies in practice (match is // lowered from source-level match forms that keep InterpCall). - _ => Ok(Value::Null), + _ => Ok(Value::null()), } } @@ -1068,8 +1070,8 @@ fn eval_decision_tree( DecisionTree::LengthCheck { scrutinee, min, exact, on_match, on_fail } => { let val = env.get(*scrutinee)?; - let len = match &val { - Value::Array(arr) => arr.len(), + let len = match val.view() { + NanView::Array(ha) => ha.data.len(), _ => return Err(crate::Error::Throw("match: length check on non-array".to_string())), }; let matches = if *exact { len == *min } else { len >= *min }; @@ -1084,16 +1086,16 @@ fn eval_decision_tree( DecisionTree::ArrayDestructure { scrutinee, index, binding, rest_from, child } => { let val = env.get(*scrutinee)?; - if let Value::Array(arr) = &val { + if let NanView::Array(ha) = val.view() { // Extract element at index - if *index < arr.len() { - env.set(*binding, arr[*index].clone()); + if *index < ha.data.len() { + env.set(*binding, ha.data[*index].clone()); } else { return Err(crate::Error::Throw("match: array index out of bounds".to_string())); } // Optionally extract rest if let Some((from_idx, rest_id)) = rest_from { - let rest_items: Vec = arr.iter().skip(*from_idx).cloned().collect(); + let rest_items: Vec = ha.data.iter().skip(*from_idx).cloned().collect(); let rest_val = Value::array(rest_items, budget)?; env.set(*rest_id, rest_val); } @@ -1105,9 +1107,8 @@ fn eval_decision_tree( DecisionTree::ObjectDestructure { scrutinee, key, binding, child } => { let val = env.get(*scrutinee)?; - if let Value::Object(obj) = &val { - let found = obj.iter().find(|(k, _)| &**k == key.as_str()); - if let Some((_, v)) = found { + if let NanView::Object(ho) = val.view() { + if let Some(v) = ho.get(key) { env.set(*binding, v.clone()); } else { return Err(crate::Error::Throw(format!("match: key {:?} not found", key))); @@ -1148,9 +1149,9 @@ fn eval_decision_tree( DecisionTree::TagMatch { scrutinee, tag, payload_binding, payload_tree, on_fail } => { let val = env.get(*scrutinee)?; - if let Value::Object(obj) = &val - && obj.len() == 1 - && let Some((key, payload)) = obj.iter().next() + if let NanView::Object(ho) = val.view() + && ho.len() == 1 + && let Some((key, payload)) = ho.iter().next() && &**key == tag.as_str() { env.set(*payload_binding, payload.clone()); return eval_decision_tree(payload_tree, payload, env, ctx, budget, eval_limit, scope); @@ -1166,34 +1167,34 @@ fn eval_decision_tree( /// Map a runtime Value to a TypeTag for TypeSwitch dispatch. fn value_to_type_tag(val: &Value) -> TypeTag { - match val { - Value::Null => TypeTag::Null, - Value::Bool(_) => TypeTag::Bool, - Value::Int(_) => TypeTag::Integer, - Value::Float(_) => TypeTag::Float, - Value::BigNum(n) => match &**n { + match val.view() { + NanView::Null => TypeTag::Null, + NanView::Bool(_) => TypeTag::Bool, + NanView::Int(_) => TypeTag::Integer, + NanView::Float(_) => TypeTag::Float, + NanView::BigNum(n) => match n { Number::Integer(_) => TypeTag::Integer, Number::Float(_) => TypeTag::Float, }, - Value::String(_) => TypeTag::String, - Value::Array(_) => TypeTag::Array, - Value::Object(_) => TypeTag::Object, + NanView::String(_) => TypeTag::String, + NanView::Array(_) => TypeTag::Array, + NanView::Object(_) => TypeTag::Object, } } /// Map a runtime Value to a LitValue for LitSwitch dispatch. fn value_to_lit(val: &Value) -> LitValue { - match val { - Value::Null => LitValue::Null, - Value::Bool(b) => LitValue::Bool(*b), - Value::Int(i) => LitValue::Integer(crate::runtime::IntegerValue::from_i64(*i)), - Value::Float(f) => LitValue::Float(f.to_string()), - Value::BigNum(n) => match &**n { + match val.view() { + NanView::Null => LitValue::Null, + NanView::Bool(b) => LitValue::Bool(b), + NanView::Int(i) => LitValue::Integer(crate::runtime::IntegerValue::from_i64(i)), + NanView::Float(f) => LitValue::Float(f.to_string()), + NanView::BigNum(n) => match n { Number::Integer(i) => LitValue::Integer(i.clone()), Number::Float(f) => LitValue::Float(f.to_string()), }, - Value::String(s) => LitValue::String((**s).to_string()), - Value::Array(_) | Value::Object(_) => { + NanView::String(s) => LitValue::String(s.to_string()), + NanView::Array(_) | NanView::Object(_) => { // Compound values can't appear in LitSwitch cases LitValue::Null } @@ -1273,14 +1274,14 @@ fn compute_inverse( } "cons" => { // cons pattern: {"cons": [h, t]} matches array → h = arr[0], t = arr[1..] - if let Value::Array(arr) = result { + if let NanView::Array(ha) = result.view() { if var_index == 0 { // head - arr.iter().next().cloned().ok_or_else(|| + ha.data.first().cloned().ok_or_else(|| crate::Error::Throw("match: cons inverse on empty array".to_string())) } else { // tail - let items: Vec = arr.iter().skip(1).cloned().collect(); + let items: Vec = ha.data.iter().skip(1).cloned().collect(); Value::array(items, budget) } } else { @@ -1289,12 +1290,12 @@ fn compute_inverse( } "str" => { // String concat inverse: strip prefix/suffix - if let Value::String(s) = result { - let s_str: &str = s.as_ref(); + if let NanView::String(s) = result.view() { + let s_str: &str = &**s; // ground_args are the known string parts; we need to figure out what the var part is if ground_args.len() == 1 - && let Value::String(g) = &ground_args[0] { - let g_str: &str = g.as_ref(); + && let NanView::String(g) = ground_args[0].view() { + let g_str: &str = &**g; if var_index == 0 { // var ++ ground = result → var = result.strip_suffix(ground) if let Some(prefix) = s_str.strip_suffix(g_str) { @@ -1314,17 +1315,17 @@ fn compute_inverse( } "concat" => { // Array concat inverse: strip prefix/suffix - if let Value::Array(arr) = result { + if let NanView::Array(ha) = result.view() { if ground_args.len() == 1 - && let Value::Array(g) = &ground_args[0] { + && let NanView::Array(ga) = ground_args[0].view() { if var_index == 0 { // var ++ ground = result → var = result[..result.len()-ground.len()] - let rest_len = arr.len().saturating_sub(g.len()); - let items: Vec = arr.iter().take(rest_len).cloned().collect(); + let rest_len = ha.data.len().saturating_sub(ga.data.len()); + let items: Vec = ha.data.iter().take(rest_len).cloned().collect(); return Value::array(items, budget); } else { // ground ++ var = result → var = result[ground.len()..] - let items: Vec = arr.iter().skip(g.len()).cloned().collect(); + let items: Vec = ha.data.iter().skip(ga.data.len()).cloned().collect(); return Value::array(items, budget); } } @@ -1337,13 +1338,12 @@ fn compute_inverse( // assoc inverse: extract key, return remainder // ground_args[0] = base (or var), ground_args[1] = key // The scrutinee is the full object; we need to dissoc the key and match - if let Value::Object(obj) = result { + if let NanView::Object(ho) = result.view() { // key is always ground if !ground_args.is_empty() - && let Value::String(key) = &ground_args[0] { - let key_str: &str = key.as_ref(); - let found = obj.iter().find(|(k, _)| &**k == key_str); - if let Some((_, v)) = found { + && let NanView::String(key) = ground_args[0].view() { + let key_str: &str = &**key; + if let Some(v) = ho.get(key_str) { return Ok(v.clone()); } } @@ -1372,11 +1372,11 @@ fn eval_try( // If budget_expr is present, evaluate it to get an eval-limit budget let mut local_limit = if let Some(be) = budget_expr { let bv = ir_step(be, env, ctx, budget, eval_limit, scope)?; - match &bv { - Value::Int(i) => { - usize::try_from(*i).unwrap_or(*eval_limit) + match bv.view() { + NanView::Int(i) => { + usize::try_from(i).unwrap_or(*eval_limit) } - Value::BigNum(n) => match &**n { + NanView::BigNum(n) => match n { Number::Integer(i) => i.to_usize().unwrap_or(*eval_limit), _ => *eval_limit, }, diff --git a/src/compile/ir.rs b/src/compile/ir.rs index b2f7506..f6fc38c 100644 --- a/src/compile/ir.rs +++ b/src/compile/ir.rs @@ -384,14 +384,14 @@ mod tests { #[test] fn ir_node_construction() { - let node = IrNode::new(IrKind::Lit(Value::Null), JestType::Null); + let node = IrNode::new(IrKind::Lit(Value::null()), JestType::Null); assert_eq!(node.ty, JestType::Null); assert!(node.source.is_none()); } #[test] fn ir_node_with_source() { - let node = IrNode::new(IrKind::Lit(Value::Null), JestType::Null) + let node = IrNode::new(IrKind::Lit(Value::null()), JestType::Null) .with_source(JestPointer::root()); assert!(node.source.is_some()); } @@ -407,9 +407,9 @@ mod tests { #[test] fn ir_kind_branch() { - let cond = Box::new(IrNode::new(IrKind::Lit(Value::Bool(true)), JestType::Bool(Some(true)))); - let then_b = Box::new(IrNode::new(IrKind::Lit(Value::Null), JestType::Null)); - let else_b = Box::new(IrNode::new(IrKind::Lit(Value::Null), JestType::Null)); + let cond = Box::new(IrNode::new(IrKind::Lit(Value::bool(true)), JestType::Bool(Some(true)))); + let then_b = Box::new(IrNode::new(IrKind::Lit(Value::null()), JestType::Null)); + let else_b = Box::new(IrNode::new(IrKind::Lit(Value::null()), JestType::Null)); let node = IrNode::new( IrKind::Branch { cond, then_branch: then_b, else_branch: else_b }, JestType::Null, @@ -424,7 +424,7 @@ mod tests { original_name: "fact".into(), kind: FnKind::Fn, params: vec![IrParam { id: IrId(1), name: "n".into(), ty: JestType::Top }], - body: Box::new(IrNode::new(IrKind::Lit(Value::Null), JestType::Null)), + body: Box::new(IrNode::new(IrKind::Lit(Value::null()), JestType::Null)), captures: vec![], ty: JestType::Top, }; @@ -477,10 +477,10 @@ mod tests { let _bind = IrDoStmt::Bind { name: IrId(0), original_name: "x".into(), - value: IrNode::new(IrKind::Lit(Value::Null), JestType::Null), + value: IrNode::new(IrKind::Lit(Value::null()), JestType::Null), }; - let _expr = IrDoStmt::Expr(IrNode::new(IrKind::Lit(Value::Null), JestType::Null)); - let _dt = IrDoStmt::DefType { name: "rational".into(), raw_args: Value::Null }; + let _expr = IrDoStmt::Expr(IrNode::new(IrKind::Lit(Value::null()), JestType::Null)); + let _dt = IrDoStmt::DefType { name: "rational".into(), raw_args: Value::null() }; let _dg = IrDoStmt::DefGeneric { name: "+".into(), arity: 2 }; } } diff --git a/src/compile/lower.rs b/src/compile/lower.rs index 7dd71c3..f9fc285 100644 --- a/src/compile/lower.rs +++ b/src/compile/lower.rs @@ -15,6 +15,7 @@ use crate::runtime::procs::forms::{ classify_do_stmt, parse_def, parse_defn, parse_defrec_entries, DoStmtKind, }; use crate::runtime::Value; +use crate::runtime::nanvalue::NanView; use crate::typing::check::{ConstraintSet, JestPointer}; use crate::typing::JestType; use crate::typing::types::{IntegerInfo, StringInfo}; @@ -239,42 +240,42 @@ impl IrContext { /// `tail` tracks whether this expression is in tail position (for TCO). /// `ptr` is the source location for diagnostics. pub fn lower(value: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) -> IrNode { - match value { + match value.view() { // ── Primitives → Lit ──────────────────────────────────────── - Value::Null => IrNode::new(IrKind::Lit(Value::Null), JestType::Null) + NanView::Null => IrNode::new(IrKind::Lit(Value::null()), JestType::Null) .with_source(ptr.clone()), - Value::Bool(b) => IrNode::new( - IrKind::Lit(Value::Bool(*b)), - JestType::Bool(Some(*b)), + NanView::Bool(b) => IrNode::new( + IrKind::Lit(Value::bool(b)), + JestType::Bool(Some(b)), ).with_source(ptr.clone()), - Value::Int(i) => { - let ty = JestType::Integer(crate::typing::types::IntegerInfo::Exact(crate::runtime::IntegerValue::from_i64(*i))); + NanView::Int(i) => { + let ty = JestType::Integer(crate::typing::types::IntegerInfo::Exact(crate::runtime::IntegerValue::from_i64(i))); IrNode::new(IrKind::Lit(value.clone()), ty).with_source(ptr.clone()) } - Value::Float(f) => { - let ty = JestType::Float(Some(crate::runtime::FloatValue::from_f64(*f))); + NanView::Float(f) => { + let ty = JestType::Float(Some(crate::runtime::FloatValue::from_f64(f))); IrNode::new(IrKind::Lit(value.clone()), ty).with_source(ptr.clone()) } - Value::BigNum(n) => { - let ty = match &**n { + NanView::BigNum(n) => { + let ty = match n { crate::runtime::Number::Integer(i) => JestType::Integer(crate::typing::types::IntegerInfo::Exact(i.clone())), crate::runtime::Number::Float(f) => JestType::Float(Some(f.clone())), }; IrNode::new(IrKind::Lit(value.clone()), ty).with_source(ptr.clone()) } - Value::String(s) => IrNode::new( + NanView::String(s) => IrNode::new( IrKind::Lit(value.clone()), JestType::String(StringInfo::Exact(s.to_string())), ).with_source(ptr.clone()), // ── Arrays → ArrayLit ─────────────────────────────────────── - Value::Array(arr) => { - let elements: Vec = arr + NanView::Array(ha) => { + let elements: Vec = ha.data .iter() .enumerate() .map(|(i, elem)| lower(elem, &ptr.push_index(i), false, ctx)) @@ -285,14 +286,14 @@ pub fn lower(value: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) } // ── Objects → dispatch on singleton vs multi-key ──────────── - Value::Object(obj) => { - if obj.len() == 1 { - let (key, arg) = obj.iter().next().unwrap(); + NanView::Object(ho) => { + if ho.len() == 1 { + let (key, arg) = ho.iter().next().unwrap(); let key_str = key.to_string(); lower_call(&key_str, arg, ptr, tail, ctx) } else { // Multi-key object: evaluate values, keys unchanged - let pairs: Vec<(String, IrNode)> = obj + let pairs: Vec<(String, IrNode)> = ho .iter() .map(|(k, v)| { let k_str = k.to_string(); @@ -316,12 +317,12 @@ pub fn lower(value: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) /// Check if a value is statically known (literal or simple structure). /// Returns true if the value can be used for compile-time macro expansion. fn is_static_value(v: &Value) -> bool { - match v { - Value::Null | Value::Bool(_) | Value::Int(_) | Value::Float(_) | Value::BigNum(_) | Value::String(_) => true, - Value::Array(arr) => arr.iter().all(is_static_value), - Value::Object(obj) => { + match v.view() { + NanView::Null | NanView::Bool(_) | NanView::Int(_) | NanView::Float(_) | NanView::BigNum(_) | NanView::String(_) => true, + NanView::Array(ha) => ha.data.iter().all(is_static_value), + NanView::Object(ho) => { // Allow static objects and calls with static args - obj.iter().all(|(_, v)| is_static_value(v)) + ho.iter().all(|(_, v)| is_static_value(v)) } } } @@ -467,7 +468,7 @@ fn lower_macro_expand( _ => { ctx.warnings.push(format!("Unknown macro: {} at {}", name, ptr)); - IrNode::new(IrKind::Lit(Value::Null), JestType::Null) + IrNode::new(IrKind::Lit(Value::null()), JestType::Null) } } } @@ -479,16 +480,16 @@ fn lower_fn_sugar(arg: &Value, ptr: &JestPointer, ctx: &mut IrContext) -> IrNode /// Core closure lowering shared by fn and macro. fn lower_closure(arg: &Value, ptr: &JestPointer, kind: FnKind, ctx: &mut IrContext) -> IrNode { - let arr = match arg { - Value::Array(arr) if arr.len() == 2 => arr, + let ha = match arg.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => { ctx.warnings.push(format!("Malformed fn at {}", ptr)); - return IrNode::new(IrKind::Lit(Value::Null), JestType::Null); + return IrNode::new(IrKind::Lit(Value::null()), JestType::Null); } }; - let formals_val = &arr[0]; - let body_val = &arr[1]; + let formals_val = &ha.data[0]; + let body_val = &ha.data[1]; let param_names = pattern_bound_names_ordered(formals_val); @@ -542,8 +543,8 @@ fn lower_defn_standalone( // Parse as defn and expand to def + fn match parse_defn(arg) { Ok(parsed) => { - let fn_body = match arg { - Value::Array(arr) if arr.len() == 3 => { + let fn_body = match arg.view() { + NanView::Array(ha) if ha.data.len() == 3 => { if name == "defmacro" { lower_macro_sugar( &build_array_2(parsed.formals_val.clone(), parsed.body.clone(), ctx), @@ -558,7 +559,7 @@ fn lower_defn_standalone( ) } } - _ => IrNode::new(IrKind::Lit(Value::Null), JestType::Null), + _ => IrNode::new(IrKind::Lit(Value::null()), JestType::Null), }; let bind_id = ctx.fresh_id(&parsed.bind_name, fn_body.ty.clone()); @@ -578,36 +579,36 @@ fn lower_defn_standalone( } Err(msg) => { ctx.warnings.push(format!("Malformed defn at {}: {}", ptr, msg)); - IrNode::new(IrKind::Lit(Value::Null), JestType::Null) + IrNode::new(IrKind::Lit(Value::null()), JestType::Null) } } } /// Lower `{"let*": [bindings, body]}` to nested Let nodes. fn lower_let_star(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) -> IrNode { - let arr = match arg { - Value::Array(arr) if arr.len() == 2 => arr, + let ha = match arg.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => { ctx.warnings.push(format!("Malformed let* at {}", ptr)); - return IrNode::new(IrKind::Lit(Value::Null), JestType::Null); + return IrNode::new(IrKind::Lit(Value::null()), JestType::Null); } }; - let bindings = &arr[0]; - let body = &arr[1]; + let bindings = &ha.data[0]; + let body = &ha.data[1]; // bindings can be: // - object {"name": expr, ...} (let form) // - array [["name", expr], ...] (let* form) - let binding_pairs: Vec<(String, Value)> = match bindings { - Value::Object(obj) => obj.iter().map(|(k, v)| (k.to_string(), v.clone())).collect(), - Value::Array(arr) => { - arr.iter().filter_map(|entry| { - if let Value::Array(pair) = entry - && pair.len() == 2 - && let Value::String(name) = &pair[0] + let binding_pairs: Vec<(String, Value)> = match bindings.view() { + NanView::Object(ho) => ho.iter().map(|(k, v)| (k.to_string(), v.clone())).collect(), + NanView::Array(ha) => { + ha.data.iter().filter_map(|entry| { + if let NanView::Array(pair) = entry.view() + && pair.data.len() == 2 + && let NanView::String(name) = pair.data[0].view() { - Some((name.to_string(), pair[1].clone())) + Some((name.to_string(), pair.data[1].clone())) } else { None } @@ -615,7 +616,7 @@ fn lower_let_star(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContex } _ => { ctx.warnings.push(format!("let/let* bindings must be an object or array at {}", ptr)); - return IrNode::new(IrKind::Lit(Value::Null), JestType::Null); + return IrNode::new(IrKind::Lit(Value::null()), JestType::Null); } }; @@ -655,26 +656,26 @@ fn lower_let_star(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContex /// Lower `{"and": [args...]}` to if-chain. fn lower_and(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) -> IrNode { - let args = match arg { - Value::Array(arr) => arr.as_ref(), - _ => return IrNode::new(IrKind::Lit(Value::Bool(true)), JestType::Bool(Some(true))), + let args = match arg.view() { + NanView::Array(ha) => ha, + _ => return IrNode::new(IrKind::Lit(Value::bool(true)), JestType::Bool(Some(true))), }; - if args.is_empty() { - return IrNode::new(IrKind::Lit(Value::Bool(true)), JestType::Bool(Some(true))); + if args.data.is_empty() { + return IrNode::new(IrKind::Lit(Value::bool(true)), JestType::Bool(Some(true))); } - if args.len() == 1 { - return lower(&args[0], &ptr.push_index(0), tail, ctx); + if args.data.len() == 1 { + return lower(&args.data[0], &ptr.push_index(0), tail, ctx); } // and(a, b, c) → if a then (if b then c else false) else false - let last = lower(args.last().unwrap(), &ptr.push_index(args.len() - 1), tail, ctx); - let false_node = IrNode::new(IrKind::Lit(Value::Bool(false)), JestType::Bool(Some(false))); + let last = lower(args.data.last().unwrap(), &ptr.push_index(args.data.len() - 1), tail, ctx); + let false_node = IrNode::new(IrKind::Lit(Value::bool(false)), JestType::Bool(Some(false))); let mut result = last; - for i in (0..args.len() - 1).rev() { - let cond = lower(&args[i], &ptr.push_index(i), false, ctx); + for i in (0..args.data.len() - 1).rev() { + let cond = lower(&args.data[i], &ptr.push_index(i), false, ctx); let ty = result.ty.clone(); result = IrNode::new( IrKind::Branch { @@ -691,30 +692,30 @@ fn lower_and(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) -> /// Lower `{"or": [args...]}` to if-chain. fn lower_or(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) -> IrNode { - let args = match arg { - Value::Array(arr) => arr.as_ref(), - _ => return IrNode::new(IrKind::Lit(Value::Bool(false)), JestType::Bool(Some(false))), + let args = match arg.view() { + NanView::Array(ha) => ha, + _ => return IrNode::new(IrKind::Lit(Value::bool(false)), JestType::Bool(Some(false))), }; - if args.is_empty() { - return IrNode::new(IrKind::Lit(Value::Bool(false)), JestType::Bool(Some(false))); + if args.data.is_empty() { + return IrNode::new(IrKind::Lit(Value::bool(false)), JestType::Bool(Some(false))); } - if args.len() == 1 { - return lower(&args[0], &ptr.push_index(0), tail, ctx); + if args.data.len() == 1 { + return lower(&args.data[0], &ptr.push_index(0), tail, ctx); } // or(a, b, c) → let t = a; if t then t else (let t = b; if t then t else c) // Simplified: since we don't have short-circuit eval yet, just use Branch - let last = lower(args.last().unwrap(), &ptr.push_index(args.len() - 1), tail, ctx); + let last = lower(args.data.last().unwrap(), &ptr.push_index(args.data.len() - 1), tail, ctx); let mut result = last; - for i in (0..args.len() - 1).rev() { - let cond = lower(&args[i], &ptr.push_index(i), false, ctx); + for i in (0..args.data.len() - 1).rev() { + let cond = lower(&args.data[i], &ptr.push_index(i), false, ctx); let ty = result.ty.clone(); // In a full implementation, we'd bind the cond to avoid double-eval. // For now, duplicate the cond in both positions. - let cond_clone = lower(&args[i], &ptr.push_index(i), false, ctx); + let cond_clone = lower(&args.data[i], &ptr.push_index(i), false, ctx); result = IrNode::new( IrKind::Branch { cond: Box::new(cond), @@ -762,20 +763,20 @@ fn lower_control_flow( /// Lower `{"if": [cond, then, else]}`. fn lower_if(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) -> IrNode { - let arr = match arg { - Value::Array(arr) => arr, + let ha = match arg.view() { + NanView::Array(ha) => ha, _ => { ctx.warnings.push(format!("if requires an array arg at {}", ptr)); - return IrNode::new(IrKind::Lit(Value::Null), JestType::Null); + return IrNode::new(IrKind::Lit(Value::null()), JestType::Null); } }; - let (cond_val, then_val, else_val) = match arr.len() { - 2 => (&arr[0], &arr[1], None), - 3 => (&arr[0], &arr[1], Some(&arr[2])), + let (cond_val, then_val, else_val) = match ha.data.len() { + 2 => (&ha.data[0], &ha.data[1], None), + 3 => (&ha.data[0], &ha.data[1], Some(&ha.data[2])), _ => { ctx.warnings.push(format!("if requires 2 or 3 args at {}", ptr)); - return IrNode::new(IrKind::Lit(Value::Null), JestType::Null); + return IrNode::new(IrKind::Lit(Value::null()), JestType::Null); } }; @@ -803,7 +804,7 @@ fn lower_if(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) -> } let else_ir = match else_val { Some(e) => lower(e, &ptr.push_index(2), tail, ctx), - None => IrNode::new(IrKind::Lit(Value::Null), JestType::Null), + None => IrNode::new(IrKind::Lit(Value::null()), JestType::Null), }; if narrowing.is_some() { ctx.pop_narrowing(); @@ -829,15 +830,15 @@ fn extract_predicate_narrowing(cond: &Value) -> Option<(String, JestType, JestTy // pred_arg is either: // {"var": "x"} (bare operative call) // [{"var": "x"}] (applicative call with 1-element array) - let var_val = match pred_arg { - Value::Array(arr) if arr.len() == 1 => &arr[0], + let var_val = match pred_arg.view() { + NanView::Array(ha) if ha.data.len() == 1 => &ha.data[0], _ => pred_arg, }; let (var_key, var_arg) = var_val.as_call()?; if var_key != "var" { return None; } - let var_name = match var_arg { - Value::String(s) => s.to_string(), + let var_name = match var_arg.view() { + NanView::String(s) => s.to_string(), _ => return None, }; @@ -884,16 +885,17 @@ fn extract_predicate_narrowing(cond: &Value) -> Option<(String, JestType, JestTy /// Lower `{"do": [stmts...]}`. fn lower_do(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) -> IrNode { - let stmts_val = match arg { - Value::Array(arr) => arr.as_ref(), + let stmts_ha = match arg.view() { + NanView::Array(ha) => ha, _ => { ctx.warnings.push(format!("do requires an array arg at {}", ptr)); - return IrNode::new(IrKind::Lit(Value::Null), JestType::Null); + return IrNode::new(IrKind::Lit(Value::null()), JestType::Null); } }; + let stmts_val = &stmts_ha.data; if stmts_val.is_empty() { - return IrNode::new(IrKind::Lit(Value::Null), JestType::Null); + return IrNode::new(IrKind::Lit(Value::null()), JestType::Null); } ctx.push_scope(); @@ -1003,21 +1005,23 @@ fn lower_do(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) -> } DoStmtKind::Deftype(arg) => { - if let Value::Array(arr) = arg - && let Some(Value::String(name)) = arr.first() { + if let NanView::Array(ha) = arg.view() + && let Some(first) = ha.data.first() + && let NanView::String(name) = first.view() { ir_stmts.push(IrDoStmt::DefType { name: name.to_string(), raw_args: arg.clone() }); } } DoStmtKind::Defgeneric(arg) => { - if let Value::Array(arr) = arg - && arr.len() == 2 - && let Some(Value::String(name)) = arr.first() + if let NanView::Array(ha) = arg.view() + && ha.data.len() == 2 + && let Some(first) = ha.data.first() + && let NanView::String(name) = first.view() { - let arity = match arr.get(1) { - Some(Value::Int(i)) => usize::try_from(*i).unwrap_or(0), - Some(Value::Float(f)) => *f as usize, - Some(Value::BigNum(n)) => match &**n { + let arity = match ha.data.get(1).map(|v| v.view()) { + Some(NanView::Int(i)) => usize::try_from(i).unwrap_or(0), + Some(NanView::Float(f)) => f as usize, + Some(NanView::BigNum(n)) => match n { crate::runtime::Number::Integer(i) => i.to_usize().unwrap_or(0), crate::runtime::Number::Float(f) => f.to_u64().map(|v| v as usize).unwrap_or(0), }, @@ -1058,16 +1062,16 @@ fn lower_do(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) -> /// Lower `{"match": [scrutinee, [[pat, body], ...]]}`. fn lower_match(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) -> IrNode { - let arr = match arg { - Value::Array(arr) if arr.len() == 2 => arr, + let ha = match arg.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => { ctx.warnings.push(format!("match requires [scrutinee, clauses] at {}", ptr)); - return IrNode::new(IrKind::Lit(Value::Null), JestType::Null); + return IrNode::new(IrKind::Lit(Value::null()), JestType::Null); } }; - let scrutinee_val = &arr[0]; - let clauses_val = &arr[1]; + let scrutinee_val = &ha.data[0]; + let clauses_val = &ha.data[1]; // Lower scrutinee let scrutinee_ir = lower(scrutinee_val, &ptr.push_index(0), false, ctx); @@ -1077,22 +1081,22 @@ fn lower_match(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) let scrut_id = ctx.fresh_id("_match_scrut", scrutinee_ty.clone()); // Parse clauses - let clauses_arr = match clauses_val { - Value::Array(arr) => arr, + let clauses_ha = match clauses_val.view() { + NanView::Array(ha) => ha, _ => { ctx.warnings.push(format!("match clauses must be an array at {}", ptr)); - return IrNode::new(IrKind::Lit(Value::Null), JestType::Null); + return IrNode::new(IrKind::Lit(Value::null()), JestType::Null); } }; let mut compiled_clauses: Vec<(Value, IrNode)> = Vec::new(); - for (i, clause) in clauses_arr.iter().enumerate() { - if let Value::Array(pair) = clause - && pair.len() == 2 { - let pat = pair[0].clone(); + for (i, clause) in clauses_ha.data.iter().enumerate() { + if let NanView::Array(pair) = clause.view() + && pair.data.len() == 2 { + let pat = pair.data[0].clone(); let body_ir = { ctx.push_scope(); - let b = lower(&pair[1], &ptr.push_index(1).push_index(i).push_index(1), tail, ctx); + let b = lower(&pair.data[1], &ptr.push_index(1).push_index(i).push_index(1), tail, ctx); ctx.pop_scope(); b }; @@ -1126,23 +1130,23 @@ fn lower_match(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) /// Lower `{"letrec": [defs, body]}`. fn lower_letrec(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) -> IrNode { - let arr = match arg { - Value::Array(arr) if arr.len() == 2 => arr, + let ha = match arg.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => { ctx.warnings.push(format!("letrec requires [defs, body] at {}", ptr)); - return IrNode::new(IrKind::Lit(Value::Null), JestType::Null); + return IrNode::new(IrKind::Lit(Value::null()), JestType::Null); } }; - let defs_val = &arr[0]; - let body_val = &arr[1]; + let defs_val = &ha.data[0]; + let body_val = &ha.data[1]; // defs is an object {"name": ["kind", formals, body], ...} - let defs_obj = match defs_val { - Value::Object(obj) => obj, + let defs_ho = match defs_val.view() { + NanView::Object(ho) => ho, _ => { ctx.warnings.push(format!("letrec defs must be an object at {}", ptr)); - return IrNode::new(IrKind::Lit(Value::Null), JestType::Null); + return IrNode::new(IrKind::Lit(Value::null()), JestType::Null); } }; @@ -1150,7 +1154,7 @@ fn lower_letrec(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) // First pass: register all names let mut names: Vec<(String, IrId)> = Vec::new(); - for (k, _) in defs_obj.iter() { + for (k, _) in defs_ho.iter() { let name = k.to_string(); let id = ctx.fresh_id(&name, JestType::Top); ctx.bind(&name, id); @@ -1159,28 +1163,28 @@ fn lower_letrec(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) // Second pass: lower each definition let mut ir_defs: Vec = Vec::new(); - for ((name, id), (_, def_val)) in names.iter().zip(defs_obj.iter()) { + for ((name, id), (_, def_val)) in names.iter().zip(defs_ho.iter()) { // Each RHS is a call form: {"fn": [formals, body]} or {"macro": [formals, body]} // or a pre-parsed array [kind, formals, body]. let (kind_str, formals_val_owned, body_val_owned); if let Some((call_key, call_arg)) = def_val.as_call() { kind_str = call_key.to_string(); - match call_arg { - Value::Array(arr) if arr.len() >= 2 => { - formals_val_owned = arr[0].clone(); - body_val_owned = arr[1].clone(); + match call_arg.view() { + NanView::Array(ha) if ha.data.len() >= 2 => { + formals_val_owned = ha.data[0].clone(); + body_val_owned = ha.data[1].clone(); } _ => continue, } - } else if let Value::Array(def_arr) = def_val - && def_arr.len() >= 2 + } else if let NanView::Array(def_ha) = def_val.view() + && def_ha.data.len() >= 2 { - kind_str = match &def_arr[0] { - Value::String(s) => s.to_string(), + kind_str = match def_ha.data[0].view() { + NanView::String(s) => s.to_string(), _ => "fn".to_string(), }; - formals_val_owned = def_arr[1].clone(); - body_val_owned = if def_arr.len() >= 3 { def_arr[2].clone() } else { Value::Null }; + formals_val_owned = def_ha.data[1].clone(); + body_val_owned = if def_ha.data.len() >= 3 { def_ha.data[2].clone() } else { Value::null() }; } else { continue; } @@ -1235,28 +1239,28 @@ fn lower_letrec(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) /// Lower `{"try": [expr, handler?, budget?]}`. fn lower_try(arg: &Value, ptr: &JestPointer, ctx: &mut IrContext) -> IrNode { - let arr = match arg { - Value::Array(arr) => arr, + let ha = match arg.view() { + NanView::Array(ha) => ha, _ => { ctx.warnings.push(format!("try requires an array arg at {}", ptr)); - return IrNode::new(IrKind::Lit(Value::Null), JestType::Null); + return IrNode::new(IrKind::Lit(Value::null()), JestType::Null); } }; - let expr_ir = if !arr.is_empty() { - lower(&arr[0], &ptr.push_index(0), false, ctx) // NOT tail — must inspect + let expr_ir = if !ha.data.is_empty() { + lower(&ha.data[0], &ptr.push_index(0), false, ctx) // NOT tail — must inspect } else { - IrNode::new(IrKind::Lit(Value::Null), JestType::Null) + IrNode::new(IrKind::Lit(Value::null()), JestType::Null) }; - let handler_ir = if arr.len() > 1 { - Some(Box::new(lower(&arr[1], &ptr.push_index(1), false, ctx))) + let handler_ir = if ha.data.len() > 1 { + Some(Box::new(lower(&ha.data[1], &ptr.push_index(1), false, ctx))) } else { None }; - let budget_ir = if arr.len() > 2 { - Some(Box::new(lower(&arr[2], &ptr.push_index(2), false, ctx))) + let budget_ir = if ha.data.len() > 2 { + Some(Box::new(lower(&ha.data[2], &ptr.push_index(2), false, ctx))) } else { None }; @@ -1264,7 +1268,7 @@ fn lower_try(arg: &Value, ptr: &JestPointer, ctx: &mut IrContext) -> IrNode { IrNode::new( IrKind::Try { expr: Box::new(expr_ir), - handler: handler_ir.unwrap_or_else(|| Box::new(IrNode::new(IrKind::Lit(Value::Null), JestType::Null))), + handler: handler_ir.unwrap_or_else(|| Box::new(IrNode::new(IrKind::Lit(Value::null()), JestType::Null))), budget: budget_ir, }, JestType::Top, // try can return anything @@ -1278,7 +1282,7 @@ fn lower_throw(arg: &Value, ptr: &JestPointer, ctx: &mut IrContext) -> IrNode { IrNode::new( IrKind::Call { proc: Box::new(IrNode::new( - IrKind::Lit(Value::Null), // placeholder for builtin ref + IrKind::Lit(Value::null()), // placeholder for builtin ref JestType::Top, )), args: vec![expr_ir], @@ -1294,8 +1298,8 @@ fn lower_throw(arg: &Value, ptr: &JestPointer, ctx: &mut IrContext) -> IrNode { /// evaluation of that literal. With env, or with a dynamic expr, we fall /// back to InterpCall. fn lower_eval(arg: &Value, ptr: &JestPointer, ctx: &mut IrContext) -> IrNode { - let arr = match arg { - Value::Array(arr) if !arr.is_empty() => arr, + let ha = match arg.view() { + NanView::Array(ha) if !ha.data.is_empty() => ha, _ => { return IrNode::new( IrKind::InterpCall { @@ -1311,14 +1315,14 @@ fn lower_eval(arg: &Value, ptr: &JestPointer, ctx: &mut IrContext) -> IrNode { // 1-arg eval with a literal expr: lower the expression directly. // A "literal" here means a concrete JSON value — not a {"var": "x"} // reference that would need runtime resolution. - if arr.len() == 1 && !is_var_reference(&arr[0]) { - return lower(&arr[0], &ptr.push_index(0), false, ctx); + if ha.data.len() == 1 && !is_var_reference(&ha.data[0]) { + return lower(&ha.data[0], &ptr.push_index(0), false, ctx); } // 2-arg eval where expr is self-evaluating (no free variables, no calls): // the env doesn't matter, so we can lower the expr directly. - if arr.len() == 2 && is_self_evaluating(&arr[0]) { - return lower(&arr[0], &ptr.push_index(0), false, ctx); + if ha.data.len() == 2 && is_self_evaluating(&ha.data[0]) { + return lower(&ha.data[0], &ptr.push_index(0), false, ctx); } // Dynamic expr or non-trivial env — fall back to InterpCall @@ -1343,17 +1347,17 @@ fn is_var_reference(val: &Value) -> bool { /// Check if a value is self-evaluating (no free variables, no procedure calls). /// Self-evaluating values produce the same result regardless of the environment. fn is_self_evaluating(val: &Value) -> bool { - match val { - Value::Null | Value::Bool(_) | Value::Int(_) | Value::Float(_) | Value::BigNum(_) | Value::String(_) => true, - Value::Array(elems) => elems.iter().all(is_self_evaluating), - Value::Object(obj) => { + match val.view() { + NanView::Null | NanView::Bool(_) | NanView::Int(_) | NanView::Float(_) | NanView::BigNum(_) | NanView::String(_) => true, + NanView::Array(ha) => ha.data.iter().all(is_self_evaluating), + NanView::Object(ho) => { // Multi-key objects evaluate values (never calls). Singleton objects // are calls unless the key is a self-eval tag. - if obj.len() == 1 { + if ho.len() == 1 { // Singleton = potential call form → not self-evaluating false } else { - obj.iter().all(|(_, v)| is_self_evaluating(v)) + ho.iter().all(|(_, v)| is_self_evaluating(v)) } } } @@ -1365,8 +1369,8 @@ fn is_self_evaluating(val: &Value) -> bool { /// we know the arity statically and can lower each element. Otherwise, /// fall back to InterpCall. fn lower_apply(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) -> IrNode { - let arr = match arg { - Value::Array(arr) if arr.len() == 2 => arr, + let ha = match arg.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => { return IrNode::new( IrKind::InterpCall { @@ -1379,13 +1383,13 @@ fn lower_apply(arg: &Value, ptr: &JestPointer, tail: bool, ctx: &mut IrContext) } }; - let proc_val = &arr[0]; - let args_val = &arr[1]; + let proc_val = &ha.data[0]; + let args_val = &ha.data[1]; // If args is a literal array, decompose into a Call node - if let Value::Array(elems) = args_val { + if let NanView::Array(elems_ha) = args_val.view() { let proc_ir = lower(proc_val, &ptr.push_index(0), false, ctx); - let args_ir: Vec = elems + let args_ir: Vec = elems_ha.data .iter() .enumerate() .map(|(i, a)| lower(a, &ptr.push_index(1).push_index(i), false, ctx)) @@ -1431,7 +1435,7 @@ fn lower_def_standalone(arg: &Value, ptr: &JestPointer, ctx: &mut IrContext) -> } Err(msg) => { ctx.warnings.push(format!("Malformed def at {}: {}", ptr, msg)); - IrNode::new(IrKind::Lit(Value::Null), JestType::Null) + IrNode::new(IrKind::Lit(Value::null()), JestType::Null) } } } @@ -1453,7 +1457,7 @@ fn lower_defrec_standalone(arg: &Value, ptr: &JestPointer, _tail: bool, ctx: &mu } Err(msg) => { ctx.warnings.push(format!("Malformed defrec at {}: {}", ptr, msg)); - IrNode::new(IrKind::Lit(Value::Null), JestType::Null) + IrNode::new(IrKind::Lit(Value::null()), JestType::Null) } } } @@ -1522,20 +1526,20 @@ fn lower_data_construct( /// Lower `{"vau": [formals, body]}` or `{"vau": [formals, env_formal, body]}`. fn lower_vau(arg: &Value, ptr: &JestPointer, ctx: &mut IrContext) -> IrNode { - let arr = match arg { - Value::Array(arr) => arr, + let ha = match arg.view() { + NanView::Array(ha) => ha, _ => { ctx.warnings.push(format!("vau requires an array at {}", ptr)); - return IrNode::new(IrKind::Lit(Value::Null), JestType::Null); + return IrNode::new(IrKind::Lit(Value::null()), JestType::Null); } }; - let (formals_val, body_val, has_env) = match arr.len() { - 2 => (&arr[0], &arr[1], false), - 3 => (&arr[0], &arr[2], true), // [formals, env_formal, body] + let (formals_val, body_val, has_env) = match ha.data.len() { + 2 => (&ha.data[0], &ha.data[1], false), + 3 => (&ha.data[0], &ha.data[2], true), // [formals, env_formal, body] _ => { ctx.warnings.push(format!("vau requires 2 or 3 args at {}", ptr)); - return IrNode::new(IrKind::Lit(Value::Null), JestType::Null); + return IrNode::new(IrKind::Lit(Value::null()), JestType::Null); } }; @@ -1604,11 +1608,11 @@ fn lower_quote(arg: &Value, ptr: &JestPointer) -> IrNode { /// Lower `{"var": "name"}`. fn lower_var(arg: &Value, ptr: &JestPointer, ctx: &mut IrContext) -> IrNode { - let name = match arg { - Value::String(s) => s.to_string(), + let name = match arg.view() { + NanView::String(s) => s.to_string(), _ => { ctx.warnings.push(format!("var requires a string name at {}", ptr)); - return IrNode::new(IrKind::Lit(Value::Null), JestType::Null); + return IrNode::new(IrKind::Lit(Value::null()), JestType::Null); } }; @@ -1644,8 +1648,8 @@ fn lower_generic_dispatch( tail: bool, ctx: &mut IrContext, ) -> IrNode { - let args = match arg { - Value::Array(arr) => arr.as_ref(), + let args = match arg.view() { + NanView::Array(ha) => ha, // Single-arg: wrap in vec _ => { let arg_ir = lower(arg, &ptr.push_key(name), false, ctx); @@ -1681,7 +1685,7 @@ fn lower_generic_dispatch( } }; - let arg_irs: Vec = args + let arg_irs: Vec = args.data .iter() .enumerate() .map(|(i, a)| lower(a, &ptr.push_index(i), false, ctx)) @@ -2010,8 +2014,8 @@ fn lower_applicative_call( }; // Lower arguments - let args_ir: Vec = match arg { - Value::Array(arr) => arr + let args_ir: Vec = match arg.view() { + NanView::Array(ha) => ha.data .iter() .enumerate() .map(|(i, a)| lower(a, &ptr.push_index(i), false, ctx)) @@ -2104,8 +2108,8 @@ fn lower_defrec_group( /// Lower an import statement in a do block. fn lower_import_stmt(arg: &Value, ptr: &JestPointer, ctx: &mut IrContext) -> IrDoStmt { - let arr = match arg { - Value::Array(arr) => arr, + let ha = match arg.view() { + NanView::Array(ha) => ha, _ => { return IrDoStmt::Expr(IrNode::new( IrKind::InterpCall { @@ -2118,14 +2122,14 @@ fn lower_import_stmt(arg: &Value, ptr: &JestPointer, ctx: &mut IrContext) -> IrD } }; - let path = if !arr.is_empty() { - lower(&arr[0], &ptr.push_index(0), false, ctx) + let path = if !ha.data.is_empty() { + lower(&ha.data[0], &ptr.push_index(0), false, ctx) } else { - IrNode::new(IrKind::Lit(Value::Null), JestType::Null) + IrNode::new(IrKind::Lit(Value::null()), JestType::Null) }; - let (names, bindings) = if arr.len() > 1 { - parse_import_spec(&arr[1], ctx) + let (names, bindings) = if ha.data.len() > 1 { + parse_import_spec(&ha.data[1], ctx) } else { (ImportSpec::Wildcard, vec![]) }; @@ -2135,19 +2139,19 @@ fn lower_import_stmt(arg: &Value, ptr: &JestPointer, ctx: &mut IrContext) -> IrD /// Parse an import spec from the second arg of import. fn parse_import_spec(spec: &Value, ctx: &mut IrContext) -> (ImportSpec, Vec<(String, IrId)>) { - match spec { - Value::Null => (ImportSpec::Wildcard, vec![]), - Value::String(s) => { + match spec.view() { + NanView::Null => (ImportSpec::Wildcard, vec![]), + NanView::String(s) => { let name = s.to_string(); let id = ctx.fresh_id(&name, JestType::Top); ctx.bind(&name, id); (ImportSpec::Namespace(name.clone()), vec![(name, id)]) } - Value::Array(arr) => { - let names: Vec = arr + NanView::Array(ha) => { + let names: Vec = ha.data .iter() - .filter_map(|v| match v { - Value::String(s) => Some(s.to_string()), + .filter_map(|v| match v.view() { + NanView::String(s) => Some(s.to_string()), _ => None, }) .collect(); @@ -2161,13 +2165,13 @@ fn parse_import_spec(spec: &Value, ctx: &mut IrContext) -> (ImportSpec, Vec<(Str .collect(); (ImportSpec::Selective(names), bindings) } - Value::Object(obj) => { - let pairs: Vec<(String, String)> = obj + NanView::Object(ho) => { + let pairs: Vec<(String, String)> = ho .iter() .map(|(k, v)| { let local = k.to_string(); - let exported = match v { - Value::String(s) => s.to_string(), + let exported = match v.view() { + NanView::String(s) => s.to_string(), _ => local.clone(), }; (local, exported) @@ -2190,8 +2194,8 @@ fn parse_import_spec(spec: &Value, ctx: &mut IrContext) -> (ImportSpec, Vec<(Str /// Lower a defmethod statement in a do block. fn lower_defmethod_stmt(arg: &Value, ptr: &JestPointer, ctx: &mut IrContext) -> IrDoStmt { - let arr = match arg { - Value::Array(arr) if arr.len() >= 3 => arr, + let ha = match arg.view() { + NanView::Array(ha) if ha.data.len() >= 3 => ha, _ => { return IrDoStmt::Expr(IrNode::new( IrKind::InterpCall { @@ -2204,23 +2208,23 @@ fn lower_defmethod_stmt(arg: &Value, ptr: &JestPointer, ctx: &mut IrContext) -> } }; - let generic_name = match &arr[0] { - Value::String(s) => s.to_string(), + let generic_name = match ha.data[0].view() { + NanView::String(s) => s.to_string(), _ => "unknown".to_string(), }; - let type_pattern: Vec = match &arr[1] { - Value::Array(pats) => pats + let type_pattern: Vec = match ha.data[1].view() { + NanView::Array(pats_ha) => pats_ha.data .iter() - .filter_map(|p| match p { - Value::String(s) => Some(s.to_string()), + .filter_map(|p| match p.view() { + NanView::String(s) => Some(s.to_string()), _ => None, }) .collect(), _ => vec![], }; - let body_ir = lower(&arr[2], &ptr.push_index(2), false, ctx); + let body_ir = lower(&ha.data[2], &ptr.push_index(2), false, ctx); IrDoStmt::DefMethod { generic_name, @@ -2233,7 +2237,7 @@ fn lower_defmethod_stmt(arg: &Value, ptr: &JestPointer, ctx: &mut IrContext) -> fn build_array_2(a: Value, b: Value, _ctx: &IrContext) -> Value { // We need a budget for this — use a temporary one let budget = crate::runtime::Budget::new(1024); - Value::array(vec![a, b], &budget).unwrap_or(Value::Null) + Value::array(vec![a, b], &budget).unwrap_or(Value::null()) } // ── Tests ─────────────────────────────────────────────────────────────────── @@ -2254,23 +2258,23 @@ mod tests { #[test] fn lower_null() { let mut ctx = IrContext::new(); - let ir = lower(&Value::Null, &JestPointer::root(), false, &mut ctx); - assert!(matches!(ir.kind, IrKind::Lit(Value::Null))); + let ir = lower(&Value::null(), &JestPointer::root(), false, &mut ctx); + assert!(matches!(&ir.kind, IrKind::Lit(v) if matches!(v.view(), NanView::Null))); assert_eq!(ir.ty, JestType::Null); } #[test] fn lower_bool() { let mut ctx = IrContext::new(); - let ir = lower(&Value::Bool(true), &JestPointer::root(), false, &mut ctx); - assert!(matches!(ir.kind, IrKind::Lit(Value::Bool(true)))); + let ir = lower(&Value::bool(true), &JestPointer::root(), false, &mut ctx); + assert!(matches!(&ir.kind, IrKind::Lit(v) if matches!(v.view(), NanView::Bool(true)))); assert_eq!(ir.ty, JestType::Bool(Some(true))); } #[test] fn lower_integer() { let mut ctx = IrContext::new(); - let val = Value::Int(42); + let val = Value::int(42); let ir = lower(&val, &JestPointer::root(), false, &mut ctx); assert!(matches!(ir.kind, IrKind::Lit(_))); assert!(matches!(ir.ty, JestType::Integer(crate::typing::IntegerInfo::Exact(_)))); @@ -2290,7 +2294,7 @@ mod tests { fn lower_array() { let b = budget(); let mut ctx = IrContext::new(); - let val = Value::array(vec![Value::Null, Value::Bool(true)], &b).unwrap(); + let val = Value::array(vec![Value::null(), Value::bool(true)], &b).unwrap(); let ir = lower(&val, &JestPointer::root(), false, &mut ctx); assert!(matches!(ir.kind, IrKind::ArrayLit(_))); } @@ -2315,9 +2319,9 @@ mod tests { // {"if": [true, 1, 2]} let args = Value::array( vec![ - Value::Bool(true), - Value::Int(1), - Value::Int(2), + Value::bool(true), + Value::int(1), + Value::int(2), ], &b, ).unwrap(); @@ -2334,7 +2338,7 @@ mod tests { let def_args = Value::array( vec![ Value::string("x", &b).unwrap(), - Value::Int(1), + Value::int(1), ], &b, ).unwrap(); @@ -2367,9 +2371,9 @@ mod tests { let arr = Value::array( vec![Value::array( vec![ - Value::Int(1), - Value::Int(2), - Value::Int(3), + Value::int(1), + Value::int(2), + Value::int(3), ], &b, ).unwrap()], @@ -2387,8 +2391,8 @@ mod tests { // {"+": [1, 2]} — specialized to runtime/add-ii let args = Value::array( vec![ - Value::Int(1), - Value::Int(2), + Value::int(1), + Value::int(2), ], &b, ).unwrap(); @@ -2422,7 +2426,7 @@ mod tests { // {"f": [1]} in tail position let args = Value::array( - vec![Value::Int(1)], + vec![Value::int(1)], &b, ).unwrap(); let val = Value::call("f", args, &b).unwrap(); @@ -2436,7 +2440,7 @@ mod tests { let b = budget(); let mut ctx = IrContext::new(); // {"===": [a, b]} — irreducibly interpretive - let args = Value::array(vec![Value::Null, Value::Null], &b).unwrap(); + let args = Value::array(vec![Value::null(), Value::null()], &b).unwrap(); let val = Value::call("===", args, &b).unwrap(); let ir = lower(&val, &JestPointer::root(), false, &mut ctx); assert!(matches!(ir.kind, IrKind::InterpCall { .. })); @@ -2446,7 +2450,7 @@ mod tests { fn lower_pattern() { let b = budget(); let mut ctx = IrContext::new(); - let inner = Value::array(vec![Value::Null, Value::Bool(true)], &b).unwrap(); + let inner = Value::array(vec![Value::null(), Value::bool(true)], &b).unwrap(); let val = Value::call("pattern", inner.clone(), &b).unwrap(); let ir = lower(&val, &JestPointer::root(), false, &mut ctx); assert!(matches!(ir.kind, IrKind::Lit(_))); @@ -2484,7 +2488,7 @@ mod tests { let b = budget(); let mut ctx = IrContext::new(); // {"count": [[1, 2, 3]]} - let inner_arr = Value::array(vec![Value::Int(1), Value::Int(2), Value::Int(3)], &b).unwrap(); + let inner_arr = Value::array(vec![Value::int(1), Value::int(2), Value::int(3)], &b).unwrap(); let args = Value::array(vec![inner_arr], &b).unwrap(); let val = Value::call("count", args, &b).unwrap(); let ir = lower(&val, &JestPointer::root(), false, &mut ctx); @@ -2502,7 +2506,7 @@ mod tests { let mut ctx = IrContext::new(); // {"first": [[1, "hi", 3]]} let inner_arr = Value::array( - vec![Value::Int(1), Value::string("hi", &b).unwrap(), Value::Int(3)], + vec![Value::int(1), Value::string("hi", &b).unwrap(), Value::int(3)], &b, ).unwrap(); let args = Value::array(vec![inner_arr], &b).unwrap(); @@ -2516,7 +2520,7 @@ mod tests { let b = budget(); let mut ctx = IrContext::new(); // {"rest": [[1, 2, 3]]} - let inner_arr = Value::array(vec![Value::Int(1), Value::Int(2), Value::Int(3)], &b).unwrap(); + let inner_arr = Value::array(vec![Value::int(1), Value::int(2), Value::int(3)], &b).unwrap(); let args = Value::array(vec![inner_arr], &b).unwrap(); let val = Value::call("rest", args, &b).unwrap(); let ir = lower(&val, &JestPointer::root(), false, &mut ctx); @@ -2534,8 +2538,8 @@ mod tests { let b = budget(); let mut ctx = IrContext::new(); // {"cons": [0, [1, 2]]} - let inner_arr = Value::array(vec![Value::Int(1), Value::Int(2)], &b).unwrap(); - let args = Value::array(vec![Value::Int(0), inner_arr], &b).unwrap(); + let inner_arr = Value::array(vec![Value::int(1), Value::int(2)], &b).unwrap(); + let args = Value::array(vec![Value::int(0), inner_arr], &b).unwrap(); let val = Value::call("cons", args, &b).unwrap(); let ir = lower(&val, &JestPointer::root(), false, &mut ctx); match &ir.ty { @@ -2553,10 +2557,10 @@ mod tests { let mut ctx = IrContext::new(); // {"nth": [[1, "hi", 3], 1]} let inner_arr = Value::array( - vec![Value::Int(1), Value::string("hi", &b).unwrap(), Value::Int(3)], + vec![Value::int(1), Value::string("hi", &b).unwrap(), Value::int(3)], &b, ).unwrap(); - let args = Value::array(vec![inner_arr, Value::Int(1)], &b).unwrap(); + let args = Value::array(vec![inner_arr, Value::int(1)], &b).unwrap(); let val = Value::call("nth", args, &b).unwrap(); let ir = lower(&val, &JestPointer::root(), false, &mut ctx); // Element at index 1 is a string diff --git a/src/compile/mod.rs b/src/compile/mod.rs index ab7215b..143b061 100644 --- a/src/compile/mod.rs +++ b/src/compile/mod.rs @@ -25,7 +25,6 @@ pub mod eval; pub mod ir; pub mod lower; pub mod opt; -pub mod nanvalue; pub mod vm; pub use ir::*; @@ -34,7 +33,7 @@ pub use decision::{compile_clauses, DecisionCtx}; pub use eval::ir_eval; pub use lower::{lower as lower_value, IrContext}; -use crate::runtime::{Budget, Scope, Value}; +use crate::runtime::{Budget, Scope, Value, nanvalue::NanView}; use crate::typing::JestType; use crate::typing::types::{ArrayType, StringInfo}; use crate::typing::check::JestPointer; @@ -175,16 +174,16 @@ struct DerecCallSite { /// Infer the type of a literal value (for call-site arg type extraction). fn literal_type(val: &Value) -> JestType { - match val { - Value::Null => JestType::Null, - Value::Bool(b) => JestType::Bool(Some(*b)), - Value::Int(i) => JestType::Integer(crate::typing::types::IntegerInfo::Exact(crate::runtime::IntegerValue::from_i64(*i))), - Value::Float(f) => JestType::Float(Some(crate::runtime::FloatValue::from_f64(*f))), - Value::BigNum(n) => match &**n { + match val.view() { + NanView::Null => JestType::Null, + NanView::Bool(b) => JestType::Bool(Some(b)), + NanView::Int(i) => JestType::Integer(crate::typing::types::IntegerInfo::Exact(crate::runtime::IntegerValue::from_i64(i))), + NanView::Float(f) => JestType::Float(Some(crate::runtime::FloatValue::from_f64(f))), + NanView::BigNum(n) => match n { crate::runtime::Number::Integer(iv) => JestType::Integer(crate::typing::types::IntegerInfo::Exact(iv.clone())), crate::runtime::Number::Float(fv) => JestType::Float(Some(fv.clone())), }, - Value::String(s) => JestType::String(StringInfo::Exact(s.to_string())), + NanView::String(s) => JestType::String(StringInfo::Exact(s.to_string())), _ => JestType::Top, } } @@ -197,13 +196,13 @@ fn literal_type(val: &Value) -> JestType { /// if the RHS is a literal, we know the exact type. fn extract_def_literal_types(value: &Value, binding_types: &mut std::collections::HashMap) { if let Some(("do", args)) = value.as_call() { - if let Value::Array(stmts) = args { - for stmt in stmts.iter() { + if let NanView::Array(stmts) = args.view() { + for stmt in stmts.data.iter() { if let Some(("def", def_args)) = stmt.as_call() { - if let Value::Array(parts) = def_args { - if parts.len() == 2 { - if let Value::String(name) = &parts[0] { - let ty = literal_type(&parts[1]); + if let NanView::Array(parts) = def_args.view() { + if parts.data.len() == 2 { + if let NanView::String(name) = parts.data[0].view() { + let ty = literal_type(&parts.data[1]); if !matches!(ty, JestType::Top) { binding_types.insert(name.to_string(), ty); } @@ -222,16 +221,18 @@ fn extract_defrec_call_sites(value: &Value) -> Vec { let mut results = Vec::new(); if let Some(("do", args)) = value.as_call() { - if let Value::Array(stmts) = args { + if let NanView::Array(stmts) = args.view() { // First pass: collect defrec function names let mut defrec_names: Vec = Vec::new(); - for stmt in stmts.iter() { + for stmt in stmts.data.iter() { if let Some(("defrec", defrec_args)) = stmt.as_call() { - if let Value::Array(entries) = defrec_args { - for entry in entries.iter() { - if let Value::Array(parts) = entry { - if let Some(Value::String(name)) = parts.first() { - defrec_names.push(name.to_string()); + if let NanView::Array(entries) = defrec_args.view() { + for entry in entries.data.iter() { + if let NanView::Array(parts) = entry.view() { + if let Some(first) = parts.data.first() { + if let NanView::String(name) = first.view() { + defrec_names.push(name.to_string()); + } } } } @@ -240,11 +241,11 @@ fn extract_defrec_call_sites(value: &Value) -> Vec { } // Second pass: find calls to those functions - for stmt in stmts.iter() { + for stmt in stmts.data.iter() { if let Some((name, call_args)) = stmt.as_call() { if defrec_names.contains(&name.to_string()) { - if let Value::Array(args) = call_args { - let arg_types: Vec = args.iter() + if let NanView::Array(args) = call_args.view() { + let arg_types: Vec = args.data.iter() .map(literal_type) .collect(); // Only use if all args have known types @@ -271,8 +272,8 @@ fn extract_defrec_param_names(value: &Value, fn_name: &str) -> Option { // Search do-block statements for defrec - if let Value::Array(stmts) = args { - for stmt in stmts.iter() { + if let NanView::Array(stmts) = args.view() { + for stmt in stmts.data.iter() { if let Some(result) = extract_defrec_param_names(stmt, fn_name) { return Some(result); } @@ -282,19 +283,19 @@ fn extract_defrec_param_names(value: &Value, fn_name: &str) -> Option { // defrec: [[name, formals, body], ...] - if let Value::Array(entries) = args { - for entry in entries.iter() { - if let Value::Array(parts) = entry { - if parts.len() >= 3 { - if let Value::String(name) = &parts[0] { + if let NanView::Array(entries) = args.view() { + for entry in entries.data.iter() { + if let NanView::Array(parts) = entry.view() { + if parts.data.len() >= 3 { + if let NanView::String(name) = parts.data[0].view() { if &**name == fn_name { // Extract param names from formals array - if let Value::Array(formals) = &parts[1] { - let names: Vec = formals.iter() + if let NanView::Array(formals) = parts.data[1].view() { + let names: Vec = formals.data.iter() .filter_map(|f| { f.as_call().and_then(|(k, v)| { if k == "var" { - if let Value::String(s) = v { Some(s.to_string()) } else { None } + if let NanView::String(s) = v.view() { Some(s.to_string()) } else { None } } else { None } }) }) @@ -311,23 +312,27 @@ fn extract_defrec_param_names(value: &Value, fn_name: &str) -> Option { // letrec: [{name: {"fn": [formals, body]}, ...}, body_expr] - if let Value::Array(parts) = args { - if let Some(Value::Object(defs)) = parts.first() { - for (name, def_val) in defs.iter() { - if &**name == fn_name { - if let Some(("fn", fn_args)) = def_val.as_call() { - if let Value::Array(fn_parts) = fn_args { - if let Some(Value::Array(formals)) = fn_parts.first() { - let names: Vec = formals.iter() - .filter_map(|f| { - f.as_call().and_then(|(k, v)| { - if k == "var" { - if let Value::String(s) = v { Some(s.to_string()) } else { None } - } else { None } - }) - }) - .collect(); - return Some(names); + if let NanView::Array(parts) = args.view() { + if let Some(first) = parts.data.first() { + if let NanView::Object(defs) = first.view() { + for (name, def_val) in defs.iter() { + if &**name == fn_name { + if let Some(("fn", fn_args)) = def_val.as_call() { + if let NanView::Array(fn_parts) = fn_args.view() { + if let Some(formals_val) = fn_parts.data.first() { + if let NanView::Array(formals) = formals_val.view() { + let names: Vec = formals.data.iter() + .filter_map(|f| { + f.as_call().and_then(|(k, v)| { + if k == "var" { + if let NanView::String(s) = v.view() { Some(s.to_string()) } else { None } + } else { None } + }) + }) + .collect(); + return Some(names); + } + } } } } @@ -352,21 +357,21 @@ fn infer_defrec_return_types( binding_types: &mut std::collections::HashMap, ) { let stmts = match value.as_call() { - Some(("do", args)) => match args { - Value::Array(arr) => arr.as_ref(), + Some(("do", args)) => match args.view() { + NanView::Array(ha) => ha, _ => return, }, _ => return, }; - for stmt in stmts { + for stmt in stmts.data.iter() { if let Some(("defrec", args)) = stmt.as_call() { - if let Value::Array(entries) = args { - for entry in entries.iter() { - if let Value::Array(parts) = entry { - if parts.len() >= 3 { - if let Value::String(name) = &parts[0] { - if let Some(param_names) = extract_defrec_param_names(value, name) { + if let NanView::Array(entries) = args.view() { + for entry in entries.data.iter() { + if let NanView::Array(parts) = entry.view() { + if parts.data.len() >= 3 { + if let NanView::String(name) = parts.data[0].view() { + if let Some(param_names) = extract_defrec_param_names(value, &**name) { let all_integer = param_names.iter().all(|p| { matches!( binding_types.get(p), @@ -374,7 +379,7 @@ fn infer_defrec_return_types( ) }); if all_integer { - let body = &parts[2]; + let body = &parts.data[2]; let ret_ty = infer_body_return_type( body, &name.to_string(), ¶m_names, binding_types, ); @@ -416,7 +421,7 @@ fn infer_body_return_type( match key { "var" => { // Variable reference: check if it's a param (Integer) or known binding - if let Value::String(name) = args { + if let NanView::String(name) = args.view() { if param_names.contains(&name.to_string()) { return JestType::Integer(crate::typing::types::IntegerInfo::Any); } @@ -428,10 +433,10 @@ fn infer_body_return_type( } "if" => { // if: return type is join of then/else branches - if let Value::Array(arr) = args { - if arr.len() == 3 { - let then_ty = infer_body_return_type(&arr[1], fn_name, param_names, binding_types); - let else_ty = infer_body_return_type(&arr[2], fn_name, param_names, binding_types); + if let NanView::Array(ha) = args.view() { + if ha.data.len() == 3 { + let then_ty = infer_body_return_type(&ha.data[1], fn_name, param_names, binding_types); + let else_ty = infer_body_return_type(&ha.data[2], fn_name, param_names, binding_types); return then_ty.join(&else_ty); } } @@ -439,10 +444,10 @@ fn infer_body_return_type( } "+" | "-" | "*" | "%" => { // Arithmetic: if both args are Integer, result is Integer - if let Value::Array(arr) = args { - if arr.len() == 2 { - let a = infer_body_return_type(&arr[0], fn_name, param_names, binding_types); - let b = infer_body_return_type(&arr[1], fn_name, param_names, binding_types); + if let NanView::Array(ha) = args.view() { + if ha.data.len() == 2 { + let a = infer_body_return_type(&ha.data[0], fn_name, param_names, binding_types); + let b = infer_body_return_type(&ha.data[1], fn_name, param_names, binding_types); if matches!(a, JestType::Integer(_)) && matches!(b, JestType::Integer(_)) { return JestType::Integer(crate::typing::types::IntegerInfo::Any); } @@ -468,15 +473,15 @@ fn infer_body_return_type( } } } else { - match body { - Value::Int(_) => JestType::Integer(crate::typing::types::IntegerInfo::Any), - Value::Float(_) => JestType::Float(None), - Value::BigNum(n) => match &**n { + match body.view() { + NanView::Int(_) => JestType::Integer(crate::typing::types::IntegerInfo::Any), + NanView::Float(_) => JestType::Float(None), + NanView::BigNum(n) => match n { crate::runtime::Number::Integer(_) => JestType::Integer(crate::typing::types::IntegerInfo::Any), crate::runtime::Number::Float(_) => JestType::Float(None), }, - Value::Bool(_) => JestType::Bool(None), - Value::Null => JestType::Null, + NanView::Bool(_) => JestType::Bool(None), + NanView::Null => JestType::Null, _ => JestType::Top, } } @@ -521,11 +526,11 @@ fn extract_dispatch_table( // Unwrap: wrap(mu([defs, identity, scope_obj, ?bytecode])) let Some(("wrap", inner)) = generic_fn.as_call() else { return vec![] }; let Some(("mu", mu_arg)) = inner.as_call() else { return vec![] }; - let Value::Array(mu_arr) = mu_arg else { return vec![] }; - if mu_arr.len() < 3 { return vec![]; } + let NanView::Array(mu_arr) = mu_arg.view() else { return vec![] }; + if mu_arr.data.len() < 3 { return vec![]; } - let defs = &mu_arr[0]; - let scope_obj = &mu_arr[2]; + let defs = &mu_arr.data[0]; + let scope_obj = &mu_arr.data[2]; // Extract scope map from scope_obj (contains _m0, _m1, etc.) let scope_map = match crate::runtime::procs::lambda::extract_scope_map(scope_obj.clone()) { @@ -534,13 +539,13 @@ fn extract_dispatch_table( }; // Extract defs object to find the match body - let Value::Object(defs_obj) = defs else { return vec![] }; + let NanView::Object(defs_obj) = defs.view() else { return vec![] }; let Some((_, entry_val)) = defs_obj.iter().next() else { return vec![] }; // Entry is ["fn", formals, body] - let Value::Array(entry_arr) = entry_val else { return vec![] }; - if entry_arr.len() < 3 { return vec![]; } - let body = &entry_arr[2]; + let NanView::Array(entry_arr) = entry_val.view() else { return vec![] }; + if entry_arr.data.len() < 3 { return vec![]; } + let body = &entry_arr.data[2]; // Body should be {"match": [scrutinee, clauses]} or {"_match-clauses": [...]} // After the mu-based generics refactor, the match body is lowered to @@ -567,17 +572,17 @@ fn extract_clauses_from_body(body: &Value) -> Option<&[Value]> { match key { "_match-clauses" => { // {"_match-clauses": [scrutinee, clauses, env]} - let Value::Array(arr) = arg else { return None }; - if arr.len() < 2 { return None; } - let Value::Array(clauses) = &arr[1] else { return None }; - Some(clauses.as_ref()) + let NanView::Array(arr) = arg.view() else { return None }; + if arr.data.len() < 2 { return None; } + let NanView::Array(clauses) = arr.data[1].view() else { return None }; + Some(&clauses.data) } "match" => { // {"match": [scrutinee, clauses]} - let Value::Array(arr) = arg else { return None }; - if arr.len() < 2 { return None; } - let Value::Array(clauses) = &arr[1] else { return None }; - Some(clauses.as_ref()) + let NanView::Array(arr) = arg.view() else { return None }; + if arr.data.len() < 2 { return None; } + let NanView::Array(clauses) = arr.data[1].view() else { return None }; + Some(&clauses.data) } _ => None, } @@ -589,16 +594,16 @@ fn extract_clause_entry( scope_map: &std::collections::HashMap, _budget: &Budget, ) -> Option { - let Value::Array(arr) = clause else { return None }; - if arr.len() != 2 { return None; } - let pattern = &arr[0]; - let body = &arr[1]; + let NanView::Array(arr) = clause.view() else { return None }; + if arr.data.len() != 2 { return None; } + let pattern = &arr.data[0]; + let body = &arr.data[1]; // Pattern must be an array of guard elements (one per arg) - let Value::Array(pat_elems) = pattern else { return None }; + let NanView::Array(pat_elems) = pattern.view() else { return None }; let mut arg_tags = Vec::new(); - for elem in pat_elems.iter() { + for elem in pat_elems.data.iter() { match guard_to_type_tag(elem) { Some(tag) => arg_tags.push(tag), None => return None, // unknown guard, skip this clause @@ -624,27 +629,27 @@ fn guard_to_type_tag(elem: &Value) -> Option { match key { "lazy-guard" => { - let Value::Array(arr) = arg else { return None }; - if arr.len() != 2 { return None; } - let pred_expr = &arr[1]; + let NanView::Array(arr) = arg.view() else { return None }; + if arr.data.len() != 2 { return None; } + let pred_expr = &arr.data[1]; // pred_expr is {"var": "int?"} — extract the predicate name let (vkey, name_val) = pred_expr.as_call()?; if vkey != "var" { return None; } - let Value::String(pred_name) = name_val else { return None; }; + let NanView::String(pred_name) = name_val.view() else { return None; }; predicate_name_to_type_tag(&**pred_name) } "guard-pattern" => { - let Value::Array(arr) = arg else { return None }; - if arr.len() != 2 { return None; } - let pred_val = &arr[1]; + let NanView::Array(arr) = arg.view() else { return None }; + if arr.data.len() != 2 { return None; } + let pred_val = &arr.data[1]; // pred_val is a {"type": {"name": "integer", ...}} value let (tkey, payload) = pred_val.as_call()?; if tkey != "type" { return None; } - let Value::Object(type_obj) = payload else { return None }; + let NanView::Object(type_obj) = payload.view() else { return None }; let name_val = type_obj.get("name")?; - let Value::String(type_name) = name_val else { return None }; + let NanView::String(type_name) = name_val.view() else { return None }; type_name_to_type_tag(&**type_name) } _ => None, @@ -692,21 +697,21 @@ fn extract_method_ref( ) -> Option<(MethodRef, Option)> { let (key, arg) = body.as_call()?; if key != "apply" { return None; } - let Value::Array(arr) = arg else { return None }; - if arr.len() != 2 { return None; } + let NanView::Array(arr) = arg.view() else { return None }; + if arr.data.len() != 2 { return None; } // First element: {"var": "_mN"} - let (vkey, name_val) = arr[0].as_call()?; + let (vkey, name_val) = arr.data[0].as_call()?; if vkey != "var" { return None; } - let Value::String(method_name) = name_val else { return None }; + let NanView::String(method_name) = name_val.view() else { return None }; let method_val = scope_map.get(&method_name.to_string())?; // Check if it's a builtin: wrap({"builtins": "add-ii"}) if let Some(("wrap", inner)) = method_val.as_call() { if let Some(("builtins", builtin_name)) = inner.as_call() { - if let Value::String(s) = builtin_name { - return Some((MethodRef::Builtin((**s).to_string()), None)); + if let NanView::String(s) = builtin_name.view() { + return Some((MethodRef::Builtin((&**s).to_string()), None)); } } } @@ -725,8 +730,8 @@ mod tests { #[test] fn compile_null() { - let (ir, _ctx) = compile(&Value::Null); - assert!(matches!(ir.kind, IrKind::Lit(Value::Null))); + let (ir, _ctx) = compile(&Value::null()); + assert!(matches!(ir.kind, IrKind::Lit(ref v) if matches!(v.view(), NanView::Null))); } #[test] @@ -736,7 +741,7 @@ mod tests { let def_args = Value::array( vec![ Value::string("x", &b).unwrap(), - Value::Int(42), + Value::int(42), ], &b, ).unwrap(); @@ -756,9 +761,9 @@ mod tests { // {"if": [true, 1, 2]} let args = Value::array( vec![ - Value::Bool(true), - Value::Int(1), - Value::Int(2), + Value::bool(true), + Value::int(1), + Value::int(2), ], &b, ).unwrap(); @@ -774,7 +779,7 @@ mod tests { // {"fn": [[{"var": "n"}], {"+": [{"var": "n"}, 1]}]} let var_n = Value::call("var", Value::string("n", &b).unwrap(), &b).unwrap(); let formals = Value::array(vec![var_n.clone()], &b).unwrap(); - let one = Value::Int(1); + let one = Value::int(1); let plus_args = Value::array(vec![var_n, one], &b).unwrap(); let body = Value::call("+", plus_args, &b).unwrap(); let fn_args = Value::array(vec![formals, body], &b).unwrap(); @@ -790,8 +795,8 @@ mod tests { // {"+": [1, 2]} — specialized to runtime/add-ii let args = Value::array( vec![ - Value::Int(1), - Value::Int(2), + Value::int(1), + Value::int(2), ], &b, ).unwrap(); @@ -818,7 +823,7 @@ mod tests { #[test] fn compile_preserves_no_warnings_for_simple_programs() { - let (_, ctx) = compile(&Value::Null); + let (_, ctx) = compile(&Value::null()); assert!(ctx.warnings.is_empty()); } diff --git a/src/compile/opt/constant_fold.rs b/src/compile/opt/constant_fold.rs index 4fee2df..50006fd 100644 --- a/src/compile/opt/constant_fold.rs +++ b/src/compile/opt/constant_fold.rs @@ -28,6 +28,7 @@ use std::collections::HashMap; use crate::runtime::{ convert::is_truthy, + nanvalue::NanView, procs::lookup_builtin, Budget, Eval, Number, Scope, Value, }; @@ -123,10 +124,10 @@ fn resolve_dispatch_to_builtin(generic_name: &str, arg_values: &[Value]) -> Opti } let type_tag = |v: &Value| -> Option<&'static str> { - match v { - Value::Int(_) => Some("i"), - Value::Float(_) => Some("f"), - Value::BigNum(n) => match &**n { + match v.view() { + NanView::Int(_) => Some("i"), + NanView::Float(_) => Some("f"), + NanView::BigNum(n) => match n { Number::Integer(_) => Some("i"), Number::Float(_) => Some("f"), }, diff --git a/src/compile/opt/mod.rs b/src/compile/opt/mod.rs index b380020..0e53ef3 100644 --- a/src/compile/opt/mod.rs +++ b/src/compile/opt/mod.rs @@ -72,7 +72,7 @@ mod tests { } fn int_val(n: i64) -> Value { - Value::Int(n) + Value::int(n) } #[allow(dead_code)] diff --git a/src/compile/tests/macro_expansion.rs b/src/compile/tests/macro_expansion.rs index 8768f78..b245b5c 100644 --- a/src/compile/tests/macro_expansion.rs +++ b/src/compile/tests/macro_expansion.rs @@ -2,6 +2,7 @@ use crate::compile::ir::IrKind; use crate::runtime::{Budget, Value}; +use crate::runtime::nanvalue::NanView; use crate::compile::compile; fn budget() -> Budget { @@ -35,7 +36,7 @@ fn test_stdlib_macro_expansion() { let var_x = Value::call("var", Value::string("x", &b).unwrap(), &b).unwrap(); let formals = Value::array(vec![var_x.clone()], &b).unwrap(); - let one = Value::Int(1); + let one = Value::int(1); let body_args = Value::array(vec![var_x, one], &b).unwrap(); let body = Value::call("+", body_args, &b).unwrap(); @@ -47,7 +48,7 @@ fn test_stdlib_macro_expansion() { let defn_stmt = Value::call("defn", defn_args, &b).unwrap(); let call_args = Value::array(vec![ - Value::Int(5), + Value::int(5), ], &b).unwrap(); let call_stmt = Value::call("add1", call_args, &b).unwrap(); @@ -85,7 +86,7 @@ fn test_user_macro_expansion() { let defmacro_stmt = Value::call("defmacro", defmacro_args, &b).unwrap(); let macro_call_args = Value::array(vec![ - Value::Int(42), + Value::int(42), ], &b).unwrap(); let macro_call = Value::call("identity-macro", macro_call_args, &b).unwrap(); @@ -121,7 +122,7 @@ fn test_user_macro_execution_matches() { let defmacro_stmt = Value::call("defmacro", defmacro_args, &b).unwrap(); let macro_call_args = Value::array(vec![ - Value::Int(42), + Value::int(42), ], &b).unwrap(); let macro_call = Value::call("identity-macro", macro_call_args, &b).unwrap(); @@ -132,8 +133,8 @@ fn test_user_macro_execution_matches() { let mut rt = crate::Runtime::new(1024 * 1024, 100_000); let result = rt.run_value(program).expect("Should execute"); - match result { - Value::Int(42) => {} + match result.view() { + NanView::Int(42) => {} _ => panic!("Expected number 42, got {:?}", result), } } diff --git a/src/compile/vm.rs b/src/compile/vm.rs index 463c1d9..6e771fa 100644 --- a/src/compile/vm.rs +++ b/src/compile/vm.rs @@ -13,7 +13,7 @@ use super::eval::{ vm_eval_decision_tree, vm_eval_dispatch, vm_eval_letrec, vm_eval_seq, vm_eval_try, }; use super::ir::FnKind; -use super::nanvalue::{NanValue, NanView}; +use crate::runtime::nanvalue::{NanValue, NanView}; use crate::runtime::convert::is_truthy; use crate::runtime::procs::dispatch_call_with_ctx; use crate::runtime::{eval_trampoline, Budget, Eval, Scope, Value}; @@ -33,9 +33,9 @@ thread_local! { /// Get an Rc pointer identity for a Value (returns 0 for non-Rc values). fn value_rc_identity(v: &Value) -> usize { - match v { - Value::Array(a) => a.rc_identity(), - Value::Object(o) => o.rc_identity(), + match v.view() { + NanView::Array(ha) => ha as *const _ as usize, + NanView::Object(ho) => ho as *const _ as usize, _ => 0, } } @@ -123,12 +123,12 @@ fn jit_compile_body( } fn extract_param_names(formals: &Value) -> Option> { - match formals { - Value::Array(arr) => { + match formals.view() { + NanView::Array(ha) => { let mut names = Vec::new(); - for item in arr.as_ref() { + for item in ha.data.iter() { if let Some(("var", name_val)) = item.as_call() { - if let Value::String(s) = name_val { + if let NanView::String(s) = name_val.view() { names.push(s.to_string()); } else { return None; @@ -302,8 +302,8 @@ impl<'a> VmWithCtx<'a> { /// Get Rc pointer identity from a Value::Object for proc identity checks. fn proc_identity(proc_val: &Value) -> usize { - match proc_val { - Value::Object(obj) => obj.rc_identity(), + match proc_val.view() { + NanView::Object(ho) => ho as *const _ as usize, _ => 0, } } @@ -405,7 +405,7 @@ impl<'a> VmWithCtx<'a> { self.set_nan(frame.dst_reg, NanValue::null()); continue; } - return Ok(Value::Null); + return Ok(Value::null()); } let op = chunk.code[self.ip]; @@ -520,9 +520,9 @@ impl<'a> VmWithCtx<'a> { Op::TypeConstruct(dst, payload_reg, name_idx) => { let payload = self.get_reg(payload_reg)?; let type_name = &chunk.constants[name_idx as usize]; - let name_str = match type_name { - Value::String(s) => { - let s_ref: &str = s.as_ref(); + let name_str = match type_name.view() { + NanView::String(s) => { + let s_ref: &str = &**s; s_ref.to_string() } _ => "unknown".to_string(), @@ -930,8 +930,8 @@ impl<'a> VmWithCtx<'a> { // Fast path: direct builtin call (bare operative) if let Some(("builtins", name_val)) = proc_val.as_call() { - if let Value::String(name) = name_val { - if let Some(f) = crate::runtime::procs::lookup_builtin(name) { + if let NanView::String(name) = name_val.view() { + if let Some(f) = crate::runtime::procs::lookup_builtin(&**name) { let eval_result = f(&args, self.budget, self.eval_limit, self.scope.clone())?; return self.resolve_eval(eval_result); } @@ -942,8 +942,8 @@ impl<'a> VmWithCtx<'a> { if let Some(("wrap", inner)) = proc_val.as_call() { // Wrapped builtin (applicative -- args already evaluated) if let Some(("builtins", name_val)) = inner.as_call() { - if let Value::String(name) = name_val { - if let Some(f) = crate::runtime::procs::lookup_builtin(name) { + if let NanView::String(name) = name_val.view() { + if let Some(f) = crate::runtime::procs::lookup_builtin(&**name) { let eval_result = f(&args, self.budget, self.eval_limit, self.scope.clone())?; return self.resolve_eval(eval_result); } @@ -952,18 +952,18 @@ impl<'a> VmWithCtx<'a> { // Wrapped vau (closure / fn -- args already evaluated by VM) if let Some(("vau", vau_arg)) = inner.as_call() { // Try bytecode fast path via closure chunk cache - if let Value::Array(vau_items) = vau_arg { - if vau_items.len() == 3 { - let body_key = value_rc_identity(&vau_items[1]); + if let NanView::Array(vau_items) = vau_arg.view() { + if vau_items.data.len() == 3 { + let body_key = value_rc_identity(&vau_items.data[1]); let cached_chunk = if body_key != 0 { CLOSURE_CHUNK_CACHE.with(|c| c.borrow().get(&body_key).cloned()) } else { None }; if let Some(body_chunk) = cached_chunk { - let scope_map = crate::runtime::procs::extract_scope_map(vau_items[2].clone())?; + let scope_map = crate::runtime::procs::extract_scope_map(vau_items.data[2].clone())?; let exec_scope = self.scope.globals().local(scope_map); - let formals = &vau_items[0]; + let formals = &vau_items.data[0]; let match_target = if crate::runtime::procs::lambda::formals_matches_array(formals) { Value::array(args, self.budget)? } else { @@ -1057,8 +1057,8 @@ impl<'a> VmWithCtx<'a> { if items.len() == 4 { // Pre-attached bytecode (4th element) - let cache_key = match &items[3] { - Value::Object(obj) => obj.rc_identity(), + let cache_key = match items[3].view() { + NanView::Object(ho) => ho as *const _ as usize, _ => return None, }; let cached = CHUNK_CACHE.with(|cache| cache.borrow().get(&cache_key).cloned()); @@ -1079,24 +1079,24 @@ impl<'a> VmWithCtx<'a> { if items.len() == 3 { // No bytecode -- try JIT compilation via MU_JIT_CACHE let defs = &items[0]; - let identity = match &items[1] { - Value::String(s) => s.to_string(), + let identity = match items[1].view() { + NanView::String(s) => s.to_string(), _ => return None, }; // Look up identity entry in defs let entry_val = if let Some((key, val)) = defs.as_call() { if key == identity { val.clone() } else { return None; } - } else if let Value::Object(obj) = defs { - obj.get(&identity)?.clone() + } else if let NanView::Object(ho) = defs.view() { + ho.get(&identity)?.clone() } else { return None; }; - let entry_arr = match &entry_val { - Value::Array(arr) if arr.len() == 3 => arr, + let entry_arr = match entry_val.view() { + NanView::Array(ha) if ha.data.len() == 3 => ha, _ => return None, }; - let formals = &entry_arr[1]; - let body = &entry_arr[2]; + let formals = &entry_arr.data[1]; + let body = &entry_arr.data[2]; let body_key = value_rc_identity(body); if body_key == 0 { return None; } diff --git a/src/core/mod.rs b/src/core/mod.rs index f33d266..89948ff 100644 --- a/src/core/mod.rs +++ b/src/core/mod.rs @@ -6,6 +6,7 @@ use std::collections::HashMap; use crate::runtime::{Budget, Eval, Scope, Value, eval_trampoline}; +use crate::runtime::nanvalue::NanView; use crate::runtime::procs::control::try_match; use crate::{Error, Result}; @@ -196,9 +197,12 @@ fn core_first( _eval_limit: &mut usize, _scope: Scope, ) -> Result { - match args { - [Value::Array(arr)] if !arr.is_empty() => Ok(Eval::Done(arr[0].clone())), - [Value::Array(_)] => Err(Error::arity("first", "non-empty array", 0)), + if args.len() != 1 { + return Err(Error::arity("first", "1 array", args.len())); + } + match args[0].view() { + NanView::Array(ha) if !ha.data.is_empty() => Ok(Eval::Done(ha.data[0].clone())), + NanView::Array(_) => Err(Error::arity("first", "non-empty array", 0)), _ => Err(Error::arity("first", "1 array", args.len())), } } @@ -209,9 +213,12 @@ fn core_rest( _eval_limit: &mut usize, _scope: Scope, ) -> Result { - match args { - [Value::Array(arr)] => { - let items: Vec = arr.iter().skip(1).cloned().collect(); + if args.len() != 1 { + return Err(Error::arity("rest", "1 array", args.len())); + } + match args[0].view() { + NanView::Array(ha) => { + let items: Vec = ha.data.iter().skip(1).cloned().collect(); Value::array(items, budget).map(Eval::Done) } _ => Err(Error::arity("rest", "1 array", args.len())), diff --git a/src/error.rs b/src/error.rs index cc02925..acdab7c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -209,12 +209,12 @@ impl Error { "type" => Value::string("arity", budget)?, "proc" => Value::string(proc, budget)?, "expected" => Value::string(expected, budget)?, - "got" => Value::Int(*got as i64), + "got" => Value::int(*got as i64), )?, Error::Type { proc, index, expected, .. } => obj!(budget; "type" => Value::string("type", budget)?, "proc" => Value::string(proc, budget)?, - "index" => Value::Int(*index as i64), + "index" => Value::int(*index as i64), "expected" => Value::string(expected, budget)?, )?, Error::BadSyntax { proc, expected, .. } => obj!(budget; @@ -253,8 +253,8 @@ impl Error { Error::Parse { message, line, column } => obj!(budget; "type" => Value::string("parse", budget)?, "message" => Value::string(message, budget)?, - "line" => Value::Int(*line as i64), - "column" => Value::Int(*column as i64), + "line" => Value::int(*line as i64), + "column" => Value::int(*column as i64), )?, Error::Throw(msg) => obj!(budget; "type" => Value::string("throw", budget)?, diff --git a/src/main.rs b/src/main.rs index d16e1f5..2b865c9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,5 @@ use jest::runtime::{Budget, Value, parse}; +use jest::runtime::nanvalue::NanView; use jest::Runtime; use rustyline::DefaultEditor; use rustyline::error::ReadlineError; @@ -89,26 +90,26 @@ fn validate_structure(value: &Value) -> Vec { } fn validate_structure_impl(value: &Value, path: &mut Vec, errors: &mut Vec) { - match value { - Value::Object(obj) => { + match value.view() { + NanView::Object(ho) => { // Check singleton call convention - if obj.len() == 1 { - let (key, args) = obj.iter().next().unwrap(); + if ho.len() == 1 { + let (key, args) = ho.iter().next().unwrap(); // Validate known forms match key.as_ref() { "def" => { path.push("def".to_string()); - if let Value::Array(arr) = args { - if arr.len() != 2 { + if let NanView::Array(ha) = args.view() { + if ha.data.len() != 2 { errors.push(StructureError { path: path.join("."), - message: format!("def requires exactly 2 elements [name, expr], got {}", arr.len()), + message: format!("def requires exactly 2 elements [name, expr], got {}", ha.data.len()), }); } // Validate children - for (i, elem) in arr.iter().enumerate() { + for (i, elem) in ha.data.iter().enumerate() { path.push(format!("[{i}]")); validate_structure_impl(elem, path, errors); path.pop(); @@ -118,15 +119,15 @@ fn validate_structure_impl(value: &Value, path: &mut Vec, errors: &mut V } "defn" => { path.push("defn".to_string()); - if let Value::Array(arr) = args { - if arr.len() != 3 { + if let NanView::Array(ha) = args.view() { + if ha.data.len() != 3 { errors.push(StructureError { path: path.join("."), - message: format!("defn requires exactly 3 elements [name, formals, body], got {}", arr.len()), + message: format!("defn requires exactly 3 elements [name, formals, body], got {}", ha.data.len()), }); } - for (i, elem) in arr.iter().enumerate() { + for (i, elem) in ha.data.iter().enumerate() { path.push(format!("[{i}]")); validate_structure_impl(elem, path, errors); path.pop(); @@ -137,15 +138,15 @@ fn validate_structure_impl(value: &Value, path: &mut Vec, errors: &mut V "fn" | "macro" => { let key_str: &str = key.as_ref(); path.push(key_str.to_string()); - if let Value::Array(arr) = args { - if arr.len() < 2 || arr.len() > 3 { + if let NanView::Array(ha) = args.view() { + if ha.data.len() < 2 || ha.data.len() > 3 { errors.push(StructureError { path: path.join("."), - message: format!("{} requires 2-3 elements [formals, body] or [formals, env, body], got {}", key_str, arr.len()), + message: format!("{} requires 2-3 elements [formals, body] or [formals, env, body], got {}", key_str, ha.data.len()), }); } - for (i, elem) in arr.iter().enumerate() { + for (i, elem) in ha.data.iter().enumerate() { path.push(format!("[{i}]")); validate_structure_impl(elem, path, errors); path.pop(); @@ -160,19 +161,19 @@ fn validate_structure_impl(value: &Value, path: &mut Vec, errors: &mut V } "defrec" => { path.push("defrec".to_string()); - if let Value::Array(arr) = args { + if let NanView::Array(ha) = args.view() { // Each element should be [name, formals, body] - for (i, func_def) in arr.iter().enumerate() { + for (i, func_def) in ha.data.iter().enumerate() { path.push(format!("[{i}]")); - if let Value::Array(def_arr) = func_def { - if def_arr.len() != 3 { + if let NanView::Array(def_ha) = func_def.view() { + if def_ha.data.len() != 3 { errors.push(StructureError { path: path.join("."), - message: format!("defrec function requires [name, formals, body], got {} elements", def_arr.len()), - + message: format!("defrec function requires [name, formals, body], got {} elements", def_ha.data.len()), + }); } - for (j, elem) in def_arr.iter().enumerate() { + for (j, elem) in def_ha.data.iter().enumerate() { path.push(format!("[{j}]")); validate_structure_impl(elem, path, errors); path.pop(); @@ -192,15 +193,15 @@ fn validate_structure_impl(value: &Value, path: &mut Vec, errors: &mut V } } else { // Multi-key object: recurse into each value - for (key, val) in obj.iter() { + for (key, val) in ho.iter() { path.push(key.to_string()); validate_structure_impl(val, path, errors); path.pop(); } } } - Value::Array(arr) => { - for (i, elem) in arr.iter().enumerate() { + NanView::Array(ha) => { + for (i, elem) in ha.data.iter().enumerate() { path.push(format!("[{i}]")); validate_structure_impl(elem, path, errors); path.pop(); @@ -360,7 +361,7 @@ fn run_repl(trace_config: TraceConfig) { match eval_result { Ok(result) => { // Suppress null — it's the silent return from define/defn/defmacro. - if !matches!(result, Value::Null) { + if !matches!(result.view(), NanView::Null) { println!("{}", result.to_json_pretty()); } rl.add_history_entry(&input).ok(); @@ -399,7 +400,7 @@ fn run_file(path: &str, json_args: Option<&str>) { // Inject program arguments as `args` in the initial scope. let args_val: Value = json_args .map(|s| parse::parse_json(s, &budget).expect("args must be valid JSON")) - .unwrap_or(Value::Null); + .unwrap_or(Value::null()); let dir = std::path::Path::new(path) .parent() @@ -627,14 +628,14 @@ fn cmd_test(path: &str) { let mut test_results = Vec::new(); fn extract_tests(val: &Value, results: &mut Vec) { - match val { - Value::Array(arr) => { - for item in arr.iter() { + match val.view() { + NanView::Array(ha) => { + for item in ha.data.iter() { extract_tests(item, results); } } - Value::Object(obj) => { - if obj.get("test-result").is_some() { + NanView::Object(ho) => { + if ho.get("test-result").is_some() { results.push(val.clone()); } } @@ -656,28 +657,34 @@ fn cmd_test(path: &str) { let mut failed = 0; for test_result in &test_results { - if let Value::Object(obj) = test_result - && let Some(Value::Object(inner)) = obj.get("test-result") - { - let name = match inner.get("name") { - Some(Value::String(s)) => (**s).to_string(), - Some(v) => v.to_string(), - None => "unknown".to_string(), - }; - let is_passed = matches!(inner.get("passed"), Some(Value::Bool(true))); - - if is_passed { - println!("✅ {name}"); - passed += 1; - } else { - println!("❌ {name}"); - failed += 1; - - if let (Some(actual), Some(expected)) = (inner.get("actual"), inner.get("expected")) { - println!(" Expected: {}", expected.to_json_pretty()); - println!(" Actual: {}", actual.to_json_pretty()); + if let NanView::Object(obj) = test_result.view() { + if let Some(inner_val) = obj.get("test-result") { + if let NanView::Object(inner) = inner_val.view() { + let name = match inner.get("name") { + Some(v) => match v.view() { + NanView::String(s) => s.to_string(), + _ => v.to_string(), + }, + None => "unknown".to_string(), + }; + let is_passed = inner.get("passed") + .map(|v| matches!(v.view(), NanView::Bool(true))) + .unwrap_or(false); + + if is_passed { + println!("✅ {name}"); + passed += 1; + } else { + println!("❌ {name}"); + failed += 1; + + if let (Some(actual), Some(expected)) = (inner.get("actual"), inner.get("expected")) { + println!(" Expected: {}", expected.to_json_pretty()); + println!(" Actual: {}", actual.to_json_pretty()); + } } } + } } } diff --git a/src/runtime/cek.rs b/src/runtime/cek.rs index 5ead834..0abcf74 100644 --- a/src/runtime/cek.rs +++ b/src/runtime/cek.rs @@ -195,27 +195,29 @@ impl Machine { budget: &Budget, eval_limit: &mut usize, ) -> crate::Result { + use super::nanvalue::NanView; + if *eval_limit == 0 { return Err(crate::Error::Runtime(RuntimeError::EvalLimit)); } *eval_limit -= 1; // Take focus by replacing with Null (we'll set it to something else before continuing) - let value = std::mem::replace(&mut self.focus, Value::Null); + let value = std::mem::replace(&mut self.focus, Value::null()); - match value { + match value.view() { // ── Primitives: self-evaluating ────────────────────────────── - Value::Null | Value::Bool(_) | Value::Int(_) | Value::Float(_) - | Value::BigNum(_) | Value::String(_) => { + NanView::Null | NanView::Bool(_) | NanView::Int(_) | NanView::Float(_) + | NanView::BigNum(_) | NanView::String(_) => { self.ret(value, budget, eval_limit) } // ── Arrays: push ArrayCollect, focus on first element ──────── - Value::Array(ref arr) if arr.is_empty() => { + NanView::Array(ha) if ha.data.is_empty() => { self.ret(value, budget, eval_limit) } - Value::Array(arr) => { - let mut items: Vec = arr.iter().cloned().collect(); + NanView::Array(ha) => { + let mut items: Vec = ha.data.iter().cloned().collect(); let first = items.remove(0); let scope = self.env.clone(); self.eval(first, scope.clone(), Cont::ArrayCollect { @@ -227,9 +229,9 @@ impl Machine { } // ── Objects ────────────────────────────────────────────────── - Value::Object(ref obj) if obj.len() == 1 => { + NanView::Object(ho) if ho.len() == 1 => { // We need `value` intact for self-eval returns, so we work via ref. - let (key, arg) = obj.iter().next().unwrap(); + let (key, arg) = ho.iter().next().unwrap(); let key_ref: &str = key; // 1. Self-eval tags @@ -244,8 +246,8 @@ impl Machine { // Check for wrap — handle arg eval in the CEK loop if let Some(("wrap", inner)) = proc_val.as_call() { - let args: Vec = match arg { - Value::Array(ref arr) => arr.iter().cloned().collect(), + let args: Vec = match arg.view() { + NanView::Array(ha) => ha.data.iter().cloned().collect(), _ => vec![arg], }; @@ -275,10 +277,10 @@ impl Machine { // Non-wrap dispatch (operatives, builtins, mu, type) // Fast path: bare builtin operative — call directly if let Some(("builtins", name_val)) = proc_val.as_call() { - if let Value::String(name) = name_val { - if let Some(f) = procs::lookup_builtin(name) { - let args: Vec = match arg { - Value::Array(ref arr) => arr.iter().cloned().collect(), + if let NanView::String(name) = name_val.view() { + if let Some(f) = procs::lookup_builtin(&**name) { + let args: Vec = match arg.view() { + NanView::Array(ha) => ha.data.iter().cloned().collect(), _ => vec![arg], }; let result = f(&args, budget, eval_limit, scope)?; @@ -288,8 +290,8 @@ impl Machine { } // Fast path: mu — call_mu directly without dispatch_call overhead if let Some(("mu", mu_arg)) = proc_val.as_call() { - let args: Vec = match arg { - Value::Array(ref arr) => arr.iter().cloned().collect(), + let args: Vec = match arg.view() { + NanView::Array(ha) => ha.data.iter().cloned().collect(), _ => vec![arg], }; let result = procs::call_mu_public( @@ -297,8 +299,8 @@ impl Machine { )?; return self.handle_eval(result, budget, eval_limit); } - let args: Vec = match arg { - Value::Array(ref arr) => arr.iter().cloned().collect(), + let args: Vec = match arg.view() { + NanView::Array(ha) => ha.data.iter().cloned().collect(), _ => vec![arg], }; let result = procs::dispatch_call( @@ -334,8 +336,8 @@ impl Machine { } // ── Multi-key objects: push ObjectCollect ──────────────────── - Value::Object(ref obj) => { - let mut pairs: Vec<(RStr, Value)> = obj.iter() + NanView::Object(ho) => { + let mut pairs: Vec<(RStr, Value)> = ho.iter() .map(|(k, v)| (k.clone(), v.clone())) .collect(); if pairs.is_empty() { @@ -374,8 +376,8 @@ fn dispatch_inner( evaled_args, budget, eval_limit, scope, call_name, None, ) } else if let Some(("builtins", name)) = inner.as_call() { - if let Value::String(s) = name { - let name_str: &str = s; + if let super::nanvalue::NanView::String(s) = name.view() { + let name_str: &str = &**s; match procs::lookup_builtin(name_str) { Some(f) => f(evaled_args, budget, eval_limit, scope), None => Err(crate::Error::MalformedFnValue(format!( diff --git a/src/runtime/convert.rs b/src/runtime/convert.rs index 6f1c399..8fc2a4e 100644 --- a/src/runtime/convert.rs +++ b/src/runtime/convert.rs @@ -1,4 +1,5 @@ use crate::runtime::data::String as RStr; +use crate::runtime::nanvalue::NanView; use crate::runtime::{Budget, Number, Value}; // ── from_input: &dyn JestInput → Value ──────────────────────────────────────── @@ -10,8 +11,8 @@ use crate::runtime::{Budget, Number, Value}; pub(crate) fn from_input(v: &dyn crate::JestInput, budget: &Budget) -> crate::Result { use crate::ValueKind; match v.kind() { - ValueKind::Null => Ok(Value::Null), - ValueKind::Bool(b) => Ok(Value::Bool(b)), + ValueKind::Null => Ok(Value::null()), + ValueKind::Bool(b) => Ok(Value::bool(b)), ValueKind::Number(n) => Ok(Value::from_number(n)), ValueKind::String(s) => Value::string(s, budget), ValueKind::Array(it) => { @@ -44,15 +45,15 @@ pub(crate) fn from_input(v: &dyn crate::JestInput, budget: &Budget) -> crate::Re /// Note: Object key order reflects the runtime's internal iteration order; /// `JestOutput` implementations should treat key order as unspecified. pub(crate) fn to_output(v: Value) -> O { - match v { - Value::Null => O::from_null(), - Value::Bool(b) => O::from_bool(b), - Value::Int(i) => O::from_number(Number::int(i)), - Value::Float(f) => O::from_number(Number::Float(crate::runtime::number::FloatValue::from_f64(f))), - Value::BigNum(n) => O::from_number(*n), - Value::String(s) => O::from_string(&s), - Value::Array(arr) => O::from_array(arr.iter().map(|v| to_output(v.clone())).collect()), - Value::Object(obj) => O::from_object( + match v.view() { + NanView::Null => O::from_null(), + NanView::Bool(b) => O::from_bool(b), + NanView::Int(i) => O::from_number(Number::int(i)), + NanView::Float(f) => O::from_number(Number::Float(crate::runtime::number::FloatValue::from_f64(f))), + NanView::BigNum(n) => O::from_number(n.clone()), + NanView::String(s) => O::from_string(&**s), + NanView::Array(arr) => O::from_array(arr.data.iter().map(|v| to_output(v.clone())).collect()), + NanView::Object(obj) => O::from_object( obj.iter() .map(|(k, v)| ((*k).to_string(), to_output(v.clone()))) .collect(), @@ -63,16 +64,16 @@ pub(crate) fn to_output(v: Value) -> O { // ── is_truthy ───────────────────────────────────────────────────────────────── pub(crate) fn is_truthy(v: &Value) -> bool { - match v { - Value::Null => false, - Value::Bool(false) => false, - Value::Int(0) => false, - Value::Float(f) if *f == 0.0 => false, - Value::BigNum(n) => match &**n { + match v.view() { + NanView::Null => false, + NanView::Bool(false) => false, + NanView::Int(0) => false, + NanView::Float(f) if f == 0.0 => false, + NanView::BigNum(n) => match n { Number::Integer(i) => !i.is_zero(), Number::Float(d) => !d.is_zero(), }, - Value::String(s) if s.is_empty() => false, + NanView::String(s) if s.is_empty() => false, _ => true, } } diff --git a/src/runtime/data.rs b/src/runtime/data.rs index 6664dcb..111b1b5 100644 --- a/src/runtime/data.rs +++ b/src/runtime/data.rs @@ -4,6 +4,7 @@ use std::ops::Deref; use std::rc::Rc; use super::{Budget, Number}; +use super::nanvalue::NanView; // ── InlineString ────────────────────────────────────────────────────────────── @@ -168,6 +169,38 @@ pub struct HeapObj { pub budget: Budget, } +impl HeapObj { + pub fn get(&self, key: &str) -> Option<&Value> { + if self.data.len() <= 8 { + self.data.iter().find(|(k, _)| &**k == key).map(|(_, v)| v) + } else { + self.data.binary_search_by_key(&key, |(k, _)| &**k) + .ok() + .map(|i| &self.data[i].1) + } + } + + pub fn iter(&self) -> impl Iterator { + self.data.iter().map(|(k, v)| (k, v)) + } + + pub fn keys(&self) -> impl Iterator { + self.data.iter().map(|(k, _)| k) + } + + pub fn values(&self) -> impl Iterator { + self.data.iter().map(|(_, v)| v) + } + + pub fn len(&self) -> usize { + self.data.len() + } + + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } +} + impl Drop for HeapObj { fn drop(&mut self) { self.budget.free(); @@ -269,9 +302,9 @@ impl Value { match &n { Number::Integer(iv) => { if let Some(i) = iv.to_i64() { - Value::Int(i) + Value::int(i) } else { - Value::BigNum(Box::new(n)) + Value::bignum(n) } } Number::Float(fv) => { @@ -284,12 +317,12 @@ impl Value { use std::str::FromStr; let roundtrip = BigDecimal::from_str(&format!("{f}")).unwrap_or_default(); if roundtrip.normalized() != bd.normalized() { - return Value::BigNum(Box::new(n)); + return Value::bignum(n); } } - Value::Float(f) + Value::float(f) } else { - Value::BigNum(Box::new(n)) + Value::bignum(n) } } } @@ -297,38 +330,44 @@ impl Value { /// Reconstruct a Number from this value (returns None for non-numeric). pub fn as_number(&self) -> Option { - match self { - Value::Int(i) => Some(Number::int(*i)), - Value::Float(f) => Some(Number::Float(super::number::FloatValue::from_f64(*f))), - Value::BigNum(n) => Some((**n).clone()), + match self.view() { + NanView::Int(i) => Some(Number::int(i)), + NanView::Float(f) => Some(Number::Float(super::number::FloatValue::from_f64(f))), + NanView::BigNum(n) => Some(n.clone()), _ => None, } } /// Check if this value is numeric (Int, Float, or BigNum). pub fn is_number(&self) -> bool { - matches!(self, Value::Int(_) | Value::Float(_) | Value::BigNum(_)) + matches!(self.view(), NanView::Int(_) | NanView::Float(_) | NanView::BigNum(_)) } } impl PartialEq for Value { fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Value::Null, Value::Null) => true, - (Value::Bool(a), Value::Bool(b)) => a == b, - (Value::Int(a), Value::Int(b)) => a == b, - (Value::Float(a), Value::Float(b)) => a == b, - (Value::Int(a), Value::Float(b)) => *a as f64 == *b, - (Value::Float(a), Value::Int(b)) => *a == *b as f64, - (Value::BigNum(a), Value::BigNum(b)) => a == b, + match (self.view(), other.view()) { + (NanView::Null, NanView::Null) => true, + (NanView::Bool(a), NanView::Bool(b)) => a == b, + (NanView::Int(a), NanView::Int(b)) => a == b, + (NanView::Float(a), NanView::Float(b)) => a == b, + (NanView::Int(a), NanView::Float(b)) => a as f64 == b, + (NanView::Float(a), NanView::Int(b)) => a == b as f64, + (NanView::BigNum(a), NanView::BigNum(b)) => a == b, // Cross int/bignum: reconstruct Number for comparison - (Value::Int(_) | Value::Float(_) | Value::BigNum(_), - Value::Int(_) | Value::Float(_) | Value::BigNum(_)) => { + (NanView::Int(_) | NanView::Float(_) | NanView::BigNum(_), + NanView::Int(_) | NanView::Float(_) | NanView::BigNum(_)) => { self.as_number() == other.as_number() } - (Value::String(a), Value::String(b)) => a == b, - (Value::Array(a), Value::Array(b)) => a == b, - (Value::Object(a), Value::Object(b)) => a == b, + (NanView::String(a), NanView::String(b)) => a == b, + (NanView::Array(a), NanView::Array(b)) => a.data == b.data, + (NanView::Object(a), NanView::Object(b)) => { + if a.data.len() != b.data.len() { + return false; + } + a.data.iter().zip(b.data.iter()) + .all(|((k1, v1), (k2, v2))| k1 == k2 && v1 == v2) + } _ => false, } } @@ -345,6 +384,45 @@ impl Value { Self::Bool(b) } + pub fn int(i: i64) -> Self { + Self::Int(i) + } + + pub fn float(f: f64) -> Self { + Self::Float(f) + } + + pub fn bignum(n: Number) -> Self { + Self::BigNum(Box::new(n)) + } + + pub fn from_string_val(s: String) -> Self { + Self::String(Box::new(s)) + } + + pub fn from_array(arr: Array) -> Self { + Self::Array(arr) + } + + pub fn from_object(obj: Object) -> Self { + Self::Object(obj) + } + + /// Zero-cost borrowed view for pattern matching. + pub fn view(&self) -> super::nanvalue::NanView<'_> { + use super::nanvalue::NanView; + match self { + Self::Null => NanView::Null, + Self::Bool(b) => NanView::Bool(*b), + Self::Int(i) => NanView::Int(*i), + Self::Float(f) => NanView::Float(*f), + Self::BigNum(n) => NanView::BigNum(n), + Self::String(s) => NanView::String(s), + Self::Array(arr) => NanView::Array(&arr.0), + Self::Object(obj) => NanView::Object(&obj.0), + } + } + /// Create a numeric Value. Routes to Int/Float/BigNum automatically. pub fn number>(n: N) -> Self { Self::from_number(n.into()) @@ -371,9 +449,9 @@ impl Value { /// /// Single-entry objects `{"key": arg}` are the call form in Jest. pub fn as_call(&self) -> Option<(&str, &Value)> { - match self { - Value::Object(obj) if obj.len() == 1 => { - let (k, v) = obj.iter().next().unwrap(); + match self.view() { + NanView::Object(ho) if ho.len() == 1 => { + let (k, v) = &ho.data[0]; Some((&**k, v)) } _ => None, @@ -382,10 +460,10 @@ impl Value { /// Quick numeric type tag for inline arithmetic dispatch. pub fn numeric_tag(&self) -> Option { - match self { - Value::Int(_) => Some(NumTag::Int), - Value::Float(_) => Some(NumTag::Float), - Value::BigNum(n) => match &**n { + match self.view() { + NanView::Int(_) => Some(NumTag::Int), + NanView::Float(_) => Some(NumTag::Float), + NanView::BigNum(n) => match n { Number::Integer(_) => Some(NumTag::Int), Number::Float(_) => Some(NumTag::Float), }, @@ -394,12 +472,12 @@ impl Value { } pub fn is_const(&self) -> bool { - match self { - Self::Null | Self::Bool(_) | Self::Int(_) | Self::Float(_) - | Self::BigNum(_) | Self::String(_) => true, - Self::Array(arr) => arr.iter().all(Value::is_const), - Self::Object(obj) if obj.len() == 1 => { - let (k, v) = obj.iter().next().unwrap(); + match self.view() { + NanView::Null | NanView::Bool(_) | NanView::Int(_) | NanView::Float(_) + | NanView::BigNum(_) | NanView::String(_) => true, + NanView::Array(ha) => ha.data.iter().all(Value::is_const), + NanView::Object(ho) if ho.len() == 1 => { + let (k, v) = &ho.data[0]; // Hardcoded self-eval tags with const payloads are const if super::SELF_EVAL_TAGS.contains(&&**k) { v.is_const() @@ -407,7 +485,7 @@ impl Value { false } } - Self::Object(obj) => obj.values().all(Value::is_const), + NanView::Object(ho) => ho.values().all(Value::is_const), } } @@ -447,11 +525,11 @@ impl Value { } }; } - match self { - Self::Null | Self::Bool(_) | Self::Int(_) | Self::Float(_) - | Self::BigNum(_) | Self::String(_) => HashSet::new(), - Self::Array(arr) => arr.iter().flat_map(|v| v.free_vars()).collect(), - Self::Object(obj) => obj.iter().flat_map(|(_, v)| v.free_vars()).collect(), + match self.view() { + NanView::Null | NanView::Bool(_) | NanView::Int(_) | NanView::Float(_) + | NanView::BigNum(_) | NanView::String(_) => HashSet::new(), + NanView::Array(ha) => ha.data.iter().flat_map(|v| v.free_vars()).collect(), + NanView::Object(ho) => ho.data.iter().flat_map(|(_, v)| v.free_vars()).collect(), } } @@ -461,18 +539,18 @@ impl Value { budget: &Budget, f: &impl Fn(Self, &Budget) -> crate::Result, ) -> crate::Result { - let descended = match self { - Self::Null | Self::Bool(_) | Self::Int(_) | Self::Float(_) - | Self::BigNum(_) | Self::String(_) => self, - Self::Array(arr) => { - let items: Vec = arr + let descended = match self.view() { + NanView::Null | NanView::Bool(_) | NanView::Int(_) | NanView::Float(_) + | NanView::BigNum(_) | NanView::String(_) => self, + NanView::Array(ha) => { + let items: Vec = ha.data .iter() .map(|v| v.clone().transform(budget, f)) .collect::>()?; Value::array(items, budget)? } - Self::Object(obj) => { - let pairs: Vec<(String, Value)> = obj + NanView::Object(ho) => { + let pairs: Vec<(String, Value)> = ho.data .iter() .map(|(k, v)| v.clone().transform(budget, f).map(|ev| (k.clone(), ev))) .collect::>()?; @@ -491,11 +569,11 @@ impl Value { f: &impl Fn(Self, &C, &Budget) -> crate::Result, ) -> crate::Result { let child_ctx = update(&self, ctx.clone()); - let descended = match self { - Self::Null | Self::Bool(_) | Self::Int(_) | Self::Float(_) - | Self::BigNum(_) | Self::String(_) => self, - Self::Array(arr) => { - let items: Vec = arr + let descended = match self.view() { + NanView::Null | NanView::Bool(_) | NanView::Int(_) | NanView::Float(_) + | NanView::BigNum(_) | NanView::String(_) => self, + NanView::Array(ha) => { + let items: Vec = ha.data .iter() .map(|v| { v.clone() @@ -504,8 +582,8 @@ impl Value { .collect::>()?; Value::array(items, budget)? } - Self::Object(obj) => { - let pairs: Vec<(String, Value)> = obj + NanView::Object(ho) => { + let pairs: Vec<(String, Value)> = ho.data .iter() .map(|(k, v)| { v.clone() @@ -522,18 +600,18 @@ impl Value { impl crate::JestInput for Value { fn kind(&self) -> crate::ValueKind<'_> { - match self { - Value::Null => crate::ValueKind::Null, - Value::Bool(b) => crate::ValueKind::Bool(*b), - Value::Int(i) => crate::ValueKind::Number(Number::int(*i)), - Value::Float(f) => crate::ValueKind::Number(Number::Float(super::number::FloatValue::from_f64(*f))), - Value::BigNum(n) => crate::ValueKind::Number((**n).clone()), - Value::String(s) => crate::ValueKind::String(s), - Value::Array(arr) => crate::ValueKind::Array( - Box::new(arr.iter().map(|v| v as &dyn crate::JestInput)), + match self.view() { + NanView::Null => crate::ValueKind::Null, + NanView::Bool(b) => crate::ValueKind::Bool(b), + NanView::Int(i) => crate::ValueKind::Number(Number::int(i)), + NanView::Float(f) => crate::ValueKind::Number(Number::Float(super::number::FloatValue::from_f64(f))), + NanView::BigNum(n) => crate::ValueKind::Number(n.clone()), + NanView::String(s) => crate::ValueKind::String(s), + NanView::Array(ha) => crate::ValueKind::Array( + Box::new(ha.data.iter().map(|v| v as &dyn crate::JestInput)), ), - Value::Object(obj) => crate::ValueKind::Object( - Box::new(obj.iter().map(|(k, v)| (&**k as &str, v as &dyn crate::JestInput))), + NanView::Object(ho) => crate::ValueKind::Object( + Box::new(ho.data.iter().map(|(k, v)| (&**k as &str, v as &dyn crate::JestInput))), ), } } @@ -560,23 +638,23 @@ fn write_json_string(f: &mut fmt::Formatter<'_>, s: &str) -> fmt::Result { impl fmt::Display for Value { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Value::Null => f.write_str("null"), - Value::Bool(true) => f.write_str("true"), - Value::Bool(false) => f.write_str("false"), - Value::Int(n) => write!(f, "{n}"), - Value::Float(n) => { + match self.view() { + NanView::Null => f.write_str("null"), + NanView::Bool(true) => f.write_str("true"), + NanView::Bool(false) => f.write_str("false"), + NanView::Int(n) => write!(f, "{n}"), + NanView::Float(n) => { if n.fract() == 0.0 && n.is_finite() { write!(f, "{n:.1}") } else { write!(f, "{n}") } } - Value::BigNum(n) => write!(f, "{n}"), - Value::String(s) => write_json_string(f, s), - Value::Array(arr) => { + NanView::BigNum(n) => write!(f, "{n}"), + NanView::String(s) => write_json_string(f, s), + NanView::Array(ha) => { f.write_str("[")?; - for (i, v) in arr.iter().enumerate() { + for (i, v) in ha.data.iter().enumerate() { if i > 0 { f.write_str(",")?; } @@ -584,9 +662,9 @@ impl fmt::Display for Value { } f.write_str("]") } - Value::Object(obj) => { + NanView::Object(ho) => { f.write_str("{")?; - for (i, (k, v)) in obj.iter().enumerate() { + for (i, (k, v)) in ho.data.iter().enumerate() { if i > 0 { f.write_str(",")?; } @@ -610,20 +688,20 @@ impl Value { } fn write_pretty(buf: &mut std::string::String, val: &Value, indent: usize) { - match val { - Value::Null => buf.push_str("null"), - Value::Bool(true) => buf.push_str("true"), - Value::Bool(false) => buf.push_str("false"), - Value::Int(n) => buf.push_str(&format!("{n}")), - Value::Float(n) => { + match val.view() { + NanView::Null => buf.push_str("null"), + NanView::Bool(true) => buf.push_str("true"), + NanView::Bool(false) => buf.push_str("false"), + NanView::Int(n) => buf.push_str(&format!("{n}")), + NanView::Float(n) => { if n.fract() == 0.0 && n.is_finite() { buf.push_str(&format!("{n:.1}")); } else { buf.push_str(&format!("{n}")); } } - Value::BigNum(n) => buf.push_str(&format!("{n}")), - Value::String(s) => { + NanView::BigNum(n) => buf.push_str(&format!("{n}")), + NanView::String(s) => { buf.push('"'); for c in s.chars() { match c { @@ -638,13 +716,13 @@ fn write_pretty(buf: &mut std::string::String, val: &Value, indent: usize) { } buf.push('"'); } - Value::Array(arr) => { - if arr.is_empty() { + NanView::Array(ha) => { + if ha.data.is_empty() { buf.push_str("[]"); return; } buf.push_str("[\n"); - for (i, v) in arr.iter().enumerate() { + for (i, v) in ha.data.iter().enumerate() { if i > 0 { buf.push_str(",\n"); } @@ -659,13 +737,13 @@ fn write_pretty(buf: &mut std::string::String, val: &Value, indent: usize) { } buf.push(']'); } - Value::Object(obj) => { - if obj.is_empty() { + NanView::Object(ho) => { + if ho.data.is_empty() { buf.push_str("{}"); return; } buf.push_str("{\n"); - for (i, (k, v)) in obj.iter().enumerate() { + for (i, (k, v)) in ho.data.iter().enumerate() { if i > 0 { buf.push_str(",\n"); } @@ -713,14 +791,14 @@ fn pattern_bound_names_ordered_inner( // Handle single-key forms via as_call (covers both Call and 1-key Object) if let Some((key, arg)) = val.as_call() { if key == "var" { - match arg { - Value::String(s) => { + match arg.view() { + NanView::String(s) => { let name = s.to_string(); if !name.is_empty() && seen.insert(name.clone()) { names.push(name); } } - Value::Null => {} // wildcard + NanView::Null => {} // wildcard _ => {} } } else { @@ -729,16 +807,16 @@ fn pattern_bound_names_ordered_inner( } return; } - match val { - Value::Null | Value::Bool(_) | Value::Int(_) | Value::Float(_) - | Value::BigNum(_) | Value::String(_) => {} - Value::Array(arr) => { - for elem in arr.iter() { + match val.view() { + NanView::Null | NanView::Bool(_) | NanView::Int(_) | NanView::Float(_) + | NanView::BigNum(_) | NanView::String(_) => {} + NanView::Array(ha) => { + for elem in ha.data.iter() { pattern_bound_names_ordered_inner(elem, names, seen); } } - Value::Object(obj) => { - for (_, v) in obj.iter() { + NanView::Object(ho) => { + for (_, v) in ho.data.iter() { pattern_bound_names_ordered_inner(v, names, seen); } } @@ -749,14 +827,14 @@ fn pattern_bound_names_inner(val: &Value, names: &mut HashSet { + match arg.view() { + NanView::String(s) => { let name = s.to_string(); if !name.is_empty() { names.insert(name); } } - Value::Null => {} // wildcard + NanView::Null => {} // wildcard _ => {} } } else { @@ -764,16 +842,16 @@ fn pattern_bound_names_inner(val: &Value, names: &mut HashSet {} - Value::Array(arr) => { - for elem in arr.iter() { + match val.view() { + NanView::Null | NanView::Bool(_) | NanView::Int(_) | NanView::Float(_) + | NanView::BigNum(_) | NanView::String(_) => {} + NanView::Array(ha) => { + for elem in ha.data.iter() { pattern_bound_names_inner(elem, names); } } - Value::Object(obj) => { - for (_, v) in obj.iter() { + NanView::Object(ho) => { + for (_, v) in ho.data.iter() { pattern_bound_names_inner(v, names); } } @@ -781,13 +859,15 @@ fn pattern_bound_names_inner(val: &Value, names: &mut HashSet HashSet { - match arg { - Value::String(s) => HashSet::from([(*s).to_string()]), - Value::Array(items) => { + match arg.view() { + NanView::String(s) => HashSet::from([s.to_string()]), + NanView::Array(ha) => { let mut vars = HashSet::new(); - let mut iter = items.iter(); - if let Some(Value::String(name)) = iter.next() { - vars.insert((*name).to_string()); + let mut iter = ha.data.iter(); + if let Some(first) = iter.next() { + if let NanView::String(name) = first.view() { + vars.insert(name.to_string()); + } } for item in iter { vars.extend(item.free_vars()); @@ -799,11 +879,11 @@ fn free_vars_var(arg: &Value) -> HashSet { } fn free_vars_function(arg: &Value) -> HashSet { - let arr = match arg { - Value::Array(arr) => arr, + let ha = match arg.view() { + NanView::Array(ha) => ha, _ => return HashSet::new(), }; - match arr.as_ref() { + match ha.data.as_slice() { // 2-element source form: [formals, body] [formals, body] => { let bound = pattern_bound_names(formals); @@ -815,7 +895,7 @@ fn free_vars_function(arg: &Value) -> HashSet { // 3-element: either [formals, body, scope_obj] or [formals, env_str, body] [formals, second, third] => { // If second is a String, it's [formals, env_str, body] (source form with env) - if let Value::String(env) = second { + if let NanView::String(env) = second.view() { let mut bound = pattern_bound_names(formals); bound.insert(env.to_string()); third.free_vars() @@ -899,15 +979,15 @@ mod tests { fn array_from_values() { let budget = Budget::new(512); let items = vec![ - Value::Null, - Value::Bool(true), - Value::Int(42), + Value::null(), + Value::bool(true), + Value::int(42), ]; let arr = Array::new(items, &budget).unwrap(); assert_eq!(arr.len(), 3); - assert_eq!(arr[0], Value::Null); - assert_eq!(arr[1], Value::Bool(true)); - assert_eq!(arr[2], Value::Int(42)); + assert_eq!(arr[0], Value::null()); + assert_eq!(arr[1], Value::bool(true)); + assert_eq!(arr[2], Value::int(42)); } #[test] @@ -926,9 +1006,9 @@ mod tests { fn object_get() { let budget = Budget::new(512); let key = RStr::new("x", &budget).unwrap(); - let pairs = vec![(key, Value::Int(7))]; + let pairs = vec![(key, Value::int(7))]; let obj = Object::new(pairs, &budget).unwrap(); - assert_eq!(obj.get("x"), Some(&Value::Int(7))); + assert_eq!(obj.get("x"), Some(&Value::int(7))); assert_eq!(obj.get("y"), None); } @@ -937,7 +1017,7 @@ mod tests { let budget = Budget::new(512); let ka = RStr::new("a", &budget).unwrap(); let kb = RStr::new("b", &budget).unwrap(); - let pairs = vec![(ka, Value::Bool(false)), (kb, Value::Bool(true))]; + let pairs = vec![(ka, Value::bool(false)), (kb, Value::bool(true))]; let obj = Object::new(pairs, &budget).unwrap(); let keys: Vec<&str> = obj.keys().map(|k| &**k).collect(); assert!(keys.contains(&"a")); @@ -957,17 +1037,17 @@ mod tests { #[test] fn value_null_is_const() { - assert!(Value::Null.is_const()); + assert!(Value::null().is_const()); } #[test] fn value_bool_is_const() { - assert!(Value::Bool(true).is_const()); + assert!(Value::bool(true).is_const()); } #[test] fn value_number_is_const() { - assert!(Value::Int(0).is_const()); + assert!(Value::int(0).is_const()); } #[test] @@ -979,14 +1059,14 @@ mod tests { #[test] fn value_array_of_consts_is_const() { let budget = Budget::new(512); - let arr = Value::array(vec![Value::Null, Value::Bool(false)], &budget).unwrap(); + let arr = Value::array(vec![Value::null(), Value::bool(false)], &budget).unwrap(); assert!(arr.is_const()); } #[test] fn value_call_is_not_const() { let budget = Budget::new(512); - let call = Value::call("if", Value::Null, &budget).unwrap(); + let call = Value::call("if", Value::null(), &budget).unwrap(); assert!(!call.is_const()); } @@ -1002,10 +1082,10 @@ mod tests { #[test] fn value_call_key_and_arg() { let budget = Budget::new(512); - let call = Value::call("not", Value::Bool(true), &budget).unwrap(); + let call = Value::call("not", Value::bool(true), &budget).unwrap(); if let Some((key, arg)) = call.as_call() { assert_eq!(key, "not"); - assert_eq!(*arg, Value::Bool(true)); + assert_eq!(*arg, Value::bool(true)); } else { panic!("expected singleton object (call form)"); } diff --git a/src/runtime/mod.rs b/src/runtime/mod.rs index a29e649..a219493 100644 --- a/src/runtime/mod.rs +++ b/src/runtime/mod.rs @@ -4,6 +4,7 @@ pub(crate) mod convert; pub(crate) mod ctx; pub(crate) mod data; pub mod intern; +pub mod nanvalue; pub(crate) mod number; pub mod parse; pub(crate) mod procs; @@ -11,7 +12,7 @@ mod scope; pub use scope::Scope; pub use budget::Budget; -pub use data::{Array, NumTag, Object, String, Value, pattern_bound_names, pattern_bound_names_ordered}; +pub use data::{Array, HeapArr, HeapObj, NumTag, Object, String, Value, pattern_bound_names, pattern_bound_names_ordered}; pub use number::{Number, IntegerValue, FloatValue, NumFormat, set_numeric_format}; pub use procs::try_match_simple; @@ -498,19 +499,20 @@ fn eval_inner( scope: Scope, ) -> crate::Result { use data::String as RStr; + use nanvalue::NanView; if *eval_limit == 0 { return Err(crate::Error::Runtime(RuntimeError::EvalLimit)); } *eval_limit -= 1; - match value { + match value.view() { // Primitives are self-evaluating. - Value::Null | Value::Bool(_) | Value::Int(_) | Value::Float(_) | Value::BigNum(_) | Value::String(_) => Eval::done(value), + NanView::Null | NanView::Bool(_) | NanView::Int(_) | NanView::Float(_) | NanView::BigNum(_) | NanView::String(_) => Eval::done(value), // Arrays evaluate element-wise. - Value::Array(arr) => { - let items: Vec = arr + NanView::Array(ha) => { + let items: Vec = ha.data .iter() .map(|v| eval_trampoline(budget, eval_limit, v.clone(), scope.clone())) .collect::>()?; @@ -518,23 +520,23 @@ fn eval_inner( } // Objects: single-key = procedure dispatch, multi-key = eval value-wise. - Value::Object(obj) => { - if obj.len() == 1 { + NanView::Object(ho) => { + if ho.len() == 1 { // Singleton call form — procedure dispatch. - let (key, arg) = obj.iter().next().unwrap(); + let (key, arg) = ho.iter().next().unwrap(); let key_ref: &str = key; // 1. Hardcoded self-eval tags (bootstrap-critical, 7 items) if SELF_EVAL_TAGS.contains(&key_ref) { - return Eval::done(Value::Object(obj)); + return Eval::done(value); } // 2. Scope lookup (fast path — covers 99% of calls) if let Some(proc_val) = scope.get(key_ref) { let arg = arg.clone(); - return match arg { - Value::Array(arr) => { - let items: Vec = arr.iter().cloned().collect(); + return match arg.view() { + NanView::Array(ha) => { + let items: Vec = ha.data.iter().cloned().collect(); procs::dispatch_call(proc_val, &items, budget, eval_limit, scope, key_ref) } _ => { @@ -547,7 +549,7 @@ fn eval_inner( // 3. User type registry (rare — only for keys NOT in scope) if let Some(descriptor) = lookup_user_type(key_ref, &scope) { if is_raw_self_eval(&descriptor) { - return Eval::done(Value::Object(obj)); + return Eval::done(value); } let arg = arg.clone(); let evaled_arg = eval_trampoline(budget, eval_limit, arg, scope.clone())?; @@ -561,7 +563,7 @@ fn eval_inner( return Err(crate::Error::UndefinedName(key_ref.to_string())); } else { // Multi-key object — eval value-wise. - let pairs: Vec<(RStr, Value)> = obj + let pairs: Vec<(RStr, Value)> = ho .iter() .map(|(k, v)| { eval_trampoline(budget, eval_limit, v.clone(), scope.clone()) @@ -579,21 +581,22 @@ fn eval_inner( /// Look up a user-defined type descriptor for `key` in the scope's runtime.types registry. /// Returns `Some(descriptor)` only if the type has `"self-eval": true`. pub(crate) fn lookup_user_type(key: &str, scope: &Scope) -> Option { + use nanvalue::NanView; // Access runtime.types via nested get let runtime = scope.get("runtime")?; - let registry = match &runtime { - Value::Object(obj) => obj.get("types")?.clone(), + let registry = match runtime.view() { + NanView::Object(ho) => ho.get("types")?.clone(), _ => return None, }; - let descriptor = match ®istry { - Value::Object(obj) => obj.get(key)?.clone(), + let descriptor = match registry.view() { + NanView::Object(ho) => ho.get(key)?.clone(), _ => return None, }; // Check self-eval flag - match &descriptor { - Value::Object(obj) => { - match obj.get("self-eval") { - Some(Value::Bool(true)) => Some(descriptor), + match descriptor.view() { + NanView::Object(ho) => { + match ho.get("self-eval") { + Some(v) if matches!(v.view(), NanView::Bool(true)) => Some(descriptor), _ => None, } } @@ -603,8 +606,12 @@ pub(crate) fn lookup_user_type(key: &str, scope: &Scope) -> Option { /// Check if a type descriptor has `"raw": true` (skip payload evaluation). pub(crate) fn is_raw_self_eval(descriptor: &Value) -> bool { - match descriptor { - Value::Object(obj) => matches!(obj.get("raw"), Some(Value::Bool(true))), + use nanvalue::NanView; + match descriptor.view() { + NanView::Object(ho) => match ho.get("raw") { + Some(v) => matches!(v.view(), NanView::Bool(true)), + None => false, + }, _ => false, } } @@ -618,21 +625,21 @@ pub(crate) fn validate_and_normalize( eval_limit: &mut usize, scope: &Scope, ) -> crate::Result { - let desc_obj = match descriptor { - Value::Object(obj) => obj, + let desc_obj = match descriptor.view() { + nanvalue::NanView::Object(ho) => ho, _ => return Ok(payload), }; // 1. Validate shape if non-null if let Some(shape) = desc_obj.get("shape") - && !matches!(shape, Value::Null) + && !matches!(shape.view(), nanvalue::NanView::Null) { validate_shape(&payload, shape, tag)?; } // 2. Run normalizer if non-null if let Some(normalizer) = desc_obj.get("normalizer") - && !matches!(normalizer, Value::Null) + && !matches!(normalizer.view(), nanvalue::NanView::Null) { let result = procs::dispatch_call( normalizer.clone(), @@ -664,8 +671,10 @@ pub(crate) fn validate_and_normalize( /// Shape spec is an array of 1-element arrays, each containing a type family string: /// `[["integer"], ["integer"]]` means "2-element array, both integers." fn validate_shape(payload: &Value, shape: &Value, tag: &str) -> crate::Result<()> { - let shape_arr = match shape { - Value::Array(arr) => arr, + use nanvalue::NanView; + + let shape_ha = match shape.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::BadSyntax { proc: "deftype", args: shape.to_string(), @@ -673,27 +682,27 @@ fn validate_shape(payload: &Value, shape: &Value, tag: &str) -> crate::Result<() }), }; - let payload_arr = match payload { - Value::Array(arr) => arr, + let payload_ha = match payload.view() { + NanView::Array(ha) => ha, _ => return Err(crate::Error::Throw(format!( "{tag}: shape mismatch: expected array payload, got {}", type_family_of(payload), ))), }; - if payload_arr.len() != shape_arr.len() { + if payload_ha.data.len() != shape_ha.data.len() { return Err(crate::Error::Throw(format!( "{tag}: shape mismatch: expected {} elements, got {}", - shape_arr.len(), - payload_arr.len(), + shape_ha.data.len(), + payload_ha.data.len(), ))); } - for (i, (elem, spec)) in payload_arr.iter().zip(shape_arr.iter()).enumerate() { - let expected_family = match spec { - Value::Array(spec_arr) if spec_arr.len() == 1 => { - match &spec_arr[0] { - Value::String(s) => s.to_string(), + for (i, (elem, spec)) in payload_ha.data.iter().zip(shape_ha.data.iter()).enumerate() { + let expected_family = match spec.view() { + NanView::Array(spec_ha) if spec_ha.data.len() == 1 => { + match spec_ha.data[0].view() { + NanView::String(s) => s.to_string(), _ => continue, } } @@ -715,18 +724,19 @@ fn validate_shape(payload: &Value, shape: &Value, tag: &str) -> crate::Result<() /// Get the type family name of a value. fn type_family_of(val: &Value) -> std::string::String { - match val { - Value::Null => "null".into(), - Value::Bool(_) => "bool".into(), - Value::Int(_) => "integer".into(), - Value::Float(_) => "float".into(), - Value::BigNum(n) => match &**n { + use nanvalue::NanView; + match val.view() { + NanView::Null => "null".into(), + NanView::Bool(_) => "bool".into(), + NanView::Int(_) => "integer".into(), + NanView::Float(_) => "float".into(), + NanView::BigNum(n) => match n { Number::Integer(_) => "integer".into(), Number::Float(_) => "float".into(), }, - Value::String(_) => "string".into(), - Value::Array(_) => "array".into(), - Value::Object(_) => "object".into(), + NanView::String(_) => "string".into(), + NanView::Array(_) => "array".into(), + NanView::Object(_) => "object".into(), } } diff --git a/src/compile/nanvalue.rs b/src/runtime/nanvalue.rs similarity index 98% rename from src/compile/nanvalue.rs rename to src/runtime/nanvalue.rs index ecd7f9b..dbc4ebc 100644 --- a/src/compile/nanvalue.rs +++ b/src/runtime/nanvalue.rs @@ -1,8 +1,8 @@ -//! NaN-boxed value representation for the bytecode VM register file. +//! NaN-boxed value representation for Jest values. //! //! Packs every Jest value into 8 bytes (u64) using IEEE 754 quiet NaN payload -//! space. Used only inside `VmWithCtx.locals` — the rest of the codebase uses -//! the 16-byte `Value` enum. Conversion happens at VM boundaries. +//! space. `NanValue` is the inner representation of `Value` (RAII wrapper). +//! `NanView<'a>` provides zero-cost borrowed pattern matching. //! //! ## Encoding //! @@ -20,9 +20,9 @@ use std::rc::Rc; -use crate::runtime::data::{HeapArr, HeapObj}; -use crate::runtime::{Number, Value}; -use crate::runtime::String as RStr; +use super::data::{HeapArr, HeapObj}; +use super::{Number, Value}; +use super::String as RStr; // ── Tag constants ─────────────────────────────────────────────────────────── @@ -72,7 +72,7 @@ pub(crate) struct NanValue(u64); /// /// The lifetime `'a` is tied to the NanValue (which must outlive the view). #[derive(Debug)] -pub(crate) enum NanView<'a> { +pub enum NanView<'a> { Null, Bool(bool), Int(i64), diff --git a/src/runtime/parse.rs b/src/runtime/parse.rs index d923e01..06c18a4 100644 --- a/src/runtime/parse.rs +++ b/src/runtime/parse.rs @@ -123,17 +123,17 @@ impl<'a> Parser<'a> { fn parse_null(&mut self) -> Result { self.expect_literal(b"null")?; - Ok(Value::Null) + Ok(Value::null()) } fn parse_true(&mut self) -> Result { self.expect_literal(b"true")?; - Ok(Value::Bool(true)) + Ok(Value::bool(true)) } fn parse_false(&mut self) -> Result { self.expect_literal(b"false")?; - Ok(Value::Bool(false)) + Ok(Value::bool(false)) } fn expect_literal(&mut self, lit: &[u8]) -> Result<()> { @@ -412,6 +412,7 @@ impl<'a> Parser<'a> { #[cfg(test)] mod tests { use super::*; + use crate::runtime::nanvalue::NanView; fn budget() -> Budget { Budget::new(usize::MAX / 2) @@ -474,7 +475,7 @@ mod tests { // U+1F600 = 😀 encoded as \uD83D\uDE00 let v = parse_json(r#""\uD83D\uDE00""#, &budget()).unwrap(); // Check the actual string value - if let Value::String(s) = &v { + if let NanView::String(s) = v.view() { assert_eq!(&**s, "😀"); } else { panic!("expected string"); @@ -511,7 +512,7 @@ mod tests { fn multi_key_object() { let v = parse_json(r#"{"a": 1, "b": 2}"#, &budget()).unwrap(); // Multi-key → Value::Object - assert!(matches!(v, Value::Object(_))); + assert!(matches!(v.view(), NanView::Object(_))); assert_eq!(v.to_string(), r#"{"a":1,"b":2}"#); } diff --git a/src/runtime/procs/array.rs b/src/runtime/procs/array.rs index 76c5da6..b94f9b3 100644 --- a/src/runtime/procs/array.rs +++ b/src/runtime/procs/array.rs @@ -1,3 +1,4 @@ +use crate::runtime::nanvalue::NanView; use crate::runtime::{Budget, Eval, Number, Value}; use crate::runtime::Scope; use crate::{Error, Result}; @@ -12,19 +13,19 @@ fn require_array(arg: &Value, proc: &'static str, index: usize) -> Result Result { - match arg { - Value::Int(i) if *i >= 0 => { - Ok(*i as usize) + match arg.view() { + NanView::Int(i) if i >= 0 => { + Ok(i as usize) } - Value::BigNum(n) => { - if let Number::Integer(iv) = &**n { + NanView::BigNum(n) => { + if let Number::Integer(iv) = n { if !iv.is_negative() { return iv.to_usize().ok_or_else(|| Error::type_err(proc, index, arg.clone(), "a non-negative integer index that fits in memory")); } } Err(Error::type_err(proc, index, arg.clone(), "a non-negative integer index")) } - other => Err(Error::type_err(proc, index, other.clone(), "a non-negative integer index")), + _ => Err(Error::type_err(proc, index, arg.clone(), "a non-negative integer index")), } } @@ -37,7 +38,7 @@ pub fn op_count( match args { [arg] => { let arr = require_array(arg, "count", 0)?; - Eval::done(Value::Int(arr.len() as i64)) + Eval::done(Value::int(arr.len() as i64)) } _ => Err(Error::arity("count", "1", args.len())), } @@ -54,7 +55,7 @@ pub fn op_nth( let arr = require_array(arr_arg, "nth", 0)?; let i = require_index(idx_arg, "nth", 1)?; arr.get(i).cloned().ok_or_else(|| Error::Access { - data: Value::Array(arr.clone()).to_string(), + data: Value::from_array(arr.clone()).to_string(), key: i.to_string(), }).map(Eval::Done) } @@ -120,7 +121,7 @@ pub fn op_push( let heap_arr = std::rc::Rc::make_mut(&mut arr.0); heap_arr.data.push(val); - Ok(Eval::Done(Value::Array(arr))) + Ok(Eval::Done(Value::from_array(arr))) } _ => Err(Error::arity("push", "2", args.len())), } diff --git a/src/runtime/procs/cmp.rs b/src/runtime/procs/cmp.rs index fb326f1..7ae83e2 100644 --- a/src/runtime/procs/cmp.rs +++ b/src/runtime/procs/cmp.rs @@ -10,7 +10,7 @@ pub fn op_eq( ) -> Result { match args { [a, b] => { - Eval::done(Value::Bool(a == b)) + Eval::done(Value::bool(a == b)) } _ => Err(Error::arity("==", "2", args.len())), } @@ -36,7 +36,7 @@ pub fn op_lt_ii( Some(Number::Integer(i)) => i, _ => return Err(Error::arity("lt-ii", "2 integers", args.len())), }; - Eval::done(Value::Bool(a < b)) + Eval::done(Value::bool(a < b)) } /// `runtime/lt-ff`: Less-than for two floats @@ -57,7 +57,7 @@ pub fn op_lt_ff( Some(Number::Float(f)) => f, _ => return Err(Error::arity("lt-ff", "2 floats", args.len())), }; - Eval::done(Value::Bool(a < b)) + Eval::done(Value::bool(a < b)) } /// `runtime/lt-if`: Less-than for integer and float @@ -79,7 +79,7 @@ pub fn op_lt_if( _ => return Err(Error::arity("lt-if", "integer and float", args.len())), }; let af = a.to_float_value(); - Eval::done(Value::Bool(af < b)) + Eval::done(Value::bool(af < b)) } /// `runtime/lt-fi`: Less-than for float and integer @@ -101,5 +101,5 @@ pub fn op_lt_fi( _ => return Err(Error::arity("lt-fi", "float and integer", args.len())), }; let bf = b.to_float_value(); - Eval::done(Value::Bool(a < bf)) + Eval::done(Value::bool(a < bf)) } diff --git a/src/runtime/procs/control.rs b/src/runtime/procs/control.rs index dac778a..7ae8a61 100644 --- a/src/runtime/procs/control.rs +++ b/src/runtime/procs/control.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use crate::runtime::convert::is_truthy; +use crate::runtime::nanvalue::NanView; use crate::runtime::number::Number; use crate::runtime::{Budget, Eval, Scope, Value, eval_trampoline}; use crate::{Error, Result}; @@ -63,8 +64,8 @@ pub fn op_eval( /// Convert a Value::Object into a Scope layered on top of globals. fn value_to_scope(val: Value, parent: &Scope) -> Result { - let map: HashMap = match val { - Value::Object(obj) => obj.iter().map(|(k, v)| (k.to_string(), v.clone())).collect(), + let map: HashMap = match val.view() { + NanView::Object(obj) => obj.iter().map(|(k, v)| (k.to_string(), v.clone())).collect(), _ => return Err(Error::type_err("eval", 1, &val, "object (environment)")), }; Ok(parent.globals().local(map)) @@ -175,9 +176,9 @@ pub fn op_throw( expr: arg.clone(), scope, resume: Box::new(move |val, _b, _el| { - let msg_str = match val { - Value::String(s) => s.to_string(), - other => other.to_string(), + let msg_str = match val.view() { + NanView::String(s) => s.to_string(), + _ => val.to_string(), }; Err(Error::Throw(msg_str)) }), @@ -204,7 +205,7 @@ pub fn op_if( if is_truthy(&cond_val) { Ok(Eval::TailEval { value: then, scope: scope2 }) } else { - Eval::done(Value::Null) + Eval::done(Value::null()) } }), }) @@ -269,9 +270,9 @@ fn apply_with_proc( expr: arg, scope, resume: Box::new(move |arg_val, budget, eval_limit| { - let actual_args: Vec = match arg_val { - Value::Array(arr) => arr.iter().cloned().collect(), - single => vec![single], + let actual_args: Vec = match arg_val.view() { + NanView::Array(ha) => ha.data.iter().cloned().collect(), + _ => vec![arg_val], }; super::lambda::call_operative( inner_arg, &actual_args, budget, eval_limit, scope2, @@ -286,9 +287,9 @@ fn apply_with_proc( expr: arg, scope, resume: Box::new(move |arg_val, budget, eval_limit| { - let actual_args: Vec = match arg_val { - Value::Array(arr) => arr.iter().cloned().collect(), - single => vec![single], + let actual_args: Vec = match arg_val.view() { + NanView::Array(ha) => ha.data.iter().cloned().collect(), + _ => vec![arg_val], }; super::dispatch_call(proc_val2, &actual_args, budget, eval_limit, scope2, "apply") }), @@ -298,18 +299,18 @@ fn apply_with_proc( // For operatives (vau) and builtins: extract the arg list without // evaluating elements (operatives receive args unevaluated). - let actual_args: Vec = match arg { - Value::Array(arr) => arr.iter().cloned().collect(), - other => { + let actual_args: Vec = match arg.view() { + NanView::Array(ha) => ha.data.iter().cloned().collect(), + _ => { let scope2 = scope.clone(); let proc_val2 = proc_val.clone(); return Ok(Eval::Yield { - expr: other, + expr: arg, scope, resume: Box::new(move |eval_result, budget, eval_limit| { - let actual_args: Vec = match eval_result { - Value::Array(arr) => arr.iter().cloned().collect(), - single => vec![single], + let actual_args: Vec = match eval_result.view() { + NanView::Array(ha) => ha.data.iter().cloned().collect(), + _ => vec![eval_result], }; super::dispatch_call(proc_val2, &actual_args, budget, eval_limit, scope2, "apply") }), @@ -328,11 +329,16 @@ pub fn op_letrec( // Extract (name, expr) pairs from the bindings argument. // Objects use Value::Object; single-key objects matched via as_call(). let binding_pairs: Vec<(std::string::String, Value)> = match args { - [Value::Object(obj), _] => obj.iter().map(|(k, v)| (k.to_string(), v.clone())).collect(), - [bindings_val, _] if bindings_val.as_call().is_some() => { - let (k, v) = bindings_val.as_call().unwrap(); - vec![(k.to_string(), v.clone())] - } + [bindings_val, _] => match bindings_val.view() { + NanView::Object(obj) => obj.iter().map(|(k, v)| (k.to_string(), v.clone())).collect(), + _ if bindings_val.as_call().is_some() => { + let (k, v) = bindings_val.as_call().unwrap(); + vec![(k.to_string(), v.clone())] + } + _ => { + return Err(Error::arity("letrec", "2", args.len())) + } + }, _ => { return Err(Error::arity("letrec", "2", args.len())) } @@ -364,8 +370,8 @@ pub fn op_letrec( expected: "letrec binding must use 'fn', 'macro', or 'vau'", }); } - let fn_args = match fn_arg { - Value::Array(arr) => arr, + let fn_args = match fn_arg.view() { + NanView::Array(ha) => ha, _ => { return Err(Error::BadSyntax { proc: "letrec", @@ -374,7 +380,7 @@ pub fn op_letrec( }); } }; - let (formals, inner_body) = match fn_args.as_ref() { + let (formals, inner_body) = match fn_args.data.as_slice() { [f, b] => (f.clone(), b.clone()), _ => { return Err(Error::BadSyntax { @@ -545,7 +551,7 @@ pub fn op_do( scope: Scope, ) -> Result { let stmts: Vec = args.to_vec(); - do_step(stmts, 0, scope, Value::Null, true, budget, eval_limit) + do_step(stmts, 0, scope, Value::null(), true, budget, eval_limit) } /// Execute one step of a `do` block. Returns `Yield` for `def`/`expr` statements @@ -562,7 +568,7 @@ fn do_step( use super::forms::{classify_do_stmt, DoStmtKind, parse_def, parse_defn}; if idx >= stmts.len() { - return if last_is_def { Eval::done(Value::Null) } else { Eval::done(last_val) }; + return if last_is_def { Eval::done(Value::null()) } else { Eval::done(last_val) }; } let stmt = &stmts[idx]; @@ -579,7 +585,7 @@ fn do_step( scope, resume: Box::new(move |val, budget, eval_limit| { let new_scope = scope2.local([(name, val)].into_iter().collect()); - do_step(stmts, idx + 1, new_scope, Value::Null, true, budget, eval_limit) + do_step(stmts, idx + 1, new_scope, Value::null(), true, budget, eval_limit) }), }) } @@ -593,7 +599,7 @@ fn do_step( )?; let fn_val = Value::call("wrap", vau_val, budget)?; let new_scope = scope.local([(parsed.bind_name, fn_val)].into_iter().collect()); - do_step(stmts, idx + 1, new_scope, Value::Null, true, budget, eval_limit) + do_step(stmts, idx + 1, new_scope, Value::null(), true, budget, eval_limit) } DoStmtKind::Defmacro(inner) => { let parsed = parse_defn(inner).map_err(|msg| Error::BadSyntax { @@ -606,36 +612,36 @@ fn do_step( let macro_call = Value::call("macro", macro_args, budget)?; let macro_val = eval_trampoline(budget, eval_limit, macro_call, scope.clone())?; let new_scope = scope.local([(parsed.bind_name, macro_val)].into_iter().collect()); - do_step(stmts, idx + 1, new_scope, Value::Null, true, budget, eval_limit) + do_step(stmts, idx + 1, new_scope, Value::null(), true, budget, eval_limit) } DoStmtKind::Defrec(inner) => { let bindings = op_defrec(inner, budget, eval_limit, scope.clone())?; let new_scope = scope.local(bindings); - do_step(stmts, idx + 1, new_scope, Value::Null, true, budget, eval_limit) + do_step(stmts, idx + 1, new_scope, Value::null(), true, budget, eval_limit) } DoStmtKind::Import(inner) => { let bindings = eval_import(inner, budget, eval_limit, scope.clone())?; let mut new_scope = scope; merge_runtime_bindings(&mut new_scope, bindings, budget)?; - do_step(stmts, idx + 1, new_scope, Value::Null, true, budget, eval_limit) + do_step(stmts, idx + 1, new_scope, Value::null(), true, budget, eval_limit) } DoStmtKind::Deftype(inner) => { let bindings = deftype_bindings(inner, budget, eval_limit, &scope)?; let mut new_scope = scope; merge_runtime_bindings(&mut new_scope, bindings, budget)?; - do_step(stmts, idx + 1, new_scope, Value::Null, true, budget, eval_limit) + do_step(stmts, idx + 1, new_scope, Value::null(), true, budget, eval_limit) } DoStmtKind::Defgeneric(inner) => { let bindings = defgeneric_bindings(inner, budget, &scope)?; let mut new_scope = scope; merge_runtime_bindings(&mut new_scope, bindings, budget)?; - do_step(stmts, idx + 1, new_scope, Value::Null, true, budget, eval_limit) + do_step(stmts, idx + 1, new_scope, Value::null(), true, budget, eval_limit) } DoStmtKind::Defmethod(inner) => { let bindings = defmethod_bindings(inner, budget, eval_limit, &scope)?; let mut new_scope = scope; merge_runtime_bindings(&mut new_scope, bindings, budget)?; - do_step(stmts, idx + 1, new_scope, Value::Null, true, budget, eval_limit) + do_step(stmts, idx + 1, new_scope, Value::null(), true, budget, eval_limit) } DoStmtKind::Expr(_) => { // Last expr is tail position @@ -679,8 +685,8 @@ fn eval_import( eval_limit: &mut usize, scope: Scope, ) -> Result> { - let (path_val, names_val) = match arg { - Value::Array(arr) => match arr.as_ref() { + let (path_val, names_val) = match arg.view() { + NanView::Array(ha) => match ha.data.as_slice() { [p, n] => (p.clone(), n.clone()), _ => return Err(Error::BadSyntax { proc: "import", @@ -695,8 +701,9 @@ fn eval_import( }), }; - let path_str = match eval_trampoline(budget, eval_limit, path_val, scope.clone())? { - Value::String(s) => s.to_string(), + let evaled_path = eval_trampoline(budget, eval_limit, path_val, scope.clone())?; + let path_str = match evaled_path.view() { + NanView::String(s) => s.to_string(), _ => return Err(Error::BadSyntax { proc: "import", args: arg.to_string(), @@ -803,17 +810,17 @@ fn extract_imports( ) -> Result> { // Helper: look up a key in the module (works for single and multi-key objects). let module_get = |name: &str| -> Option { - match &module { - Value::Object(obj) => obj.get(name).cloned(), + match module.view() { + NanView::Object(obj) => obj.get(name).cloned(), _ => None, } }; - match names { + match names.view() { // null → wildcard flat: import everything from the module object. - Value::Null => { - match module { - Value::Object(obj) => Ok(obj.iter().map(|(k, v)| (k.to_string(), v.clone())).collect()), + NanView::Null => { + match module.view() { + NanView::Object(obj) => Ok(obj.iter().map(|(k, v)| (k.to_string(), v.clone())).collect()), _ => Err(Error::BadSyntax { proc: "import", args: arg.to_string(), @@ -822,13 +829,13 @@ fn extract_imports( } } // String → namespace bind: bind the whole module as that name. - Value::String(ns) => Ok([(ns.to_string(), module)].into_iter().collect()), + NanView::String(ns) => Ok([(ns.to_string(), module)].into_iter().collect()), // Array → selective flat: import only the listed names. - Value::Array(names_arr) => { + NanView::Array(names_ha) => { let mut map = HashMap::new(); - for item in names_arr.iter() { - let name = match item { - Value::String(s) => s.to_string(), + for item in names_ha.data.iter() { + let name = match item.view() { + NanView::String(s) => s.to_string(), _ => return Err(Error::BadSyntax { proc: "import", args: arg.to_string(), @@ -844,19 +851,13 @@ fn extract_imports( Ok(map) } // Object → rename map: {local_name: exported_name}. - ref names_val if names_val.as_call().is_some() || matches!(names_val, Value::Object(_)) => { - // Collect all (key, value) pairs from either Object or Call. - let rename_pairs: Vec<(std::string::String, Value)> = match names { - Value::Object(renames) => renames.iter().map(|(k, v)| (k.to_string(), v.clone())).collect(), - _ => { - let (k, v) = names.as_call().unwrap(); - vec![(k.to_string(), v.clone())] - } - }; + NanView::Object(names_ho) => { + // Collect all (key, value) pairs from multi-key Object. + let rename_pairs: Vec<(std::string::String, Value)> = names_ho.iter().map(|(k, v)| (k.to_string(), v.clone())).collect(); let mut map = HashMap::new(); for (local, exported_val) in &rename_pairs { - let exported = match exported_val { - Value::String(s) => s.to_string(), + let exported = match exported_val.view() { + NanView::String(s) => s.to_string(), _ => return Err(Error::BadSyntax { proc: "import", args: arg.to_string(), @@ -871,11 +872,36 @@ fn extract_imports( } Ok(map) } - _ => Err(Error::BadSyntax { - proc: "import", - args: arg.to_string(), - expected: "import names must be null, string, array, or object", - }), + _ => { + // Single-key object (call form) → rename map + if names.as_call().is_some() { + let (k, v) = names.as_call().unwrap(); + let rename_pairs = vec![(k.to_string(), v.clone())]; + let mut map = HashMap::new(); + for (local, exported_val) in &rename_pairs { + let exported = match exported_val.view() { + NanView::String(s) => s.to_string(), + _ => return Err(Error::BadSyntax { + proc: "import", + args: arg.to_string(), + expected: "rename map values must be strings (exported names)", + }), + }; + let val = module_get(&exported); + map.insert(local.clone(), val.ok_or_else(|| Error::Access { + data: module.clone().to_string(), + key: format!("\"{}\"" , exported), + })?); + } + Ok(map) + } else { + Err(Error::BadSyntax { + proc: "import", + args: arg.to_string(), + expected: "import names must be null, string, array, or object", + }) + } + } } } @@ -905,13 +931,13 @@ fn deep_merge_runtime(base: &Value, overlay: &Value, budget: &Budget) -> Result< use crate::runtime::String as RStr; // If overlay is not an object, just replace - let Value::Object(overlay_obj) = overlay else { + let NanView::Object(overlay_obj) = overlay.view() else { return Ok(overlay.clone()); }; // Start with base pairs - let mut pairs: Vec<(crate::runtime::String, Value)> = match base { - Value::Object(obj) => obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect(), + let mut pairs: Vec<(crate::runtime::String, Value)> = match base.view() { + NanView::Object(obj) => obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect(), _ => Vec::new(), }; @@ -923,7 +949,7 @@ fn deep_merge_runtime(base: &Value, overlay: &Value, budget: &Budget) -> Result< if let Some(pos) = pairs.iter().position(|(k, _)| &**k == key_str) { let (_, base_val) = &pairs[pos]; // If both are objects, merge recursively - if matches!(base_val, Value::Object(_)) && matches!(ov, Value::Object(_)) { + if matches!(base_val.view(), NanView::Object(_)) && matches!(ov.view(), NanView::Object(_)) { let merged_val = deep_merge_runtime(base_val, ov, budget)?; pairs[pos] = (RStr::new(key_str, budget)?, merged_val); } else { @@ -947,63 +973,74 @@ fn deftype_bindings( eval_limit: &mut usize, scope: &Scope, ) -> Result> { - let arr = match arg { - Value::Array(arr) => arr, + let arr = match arg.view() { + NanView::Array(ha) => ha, _ => return Err(Error::BadSyntax { proc: "deftype", args: arg.to_string(), expected: "deftype requires [name, base_or_shape, pred_or_opts]", }), }; - match arr.as_ref() { - [Value::String(name), Value::String(_base_family), pred_expr] => { - // Legacy form - let pred = eval_trampoline(budget, eval_limit, pred_expr.clone(), scope.clone())?; - let pred_name = format!("{}?", &**name); - Ok([(pred_name, pred)].into_iter().collect()) - } - [Value::String(name), shape, opts_expr] => { - // Extended form - let opts = eval_opts_object(opts_expr, budget, eval_limit, scope)?; - let name_s: std::string::String = name.to_string(); - - if let Value::Object(obj) = &opts - && let Some(Value::String(parent_name)) = obj.get("refines") - { - let types_registry = get_runtime_types(scope); - let parent_exists = matches!(&types_registry, Value::Object(reg_obj) if reg_obj.get(parent_name).is_some()); - if !parent_exists { - return Err(Error::BadSyntax { - proc: "deftype", - args: format!("refines parent '{}' does not exist", &**parent_name), - expected: "parent type must be defined before refinement", - }); - } + if arr.data.len() != 3 { + return Err(Error::BadSyntax { + proc: "deftype", args: arg.to_string(), + expected: "deftype requires [name, base_or_shape, pred_or_opts]", + }); + } + let name_s = match arr.data[0].view() { + NanView::String(s) => s.to_string(), + _ => return Err(Error::BadSyntax { + proc: "deftype", args: arg.to_string(), + expected: "deftype requires [name, base_or_shape, pred_or_opts]", + }), + }; + // Check if second arg is also a string (legacy form) + let is_legacy = matches!(arr.data[1].view(), NanView::String(_)); + if is_legacy { + // Legacy form: [name, base_family, pred_expr] + let pred_expr = &arr.data[2]; + let pred = eval_trampoline(budget, eval_limit, pred_expr.clone(), scope.clone())?; + let pred_name = format!("{}?", name_s); + Ok([(pred_name, pred)].into_iter().collect()) + } else { + // Extended form: [name, shape, opts_expr] + let shape = &arr.data[1]; + let opts_expr = &arr.data[2]; + let opts = eval_opts_object(opts_expr, budget, eval_limit, scope)?; + + if let NanView::Object(obj) = opts.view() + && let Some(parent_name_val) = obj.get("refines") + && let NanView::String(parent_name) = parent_name_val.view() + { + let types_registry = get_runtime_types(scope); + let parent_exists = matches!(types_registry.view(), NanView::Object(reg_obj) if reg_obj.get(parent_name).is_some()); + if !parent_exists { + return Err(Error::BadSyntax { + proc: "deftype", + args: format!("refines parent '{}' does not exist", &**parent_name), + expected: "parent type must be defined before refinement", + }); } + } - let descriptor = build_type_descriptor(shape, &opts, budget)?; - let mut bindings: HashMap = HashMap::new(); - - let pred_fn = make_type_predicate(&name_s, shape, &opts, budget, eval_limit, scope)?; - bindings.insert(format!("{}?", name_s), pred_fn); + let descriptor = build_type_descriptor(shape, &opts, budget)?; + let mut bindings: HashMap = HashMap::new(); - let is_self_eval = match &opts { - Value::Object(obj) => matches!(obj.get("self-eval"), Some(Value::Bool(true))), - _ => false, - }; - if is_self_eval { - let existing_types = get_runtime_types(scope); - let updated_types = assoc_value(existing_types, &name_s, descriptor, budget)?; - let runtime_obj = scope.get("runtime").unwrap_or(Value::Null); - let updated_runtime = assoc_value(runtime_obj, "types", updated_types, budget)?; - bindings.insert("runtime".to_string(), updated_runtime); - } + let pred_fn = make_type_predicate(&name_s, shape, &opts, budget, eval_limit, scope)?; + bindings.insert(format!("{}?", name_s), pred_fn); - Ok(bindings) + let is_self_eval = match opts.view() { + NanView::Object(obj) => matches!(obj.get("self-eval").map(|v| v.view()), Some(NanView::Bool(true))), + _ => false, + }; + if is_self_eval { + let existing_types = get_runtime_types(scope); + let updated_types = assoc_value(existing_types, &name_s, descriptor, budget)?; + let runtime_obj = scope.get("runtime").unwrap_or(Value::null()); + let updated_runtime = assoc_value(runtime_obj, "types", updated_types, budget)?; + bindings.insert("runtime".to_string(), updated_runtime); } - _ => Err(Error::BadSyntax { - proc: "deftype", args: arg.to_string(), - expected: "deftype requires [name, base_or_shape, pred_or_opts]", - }), + + Ok(bindings) } } @@ -1020,11 +1057,23 @@ fn defgeneric_bindings( _scope: &Scope, ) -> Result> { // Parse name and optional config - let (name, config) = match arg { - Value::String(s) => (s.to_string(), None), - Value::Array(arr) => match arr.as_ref() { - [Value::String(s)] => (s.to_string(), None), - [Value::String(s), cfg] => (s.to_string(), Some(cfg.clone())), + let (name, config) = match arg.view() { + NanView::String(s) => (s.to_string(), None), + NanView::Array(ha) => match ha.data.as_slice() { + [first] => match first.view() { + NanView::String(s) => (s.to_string(), None), + _ => return Err(Error::BadSyntax { + proc: "defgeneric", args: arg.to_string(), + expected: "defgeneric requires [name] or [name, config]", + }), + }, + [first, cfg] => match first.view() { + NanView::String(s) => (s.to_string(), Some(cfg.clone())), + _ => return Err(Error::BadSyntax { + proc: "defgeneric", args: arg.to_string(), + expected: "defgeneric requires [name] or [name, config]", + }), + }, _ => return Err(Error::BadSyntax { proc: "defgeneric", args: arg.to_string(), expected: "defgeneric requires [name] or [name, config]", @@ -1039,12 +1088,12 @@ fn defgeneric_bindings( // Parse config fields from the config object let get_field = |key: &str| -> Option { config.as_ref().and_then(|c| { - if let Value::Object(o) = c { o.get(key).cloned() } else { None } + if let NanView::Object(o) = c.view() { o.get(key).cloned() } else { None } }) }; let get_str_field = |key: &str| -> Option { get_field(key).and_then(|v| { - if let Value::String(s) = v { Some(s.to_string()) } else { None } + if let NanView::String(s) = v.view() { Some(s.to_string()) } else { None } }) }; let identity = get_field("identity"); @@ -1071,14 +1120,14 @@ fn defgeneric_bindings( (Some("negate"), _) => { // {"-": [0, x]} — unary negate via self-call with 2 args let body = Value::call(&name, Value::array(vec![ - Value::Int(0), var_x, + Value::int(0), var_x, ], budget)?, budget)?; default_clauses.push(Value::array(vec![one_pat, body], budget)?); } (Some("reciprocal"), _) => { // {"/": [1, x]} — unary reciprocal via self-call with 2 args let body = Value::call(&name, Value::array(vec![ - Value::Int(1), var_x, + Value::int(1), var_x, ], budget)?, budget)?; default_clauses.push(Value::array(vec![one_pat, body], budget)?); } @@ -1122,7 +1171,7 @@ fn defgeneric_bindings( let rest_list = Value::call("cons", Value::array(vec![var_hd2, var_tl], budget)?, budget)?; let rest_call = Value::call("apply", Value::array(vec![self_var, rest_list], budget)?, budget)?; let pairwise_body = Value::call("if", Value::array(vec![ - binary_call, rest_call, Value::Bool(false), + binary_call, rest_call, Value::bool(false), ], budget)?, budget)?; default_clauses.push(Value::array(vec![cons_outer, pairwise_body], budget)?); } @@ -1242,16 +1291,16 @@ fn build_multi_clause( // Process each clause for clause_val in clauses { - let clause_arr = match clause_val { - Value::Array(a) if a.len() == 2 => a, + let clause_ha = match clause_val.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return Err(Error::BadSyntax { proc: if is_applicative { "fn*" } else { "vau*" }, args: clause_val.to_string(), expected: "each clause must be [pattern, body]", }), }; - let pattern = &clause_arr[0]; - let body = &clause_arr[1]; + let pattern = &clause_ha.data[0]; + let body = &clause_ha.data[1]; // Strip guards from pattern to get handler formals let formals = strip_guards_for_formals(pattern, budget)?; @@ -1280,18 +1329,18 @@ fn build_multi_clause( /// `{"guard": [inner, pred]}` → recurse on inner. Literals → wildcard. /// `{"var": name}` → keep. Arrays → recurse elements. fn strip_guards_for_formals(pat: &Value, budget: &Budget) -> Result { - match pat { - Value::Array(arr) => { - let stripped: Vec = arr.iter() + match pat.view() { + NanView::Array(ha) => { + let stripped: Vec = ha.data.iter() .map(|elem| strip_guards_for_formals(elem, budget)) .collect::>()?; Value::array(stripped, budget) } _ => { if let Some(("guard", guard_args)) = pat.as_call() { - if let Value::Array(arr) = guard_args { - if arr.len() == 2 { - return strip_guards_for_formals(&arr[0], budget); + if let NanView::Array(ha) = guard_args.view() { + if ha.data.len() == 2 { + return strip_guards_for_formals(&ha.data[0], budget); } } } @@ -1299,7 +1348,7 @@ fn strip_guards_for_formals(pat: &Value, budget: &Budget) -> Result { Ok(pat.clone()) // var pattern: keep for binding } else { // Literal or other: replace with wildcard (no binding needed) - Value::call("var", Value::Null, budget) + Value::call("var", Value::null(), budget) } } } @@ -1404,22 +1453,22 @@ fn defmethod_bindings( eval_limit: &mut usize, scope: &Scope, ) -> Result> { - let arr = match arg { - Value::Array(arr) if arr.len() == 3 => arr, + let arr = match arg.view() { + NanView::Array(ha) if ha.data.len() == 3 => ha, _ => return Err(Error::BadSyntax { proc: "defmethod", args: arg.to_string(), expected: "defmethod requires [name, pattern, body]", }), }; - let name = match &arr[0] { - Value::String(s) => s.to_string(), + let name = match arr.data[0].view() { + NanView::String(s) => s.to_string(), _ => return Err(Error::BadSyntax { proc: "defmethod", args: arg.to_string(), expected: "defmethod requires a string name", }), }; - let pattern = &arr[1]; - let body_expr = &arr[2]; + let pattern = &arr.data[1]; + let body_expr = &arr.data[2]; // Get the current callable from scope let callable = scope.get(&name).ok_or_else(|| Error::UndefinedName(name.clone()))?; @@ -1439,13 +1488,13 @@ fn count_mu_scope_methods(fn_val: &Value) -> usize { _ => return 0, }; let mu_arr = match inner.as_call() { - Some(("mu", arg)) => match arg { Value::Array(a) => a, _ => return 0 }, - Some(("vau", arg)) => match arg { - Value::Array(a) if a.len() > 2 => { + Some(("mu", arg)) => match arg.view() { NanView::Array(ha) => ha, _ => return 0 }, + Some(("vau", arg)) => match arg.view() { + NanView::Array(ha) if ha.data.len() > 2 => { // Count keys in vau scope_obj that start with "_m" - return match a[2].as_call() { - Some(("scope", inner)) => match inner { - Value::Object(o) => o.iter().filter(|(k, _)| k.starts_with("_m")).count(), + return match ha.data[2].as_call() { + Some(("scope", inner)) => match inner.view() { + NanView::Object(o) => o.iter().filter(|(k, _)| k.starts_with("_m")).count(), _ => 0, }, _ => 0, @@ -1456,10 +1505,10 @@ fn count_mu_scope_methods(fn_val: &Value) -> usize { _ => return 0, }; // For mu: count _m* keys in scope_obj (3rd element) - if mu_arr.len() > 2 { - match mu_arr[2].as_call() { - Some(("scope", inner)) => match inner { - Value::Object(o) => o.iter().filter(|(k, _)| k.starts_with("_m")).count(), + if mu_arr.data.len() > 2 { + match mu_arr.data[2].as_call() { + Some(("scope", inner)) => match inner.view() { + NanView::Object(o) => o.iter().filter(|(k, _)| k.starts_with("_m")).count(), _ => 0, }, _ => 0, @@ -1533,7 +1582,7 @@ fn convert_to_extensible(callable: Value, budget: &Budget) -> Result { let var_default = Value::call("var", Value::string("_default", budget)?, budget)?; let apply_args = Value::array(vec![var_default, var_args.clone()], budget)?; let catchall_body = Value::call("apply", apply_args, budget)?; - let wildcard = Value::call("var", Value::Null, budget)?; + let wildcard = Value::call("var", Value::null(), budget)?; let catchall = Value::array(vec![wildcard, catchall_body], budget)?; // Build match body @@ -1579,16 +1628,16 @@ fn insert_clause_mu( scope: &Scope, ) -> Result { // Extract mu parts: [defs, identity, scope_obj] - let mu_arr = match mu_arg { - Value::Array(a) if a.len() >= 3 => a, + let mu_arr = match mu_arg.view() { + NanView::Array(ha) if ha.data.len() >= 3 => ha, _ => return Err(Error::MalformedFnValue("mu value must be [defs, identity, scope_obj]".to_string())), }; - let defs = &mu_arr[0]; - let identity = &mu_arr[1]; - let scope_obj = &mu_arr[2]; + let defs = &mu_arr.data[0]; + let identity = &mu_arr.data[1]; + let scope_obj = &mu_arr.data[2]; - let identity_str = match identity { - Value::String(s) => s.to_string(), + let identity_str = match identity.view() { + NanView::String(s) => s.to_string(), _ => return Err(Error::MalformedFnValue("mu identity must be a string".to_string())), }; @@ -1600,7 +1649,7 @@ fn insert_clause_mu( "mu defs key '{}' doesn't match identity '{}'", k, identity_str ))); } - } else if let Value::Object(o) = defs { + } else if let NanView::Object(o) = defs.view() { o.get(&identity_str).cloned().ok_or_else(|| Error::MalformedFnValue( format!("cannot find '{}' in mu defs", identity_str) ))? @@ -1608,13 +1657,13 @@ fn insert_clause_mu( return Err(Error::MalformedFnValue("mu defs must be an object".to_string())); }; - let entry_arr = match &fn_entry { - Value::Array(a) if a.len() == 3 => a, + let entry_ha = match fn_entry.view() { + NanView::Array(ha) if ha.data.len() == 3 => ha, _ => return Err(Error::MalformedFnValue("mu entry must be [kind, formals, body]".to_string())), }; - let kind = &entry_arr[0]; - let formals = &entry_arr[1]; - let body = &entry_arr[2]; + let kind = &entry_ha.data[0]; + let formals = &entry_ha.data[1]; + let body = &entry_ha.data[2]; // Extract match from body: {"match": [scrutinee, clauses]} let (scrutinee, clauses_vec) = extract_match_clauses(body, budget)?; @@ -1643,8 +1692,8 @@ fn insert_clause_mu( let new_defs = Value::call(&identity_str, new_entry, budget)?; let mut mu_parts = vec![new_defs, identity.clone(), updated_scope]; - if mu_arr.len() > 3 { - mu_parts.push(mu_arr[3].clone()); // bytecode if present + if mu_arr.data.len() > 3 { + mu_parts.push(mu_arr.data[3].clone()); // bytecode if present } let new_mu = Value::call("mu", Value::array(mu_parts, budget)?, budget)?; Value::call("wrap", new_mu, budget) @@ -1666,13 +1715,13 @@ fn convert_guards_to_lazy( ) -> Result { match pattern.as_call() { Some(("guard", arg)) => { - if let Value::Array(arr) = arg { - if arr.len() == 2 { - let inner_pat = &arr[0]; - let pred_expr = &arr[1]; + if let NanView::Array(ha) = arg.view() { + if ha.data.len() == 2 { + let inner_pat = &ha.data[0]; + let pred_expr = &ha.data[1]; // Check if predicate is a global name let is_global = if let Some(("var", name_val)) = pred_expr.as_call() { - if let Value::String(name) = name_val { + if let NanView::String(name) = name_val.view() { scope.globals().get(name).is_some() } else { false } } else { false }; @@ -1694,9 +1743,9 @@ fn convert_guards_to_lazy( } _ => { // Recursively convert arrays - match pattern { - Value::Array(arr) => { - let converted: Vec = arr.iter() + match pattern.view() { + NanView::Array(ha) => { + let converted: Vec = ha.data.iter() .map(|elem| convert_guards_to_lazy(elem, budget, pred_names, scope, eval_limit)) .collect::>()?; Value::array(converted, budget) @@ -1710,13 +1759,13 @@ fn convert_guards_to_lazy( /// Extract match clauses from a match body: {"match": [scrutinee, clauses]}. fn extract_match_clauses(body: &Value, _budget: &Budget) -> Result<(Value, Vec)> { match body.as_call() { - Some(("match", arg)) => match arg { - Value::Array(a) if a.len() == 2 => { - let clauses = match &a[1] { - Value::Array(arr) => arr.iter().cloned().collect(), + Some(("match", arg)) => match arg.view() { + NanView::Array(ha) if ha.data.len() == 2 => { + let clauses = match ha.data[1].view() { + NanView::Array(ca) => ca.data.iter().cloned().collect(), _ => vec![], }; - Ok((a[0].clone(), clauses)) + Ok((ha.data[0].clone(), clauses)) } _ => Err(Error::MalformedFnValue("match must have [scrutinee, clauses]".to_string())), }, @@ -1734,8 +1783,8 @@ fn insert_by_specificity( let new_spec = super::generic::pattern_specificity_with_scope(new_pattern, scope); let mut insert_pos = clauses.len(); for (i, clause) in clauses.iter().enumerate() { - if let Value::Array(c) = clause { - if let Some(existing_pat) = c.first() { + if let NanView::Array(c) = clause.view() { + if let Some(existing_pat) = c.data.first() { let existing_spec = super::generic::pattern_specificity_with_scope(existing_pat, scope); if new_spec >= existing_spec { insert_pos = i; @@ -1758,10 +1807,17 @@ pub fn op_def_binding( scope: Scope, ) -> Result { match args { - [Value::String(name), expr] => { - let val = eval_trampoline(budget, eval_limit, expr.clone(), scope)?; - make_binding_result([(name.to_string(), val)].into_iter().collect(), budget).map(Eval::Done) - } + [name_val, expr] => match name_val.view() { + NanView::String(name) => { + let val = eval_trampoline(budget, eval_limit, expr.clone(), scope)?; + make_binding_result([(name.to_string(), val)].into_iter().collect(), budget).map(Eval::Done) + } + _ => Err(Error::BadSyntax { + proc: "def", + args: format!("{:?}", args.iter().map(|v| v.to_string()).collect::>()), + expected: "def requires [name_string, expr]", + }), + }, _ => Err(Error::BadSyntax { proc: "def", args: format!("{:?}", args.iter().map(|v| v.to_string()).collect::>()), @@ -1821,30 +1877,40 @@ pub fn op_deftype_binding( scope: Scope, ) -> Result { match args { - [Value::String(name), Value::String(base_family), pred_expr] => { - // Legacy form: ["name", "base_family_string", pred_expr] - let _ = base_family; // base_family is unused at runtime (type checker uses it) - let pred = eval_trampoline(budget, eval_limit, pred_expr.clone(), scope)?; - let pred_name = format!("{}?", &**name); - make_binding_result( - [(pred_name, pred)].into_iter().collect(), - budget, - ) - .map(Eval::Done) - } - [Value::String(name), shape, opts_expr] => { + [name_val, second, third] => { + let name_s = match name_val.view() { + NanView::String(s) => s.to_string(), + _ => return Err(Error::BadSyntax { + proc: "deftype", + args: format!("{:?}", args.iter().map(|v| v.to_string()).collect::>()), + expected: "deftype requires [name, base_or_shape, pred_or_opts]", + }), + }; + let is_legacy = matches!(second.view(), NanView::String(_)); + if is_legacy { + // Legacy form: ["name", "base_family_string", pred_expr] + let pred = eval_trampoline(budget, eval_limit, third.clone(), scope)?; + let pred_name = format!("{}?", name_s); + return make_binding_result( + [(pred_name, pred)].into_iter().collect(), + budget, + ) + .map(Eval::Done); + } // Extended form: ["name", shape_array_or_null, opts_obj] + let shape = second; + let opts_expr = third; // opts is treated as a literal object with individual values evaluated. let opts = eval_opts_object(opts_expr, budget, eval_limit, &scope)?; - let name_s: std::string::String = name.to_string(); // Validate "refines" if specified - if let Value::Object(obj) = &opts - && let Some(Value::String(parent_name)) = obj.get("refines") + if let NanView::Object(obj) = opts.view() + && let Some(parent_name_val) = obj.get("refines") + && let NanView::String(parent_name) = parent_name_val.view() { // Check that parent type exists in runtime.types let types_registry = get_runtime_types(&scope); - let parent_exists = matches!(&types_registry, Value::Object(reg_obj) if reg_obj.get(parent_name).is_some()); + let parent_exists = matches!(types_registry.view(), NanView::Object(reg_obj) if reg_obj.get(parent_name).is_some()); if !parent_exists { return Err(Error::BadSyntax { proc: "deftype", @@ -1866,8 +1932,8 @@ pub fn op_deftype_binding( bindings.insert(pred_name, pred_fn); // Check if self-eval — register in type registry - let is_self_eval = match &opts { - Value::Object(obj) => matches!(obj.get("self-eval"), Some(Value::Bool(true))), + let is_self_eval = match opts.view() { + NanView::Object(obj) => matches!(obj.get("self-eval").map(|v| v.view()), Some(NanView::Bool(true))), _ => false, }; if is_self_eval { @@ -1876,7 +1942,7 @@ pub fn op_deftype_binding( let updated_types = assoc_value(existing_types, &name_s, descriptor, budget)?; // Build updated runtime object - let runtime_obj = scope.get("runtime").unwrap_or(Value::Null); + let runtime_obj = scope.get("runtime").unwrap_or(Value::null()); let updated_runtime = assoc_value(runtime_obj, "types", updated_types, budget)?; bindings.insert("runtime".to_string(), updated_runtime); } @@ -1910,8 +1976,8 @@ fn eval_opts_object( // Handle the singleton object case (e.g., {"self-eval": true}) // and multi-key object case - let pairs: Vec<(&str, &Value)> = match opts_expr { - Value::Object(obj) => obj.iter().map(|(k, v)| (&**k as &str, v)).collect(), + let pairs: Vec<(&str, &Value)> = match opts_expr.view() { + NanView::Object(ho) => ho.iter().map(|(k, v)| (&**k as &str, v)).collect(), _ => { // If not an object, evaluate it (could be a variable reference) let evaled = eval_trampoline(budget, eval_limit, opts_expr.clone(), scope.clone())?; @@ -1945,25 +2011,25 @@ fn build_type_descriptor(shape: &Value, opts: &Value, budget: &Budget) -> Result // Extract normalizer from opts (default null) let normalizer_key = RStr::new("normalizer", budget)?; - let normalizer = match opts { - Value::Object(obj) => obj.get("normalizer").cloned().unwrap_or(Value::Null), - _ => Value::Null, + let normalizer = match opts.view() { + NanView::Object(obj) => obj.get("normalizer").cloned().unwrap_or(Value::null()), + _ => Value::null(), }; pairs.push((normalizer_key, normalizer)); // self-eval flag let se_key = RStr::new("self-eval", budget)?; - let se_val = match opts { - Value::Object(obj) => obj.get("self-eval").cloned().unwrap_or(Value::Bool(false)), - _ => Value::Bool(false), + let se_val = match opts.view() { + NanView::Object(obj) => obj.get("self-eval").cloned().unwrap_or(Value::bool(false)), + _ => Value::bool(false), }; pairs.push((se_key, se_val)); // refines (parent type name, if any) let refines_key = RStr::new("refines", budget)?; - let refines_val = match opts { - Value::Object(obj) => obj.get("refines").cloned().unwrap_or(Value::Null), - _ => Value::Null, + let refines_val = match opts.view() { + NanView::Object(obj) => obj.get("refines").cloned().unwrap_or(Value::null()), + _ => Value::null(), }; pairs.push((refines_key, refines_val)); @@ -1986,7 +2052,7 @@ fn make_type_predicate( use crate::runtime::String as RStr; // 1. Build the test fn (same logic as before) - let has_shape = !matches!(shape, Value::Null); + let has_shape = !matches!(shape.view(), NanView::Null); let test_fn = if has_shape { let pred_src = build_shape_predicate_source(type_name, shape, budget)?; crate::runtime::eval_trampoline(budget, eval_limit, pred_src, scope.clone())? @@ -1996,14 +2062,15 @@ fn make_type_predicate( }; // 2. Look up parent type value if refines is specified - let refines_val = if let Value::Object(obj) = opts - && let Some(Value::String(parent_name)) = obj.get("refines") + let refines_val = if let NanView::Object(obj) = opts.view() + && let Some(parent_name_val) = obj.get("refines") + && let NanView::String(parent_name) = parent_name_val.view() { // Look up "{parent}?" in scope to get the parent runtime/type value let parent_pred_name = format!("{}?", &**parent_name); - scope.get(&parent_pred_name).unwrap_or(Value::Null) + scope.get(&parent_pred_name).unwrap_or(Value::null()) } else { - Value::Null + Value::null() }; // 3. Build runtime/type value: {"type": {"name": n, "test": fn, "refines": parent}} @@ -2021,7 +2088,7 @@ fn build_tag_predicate_source(type_name: &str, budget: &Budget) -> Result // {"not": {"null?": {"match-result": [{"var": "__x"}, // {"pattern": {"": {"var": null}}}]}}}]} let var_x = Value::call("var", Value::string("__x", budget)?, budget)?; - let wildcard = Value::call("var", Value::Null, budget)?; + let wildcard = Value::call("var", Value::null(), budget)?; let tag_pat = Value::call(type_name, wildcard, budget)?; let rt_pat = Value::call("pattern", tag_pat, budget)?; let mr = make_call("match-result", vec![var_x.clone(), rt_pat], budget)?; @@ -2061,7 +2128,7 @@ fn build_shape_predicate_source(type_name: &str, shape: &Value, budget: &Budget) // Build shape validation for payload let shape_check = build_shape_check_expr(&get_payload, shape, budget)?; - let if_expr = make_call("if", vec![is_null, Value::Bool(false), shape_check], budget)?; + let if_expr = make_call("if", vec![is_null, Value::bool(false), shape_check], budget)?; // let* binding let binding = Value::array(vec![Value::string("__mr", budget)?, mr], budget)?; @@ -2076,15 +2143,15 @@ fn build_shape_predicate_source(type_name: &str, shape: &Value, budget: &Budget) /// Build an expression that validates payload against shape spec. fn build_shape_check_expr(payload_expr: &Value, shape: &Value, budget: &Budget) -> Result { - let shape_arr = match shape { - Value::Array(arr) => arr, - _ => return Ok(Value::Bool(true)), + let shape_arr = match shape.view() { + NanView::Array(ha) => ha, + _ => return Ok(Value::bool(true)), }; - if shape_arr.is_empty() { + if shape_arr.data.is_empty() { // Empty shape = must be empty array let count_expr = make_call("count", vec![payload_expr.clone()], budget)?; - return make_call("==", vec![count_expr, Value::Int(0)], budget); + return make_call("==", vec![count_expr, Value::int(0)], budget); } // Build: {"and": [{"==": [count(payload), N]}, check_0, check_1, ...]} @@ -2092,16 +2159,16 @@ fn build_shape_check_expr(payload_expr: &Value, shape: &Value, budget: &Budget) // check count first, then each element let count_check = make_call("==", vec![ make_call("count", vec![payload_expr.clone()], budget)?, - Value::Int(shape_arr.len() as i64), + Value::int(shape_arr.data.len() as i64), ], budget)?; let mut checks = vec![count_check]; - for (i, spec) in shape_arr.iter().enumerate() { - let expected_family = match spec { - Value::Array(spec_arr) if spec_arr.len() == 1 => { - match &spec_arr[0] { - Value::String(s) => s.to_string(), + for (i, spec) in shape_arr.data.iter().enumerate() { + let expected_family = match spec.view() { + NanView::Array(spec_ha) if spec_ha.data.len() == 1 => { + match spec_ha.data[0].view() { + NanView::String(s) => s.to_string(), _ => continue, } } @@ -2114,7 +2181,7 @@ fn build_shape_check_expr(payload_expr: &Value, shape: &Value, budget: &Budget) // Build typeof check for element i let nth_expr = make_call("nth", vec![ payload_expr.clone(), - Value::Int(i as i64), + Value::int(i as i64), ], budget)?; let typeof_expr = make_call("typeof", vec![nth_expr], budget)?; let get_type = make_call("get", vec![typeof_expr, Value::string("type", budget)?], budget)?; @@ -2142,8 +2209,8 @@ fn build_shape_check_expr(payload_expr: &Value, shape: &Value, budget: &Budget) /// Assoc a key-value pair into an object value (or create a new one). fn assoc_value(base: Value, key: &str, val: Value, budget: &Budget) -> Result { use crate::runtime::String as RStr; - let mut pairs: Vec<(crate::runtime::String, Value)> = match base { - Value::Object(obj) => obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect(), + let mut pairs: Vec<(crate::runtime::String, Value)> = match base.view() { + NanView::Object(obj) => obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect(), _ => Vec::new(), }; // Remove existing key if present @@ -2162,9 +2229,9 @@ pub(crate) fn contains_var(val: &Value) -> bool { } return contains_var(arg); } - match val { - Value::Array(arr) => arr.iter().any(contains_var), - Value::Object(obj) => obj.values().any(contains_var), + match val.view() { + NanView::Array(ha) => ha.data.iter().any(contains_var), + NanView::Object(ho) => ho.values().any(contains_var), _ => false, } } @@ -2262,13 +2329,13 @@ fn try_arith_inversion( } // pat_arg must be a 2-element array - let arr = match pat_arg { - Value::Array(a) if a.len() == 2 => a, + let arr = match pat_arg.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return Ok(None), }; - let left = &arr[0]; - let right = &arr[1]; + let left = &arr.data[0]; + let right = &arr.data[1]; let left_has_var = contains_var(left); let right_has_var = contains_var(right); @@ -2349,22 +2416,22 @@ fn try_cons_inversion( scope: Scope, ) -> Result> { // pat_arg must be a 2-element array [head_pat, tail_pat] - let arr = match pat_arg { - Value::Array(a) if a.len() == 2 => a, + let arr = match pat_arg.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return Ok(None), }; // value must be a non-empty array - let val_arr = match value { - Value::Array(a) if !a.is_empty() => a, - Value::Array(_) => return Ok(Some(false)), // empty array doesn't match cons + let val_arr = match value.view() { + NanView::Array(ha) if !ha.data.is_empty() => ha, + NanView::Array(_) => return Ok(Some(false)), // empty array doesn't match cons _ => return Ok(None), // not an array at all }; - let head_pat = &arr[0]; - let tail_pat = &arr[1]; - let head_val = &val_arr[0]; - let tail_items: Vec = val_arr[1..].to_vec(); + let head_pat = &arr.data[0]; + let tail_pat = &arr.data[1]; + let head_val = &val_arr.data[0]; + let tail_items: Vec = val_arr.data[1..].to_vec(); let tail_val = Value::array(tail_items, budget)?; if !try_match(head_pat, head_val, bindings, budget, eval_limit, scope.clone())? { @@ -2379,9 +2446,9 @@ fn try_cons_inversion( /// Stringify a value the same way the `str` builtin does: /// strings → raw content, everything else → JSON representation (via Display). pub(crate) fn stringify_value(val: &Value) -> std::string::String { - match val { - Value::String(s) => s.to_string(), - other => other.to_string(), + match val.view() { + NanView::String(s) => s.to_string(), + _ => val.to_string(), } } @@ -2398,14 +2465,14 @@ fn try_str_inversion( eval_limit: &mut usize, scope: Scope, ) -> Result> { - let segments = match pat_arg { - Value::Array(a) if !a.is_empty() => a, + let segments = match pat_arg.view() { + NanView::Array(ha) if !ha.data.is_empty() => ha, _ => return Ok(None), }; // Partition segments: find the one var-containing segment let mut var_index = None; - for (i, seg) in segments.iter().enumerate() { + for (i, seg) in segments.data.iter().enumerate() { if contains_var(seg) { if var_index.is_some() { return Ok(None); // 2+ var segments — underdetermined @@ -2417,7 +2484,7 @@ fn try_str_inversion( // Both-ground case: forward-eval all segments, concat, compare let Some(vi) = var_index else { let mut result = std::string::String::new(); - for seg in segments.iter() { + for seg in segments.data.iter() { let val = eval_trampoline(budget, eval_limit, seg.clone(), scope.clone())?; result.push_str(&stringify_value(&val)); } @@ -2426,20 +2493,20 @@ fn try_str_inversion( }; // Target must be a string - let target_str = match value { - Value::String(s) => s, + let target_str = match value.view() { + NanView::String(s) => s, _ => return Ok(Some(false)), }; let target: &str = target_str; // Evaluate ground segments before and after the var segment let mut prefix = std::string::String::new(); - for seg in &segments[..vi] { + for seg in &segments.data[..vi] { let val = eval_trampoline(budget, eval_limit, seg.clone(), scope.clone())?; prefix.push_str(&stringify_value(&val)); } let mut suffix = std::string::String::new(); - for seg in &segments[vi + 1..] { + for seg in &segments.data[vi + 1..] { let val = eval_trampoline(budget, eval_limit, seg.clone(), scope.clone())?; suffix.push_str(&stringify_value(&val)); } @@ -2461,7 +2528,7 @@ fn try_str_inversion( // Recursively match the var-containing segment against the middle Ok(Some(try_match( - &segments[vi], &middle_val, bindings, budget, eval_limit, scope, + &segments.data[vi], &middle_val, bindings, budget, eval_limit, scope, )?)) } @@ -2475,14 +2542,14 @@ fn try_concat_inversion( eval_limit: &mut usize, scope: Scope, ) -> Result> { - let segments = match pat_arg { - Value::Array(a) if !a.is_empty() => a, + let segments = match pat_arg.view() { + NanView::Array(ha) if !ha.data.is_empty() => ha, _ => return Ok(None), }; // Partition: find the one var-containing segment let mut var_index = None; - for (i, seg) in segments.iter().enumerate() { + for (i, seg) in segments.data.iter().enumerate() { if contains_var(seg) { if var_index.is_some() { return Ok(None); // 2+ var segments — underdetermined @@ -2494,10 +2561,10 @@ fn try_concat_inversion( // Both-ground case: forward-eval all, flatten, compare let Some(vi) = var_index else { let mut result_items = Vec::new(); - for seg in segments.iter() { + for seg in segments.data.iter() { let val = eval_trampoline(budget, eval_limit, seg.clone(), scope.clone())?; - match val { - Value::Array(arr) => result_items.extend(arr.iter().cloned()), + match val.view() { + NanView::Array(ha) => result_items.extend(ha.data.iter().cloned()), _ => return Ok(Some(false)), // concat expects arrays } } @@ -2506,58 +2573,58 @@ fn try_concat_inversion( }; // Target must be an array - let target_arr = match value { - Value::Array(a) => a, + let target_arr = match value.view() { + NanView::Array(ha) => ha, _ => return Ok(Some(false)), }; // Evaluate ground segments before the var to get prefix elements let mut prefix_elems = Vec::new(); - for seg in &segments[..vi] { + for seg in &segments.data[..vi] { let val = eval_trampoline(budget, eval_limit, seg.clone(), scope.clone())?; - match val { - Value::Array(arr) => prefix_elems.extend(arr.iter().cloned()), + match val.view() { + NanView::Array(ha) => prefix_elems.extend(ha.data.iter().cloned()), _ => return Ok(Some(false)), } } // Evaluate ground segments after the var to get suffix elements let mut suffix_elems = Vec::new(); - for seg in &segments[vi + 1..] { + for seg in &segments.data[vi + 1..] { let val = eval_trampoline(budget, eval_limit, seg.clone(), scope.clone())?; - match val { - Value::Array(arr) => suffix_elems.extend(arr.iter().cloned()), + match val.view() { + NanView::Array(ha) => suffix_elems.extend(ha.data.iter().cloned()), _ => return Ok(Some(false)), } } // Check prefix + suffix fit in the target - if prefix_elems.len() + suffix_elems.len() > target_arr.len() { + if prefix_elems.len() + suffix_elems.len() > target_arr.data.len() { return Ok(Some(false)); } // Verify prefix matches for (i, elem) in prefix_elems.iter().enumerate() { - if &target_arr[i] != elem { + if &target_arr.data[i] != elem { return Ok(Some(false)); } } // Verify suffix matches - let suffix_start = target_arr.len() - suffix_elems.len(); + let suffix_start = target_arr.data.len() - suffix_elems.len(); for (i, elem) in suffix_elems.iter().enumerate() { - if &target_arr[suffix_start + i] != elem { + if &target_arr.data[suffix_start + i] != elem { return Ok(Some(false)); } } // Extract middle - let middle_items: Vec = target_arr[prefix_elems.len()..suffix_start].to_vec(); + let middle_items: Vec = target_arr.data[prefix_elems.len()..suffix_start].to_vec(); let middle_val = Value::array(middle_items, budget)?; // Recursively match var segment against the middle array Ok(Some(try_match( - &segments[vi], &middle_val, bindings, budget, eval_limit, scope, + &segments.data[vi], &middle_val, bindings, budget, eval_limit, scope, )?)) } @@ -2573,14 +2640,14 @@ fn try_assoc_inversion( eval_limit: &mut usize, scope: Scope, ) -> Result> { - let arr = match pat_arg { - Value::Array(a) if a.len() == 3 => a, + let arr = match pat_arg.view() { + NanView::Array(ha) if ha.data.len() == 3 => ha, _ => return Ok(None), }; - let base_pat = &arr[0]; - let key_expr = &arr[1]; - let val_pat = &arr[2]; + let base_pat = &arr.data[0]; + let key_expr = &arr.data[1]; + let val_pat = &arr.data[2]; // Key must be ground (no vars) if contains_var(key_expr) { @@ -2589,14 +2656,14 @@ fn try_assoc_inversion( // Evaluate key to get the key string let key_val = eval_trampoline(budget, eval_limit, key_expr.clone(), scope.clone())?; - let key_str = match &key_val { - Value::String(s) => s.to_string(), + let key_str = match key_val.view() { + NanView::String(s) => s.to_string(), _ => return Ok(Some(false)), // key must be a string }; // Target must be an object with that key - let target_obj = match value { - Value::Object(obj) => obj, + let target_obj = match value.view() { + NanView::Object(ho) => ho, _ => return Ok(Some(false)), }; @@ -2640,9 +2707,9 @@ fn try_match_call( ) -> Result { match pat_key { // var binding: {"var": "name"} or {"var": null} - "var" => match pat_arg { - Value::Null => Ok(true), // wildcard - Value::String(name) => { + "var" => match pat_arg.view() { + NanView::Null => Ok(true), // wildcard + NanView::String(name) => { bindings.insert(name.to_string(), value.clone()); Ok(true) } @@ -2651,8 +2718,9 @@ fn try_match_call( // Composite patterns: {"and-pattern": [p1, p2]} "and-pattern" => { - if let Value::Array(arr) = pat_arg - && arr.len() == 2 { + if let NanView::Array(ha) = pat_arg.view() + && ha.data.len() == 2 { + let arr = &ha.data; let mut bindings1 = HashMap::new(); if !try_match(&arr[0], value, &mut bindings1, budget, eval_limit, scope.clone())? { return Ok(false); @@ -2670,8 +2738,9 @@ fn try_match_call( // Composite patterns: {"or-pattern": [p1, p2]} "or-pattern" => { - if let Value::Array(arr) = pat_arg - && arr.len() == 2 { + if let NanView::Array(ha) = pat_arg.view() + && ha.data.len() == 2 { + let arr = &ha.data; let mut bindings1 = HashMap::new(); if try_match(&arr[0], value, &mut bindings1, budget, eval_limit, scope.clone())? { bindings.extend(bindings1); @@ -2693,8 +2762,9 @@ fn try_match_call( // Distinct from "guard" (which is structural matching) and "guard-pattern" // (which has pre-resolved predicate values). "lazy-guard" => { - if let Value::Array(arr) = pat_arg - && arr.len() == 2 { + if let NanView::Array(ha) = pat_arg.view() + && ha.data.len() == 2 { + let arr = &ha.data; let mut inner_bindings = HashMap::new(); if !try_match(&arr[0], value, &mut inner_bindings, budget, eval_limit, scope.clone())? { return Ok(false); @@ -2723,8 +2793,9 @@ fn try_match_call( // Composite patterns: {"guard-pattern": [p, pred_fn]} "guard-pattern" => { - if let Value::Array(arr) = pat_arg - && arr.len() == 2 { + if let NanView::Array(ha) = pat_arg.view() + && ha.data.len() == 2 { + let arr = &ha.data; let mut inner_bindings = HashMap::new(); if !try_match(&arr[0], value, &mut inner_bindings, budget, eval_limit, scope.clone())? { return Ok(false); @@ -2838,26 +2909,26 @@ pub(crate) fn try_match( if let Some((pat_key, pat_arg)) = pattern.as_call() { return try_match_call(pat_key, pat_arg, value, bindings, budget, eval_limit, scope); } - match pattern { + match pattern.view() { // ── Literal patterns ───────────────────────────────────────────── - Value::Null => Ok(value == &Value::Null), - Value::Bool(b) => Ok(matches!(value, Value::Bool(vb) if vb == b)), - Value::Int(_) | Value::Float(_) | Value::BigNum(_) => Ok(pattern == value), - Value::String(s) => Ok(matches!(value, Value::String(vs) if vs == s)), + NanView::Null => Ok(matches!(value.view(), NanView::Null)), + NanView::Bool(b) => Ok(matches!(value.view(), NanView::Bool(vb) if vb == b)), + NanView::Int(_) | NanView::Float(_) | NanView::BigNum(_) => Ok(pattern == value), + NanView::String(s) => Ok(matches!(value.view(), NanView::String(vs) if vs == s)), // ── Array patterns ─────────────────────────────────────────────── - Value::Array(pat_arr) => { - let val_arr = match value { - Value::Array(a) => a, + NanView::Array(pat_ha) => { + let val_ha = match value.view() { + NanView::Array(ha) => ha, _ => return Ok(false), }; { // Exact length match - if val_arr.len() != pat_arr.len() { + if val_ha.data.len() != pat_ha.data.len() { return Ok(false); } - for (pat_elem, val_elem) in pat_arr.iter().zip(val_arr.iter()) { + for (pat_elem, val_elem) in pat_ha.data.iter().zip(val_ha.data.iter()) { if !try_match(pat_elem, val_elem, bindings, budget, eval_limit, scope.clone())? { return Ok(false); } @@ -2868,11 +2939,11 @@ pub(crate) fn try_match( // ── Multi-key object patterns ──────────────────────────────────── // (Single-key objects handled above by as_call check.) - Value::Object(pat_obj) => { - match value { - Value::Object(val_obj) => { - for (key, pat_val) in pat_obj.iter() { - match val_obj.get(key) { + NanView::Object(pat_ho) => { + match value.view() { + NanView::Object(val_ho) => { + for (key, pat_val) in pat_ho.iter() { + match val_ho.get(key) { Some(v) => { if !try_match(pat_val, v, bindings, budget, eval_limit, scope.clone())? { return Ok(false); @@ -2905,7 +2976,7 @@ fn resolve_pattern( continue; } if key == "var" - && let Value::String(name) = arg + && let NanView::String(name) = arg.view() && let Some(val) = scope.get(name) && let Some(("pattern", inner)) = val.as_call() { current = inner.clone(); @@ -2933,14 +3004,14 @@ pub fn op_match_result( // Build result object: {"ok": true, ...bindings} let mut pairs = Vec::with_capacity(bindings.len() + 1); let ok_key = crate::runtime::String::new("ok", budget)?; - pairs.push((ok_key, Value::Bool(true))); + pairs.push((ok_key, Value::bool(true))); for (name, val) in bindings { let key = crate::runtime::String::new(&name, budget)?; pairs.push((key, val)); } Value::object(pairs, budget).map(Eval::Done) } else { - Eval::done(Value::Null) + Eval::done(Value::null()) } } _ => Err(Error::arity("match-result", "2", args.len())), @@ -2964,8 +3035,8 @@ pub fn op_match_clauses( ) -> Result { match args { [value, clauses_val, env_val] => { - let clauses = match clauses_val { - Value::Array(arr) => arr, + let clauses = match clauses_val.view() { + NanView::Array(ha) => ha, _ => return Err(Error::BadSyntax { proc: "_match-clauses", args: clauses_val.to_string(), expected: "clauses must be an array", @@ -2976,9 +3047,9 @@ pub fn op_match_clauses( // (same as `eval`'s 2-arg form via `value_to_scope`) let env_scope = value_to_scope(env_val.clone(), &scope)?; - for clause in clauses.iter() { - let (pat_val, body_val) = match clause { - Value::Array(arr) if arr.len() == 2 => (&arr[0], &arr[1]), + for clause in clauses.data.iter() { + let (pat_val, body_val) = match clause.view() { + NanView::Array(ha) if ha.data.len() == 2 => (&ha.data[0], &ha.data[1]), _ => continue, }; @@ -3010,9 +3081,9 @@ pub fn op_match_clauses( fn get_runtime_types(scope: &Scope) -> Value { scope .get("runtime") - .and_then(|rt| match rt { - Value::Object(obj) => obj.get("types").cloned(), + .and_then(|rt| match rt.view() { + NanView::Object(obj) => obj.get("types").cloned(), _ => None, }) - .unwrap_or(Value::Null) + .unwrap_or(Value::null()) } diff --git a/src/runtime/procs/convert.rs b/src/runtime/procs/convert.rs index 7b9c716..adb9400 100644 --- a/src/runtime/procs/convert.rs +++ b/src/runtime/procs/convert.rs @@ -1,3 +1,4 @@ +use crate::runtime::nanvalue::NanView; use crate::runtime::Scope; use crate::runtime::{Budget, Eval, Number, Value}; use crate::{Error, Result}; @@ -14,8 +15,8 @@ pub fn op_number( if arg.is_number() { return Eval::done(arg.clone()); } - match arg { - Value::String(s) => { + match arg.view() { + NanView::String(s) => { let s_str: &str = s; s_str.parse::() .map(Value::from_number) @@ -27,7 +28,7 @@ pub fn op_number( expected: "a string containing a valid number", }) } - other => Err(Error::type_err("number", 0, other, "a number or numeric string")), + _ => Err(Error::type_err("number", 0, arg, "a number or numeric string")), } } _ => Err(Error::arity("number", "1", args.len())), diff --git a/src/runtime/procs/forms.rs b/src/runtime/procs/forms.rs index b7b10a0..b34ae58 100644 --- a/src/runtime/procs/forms.rs +++ b/src/runtime/procs/forms.rs @@ -5,6 +5,7 @@ //! type checker (`collect.rs`) call these to avoid duplicating arg parsing. use crate::runtime::Value; +use crate::runtime::nanvalue::NanView; // ── Parsed structures ──────────────────────────────────────────────────────── @@ -56,18 +57,18 @@ pub enum DoStmtKind<'a> { /// /// Returns `Err` with a static message if the form is malformed. pub fn parse_def(arg: &Value) -> Result, &'static str> { - let arr = match arg { - Value::Array(arr) if arr.len() == 2 => arr, - Value::Array(_) => return Err("def requires [name, expr]"), + let arr = match arg.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, + NanView::Array(_) => return Err("def requires [name, expr]"), _ => return Err("def requires [name, expr]"), }; - let name = match &arr[0] { - Value::String(s) => s.to_string(), + let name = match arr.data[0].view() { + NanView::String(s) => s.to_string(), _ => return Err("def requires a string name at index 0"), }; Ok(ParsedDef { name, - expr: &arr[1], + expr: &arr.data[1], }) } @@ -75,22 +76,22 @@ pub fn parse_def(arg: &Value) -> Result, &'static str> { /// /// Works identically for `defmacro`. pub fn parse_defn(arg: &Value) -> Result, &'static str> { - let arr = match arg { - Value::Array(arr) if arr.len() == 3 => arr, + let arr = match arg.view() { + NanView::Array(ha) if ha.data.len() == 3 => ha, _ => return Err("defn/defmacro requires [name, formals, body]"), }; - let bind_name = match &arr[0] { - Value::String(s) => s.to_string(), + let bind_name = match arr.data[0].view() { + NanView::String(s) => s.to_string(), _ => return Err("defn/defmacro requires a string name at index 0"), }; - let formals = extract_formals(&arr[1]); + let formals = extract_formals(&arr.data[1]); Ok(ParsedDefn { bind_name, formals, - formals_val: &arr[1], - body: &arr[2], + formals_val: &arr.data[1], + body: &arr.data[2], formals_idx: 1, body_idx: 2, }) @@ -100,27 +101,27 @@ pub fn parse_defn(arg: &Value) -> Result, &'static str> { /// /// Returns a vec of parsed entries. Returns `Err` if the outer arg is not an array. pub fn parse_defrec_entries(arg: &Value) -> Result>, &'static str> { - let entries = match arg { - Value::Array(arr) => arr.as_ref(), + let entries = match arg.view() { + NanView::Array(ha) => &ha.data[..], _ => return Err("defrec requires an array of [name, formals, body] triples"), }; let mut result = Vec::with_capacity(entries.len()); for entry in entries { - let triple = match entry { - Value::Array(arr) if arr.len() == 3 => arr, + let triple = match entry.view() { + NanView::Array(ha) if ha.data.len() == 3 => ha, _ => return Err("each defrec entry must be [name, formals, body]"), }; - let name = match &triple[0] { - Value::String(n) => n.to_string(), + let name = match triple.data[0].view() { + NanView::String(n) => n.to_string(), _ => return Err("defrec entry must have a string name at index 0"), }; - let formals = extract_formals(&triple[1]); + let formals = extract_formals(&triple.data[1]); result.push(ParsedDefrecEntry { name, formals, - formals_val: &triple[1], - body: &triple[2], + formals_val: &triple.data[1], + body: &triple.data[2], }); } Ok(result) @@ -171,7 +172,7 @@ mod tests { fn parse_def_valid() { let budget = b(); let name = Value::string("x", &budget).unwrap(); - let expr = Value::Int(42); + let expr = Value::int(42); let arg = Value::array(vec![name, expr], &budget).unwrap(); let parsed = parse_def(&arg).unwrap(); assert_eq!(parsed.name, "x"); @@ -180,13 +181,13 @@ mod tests { #[test] fn parse_def_bad_arity() { let budget = b(); - let arg = Value::array(vec![Value::Null], &budget).unwrap(); + let arg = Value::array(vec![Value::null()], &budget).unwrap(); assert!(parse_def(&arg).is_err()); } #[test] fn parse_def_not_array() { - assert!(parse_def(&Value::Null).is_err()); + assert!(parse_def(&Value::null()).is_err()); } #[test] @@ -195,7 +196,7 @@ mod tests { let name = Value::string("f", &budget).unwrap(); let var_a = Value::call("var", Value::string("a", &budget).unwrap(), &budget).unwrap(); let formals = Value::array(vec![var_a], &budget).unwrap(); - let body = Value::Null; + let body = Value::null(); let arg = Value::array(vec![name, formals, body], &budget).unwrap(); let parsed = parse_defn(&arg).unwrap(); assert_eq!(parsed.bind_name, "f"); @@ -206,7 +207,7 @@ mod tests { #[test] fn parse_defn_bad() { - assert!(parse_defn(&Value::Null).is_err()); + assert!(parse_defn(&Value::null()).is_err()); } #[test] @@ -217,7 +218,7 @@ mod tests { vec![ Value::string("f", &budget).unwrap(), Value::array(vec![var_x], &budget).unwrap(), - Value::Null, + Value::null(), ], &budget, ) @@ -231,26 +232,26 @@ mod tests { #[test] fn parse_defrec_not_array() { - assert!(parse_defrec_entries(&Value::Null).is_err()); + assert!(parse_defrec_entries(&Value::null()).is_err()); } #[test] fn classify_def() { let budget = b(); - let stmt = Value::call("def", Value::Null, &budget).unwrap(); + let stmt = Value::call("def", Value::null(), &budget).unwrap(); assert!(matches!(classify_do_stmt(&stmt), DoStmtKind::Def(_))); } #[test] fn classify_defn() { let budget = b(); - let stmt = Value::call("defn", Value::Null, &budget).unwrap(); + let stmt = Value::call("defn", Value::null(), &budget).unwrap(); assert!(matches!(classify_do_stmt(&stmt), DoStmtKind::Defn(_))); } #[test] fn classify_expr() { - assert!(matches!(classify_do_stmt(&Value::Null), DoStmtKind::Expr(_))); + assert!(matches!(classify_do_stmt(&Value::null()), DoStmtKind::Expr(_))); } } diff --git a/src/runtime/procs/generic.rs b/src/runtime/procs/generic.rs index bdbb87b..c2de051 100644 --- a/src/runtime/procs/generic.rs +++ b/src/runtime/procs/generic.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use crate::runtime::{Budget, Eval, Scope, Value, eval_trampoline}; +use crate::runtime::nanvalue::NanView; use crate::{Error, Result}; /// `{"resolve-guards": [pattern_ast, env_obj]}` — walks a pattern AST @@ -19,9 +20,9 @@ pub fn op_resolve_guards( match args { [pattern_val, env_obj] => { // Build a scope from the env object for resolving predicates - let env_scope = match env_obj { - Value::Object(obj) => { - let map: HashMap = obj + let env_scope = match env_obj.view() { + NanView::Object(ho) => { + let map: HashMap = ho .iter() .map(|(k, v)| (k.to_string(), v.clone())) .collect(); @@ -55,21 +56,21 @@ fn resolve_guards_recursive( budget: &Budget, eval_limit: &mut usize, ) -> Result { - match pat { + match pat.view() { // Check if it's a singleton object (call form) - Value::Object(obj) if obj.len() == 1 => { - let (key, val) = obj.iter().next().unwrap(); + NanView::Object(ho) if ho.len() == 1 => { + let (key, val) = ho.iter().next().unwrap(); if &**key == "guard" { // Guard pattern: {"guard": [inner_pat, pred_expr]} // Only resolve if pred_expr is a var reference (standard type guard form). // Literal values like {"guard": [1, 2]} are left as structural matches. - if let Value::Array(arr) = val - && arr.len() == 2 - && arr[1].as_call().map_or(false, |(k, _)| k == "var") + if let NanView::Array(ha) = val.view() + && ha.data.len() == 2 + && ha.data[1].as_call().map_or(false, |(k, _)| k == "var") { - let inner_pat = resolve_guards_recursive(&arr[0], env_scope, budget, eval_limit)?; + let inner_pat = resolve_guards_recursive(&ha.data[0], env_scope, budget, eval_limit)?; // Evaluate the predicate expression in the env scope - let resolved_pred = eval_trampoline(budget, eval_limit, arr[1].clone(), env_scope.clone())?; + let resolved_pred = eval_trampoline(budget, eval_limit, ha.data[1].clone(), env_scope.clone())?; let inner_arr = Value::array(vec![inner_pat, resolved_pred], budget)?; return Value::call("guard-pattern", inner_arr, budget).map(Ok)?; @@ -79,8 +80,8 @@ fn resolve_guards_recursive( Ok(pat.clone()) } // Recurse into arrays - Value::Array(arr) => { - let items: Vec = arr + NanView::Array(ha) => { + let items: Vec = ha.data .iter() .map(|elem| resolve_guards_recursive(elem, env_scope, budget, eval_limit)) .collect::>()?; @@ -144,13 +145,13 @@ fn classify_pattern(pat: &Value, budget: &Budget) -> Result { } "guard-pattern" => { // {"guard-pattern": [inner_pat, pred]} - if let Value::Array(arr) = arg - && arr.len() == 2 + if let NanView::Array(ha) = arg.view() + && ha.data.len() == 2 { let tag = Value::string("guard", budget)?; - let inner = Value::call("pattern", arr[0].clone(), budget)?; + let inner = Value::call("pattern", ha.data[0].clone(), budget)?; // Guard predicates are {"type": ...} values — already self-evaluating. - let pred = arr[1].clone(); + let pred = ha.data[1].clone(); let pairs = vec![ (RStr::new("tag", budget)?, tag), (RStr::new("inner", budget)?, inner), @@ -160,12 +161,12 @@ fn classify_pattern(pat: &Value, budget: &Budget) -> Result { } } "and-pattern" => { - if let Value::Array(arr) = arg - && arr.len() == 2 + if let NanView::Array(ha) = arg.view() + && ha.data.len() == 2 { let tag = Value::string("and", budget)?; - let left = Value::call("pattern", arr[0].clone(), budget)?; - let right = Value::call("pattern", arr[1].clone(), budget)?; + let left = Value::call("pattern", ha.data[0].clone(), budget)?; + let right = Value::call("pattern", ha.data[1].clone(), budget)?; let pairs = vec![ (RStr::new("tag", budget)?, tag), (RStr::new("left", budget)?, left), @@ -175,12 +176,12 @@ fn classify_pattern(pat: &Value, budget: &Budget) -> Result { } } "or-pattern" => { - if let Value::Array(arr) = arg - && arr.len() == 2 + if let NanView::Array(ha) = arg.view() + && ha.data.len() == 2 { let tag = Value::string("or", budget)?; - let left = Value::call("pattern", arr[0].clone(), budget)?; - let right = Value::call("pattern", arr[1].clone(), budget)?; + let left = Value::call("pattern", ha.data[0].clone(), budget)?; + let right = Value::call("pattern", ha.data[1].clone(), budget)?; let pairs = vec![ (RStr::new("tag", budget)?, tag), (RStr::new("left", budget)?, left), @@ -191,12 +192,12 @@ fn classify_pattern(pat: &Value, budget: &Budget) -> Result { } "cons" => { // {"cons": [head, tail]} → structured cons pattern - if let Value::Array(arr) = arg - && arr.len() == 2 + if let NanView::Array(ha) = arg.view() + && ha.data.len() == 2 { let tag = Value::string("cons", budget)?; - let head = Value::call("pattern", arr[0].clone(), budget)?; - let tail = Value::call("pattern", arr[1].clone(), budget)?; + let head = Value::call("pattern", ha.data[0].clone(), budget)?; + let tail = Value::call("pattern", ha.data[1].clone(), budget)?; let pairs = vec![ (RStr::new("tag", budget)?, tag), (RStr::new("head", budget)?, head), @@ -220,11 +221,11 @@ fn classify_pattern(pat: &Value, budget: &Budget) -> Result { } } - match pat { + match pat.view() { // Array patterns - Value::Array(arr) => { + NanView::Array(ha) => { let tag = Value::string("array", budget)?; - let elements: Vec = arr + let elements: Vec = ha.data .iter() .map(|e| Value::call("pattern", e.clone(), budget)) .collect::>()?; @@ -236,7 +237,7 @@ fn classify_pattern(pat: &Value, budget: &Budget) -> Result { Value::object(pairs, budget).map(Eval::Done) } // Multi-key object patterns - Value::Object(_) => { + NanView::Object(_) => { let tag = Value::string("object", budget)?; // Object patterns are data — used via get(), not evaluated. let pairs = vec![ @@ -285,21 +286,21 @@ fn pattern_specificity(pat: &Value, scope: &Scope) -> usize { _ => pat, }; - match inner { - Value::Array(arr) => { + match inner.view() { + NanView::Array(ha) => { // Array pattern: sum specificity of each element - arr.iter().map(|elem| pattern_specificity(elem, scope)).sum() + ha.data.iter().map(|elem| pattern_specificity(elem, scope)).sum() } _ => { if let Some(("lazy-guard", guard_args)) = inner.as_call() { // Lazy guard: resolve pred_expr from scope to compute refinement depth. - if let Value::Array(arr) = guard_args { - if arr.len() == 2 { - let inner_score = pattern_specificity(&arr[0], scope); + if let NanView::Array(ha) = guard_args.view() { + if ha.data.len() == 2 { + let inner_score = pattern_specificity(&ha.data[0], scope); // Try to resolve the predicate from scope for depth scoring - let pred_depth = if let Some(("var", name_val)) = arr[1].as_call() { - if let Value::String(name) = name_val { - scope.get(name).map(|v| predicate_depth(&v)).unwrap_or(0) + let pred_depth = if let Some(("var", name_val)) = ha.data[1].as_call() { + if let NanView::String(name) = name_val.view() { + scope.get(&**name).map(|v| predicate_depth(&v)).unwrap_or(0) } else { 0 } } else { 0 }; return inner_score + 10 + pred_depth * 10; @@ -308,10 +309,10 @@ fn pattern_specificity(pat: &Value, scope: &Scope) -> usize { 10 } else if let Some(("guard-pattern", guard_args)) = inner.as_call() { // Guard pattern: score by refinement depth of the predicate - if let Value::Array(arr) = guard_args { - if arr.len() == 2 { - let inner_score = pattern_specificity(&arr[0], scope); - let pred_score = predicate_depth(&arr[1]); + if let NanView::Array(ha) = guard_args.view() { + if ha.data.len() == 2 { + let inner_score = pattern_specificity(&ha.data[0], scope); + let pred_score = predicate_depth(&ha.data[1]); return inner_score + 10 + pred_score * 10; } } @@ -329,9 +330,9 @@ fn pattern_specificity(pat: &Value, scope: &Scope) -> usize { /// int? refines number? → depth 2. number? refines nothing → depth 1. fn predicate_depth(pred: &Value) -> usize { if let Some(("type", payload)) = pred.as_call() { - if let Value::Object(obj) = payload { - if let Some(refines) = obj.get("refines") { - if matches!(refines, Value::Null) { + if let NanView::Object(ho) = payload.view() { + if let Some(refines) = ho.get("refines") { + if matches!(refines.view(), NanView::Null) { return 1; // base type, no parent } return 1 + predicate_depth(refines); diff --git a/src/runtime/procs/kanren.rs b/src/runtime/procs/kanren.rs index 65c464a..f4f3581 100644 --- a/src/runtime/procs/kanren.rs +++ b/src/runtime/procs/kanren.rs @@ -7,6 +7,7 @@ use std::sync::atomic::{AtomicUsize, Ordering}; +use crate::runtime::nanvalue::NanView; use crate::runtime::{Budget, Eval, Value}; use crate::runtime::Scope; use crate::runtime::String as RStr; @@ -32,21 +33,27 @@ pub fn op_lvar( // Accept either no args or an empty array match args { [] => {} - [Value::Array(arr)] if arr.is_empty() => {} + [arg] => match arg.view() { + NanView::Array(ha) if ha.data.is_empty() => {} + _ => return Err(crate::Error::arity("lvar", "0", args.len())), + }, _ => return Err(crate::Error::arity("lvar", "0", args.len())), } let id = LVAR_COUNTER.fetch_add(1, Ordering::Relaxed); - let id_val = Value::Int(id as i64); + let id_val = Value::int(id as i64); Value::call("runtime/lvar", id_val, budget).map(Eval::Done) } // ── Substitution helpers ───────────────────────────────────────────────────── /// Extract the substitution map from a `{"sigma": {...}}` value. -fn sigma_map(sigma: &Value) -> Option<&crate::runtime::data::Object> { +fn sigma_map(sigma: &Value) -> Option<&crate::runtime::data::HeapObj> { match sigma.as_call() { - Some(("sigma", Value::Object(obj))) => Some(obj), + Some(("sigma", inner)) => match inner.view() { + NanView::Object(ho) => Some(ho), + _ => None, + }, _ => None, } } @@ -80,9 +87,9 @@ fn occurs_check(lvar_id: &str, term: &Value, sigma: &Value) -> bool { if let Some(("runtime/lvar", id_val)) = walked.as_call() { return lvar_id_string(id_val) == lvar_id; } - match walked { - Value::Array(arr) => arr.iter().any(|v| occurs_check(lvar_id, v, sigma)), - Value::Object(obj) => obj.values().any(|v| occurs_check(lvar_id, v, sigma)), + match walked.view() { + NanView::Array(ha) => ha.data.iter().any(|v| occurs_check(lvar_id, v, sigma)), + NanView::Object(ho) => ho.values().any(|v| occurs_check(lvar_id, v, sigma)), _ => false, } } @@ -93,9 +100,9 @@ fn contains_lvar(val: &Value, sigma: &Value) -> bool { if let Some(("runtime/lvar", _)) = walked.as_call() { return true; // unbound lvar } - match walked { - Value::Array(arr) => arr.iter().any(|v| contains_lvar(v, sigma)), - Value::Object(obj) => obj.values().any(|v| contains_lvar(v, sigma)), + match walked.view() { + NanView::Array(ha) => ha.data.iter().any(|v| contains_lvar(v, sigma)), + NanView::Object(ho) => ho.values().any(|v| contains_lvar(v, sigma)), _ => false, } } @@ -145,21 +152,21 @@ fn eval_ground_call(key: &str, arg: &Value, sigma: &Value, budget: &Budget) -> c let resolved_arg = walk_deep(arg, sigma, budget)?; match key { "cons" => { - if let Value::Array(arr) = &resolved_arg - && arr.len() == 2 - && let Value::Array(tail) = &arr[1] + if let NanView::Array(ha) = resolved_arg.view() + && ha.data.len() == 2 + && let NanView::Array(tail_ha) = ha.data[1].view() { - let mut items = vec![arr[0].clone()]; - items.extend(tail.iter().cloned()); + let mut items = vec![ha.data[0].clone()]; + items.extend(tail_ha.data.iter().cloned()); return Value::array(items, budget); } // Can't evaluate — return as call-form Value::call(key, resolved_arg, budget) } "+" | "-" | "*" | "/" => { - if let Value::Array(arr) = &resolved_arg - && arr.len() == 2 - && let (Some(l), Some(r)) = (arr[0].as_number(), arr[1].as_number()) + if let NanView::Array(ha) = resolved_arg.view() + && ha.data.len() == 2 + && let (Some(l), Some(r)) = (ha.data[0].as_number(), ha.data[1].as_number()) && let Some(result) = forward_eval_arith(key, &l, &r) { return Ok(Value::from_number(result)); @@ -167,9 +174,9 @@ fn eval_ground_call(key: &str, arg: &Value, sigma: &Value, budget: &Budget) -> c Value::call(key, resolved_arg, budget) } "str" => { - if let Value::Array(arr) = &resolved_arg { + if let NanView::Array(ha) = resolved_arg.view() { let mut result = std::string::String::new(); - for seg in arr.iter() { + for seg in ha.data.iter() { result.push_str(&stringify_value(seg)); } return Value::string(&result, budget); @@ -177,11 +184,11 @@ fn eval_ground_call(key: &str, arg: &Value, sigma: &Value, budget: &Budget) -> c Value::call(key, resolved_arg, budget) } "concat" => { - if let Value::Array(arr) = &resolved_arg { + if let NanView::Array(ha) = resolved_arg.view() { let mut result_items = Vec::new(); - for seg in arr.iter() { - if let Value::Array(sub) = seg { - result_items.extend(sub.iter().cloned()); + for seg in ha.data.iter() { + if let NanView::Array(sub_ha) = seg.view() { + result_items.extend(sub_ha.data.iter().cloned()); } else { return Value::call(key, resolved_arg, budget); } @@ -191,18 +198,18 @@ fn eval_ground_call(key: &str, arg: &Value, sigma: &Value, budget: &Budget) -> c Value::call(key, resolved_arg, budget) } "assoc" => { - if let Value::Array(arr) = &resolved_arg - && arr.len() == 3 - && let Value::Object(obj) = &arr[0] - && let Value::String(k) = &arr[1] + if let NanView::Array(ha) = resolved_arg.view() + && ha.data.len() == 3 + && let NanView::Object(ho) = ha.data[0].view() + && let NanView::String(k) = ha.data[1].view() { - let key_str: &str = k; - let mut pairs: Vec<(RStr, Value)> = obj + let key_str: &str = &**k; + let mut pairs: Vec<(RStr, Value)> = ho .iter() .filter(|(ek, _)| &**ek != key_str) .map(|(ek, ev)| (ek.clone(), ev.clone())) .collect(); - pairs.push((RStr::new(key_str, budget)?, arr[2].clone())); + pairs.push((RStr::new(key_str, budget)?, ha.data[2].clone())); return Value::object(pairs, budget); } Value::call(key, resolved_arg, budget) @@ -247,12 +254,12 @@ fn unify_inner( } // 3. Both arrays of same length → fold unify over elements pairwise - if let (Value::Array(u_arr), Value::Array(v_arr)) = (&u_walked, &v_walked) { - if u_arr.len() != v_arr.len() { + if let (NanView::Array(u_ha), NanView::Array(v_ha)) = (u_walked.view(), v_walked.view()) { + if u_ha.data.len() != v_ha.data.len() { return Ok(None); } let mut s = sigma; - for (ue, ve) in u_arr.iter().zip(v_arr.iter()) { + for (ue, ve) in u_ha.data.iter().zip(v_ha.data.iter()) { match unify_inner(ue, ve, s, budget, eval_limit, scope.clone())? { Some(s2) => s = s2, None => return Ok(None), @@ -282,8 +289,8 @@ fn unify_inner( } // 5. Both objects → symmetric unification with rest-lvar support - if let (Value::Object(u_obj), Value::Object(v_obj)) = (&u_walked, &v_walked) { - return unify_objects(u_obj, v_obj, sigma, budget, eval_limit, scope); + if let (NanView::Object(u_ho), NanView::Object(v_ho)) = (u_walked.view(), v_walked.view()) { + return unify_objects(u_ho, v_ho, sigma, budget, eval_limit, scope); } // 6. Mismatch @@ -292,8 +299,8 @@ fn unify_inner( /// Object unification with `".."` rest-lvar support. fn unify_objects( - u_obj: &crate::runtime::data::Object, - v_obj: &crate::runtime::data::Object, + u_obj: &crate::runtime::data::HeapObj, + v_obj: &crate::runtime::data::HeapObj, sigma: Value, budget: &Budget, eval_limit: &mut usize, @@ -430,22 +437,22 @@ fn unify_cons( eval_limit: &mut usize, scope: Scope, ) -> crate::Result>> { - let arr = match arg { - Value::Array(a) if a.len() == 2 => a, + let ha = match arg.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return Ok(None), }; - let head = &arr[0]; - let tail = &arr[1]; + let head = &ha.data[0]; + let tail = &ha.data[1]; // If both operands are ground, forward-evaluate cons and unify result if is_ground(head, &sigma) && is_ground(tail, &sigma) && !contains_lvar(head, &sigma) && !contains_lvar(tail, &sigma) { // Walk deeply to resolve lvars before constructing let h_walked = walk_deep(head, &sigma, budget)?; let t_walked = walk_deep(tail, &sigma, budget)?; - if let Value::Array(t_arr) = &t_walked { + if let NanView::Array(t_ha) = t_walked.view() { let mut items = vec![h_walked]; - items.extend(t_arr.iter().cloned()); + items.extend(t_ha.data.iter().cloned()); let constructed = Value::array(items, budget)?; return Ok(Some(unify_inner(&constructed, other, sigma, budget, eval_limit, scope)?)); } @@ -453,10 +460,10 @@ fn unify_cons( } // Other must be an array to decompose - match other { - Value::Array(val_arr) if !val_arr.is_empty() => { - let head_val = &val_arr[0]; - let tail_items: Vec = val_arr[1..].to_vec(); + match other.view() { + NanView::Array(val_ha) if !val_ha.data.is_empty() => { + let head_val = &val_ha.data[0]; + let tail_items: Vec = val_ha.data[1..].to_vec(); let tail_val = Value::array(tail_items, budget)?; let s = match unify_inner(head, head_val, sigma, budget, eval_limit, scope.clone())? { Some(s) => s, @@ -464,7 +471,7 @@ fn unify_cons( }; Ok(Some(unify_inner(tail, &tail_val, s, budget, eval_limit, scope)?)) } - Value::Array(_) => Ok(Some(None)), // empty array can't match cons + NanView::Array(_) => Ok(Some(None)), // empty array can't match cons _ => Ok(None), // not an array — can't handle } } @@ -479,13 +486,13 @@ fn unify_arith( eval_limit: &mut usize, scope: Scope, ) -> crate::Result>> { - let arr = match arg { - Value::Array(a) if a.len() == 2 => a, + let ha = match arg.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return Ok(None), }; - let left = &arr[0]; - let right = &arr[1]; + let left = &ha.data[0]; + let right = &ha.data[1]; let left_ground = is_ground(left, &sigma); let right_ground = is_ground(right, &sigma); @@ -556,14 +563,14 @@ fn unify_str( eval_limit: &mut usize, scope: Scope, ) -> crate::Result>> { - let segments = match arg { - Value::Array(a) if !a.is_empty() => a, + let segments = match arg.view() { + NanView::Array(ha) if !ha.data.is_empty() => ha, _ => return Ok(None), }; // Find the single non-ground segment let mut var_index = None; - for (i, seg) in segments.iter().enumerate() { + for (i, seg) in segments.data.iter().enumerate() { if !is_ground(seg, &sigma) { if var_index.is_some() { return Ok(None); // 2+ non-ground segments — underdetermined @@ -575,7 +582,7 @@ fn unify_str( // All-ground: forward-evaluate and unify let Some(vi) = var_index else { let mut result = std::string::String::new(); - for seg in segments.iter() { + for seg in segments.data.iter() { let val = walk_deep(seg, &sigma, budget)?; result.push_str(&stringify_value(&val)); } @@ -584,20 +591,20 @@ fn unify_str( }; // Target must be a string - let target_str = match other { - Value::String(s) => s, + let target_str = match other.view() { + NanView::String(s) => s, _ => return Ok(Some(None)), }; - let target: &str = target_str; + let target: &str = &**target_str; // Evaluate ground segments before/after the var segment let mut prefix = std::string::String::new(); - for seg in &segments[..vi] { + for seg in &segments.data[..vi] { let val = walk_deep(seg, &sigma, budget)?; prefix.push_str(&stringify_value(&val)); } let mut suffix = std::string::String::new(); - for seg in &segments[vi + 1..] { + for seg in &segments.data[vi + 1..] { let val = walk_deep(seg, &sigma, budget)?; suffix.push_str(&stringify_value(&val)); } @@ -611,7 +618,7 @@ fn unify_str( let middle = &target[prefix.len()..target.len() - suffix.len()]; let middle_val = Value::string(middle, budget)?; - Ok(Some(unify_inner(&segments[vi], &middle_val, sigma, budget, eval_limit, scope)?)) + Ok(Some(unify_inner(&segments.data[vi], &middle_val, sigma, budget, eval_limit, scope)?)) } /// Array concatenation inversion: `{"concat": [seg1, ..., segN]}` ↔ array @@ -623,13 +630,13 @@ fn unify_concat( eval_limit: &mut usize, scope: Scope, ) -> crate::Result>> { - let segments = match arg { - Value::Array(a) if !a.is_empty() => a, + let segments = match arg.view() { + NanView::Array(ha) if !ha.data.is_empty() => ha, _ => return Ok(None), }; let mut var_index = None; - for (i, seg) in segments.iter().enumerate() { + for (i, seg) in segments.data.iter().enumerate() { if !is_ground(seg, &sigma) { if var_index.is_some() { return Ok(None); // 2+ non-ground — underdetermined @@ -641,10 +648,10 @@ fn unify_concat( // All-ground: forward-evaluate and unify let Some(vi) = var_index else { let mut result_items = Vec::new(); - for seg in segments.iter() { + for seg in segments.data.iter() { let val = walk_deep(seg, &sigma, budget)?; - match val { - Value::Array(arr) => result_items.extend(arr.iter().cloned()), + match val.view() { + NanView::Array(ha) => result_items.extend(ha.data.iter().cloned()), _ => return Ok(Some(None)), } } @@ -652,46 +659,46 @@ fn unify_concat( return Ok(Some(unify_inner(&result_val, other, sigma, budget, eval_limit, scope)?)); }; - let target_arr = match other { - Value::Array(a) => a, + let target_ha = match other.view() { + NanView::Array(ha) => ha, _ => return Ok(Some(None)), }; let mut prefix_elems = Vec::new(); - for seg in &segments[..vi] { + for seg in &segments.data[..vi] { let val = walk_deep(seg, &sigma, budget)?; - match val { - Value::Array(arr) => prefix_elems.extend(arr.iter().cloned()), + match val.view() { + NanView::Array(ha) => prefix_elems.extend(ha.data.iter().cloned()), _ => return Ok(Some(None)), } } let mut suffix_elems = Vec::new(); - for seg in &segments[vi + 1..] { + for seg in &segments.data[vi + 1..] { let val = walk_deep(seg, &sigma, budget)?; - match val { - Value::Array(arr) => suffix_elems.extend(arr.iter().cloned()), + match val.view() { + NanView::Array(ha) => suffix_elems.extend(ha.data.iter().cloned()), _ => return Ok(Some(None)), } } - if prefix_elems.len() + suffix_elems.len() > target_arr.len() { + if prefix_elems.len() + suffix_elems.len() > target_ha.data.len() { return Ok(Some(None)); } for (i, elem) in prefix_elems.iter().enumerate() { - if &target_arr[i] != elem { + if &target_ha.data[i] != elem { return Ok(Some(None)); } } - let suffix_start = target_arr.len() - suffix_elems.len(); + let suffix_start = target_ha.data.len() - suffix_elems.len(); for (i, elem) in suffix_elems.iter().enumerate() { - if &target_arr[suffix_start + i] != elem { + if &target_ha.data[suffix_start + i] != elem { return Ok(Some(None)); } } - let middle_items: Vec = target_arr[prefix_elems.len()..suffix_start].to_vec(); + let middle_items: Vec = target_ha.data[prefix_elems.len()..suffix_start].to_vec(); let middle_val = Value::array(middle_items, budget)?; - Ok(Some(unify_inner(&segments[vi], &middle_val, sigma, budget, eval_limit, scope)?)) + Ok(Some(unify_inner(&segments.data[vi], &middle_val, sigma, budget, eval_limit, scope)?)) } /// Assoc inversion: `{"assoc": [base, key, val]}` ↔ object @@ -703,22 +710,22 @@ fn unify_assoc( eval_limit: &mut usize, scope: Scope, ) -> crate::Result>> { - let arr = match arg { - Value::Array(a) if a.len() == 3 => a, + let ha = match arg.view() { + NanView::Array(ha) if ha.data.len() == 3 => ha, _ => return Ok(None), }; - let base = &arr[0]; - let key_term = &arr[1]; - let val_term = &arr[2]; + let base = &ha.data[0]; + let key_term = &ha.data[1]; + let val_term = &ha.data[2]; // Key must be ground if !is_ground(key_term, &sigma) { return Ok(None); } let key_val = walk_deep(key_term, &sigma, budget)?; - let key_str = match &key_val { - Value::String(s) => s.to_string(), + let key_str = match key_val.view() { + NanView::String(s) => s.to_string(), _ => return Ok(Some(None)), }; @@ -726,8 +733,8 @@ fn unify_assoc( if is_ground(base, &sigma) && is_ground(val_term, &sigma) { let base_val = walk_deep(base, &sigma, budget)?; let v_val = walk_deep(val_term, &sigma, budget)?; - if let Value::Object(obj) = &base_val { - let mut pairs: Vec<(RStr, Value)> = obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); + if let NanView::Object(ho) = base_val.view() { + let mut pairs: Vec<(RStr, Value)> = ho.iter().map(|(k, v)| (k.clone(), v.clone())).collect(); // Remove existing key if present, then add new pairs.retain(|(k, _)| &**k != key_str.as_str()); pairs.push((RStr::new(&key_str, budget)?, v_val)); @@ -738,12 +745,12 @@ fn unify_assoc( } // Other must be an object with that key - let target_obj = match other { - Value::Object(obj) => obj, + let target_ho = match other.view() { + NanView::Object(ho) => ho, _ => return Ok(Some(None)), }; - let target_val_at_key = match target_obj.get(&key_str) { + let target_val_at_key = match target_ho.get(&key_str) { Some(v) => v.clone(), None => return Ok(Some(None)), }; @@ -755,7 +762,7 @@ fn unify_assoc( }; // Build remainder (dissoc key) and unify with base - let remainder_pairs: Vec<(RStr, Value)> = target_obj + let remainder_pairs: Vec<(RStr, Value)> = target_ho .iter() .filter(|(k, _)| &**k != key_str.as_str()) .map(|(k, v)| (k.clone(), v.clone())) @@ -771,16 +778,16 @@ fn walk_deep(term: &Value, sigma: &Value, budget: &Budget) -> crate::Result { - let items: Vec = arr + match walked.view() { + NanView::Array(ha) => { + let items: Vec = ha.data .iter() .map(|v| walk_deep(v, sigma, budget)) .collect::>()?; Value::array(items, budget) } - Value::Object(obj) => { - let pairs: Vec<(RStr, Value)> = obj + NanView::Object(ho) => { + let pairs: Vec<(RStr, Value)> = ho .iter() .map(|(k, v)| walk_deep(v, sigma, budget).map(|rv| (k.clone(), rv))) .collect::>()?; @@ -803,7 +810,7 @@ pub fn op_unify( [term1, term2, sigma] => { match unify_inner(term1, term2, sigma.clone(), budget, eval_limit, scope)? { Some(s) => Eval::done(s), - None => Eval::done(Value::Null), + None => Eval::done(Value::null()), } } _ => Err(crate::Error::arity("unify", "3", args.len())), @@ -822,15 +829,15 @@ fn walk_star_inner(term: &Value, sigma: &Value, budget: &Budget) -> crate::Resul return Ok(walked.clone()); } - match walked { - Value::Array(arr) => { - let items: Vec = arr + match walked.view() { + NanView::Array(ha) => { + let items: Vec = ha.data .iter() .map(|v| walk_star_inner(v, sigma, budget)) .collect::>()?; Value::array(items, budget) } - Value::Object(obj) => { + NanView::Object(ho) => { // Check for invertible call-forms that should be evaluated if let Some((key, arg)) = walked.as_call() && is_invertible(key) @@ -839,13 +846,13 @@ fn walk_star_inner(term: &Value, sigma: &Value, budget: &Budget) -> crate::Resul let walked_arg = walk_star_inner(arg, sigma, budget)?; // If fully ground (no remaining lvars), forward-evaluate if !contains_lvar_in_value(&walked_arg) { - return eval_ground_call(key, &walked_arg, &Value::Null, budget) + return eval_ground_call(key, &walked_arg, &Value::null(), budget) .or_else(|_| Value::call(key, walked_arg.clone(), budget)); } // Not fully ground — keep as call-form with walked args return Value::call(key, walked_arg, budget); } - let pairs: Vec<(RStr, Value)> = obj + let pairs: Vec<(RStr, Value)> = ho .iter() .map(|(k, v)| { walk_star_inner(v, sigma, budget).map(|rv| (k.clone(), rv)) @@ -862,9 +869,9 @@ fn contains_lvar_in_value(val: &Value) -> bool { if let Some(("runtime/lvar", _)) = val.as_call() { return true; } - match val { - Value::Array(arr) => arr.iter().any(contains_lvar_in_value), - Value::Object(obj) => obj.values().any(contains_lvar_in_value), + match val.view() { + NanView::Array(ha) => ha.data.iter().any(contains_lvar_in_value), + NanView::Object(ho) => ho.values().any(contains_lvar_in_value), _ => false, } } @@ -940,7 +947,7 @@ pub fn op_relational_unify( let eq_check = Value::call( "==", - Value::array(vec![var_r.clone(), Value::Null], budget)?, + Value::array(vec![var_r.clone(), Value::null()], budget)?, budget, )?; @@ -1000,32 +1007,32 @@ fn relational_walk( eval_limit: &mut usize, scope: Scope, ) -> crate::Result { - match value { - Value::Null | Value::Bool(_) | Value::Int(_) | Value::Float(_) | Value::BigNum(_) | Value::String(_) => Ok(value.clone()), + match value.view() { + NanView::Null | NanView::Bool(_) | NanView::Int(_) | NanView::Float(_) | NanView::BigNum(_) | NanView::String(_) => Ok(value.clone()), - Value::Array(arr) => { - let items: Vec = arr + NanView::Array(ha) => { + let items: Vec = ha.data .iter() .map(|v| relational_walk(v, budget, eval_limit, scope.clone())) .collect::>()?; Value::array(items, budget) } - Value::Object(obj) => { + NanView::Object(_ho) => { if let Some((key, arg)) = value.as_call() { // Singleton object if key == "var" { // Resolve var from scope - if let Value::String(name) = arg { - let name_str: &str = name; + if let NanView::String(name) = arg.view() { + let name_str: &str = &**name; match scope.get(name_str) { Some(val) => Ok(val), None => Err(crate::Error::UndefinedName(name_str.to_string())), } - } else if matches!(arg, Value::Null) { + } else if matches!(arg.view(), NanView::Null) { // Wildcard var — create a fresh lvar let id = LVAR_COUNTER.fetch_add(1, Ordering::Relaxed); - let id_val = Value::Int(id as i64); + let id_val = Value::int(id as i64); Value::call("runtime/lvar", id_val, budget) } else { Ok(value.clone()) @@ -1043,7 +1050,12 @@ fn relational_walk( } } else { // Multi-key object: walk values - let pairs: Vec<(RStr, Value)> = obj + // Need to re-view to get the HeapObj for iteration + let ho = match value.view() { + NanView::Object(ho) => ho, + _ => unreachable!(), + }; + let pairs: Vec<(RStr, Value)> = ho .iter() .map(|(k, v)| { relational_walk(v, budget, eval_limit, scope.clone()) diff --git a/src/runtime/procs/lambda.rs b/src/runtime/procs/lambda.rs index d9d1e66..41f3807 100644 --- a/src/runtime/procs/lambda.rs +++ b/src/runtime/procs/lambda.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use crate::runtime::data::{String as RStr, pattern_bound_names}; +use crate::runtime::nanvalue::NanView; use crate::runtime::{eval_trampoline, Budget, Eval, Scope, Value}; use crate::{Error, Result}; @@ -16,7 +17,7 @@ use super::control::try_match; /// or capture the full argument list. Other patterns (object destructuring, /// literals) expect a single argument. pub(crate) fn formals_matches_array(formals: &Value) -> bool { - if matches!(formals, Value::Array(_)) { + if matches!(formals.view(), NanView::Array(_)) { return true; } if let Some((key, _)) = formals.as_call() { @@ -80,8 +81,8 @@ fn ensure_scope_obj_tagged(v: Value, budget: &Budget) -> Result { if let Some(("scope", _)) = v.as_call() { return Ok(v); } - match &v { - Value::Object(_) => Value::call("scope", v, budget), + match v.view() { + NanView::Object(_) => Value::call("scope", v, budget), _ => Err(Error::MalformedFnValue( "scope_obj must evaluate to an object".to_string(), )), @@ -113,7 +114,7 @@ pub(crate) fn make_fn_value( // or env source form [formals, env_str, body] [formals, second, third] => { // Check if this is [formals, env_str, body] — 3-element source form with env formal - if let Value::String(env_name) = second { + if let NanView::String(env_name) = second.view() { // 3-element source with env formal: [formals, "env", body] let body = third; let mut bound = pattern_bound_names(formals); @@ -232,8 +233,8 @@ pub(crate) fn extract_scope_map( sobj: Value, ) -> crate::Result> { fn from_val(val: &Value) -> crate::Result> { - match val { - Value::Object(obj) => Ok(obj.iter() + match val.view() { + NanView::Object(ho) => Ok(ho.iter() .map(|(k, v)| (k.to_string(), v.clone())) .collect()), _ => { @@ -273,8 +274,8 @@ pub(crate) fn call_operative( eval_limit: &mut usize, caller_scope: Scope, ) -> Result { - let items = match fn_arg { - Value::Array(arr) => arr, + let items = match fn_arg.view() { + NanView::Array(ha) => ha, _ => { return Err(Error::MalformedFnValue( "operative value is not an array".to_string(), @@ -287,14 +288,14 @@ pub(crate) fn call_operative( // [formals_pat, body, scope_obj] — closed, no env // [formals_pat, env_str, body, scope_obj] — closed, with env let (formals_pat, env_formal, body, scope_opt): (Value, Option, Value, Option) = - match items.as_ref() { + match items.data.as_slice() { [f, b] => (f.clone(), None, b.clone(), None), [f, b, s] => { // Disambiguate: if s is a runtime/scope tag, this is [formals, body, scope_obj]. // Otherwise check if b is a String (env formal): [formals, env_str, body]. if matches!(s.as_call(), Some(("scope", _))) { (f.clone(), None, b.clone(), Some(s.clone())) - } else if let Value::String(env) = b { + } else if let NanView::String(env) = b.view() { // 3-element without scope_obj: [formals, env, body] (unclosed) (f.clone(), Some(env.to_string()), s.clone(), None) } else { @@ -303,8 +304,8 @@ pub(crate) fn call_operative( } } [f, env, b, s] => { - let env_name = match env { - Value::String(s) => s.to_string(), + let env_name = match env.view() { + NanView::String(s) => s.to_string(), _ => return Err(Error::MalformedFnValue( "env formal must be a string".to_string(), )), diff --git a/src/runtime/procs/mod.rs b/src/runtime/procs/mod.rs index e479e3e..3094089 100644 --- a/src/runtime/procs/mod.rs +++ b/src/runtime/procs/mod.rs @@ -40,6 +40,7 @@ use std::collections::HashMap; use std::sync::OnceLock; use crate::runtime::Scope; +use crate::runtime::nanvalue::NanView; use crate::runtime::{Budget, Eval, Value}; // ── BuiltinFn type ──────────────────────────────────────────────────────────── @@ -273,16 +274,16 @@ pub(crate) fn call_mu( )), }; - let identity_str = match &identity { - Value::String(s) => s.to_string(), + let identity_str = match identity.view() { + NanView::String(s) => s.to_string(), _ => return Err(crate::Error::MalformedFnValue( "mu identity must be a string".to_string(), )), }; // Extract defs as an object - let defs_map: Vec<(std::string::String, Value)> = match &defs { - Value::Object(obj) => obj.iter().map(|(k, v)| (k.to_string(), v.clone())).collect(), + let defs_map: Vec<(std::string::String, Value)> = match defs.view() { + NanView::Object(ho) => ho.iter().map(|(k, v)| (k.to_string(), v.clone())).collect(), _ => { if let Some((k, v)) = defs.as_call() { vec![(k.to_string(), v.clone())] @@ -379,15 +380,15 @@ pub(crate) fn call_mu_public( /// Parse a mu defs entry: [kind, formals, body] fn parse_mu_entry(val: &Value) -> crate::Result<(std::string::String, Value, Value)> { - match val { - Value::Array(arr) if arr.len() == 3 => { - let kind = match &arr[0] { - Value::String(s) => s.to_string(), + match val.view() { + NanView::Array(ha) if ha.data.len() == 3 => { + let kind = match ha.data[0].view() { + NanView::String(s) => s.to_string(), _ => return Err(crate::Error::MalformedFnValue( "mu entry kind must be a string".to_string(), )), }; - Ok((kind, arr[1].clone(), arr[2].clone())) + Ok((kind, ha.data[1].clone(), ha.data[2].clone())) } _ => Err(crate::Error::MalformedFnValue( "mu defs entry must be [kind, formals, body]".to_string(), @@ -419,9 +420,9 @@ pub(crate) fn dispatch_call_with_ctx( ) -> crate::Result { if let Some((key, arg)) = proc_val.as_call() { match key { - "builtins" => match arg { - Value::String(s) => { - let name: &str = s; + "builtins" => match arg.view() { + NanView::String(s) => { + let name: &str = &**s; match lookup_builtin(name) { Some(f) => f(args, budget, eval_limit, scope), None => Err(crate::Error::MalformedFnValue(format!( @@ -447,8 +448,8 @@ pub(crate) fn dispatch_call_with_ctx( } else if let Some(("mu", inner_arg)) = arg.as_call() { call_mu(inner_arg.clone(), &evaled, budget, eval_limit, scope, ctx) } else if let Some(("builtins", name)) = arg.as_call() { - if let Value::String(s) = name { - let name_str: &str = &s; + if let NanView::String(s) = name.view() { + let name_str: &str = &**s; match lookup_builtin(name_str) { Some(f) => f(&evaled, budget, eval_limit, scope), None => Err(crate::Error::MalformedFnValue(format!( @@ -468,8 +469,8 @@ pub(crate) fn dispatch_call_with_ctx( } "type" => { // First-class type predicate: extract `test` fn and delegate. - if let Value::Object(payload) = &arg { - if let Some(test_fn) = payload.get("test") { + if let NanView::Object(ho) = arg.view() { + if let Some(test_fn) = ho.get("test") { dispatch_call_with_ctx(test_fn.clone(), args, budget, eval_limit, scope, call_name, ctx) } else { Err(crate::Error::MalformedFnValue( @@ -512,9 +513,9 @@ pub(crate) fn call_with_values( ) -> crate::Result { if let Some((key, arg)) = proc_val.as_call() { match key { - "builtins" => match arg { - Value::String(s) => { - let name: &str = s; + "builtins" => match arg.view() { + NanView::String(s) => { + let name: &str = &**s; match lookup_builtin(name) { Some(f) => f(args, budget, eval_limit, scope), None => Err(crate::Error::MalformedFnValue(format!( @@ -535,8 +536,8 @@ pub(crate) fn call_with_values( } else if let Some(("mu", inner_arg)) = arg.as_call() { call_mu(inner_arg.clone(), args, budget, eval_limit, scope, None) } else if let Some(("builtins", name)) = arg.as_call() { - if let Value::String(s) = name { - let name_str: &str = &s; + if let NanView::String(s) = name.view() { + let name_str: &str = &**s; match lookup_builtin(name_str) { Some(f) => f(args, budget, eval_limit, scope), None => Err(crate::Error::MalformedFnValue(format!( @@ -555,8 +556,8 @@ pub(crate) fn call_with_values( } } "type" => { - if let Value::Object(payload) = &arg { - if let Some(test_fn) = payload.get("test") { + if let NanView::Object(ho) = arg.view() { + if let Some(test_fn) = ho.get("test") { call_with_values(test_fn.clone(), args, budget, eval_limit, scope) } else { Err(crate::Error::MalformedFnValue( @@ -714,9 +715,9 @@ fn bootstrap_object( .expect("stdlib eval succeeds"); // Result must be an object; merge each entry. - match result { - Value::Object(obj) => { - for (key, val) in obj.iter() { + match result.view() { + NanView::Object(ho) => { + for (key, val) in ho.iter() { let key_str = key.to_string(); // Special handling for runtime: deep merge @@ -743,20 +744,20 @@ fn bootstrap_object( fn deep_merge_for_bootstrap(base: &Value, overlay: &Value, budget: &Budget) -> crate::Result { use crate::runtime::String as RStr; - let Value::Object(overlay_obj) = overlay else { + let NanView::Object(overlay_ho) = overlay.view() else { return Ok(overlay.clone()); }; - let mut pairs: Vec<(crate::runtime::String, Value)> = match base { - Value::Object(obj) => obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect(), + let mut pairs: Vec<(crate::runtime::String, Value)> = match base.view() { + NanView::Object(ho) => ho.iter().map(|(k, v)| (k.clone(), v.clone())).collect(), _ => Vec::new(), }; - for (ok, ov) in overlay_obj.iter() { + for (ok, ov) in overlay_ho.iter() { let key_str: &str = ok; if let Some(pos) = pairs.iter().position(|(k, _)| &**k == key_str) { let (_, base_val) = &pairs[pos]; - if matches!(base_val, Value::Object(_)) && matches!(ov, Value::Object(_)) { + if matches!(base_val.view(), NanView::Object(_)) && matches!(ov.view(), NanView::Object(_)) { let merged_val = deep_merge_for_bootstrap(base_val, ov, budget)?; pairs[pos] = (RStr::new(key_str, budget)?, merged_val); } else { diff --git a/src/runtime/procs/object.rs b/src/runtime/procs/object.rs index 96e1045..b2cfd55 100644 --- a/src/runtime/procs/object.rs +++ b/src/runtime/procs/object.rs @@ -1,4 +1,5 @@ use crate::runtime::data::String as RStr; +use crate::runtime::nanvalue::NanView; use crate::runtime::{Budget, Eval, Number, Value}; use crate::runtime::Scope; use crate::{Error, Result}; @@ -11,10 +12,10 @@ fn obj_to_pairs( _scope: Scope, proc: &'static str, ) -> Result> { - match arg { - Value::Object(obj) => Ok(obj.iter().map(|(k, v)| (k.clone(), v.clone())).collect()), - other => { - Err(Error::type_err(proc, 0, &other, "an object")) + match arg.view() { + NanView::Object(ho) => Ok(ho.iter().map(|(k, v)| (k.clone(), v.clone())).collect()), + _ => { + Err(Error::type_err(proc, 0, &arg, "an object")) } } } @@ -27,8 +28,8 @@ pub fn op_keys( ) -> Result { match args { [val] => { - let keys: Vec = match val { - Value::Object(obj) => obj + let keys: Vec = match val.view() { + NanView::Object(ho) => ho .iter() .map(|(k, _)| Value::string(k, budget)) .collect::>()?, @@ -52,9 +53,9 @@ pub fn op_assoc( [obj_arg, key_arg, val_arg] => { let mut pairs = obj_to_pairs(obj_arg.clone(), budget, eval_limit, scope.clone(), "assoc")?; - let key_str = match key_arg { - Value::String(s) => (*s).to_string(), - other => return Err(Error::type_err("assoc", 1, other, "a string")), + let key_str = match key_arg.view() { + NanView::String(s) => s.to_string(), + _ => return Err(Error::type_err("assoc", 1, key_arg, "a string")), }; let val = val_arg.clone(); let _ = scope; @@ -86,19 +87,19 @@ pub fn op_get( let default_val = if args.len() >= 3 { args[2].clone() } else { - Value::Null + Value::null() }; let _ = (budget, eval_limit, scope); - match (data, key) { - (Value::Object(obj), Value::String(s)) => { - Eval::done(obj.get(&s).cloned().unwrap_or(default_val)) + match (data.view(), key.view()) { + (NanView::Object(ho), NanView::String(s)) => { + Eval::done(ho.get(s).cloned().unwrap_or(default_val)) } - (Value::Array(arr), ref key_v) if key_v.is_number() => { - let idx = match key_v.as_number().unwrap() { + (NanView::Array(ha), _) if key.is_number() => { + let idx = match key.as_number().unwrap() { Number::Integer(iv) if !iv.is_negative() => iv.to_usize().unwrap_or(usize::MAX), _ => return Eval::done(default_val), }; - Eval::done(arr.get(idx).cloned().unwrap_or(default_val)) + Eval::done(ha.data.get(idx).cloned().unwrap_or(default_val)) } _ => Eval::done(default_val), } @@ -113,16 +114,16 @@ pub fn op_get( /// into arrays. Returns an `Access` error on missing keys / out-of-range indices. fn traverse_step(value: Value, key: Value) -> Result { let data = value.clone(); - match (&value, &key) { - (Value::Array(arr), k) if k.is_number() => { - let n = k.as_number().unwrap(); + match (value.view(), key.view()) { + (NanView::Array(ha), _) if key.is_number() => { + let n = key.as_number().unwrap(); match n.as_integer() { Some(iv) if !iv.is_negative() => { let idx = iv.to_usize().ok_or_else(|| Error::Access { data: data.to_string(), key: key.to_string(), })?; - arr.get(idx).cloned().ok_or_else(|| Error::Access { + ha.data.get(idx).cloned().ok_or_else(|| Error::Access { data: data.to_string(), key: key.to_string(), }) @@ -133,8 +134,8 @@ fn traverse_step(value: Value, key: Value) -> Result { }), } } - (Value::Object(obj), Value::String(s)) => { - obj.get(s).cloned().ok_or_else(|| Error::Access { + (NanView::Object(ho), NanView::String(s)) => { + ho.get(s).cloned().ok_or_else(|| Error::Access { data: data.to_string(), key: format!("\"{}\"", &**s), }) @@ -157,11 +158,11 @@ pub fn op_get_in( [val_arg, path_arg] => { let mut current = val_arg.clone(); let _ = (budget, eval_limit, scope); - let steps = match path_arg { - Value::Array(arr) => arr, + let steps = match path_arg.view() { + NanView::Array(ha) => ha, _ => return Err(Error::type_err("get-in", 1, path_arg, "an array path")), }; - for step in steps.iter() { + for step in steps.data.iter() { current = traverse_step(current, step.clone())?; } Eval::done(current) diff --git a/src/runtime/procs/string.rs b/src/runtime/procs/string.rs index 4c1a786..add5a38 100644 --- a/src/runtime/procs/string.rs +++ b/src/runtime/procs/string.rs @@ -1,6 +1,7 @@ use crate::runtime::{Budget, Eval, Value}; use crate::runtime::Number; use crate::runtime::Scope; +use crate::runtime::nanvalue::NanView; use crate::{Error, Result}; fn one_string( @@ -9,9 +10,9 @@ fn one_string( ) -> Result { match args { [arg] => { - match arg { - Value::String(s) => Ok((*s).to_string()), - other => Err(Error::type_err(proc, 0, other, "a string")), + match arg.view() { + NanView::String(s) => Ok(s.to_string()), + _ => Err(Error::type_err(proc, 0, arg, "a string")), } } _ => Err(Error::arity(proc, "1", args.len())), @@ -32,9 +33,9 @@ pub fn op_str( } let mut result = std::string::String::new(); for arg in args { - match arg { - Value::String(s) => result.push_str(s), - other => result.push_str(&other.to_string()), + match arg.view() { + NanView::String(s) => result.push_str(s), + _ => result.push_str(&arg.to_string()), } } Value::string(&result, budget).map(Eval::Done) @@ -53,7 +54,7 @@ pub fn op_codepoints( let s = one_string(args, "codepoints")?; let cps: Vec = s .chars() - .map(|c| Value::Int(c as u32 as i64)) + .map(|c| Value::int(c as u32 as i64)) .collect(); Value::array(cps, budget).map(Eval::Done) } @@ -67,16 +68,16 @@ pub fn op_from_codepoints( ) -> Result { match args { [arg] => { - let arr = match arg { - Value::Array(a) => a, - other => return Err(Error::type_err("from-codepoints", 0, other, "an array")), + let ha = match arg.view() { + NanView::Array(ha) => ha, + _ => return Err(Error::type_err("from-codepoints", 0, arg, "an array")), }; let mut result = std::string::String::new(); - for (idx, v) in arr.iter().enumerate() { - let cp = match v { - Value::Int(i) if *i >= 0 && *i <= 0x10FFFF => Some(*i as u32), - Value::BigNum(n) => { - if let Number::Integer(iv) = &**n { + for (idx, v) in ha.data.iter().enumerate() { + let cp = match v.view() { + NanView::Int(i) if i >= 0 && i <= 0x10FFFF => Some(i as u32), + NanView::BigNum(n) => { + if let Number::Integer(iv) = n { iv.to_u32().filter(|&u| u <= 0x10FFFF) } else { None diff --git a/src/runtime/procs/types.rs b/src/runtime/procs/types.rs index 6fffa3d..afb2ca8 100644 --- a/src/runtime/procs/types.rs +++ b/src/runtime/procs/types.rs @@ -1,6 +1,7 @@ use crate::runtime::{Budget, Eval, Value}; use crate::runtime::Scope; use crate::runtime::number::Number; +use crate::runtime::nanvalue::NanView; use crate::runtime::String as RStr; use crate::{Error, Result}; use crate::typing::JestType; @@ -29,7 +30,7 @@ pub(crate) fn typeof_value(val: Value, budget: &Budget, scope: &Scope) -> Result if let Some((key, inner)) = val.as_call() { // runtime/type: report as {"type": "type", "name": } if key == "type" { - if let Value::Object(payload) = &inner + if let NanView::Object(payload) = inner.view() && let Some(name_val) = payload.get("name") { let type_str = Value::string("type", budget)?; @@ -86,16 +87,16 @@ pub(crate) fn typeof_value(val: Value, budget: &Budget, scope: &Scope) -> Result // (even though as_call() matched, the key isn't a known callable form) } } - match val { - Value::Null => { + match val.view() { + NanView::Null => { let null_str = Value::string("null", budget)?; Value::object(vec![(type_key, null_str)], budget) } - Value::Bool(b) => { + NanView::Bool(b) => { let bool_str = Value::string("bool", budget)?; - Value::object(vec![(type_key, bool_str), (value_key, Value::Bool(b))], budget) + Value::object(vec![(type_key, bool_str), (value_key, Value::bool(b))], budget) } - Value::Int(_) | Value::Float(_) | Value::BigNum(_) => { + NanView::Int(_) | NanView::Float(_) | NanView::BigNum(_) => { let n = val.as_number().unwrap(); let type_name = match &n { Number::Integer(_) => "integer", @@ -104,20 +105,20 @@ pub(crate) fn typeof_value(val: Value, budget: &Budget, scope: &Scope) -> Result let name_str = Value::string(type_name, budget)?; Value::object(vec![(type_key, name_str), (value_key, val)], budget) } - Value::String(_) => { + NanView::String(_) => { let str_str = Value::string("string", budget)?; Value::object(vec![(type_key, str_str), (value_key, val)], budget) } - Value::Array(ref arr) => { + NanView::Array(ha) => { let arr_str = Value::string("array", budget)?; let count_key = RStr::new("count", budget)?; - let count_val = Value::Int(arr.len() as i64); + let count_val = Value::int(ha.data.len() as i64); Value::object(vec![(count_key, count_val), (type_key, arr_str)], budget) } - Value::Object(ref obj) => { + NanView::Object(ho) => { let obj_str = Value::string("object", budget)?; let keys_key = RStr::new("keys", budget)?; - let key_vals: Vec = obj.iter() + let key_vals: Vec = ho.iter() .map(|(k, _)| Value::string(k, budget)) .collect::>()?; let keys_arr = Value::array(key_vals, budget)?; @@ -168,22 +169,22 @@ fn value_to_type(val: &Value) -> JestType { _ => JestType::Top, }; } - match val { - Value::Null => JestType::Null, - Value::Bool(b) => JestType::Bool(Some(*b)), - Value::Int(i) => JestType::Integer(crate::typing::types::IntegerInfo::Exact(crate::runtime::number::IntegerValue::from_i64(*i))), - Value::Float(f) => JestType::Float(Some(crate::runtime::number::FloatValue::from_f64(*f))), - Value::BigNum(n) => match &**n { + match val.view() { + NanView::Null => JestType::Null, + NanView::Bool(b) => JestType::Bool(Some(b)), + NanView::Int(i) => JestType::Integer(crate::typing::types::IntegerInfo::Exact(crate::runtime::number::IntegerValue::from_i64(i))), + NanView::Float(f) => JestType::Float(Some(crate::runtime::number::FloatValue::from_f64(f))), + NanView::BigNum(n) => match n { Number::Integer(i) => JestType::Integer(crate::typing::types::IntegerInfo::Exact(i.clone())), Number::Float(f) => JestType::Float(Some(f.clone())), }, - Value::String(s) => JestType::String(StringInfo::Exact(s.to_string())), - Value::Array(arr) => { - let head: Vec = arr.iter().map(value_to_type).collect(); + NanView::String(s) => JestType::String(StringInfo::Exact(s.to_string())), + NanView::Array(ha) => { + let head: Vec = ha.data.iter().map(value_to_type).collect(); JestType::Array(ArrayType::fixed(head)) } - Value::Object(obj) => { - let fields = obj + NanView::Object(ho) => { + let fields = ho .iter() .map(|(k, v)| (k.to_string(), value_to_type(v))) .collect(); diff --git a/src/runtime/procs/var.rs b/src/runtime/procs/var.rs index 9c6cf83..3343d75 100644 --- a/src/runtime/procs/var.rs +++ b/src/runtime/procs/var.rs @@ -1,4 +1,5 @@ use crate::runtime::data::String as RStr; +use crate::runtime::nanvalue::NanView; use crate::runtime::{Budget, Eval, Scope, Value, eval_trampoline}; use crate::{Error, Result}; @@ -10,7 +11,17 @@ pub fn op_var( scope: Scope, ) -> Result { let (name, path) = match args { - [Value::String(s), rest @ ..] => (&**s as &str, rest), + [first, rest @ ..] => { + if let NanView::String(s) = first.view() { + (&**s as &str, rest) + } else { + return Err(Error::BadSyntax { + proc: "var", + args: format!("{:?}", args.iter().map(|v| v.to_string()).collect::>()), + expected: "variable name as first argument", + }); + } + } _ => { return Err(Error::BadSyntax { proc: "var", @@ -27,34 +38,34 @@ pub fn op_var( for step in path { let key = eval_trampoline(budget, eval_limit, step.clone(), scope.clone())?; let data = value.clone(); - value = match (value, key) { - (Value::Array(arr), ref key_v) if key_v.is_number() => { - let n = key_v.as_number().unwrap(); + value = match (value.view(), key.view()) { + (NanView::Array(ha), _) if key.is_number() => { + let n = key.as_number().unwrap(); let i = match n.as_integer() { Some(iv) if !iv.is_negative() => iv.to_usize().ok_or_else(|| Error::Access { data: data.to_string(), - key: key_v.to_string(), + key: key.to_string(), })?, _ => return Err(Error::Access { data: data.to_string(), - key: key_v.to_string(), + key: key.to_string(), }), }; - arr.get(i).cloned().ok_or_else(|| Error::Access { + ha.data.get(i).cloned().ok_or_else(|| Error::Access { data: data.to_string(), - key: key_v.to_string(), + key: key.to_string(), })? } - (Value::Object(obj), Value::String(s)) => { - obj.get(&s).cloned().ok_or_else(|| Error::Access { + (NanView::Object(ho), NanView::String(s)) => { + ho.get(&**s).cloned().ok_or_else(|| Error::Access { data: data.to_string(), - key: format!("\"{}\"", &*s), + key: format!("\"{}\"", &**s), })? } - (_, k) => { + _ => { return Err(Error::Access { data: data.to_string(), - key: k.to_string(), + key: key.to_string(), }); } }; diff --git a/src/runtime/scope.rs b/src/runtime/scope.rs index 5338c97..5df72cb 100644 --- a/src/runtime/scope.rs +++ b/src/runtime/scope.rs @@ -27,7 +27,7 @@ impl GlobalFrame { .enumerate() .map(|(i, &name)| (name.to_string(), i as u32)) .collect(); - let mut slots = vec![Value::Null; names.len()]; + let mut slots = vec![Value::null(); names.len()]; for (name, val) in &vars { if let Some(&idx) = slot_index.get(name) { slots[idx as usize] = val.clone(); diff --git a/src/tests.rs b/src/tests.rs index 490babf..0029713 100644 --- a/src/tests.rs +++ b/src/tests.rs @@ -1,4 +1,5 @@ use crate::Runtime; +use crate::runtime::nanvalue::NanView; /// Evaluate a JSON source string and return the result as a compact JSON string. fn run(src: &str) -> String { @@ -52,8 +53,8 @@ fn run_suite(source: &str) { }; // Extract fields - let case_obj = match &case { - crate::Value::Object(obj) => obj, + let case_obj = match case.view() { + NanView::Object(ho) => ho, _ => { failures.push(format!("line {}: test case must be an object", line_no + 1)); continue; @@ -73,7 +74,7 @@ fn run_suite(source: &str) { }; let expect_error = case_obj.get("error") - .map(|v| matches!(v, crate::Value::Bool(true))) + .map(|v| matches!(v.view(), NanView::Bool(true))) .unwrap_or(false); count += 1; @@ -258,8 +259,8 @@ fn function_value_has_embedded_scope_object() { assert_eq!(wrap_key, "wrap"); if let Some((vau_key, vau_arg)) = wrap_arg.as_call() { assert_eq!(vau_key, "vau"); - if let crate::Value::Array(items) = vau_arg { - let scope_obj = items.iter().last().expect("vau value should have elements"); + if let NanView::Array(ha) = vau_arg.view() { + let scope_obj = ha.data.iter().last().expect("vau value should have elements"); // scope_obj is {"scope": {...}} — a singleton object if let Some((scope_key, _scope_arg)) = scope_obj.as_call() { assert_eq!( @@ -295,7 +296,7 @@ fn proc_predicate_recognizes_function_values() { #[test] fn merge_combines_objects_later_wins() { let result = run_val(r#"{"merge": [{"a": 1, "b": 2}, {"b": 99, "c": 3}]}"#); - if let crate::Value::Object(obj) = &result { + if let NanView::Object(obj) = result.view() { assert_eq!(obj.get("a").unwrap().to_string(), "1"); assert_eq!(obj.get("b").unwrap().to_string(), "99"); // later arg wins assert_eq!(obj.get("c").unwrap().to_string(), "3"); @@ -311,7 +312,7 @@ fn merge_combines_objects_later_wins() { #[test] fn select_builds_object_from_scope() { let result = run_val(r#"{"do": [{"def": ["x", 1]}, {"def": ["y", 2]}, {"def": ["z", 3]}, {"select": ["x", "z"]}]}"#); - if let crate::Value::Object(obj) = &result { + if let NanView::Object(obj) = result.view() { assert_eq!(obj.get("x").unwrap().to_string(), "1"); assert_eq!(obj.get("z").unwrap().to_string(), "3"); assert_eq!(obj.len(), 2); @@ -337,7 +338,7 @@ fn select_undefined_name_is_error() { #[test] fn select_as_module_export() { let result = run_val(r#"{"do": [{"defn": ["double", [{"var": "x"}], {"+": [{"var": "x"}, {"var": "x"}]}]}, {"def": ["private-helper", 99]}, {"select": ["double"]}]}"#); - if let crate::Value::Object(obj) = &result { + if let NanView::Object(obj) = result.view() { assert!(obj.get("double").is_some()); assert!(obj.get("private-helper").is_none()); } else { @@ -349,7 +350,7 @@ fn select_as_module_export() { #[test] fn get_scope_contains_local_binding() { let result = run_val(r#"{"let*": [[["secret", 42]], {"get-scope": []}]}"#); - if let crate::Value::Object(obj) = &result { + if let NanView::Object(obj) = result.view() { assert_eq!( obj.get("secret").expect("scope must contain 'secret'").to_string(), "42" @@ -522,6 +523,7 @@ fn arithmetic_negation_bigint_succeeds() { #[cfg(test)] mod mock_input_tests { use crate::runtime::{IntegerValue, FloatValue}; + use crate::runtime::nanvalue::NanView; use crate::{JestInput, Number, Runtime, ValueKind}; // ── MockInput definition ────────────────────────────────────────────────── @@ -595,8 +597,8 @@ mod mock_input_tests { #[test] fn mock_float_self_evaluates() { let result = run_val(&MockInput::Float(3.14)); - if let crate::Value::Float(f) = &result { - assert_eq!(*f, 3.14); + if let NanView::Float(f) = result.view() { + assert_eq!(f, 3.14); } else { panic!("expected Float, got {:?}", result); } @@ -640,7 +642,7 @@ mod mock_input_tests { ("b".to_string(), MockInput::Int(99)), ]); let result = run_val(&program); - if let crate::Value::Object(obj) = &result { + if let NanView::Object(obj) = result.view() { assert_eq!(obj.get("a").unwrap().to_string(), "2"); assert_eq!(obj.get("b").unwrap().to_string(), "99"); } else { @@ -930,7 +932,7 @@ fn parse_round_trips_with_str() { #[test] fn type_of_integer_literal() { let result = run_val(r#"{"type-of": 42}"#); - if let crate::Value::Object(obj) = &result { + if let NanView::Object(obj) = result.view() { assert_eq!(obj.get("type").unwrap().to_string(), r#""integer""#); assert_eq!(obj.get("value").unwrap().to_string(), "42"); } else { @@ -941,7 +943,7 @@ fn type_of_integer_literal() { #[test] fn type_of_string_literal() { let result = run_val(r#"{"type-of": "hello"}"#); - if let crate::Value::Object(obj) = &result { + if let NanView::Object(obj) = result.view() { assert_eq!(obj.get("type").unwrap().to_string(), r#""string""#); assert_eq!(obj.get("value").unwrap().to_string(), r#""hello""#); } else { @@ -952,7 +954,7 @@ fn type_of_string_literal() { #[test] fn type_of_null_literal() { let result = run_val(r#"{"type-of": null}"#); - if let crate::Value::Object(obj) = &result { + if let NanView::Object(obj) = result.view() { assert_eq!(obj.get("type").unwrap().to_string(), r#""null""#); } else { panic!("type-of should return an object, got {result}"); @@ -962,7 +964,7 @@ fn type_of_null_literal() { #[test] fn type_of_bool_literal() { let result = run_val(r#"{"type-of": true}"#); - if let crate::Value::Object(obj) = &result { + if let NanView::Object(obj) = result.view() { assert_eq!(obj.get("type").unwrap().to_string(), r#""bool""#); assert_eq!(obj.get("value").unwrap().to_string(), "true"); } else { @@ -973,7 +975,7 @@ fn type_of_bool_literal() { #[test] fn type_of_array_literal() { let result = run_val(r#"{"type-of": [[1, "a"]]}"#); - if let crate::Value::Object(obj) = &result { + if let NanView::Object(obj) = result.view() { assert_eq!(obj.get("type").unwrap().to_string(), r#""array""#); } else { panic!("type-of should return an object, got {result}"); @@ -984,7 +986,7 @@ fn type_of_array_literal() { fn type_of_with_scope_variable() { // type-of is a macro: it receives {var: "x"} unevaluated and infers its type from scope let result = run_val(r#"{"let*": [[["x", 42]], {"type-of": {"var": "x"}}]}"#); - if let crate::Value::Object(obj) = &result { + if let NanView::Object(obj) = result.view() { assert_eq!(obj.get("type").unwrap().to_string(), r#""integer""#); assert_eq!(obj.get("value").unwrap().to_string(), "42"); } else { @@ -996,7 +998,7 @@ fn type_of_with_scope_variable() { fn type_of_expression_plus() { // type-of on an arithmetic expression: infers integer type let result = run_val(r#"{"type-of": {"+": [1, 2]}}"#); - if let crate::Value::Object(obj) = &result { + if let NanView::Object(obj) = result.view() { // The type checker should infer at least integer for constant int + int assert_eq!(obj.get("type").unwrap().to_string(), r#""integer""#); } else { @@ -1009,7 +1011,7 @@ fn type_of_does_not_evaluate() { // type-of is a macro: it must not evaluate its argument // If it did evaluate, this would cause a division-by-zero error let result = run_val(r#"{"type-of": {"/": [1, 0]}}"#); - if let crate::Value::Object(obj) = &result { + if let NanView::Object(obj) = result.view() { // Should still return a type (float, since division returns float) without error assert_eq!(obj.get("type").unwrap().to_string(), r#""float""#); } else { @@ -1020,7 +1022,7 @@ fn type_of_does_not_evaluate() { #[test] fn type_of_float_literal() { let result = run_val(r#"{"type-of": 3.14}"#); - if let crate::Value::Object(obj) = &result { + if let NanView::Object(obj) = result.view() { assert_eq!(obj.get("type").unwrap().to_string(), r#""float""#); } else { panic!("type-of should return an object, got {result}"); @@ -1044,7 +1046,7 @@ fn try_catches_arithmetic_error() { let result = run_val(r#"{"try": {"/": [1, 0]}}"#); if let Some((key, arg)) = result.as_call() { assert_eq!(key, "error"); - if let crate::Value::Object(inner) = arg { + if let NanView::Object(inner) = arg.view() { assert_eq!(inner.get("type").unwrap().to_string(), r#""arithmetic""#); assert_eq!(inner.get("proc").unwrap().to_string(), r#""/""#); assert_eq!(inner.get("kind").unwrap().to_string(), r#""divide-by-zero""#); @@ -1061,7 +1063,7 @@ fn try_catches_undefined_name() { let result = run_val(r#"{"try": {"var": "nonexistent"}}"#); if let Some((key, arg)) = result.as_call() { assert_eq!(key, "error"); - if let crate::Value::Object(inner) = arg { + if let NanView::Object(inner) = arg.view() { assert_eq!(inner.get("type").unwrap().to_string(), r#""name""#); assert_eq!(inner.get("name").unwrap().to_string(), r#""nonexistent""#); } else { @@ -1098,7 +1100,7 @@ fn try_with_budget() { let result = run_val(r#"{"try": [{"letrec": [{"f": {"fn": [[{"var": "n"}], {"f": [{"var": "n"}]}]}}, {"f": [0]}]}, 50]}"#); if let Some((key, arg)) = result.as_call() { assert_eq!(key, "error"); - if let crate::Value::Object(inner) = arg { + if let NanView::Object(inner) = arg.view() { assert_eq!(inner.get("type").unwrap().to_string(), r#""eval-limit""#); } else { panic!("error arg should be an object, got {}", arg); @@ -1285,7 +1287,7 @@ fn matches_pred_array_wrong_length() { #[test] fn match_result_success() { let result = run_val(r#"{"match-result": [42, {"var": "x"}]}"#); - if let crate::Value::Object(obj) = &result { + if let NanView::Object(obj) = result.view() { assert_eq!(obj.get("ok").unwrap().to_string(), "true"); assert_eq!(obj.get("x").unwrap().to_string(), "42"); } else { @@ -1302,7 +1304,7 @@ fn match_result_failure() { #[test] fn match_result_multiple_bindings() { let result = run_val(r#"{"match-result": [[1, 2], [{"var": "a"}, {"var": "b"}]]}"#); - if let crate::Value::Object(obj) = &result { + if let NanView::Object(obj) = result.view() { assert_eq!(obj.get("ok").unwrap().to_string(), "true"); assert_eq!(obj.get("a").unwrap().to_string(), "1"); assert_eq!(obj.get("b").unwrap().to_string(), "2"); @@ -1336,7 +1338,7 @@ fn pattern_self_evaluates() { #[test] fn and_pattern_both_match() { let result = run_val(r#"{"do": [{"def": ["p1", {"pattern": [{"var": "a"}, {"var": null}]}]}, {"def": ["p2", {"pattern": [{"var": null}, {"var": "b"}]}]}, {"def": ["combined", {"and-pattern": [{"var": "p1"}, {"var": "p2"}]}]}, {"match-result": [[1, 2], {"var": "combined"}]}]}"#); - if let crate::Value::Object(obj) = &result { + if let NanView::Object(obj) = result.view() { assert_eq!(obj.get("ok").unwrap().to_string(), "true"); assert_eq!(obj.get("a").unwrap().to_string(), "1"); assert_eq!(obj.get("b").unwrap().to_string(), "2"); @@ -1433,7 +1435,7 @@ fn try_catches_import_file_not_found() { let result = rt.run_json(r#"{"try": {"do": [{"import": ["./nonexistent.jst", null]}]}}"#).unwrap(); if let Some((key, arg)) = result.as_call() { assert_eq!(key, "error"); - if let crate::Value::Object(inner) = arg { + if let NanView::Object(inner) = arg.view() { assert_eq!(inner.get("type").unwrap().to_string(), r#""syntax""#); } else { panic!("error arg should be an object, got {}", arg); @@ -1451,7 +1453,7 @@ fn try_catches_import_bad_syntax() { let result = rt.run_json(r#"{"try": {"do": [{"import": ["./bad.jst", null]}]}}"#).unwrap(); if let Some((key, arg)) = result.as_call() { assert_eq!(key, "error"); - if let crate::Value::Object(inner) = arg { + if let NanView::Object(inner) = arg.view() { assert_eq!(inner.get("type").unwrap().to_string(), r#""parse""#); } else { panic!("error arg should be an object, got {}", arg); @@ -1480,7 +1482,7 @@ fn try_import_with_budget() { let result = rt.run_json(r#"{"try": [{"do": [{"import": ["./loop.jst", null]}]}, 100]}"#).unwrap(); if let Some((key, arg)) = result.as_call() { assert_eq!(key, "error"); - if let crate::Value::Object(inner) = arg { + if let NanView::Object(inner) = arg.view() { assert_eq!(inner.get("type").unwrap().to_string(), r#""eval-limit""#); } else { panic!("error arg should be an object, got {}", arg); diff --git a/src/typing/check/collect.rs b/src/typing/check/collect.rs index 9a8a30b..1790782 100644 --- a/src/typing/check/collect.rs +++ b/src/typing/check/collect.rs @@ -5,6 +5,7 @@ use super::registry::{BuiltinRegistry, apply_ret_rule, value_to_type}; use super::termination; use crate::runtime::Value; use crate::runtime::Number; +use crate::runtime::nanvalue::NanView; use crate::typing::intervals::Interval; use crate::runtime::procs::forms::{ DoStmtKind, classify_do_stmt, parse_def, parse_defn, parse_defrec_entries, @@ -36,13 +37,13 @@ macro_rules! unpack_args { // Exact form: all required, no optional. ($op:literal, $args:expr, $call_ptr:expr, $obs:expr => [$($req:ident),+ $(,)?]) => { let _n = 0usize $(+ { let _ = stringify!($req); 1 })+; - let _arr = match $args { - Value::Array(arr) if arr.len() == _n => arr, - Value::Array(arr) => { + let _arr = match $args.view() { + NanView::Array(ha) if ha.data.len() == _n => ha, + NanView::Array(ha) => { $obs.error(TypeErrorKind::Arity { op: $op.into(), expected: _n.to_string(), - actual: arr.len(), + actual: ha.data.len(), }, $call_ptr.clone()); return JestType::Bottom; } @@ -56,7 +57,7 @@ macro_rules! unpack_args { } }; let mut _i = 0usize; - $(let $req = &_arr[{ let i = _i; _i += 1; i }];)+ + $(let $req = &_arr.data[{ let i = _i; _i += 1; i }];)+ }; // Range form: required args + optional trailing args. // Required binds as &Value; optional binds as Option<&Value>. @@ -64,13 +65,13 @@ macro_rules! unpack_args { => [$($req:ident),+ $(,)?] + [$($opt:ident),+ $(,)?]) => { let _nreq = 0usize $(+ { let _ = stringify!($req); 1 })+; let _nopt = 0usize $(+ { let _ = stringify!($opt); 1 })+; - let _arr = match $args { - Value::Array(arr) if arr.len() >= _nreq && arr.len() <= _nreq + _nopt => arr, - Value::Array(arr) => { + let _arr = match $args.view() { + NanView::Array(ha) if ha.data.len() >= _nreq && ha.data.len() <= _nreq + _nopt => ha, + NanView::Array(ha) => { $obs.error(TypeErrorKind::Arity { op: $op.into(), expected: format!("{} or {}", _nreq, _nreq + _nopt), - actual: arr.len(), + actual: ha.data.len(), }, $call_ptr.clone()); return JestType::Bottom; } @@ -84,8 +85,8 @@ macro_rules! unpack_args { } }; let mut _i = 0usize; - $(let $req = &_arr[{ let i = _i; _i += 1; i }];)+ - $(let $opt = _arr.get({ let i = _i; _i += 1; i });)+ + $(let $req = &_arr.data[{ let i = _i; _i += 1; i }];)+ + $(let $opt = _arr.data.get({ let i = _i; _i += 1; i });)+ }; } @@ -570,8 +571,9 @@ fn derive_residue_info( body: &Value, ) -> Option { if let Some((op, args)) = body.as_call() - && let Value::Array(arr) = args + && let NanView::Array(ha) = args.view() { + let arr = &ha.data; if op == "==" && arr.len() == 2 { // Check {"%": [x, M]} == R if let Some((r, m)) = extract_modulo_cmp(param_name, &arr[0], &arr[1]) { @@ -589,7 +591,8 @@ fn derive_residue_info( /// Extract (residue_value, modulus) from `{"%": [x, M]}` on LHS and literal R on RHS. fn extract_modulo_cmp(param_name: &str, mod_expr: &Value, residue_val: &Value) -> Option<(u64, u64)> { if let Some(("%", mod_args)) = mod_expr.as_call() { - if let Value::Array(mod_arr) = mod_args { + if let NanView::Array(mod_ha) = mod_args.view() { + let mod_arr = &mod_ha.data; if mod_arr.len() == 2 && is_param_ref(param_name, &mod_arr[0]) { @@ -617,7 +620,8 @@ fn derive_array_shape( ) -> Option> { // Check if this is a comparison involving count if let Some((op, args)) = body.as_call() { - if let Value::Array(arr) = args { + if let NanView::Array(ha) = args.view() { + let arr = &ha.data; // Handle count comparisons: {">": [{"count": x}, N]} if matches!(op, ">" | ">=" | "==" | "<" | "<=") && arr.len() == 2 { if let Some(n) = extract_count_cmp(param_name, op, &arr[0], &arr[1]) { @@ -680,14 +684,14 @@ fn extract_count_cmp_reversed(param_name: &str, _op: &str, lhs: &Value, rhs: &Va fn is_param_ref(param_name: &str, val: &Value) -> bool { // Direct: {"var": "param"} if let Some(("var", arg)) = val.as_call() { - if let Value::String(s) = arg { + if let NanView::String(s) = arg.view() { return &**s == param_name; } } // Wrapped in array: [{"var": "param"}] (common for applicative builtins) - if let Value::Array(arr) = val { - if arr.len() == 1 { - return is_param_ref(param_name, &arr[0]); + if let NanView::Array(ha) = val.view() { + if ha.data.len() == 1 { + return is_param_ref(param_name, &ha.data[0]); } } false @@ -695,8 +699,8 @@ fn is_param_ref(param_name: &str, val: &Value) -> bool { /// Extract a usize from a literal integer Value. fn value_to_usize(val: &Value) -> Option { - match val { - Value::Int(i) if *i >= 0 => Some(*i as usize), + match val.view() { + NanView::Int(i) if i >= 0 => Some(i as usize), _ => None, } } @@ -764,7 +768,8 @@ fn shape_up_to(max: usize) -> super::type_shape::Shape { fn derive_not_shape(param_name: &str, inner: &Value) -> Option> { use super::type_shape::Shape; if let Some(("==", args)) = inner.as_call() { - if let Value::Array(arr) = args { + if let NanView::Array(ha) = args.view() { + let arr = &ha.data; if arr.len() == 2 { if let Some(n) = extract_count_cmp(param_name, "==", &arr[0], &arr[1]) { if n == 0 { @@ -891,9 +896,9 @@ fn scan_recursive_element_pattern( } } - if let Value::Array(arr) = args { + if let NanView::Array(ha) = args.view() { // Recurse into combinators - for elem in arr.iter() { + for elem in ha.data.iter() { scan_recursive_element_pattern( pred_name, param_name, elem, elem_shape, has_rest_recursion, has_first_recursion, scalar_types, @@ -907,8 +912,8 @@ fn scan_recursive_element_pattern( } } // Recurse into array elements (do-blocks, etc.) - if let Value::Array(arr) = body { - for elem in arr.iter() { + if let NanView::Array(ha) = body.view() { + for elem in ha.data.iter() { scan_recursive_element_pattern( pred_name, param_name, elem, elem_shape, has_rest_recursion, has_first_recursion, scalar_types, @@ -925,9 +930,9 @@ fn is_first_of_param_value(param_name: &str, val: &Value) -> bool { return true; } // Wrapped in array: [{"first": [x]}] - if let Value::Array(arr) = val { - if arr.len() == 1 { - return is_first_of_param(param_name, &arr[0]); + if let NanView::Array(ha) = val.view() { + if ha.data.len() == 1 { + return is_first_of_param(param_name, &ha.data[0]); } } false @@ -1007,7 +1012,8 @@ fn scan_relational_pattern<'a>( has_recursive: &mut bool, ) { if let Some((op, args)) = body.as_call() { - if let Value::Array(arr) = args { + if let NanView::Array(ha) = args.view() { + let arr = &ha.data; // Check for pairwise comparison: {"<=": [{"first": [x]}, {"first": [{"rest": [x]}]}]} if matches!(op, "<=" | "<" | ">=" | ">" | "!=" | "==") && arr.len() == 2 { if is_first_of_param(param_name, &arr[0]) @@ -1055,8 +1061,8 @@ fn scan_relational_pattern<'a>( } } // Also recurse into array elements (for do-blocks, etc.) - if let Value::Array(arr) = body { - for elem in arr.iter() { + if let NanView::Array(ha) = body.view() { + for elem in ha.data.iter() { scan_relational_pattern(pred_name, param_name, elem, cmp_op, has_recursive); } } @@ -1084,9 +1090,9 @@ fn is_rest_of_param(param_name: &str, val: &Value) -> bool { return is_param_ref(param_name, arg); } // Wrapped in array: [{"rest": [x]}] - if let Value::Array(arr) = val { - if arr.len() == 1 { - return is_rest_of_param(param_name, &arr[0]); + if let NanView::Array(ha) = val.view() { + if ha.data.len() == 1 { + return is_rest_of_param(param_name, &ha.data[0]); } } false @@ -1212,32 +1218,32 @@ pub(super) fn collect( ctx: &mut TypeContext, obs: &mut Vec, reg: &BuiltinRegistry, ) -> JestType { - match expr { - Value::Null => JestType::Null, - Value::Bool(b) => JestType::Bool(Some(*b)), - Value::Int(i) => JestType::Integer(crate::typing::IntegerInfo::Exact(crate::runtime::IntegerValue::from_i64(*i))), - Value::Float(f) => JestType::Float(Some(crate::runtime::FloatValue::from_f64(*f))), - Value::BigNum(n) => match &**n { + // Check for call first (before general Object matching) + if let Some((proc_name, args)) = expr.as_call() { + let args_ptr = ptr.push_key(proc_name); + return collect_call(proc_name, args, &args_ptr, ptr, env, ctx, obs, reg); + } + match expr.view() { + NanView::Null => JestType::Null, + NanView::Bool(b) => JestType::Bool(Some(b)), + NanView::Int(i) => JestType::Integer(crate::typing::IntegerInfo::Exact(crate::runtime::IntegerValue::from_i64(i))), + NanView::Float(f) => JestType::Float(Some(crate::runtime::FloatValue::from_f64(f))), + NanView::BigNum(n) => match n { Number::Integer(i) => JestType::Integer(crate::typing::IntegerInfo::Exact(i.clone())), Number::Float(f) => JestType::Float(Some(f.clone())), }, - Value::String(s) => JestType::String(StringInfo::Exact(s.to_string())), - Value::Array(arr) => { - let head: Vec<_> = arr + NanView::String(s) => JestType::String(StringInfo::Exact(s.to_string())), + NanView::Array(ha) => { + let head: Vec<_> = ha.data .iter() .enumerate() .map(|(i, e)| collect(e, &ptr.push_index(i), env, ctx, obs, reg)) .collect(); JestType::Array(ArrayType::fixed(head)) } - _ if expr.as_call().is_some() => { - let (proc_name, args) = expr.as_call().unwrap(); - let args_ptr = ptr.push_key(proc_name); - collect_call(proc_name, args, &args_ptr, ptr, env, ctx, obs, reg) - } - Value::Object(obj) => { + NanView::Object(ho) => { let mut fields = BTreeMap::new(); - for (k, v) in obj.iter() { + for (k, v) in ho.iter() { fields.insert( k.to_string(), collect(v, &ptr.push_key(&**k), env, ctx, obs, reg), @@ -1265,9 +1271,9 @@ fn collect_call( "and" | "or" => { // Short-circuiting macros: result is one of the arg values. // Return join of all arg types (precise: any arg could be the result). - if let Value::Array(arr) = args { + if let NanView::Array(ha) = args.view() { let mut result = JestType::Bottom; - for (i, e) in arr.iter().enumerate() { + for (i, e) in ha.data.iter().enumerate() { let ty = collect(e, &args_ptr.push_index(i), env, ctx, obs, reg); result = result.join(&ty); } @@ -1292,15 +1298,15 @@ fn collect_call( )]))) } "try" => { - match args { - Value::Array(arr) if arr.len() == 1 => { + match args.view() { + NanView::Array(ha) if ha.data.len() == 1 => { // 1-arg: {"try": [expr]} — returns expr_ty | error - let expr_ty = collect(&arr[0], &args_ptr.push_index(0), env, ctx, obs, reg); + let expr_ty = collect(&ha.data[0], &args_ptr.push_index(0), env, ctx, obs, reg); expr_ty.join(&try_error_type()) } - Value::Array(arr) if arr.len() == 2 => { - let expr_ty = collect(&arr[0], &args_ptr.push_index(0), env, ctx, obs, reg); - let second_ty = collect(&arr[1], &args_ptr.push_index(1), env, ctx, obs, reg); + NanView::Array(ha) if ha.data.len() == 2 => { + let expr_ty = collect(&ha.data[0], &args_ptr.push_index(0), env, ctx, obs, reg); + let second_ty = collect(&ha.data[1], &args_ptr.push_index(1), env, ctx, obs, reg); // Disambiguate: handler (Proc) vs budget (number) match &second_ty { JestType::Proc(_, ret) => { @@ -1322,19 +1328,19 @@ fn collect_call( } } } - Value::Array(arr) if arr.len() == 3 => { + NanView::Array(ha) if ha.data.len() == 3 => { // 3-arg: {"try": [expr, handler, budget]} - let expr_ty = collect(&arr[0], &args_ptr.push_index(0), env, ctx, obs, reg); - let handler_ty = collect(&arr[1], &args_ptr.push_index(1), env, ctx, obs, reg); - collect(&arr[2], &args_ptr.push_index(2), env, ctx, obs, reg); + let expr_ty = collect(&ha.data[0], &args_ptr.push_index(0), env, ctx, obs, reg); + let handler_ty = collect(&ha.data[1], &args_ptr.push_index(1), env, ctx, obs, reg); + collect(&ha.data[2], &args_ptr.push_index(2), env, ctx, obs, reg); match &handler_ty { JestType::Proc(_, ret) => expr_ty.join(ret), _ => expr_ty.join(&try_error_type()), } } - Value::Array(arr) => { + NanView::Array(ha) => { // Malformed: collect for side-effects - for (i, e) in arr.iter().enumerate() { + for (i, e) in ha.data.iter().enumerate() { collect(e, &args_ptr.push_index(i), env, ctx, obs, reg); } JestType::Top @@ -1355,18 +1361,18 @@ fn collect_call( "match" => collect_match(args, args_ptr, call_ptr, env, ctx, obs, reg), "matches?" => { // matches? is a macro: evaluates first arg, matches against second (unevaluated). - if let Value::Array(arr) = args - && arr.len() == 2 { - collect(&arr[0], &args_ptr.push_index(0), env, ctx, obs, reg); + if let NanView::Array(ha) = args.view() + && ha.data.len() == 2 { + collect(&ha.data[0], &args_ptr.push_index(0), env, ctx, obs, reg); } JestType::Bool(None) } "match-result" => { // match-result: evaluates first arg, matches against second. // Returns null (no match) or {ok: true, ...bindings} (match). - if let Value::Array(arr) = args - && arr.len() == 2 { - collect(&arr[0], &args_ptr.push_index(0), env, ctx, obs, reg); + if let NanView::Array(ha) = args.view() + && ha.data.len() == 2 { + collect(&ha.data[0], &args_ptr.push_index(0), env, ctx, obs, reg); } let ok_obj = JestType::Object(ObjectType::open(BTreeMap::from([ ("ok".into(), JestType::Bool(Some(true))), @@ -1382,11 +1388,11 @@ fn collect_call( // eval: self-evaluating types pass through; call-form types get precise dispatch; // other code-like types → Top "eval" => { - match args { - Value::Array(arr) => { + match args.view() { + NanView::Array(ha) => { // 2-arg form: {"eval": [expr, env]} - let arg_ty = collect(&arr[0], &args_ptr.push_index(0), env, ctx, obs, reg); - for (i, e) in arr.iter().enumerate().skip(1) { + let arg_ty = collect(&ha.data[0], &args_ptr.push_index(0), env, ctx, obs, reg); + for (i, e) in ha.data.iter().enumerate().skip(1) { collect(e, &args_ptr.push_index(i), env, ctx, obs, reg); } eval_call_form_type(&arg_ty, env, ctx, reg) @@ -1402,14 +1408,14 @@ fn collect_call( } // apply: if proc type is known, return its ret type "apply" => { - match args { - Value::Array(arr) if arr.len() == 2 => { - let proc_ty = collect(&arr[0], &args_ptr.push_index(0), env, ctx, obs, reg); - collect(&arr[1], &args_ptr.push_index(1), env, ctx, obs, reg); + match args.view() { + NanView::Array(ha) if ha.data.len() == 2 => { + let proc_ty = collect(&ha.data[0], &args_ptr.push_index(0), env, ctx, obs, reg); + collect(&ha.data[1], &args_ptr.push_index(1), env, ctx, obs, reg); extract_proc_return(&proc_ty).unwrap_or(JestType::Top) } - Value::Array(arr) => { - for (i, e) in arr.iter().enumerate() { + NanView::Array(ha) => { + for (i, e) in ha.data.iter().enumerate() { collect(e, &args_ptr.push_index(i), env, ctx, obs, reg); } JestType::Top @@ -1450,15 +1456,21 @@ fn collect_var( ctx: &mut TypeContext, obs: &mut Vec, ) -> JestType { // Simple case: {"var": "name"} - let (name, path) = match args { - Value::String(s) => (&***s, &[] as &[Value]), - Value::Array(arr) => match arr.as_ref() { - [Value::String(s), rest @ ..] => (&***s, rest), - _ => { + let (name, path) = match args.view() { + NanView::String(s) => (&**s, &[] as &[Value]), + NanView::Array(ha) => { + if !ha.data.is_empty() { + if let NanView::String(s) = ha.data[0].view() { + (s as &str, &ha.data[1..]) + } else { + obs.error(TypeErrorKind::UndefinedVar("".into()), args_ptr.clone()); + return JestType::Bottom; + } + } else { obs.error(TypeErrorKind::UndefinedVar("".into()), args_ptr.clone()); return JestType::Bottom; } - }, + } _ => { obs.error(TypeErrorKind::UndefinedVar("".into()), args_ptr.clone()); return JestType::Bottom; @@ -1474,16 +1486,16 @@ fn collect_var( // Path traversal: narrow type through each static key. let mut ty = root_ty; for step in path { - ty = match step { - Value::String(key) => match &ty { + ty = match step.view() { + NanView::String(key) => match &ty { JestType::Object(obj) => { - obj.fields.get(&***key).cloned().unwrap_or(JestType::Top) + obj.fields.get(&**key).cloned().unwrap_or(JestType::Top) } _ => JestType::Top, }, - Value::Int(i) if *i >= 0 => match &ty { + NanView::Int(i) if i >= 0 => match &ty { JestType::Array(arr) => { - let idx = usize::try_from(*i).unwrap_or(usize::MAX); + let idx = i as usize; if let Some(t) = arr.type_at(idx) { t.clone() } else { @@ -1516,7 +1528,7 @@ fn collect_if( // in the source (Value::Bool), not when it folds to Bool(Some(...)) through // computation. This avoids pathological cases in recursive mini-fixpoints where // comparison folding on concrete args would hide the base case. - let is_literal_bool = matches!(cond, Value::Bool(_)); + let is_literal_bool = matches!(cond.view(), NanView::Bool(_)); if is_literal_bool { match &cond_ty { JestType::Bool(Some(true)) => { @@ -1627,8 +1639,8 @@ fn split_on_predicate( if let Some((op, args)) = cond.as_call() && matches!(op, "<" | "<=" | ">" | ">=") { - if let Value::Array(args_arr) = args { - if let Some(envs) = split_on_comparison(op, args_arr, env, ctx, obs, cond_ptr) { + if let NanView::Array(ha) = args.view() { + if let Some(envs) = split_on_comparison(op, &ha.data, env, ctx, obs, cond_ptr) { return envs; } } @@ -1640,9 +1652,9 @@ fn split_on_predicate( /// If `expr` is `{"var": "name"}`, return the name. Otherwise None. pub(super) fn singleton_var_name(expr: &Value) -> Option<&str> { if let Some(("var", arg)) = expr.as_call() - && let Value::String(s) = arg + && let NanView::String(s) = arg.view() { - return Some(&**s); + return Some(s); } None } @@ -1735,11 +1747,11 @@ fn narrow_integer_with_interval(ty: &JestType, interval: &Interval) -> JestType /// Extract a literal integer type directly from a Value without re-collecting. fn literal_integer_type(v: &Value) -> Option { - match v { - Value::Int(i) => Some(JestType::Integer(IntegerInfo::Exact( - crate::runtime::IntegerValue::from_i64(*i), + match v.view() { + NanView::Int(i) => Some(JestType::Integer(IntegerInfo::Exact( + crate::runtime::IntegerValue::from_i64(i), ))), - Value::BigNum(n) => match &**n { + NanView::BigNum(n) => match n { Number::Integer(i) => Some(JestType::Integer(IntegerInfo::Exact(i.clone()))), _ => None, }, @@ -1851,10 +1863,11 @@ fn collect_match( // Thread `remaining_ty` through: after each clause, subtract what it covered. let mut result_ty = JestType::Bottom; let mut remaining_ty = scrutinee_ty.clone(); - if let Value::Array(clause_arr) = clauses { - for (i, clause) in clause_arr.iter().enumerate() { - if let Value::Array(pair) = clause - && pair.len() == 2 { + if let NanView::Array(clause_ha) = clauses.view() { + for (i, clause) in clause_ha.data.iter().enumerate() { + if let NanView::Array(pair_ha) = clause.view() + && pair_ha.data.len() == 2 { + let pair = &pair_ha.data; let pattern = &pair[0]; let body = &pair[1]; let clause_ptr = args_ptr.push_index(1).push_index(i); @@ -1914,33 +1927,33 @@ fn extract_pattern_var_names(pattern: &Value) -> Vec { } fn extract_pattern_vars_inner(pattern: &Value, names: &mut Vec) { - match pattern { - Value::Null | Value::Bool(_) | Value::Int(_) | Value::Float(_) | Value::BigNum(_) | Value::String(_) => {} - Value::Array(arr) => { - for elem in arr.iter() { - extract_pattern_vars_inner(elem, names); - } - } - _ if pattern.as_call().is_some() => { - let (key, arg) = pattern.as_call().unwrap(); - if key == "var" { - match arg { - Value::String(s) => { - let name = s.to_string(); - if !name.is_empty() { - names.push(name); - } + if let Some((key, arg)) = pattern.as_call() { + if key == "var" { + match arg.view() { + NanView::String(s) => { + let name = s.to_string(); + if !name.is_empty() { + names.push(name); } - Value::Null => {} // wildcard - _ => {} } - } else { - // Recurse into the arg of other Call forms (cons, and-pattern, etc.) - extract_pattern_vars_inner(arg, names); + NanView::Null => {} // wildcard + _ => {} + } + } else { + // Recurse into the arg of other Call forms (cons, and-pattern, etc.) + extract_pattern_vars_inner(arg, names); + } + return; + } + match pattern.view() { + NanView::Null | NanView::Bool(_) | NanView::Int(_) | NanView::Float(_) | NanView::BigNum(_) | NanView::String(_) => {} + NanView::Array(ha) => { + for elem in ha.data.iter() { + extract_pattern_vars_inner(elem, names); } } - Value::Object(obj) => { - for (_, v) in obj.iter() { + NanView::Object(ho) => { + for (_, v) in ho.iter() { extract_pattern_vars_inner(v, names); } } @@ -1953,9 +1966,9 @@ fn extract_pattern_vars_inner(pattern: &Value, names: &mut Vec) { /// - Cons pattern `{"cons": [{"var":"x"}, {"var":"xs"}]}` → head `[Top]` + variadic `[Top]` /// - Non-array pattern (object destructuring, single var) → `fixed([Top])` (single arg) fn params_from_formals_pattern(formals: &Value) -> ArrayType { - match formals { - Value::Array(arr) => { - ArrayType::fixed(vec![JestType::Top; arr.len()]) + match formals.view() { + NanView::Array(ha) => { + ArrayType::fixed(vec![JestType::Top; ha.data.len()]) } // Check for cons pattern: {"cons": [head, tail]} → variadic _ if is_cons_pattern(formals) => { @@ -1973,7 +1986,11 @@ fn params_from_formals_pattern(formals: &Value) -> ArrayType { /// Check if a pattern is a cons pattern `{"cons": [head, tail]}`. fn is_cons_pattern(pat: &Value) -> bool { - matches!(pat.as_call(), Some(("cons", Value::Array(arr))) if arr.len() == 2) + if let Some(("cons", arg)) = pat.as_call() { + matches!(arg.view(), NanView::Array(ha) if ha.data.len() == 2) + } else { + false + } } /// Count the number of fixed head elements in a cons chain. @@ -1981,10 +1998,14 @@ fn is_cons_pattern(pat: &Value) -> bool { fn count_cons_heads(pat: &Value) -> usize { let mut count = 0; let mut current = pat; - while let Some(("cons", Value::Array(arr))) = current.as_call() { - if arr.len() == 2 { - count += 1; - current = &arr[1]; + while let Some(("cons", arg)) = current.as_call() { + if let NanView::Array(ha) = arg.view() { + if ha.data.len() == 2 { + count += 1; + current = &ha.data[1]; + } else { + break; + } } else { break; } @@ -2010,8 +2031,8 @@ fn collect_let( let bindings_ptr = args_ptr.push_index(0); // Collect (name, value-expr) pairs from either form. - let pairs: Vec<(&str, &Value)> = if let Value::Object(obj) = bindings_expr { - obj.iter().map(|(k, v)| (&**k, v)).collect() + let pairs: Vec<(&str, &Value)> = if let NanView::Object(ho) = bindings_expr.view() { + ho.iter().map(|(k, v)| (&**k, v)).collect() } else if let Some((k, v)) = bindings_expr.as_call() { vec![(k, v)] } else { @@ -2052,8 +2073,8 @@ fn collect_let_star( unpack_args!("let*", args, call_ptr, obs => [pairs_expr, body]); let pairs_ptr = args_ptr.push_index(0); - let pairs = match pairs_expr { - Value::Array(arr) => arr, + let pairs_data = match pairs_expr.view() { + NanView::Array(ha) => ha, _ => { obs.error( TypeErrorKind::Arity { @@ -2068,11 +2089,11 @@ fn collect_let_star( }; let mut current_env = Rc::clone(env); - for (i, pair) in pairs.iter().enumerate() { + for (i, pair) in pairs_data.data.iter().enumerate() { let pair_ptr = pairs_ptr.push_index(i); - let (name, val_expr) = match pair { - Value::Array(arr) if arr.len() == 2 => match &arr[0] { - Value::String(s) => (&***s, &arr[1]), + let (name, val_expr) = match pair.view() { + NanView::Array(ha) if ha.data.len() == 2 => match ha.data[0].view() { + NanView::String(s) => (&**s, &ha.data[1]), _ => { obs.error( TypeErrorKind::Arity { @@ -2090,7 +2111,7 @@ fn collect_let_star( TypeErrorKind::Arity { op: "let*".into(), expected: "pair [name, expr]".into(), - actual: if let Value::Array(a) = pair { a.len() } else { 0 }, + actual: if let NanView::Array(ha) = pair.view() { ha.data.len() } else { 0 }, }, pair_ptr, ); @@ -2127,8 +2148,8 @@ fn collect_letrec( let bindings_ptr = args_ptr.push_index(0); - let pairs: Vec<(&str, &Value)> = if let Value::Object(obj) = bindings_expr { - obj.iter().map(|(k, v)| (&**k, v)).collect() + let pairs: Vec<(&str, &Value)> = if let NanView::Object(ho) = bindings_expr.view() { + ho.iter().map(|(k, v)| (&**k, v)).collect() } else if let Some((k, v)) = bindings_expr.as_call() { vec![(k, v)] } else { @@ -2225,11 +2246,11 @@ fn collect_letrec( .map(|(name, rhs)| { if let Some((key, arg)) = rhs.as_call() && matches!(key, "fn" | "macro" | "vau") - && let Value::Array(arr) = arg - && arr.len() == 2 + && let NanView::Array(ha) = arg.view() + && ha.data.len() == 2 { - let formals = extract_pattern_var_names(&arr[0]); - return (name.clone(), formals, arr[1].clone()); + let formals = extract_pattern_var_names(&ha.data[0]); + return (name.clone(), formals, ha.data[1].clone()); } (name.clone(), vec![], rhs.clone()) }) @@ -2281,10 +2302,11 @@ fn collect_function( ctx: &mut TypeContext, obs: &mut Vec, reg: &BuiltinRegistry, ) -> JestType { - let arr = match args { - Value::Array(arr) if arr.len() == 2 || arr.len() == 3 => arr, + let arr_ha = match args.view() { + NanView::Array(ha) if ha.data.len() == 2 || ha.data.len() == 3 => ha, _ => return JestType::Top, }; + let arr = &arr_ha.data; // Only [formals, body] (source) or [formals, body, scope_obj] (value) forms. // Value forms (3-element) are closed — return Top. @@ -2362,10 +2384,11 @@ fn collect_do( ctx: &mut TypeContext, obs: &mut Vec, reg: &BuiltinRegistry, ) -> JestType { - let stmts = match args { - Value::Array(arr) => arr, - other => return collect(other, args_ptr, env, ctx, obs, reg), + let stmts_ha = match args.view() { + NanView::Array(ha) => ha, + _ => return collect(args, args_ptr, env, ctx, obs, reg), }; + let stmts = &stmts_ha.data; let mut current_env = Rc::clone(env); let mut last_ty = JestType::Null; @@ -2381,9 +2404,9 @@ fn collect_do( ) { let bid = BindingId::new(name.clone(), stmt_ptr.clone()); // Extract FnDef from the def RHS (inner[1]) - let fndef = if let Value::Array(arr) = inner { - if arr.len() == 2 { - try_extract_fndef(&arr[1], ¤t_env) + let fndef = if let NanView::Array(ha) = inner.view() { + if ha.data.len() == 2 { + try_extract_fndef(&ha.data[1], ¤t_env) } else { None } @@ -2779,25 +2802,28 @@ fn collect_do_deftype( ctx: &mut TypeContext, obs: &mut Vec, reg: &BuiltinRegistry, ) -> Rc { - if let Value::Array(arr) = inner - && arr.len() == 3 - && let Value::String(name) = &arr[0] + if let NanView::Array(arr_ha) = inner.view() + && arr_ha.data.len() == 3 + && let NanView::String(name) = arr_ha.data[0].view() { + let arr = &arr_ha.data; let name_s: std::string::String = name.to_string(); let pred_name = format!("{}?", name_s); // Legacy form: ["name", "base-family", predicate_fn] - if let Value::String(base_family) = &arr[1] { - let mut base_ty = family_from_string(base_family); + if let NanView::String(base_family) = arr[1].view() { + let mut base_ty = family_from_string(&base_family); // Collect the predicate fn expression for side-effects collect(&arr[2], &inner_ptr.push_index(2), current_env, ctx, obs, reg); // Tier 3.75: Extract and store predicate body for liquid-type checking if let Some(("fn", fn_args)) = arr[2].as_call() { - if let Value::Array(fn_arr) = fn_args { + if let NanView::Array(fn_ha) = fn_args.view() { + let fn_arr = &fn_ha.data; if fn_arr.len() >= 2 { - if let Value::Array(formals) = &fn_arr[0] { + if let NanView::Array(formals_ha) = fn_arr[0].view() { + let formals = &formals_ha.data; if formals.len() == 1 { if let Some(param_name) = singleton_var_name(&formals[0]) { ctx.predicate_bodies.insert( @@ -2899,8 +2925,9 @@ fn collect_do_deftype( collect(&arr[2], &inner_ptr.push_index(2), current_env, ctx, obs, reg); // Extract "refines" parent type name from opts object (if present) - let base_ty = if let Value::Object(opts_obj) = &arr[2] - && let Some(Value::String(parent_name)) = opts_obj.get("refines") + let base_ty = if let NanView::Object(opts_ho) = arr[2].view() + && let Some(parent_val) = opts_ho.get("refines") + && let NanView::String(parent_name) = parent_val.view() { // Look up parent type in refinement registry let parent_pred = format!("{}?", &**parent_name); @@ -2941,14 +2968,14 @@ fn collect_do_deftype( /// Convert a shape spec to a JestType for the payload. fn shape_to_type(shape: &Value) -> JestType { - match shape { - Value::Null => JestType::Top, - Value::Array(arr) => { - let elem_types: Vec = arr.iter().map(|spec| { - match spec { - Value::Array(spec_arr) if spec_arr.len() == 1 => { - match &spec_arr[0] { - Value::String(s) => family_from_string(s), + match shape.view() { + NanView::Null => JestType::Top, + NanView::Array(ha) => { + let elem_types: Vec = ha.data.iter().map(|spec| { + match spec.view() { + NanView::Array(spec_ha) if spec_ha.data.len() == 1 => { + match spec_ha.data[0].view() { + NanView::String(s) => family_from_string(&s), _ => JestType::Top, } } @@ -2969,17 +2996,18 @@ fn collect_do_defgeneric( current_env: &TypeEnv, ctx: &mut TypeContext, obs: &mut Vec, ) -> Rc { - let arr = match inner { - Value::Array(arr) if !arr.is_empty() => arr, + let arr_ha = match inner.view() { + NanView::Array(ha) if !ha.data.is_empty() => ha, _ => return Rc::clone(current_env), }; - let name = match &arr[0] { - Value::String(s) => s.to_string(), + let arr = &arr_ha.data; + let name = match arr[0].view() { + NanView::String(s) => s.to_string(), _ => return Rc::clone(current_env), }; - let arity: usize = match arr.get(1) { - Some(Value::Int(i)) => usize::try_from(*i).unwrap_or(2), - Some(Value::BigNum(n)) => match &**n { + let arity: usize = match arr.get(1).map(|v| v.view()) { + Some(NanView::Int(i)) => usize::try_from(i).unwrap_or(2), + Some(NanView::BigNum(n)) => match n { Number::Integer(i) => i.to_usize().unwrap_or(2), _ => 2, }, @@ -3005,12 +3033,13 @@ fn collect_do_defmethod( ctx: &mut TypeContext, obs: &mut Vec, reg: &BuiltinRegistry, ) { - let arr = match inner { - Value::Array(arr) if arr.len() == 3 => arr, + let arr_ha = match inner.view() { + NanView::Array(ha) if ha.data.len() == 3 => ha, _ => return, }; - let name = match &arr[0] { - Value::String(s) => s.to_string(), + let arr = &arr_ha.data; + let name = match arr[0].view() { + NanView::String(s) => s.to_string(), _ => return, }; // Extract type families from pattern (the defmethod pattern array) @@ -3018,7 +3047,8 @@ fn collect_do_defmethod( // Extract formal names from pattern and bind them with their family types let mut method_bindings: HashMap = HashMap::new(); - if let Value::Array(pats) = &arr[1] { + if let NanView::Array(pats_ha) = arr[1].view() { + let pats = &pats_ha.data; for (i, pat) in pats.iter().enumerate() { if let Some(var_name) = extract_method_formal_name(pat) { let family = families.get(i).map(|s| s.as_str()).unwrap_or("any"); @@ -3040,14 +3070,16 @@ fn collect_do_defmethod( fn extract_method_formal_name(pat: &Value) -> Option { // {"guard": [{"var": "a"}, pred]} → "a" if let Some(("guard" | "guard-pattern", arg)) = pat.as_call() - && let Value::Array(arr) = arg - && let Some(inner) = arr.first() + && let NanView::Array(ha) = arg.view() + && let Some(inner) = ha.data.first() { return extract_method_formal_name(inner); } // {"var": "name"} → "name" - if let Some(("var", Value::String(s))) = pat.as_call() { - return Some(s.to_string()); + if let Some(("var", arg)) = pat.as_call() { + if let NanView::String(s) = arg.view() { + return Some(s.to_string()); + } } None } @@ -3057,8 +3089,8 @@ fn extract_method_formal_name(pat: &Value) -> Option { /// Patterns look like: `[{"guard": [{"var": "a"}, {"var": "int?"}]}, ...]` /// Each element maps to a family name: "integer", "float", "rational", etc. fn extract_method_families(pattern: &Value) -> Vec { - match pattern { - Value::Array(arr) => arr.iter().map(extract_single_family).collect(), + match pattern.view() { + NanView::Array(ha) => ha.data.iter().map(extract_single_family).collect(), _ => vec![], } } @@ -3067,10 +3099,10 @@ fn extract_method_families(pattern: &Value) -> Vec { fn extract_single_family(pat: &Value) -> String { // {"guard": [var_pat, pred_ref]} or {"guard-pattern": [...]} if let Some(("guard" | "guard-pattern", arg)) = pat.as_call() - && let Value::Array(arr) = arg - && arr.len() == 2 + && let NanView::Array(ha) = arg.view() + && ha.data.len() == 2 { - return predicate_to_family(&arr[1]); + return predicate_to_family(&ha.data[1]); } // Bare {"var": ...} or anything else → "any" "any".to_string() @@ -3079,12 +3111,12 @@ fn extract_single_family(pat: &Value) -> String { /// Map a predicate reference to a family name. /// `{"var": "int?"}` → "integer", `{"var": "rational?"}` → "rational", etc. fn predicate_to_family(pred: &Value) -> String { - let pred_name = match pred { - Value::String(s) => s.to_string(), + let pred_name = match pred.view() { + NanView::String(s) => s.to_string(), _ if pred.as_call().is_some() => { let (key, arg) = pred.as_call().unwrap(); if key == "var" { - if let Value::String(s) = arg { s.to_string() } else { return "any".to_string(); } + if let NanView::String(s) = arg.view() { s.to_string() } else { return "any".to_string(); } } else { return "any".to_string(); } @@ -3119,18 +3151,19 @@ fn collect_do_import( reg: &BuiltinRegistry, ) -> Rc { let names = collect_import_names(inner); - let arr = match inner { - Value::Array(arr) if arr.len() == 2 => arr, + let arr_ha = match inner.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => { // Can't even parse the import form — bind nothing return Rc::clone(current_env); } }; + let arr = &arr_ha.data; // Try to resolve real types via file loader let module_types: Option> = (|| { - let path = match &arr[0] { - Value::String(s) => s.to_string(), + let path = match arr[0].view() { + NanView::String(s) => s.to_string(), _ => return None, }; @@ -3207,7 +3240,7 @@ fn collect_do_import( if let Some(ref module_types) = module_types { // If we got real types from the module, use them; otherwise Top // Handle namespace import: {"import": [path, "ns"]} - if let Value::String(ns) = &arr[1] { + if let NanView::String(ns) = arr[1].view() { // Namespace import: bind ns as an object with the module's types let obj_ty = JestType::Object(ObjectType::closed( module_types.iter().map(|(k, v)| (k.clone(), v.clone())).collect(), @@ -3254,7 +3287,7 @@ fn try_collect_def( TypeErrorKind::Arity { op: "def".into(), expected: msg.into(), - actual: if let Value::Array(a) = args { a.len() } else { 0 }, + actual: if let NanView::Array(ha) = args.view() { ha.data.len() } else { 0 }, }, call_ptr.clone(), ); @@ -3285,7 +3318,7 @@ fn try_collect_defn( TypeErrorKind::Arity { op: op.into(), expected: msg.into(), - actual: if let Value::Array(a) = args { a.len() } else { 0 }, + actual: if let NanView::Array(ha) = args.view() { ha.data.len() } else { 0 }, }, call_ptr.clone(), ); @@ -3351,18 +3384,19 @@ fn try_collect_defn( /// `names` can be: `null` (wildcard, can't enumerate), `"ns"` (namespace alias), /// `["a", "b"]` (selective), or `{"local": "exported"}` / `{"local": "exported"}` (rename map). fn collect_import_names(args: &Value) -> Vec { - let arr = match args { - Value::Array(arr) if arr.len() == 2 => arr, + let arr_ha = match args.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return vec![], }; - match &arr[1] { - Value::Null => vec![], // wildcard: can't enumerate names in V1 - Value::String(ns) => vec![ns.to_string()], - Value::Array(names) => names + let arr = &arr_ha.data; + match arr[1].view() { + NanView::Null => vec![], // wildcard: can't enumerate names in V1 + NanView::String(ns) => vec![ns.to_string()], + NanView::Array(names_ha) => names_ha.data .iter() - .filter_map(|v| if let Value::String(s) = v { Some(s.to_string()) } else { None }) + .filter_map(|v| if let NanView::String(s) = v.view() { Some(s.to_string()) } else { None }) .collect(), - Value::Object(obj) => obj.iter().map(|(k, _)| k.to_string()).collect(), + NanView::Object(ho) => ho.iter().map(|(k, _)| k.to_string()).collect(), _ if arr[1].as_call().is_some() => { let (k, _) = arr[1].as_call().unwrap(); vec![k.to_string()] // single-key {"local": "exported"} @@ -3381,10 +3415,11 @@ fn try_extract_fndef(v: &Value, closure_env: &TypeEnv) -> Option { if !matches!(key, "fn" | "vau") { return None; } - let arr = match arg { - Value::Array(a) if a.len() == 2 => a, + let arr_ha = match arg.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return None, }; + let arr = &arr_ha.data; let formals: Vec = extract_pattern_var_names(&arr[0]); Some(FnDef { formals, @@ -3404,7 +3439,7 @@ fn try_extract_arg_fndef(arg_expr: &Value, env: &TypeEnv) -> Option { match arg_expr.as_call() { Some(("var", arg)) => { // {"var": "name"} - look up in env - if let Value::String(name) = arg { + if let NanView::String(name) = arg.view() { env.lookup(name).and_then(|(_, _, fndef)| fndef.clone()) } else { None @@ -3430,8 +3465,8 @@ fn collect_builtin_or_user_call( ) -> JestType { // Collect arg types by recursing (always, for side-effects). // Array args are spread (mirrors runtime); non-array is a single arg. - let arg_types: Vec = match args { - Value::Array(arr) => arr + let arg_types: Vec = match args.view() { + NanView::Array(ha) => ha.data .iter() .enumerate() .map(|(i, e)| collect(e, &args_ptr.push_index(i), env, ctx, obs, reg)) @@ -3444,9 +3479,9 @@ fn collect_builtin_or_user_call( check_arity(proc, &sig.params, arg_types.len(), call_ptr, ctx, obs); // Emit Require constraints for direct var-reference args. - match args { - Value::Array(arr) => { - for (i, arg_expr) in arr.iter().enumerate() { + match args.view() { + NanView::Array(ha) => { + for (i, arg_expr) in ha.data.iter().enumerate() { let req_ty = sig.params.type_at(i).cloned().unwrap_or(JestType::Top); emit_var_require(arg_expr, &req_ty, &args_ptr.push_index(i), call_ptr, env, ctx, obs); } @@ -3459,8 +3494,9 @@ fn collect_builtin_or_user_call( // Phase 4a: Field-specific constraints for object builtins with string literal keys. if matches!(proc, "has?" | "assoc" | "dissoc") - && let Value::Array(arr) = args - && let (Some(obj_expr), Some(Value::String(key))) = (arr.first(), arr.get(1)) + && let NanView::Array(ha) = args.view() + && let (Some(obj_expr), Some(key_val)) = (ha.data.first(), ha.data.get(1)) + && let NanView::String(key) = key_val.view() && let Some(name) = singleton_var_name(obj_expr) && let Some((_ty, bid, _)) = env.lookup(name) { @@ -3509,8 +3545,8 @@ fn collect_builtin_or_user_call( } // Extract arg expressions to enable transitive HOF precision (Phase 2). - let arg_exprs: Vec<&Value> = match args { - Value::Array(arr) => arr.iter().collect(), + let arg_exprs: Vec<&Value> = match args.view() { + NanView::Array(ha) => ha.data.iter().collect(), _ => vec![args], }; @@ -3693,8 +3729,8 @@ fn is_type_singleton(ty: &JestType) -> bool { /// Convert a singleton JestType to a runtime Value for evaluation. fn singleton_to_value(ty: &JestType, budget: &crate::runtime::Budget) -> Option { match ty { - JestType::Null => Some(crate::Value::Null), - JestType::Bool(Some(b)) => Some(crate::Value::Bool(*b)), + JestType::Null => Some(crate::Value::null()), + JestType::Bool(Some(b)) => Some(crate::Value::bool(*b)), JestType::Integer(crate::typing::IntegerInfo::Exact(i)) => Some(crate::Value::from_number(crate::Number::Integer(i.clone()))), JestType::Float(Some(f)) => Some(crate::Value::from_number(crate::Number::Float(f.clone()))), JestType::String(StringInfo::Exact(s)) => crate::Value::string(s, budget).ok(), @@ -3704,16 +3740,16 @@ fn singleton_to_value(ty: &JestType, budget: &crate::runtime::Budget) -> Option< /// Convert a runtime Value back to a singleton JestType. fn value_to_singleton(val: &crate::Value) -> Option { - match val { - crate::Value::Null => Some(JestType::Null), - crate::Value::Bool(b) => Some(JestType::Bool(Some(*b))), - crate::Value::Int(i) => Some(JestType::Integer(crate::typing::IntegerInfo::Exact(crate::runtime::IntegerValue::from_i64(*i)))), - crate::Value::Float(f) => Some(JestType::Float(Some(crate::runtime::FloatValue::from_f64(*f)))), - crate::Value::BigNum(n) => match &**n { + match val.view() { + NanView::Null => Some(JestType::Null), + NanView::Bool(b) => Some(JestType::Bool(Some(b))), + NanView::Int(i) => Some(JestType::Integer(crate::typing::IntegerInfo::Exact(crate::runtime::IntegerValue::from_i64(i)))), + NanView::Float(f) => Some(JestType::Float(Some(crate::runtime::FloatValue::from_f64(f)))), + NanView::BigNum(n) => match n { crate::Number::Integer(i) => Some(JestType::Integer(crate::typing::IntegerInfo::Exact(i.clone()))), crate::Number::Float(f) => Some(JestType::Float(Some(f.clone()))), }, - crate::Value::String(s) => Some(JestType::String(StringInfo::Exact((**s).to_string()))), + NanView::String(s) => Some(JestType::String(StringInfo::Exact(s.to_string()))), // Arrays and objects are not singletons for our purposes _ => None, } diff --git a/src/typing/check/learned_fuzzing.rs b/src/typing/check/learned_fuzzing.rs index fe32e06..4131850 100644 --- a/src/typing/check/learned_fuzzing.rs +++ b/src/typing/check/learned_fuzzing.rs @@ -110,9 +110,11 @@ impl Default for FuzzerConfig { /// /// Future: instrument type checker, runtime, compiler for richer traces. fn extract_ast_features(program: &Value) -> ExecutionTrace { + use crate::runtime::nanvalue::NanView; let mut trace = ExecutionTrace::new(); fn walk(val: &Value, depth: usize, trace: &mut ExecutionTrace) { + use crate::runtime::nanvalue::NanView; // Record depth bucket (0, 1-2, 3-5, 6+) let depth_bucket = match depth { 0 => 0, @@ -122,9 +124,9 @@ fn extract_ast_features(program: &Value) -> ExecutionTrace { }; trace.add(TraceFeature::FormDepth("expr".to_string(), depth_bucket)); - match val { - Value::Array(arr) => { - let len = arr.len(); + match val.view() { + NanView::Array(ha) => { + let len = ha.data.len(); let len_bucket = match len { 0 => 0, 1..=3 => 1, @@ -133,11 +135,11 @@ fn extract_ast_features(program: &Value) -> ExecutionTrace { }; trace.add(TraceFeature::ArrayLength(len_bucket)); - for elem in arr.iter() { + for elem in ha.data.iter() { walk(elem, depth + 1, trace); } } - Value::Object(_obj) => { + NanView::Object(_) => { // Check if it's a call form (singleton object) - method on Value if let Some((key, arg)) = val.as_call() { trace.add(TraceFeature::ValueConstructor(key.to_string())); @@ -149,8 +151,8 @@ fn extract_ast_features(program: &Value) -> ExecutionTrace { walk(arg, depth + 1, trace); } else { // Multi-key object - if let Value::Object(obj) = val { - for (_key, v) in obj.iter() { + if let NanView::Object(ho) = val.view() { + for (_key, v) in ho.iter() { walk(v, depth + 1, trace); } } @@ -362,7 +364,7 @@ mod tests { let budget = crate::runtime::Budget::new(usize::MAX / 2); // Simple literal - let lit = Value::Int(42); + let lit = Value::int(42); let trace = extract_ast_features(&lit); assert!(!trace.features.is_empty()); diff --git a/src/typing/check/narrow.rs b/src/typing/check/narrow.rs index 6108da5..e90be87 100644 --- a/src/typing/check/narrow.rs +++ b/src/typing/check/narrow.rs @@ -2,6 +2,7 @@ use super::super::*; // JestType, ArrayType, ObjectType use super::registry::BuiltinRegistry; use super::infra::TypeEnv; use crate::runtime::Value; +use crate::runtime::nanvalue::NanView; use std::collections::HashMap; /// Narrow pattern-bound variables using the scrutinee type and pattern structure. @@ -34,12 +35,12 @@ fn narrow_pattern_inner( return; } - match pattern { + match pattern.view() { // ── Literal patterns: no bindings ───────────────────────────── - Value::Null | Value::Bool(_) | Value::Int(_) | Value::Float(_) | Value::BigNum(_) | Value::String(_) => {} + NanView::Null | NanView::Bool(_) | NanView::Int(_) | NanView::Float(_) | NanView::BigNum(_) | NanView::String(_) => {} // ── Array patterns (fixed-length only; variadic uses cons) ──── - Value::Array(pat_arr) => { + NanView::Array(pat_arr) => { // Extract element types from the scrutinee let arr_ty = match scrutinee_ty { JestType::Array(at) => Some(at), @@ -49,7 +50,7 @@ fn narrow_pattern_inner( // Exact length: narrow each element positionally // (Variadic patterns use {"cons": [head, tail]} instead of {"rest": ...}) - for (i, pat_elem) in pat_arr.iter().enumerate() { + for (i, pat_elem) in pat_arr.data.iter().enumerate() { let elem_ty = arr_ty .and_then(|at| at.type_at(i).cloned()) .unwrap_or(JestType::Top); @@ -58,7 +59,7 @@ fn narrow_pattern_inner( } // ── Multi-key object patterns ───────────────────────────────── - Value::Object(pat_obj) => { + NanView::Object(pat_obj) => { // Extract field types from the scrutinee let obj_ty = match scrutinee_ty { JestType::Object(obj) => Some(obj), @@ -86,9 +87,9 @@ fn narrow_call_pattern( ) { match key { // ── var binding ─────────────────────────────────────────────── - "var" => match arg { - Value::Null => {} // wildcard — no binding - Value::String(name) => { + "var" => match arg.view() { + NanView::Null => {} // wildcard — no binding + NanView::String(name) => { let name = name.to_string(); if !name.is_empty() { bindings.insert(name, scrutinee_ty.clone()); @@ -99,13 +100,13 @@ fn narrow_call_pattern( // ── and-pattern: merge bindings, meet for overlapping names ─── "and-pattern" => { - if let Value::Array(arr) = arg - && arr.len() == 2 + if let NanView::Array(ha) = arg.view() + && ha.data.len() == 2 { let mut bindings1 = HashMap::new(); - narrow_pattern_inner(&arr[0], scrutinee_ty, &mut bindings1); + narrow_pattern_inner(&ha.data[0], scrutinee_ty, &mut bindings1); let mut bindings2 = HashMap::new(); - narrow_pattern_inner(&arr[1], scrutinee_ty, &mut bindings2); + narrow_pattern_inner(&ha.data[1], scrutinee_ty, &mut bindings2); // Merge: meet for overlapping names for (name, ty) in bindings1 { bindings.insert(name, ty); @@ -121,13 +122,13 @@ fn narrow_call_pattern( // ── or-pattern: merge bindings, join for overlapping names ──── "or-pattern" => { - if let Value::Array(arr) = arg - && arr.len() == 2 + if let NanView::Array(ha) = arg.view() + && ha.data.len() == 2 { let mut bindings1 = HashMap::new(); - narrow_pattern_inner(&arr[0], scrutinee_ty, &mut bindings1); + narrow_pattern_inner(&ha.data[0], scrutinee_ty, &mut bindings1); let mut bindings2 = HashMap::new(); - narrow_pattern_inner(&arr[1], scrutinee_ty, &mut bindings2); + narrow_pattern_inner(&ha.data[1], scrutinee_ty, &mut bindings2); // Merge: join for overlapping names, Top for names in only one branch let all_names: std::collections::BTreeSet<_> = bindings1 .keys() @@ -144,12 +145,12 @@ fn narrow_call_pattern( // ── guard-pattern: narrow inner, then meet with predicate type ─ "guard-pattern" => { - if let Value::Array(arr) = arg - && arr.len() == 2 + if let NanView::Array(ha) = arg.view() + && ha.data.len() == 2 { - narrow_pattern_inner(&arr[0], scrutinee_ty, bindings); + narrow_pattern_inner(&ha.data[0], scrutinee_ty, bindings); // If the predicate is a known type predicate, meet the bound type - if let Some(narrowed_ty) = type_predicate_narrows(&arr[1]) { + if let Some(narrowed_ty) = type_predicate_narrows(&ha.data[1]) { // Apply to all variables bound in the inner pattern // The guard narrows the scrutinee, so any var bound to the // scrutinee should be narrowed further @@ -177,20 +178,20 @@ fn narrow_inverse( match key { // ── Arithmetic inversion (+, -, *, /) ───────────────────────── "+" | "-" | "*" | "/" | "%" => { - if let Value::Array(arr) = arg - && arr.len() == 2 + if let NanView::Array(ha) = arg.view() + && ha.data.len() == 2 { // Arithmetic inversion narrows variables to the number family let num_ty = narrow_number_family(scrutinee_ty); - narrow_pattern_inner(&arr[0], &num_ty, bindings); - narrow_pattern_inner(&arr[1], &num_ty, bindings); + narrow_pattern_inner(&ha.data[0], &num_ty, bindings); + narrow_pattern_inner(&ha.data[1], &num_ty, bindings); } } // ── cons inversion ──────────────────────────────────────────── "cons" => { - if let Value::Array(arr) = arg - && arr.len() == 2 + if let NanView::Array(ha) = arg.view() + && ha.data.len() == 2 { let (head_ty, tail_ty) = match scrutinee_ty { JestType::Array(at) => { @@ -201,20 +202,20 @@ fn narrow_inverse( JestType::Top => (JestType::Top, JestType::Top), _ => (JestType::Top, JestType::Top), }; - narrow_pattern_inner(&arr[0], &head_ty, bindings); - narrow_pattern_inner(&arr[1], &tail_ty, bindings); + narrow_pattern_inner(&ha.data[0], &head_ty, bindings); + narrow_pattern_inner(&ha.data[1], &tail_ty, bindings); } } // ── str (string concat) inversion ───────────────────────────── "str" => { - if let Value::Array(arr) = arg { + if let NanView::Array(ha) = arg.view() { let seg_ty = match scrutinee_ty { JestType::String(_) => JestType::String(StringInfo::Any), JestType::Top => JestType::Top, _ => JestType::Top, }; - for seg in arr.iter() { + for seg in ha.data.iter() { narrow_pattern_inner(seg, &seg_ty, bindings); } } @@ -222,7 +223,7 @@ fn narrow_inverse( // ── concat (array concat) inversion ─────────────────────────── "concat" => { - if let Value::Array(arr) = arg { + if let NanView::Array(ha) = arg.view() { let seg_ty = match scrutinee_ty { JestType::Array(at) => { // Each segment gets an array of the element join @@ -231,7 +232,7 @@ fn narrow_inverse( JestType::Top => JestType::Top, _ => JestType::Top, }; - for seg in arr.iter() { + for seg in ha.data.iter() { narrow_pattern_inner(seg, &seg_ty, bindings); } } @@ -239,13 +240,13 @@ fn narrow_inverse( // ── assoc inversion ─────────────────────────────────────────── "assoc" => { - if let Value::Array(arr) = arg - && arr.len() == 3 + if let NanView::Array(ha) = arg.view() + && ha.data.len() == 3 { let (base_ty, val_ty) = match scrutinee_ty { JestType::Object(obj) => { // Key must be a ground string for inversion - if let Value::String(key_str) = &arr[1] { + if let NanView::String(key_str) = ha.data[1].view() { let key_name = key_str.to_string(); let val = obj.fields.get(&key_name).cloned().unwrap_or(JestType::Top); // Base is the object without this key @@ -264,9 +265,9 @@ fn narrow_inverse( JestType::Top => (JestType::Top, JestType::Top), _ => (JestType::Top, JestType::Top), }; - narrow_pattern_inner(&arr[0], &base_ty, bindings); - // arr[1] is the key — typically ground, skip narrowing - narrow_pattern_inner(&arr[2], &val_ty, bindings); + narrow_pattern_inner(&ha.data[0], &base_ty, bindings); + // ha.data[1] is the key — typically ground, skip narrowing + narrow_pattern_inner(&ha.data[2], &val_ty, bindings); } } @@ -305,16 +306,22 @@ fn narrow_number_family(scrutinee_ty: &JestType) -> JestType { /// Recognizes `type` values and stdlib type predicates by name. fn type_predicate_narrows(pred: &Value) -> Option { // Check for {"type": {"name": "...", "test": ...}} form - if let Some(("type", Value::Object(obj))) = pred.as_call() - && let Some(Value::String(name)) = obj.get("name") - { - return type_name_to_jest_type(name); + if let Some(("type", type_val)) = pred.as_call() { + if let NanView::Object(obj) = type_val.view() { + if let Some(name_val) = obj.get("name") { + if let NanView::String(name) = name_val.view() { + return type_name_to_jest_type(&name); + } + } + } } // Check for {"builtins": "name?"} form (builtin type predicates) - if let Some(("builtins", Value::String(name))) = pred.as_call() { - let name_str = name.to_string(); - if name_str.ends_with('?') { - return type_name_to_jest_type(&name_str[..name_str.len() - 1]); + if let Some(("builtins", name_val)) = pred.as_call() { + if let NanView::String(name) = name_val.view() { + let name_str = name.to_string(); + if name_str.ends_with('?') { + return type_name_to_jest_type(&name_str[..name_str.len() - 1]); + } } } None @@ -353,29 +360,29 @@ pub fn pattern_type(pattern: &Value) -> JestType { return pattern_type_call(key, arg); } - match pattern { + match pattern.view() { // Literal patterns: exact type - Value::Null => JestType::Null, - Value::Bool(b) => JestType::Bool(Some(*b)), - Value::Int(i) => JestType::Integer(crate::typing::IntegerInfo::Exact(crate::runtime::IntegerValue::from_i64(*i))), - Value::Float(f) => JestType::Float(Some(crate::runtime::FloatValue::from_f64(*f))), - Value::BigNum(n) => { + NanView::Null => JestType::Null, + NanView::Bool(b) => JestType::Bool(Some(b)), + NanView::Int(i) => JestType::Integer(crate::typing::IntegerInfo::Exact(crate::runtime::IntegerValue::from_i64(i))), + NanView::Float(f) => JestType::Float(Some(crate::runtime::FloatValue::from_f64(f))), + NanView::BigNum(n) => { use crate::runtime::Number; - match &**n { + match n { Number::Integer(i) => JestType::Integer(crate::typing::IntegerInfo::Exact(i.clone())), Number::Float(f) => JestType::Float(Some(f.clone())), } } - Value::String(s) => JestType::String(StringInfo::Exact(s.to_string())), + NanView::String(s) => JestType::String(StringInfo::Exact(s.to_string())), // Array patterns (fixed-length; variadic uses cons patterns) - Value::Array(pat_arr) => { - let head: Vec = pat_arr.iter().map(pattern_type).collect(); + NanView::Array(pat_arr) => { + let head: Vec = pat_arr.data.iter().map(pattern_type).collect(); JestType::Array(ArrayType::fixed(head)) } // Multi-key object patterns - Value::Object(pat_obj) => { + NanView::Object(pat_obj) => { let fields: std::collections::BTreeMap = pat_obj .iter() .map(|(k, v)| { let key: &str = k.as_ref(); (key.to_string(), pattern_type(v)) }) @@ -393,11 +400,11 @@ fn pattern_type_call(key: &str, arg: &Value) -> JestType { // and-pattern: meet of both coverages "and-pattern" => { - if let Value::Array(arr) = arg - && arr.len() == 2 + if let NanView::Array(ha) = arg.view() + && ha.data.len() == 2 { - let t1 = pattern_type(&arr[0]); - let t2 = pattern_type(&arr[1]); + let t1 = pattern_type(&ha.data[0]); + let t2 = pattern_type(&ha.data[1]); t1.meet(&t2) } else { JestType::Top @@ -406,11 +413,11 @@ fn pattern_type_call(key: &str, arg: &Value) -> JestType { // or-pattern: join of both coverages "or-pattern" => { - if let Value::Array(arr) = arg - && arr.len() == 2 + if let NanView::Array(ha) = arg.view() + && ha.data.len() == 2 { - let t1 = pattern_type(&arr[0]); - let t2 = pattern_type(&arr[1]); + let t1 = pattern_type(&ha.data[0]); + let t2 = pattern_type(&ha.data[1]); t1.join(&t2) } else { JestType::Top @@ -419,10 +426,10 @@ fn pattern_type_call(key: &str, arg: &Value) -> JestType { // guard-pattern: conservative — just the inner pattern's coverage "guard-pattern" => { - if let Value::Array(arr) = arg - && arr.len() == 2 + if let NanView::Array(ha) = arg.view() + && ha.data.len() == 2 { - pattern_type(&arr[0]) + pattern_type(&ha.data[0]) } else { JestType::Top } @@ -439,11 +446,11 @@ fn pattern_type_call(key: &str, arg: &Value) -> JestType { // cons: covers arrays "cons" => { - if let Value::Array(arr) = arg - && arr.len() == 2 + if let NanView::Array(ha) = arg.view() + && ha.data.len() == 2 { - let head_ty = pattern_type(&arr[0]); - let tail_covers = pattern_type(&arr[1]); + let head_ty = pattern_type(&ha.data[0]); + let tail_covers = pattern_type(&ha.data[1]); // tail should be an array; prepend head match tail_covers { JestType::Array(at) => JestType::Array(at.prepend(head_ty)), @@ -462,10 +469,10 @@ fn pattern_type_call(key: &str, arg: &Value) -> JestType { // assoc: covers objects "assoc" => { - if let Value::Array(arr) = arg - && arr.len() == 3 + if let NanView::Array(ha) = arg.view() + && ha.data.len() == 3 { - if let Value::String(key_str) = &arr[1] { + if let NanView::String(key_str) = ha.data[1].view() { let mut fields = std::collections::BTreeMap::new(); fields.insert(key_str.to_string(), JestType::Top); JestType::Object(ObjectType::open(fields)) @@ -496,16 +503,16 @@ pub(super) fn check_invertibility(pattern: &Value) -> Vec { } fn check_invertibility_inner(pattern: &Value, issues: &mut Vec) { - match pattern { + match pattern.view() { // Walk into arrays - Value::Array(arr) => { - for elem in arr.iter() { + NanView::Array(ha) => { + for elem in ha.data.iter() { check_invertibility_inner(elem, issues); } } // Walk into multi-key objects (not calls) - Value::Object(obj) if obj.len() != 1 => { - for (_, v) in obj.iter() { + NanView::Object(ho) if ho.len() != 1 => { + for (_, v) in ho.iter() { check_invertibility_inner(v, issues); } } @@ -521,10 +528,10 @@ fn check_invertibility_inner(pattern: &Value, issues: &mut Vec) { match key { // ── Arithmetic: +, -, *, /, % ──────────────────────────────── "+" | "-" | "*" | "/" | "%" => { - if let Value::Array(arr) = arg { - if arr.len() == 2 { - let left_has_var = contains_var(&arr[0]); - let right_has_var = contains_var(&arr[1]); + if let NanView::Array(ha) = arg.view() { + if ha.data.len() == 2 { + let left_has_var = contains_var(&ha.data[0]); + let right_has_var = contains_var(&ha.data[1]); if left_has_var && right_has_var { issues.push(format!( @@ -533,15 +540,12 @@ fn check_invertibility_inner(pattern: &Value, issues: &mut Vec) { )); } else if left_has_var || right_has_var { // One unknown — check for degenerate cases - let ground = if left_has_var { &arr[1] } else { &arr[0] }; + let ground = if left_has_var { &ha.data[1] } else { &ha.data[0] }; check_arith_ground(key, ground, left_has_var, issues); } // Both ground = forward eval + compare, always fine } - } - // Recurse into sub-patterns - if let Value::Array(arr) = arg { - for elem in arr.iter() { + for elem in ha.data.iter() { check_invertibility_inner(elem, issues); } } @@ -549,15 +553,15 @@ fn check_invertibility_inner(pattern: &Value, issues: &mut Vec) { // ── str: string concat ─────────────────────────────────────── "str" => { - if let Value::Array(arr) = arg { - let var_count = arr.iter().filter(|seg| contains_var(seg)).count(); + if let NanView::Array(ha) = arg.view() { + let var_count = ha.data.iter().filter(|seg| contains_var(seg)).count(); if var_count >= 2 { issues.push( "inverse 'str' pattern has multiple unknowns — cannot split unambiguously" .to_string(), ); } - for elem in arr.iter() { + for elem in ha.data.iter() { check_invertibility_inner(elem, issues); } } @@ -565,15 +569,15 @@ fn check_invertibility_inner(pattern: &Value, issues: &mut Vec) { // ── concat: array concat ───────────────────────────────────── "concat" => { - if let Value::Array(arr) = arg { - let var_count = arr.iter().filter(|seg| contains_var(seg)).count(); + if let NanView::Array(ha) = arg.view() { + let var_count = ha.data.iter().filter(|seg| contains_var(seg)).count(); if var_count >= 2 { issues.push( "inverse 'concat' pattern has multiple unknowns — cannot split unambiguously" .to_string(), ); } - for elem in arr.iter() { + for elem in ha.data.iter() { check_invertibility_inner(elem, issues); } } @@ -581,14 +585,14 @@ fn check_invertibility_inner(pattern: &Value, issues: &mut Vec) { // ── assoc: object key extraction ───────────────────────────── "assoc" => { - if let Value::Array(arr) = arg { - if arr.len() == 3 && contains_var(&arr[1]) { + if let NanView::Array(ha) = arg.view() { + if ha.data.len() == 3 && contains_var(&ha.data[1]) { issues.push( "inverse 'assoc' pattern has non-ground key — cannot determine which field to extract" .to_string(), ); } - for elem in arr.iter() { + for elem in ha.data.iter() { check_invertibility_inner(elem, issues); } } @@ -603,10 +607,10 @@ fn check_invertibility_inner(pattern: &Value, issues: &mut Vec) { /// Check arithmetic degenerate cases when one operand is ground. fn check_arith_ground(op: &str, ground: &Value, var_is_left: bool, issues: &mut Vec) { - let is_zero = match ground { - Value::Int(0) => true, - Value::Float(f) if *f == 0.0 => true, - Value::BigNum(n) => match &**n { + let is_zero = match ground.view() { + NanView::Int(0) => true, + NanView::Float(f) if f == 0.0 => true, + NanView::BigNum(n) => match n { crate::runtime::Number::Integer(i) => i.is_zero(), crate::runtime::Number::Float(f) => f.is_zero(), }, diff --git a/src/typing/check/proptest_soundness.rs b/src/typing/check/proptest_soundness.rs index bfa5539..a8d459c 100644 --- a/src/typing/check/proptest_soundness.rs +++ b/src/typing/check/proptest_soundness.rs @@ -98,18 +98,19 @@ impl TypeFamily { } fn from_value(val: &Value) -> Self { - match val { - Value::Null => TypeFamily::Null, - Value::Bool(_) => TypeFamily::Bool, - Value::Int(_) => TypeFamily::Integer, - Value::Float(_) => TypeFamily::Float, - Value::BigNum(n) => match &**n { + use crate::runtime::nanvalue::NanView; + match val.view() { + NanView::Null => TypeFamily::Null, + NanView::Bool(_) => TypeFamily::Bool, + NanView::Int(_) => TypeFamily::Integer, + NanView::Float(_) => TypeFamily::Float, + NanView::BigNum(n) => match n { crate::runtime::Number::Integer(_) => TypeFamily::Integer, crate::runtime::Number::Float(_) => TypeFamily::Float, }, - Value::String(_) => TypeFamily::String, - Value::Array(_) => TypeFamily::Array, - Value::Object(_) => TypeFamily::Object, + NanView::String(_) => TypeFamily::String, + NanView::Array(_) => TypeFamily::Array, + NanView::Object(_) => TypeFamily::Object, } } } @@ -601,17 +602,18 @@ mod behavioral_novelty_poc { let mut trace = ExecutionTrace::new(); fn walk(val: &Value, trace: &mut ExecutionTrace, depth: usize) { - match val { - Value::Null => trace.add(TraceFeature::ValueConstructor("Null".into())), - Value::Bool(_) => trace.add(TraceFeature::ValueConstructor("Bool".into())), - Value::Int(_) => { + use crate::runtime::nanvalue::NanView; + match val.view() { + NanView::Null => trace.add(TraceFeature::ValueConstructor("Null".into())), + NanView::Bool(_) => trace.add(TraceFeature::ValueConstructor("Bool".into())), + NanView::Int(_) => { trace.add(TraceFeature::ValueConstructor("Integer".into())); } - Value::Float(_) => { + NanView::Float(_) => { trace.add(TraceFeature::ValueConstructor("Float".into())); } - Value::BigNum(n) => { - match &**n { + NanView::BigNum(n) => { + match n { crate::runtime::Number::Integer(_) => { trace.add(TraceFeature::ValueConstructor("Integer".into())); } @@ -620,26 +622,28 @@ mod behavioral_novelty_poc { } } } - Value::String(_) => trace.add(TraceFeature::ValueConstructor("String".into())), - Value::Array(arr) => { + NanView::String(_) => trace.add(TraceFeature::ValueConstructor("String".into())), + NanView::Array(ha) => { trace.add(TraceFeature::ValueConstructor("Array".into())); - let len = arr.len(); + let len = ha.data.len(); let bucket = if len <= 3 { len } else if len <= 10 { 4 } else { 5 }; trace.add(TraceFeature::ArrayLength(bucket)); - for elem in arr.iter() { + for elem in ha.data.iter() { walk(elem, trace, depth + 1); } } - Value::Object(obj) => { + NanView::Object(_) => { if let Some((key, arg)) = val.as_call() { // Call form - record the operation trace.add(TraceFeature::BuiltinCall(key.to_string())); trace.add(TraceFeature::FormDepth(key.to_string(), depth / 3)); // Bucketed depth - walk(&arg, trace, depth + 1); + walk(arg, trace, depth + 1); } else { trace.add(TraceFeature::ValueConstructor("Object".into())); - for val in obj.values() { - walk(val, trace, depth + 1); + if let NanView::Object(ho) = val.view() { + for v in ho.values() { + walk(v, trace, depth + 1); + } } } } diff --git a/src/typing/check/registry.rs b/src/typing/check/registry.rs index ea67bd3..e418bc7 100644 --- a/src/typing/check/registry.rs +++ b/src/typing/check/registry.rs @@ -470,29 +470,30 @@ fn structural_dissoc_in(args: &[JestType]) -> JestType { /// Convert a runtime Value to its precise JestType (full recursive conversion). pub(super) fn value_to_type(val: &crate::Value) -> JestType { - match val { - crate::Value::Null => JestType::Null, - crate::Value::Bool(b) => JestType::Bool(Some(*b)), - crate::Value::Int(i) => JestType::Integer(crate::typing::IntegerInfo::Exact(crate::runtime::IntegerValue::from_i64(*i))), - crate::Value::Float(f) => JestType::Float(Some(crate::runtime::FloatValue::from_f64(*f))), - crate::Value::BigNum(n) => match &**n { + use crate::runtime::nanvalue::NanView; + match val.view() { + NanView::Null => JestType::Null, + NanView::Bool(b) => JestType::Bool(Some(b)), + NanView::Int(i) => JestType::Integer(crate::typing::IntegerInfo::Exact(crate::runtime::IntegerValue::from_i64(i))), + NanView::Float(f) => JestType::Float(Some(crate::runtime::FloatValue::from_f64(f))), + NanView::BigNum(n) => match n { crate::runtime::Number::Integer(i) => JestType::Integer(crate::typing::IntegerInfo::Exact(i.clone())), crate::runtime::Number::Float(f) => JestType::Float(Some(f.clone())), }, - crate::Value::String(s) => JestType::String(StringInfo::Exact((**s).to_string())), - crate::Value::Array(arr) => { - JestType::Array(ArrayType::fixed(arr.iter().map(value_to_type).collect())) + NanView::String(s) => JestType::String(StringInfo::Exact((&**s).to_string())), + NanView::Array(ha) => { + JestType::Array(ArrayType::fixed(ha.data.iter().map(value_to_type).collect())) } - _ if val.as_call().is_some() => { + NanView::Object(_) if val.as_call().is_some() => { let (key, arg) = val.as_call().unwrap(); let mut fields = BTreeMap::new(); fields.insert(key.to_string(), value_to_type(arg)); JestType::Object(ObjectType::closed(fields)) } - crate::Value::Object(obj) => { - let fields: BTreeMap = obj + NanView::Object(ho) => { + let fields: BTreeMap = ho .iter() - .map(|(k, v)| ((**k).to_string(), value_to_type(v))) + .map(|(k, v)| ((&**k).to_string(), value_to_type(v))) .collect(); JestType::Object(ObjectType::closed(fields)) } @@ -982,27 +983,28 @@ fn lookup_structural(name: &str) -> Option { /// Parse a single registry entry from a parsed Value. fn parse_entry(val: &crate::Value) -> Option<(String, BuiltinSignature)> { - let obj = match val { - crate::Value::Object(o) => o, + use crate::runtime::nanvalue::NanView; + let obj = match val.view() { + NanView::Object(o) => o, _ => return None, }; - let name = match obj.get("name")? { - crate::Value::String(s) => (**s).to_string(), + let name = match obj.get("name")?.view() { + NanView::String(s) => (&**s).to_string(), _ => return None, }; let params_val = obj.get("params")?; let params = ArrayType::from_value(params_val)?; - let ret_obj = match obj.get("ret")? { - crate::Value::Object(o) => o, + let ret_obj = match obj.get("ret")?.view() { + NanView::Object(o) => o, _ => return None, }; - let rule_str = match ret_obj.get("rule")? { - crate::Value::String(s) => &***s, + let rule_str = match ret_obj.get("rule")?.view() { + NanView::String(s) => s, _ => return None, }; - let ret = match rule_str { + let ret = match &**rule_str { "constant" => { let ty = JestType::from_value(ret_obj.get("type")?)?; RetRule::Constant(ty) @@ -1010,19 +1012,19 @@ fn parse_entry(val: &crate::Value) -> Option<(String, BuiltinSignature)> { "join-args" => RetRule::JoinArgs, "promote-number" => RetRule::PromoteNumber, "structural" => { - let fn_name = match ret_obj.get("fn")? { - crate::Value::String(s) => &***s, + let fn_name = match ret_obj.get("fn")?.view() { + NanView::String(s) => s, _ => return None, }; - let f = lookup_structural(fn_name)?; + let f = lookup_structural(&fn_name)?; RetRule::Structural(f) } _ => return None, }; - let narrowing_type = match obj.get("narrowing")? { - crate::Value::Null => None, - v => Some(JestType::from_value(v)?), + let narrowing_type = match obj.get("narrowing")?.view() { + NanView::Null => None, + _ => Some(JestType::from_value(obj.get("narrowing")?)?), }; Some((name, BuiltinSignature { params, ret, narrowing_type })) diff --git a/src/typing/check/string_shape.rs b/src/typing/check/string_shape.rs index 011ee04..a6032ee 100644 --- a/src/typing/check/string_shape.rs +++ b/src/typing/check/string_shape.rs @@ -167,21 +167,22 @@ impl Shape { /// Deserialize a `Shape` from a runtime Value. pub fn from_value(v: &crate::Value) -> Option { - let obj = match v { - crate::Value::Object(o) => o, + use crate::runtime::nanvalue::NanView; + let obj = match v.view() { + NanView::Object(o) => o, _ => return None, }; - let shape_str = match obj.get("shape")? { - crate::Value::String(s) => &***s, + let shape_str = match obj.get("shape")?.view() { + NanView::String(s) => s, _ => return None, }; - match shape_str { + match &**shape_str { "void" => Some(Shape::Void), "empty" => Some(Shape::Empty), "any" => Some(Shape::Any), "lit" => { - let s = match obj.get("value")? { - crate::Value::String(s) => (**s).to_string(), + let s = match obj.get("value")?.view() { + NanView::String(s) => (&**s).to_string(), _ => return None, }; Some(Shape::Lit(s)) @@ -201,11 +202,11 @@ impl Shape { Some(Shape::Star(Box::new(a))) } "class" => { - let name = match obj.get("name")? { - crate::Value::String(s) => &***s, + let name = match obj.get("name")?.view() { + NanView::String(s) => s, _ => return None, }; - let cc = match name { + let cc = match &**name { "any-char" => CharClass::AnyChar, "digit" => CharClass::Digit, "hex-digit" => CharClass::HexDigit, @@ -215,8 +216,8 @@ impl Shape { Some(Shape::Class(cc)) } "ref" => { - let name = match obj.get("name")? { - crate::Value::String(s) => (**s).to_string(), + let name = match obj.get("name")?.view() { + NanView::String(s) => (&**s).to_string(), _ => return None, }; Some(Shape::Ref(name)) diff --git a/src/typing/check/termination.rs b/src/typing/check/termination.rs index f212270..9f75ebc 100644 --- a/src/typing/check/termination.rs +++ b/src/typing/check/termination.rs @@ -2,6 +2,7 @@ use super::infra::{Termination, TerminationResult}; use crate::runtime::Value; use crate::runtime::Number; use crate::runtime::Budget; +use crate::runtime::nanvalue::NanView; use crate::runtime::pattern_bound_names_ordered; use std::collections::{HashMap, HashSet}; @@ -13,10 +14,10 @@ type CollectedCall<'a> = (&'a str, Vec<&'a Value>, LocalBindings<'a>); /// Check if `body` contains an `{"if": ...}` call (not inside fn/macro). fn has_if_guard(val: &Value) -> bool { - match val { - Value::Null | Value::Bool(_) | Value::Int(_) | Value::Float(_) | Value::BigNum(_) | Value::String(_) => false, - Value::Array(arr) => arr.iter().any(has_if_guard), - _ if val.as_call().is_some() => { + match val.view() { + NanView::Null | NanView::Bool(_) | NanView::Int(_) | NanView::Float(_) | NanView::BigNum(_) | NanView::String(_) => false, + NanView::Array(ha) => ha.data.iter().any(has_if_guard), + NanView::Object(_) if val.as_call().is_some() => { let (key, arg) = val.as_call().unwrap(); match key { "if" | "match" => true, @@ -24,7 +25,7 @@ fn has_if_guard(val: &Value) -> bool { _ => has_if_guard(arg), } } - Value::Object(obj) => obj.iter().any(|(_, v)| has_if_guard(v)), + NanView::Object(ho) => ho.iter().any(|(_, v)| has_if_guard(v)), } } @@ -44,14 +45,14 @@ fn walk_for_calls<'a>( self_alias: &'a str, out: &mut Vec>, ) { - match val { - Value::Null | Value::Bool(_) | Value::Int(_) | Value::Float(_) | Value::BigNum(_) | Value::String(_) => {} - Value::Array(arr) => { - for elem in arr.iter() { + match val.view() { + NanView::Null | NanView::Bool(_) | NanView::Int(_) | NanView::Float(_) | NanView::BigNum(_) | NanView::String(_) => {} + NanView::Array(ha) => { + for elem in ha.data.iter() { walk_for_calls(elem, locals, target_names, self_alias, out); } } - _ if val.as_call().is_some() => { + NanView::Object(_) if val.as_call().is_some() => { let (key, arg) = val.as_call().unwrap(); if key == "fn" || key == "macro" || key == "vau" { @@ -70,14 +71,14 @@ fn walk_for_calls<'a>( match key { "let" | "let*" => { - if let Value::Array(arr) = arg - && arr.len() == 2 + if let NanView::Array(ha) = arg.view() + && ha.data.len() == 2 { let sequential = key == "let*"; let new_locals = walk_let_bindings( - &arr[0], locals, sequential, target_names, self_alias, out, + &ha.data[0], locals, sequential, target_names, self_alias, out, ); - walk_for_calls(&arr[1], &new_locals, target_names, self_alias, out); + walk_for_calls(&ha.data[1], &new_locals, target_names, self_alias, out); } else { walk_for_calls(arg, locals, target_names, self_alias, out); } @@ -90,8 +91,8 @@ fn walk_for_calls<'a>( } } } - Value::Object(obj) => { - for (_, v) in obj.iter() { + NanView::Object(ho) => { + for (_, v) in ho.iter() { walk_for_calls(v, locals, target_names, self_alias, out); } } @@ -100,9 +101,9 @@ fn walk_for_calls<'a>( /// Extract positional args from a call's argument value. fn extract_call_args(arg: &Value) -> Vec<&Value> { - match arg { - Value::Array(arr) => arr.iter().collect(), - other => vec![other], + match arg.view() { + NanView::Array(ha) => ha.data.iter().collect(), + _ => vec![arg], } } @@ -119,8 +120,8 @@ fn walk_let_bindings<'a>( ) -> LocalBindings<'a> { let mut new_locals = base_locals.clone(); - let pairs: Vec<(&str, &Value)> = if let Value::Object(obj) = bindings { - obj.iter().map(|(k, v)| (&**k, v)).collect() + let pairs: Vec<(&str, &Value)> = if let NanView::Object(ho) = bindings.view() { + ho.iter().map(|(k, v)| (&**k, v)).collect() } else if let Some((k, v)) = bindings.as_call() { vec![(k, v)] } else { @@ -144,8 +145,8 @@ fn walk_do_stmts<'a>( self_alias: &'a str, out: &mut Vec>, ) { - let stmts = match stmts_val { - Value::Array(arr) => arr, + let stmts = match stmts_val.view() { + NanView::Array(ha) => ha, _ => { walk_for_calls(stmts_val, base_locals, target_names, self_alias, out); return; @@ -154,15 +155,15 @@ fn walk_do_stmts<'a>( let mut current_locals = base_locals.clone(); - for stmt in stmts.iter() { + for stmt in stmts.data.iter() { // Check for {"def": ["name", expr]} if let Some(("def", def_arg)) = stmt.as_call() - && let Value::Array(arr) = def_arg - && arr.len() == 2 - && let Value::String(name) = &arr[0] + && let NanView::Array(ha) = def_arg.view() + && ha.data.len() == 2 + && let NanView::String(name) = ha.data[0].view() { - walk_for_calls(&arr[1], ¤t_locals, target_names, self_alias, out); - current_locals.insert(name.to_string(), &arr[1]); + walk_for_calls(&ha.data[1], ¤t_locals, target_names, self_alias, out); + current_locals.insert(name.to_string(), &ha.data[1]); continue; } walk_for_calls(stmt, ¤t_locals, target_names, self_alias, out); @@ -181,7 +182,7 @@ fn resolve_arg<'a>( let mut current = arg; for _ in 0..5 { if let Some(("var", var_arg)) = current.as_call() - && let Value::String(name) = var_arg + && let NanView::String(name) = var_arg.view() { let name_str: &str = name; if !formals.iter().any(|f| f == name_str) @@ -407,48 +408,48 @@ fn is_decreasing_on(arg: &Value, formal: &str) -> bool { _ => return false, }; - let args = match call_arg { - Value::Array(arr) => arr, + let args = match call_arg.view() { + NanView::Array(ha) => ha, // Singleton arg form: `rest` and `first` apply (take one arg). - other => { - return matches!(key, "rest" | "first") && is_var_ref(other, formal); + _ => { + return matches!(key, "rest" | "first") && is_var_ref(call_arg, formal); } }; match key { // Same-measure decrease patterns: "rest" => { - args.len() == 1 && is_var_ref(&args[0], formal) + args.data.len() == 1 && is_var_ref(&args.data[0], formal) } "slice" => { - args.len() == 2 - && is_var_ref(&args[0], formal) - && is_positive_int_const(&args[1]) + args.data.len() == 2 + && is_var_ref(&args.data[0], formal) + && is_positive_int_const(&args.data[1]) } "dissoc" => { - args.len() == 2 && is_var_ref(&args[0], formal) + args.data.len() == 2 && is_var_ref(&args.data[0], formal) } "-" => { - args.len() == 2 - && is_var_ref(&args[0], formal) - && is_positive_int_const(&args[1]) + args.data.len() == 2 + && is_var_ref(&args.data[0], formal) + && is_positive_int_const(&args.data[1]) } "str-slice" => { // str-slice takes [str, start] or [str, start, end]. // Decreasing when start > 0 (drops characters from the front). - args.len() >= 2 - && is_var_ref(&args[0], formal) - && is_positive_int_const(&args[1]) + args.data.len() >= 2 + && is_var_ref(&args.data[0], formal) + && is_positive_int_const(&args.data[1]) } // Subterm descent patterns: "first" => { - args.len() == 1 && is_var_ref(&args[0], formal) + args.data.len() == 1 && is_var_ref(&args.data[0], formal) } "nth" => { - args.len() == 2 && is_var_ref(&args[0], formal) + args.data.len() == 2 && is_var_ref(&args.data[0], formal) } "get-in" => { - args.len() == 2 && is_var_ref(&args[0], formal) + args.data.len() == 2 && is_var_ref(&args.data[0], formal) } _ => false, } @@ -458,10 +459,10 @@ fn is_decreasing_on(arg: &Value, formal: &str) -> bool { /// Matches `{"var": ["formal", "field", ...]}` (2+ element array where first is the formal name). fn is_field_access_on(arg: &Value, formal: &str) -> bool { if let Some(("var", var_arg)) = arg.as_call() - && let Value::Array(arr) = var_arg - && arr.len() >= 2 + && let NanView::Array(ha) = var_arg.view() + && ha.data.len() >= 2 { - return matches!(&arr[0], Value::String(s) if &**s == formal); + return matches!(ha.data[0].view(), NanView::String(s) if &**s == formal); } false } @@ -505,7 +506,7 @@ pub fn analyze_mutual_termination( /// Check if `val` is `{"var": "name"}`. fn is_var_ref(val: &Value, name: &str) -> bool { if let Some(("var", arg)) = val.as_call() { - matches!(arg, Value::String(s) if &**s == name) + matches!(arg.view(), NanView::String(s) if &**s == name) } else { false } @@ -513,9 +514,9 @@ fn is_var_ref(val: &Value, name: &str) -> bool { /// Check if `val` is a positive integer literal. fn is_positive_int_const(val: &Value) -> bool { - match val { - Value::Int(i) => *i > 0, - Value::BigNum(n) => matches!(&**n, Number::Integer(i) if !i.is_negative() && !i.is_zero()), + match val.view() { + NanView::Int(i) => i > 0, + NanView::BigNum(n) => matches!(n, Number::Integer(i) if !i.is_negative() && !i.is_zero()), _ => false, } } @@ -546,13 +547,13 @@ fn try_inline_local_call( if !matches!(fn_key, "fn" | "vau") { return None; } - let fn_parts = match fn_arg { - Value::Array(arr) if arr.len() == 2 => arr, + let fn_parts = match fn_arg.view() { + NanView::Array(ha) if ha.data.len() == 2 => ha, _ => return None, }; - let param_pattern = &fn_parts[0]; - let body = &fn_parts[1]; + let param_pattern = &fn_parts.data[0]; + let body = &fn_parts.data[1]; // Extract formal names from the fn's parameter pattern. let fn_formals = pattern_bound_names_ordered(param_pattern); @@ -581,11 +582,11 @@ fn try_inline_local_call( /// Stops at `fn`/`macro` boundaries (don't substitute into nested closures). /// Returns `None` if the budget is exhausted. fn substitute(body: &Value, bindings: &HashMap<&str, &Value>, budget: &Budget) -> Option { - match body { - Value::Null | Value::Bool(_) | Value::Int(_) | Value::Float(_) | Value::BigNum(_) => Some(body.clone()), - Value::String(_) => Some(body.clone()), + match body.view() { + NanView::Null | NanView::Bool(_) | NanView::Int(_) | NanView::Float(_) | NanView::BigNum(_) => Some(body.clone()), + NanView::String(_) => Some(body.clone()), - _ if body.as_call().is_some() => { + NanView::Object(_) if body.as_call().is_some() => { let (key, arg) = body.as_call().unwrap(); // Stop at fn/macro/vau boundaries. @@ -595,8 +596,8 @@ fn substitute(body: &Value, bindings: &HashMap<&str, &Value>, budget: &Budget) - // Check for {"var": "name"} → substitute. if key == "var" { - if let Value::String(name) = arg { - let name_str: &str = name; + if let NanView::String(name) = arg.view() { + let name_str: &str = &name; if let Some(replacement) = bindings.get(name_str) { return Some((*replacement).clone()); } @@ -609,15 +610,15 @@ fn substitute(body: &Value, bindings: &HashMap<&str, &Value>, budget: &Budget) - Value::call(key, new_arg, budget).ok() } - Value::Array(arr) => { - let items: Option> = arr.iter() + NanView::Array(ha) => { + let items: Option> = ha.data.iter() .map(|v| substitute(v, bindings, budget)) .collect(); Value::array(items?, budget).ok() } - Value::Object(obj) => { - let pairs: Option> = obj.iter() + NanView::Object(ho) => { + let pairs: Option> = ho.iter() .map(|(k, v)| substitute(v, bindings, budget).map(|sv| (k.clone(), sv))) .collect(); Value::object(pairs?, budget).ok() @@ -678,10 +679,10 @@ fn find_unbounded_calls_to(body: &Value, target_names: &HashSet<&str>) -> bool { } fn walk_for_taint(val: &Value, targets: &HashSet<&str>) -> bool { - match val { - Value::Null | Value::Bool(_) | Value::Int(_) | Value::Float(_) | Value::BigNum(_) | Value::String(_) => false, - Value::Array(arr) => arr.iter().any(|v| walk_for_taint(v, targets)), - _ if val.as_call().is_some() => { + match val.view() { + NanView::Null | NanView::Bool(_) | NanView::Int(_) | NanView::Float(_) | NanView::BigNum(_) | NanView::String(_) => false, + NanView::Array(ha) => ha.data.iter().any(|v| walk_for_taint(v, targets)), + NanView::Object(_) if val.as_call().is_some() => { let (key, arg) = val.as_call().unwrap(); // Stop at fn/macro/vau boundaries. @@ -691,12 +692,12 @@ fn walk_for_taint(val: &Value, targets: &HashSet<&str>) -> bool { // Budgeted try: {"try": [expr, positive_int, ...]} — skip expr. if key == "try" { - if let Value::Array(arr) = arg - && arr.len() >= 2 - && is_positive_int_const(&arr[1]) + if let NanView::Array(ha) = arg.view() + && ha.data.len() >= 2 + && is_positive_int_const(&ha.data[1]) { - // Budget-bounded — skip arr[0], check remaining args. - return arr[2..].iter().any(|v| walk_for_taint(v, targets)); + // Budget-bounded — skip ha.data[0], check remaining args. + return ha.data[2..].iter().any(|v| walk_for_taint(v, targets)); } // No budget bound — walk normally. return walk_for_taint(arg, targets); @@ -710,7 +711,7 @@ fn walk_for_taint(val: &Value, targets: &HashSet<&str>) -> bool { // Recurse into args. walk_for_taint(arg, targets) } - Value::Object(obj) => obj.iter().any(|(_, v)| walk_for_taint(v, targets)), + NanView::Object(ho) => ho.iter().any(|(_, v)| walk_for_taint(v, targets)), } } @@ -745,7 +746,7 @@ mod tests { let body = call("if", vec![ var("xs"), call("f", vec![call("rest", vec![var("xs")])]), - Value::Null, + Value::null(), ]); assert_eq!(analyze_single(&["xs".into()], &body), Termination::Terminating); } @@ -754,9 +755,9 @@ mod tests { fn terminating_minus_recursion() { // factorial-style: if n <= 0 then 1 else f(n - 1) let body = call("if", vec![ - call("<=", vec![var("n"), Value::Int(0)]), - Value::Int(1), - call("f", vec![call("-", vec![var("n"), Value::Int(1)])]), + call("<=", vec![var("n"), Value::int(0)]), + Value::int(1), + call("f", vec![call("-", vec![var("n"), Value::int(1)])]), ]); assert_eq!(analyze_single(&["n".into()], &body), Termination::Terminating); } @@ -765,11 +766,11 @@ mod tests { fn terminating_multiple_calls_same_formal() { // fib-style: two self-calls both decreasing n let body = call("if", vec![ - call("<=", vec![var("n"), Value::Int(1)]), + call("<=", vec![var("n"), Value::int(1)]), var("n"), call("+", vec![ - call("f", vec![call("-", vec![var("n"), Value::Int(1)])]), - call("f", vec![call("-", vec![var("n"), Value::Int(2)])]), + call("f", vec![call("-", vec![var("n"), Value::int(1)])]), + call("f", vec![call("-", vec![var("n"), Value::int(2)])]), ]), ]); assert_eq!(analyze_single(&["n".into()], &body), Termination::Terminating); @@ -786,8 +787,8 @@ mod tests { fn unknown_no_decrease() { // Self-call passes formal unchanged (inside if) let body = call("if", vec![ - call("==", vec![var("n"), Value::Int(0)]), - Value::Int(0), + call("==", vec![var("n"), Value::int(0)]), + Value::int(0), call("f", vec![var("n")]), ]); assert_eq!(analyze_single(&["n".into()], &body), Termination::Unknown); @@ -812,7 +813,7 @@ mod tests { fn terminating_dissoc_recursion() { let body = call("if", vec![ call("==", vec![call("keys", vec![var("obj")]), Value::array(vec![], &budget()).unwrap()]), - Value::Null, + Value::null(), call("f", vec![call("dissoc", vec![var("obj"), Value::string("k", &budget()).unwrap()])]), ]); assert_eq!(analyze_single(&["obj".into()], &body), Termination::Terminating); @@ -830,7 +831,7 @@ mod tests { let body = call("if", vec![ var("n"), inner_fn, - Value::Null, + Value::null(), ]); // No self-calls at our level → Unknown (defensive) assert_eq!(analyze_single(&["n".into()], &body), Termination::Unknown); @@ -846,19 +847,19 @@ mod tests { // 3. self(m-1, self(m, n-1)) — m decreases, n changes // Lexicographic on (m, n) → Terminating let body = call("if", vec![ - call("==", vec![var("m"), Value::Int(0)]), - call("+", vec![var("n"), Value::Int(1)]), + call("==", vec![var("m"), Value::int(0)]), + call("+", vec![var("n"), Value::int(1)]), call("if", vec![ - call("==", vec![var("n"), Value::Int(0)]), + call("==", vec![var("n"), Value::int(0)]), call("f", vec![ - call("-", vec![var("m"), Value::Int(1)]), - Value::Int(1), + call("-", vec![var("m"), Value::int(1)]), + Value::int(1), ]), call("f", vec![ - call("-", vec![var("m"), Value::Int(1)]), + call("-", vec![var("m"), Value::int(1)]), call("f", vec![ var("m"), - call("-", vec![var("n"), Value::Int(1)]), + call("-", vec![var("n"), Value::int(1)]), ]), ]), ]), @@ -878,10 +879,10 @@ mod tests { var("a"), call("f", vec![ call("rest", vec![var("a")]), - call("+", vec![var("b"), Value::Int(1)]), + call("+", vec![var("b"), Value::int(1)]), ]), call("f", vec![ - call("+", vec![var("a"), Value::Int(1)]), + call("+", vec![var("a"), Value::int(1)]), call("rest", vec![var("b")]), ]), ]); @@ -926,7 +927,7 @@ mod tests { singleton("tail", call("rest", vec![var("xs")])), call("f", vec![var("tail")]), ]), - Value::Null, + Value::null(), ]); assert_eq!(analyze_single(&["xs".into()], &body), Termination::Terminating); } @@ -940,7 +941,7 @@ mod tests { singleton("tail", call("rest", vec![var("xs")])), call("f", vec![var("tail")]), ]), - Value::Null, + Value::null(), ]); assert_eq!(analyze_single(&["xs".into()], &body), Termination::Terminating); } @@ -957,7 +958,7 @@ mod tests { ]), call("f", vec![var("tail")]), ]), - Value::Null, + Value::null(), ]); assert_eq!(analyze_single(&["xs".into()], &body), Termination::Terminating); } @@ -971,7 +972,7 @@ mod tests { singleton("x", var("xs")), call("f", vec![var("x")]), ]), - Value::Null, + Value::null(), ]); assert_eq!(analyze_single(&["xs".into()], &body), Termination::Unknown); } @@ -981,13 +982,13 @@ mod tests { // self(m-1, self(m, n-1)) — the inner self(m, n-1) must also be checked // Both outer and inner satisfy lex decrease on (m, n) → Terminating let body = call("if", vec![ - call("==", vec![var("m"), Value::Int(0)]), + call("==", vec![var("m"), Value::int(0)]), var("n"), call("f", vec![ - call("-", vec![var("m"), Value::Int(1)]), + call("-", vec![var("m"), Value::int(1)]), call("f", vec![ var("m"), - call("-", vec![var("n"), Value::Int(1)]), + call("-", vec![var("n"), Value::int(1)]), ]), ]), ]); @@ -1005,7 +1006,7 @@ mod tests { let body = call("if", vec![ var("xs"), call("f", vec![call("first", vec![var("xs")])]), - Value::Null, + Value::null(), ]); assert_eq!(analyze_single(&["xs".into()], &body), Termination::Terminating); } @@ -1015,8 +1016,8 @@ mod tests { // Recurs on nth(xs, 0) → Terminating (subterm descent) let body = call("if", vec![ var("xs"), - call("f", vec![call("nth", vec![var("xs"), Value::Int(0)])]), - Value::Null, + call("f", vec![call("nth", vec![var("xs"), Value::int(0)])]), + Value::null(), ]); assert_eq!(analyze_single(&["xs".into()], &body), Termination::Terminating); } @@ -1030,7 +1031,7 @@ mod tests { var("x"), Value::array(vec![Value::string("child", &budget()).unwrap()], &budget()).unwrap(), ])]), - Value::Null, + Value::null(), ]); assert_eq!(analyze_single(&["x".into()], &body), Termination::Terminating); } @@ -1049,7 +1050,7 @@ mod tests { let body = call("if", vec![ var("x"), call("f", vec![field_access]), - Value::Null, + Value::null(), ]); assert_eq!(analyze_single(&["x".into()], &body), Termination::Terminating); } @@ -1061,14 +1062,14 @@ mod tests { // even?(n) = if n==0 then true else odd?(n-1) // odd?(n) = if n==0 then false else even?(n-1) let even_body = call("if", vec![ - call("==", vec![var("n"), Value::Int(0)]), - Value::Bool(true), - call("odd?", vec![call("-", vec![var("n"), Value::Int(1)])]), + call("==", vec![var("n"), Value::int(0)]), + Value::bool(true), + call("odd?", vec![call("-", vec![var("n"), Value::int(1)])]), ]); let odd_body = call("if", vec![ - call("==", vec![var("n"), Value::Int(0)]), - Value::Bool(false), - call("even?", vec![call("-", vec![var("n"), Value::Int(1)])]), + call("==", vec![var("n"), Value::int(0)]), + Value::bool(false), + call("even?", vec![call("-", vec![var("n"), Value::int(1)])]), ]); let members = vec![ ("even?".into(), vec!["n".into()], even_body), @@ -1084,9 +1085,9 @@ mod tests { // f(n) = if n==0 then 0 else g(n-1) // g(n) = f(n) — no guard, no decrease → Diverging let f_body = call("if", vec![ - call("==", vec![var("n"), Value::Int(0)]), - Value::Int(0), - call("g", vec![call("-", vec![var("n"), Value::Int(1)])]), + call("==", vec![var("n"), Value::int(0)]), + Value::int(0), + call("g", vec![call("-", vec![var("n"), Value::int(1)])]), ]); let g_body = call("f", vec![var("n")]); let members = vec![ @@ -1103,13 +1104,13 @@ mod tests { // f(n) = if n==0 then 0 else g(n) — passes n unchanged // g(n) = if n==0 then 0 else f(n) — passes n unchanged let f_body = call("if", vec![ - call("==", vec![var("n"), Value::Int(0)]), - Value::Int(0), + call("==", vec![var("n"), Value::int(0)]), + Value::int(0), call("g", vec![var("n")]), ]); let g_body = call("if", vec![ - call("==", vec![var("n"), Value::Int(0)]), - Value::Int(0), + call("==", vec![var("n"), Value::int(0)]), + Value::int(0), call("f", vec![var("n")]), ]); let members = vec![ @@ -1125,17 +1126,17 @@ mod tests { fn mutual_self_call_also_counted() { // f calls both itself and g — both decrease n let f_body = call("if", vec![ - call("==", vec![var("n"), Value::Int(0)]), - Value::Int(0), + call("==", vec![var("n"), Value::int(0)]), + Value::int(0), call("+", vec![ - call("f", vec![call("-", vec![var("n"), Value::Int(1)])]), - call("g", vec![call("-", vec![var("n"), Value::Int(2)])]), + call("f", vec![call("-", vec![var("n"), Value::int(1)])]), + call("g", vec![call("-", vec![var("n"), Value::int(2)])]), ]), ]); let g_body = call("if", vec![ - call("==", vec![var("n"), Value::Int(0)]), - Value::Int(1), - call("f", vec![call("-", vec![var("n"), Value::Int(1)])]), + call("==", vec![var("n"), Value::int(0)]), + Value::int(1), + call("f", vec![call("-", vec![var("n"), Value::int(1)])]), ]); let members = vec![ ("f".into(), vec!["n".into()], f_body), @@ -1177,7 +1178,7 @@ mod tests { // f calls g (Unknown) inside {"try": [call, 1000]} → f stays Terminating let f_body = call("try", vec![ call("g", vec![var("x")]), - Value::Int(1000), + Value::int(1000), ]); let mut results = vec![ mk_result("f", Termination::Terminating, Some(f_body)), @@ -1293,7 +1294,7 @@ mod tests { singleton("tail", tail_fn), call("f", vec![call("tail", vec![var("xs")])]), ]), - Value::Null, + Value::null(), ]); assert_eq!(analyze_single(&["xs".into()], &body), Termination::Terminating); } @@ -1305,15 +1306,15 @@ mod tests { // dec(n) inlines to n - 1 → Terminating let dec_fn = make_fn( vec![var("n")], - call("-", vec![var("n"), Value::Int(1)]), + call("-", vec![var("n"), Value::int(1)]), ); let body = call("if", vec![ - call(">", vec![var("n"), Value::Int(0)]), + call(">", vec![var("n"), Value::int(0)]), call("let", vec![ singleton("dec", dec_fn), call("f", vec![call("dec", vec![var("n")])]), ]), - Value::Int(0), + Value::int(0), ]); assert_eq!(analyze_single(&["n".into()], &body), Termination::Terminating); } @@ -1325,15 +1326,15 @@ mod tests { // inc(n) inlines to n + 1 → NOT decreasing → Unknown let inc_fn = make_fn( vec![var("n")], - call("+", vec![var("n"), Value::Int(1)]), + call("+", vec![var("n"), Value::int(1)]), ); let body = call("if", vec![ - call(">", vec![var("n"), Value::Int(0)]), + call(">", vec![var("n"), Value::int(0)]), call("let", vec![ singleton("inc", inc_fn), call("f", vec![call("inc", vec![var("n")])]), ]), - Value::Int(0), + Value::int(0), ]); assert_eq!(analyze_single(&["n".into()], &body), Termination::Unknown); } @@ -1350,7 +1351,7 @@ mod tests { singleton("hd", hd_fn), call("f", vec![call("hd", vec![var("xs")])]), ]), - Value::Null, + Value::null(), ]); assert_eq!(analyze_single(&["xs".into()], &body), Termination::Terminating); } diff --git a/src/typing/check/typegen.rs b/src/typing/check/typegen.rs index 6f45743..e1daf04 100644 --- a/src/typing/check/typegen.rs +++ b/src/typing/check/typegen.rs @@ -7,6 +7,7 @@ use crate::runtime::{Budget, Number, Value}; use crate::runtime::String as RStr; +use crate::runtime::nanvalue::NanView; use crate::runtime::FloatValue; use crate::typing::{ArrayType, JestType, ObjectType, StringInfo}; use rand::Rng; @@ -40,7 +41,7 @@ fn call1(op: &str, arg: Value) -> Value { /// Helper: build `{"var": null}` wildcard pattern. fn wildcard_pattern() -> Value { let b = budget(); - Value::call("var", Value::Null, &b).unwrap() + Value::call("var", Value::null(), &b).unwrap() } // ── Configuration ──────────────────────────────────────────────────────────── @@ -390,12 +391,12 @@ pub fn gen_expr( rng: &mut impl Rng, ) -> Value { match ty { - JestType::Null => Value::Null, + JestType::Null => Value::null(), JestType::Refinement(_, base) => gen_expr(base, scope, depth, config, rng), JestType::Top => gen_top(scope, depth, config, rng), JestType::Union(members) => { if members.is_empty() { - return Value::Null; + return Value::null(); } gen_union(members, scope, depth, config, rng) } @@ -421,7 +422,7 @@ pub fn gen_expr( let strategy = weighted_choice(&strategies, rng); dispatch_strategy(strategy, ty, scope, depth, config, rng) } - JestType::Bottom => Value::Null, + JestType::Bottom => Value::null(), // Integer, Float, String, Bool all go through the strategy table _ => { let strategies = applicable_strategies(ty, scope, depth, config); @@ -480,12 +481,12 @@ fn dispatch_strategy( fn gen_literal(ty: &JestType, rng: &mut impl Rng) -> Value { match ty { JestType::Integer(info) => literal_integer_constrained(info, rng), - JestType::Float(Some(v)) => Value::Float(v.to_f64().unwrap_or(0.0)), + JestType::Float(Some(v)) => Value::float(v.to_f64().unwrap_or(0.0)), JestType::Float(None) => literal_float(rng), JestType::String(_) => literal_string(rng), - JestType::Bool(Some(b)) => Value::Bool(*b), + JestType::Bool(Some(b)) => Value::bool(*b), JestType::Bool(None) => literal_bool(rng), - JestType::Null => Value::Null, + JestType::Null => Value::null(), JestType::Object(_) => { // Empty object literal (0 or 2+ keys) gen_simple_object(&GenScope::new(), 0, &GenConfig::default(), rng) @@ -494,13 +495,13 @@ fn gen_literal(ty: &JestType, rng: &mut impl Rng) -> Value { // Minimal fn literal: fn([x], body_literal) gen_proc(param_ty, ret_ty, &GenScope::new(), 0, &GenConfig::default(), rng) } - _ => Value::Null, + _ => Value::null(), } } fn literal_integer(rng: &mut impl Rng) -> Value { let n: i64 = rng.gen_range(-100..100); - Value::Int(n) + Value::int(n) } /// Generate an integer satisfying an IntegerInfo constraint. @@ -509,7 +510,7 @@ fn literal_integer_constrained(info: &crate::typing::IntegerInfo, rng: &mut impl match info { IntegerInfo::Any => literal_integer(rng), IntegerInfo::Exact(v) => { - Value::Int(v.to_i64().unwrap_or(0)) + Value::int(v.to_i64().unwrap_or(0)) } IntegerInfo::Interval(interval) => { let lo = interval.min.as_ref() @@ -519,16 +520,16 @@ fn literal_integer_constrained(info: &crate::typing::IntegerInfo, rng: &mut impl .and_then(|v| v.to_i64()) .unwrap_or(1000); if lo > hi { - Value::Int(lo) // degenerate interval + Value::int(lo) // degenerate interval } else { - Value::Int(rng.gen_range(lo..=hi)) + Value::int(rng.gen_range(lo..=hi)) } } IntegerInfo::Residue(value, modulus) => { // Generate n ≡ value (mod modulus) in range [-100, 100] let m = *modulus as i64; let v = *value as i64; - if m == 0 { return Value::Int(0); } + if m == 0 { return Value::int(0); } // Find smallest non-negative: v, then offset by multiples of m let base = v; // Collect candidates in [-100, 100] @@ -548,9 +549,9 @@ fn literal_integer_constrained(info: &crate::typing::IntegerInfo, rng: &mut impl n -= m; } if candidates.is_empty() { - Value::Int(base) // fallback + Value::int(base) // fallback } else { - Value::Int(candidates[rng.gen_range(0..candidates.len())]) + Value::int(candidates[rng.gen_range(0..candidates.len())]) } } } @@ -559,7 +560,7 @@ fn literal_integer_constrained(info: &crate::typing::IntegerInfo, rng: &mut impl fn literal_float(rng: &mut impl Rng) -> Value { let n: f64 = rng.gen_range(-100.0..100.0); let rounded = (n * 100.0).round() / 100.0; - Value::Float(rounded) + Value::float(rounded) } fn literal_string(rng: &mut impl Rng) -> Value { @@ -570,7 +571,7 @@ fn literal_string(rng: &mut impl Rng) -> Value { } fn literal_bool(rng: &mut impl Rng) -> Value { - Value::Bool(rng.gen_bool(0.5)) + Value::bool(rng.gen_bool(0.5)) } fn gen_var_ref(ty: &JestType, scope: &GenScope, rng: &mut impl Rng) -> Value { @@ -589,7 +590,7 @@ fn gen_top(scope: &GenScope, depth: usize, config: &GenConfig, rng: &mut impl Rn 0 => gen_expr(&JestType::Integer(crate::typing::IntegerInfo::Any), scope, depth, config, rng), 1 => gen_expr(&JestType::String(StringInfo::Any), scope, depth, config, rng), 2 => gen_expr(&JestType::Bool(None), scope, depth, config, rng), - 3 => Value::Null, + 3 => Value::null(), 4 => { let b = budget(); let len = rng.gen_range(0..4); @@ -842,7 +843,7 @@ fn gen_defrec_in_do( // For Integer: n + f(n-1), or just f(n-1) // For others: just use the base case structure with f(n-1) in a subexpression let n_ref = var_ref(¶m_name); - let n_minus_1 = call_arr("-", vec![n_ref.clone(), Value::Int(1)]); + let n_minus_1 = call_arr("-", vec![n_ref.clone(), Value::int(1)]); let recursive_call = Value::call( &fn_name, Value::array(vec![n_minus_1], &b).unwrap(), @@ -869,7 +870,7 @@ fn gen_defrec_in_do( // Full body: if(n <= 0, base, recursive_body) let condition = call_arr("<=", vec![ n_ref, - Value::Int(0), + Value::int(0), ]); let fn_body = call_arr("if", vec![condition, base_val, recursive_body]); @@ -883,7 +884,7 @@ fn gen_defrec_in_do( let defrec_stmt = call1("defrec", entries_arr); // Call with a small positive integer to avoid deep recursion - let call_arg = Value::Int(rng.gen_range(0i64..8)); + let call_arg = Value::int(rng.gen_range(0i64..8)); let fn_call = Value::call( &fn_name, Value::array(vec![call_arg], &b).unwrap(), @@ -971,12 +972,12 @@ fn gen_var_pattern(ty: &JestType, rng: &mut impl Rng) -> (Value, Vec<(String, Je fn gen_literal_pattern(ty: &JestType, rng: &mut impl Rng) -> (Value, Vec<(String, JestType)>) { let lit = match ty { JestType::Integer(info) => literal_integer_constrained(info, rng), - JestType::Float(Some(v)) => Value::Float(v.to_f64().unwrap_or(0.0)), + JestType::Float(Some(v)) => Value::float(v.to_f64().unwrap_or(0.0)), JestType::Float(None) => literal_float(rng), JestType::String(_) => literal_string(rng), JestType::Bool(_) => literal_bool(rng), - JestType::Null => Value::Null, - _ => Value::Null, + JestType::Null => Value::null(), + _ => Value::null(), }; (lit, vec![]) } @@ -1279,8 +1280,8 @@ fn gen_tag_match( let opts = Value::object( vec![ - (RStr::new("self-eval", &b).unwrap(), Value::Bool(true)), - (RStr::new("normalizer", &b).unwrap(), Value::Null), + (RStr::new("self-eval", &b).unwrap(), Value::bool(true)), + (RStr::new("normalizer", &b).unwrap(), Value::null()), ], &b, ) @@ -1529,7 +1530,7 @@ fn gen_generic_dispatch_arity1( "defgeneric", vec![ Value::string(&gen_name, &b).unwrap(), - Value::Int(1), + Value::int(1), ], ); @@ -1590,7 +1591,7 @@ fn gen_generic_dispatch_arity2( "defgeneric", vec![ Value::string(&gen_name, &b).unwrap(), - Value::Int(2), + Value::int(2), ], ); @@ -1674,8 +1675,8 @@ fn gen_arithmetic( let left = gen_expr(&JestType::Integer(crate::typing::IntegerInfo::Any), scope, depth - 1, config, rng); let right = if op == "%" { let val = gen_expr(&JestType::Integer(crate::typing::IntegerInfo::Any), scope, depth - 1, config, rng); - if matches!(val, Value::Int(0)) { - Value::Int(1) + if matches!(val.view(), NanView::Int(0)) { + Value::int(1) } else { val } @@ -1784,13 +1785,13 @@ fn gen_type_predicate( let pred = predicates[rng.gen_range(0..predicates.len())]; let val = match pred { - "null?" => Value::Null, + "null?" => Value::null(), "bool?" => gen_expr(&JestType::Bool(None), scope, depth - 1, config, rng), "number?" => gen_expr(&JestType::Integer(crate::typing::IntegerInfo::Any), scope, depth - 1, config, rng), "string?" => gen_expr(&JestType::String(StringInfo::Any), scope, depth - 1, config, rng), "array?" => gen_simple_array(scope, depth - 1, config, rng), "object?" => gen_simple_object(scope, depth - 1, config, rng), - _ => Value::Null, + _ => Value::null(), }; call_arr(pred, vec![val]) @@ -2083,8 +2084,8 @@ fn gen_self_eval_type( let opts = Value::object( vec![ - (RStr::new("self-eval", &b).unwrap(), Value::Bool(true)), - (RStr::new("normalizer", &b).unwrap(), Value::Null), + (RStr::new("self-eval", &b).unwrap(), Value::bool(true)), + (RStr::new("normalizer", &b).unwrap(), Value::null()), ], &b, ) @@ -2194,7 +2195,7 @@ const DEFTYPE_RECIPES: &[DeftypeRecipe] = &[ ">", vec![ var_ref("n"), - Value::Int(0), + Value::int(0), ], ) }, @@ -2208,7 +2209,7 @@ const DEFTYPE_RECIPES: &[DeftypeRecipe] = &[ "<", vec![ var_ref("n"), - Value::Int(0), + Value::int(0), ], ) }, @@ -2222,7 +2223,7 @@ const DEFTYPE_RECIPES: &[DeftypeRecipe] = &[ "!=", vec![ call1("str-length", var_ref("s")), - Value::Int(0), + Value::int(0), ], ) }, @@ -2236,7 +2237,7 @@ const DEFTYPE_RECIPES: &[DeftypeRecipe] = &[ "<", vec![ call1("abs", var_ref("x")), - Value::Int(100), + Value::int(100), ], ) }, @@ -2456,20 +2457,20 @@ fn gen_elements_from_shape( "sorted" | "strictly-sorted" => { // Sort integer elements elems.sort_by(|a, b| { - let ai = if let Value::Int(i) = a { *i } else { 0 }; - let bi = if let Value::Int(i) = b { *i } else { 0 }; + let ai = if let NanView::Int(i) = a.view() { i } else { 0 }; + let bi = if let NanView::Int(i) = b.view() { i } else { 0 }; ai.cmp(&bi) }); if relation == "strictly-sorted" { elems.dedup_by(|a, b| { - matches!((a, &*b), (Value::Int(x), Value::Int(y)) if x == y) + matches!((a.view(), b.view()), (NanView::Int(x), NanView::Int(y)) if x == y) }); } } "reverse-sorted" => { elems.sort_by(|a, b| { - let ai = if let Value::Int(i) = a { *i } else { 0 }; - let bi = if let Value::Int(i) = b { *i } else { 0 }; + let ai = if let NanView::Int(i) = a.view() { i } else { 0 }; + let bi = if let NanView::Int(i) = b.view() { i } else { 0 }; bi.cmp(&ai) }); } @@ -2621,7 +2622,7 @@ fn gen_object_from_type( let pad_key = "__pad__"; let already_exists = pairs.iter().any(|(k, _): &(RStr, Value)| &**k == pad_key); if !already_exists { - pairs.push((RStr::new(pad_key, &b).unwrap(), Value::Null)); + pairs.push((RStr::new(pad_key, &b).unwrap(), Value::null())); } } @@ -2753,7 +2754,7 @@ mod tests { let expr = gen_integer(&scope, 0, &config, &mut rng); - assert!(matches!(expr, Value::Int(_))); + assert!(matches!(expr.view(), NanView::Int(_))); } #[test] @@ -2767,7 +2768,7 @@ mod tests { let expr = gen_string(&scope, 0, &config, &mut rng); - assert!(matches!(expr, Value::String(_))); + assert!(matches!(expr.view(), NanView::String(_))); } #[test] @@ -2781,7 +2782,7 @@ mod tests { let expr = gen_bool(&scope, 0, &config, &mut rng); - assert!(matches!(expr, Value::Bool(_))); + assert!(matches!(expr.view(), NanView::Bool(_))); } #[test] @@ -2947,7 +2948,7 @@ mod tests { JestType::String(StringInfo::Any), ])); let (pat2, _) = gen_pattern(&arr_ty, 2, &mut rng); - if matches!(pat2, Value::Array(_)) { + if matches!(pat2.view(), NanView::Array(_)) { has_array = true; } } @@ -3528,7 +3529,7 @@ mod tests { )); for _ in 0..10 { let v = gen_literal(&ty, &mut rng); - assert_eq!(v, Value::Int(7), "Exact(7) should always produce 7"); + assert_eq!(v, Value::int(7), "Exact(7) should always produce 7"); } } @@ -3542,7 +3543,7 @@ mod tests { let ty = JestType::Integer(crate::typing::IntegerInfo::Interval(interval)); for _ in 0..50 { let v = gen_literal(&ty, &mut rng); - if let Value::Int(n) = v { + if let NanView::Int(n) = v.view() { assert!((10..=20).contains(&n), "Interval [10,20] violated: got {}", n); } else { panic!("expected Int, got {:?}", v); @@ -3556,7 +3557,7 @@ mod tests { let ty = JestType::Integer(crate::typing::IntegerInfo::Residue(0, 2)); for _ in 0..50 { let v = gen_literal(&ty, &mut rng); - if let Value::Int(n) = v { + if let NanView::Int(n) = v.view() { assert_eq!(n % 2, 0, "Residue(0,2) = even violated: got {}", n); } else { panic!("expected Int, got {:?}", v); @@ -3570,7 +3571,7 @@ mod tests { let ty = JestType::Integer(crate::typing::IntegerInfo::Residue(1, 2)); for _ in 0..50 { let v = gen_literal(&ty, &mut rng); - if let Value::Int(n) = v { + if let NanView::Int(n) = v.view() { assert!(n % 2 != 0, "Residue(1,2) = odd violated: got {}", n); } else { panic!("expected Int, got {:?}", v); @@ -3584,7 +3585,7 @@ mod tests { let ty = JestType::Integer(crate::typing::IntegerInfo::Residue(1, 3)); for _ in 0..50 { let v = gen_literal(&ty, &mut rng); - if let Value::Int(n) = v { + if let NanView::Int(n) = v.view() { assert_eq!(((n % 3) + 3) % 3, 1, "Residue(1,3) violated: got {}", n); } else { panic!("expected Int, got {:?}", v); @@ -3598,8 +3599,8 @@ mod tests { let ty_true = JestType::Bool(Some(true)); let ty_false = JestType::Bool(Some(false)); for _ in 0..10 { - assert_eq!(gen_literal(&ty_true, &mut rng), Value::Bool(true)); - assert_eq!(gen_literal(&ty_false, &mut rng), Value::Bool(false)); + assert_eq!(gen_literal(&ty_true, &mut rng), Value::bool(true)); + assert_eq!(gen_literal(&ty_false, &mut rng), Value::bool(false)); } } @@ -3626,7 +3627,7 @@ mod tests { }; for _ in 0..10 { let v = gen_array(&arr_ty, &scope, 1, &config, &mut rng); - if let Value::Array(arr) = &v { + if let NanView::Array(ha) = v.view() { let arr = &ha.data; assert_eq!(arr.len(), 2, "shape says exactly 2 elements"); } else { panic!("expected Array"); @@ -3652,10 +3653,10 @@ mod tests { }; for _ in 0..20 { let v = gen_array(&arr_ty, &scope, 1, &config, &mut rng); - if let Value::Array(arr) = &v { + if let NanView::Array(ha) = v.view() { let arr = &ha.data; // Verify sorted let ints: Vec = arr.iter().filter_map(|v| { - if let Value::Int(i) = v { Some(*i) } else { None } + if let NanView::Int(i) = v.view() { Some(i) } else { None } }).collect(); for w in ints.windows(2) { assert!(w[0] <= w[1], "sorted constraint violated: {:?}", ints); @@ -3682,10 +3683,10 @@ mod tests { }; for _ in 0..20 { let v = gen_array(&arr_ty, &scope, 1, &config, &mut rng); - if let Value::Array(arr) = &v { + if let NanView::Array(ha) = v.view() { let arr = &ha.data; for elem in arr.iter() { assert!( - matches!(elem, Value::Int(_) | Value::Bool(_)), + matches!(elem.view(), NanView::Int(_) | NanView::Bool(_)), "Alt(Int,Bool) shape violated: got {:?}", elem ); } @@ -3699,7 +3700,7 @@ mod tests { let ty = JestType::Float(Some(crate::runtime::FloatValue::from_f64(3.14))); for _ in 0..10 { let v = gen_literal(&ty, &mut rng); - if let Value::Float(f) = v { + if let NanView::Float(f) = v.view() { assert!((f - 3.14).abs() < 0.001, "Exact float violated: got {}", f); } else { panic!("expected Float, got {:?}", v); diff --git a/src/typing/types.rs b/src/typing/types.rs index 504f533..14d3759 100644 --- a/src/typing/types.rs +++ b/src/typing/types.rs @@ -210,15 +210,16 @@ impl ArrayType { /// Deserialize from a runtime Value with shape `{"head":[...],"rest":{...}}`. pub fn from_value(v: &crate::Value) -> Option { - let obj = match v { - crate::Value::Object(o) => o, + use crate::runtime::nanvalue::NanView; + let obj = match v.view() { + NanView::Object(o) => o, _ => return None, }; - let head_arr = match obj.get("head")? { - crate::Value::Array(a) => a, + let head_arr = match obj.get("head")?.view() { + NanView::Array(a) => a, _ => return None, }; - let head = head_arr.iter().map(JestType::from_value).collect::>>()?; + let head = head_arr.data.iter().map(JestType::from_value).collect::>>()?; let rest = JestType::from_value(obj.get("rest")?)?; Some(ArrayType { head, rest: Box::new(rest), shape: None }) } @@ -934,43 +935,44 @@ impl JestType { /// Deserialize a `JestType` from a runtime Value. pub fn from_value(v: &crate::Value) -> Option { use crate::runtime::Number; - let obj = match v { - crate::Value::Object(o) => o, + use crate::runtime::nanvalue::NanView; + let obj = match v.view() { + NanView::Object(o) => o, _ => return None, }; - let ty_str = match obj.get("type")? { - crate::Value::String(s) => s, + let ty_str = match obj.get("type")?.view() { + NanView::String(s) => s, _ => return None, }; - match &***ty_str { + match &**ty_str { "top" => Some(JestType::Top), "bottom" => Some(JestType::Bottom), "null" => Some(JestType::Null), "bool" => { let val = obj.get("value")?; - match val { - crate::Value::Null => Some(JestType::Bool(None)), - crate::Value::Bool(b) => Some(JestType::Bool(Some(*b))), + match val.view() { + NanView::Null => Some(JestType::Bool(None)), + NanView::Bool(b) => Some(JestType::Bool(Some(b))), _ => None, } } "integer" => { // Check for interval format first if let Some(interval_obj) = obj.get("interval") { - if let crate::Value::Object(iobj) = interval_obj { - let min = match iobj.get("min") { - Some(crate::Value::Null) => None, - Some(crate::Value::Int(i)) => Some(crate::runtime::IntegerValue::from_i64(*i)), - Some(crate::Value::BigNum(n)) => match &**n { + if let NanView::Object(iobj) = interval_obj.view() { + let min = match iobj.get("min")?.view() { + NanView::Null => None, + NanView::Int(i) => Some(crate::runtime::IntegerValue::from_i64(i)), + NanView::BigNum(n) => match n { Number::Integer(i) => Some(i.clone()), _ => return None, }, _ => return None, }; - let max = match iobj.get("max") { - Some(crate::Value::Null) => None, - Some(crate::Value::Int(i)) => Some(crate::runtime::IntegerValue::from_i64(*i)), - Some(crate::Value::BigNum(n)) => match &**n { + let max = match iobj.get("max")?.view() { + NanView::Null => None, + NanView::Int(i) => Some(crate::runtime::IntegerValue::from_i64(i)), + NanView::BigNum(n) => match n { Number::Integer(i) => Some(i.clone()), _ => return None, }, @@ -982,10 +984,10 @@ impl JestType { // Legacy single value format let val = obj.get("value")?; - match val { - crate::Value::Null => Some(JestType::Integer(IntegerInfo::Any)), - crate::Value::Int(i) => Some(JestType::Integer(IntegerInfo::Exact(crate::runtime::IntegerValue::from_i64(*i)))), - crate::Value::BigNum(n) => match &**n { + match val.view() { + NanView::Null => Some(JestType::Integer(IntegerInfo::Any)), + NanView::Int(i) => Some(JestType::Integer(IntegerInfo::Exact(crate::runtime::IntegerValue::from_i64(i)))), + NanView::BigNum(n) => match n { Number::Integer(i) => Some(JestType::Integer(IntegerInfo::Exact(i.clone()))), _ => None, }, @@ -994,10 +996,10 @@ impl JestType { } "float" => { let val = obj.get("value")?; - match val { - crate::Value::Null => Some(JestType::Float(None)), - crate::Value::Float(f) => Some(JestType::Float(Some(crate::runtime::FloatValue::from_f64(*f)))), - crate::Value::BigNum(n) => match &**n { + match val.view() { + NanView::Null => Some(JestType::Float(None)), + NanView::Float(f) => Some(JestType::Float(Some(crate::runtime::FloatValue::from_f64(f)))), + NanView::BigNum(n) => match n { Number::Float(f) => Some(JestType::Float(Some(f.clone()))), _ => None, }, @@ -1012,55 +1014,59 @@ impl JestType { } // Legacy/standard format: {"type":"string","value":...} let val = obj.get("value")?; - match val { - crate::Value::Null => Some(JestType::String(StringInfo::Any)), - crate::Value::String(s) => Some(JestType::String(StringInfo::Exact((**s).to_string()))), + match val.view() { + NanView::Null => Some(JestType::String(StringInfo::Any)), + NanView::String(s) => Some(JestType::String(StringInfo::Exact((&**s).to_string()))), _ => None, } } "array" => { - let head_arr = match obj.get("head")? { - crate::Value::Array(a) => a, + let head_arr = match obj.get("head")?.view() { + NanView::Array(a) => a, _ => return None, }; - let head = head_arr.iter().map(Self::from_value).collect::>>()?; + let head = head_arr.data.iter().map(Self::from_value).collect::>>()?; let rest = Self::from_value(obj.get("rest")?)?; Some(JestType::Array(ArrayType { head, rest: Box::new(rest), shape: None })) } "object" => { - let fields_arr = match obj.get("fields")? { - crate::Value::Array(a) => a, + let fields_arr = match obj.get("fields")?.view() { + NanView::Array(a) => a, _ => return None, }; let mut fields = BTreeMap::new(); - for pair_val in fields_arr.iter() { - let pair = match pair_val { - crate::Value::Array(a) if a.len() == 2 => a, + for pair_val in fields_arr.data.iter() { + let pair = match pair_val.view() { + NanView::Array(a) if a.data.len() == 2 => a, _ => return None, }; - let k = match &pair[0] { - crate::Value::String(s) => (**s).to_string(), + let k = match pair.data[0].view() { + NanView::String(s) => (&**s).to_string(), _ => return None, }; - let v = Self::from_value(&pair[1])?; + let v = Self::from_value(&pair.data[1])?; fields.insert(k, v); } // Support both new "rest" format and legacy "open" format let rest = if let Some(rest_val) = obj.get("rest") { Self::from_value(rest_val)? - } else if let Some(crate::Value::Bool(b)) = obj.get("open") { - if *b { JestType::Top } else { JestType::Bottom } + } else if let Some(open_val) = obj.get("open") { + if let NanView::Bool(b) = open_val.view() { + if b { JestType::Top } else { JestType::Bottom } + } else { + return None; + } } else { return None; }; Some(JestType::Object(ObjectType { fields, rest: Box::new(rest), key_patterns: None })) } "union" => { - let arr = match obj.get("of")? { - crate::Value::Array(a) => a, + let arr = match obj.get("of")?.view() { + NanView::Array(a) => a, _ => return None, }; - let members = arr.iter().map(Self::from_value).collect::>>()?; + let members = arr.data.iter().map(Self::from_value).collect::>>()?; Some(JestType::Union(members)) } "proc" => { @@ -1069,23 +1075,23 @@ impl JestType { Some(JestType::Proc(params, Box::new(ret))) } "refinement" => { - let name = match obj.get("name")? { - crate::Value::String(s) => (**s).to_string(), + let name = match obj.get("name")?.view() { + NanView::String(s) => (&**s).to_string(), _ => return None, }; let base = Self::from_value(obj.get("base")?)?; Some(JestType::Refinement(name, Box::new(base))) } "generic" => { - let family = match obj.get("family")? { - crate::Value::String(s) => (**s).to_string(), + let family = match obj.get("family")?.view() { + NanView::String(s) => (&**s).to_string(), _ => return None, }; - let params_arr = match obj.get("params")? { - crate::Value::Array(a) => a, + let params_arr = match obj.get("params")?.view() { + NanView::Array(a) => a, _ => return None, }; - let params = params_arr.iter().map(Self::from_value).collect::>>()?; + let params = params_arr.data.iter().map(Self::from_value).collect::>>()?; Some(JestType::Generic(family, params)) } _ => None, @@ -2465,8 +2471,8 @@ mod tests { let budget = crate::runtime::Budget::new(1_000_000); let val = crate::runtime::parse::parse_json(&json, &budget) .expect("type JSON should parse"); - match val { - crate::Value::Object(obj) => { + match val.view() { + crate::runtime::nanvalue::NanView::Object(obj) => { assert!(obj.len() >= 2, "type JSON should have 2+ keys: {json}"); } _ => panic!("type JSON should be an object: {json}"), -- 2.51.2 From 5a3c31433055ba2870ce44ca6d37f13ccfafa3bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Siobh=C3=A1n=20Sterrett?= Date: Wed, 8 Apr 2026 16:01:48 -0400 Subject: [PATCH 2/3] =?UTF-8?q?docs:=20update=20STATUS.md=20=E2=80=94=20Na?= =?UTF-8?q?N-boxing=20API=20migration=20complete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- STATUS.md | 36 ++++++++++++------------------------ 1 file changed, 12 insertions(+), 24 deletions(-) diff --git a/STATUS.md b/STATUS.md index cba79df..a32a2ca 100644 --- a/STATUS.md +++ b/STATUS.md @@ -571,33 +571,21 @@ NaN-boxed register VM is faster than the original stack VM on tight recursive be NanValue-native ops (no Value conversion): LoadNull, LoadTrue, LoadFalse, Move, JumpIfFalse, Return (within frames). Remaining ops still convert at get_reg/set_reg boundary. -**Step 3b — Full NaN-boxing:** Replace Value everywhere with u64-based NanValue. -Requires ValueView enum for pattern matching, ObjectRef/ArrayRef wrappers, transform() rework, -~1,900 callsite migration. Maximum benefit but massive effort. VM-only captures most of the win. +**Step 3b — Full NaN-boxing — API MIGRATION DONE (2026-04-08):** -#### NaN-Boxing Lessons Learned (2026-04-03) +All 2700+ pattern match sites across 52 files migrated to `val.view()` + `NanView::Variant`. +All constructors migrated to method-style (`Value::int()`, `Value::null()`, etc.). +Helper methods added to HeapObj/HeapArr (get, iter, keys, values, len, is_empty). +nanvalue.rs moved from compile/ to runtime/ (it's the core representation now). -Attempted full NaN-boxing in one step. WIP saved on `nan-boxing-wip` branch. Key difficulties: +Remaining to swap the representation: +1. Replace `enum Value { ... }` with `struct Value(NanValue)` + Clone/Drop impls +2. Simplify nanvalue.rs conversion functions (from_value becomes trivial) +3. 3 sites need owned Array extraction helper (vm.rs, procs/mod.rs, procs/array.rs) +4. Update size assertion test: 16 → 8 bytes -1. **Array/Object accessor gap.** Pattern matches bind `obj: &Object` which has `.get()`, - `.iter()`, `.len()`. NaN-boxed Value stores a raw `*const HeapObj` — no way to return - `&Object` from `view()` since Object is `Rc` (different layout than raw pointer). - Need an `ObjectRef<'a>(&'a HeapObj)` wrapper that mirrors Object's API. - -2. **transform() ownership conflict.** Takes `self` (consuming Value), matches on it, recurses. - With NaN-boxing, `self.view()` borrows `self` while we need to consume it. Requires cloning - the Rc pointers before consuming, or restructuring transform to work differently. - -3. **1,900 callsite migration.** Every `match value { Value::X => }` becomes - `match value.view() { ValueView::X => }` — not just a find-replace but a semantic change - (enum destructuring → method call returning view). Deep nested patterns need structural rewrites. - -4. **Inline string lifetime.** Returning `&str` from `view()` that points into the Value's own - u64 bytes requires LE-specific pointer arithmetic and careful lifetime management. - -**Conclusion:** NaN-boxing is achievable but best done incrementally. Steps 1→2→3 each halve the -size with increasing complexity. Step 1 gives the main performance win (inline i64/f64) with -minimal API disruption. +The hard work (2700 match site migration) is complete. The enum→newtype swap is a small, +contained change since all consumer code already uses `view()` and method constructors. ### Interpreter Performance — Low Priority (VM is 200x faster) -- 2.51.2 From 9d30280965319a38a0cfa244737984a40ca4cdf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Siobh=C3=A1n=20Sterrett?= Date: Wed, 8 Apr 2026 17:17:56 -0400 Subject: [PATCH 3/3] =?UTF-8?q?perf:=20complete=20NaN-boxing=20=E2=80=94?= =?UTF-8?q?=20Value=20is=20now=208=20bytes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace `enum Value { Null, Bool, Int, Float, BigNum, String, Array, Object }` with `pub struct Value(pub(crate) NanValue)` — an 8-byte RAII wrapper around the NaN-boxed u64 representation. - Clone via clone_heap(), Drop via drop_heap() - view() delegates to NanValue::view() for zero-cost pattern matching - into_nan() / as_nan() for zero-cost Value→NanValue extraction - as_array() / as_object() for owned Rc extraction - from_value/to_value_cloning/into_value simplified to trivial one-liners - All 34 VM conversion boundary points eliminated (val.into_nan() etc.) - size_of::() = 8 (down from 16) Benchmarks (VM Native64): fib25 87ms (-11%), ack3_6 58ms (-11%) 1371 tests passing, 0 failures. Co-Authored-By: Claude Opus 4.6 --- STATUS.md | 51 ++++++------ benchmarks/log.jsonl | 3 + src/compile/vm.rs | 84 +++++++------------- src/runtime/data.rs | 125 ++++++++++++++++++++--------- src/runtime/nanvalue.rs | 159 +++++++++---------------------------- src/runtime/procs/array.rs | 6 +- src/runtime/procs/mod.rs | 6 +- 7 files changed, 187 insertions(+), 247 deletions(-) diff --git a/STATUS.md b/STATUS.md index a32a2ca..2e994b5 100644 --- a/STATUS.md +++ b/STATUS.md @@ -253,9 +253,8 @@ Compilation is prerequisite for practical self-hosting of the type checker. ## Verification ``` -1163 tests passing, 0 failures +1371 tests passing, 0 failures 4 ignored (2 stress benchmarks + 2 native64) - 0 clippy warnings ``` ``` @@ -443,18 +442,18 @@ performance penalty that existed when template arrays were empty. - Complete round-trip: IR → JSON Value → IR via `to_value()` and `from_value()` - Nested Chunk serialization for LetRec body_chunks (recursive serialization) -**Benchmark results** (release mode, 2026-04-08, NaN-boxed register VM + Native64): +**Benchmark results** (release mode, 2026-04-08, full NaN-boxing + VM conversion elimination): ``` ┌──────────┬──────────────────────┬──────────────────────┬──────────┐ │ Program │ Interpreter (ms) │ Bytecode VM (ms) │ VM gain │ │ │ BigNum Native64 │ BigNum Native64 │ │ ├──────────┼──────────────────────┼──────────────────────┼──────────┤ - │ fib25 │ 30834 30542 │ 148 92 │ 331.7x │ - │ sum10k │ 1523 1521 │ 7 4 │ 395.5x │ - │ ack3_6 │ 13156 13130 │ 90 64 │ 204.1x │ - │ pat10k │ 2449 2438 │ 54 47 │ 51.4x │ - │ clos1k │ 420 430 │ 9 8 │ 52.3x │ - │ hof10k │ 574 563 │ 551 550 │ 1.0x │ + │ fib25 │ 31336 31321 │ 144 87 │ 358.4x │ + │ sum10k │ 1571 1571 │ 7 4 │ 394.9x │ + │ ack3_6 │ 13410 13478 │ 87 58 │ 231.0x │ + │ pat10k │ 2525 2471 │ 48 44 │ 55.8x │ + │ clos1k │ 420 420 │ 9 8 │ 53.7x │ + │ hof10k │ 594 567 │ 552 552 │ 1.0x │ └──────────┴──────────────────────┴──────────────────────┴──────────┘ ``` Run: `cargo run --release --bin bench` (appends to `benchmarks/log.jsonl`) @@ -518,9 +517,8 @@ for contiguous arg windows. - Temp register reuse via save/restore across sibling call expressions (high-water mark tracking) - `CallFrame.dst_reg` replaces `stack_base` — on return, write result to caller's register -**Performance (Native64):** fib25 95ms (318x), sum10k 4ms (378x), ack3_6 67ms (201x). -Slight regression vs prior stack VM (78ms fib25) due to Move instructions for arg windows -and larger locals arrays. Enables NaN-boxing (8-byte Values fit directly in register slots). +**Performance (Native64, full NaN-boxing):** fib25 87ms (358x), sum10k 4ms (395x), ack3_6 58ms (231x). +8-byte Values fit directly in register slots — no conversion overhead at VM boundaries. ### Escape Hatches: What Can't We Compile? @@ -548,8 +546,9 @@ and larger locals arrays. Enables NaN-boxing (8-byte Values fit directly in regi ### Value Representation Roadmap -**Current (2026-04-03):** 16-byte `enum Value { Null, Bool, Int(i64), Float(f64), BigNum(Box), String(Box), Array, Object }`. -All heap types behind pointers → max payload 8 bytes + discriminant = 16 bytes. +**Current (2026-04-08):** 8-byte `struct Value(NanValue)` — NaN-boxed RAII wrapper. +Inline i48 integers, f64 floats. Heap types (BigNum, String, Array, Object) as raw 48-bit pointers. +Clone/Drop via clone_heap/drop_heap. Pattern matching via `val.view()` → `NanView<'_>`. **Step 1 — Flattened enum (48→32 bytes):** Done. Inline `Int(i64)` and `Float(f64)` directly in Value. @@ -571,21 +570,21 @@ NaN-boxed register VM is faster than the original stack VM on tight recursive be NanValue-native ops (no Value conversion): LoadNull, LoadTrue, LoadFalse, Move, JumpIfFalse, Return (within frames). Remaining ops still convert at get_reg/set_reg boundary. -**Step 3b — Full NaN-boxing — API MIGRATION DONE (2026-04-08):** +**Step 3b — Full NaN-boxing — COMPLETE (2026-04-08):** -All 2700+ pattern match sites across 52 files migrated to `val.view()` + `NanView::Variant`. -All constructors migrated to method-style (`Value::int()`, `Value::null()`, etc.). -Helper methods added to HeapObj/HeapArr (get, iter, keys, values, len, is_empty). -nanvalue.rs moved from compile/ to runtime/ (it's the core representation now). +`Value` is now `pub struct Value(pub(crate) NanValue)` — 8 bytes, RAII lifecycle. +Clone delegates to `clone_heap()`, Drop delegates to `drop_heap()`. +`val.view()` → `NanView<'_>` for zero-cost borrowed pattern matching (one indirection removed). +`val.into_nan()` / `val.as_nan()` for zero-cost Value→NanValue extraction. +`val.as_array()` / `val.as_object()` for owned Rc extraction (refcount bump). +`from_value`/`to_value_cloning`/`into_value` simplified to trivial one-liners. -Remaining to swap the representation: -1. Replace `enum Value { ... }` with `struct Value(NanValue)` + Clone/Drop impls -2. Simplify nanvalue.rs conversion functions (from_value becomes trivial) -3. 3 sites need owned Array extraction helper (vm.rs, procs/mod.rs, procs/array.rs) -4. Update size assertion test: 16 → 8 bytes +**Step 3c — VM conversion elimination — COMPLETE (2026-04-08):** -The hard work (2700 match site migration) is complete. The enum→newtype swap is a small, -contained change since all consumer code already uses `view()` and method constructors. +All 34 `NanValue::from_value`/`to_value_cloning`/`into_value` calls in vm.rs replaced +with direct `val.into_nan()`, `val.as_nan().clone_heap()`, `Value(nv.clone_heap())`. +Template op boundaries still convert `Vec` ↔ `Vec>` for IR evaluators, +but this is now zero-overhead (wrapping/unwrapping the same u64). ### Interpreter Performance — Low Priority (VM is 200x faster) diff --git a/benchmarks/log.jsonl b/benchmarks/log.jsonl index 7da5bd1..1773679 100644 --- a/benchmarks/log.jsonl +++ b/benchmarks/log.jsonl @@ -59,3 +59,6 @@ {"date":"2026-04-08 13:00","commit":"aacd491","fib25_interp_big_ms":31261.2,"fib25_interp_n64_ms":31053.8,"fib25_vm_big_ms":148.2,"fib25_vm_n64_ms":92.1,"fact100_interp_big_ms":15.6,"fact100_vm_big_ms":0.7,"sum10k_interp_big_ms":1581.4,"sum10k_interp_n64_ms":1553.1,"sum10k_vm_big_ms":6.7,"sum10k_vm_n64_ms":4.2,"ack3_6_interp_big_ms":13538.2,"ack3_6_interp_n64_ms":13479.8,"ack3_6_vm_big_ms":90.2,"ack3_6_vm_n64_ms":65.1,"dyn10k_interp_big_ms":1599.2,"dyn10k_interp_n64_ms":1565.8,"dyn10k_vm_big_ms":8.8,"dyn10k_vm_n64_ms":5.9,"hof10k_interp_big_ms":619.7,"hof10k_interp_n64_ms":585.8,"hof10k_vm_big_ms":578.1,"hof10k_vm_n64_ms":580.5,"obj5k_interp_big_ms":137.2,"obj5k_interp_n64_ms":141.1,"obj5k_vm_big_ms":4.4,"obj5k_vm_n64_ms":4.7,"str5k_interp_big_ms":552.6,"str5k_interp_n64_ms":533.6,"str5k_vm_big_ms":4.8,"str5k_vm_n64_ms":4.0,"pat10k_interp_big_ms":2560.2,"pat10k_interp_n64_ms":2538.6,"pat10k_vm_big_ms":56.9,"pat10k_vm_n64_ms":48.4,"arr5k_interp_big_ms":573.8,"arr5k_interp_n64_ms":570.2,"arr5k_vm_big_ms":35.9,"arr5k_vm_n64_ms":34.9,"clos1k_interp_big_ms":424.2,"clos1k_interp_n64_ms":419.5,"clos1k_vm_big_ms":9.3,"clos1k_vm_n64_ms":8.3,"mixed_interp_big_ms":50.0,"mixed_interp_n64_ms":49.8,"mixed_vm_big_ms":2.0,"mixed_vm_n64_ms":2.0} {"date":"2026-04-08 13:13","commit":"a70e2ba","fib25_interp_big_ms":30866.0,"fib25_interp_n64_ms":30668.5,"fib25_vm_big_ms":149.2,"fib25_vm_n64_ms":93.1,"fact100_interp_big_ms":15.8,"fact100_vm_big_ms":0.7,"sum10k_interp_big_ms":1561.4,"sum10k_interp_n64_ms":1553.3,"sum10k_vm_big_ms":7.0,"sum10k_vm_n64_ms":4.0,"ack3_6_interp_big_ms":13383.9,"ack3_6_interp_n64_ms":13370.0,"ack3_6_vm_big_ms":90.3,"ack3_6_vm_n64_ms":63.2,"dyn10k_interp_big_ms":1588.8,"dyn10k_interp_n64_ms":1550.9,"dyn10k_vm_big_ms":8.7,"dyn10k_vm_n64_ms":6.0,"hof10k_interp_big_ms":578.1,"hof10k_interp_n64_ms":591.3,"hof10k_vm_big_ms":561.0,"hof10k_vm_n64_ms":557.1,"obj5k_interp_big_ms":137.1,"obj5k_interp_n64_ms":136.4,"obj5k_vm_big_ms":4.5,"obj5k_vm_n64_ms":4.2,"str5k_interp_big_ms":527.5,"str5k_interp_n64_ms":530.7,"str5k_vm_big_ms":4.8,"str5k_vm_n64_ms":3.9,"pat10k_interp_big_ms":2500.0,"pat10k_interp_n64_ms":2505.6,"pat10k_vm_big_ms":54.0,"pat10k_vm_n64_ms":49.8,"arr5k_interp_big_ms":575.2,"arr5k_interp_n64_ms":561.9,"arr5k_vm_big_ms":35.4,"arr5k_vm_n64_ms":34.6,"clos1k_interp_big_ms":423.8,"clos1k_interp_n64_ms":419.3,"clos1k_vm_big_ms":11.8,"clos1k_vm_n64_ms":9.7,"mixed_interp_big_ms":49.9,"mixed_interp_n64_ms":48.3,"mixed_vm_big_ms":2.1,"mixed_vm_n64_ms":1.9} {"date":"2026-04-08 14:09","commit":"a966c80","fib25_interp_big_ms":30772.4,"fib25_interp_n64_ms":30524.6,"fib25_vm_big_ms":148.9,"fib25_vm_n64_ms":94.1,"fact100_interp_big_ms":16.0,"fact100_vm_big_ms":0.7,"sum10k_interp_big_ms":1556.3,"sum10k_interp_n64_ms":1558.9,"sum10k_vm_big_ms":7.1,"sum10k_vm_n64_ms":4.1,"ack3_6_interp_big_ms":13639.4,"ack3_6_interp_n64_ms":13561.1,"ack3_6_vm_big_ms":98.1,"ack3_6_vm_n64_ms":65.9,"dyn10k_interp_big_ms":1608.3,"dyn10k_interp_n64_ms":1612.1,"dyn10k_vm_big_ms":9.9,"dyn10k_vm_n64_ms":6.1,"hof10k_interp_big_ms":609.1,"hof10k_interp_n64_ms":586.8,"hof10k_vm_big_ms":578.4,"hof10k_vm_n64_ms":576.5,"obj5k_interp_big_ms":137.2,"obj5k_interp_n64_ms":135.7,"obj5k_vm_big_ms":4.5,"obj5k_vm_n64_ms":4.2,"str5k_interp_big_ms":534.2,"str5k_interp_n64_ms":542.6,"str5k_vm_big_ms":5.0,"str5k_vm_n64_ms":4.0,"pat10k_interp_big_ms":2575.7,"pat10k_interp_n64_ms":2549.8,"pat10k_vm_big_ms":55.0,"pat10k_vm_n64_ms":54.9,"arr5k_interp_big_ms":587.1,"arr5k_interp_n64_ms":582.4,"arr5k_vm_big_ms":35.6,"arr5k_vm_n64_ms":34.7,"clos1k_interp_big_ms":427.9,"clos1k_interp_n64_ms":431.6,"clos1k_vm_big_ms":9.5,"clos1k_vm_n64_ms":8.5,"mixed_interp_big_ms":56.0,"mixed_interp_n64_ms":52.2,"mixed_vm_big_ms":3.7,"mixed_vm_n64_ms":1.9} +{"date":"2026-04-08 16:00","commit":"c69ddb8","fib25_interp_big_ms":29962.6,"fib25_interp_n64_ms":30080.4,"fib25_vm_big_ms":145.9,"fib25_vm_n64_ms":97.1,"fact100_interp_big_ms":16.0,"fact100_vm_big_ms":0.7,"sum10k_interp_big_ms":1539.8,"sum10k_interp_n64_ms":1572.1,"sum10k_vm_big_ms":7.1,"sum10k_vm_n64_ms":4.1,"ack3_6_interp_big_ms":13461.9,"ack3_6_interp_n64_ms":13407.3,"ack3_6_vm_big_ms":92.1,"ack3_6_vm_n64_ms":65.1,"dyn10k_interp_big_ms":1553.8,"dyn10k_interp_n64_ms":1540.0,"dyn10k_vm_big_ms":8.9,"dyn10k_vm_n64_ms":6.0,"hof10k_interp_big_ms":592.2,"hof10k_interp_n64_ms":575.9,"hof10k_vm_big_ms":552.2,"hof10k_vm_n64_ms":549.6,"obj5k_interp_big_ms":135.0,"obj5k_interp_n64_ms":134.6,"obj5k_vm_big_ms":4.3,"obj5k_vm_n64_ms":4.1,"str5k_interp_big_ms":536.1,"str5k_interp_n64_ms":536.4,"str5k_vm_big_ms":4.9,"str5k_vm_n64_ms":4.0,"pat10k_interp_big_ms":2526.6,"pat10k_interp_n64_ms":2478.4,"pat10k_vm_big_ms":53.5,"pat10k_vm_n64_ms":48.4,"arr5k_interp_big_ms":569.9,"arr5k_interp_n64_ms":558.6,"arr5k_vm_big_ms":35.4,"arr5k_vm_n64_ms":34.3,"clos1k_interp_big_ms":423.2,"clos1k_interp_n64_ms":424.8,"clos1k_vm_big_ms":9.0,"clos1k_vm_n64_ms":9.7,"mixed_interp_big_ms":49.7,"mixed_interp_n64_ms":51.1,"mixed_vm_big_ms":2.1,"mixed_vm_n64_ms":2.0} +{"date":"2026-04-08 16:16","commit":"5a3c314","fib25_interp_big_ms":31324.6,"fib25_interp_n64_ms":31532.5,"fib25_vm_big_ms":144.6,"fib25_vm_n64_ms":87.6,"fact100_interp_big_ms":16.2,"fact100_vm_big_ms":0.7,"sum10k_interp_big_ms":1581.9,"sum10k_interp_n64_ms":1579.9,"sum10k_vm_big_ms":6.6,"sum10k_vm_n64_ms":3.9,"ack3_6_interp_big_ms":13689.8,"ack3_6_interp_n64_ms":13748.4,"ack3_6_vm_big_ms":88.1,"ack3_6_vm_n64_ms":61.6,"dyn10k_interp_big_ms":1645.6,"dyn10k_interp_n64_ms":1585.7,"dyn10k_vm_big_ms":7.9,"dyn10k_vm_n64_ms":4.9,"hof10k_interp_big_ms":597.3,"hof10k_interp_n64_ms":581.3,"hof10k_vm_big_ms":567.6,"hof10k_vm_n64_ms":566.0,"obj5k_interp_big_ms":139.2,"obj5k_interp_n64_ms":136.6,"obj5k_vm_big_ms":4.3,"obj5k_vm_n64_ms":4.0,"str5k_interp_big_ms":534.2,"str5k_interp_n64_ms":532.5,"str5k_vm_big_ms":4.7,"str5k_vm_n64_ms":3.8,"pat10k_interp_big_ms":2534.8,"pat10k_interp_n64_ms":2507.8,"pat10k_vm_big_ms":49.3,"pat10k_vm_n64_ms":44.2,"arr5k_interp_big_ms":573.9,"arr5k_interp_n64_ms":565.9,"arr5k_vm_big_ms":33.6,"arr5k_vm_n64_ms":32.8,"clos1k_interp_big_ms":426.0,"clos1k_interp_n64_ms":446.4,"clos1k_vm_big_ms":8.7,"clos1k_vm_n64_ms":7.9,"mixed_interp_big_ms":50.5,"mixed_interp_n64_ms":50.3,"mixed_vm_big_ms":2.2,"mixed_vm_n64_ms":2.0} +{"date":"2026-04-08 16:27","commit":"5a3c314","fib25_interp_big_ms":31336.3,"fib25_interp_n64_ms":31321.3,"fib25_vm_big_ms":143.8,"fib25_vm_n64_ms":87.4,"fact100_interp_big_ms":16.1,"fact100_vm_big_ms":0.7,"sum10k_interp_big_ms":1571.4,"sum10k_interp_n64_ms":1570.6,"sum10k_vm_big_ms":6.7,"sum10k_vm_n64_ms":4.0,"ack3_6_interp_big_ms":13409.5,"ack3_6_interp_n64_ms":13477.5,"ack3_6_vm_big_ms":86.9,"ack3_6_vm_n64_ms":58.3,"dyn10k_interp_big_ms":1566.3,"dyn10k_interp_n64_ms":1561.7,"dyn10k_vm_big_ms":7.5,"dyn10k_vm_n64_ms":4.9,"hof10k_interp_big_ms":594.4,"hof10k_interp_n64_ms":567.0,"hof10k_vm_big_ms":552.4,"hof10k_vm_n64_ms":551.6,"obj5k_interp_big_ms":136.7,"obj5k_interp_n64_ms":135.4,"obj5k_vm_big_ms":4.2,"obj5k_vm_n64_ms":3.9,"str5k_interp_big_ms":525.4,"str5k_interp_n64_ms":526.3,"str5k_vm_big_ms":4.8,"str5k_vm_n64_ms":3.8,"pat10k_interp_big_ms":2525.2,"pat10k_interp_n64_ms":2470.7,"pat10k_vm_big_ms":48.5,"pat10k_vm_n64_ms":44.2,"arr5k_interp_big_ms":569.1,"arr5k_interp_n64_ms":562.0,"arr5k_vm_big_ms":33.6,"arr5k_vm_n64_ms":32.6,"clos1k_interp_big_ms":419.7,"clos1k_interp_n64_ms":420.0,"clos1k_vm_big_ms":8.7,"clos1k_vm_n64_ms":7.8,"mixed_interp_big_ms":49.4,"mixed_interp_n64_ms":49.7,"mixed_vm_big_ms":2.0,"mixed_vm_n64_ms":1.9} diff --git a/src/compile/vm.rs b/src/compile/vm.rs index 6e771fa..7709321 100644 --- a/src/compile/vm.rs +++ b/src/compile/vm.rs @@ -14,7 +14,6 @@ use super::eval::{ }; use super::ir::FnKind; use crate::runtime::nanvalue::{NanValue, NanView}; -use crate::runtime::convert::is_truthy; use crate::runtime::procs::dispatch_call_with_ctx; use crate::runtime::{eval_trampoline, Budget, Eval, Scope, Value}; @@ -202,7 +201,7 @@ pub(crate) fn vm_execute_with_ctx( let idx = *slot as usize; if idx < vm.locals.len() { vm.locals[idx].drop_heap(); - vm.locals[idx] = NanValue::from_value(val); + vm.locals[idx] = val.into_nan(); } } } @@ -260,7 +259,7 @@ impl<'a> VmWithCtx<'a> { scope: Scope, ) -> Self { let nan_constants: Vec = chunk.constants.iter() - .map(|v| NanValue::from_value(v.clone())) + .map(|v| v.as_nan().clone_heap()) .collect(); VmWithCtx { chunk, @@ -279,38 +278,15 @@ impl<'a> VmWithCtx<'a> { } } - /// Get a locals Vec from the pool, or allocate a new one. - fn alloc_locals(&mut self, n: usize) -> Vec { - if let Some(mut v) = self.locals_pool.pop() { - // Drop heap refs before reuse - for nv in v.iter() { - nv.drop_heap(); - } - v.clear(); - v.resize(n, NanValue::empty()); - v - } else { - vec![NanValue::empty(); n] - } - } - /// Return a locals Vec to the pool. fn free_locals(&mut self, v: Vec) { // Don't drop heap refs here — they were transferred to the new frame self.locals_pool.push(v); } - /// Get Rc pointer identity from a Value::Object for proc identity checks. + /// Get Rc pointer identity from a Value for proc identity checks. fn proc_identity(proc_val: &Value) -> usize { - match proc_val.view() { - NanView::Object(ho) => ho as *const _ as usize, - _ => 0, - } - } - - /// Get Rc pointer identity directly from a NanValue (avoids Value conversion). - fn proc_identity_nan(nv: NanValue) -> usize { - nv.proc_identity() + proc_val.as_nan().proc_identity() } /// Read a value from a register (locals slot), falling back to scope lookup. @@ -320,7 +296,7 @@ impl<'a> VmWithCtx<'a> { if idx < self.locals.len() { let nv = self.locals[idx]; if !nv.is_empty() { - return Ok(nv.to_value_cloning()); + return Ok(Value(nv.clone_heap())); } } self.lookup_from_scope(reg) @@ -334,7 +310,7 @@ impl<'a> VmWithCtx<'a> { self.locals.resize(idx + 1, NanValue::empty()); } self.locals[idx].drop_heap(); - self.locals[idx] = NanValue::from_value(val); + self.locals[idx] = val.into_nan(); } /// Read a NanValue directly from a register, with scope fallback. @@ -349,7 +325,7 @@ impl<'a> VmWithCtx<'a> { } // Fallback: look up in scope (for captured variables / globals) if let Ok(val) = self.lookup_from_scope(reg) { - NanValue::from_value(val) + val.into_nan() } else { NanValue::empty() } @@ -586,7 +562,7 @@ impl<'a> VmWithCtx<'a> { if let Some(len) = nv.array_len() { self.set_nan(dst, NanValue::int(len as i64)); } else { - let val = nv.to_value_cloning(); + let val = Value(nv.clone_heap()); return Err(crate::Error::Throw( format!("count: expected array, got {}", val))); } @@ -600,7 +576,7 @@ impl<'a> VmWithCtx<'a> { NanView::Array(_) => return Err(crate::Error::Throw( "first: empty array".to_string())), _ => return Err(crate::Error::Throw( - format!("first: expected array, got {}", nv.to_value_cloning()))), + format!("first: expected array, got {}", Value(nv.clone_heap())))), } } Op::Rest(dst, src) => { @@ -612,7 +588,7 @@ impl<'a> VmWithCtx<'a> { } NanView::Array(_) => self.set_reg(dst, Value::array(vec![], self.budget)?), _ => return Err(crate::Error::Throw( - format!("rest: expected array, got {}", nv.to_value_cloning()))), + format!("rest: expected array, got {}", Value(nv.clone_heap())))), } } Op::Cons(dst, elem_reg, arr_reg) => { @@ -626,7 +602,7 @@ impl<'a> VmWithCtx<'a> { self.set_reg(dst, Value::array(items, self.budget)?); } _ => return Err(crate::Error::Throw( - format!("cons: expected array as second argument, got {}", arr_nv.to_value_cloning()))), + format!("cons: expected array as second argument, got {}", Value(arr_nv.clone_heap())))), } } Op::Nth(dst, arr_reg, idx_reg) => { @@ -642,9 +618,9 @@ impl<'a> VmWithCtx<'a> { } } (NanView::Array(_), None) => return Err(crate::Error::Throw( - format!("nth: expected integer index, got {}", idx_nv.to_value_cloning()))), + format!("nth: expected integer index, got {}", Value(idx_nv.clone_heap())))), _ => return Err(crate::Error::Throw( - format!("nth: expected array, got {}", arr_nv.to_value_cloning()))), + format!("nth: expected array, got {}", Value(arr_nv.clone_heap())))), } } Op::NthUnchecked(dst, arr_reg, idx_reg) => { @@ -677,7 +653,7 @@ impl<'a> VmWithCtx<'a> { Op::EvalMatch(dst, idx) => { let template = &chunk.match_templates[idx as usize]; let mut value_locals: Vec> = self.locals.iter() - .map(|nv| if nv.is_empty() { None } else { Some(nv.to_value_cloning()) }) + .map(|nv| if nv.is_empty() { None } else { Some(Value(nv.clone_heap())) }) .collect(); let result = vm_eval_decision_tree( &template.tree, @@ -693,7 +669,7 @@ impl<'a> VmWithCtx<'a> { if i < self.locals.len() { self.locals[i].drop_heap(); self.locals[i] = match opt { - Some(v) => NanValue::from_value(v), + Some(v) => v.into_nan(), None => NanValue::empty(), }; } @@ -703,7 +679,7 @@ impl<'a> VmWithCtx<'a> { Op::EvalTry(dst, idx) => { let template = &chunk.try_templates[idx as usize]; let mut value_locals: Vec> = self.locals.iter() - .map(|nv| if nv.is_empty() { None } else { Some(nv.to_value_cloning()) }) + .map(|nv| if nv.is_empty() { None } else { Some(Value(nv.clone_heap())) }) .collect(); let result = vm_eval_try( &template.expr, @@ -719,7 +695,7 @@ impl<'a> VmWithCtx<'a> { if i < self.locals.len() { self.locals[i].drop_heap(); self.locals[i] = match opt { - Some(v) => NanValue::from_value(v), + Some(v) => v.into_nan(), None => NanValue::empty(), }; } @@ -729,7 +705,7 @@ impl<'a> VmWithCtx<'a> { Op::EvalDispatch(dst, idx) => { let template = &chunk.dispatch_templates[idx as usize]; let mut value_locals: Vec> = self.locals.iter() - .map(|nv| if nv.is_empty() { None } else { Some(nv.to_value_cloning()) }) + .map(|nv| if nv.is_empty() { None } else { Some(Value(nv.clone_heap())) }) .collect(); let result = vm_eval_dispatch( &template.generic_name, @@ -747,7 +723,7 @@ impl<'a> VmWithCtx<'a> { if i < self.locals.len() { self.locals[i].drop_heap(); self.locals[i] = match opt { - Some(v) => NanValue::from_value(v), + Some(v) => v.into_nan(), None => NanValue::empty(), }; } @@ -757,7 +733,7 @@ impl<'a> VmWithCtx<'a> { Op::EvalSeq(dst, idx) => { let template = &chunk.seq_templates[idx as usize]; let mut value_locals: Vec> = self.locals.iter() - .map(|nv| if nv.is_empty() { None } else { Some(nv.to_value_cloning()) }) + .map(|nv| if nv.is_empty() { None } else { Some(Value(nv.clone_heap())) }) .collect(); let result = vm_eval_seq( &template.stmts, @@ -771,7 +747,7 @@ impl<'a> VmWithCtx<'a> { if i < self.locals.len() { self.locals[i].drop_heap(); self.locals[i] = match opt { - Some(v) => NanValue::from_value(v), + Some(v) => v.into_nan(), None => NanValue::empty(), }; } @@ -781,7 +757,7 @@ impl<'a> VmWithCtx<'a> { Op::EvalLetRec(dst, idx) => { let template = &chunk.letrec_templates[idx as usize]; let mut value_locals: Vec> = self.locals.iter() - .map(|nv| if nv.is_empty() { None } else { Some(nv.to_value_cloning()) }) + .map(|nv| if nv.is_empty() { None } else { Some(Value(nv.clone_heap())) }) .collect(); let result = vm_eval_letrec( &template.defs, @@ -797,7 +773,7 @@ impl<'a> VmWithCtx<'a> { if i < self.locals.len() { self.locals[i].drop_heap(); self.locals[i] = match opt { - Some(v) => NanValue::from_value(v), + Some(v) => v.into_nan(), None => NanValue::empty(), }; } @@ -841,7 +817,7 @@ impl<'a> VmWithCtx<'a> { let idx = *slot as usize; if idx < num_locals && i < args.len() { new_locals[idx].drop_heap(); - new_locals[idx] = NanValue::from_value(args[i].clone()); + new_locals[idx] = args[i].as_nan().clone_heap(); } } @@ -851,7 +827,7 @@ impl<'a> VmWithCtx<'a> { let idx = *slot as usize; if idx < num_locals { new_locals[idx].drop_heap(); - new_locals[idx] = NanValue::from_value(proc_val); + new_locals[idx] = proc_val.into_nan(); } } @@ -861,7 +837,7 @@ impl<'a> VmWithCtx<'a> { if idx < num_locals { if let Some(val) = self.scope.get_global_slot(global_slot) { new_locals[idx].drop_heap(); - new_locals[idx] = NanValue::from_value(val); + new_locals[idx] = val.into_nan(); } } } @@ -871,7 +847,7 @@ impl<'a> VmWithCtx<'a> { if idx < num_locals { if let Some(val) = self.scope.get(name) { new_locals[idx].drop_heap(); - new_locals[idx] = NanValue::from_value(val); + new_locals[idx] = val.into_nan(); } } } @@ -1050,9 +1026,9 @@ impl<'a> VmWithCtx<'a> { Some(("mu", arg)) => arg, _ => return None, }; - let items = match mu_arg { - Value::Array(arr) => arr.clone(), - _ => return None, + let items = match mu_arg.as_array() { + Some(arr) => arr, + None => return None, }; if items.len() == 4 { diff --git a/src/runtime/data.rs b/src/runtime/data.rs index 111b1b5..9c388dc 100644 --- a/src/runtime/data.rs +++ b/src/runtime/data.rs @@ -1,10 +1,11 @@ use std::collections::HashSet; use std::fmt; +use std::mem; use std::ops::Deref; use std::rc::Rc; use super::{Budget, Number}; -use super::nanvalue::NanView; +use super::nanvalue::{NanValue, NanView}; // ── InlineString ────────────────────────────────────────────────────────────── @@ -280,17 +281,30 @@ impl Eq for Object {} #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub enum NumTag { Int, Float } -#[derive(Clone, Debug)] -pub enum Value { - Null, - Bool(bool), - Int(i64), - Float(f64), - /// Overflow: BigInt or BigDecimal that doesn't fit in i64/f64. - BigNum(Box), - String(Box), - Array(Array), - Object(Object), +/// 8-byte NaN-boxed value with RAII lifecycle. +/// +/// All pattern matching goes through `val.view()` → `NanView<'_>`. +/// All construction goes through named constructors (`Value::int()`, etc.). +pub struct Value(pub(crate) NanValue); + +impl Clone for Value { + #[inline] + fn clone(&self) -> Self { + Value(self.0.clone_heap()) + } +} + +impl Drop for Value { + #[inline] + fn drop(&mut self) { + self.0.drop_heap(); + } +} + +impl fmt::Debug for Value { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Value({:?})", self.0) + } } /// Backward-compatible alias: `Value::Number(n)` is now split into @@ -376,50 +390,86 @@ impl PartialEq for Value { impl Eq for Value {} impl Value { + #[inline] pub fn null() -> Self { - Self::Null + Value(NanValue::null()) } + #[inline] pub fn bool(b: bool) -> Self { - Self::Bool(b) + Value(NanValue::bool(b)) } + #[inline] pub fn int(i: i64) -> Self { - Self::Int(i) + Value(NanValue::int(i)) } + #[inline] pub fn float(f: f64) -> Self { - Self::Float(f) + Value(NanValue::float(f)) } pub fn bignum(n: Number) -> Self { - Self::BigNum(Box::new(n)) + Value(NanValue::from_bignum(Box::new(n))) } pub fn from_string_val(s: String) -> Self { - Self::String(Box::new(s)) + Value(NanValue::from_string(Box::new(s))) } pub fn from_array(arr: Array) -> Self { - Self::Array(arr) + Value(NanValue::from_array(arr.0)) } pub fn from_object(obj: Object) -> Self { - Self::Object(obj) + Value(NanValue::from_object(obj.0)) } /// Zero-cost borrowed view for pattern matching. - pub fn view(&self) -> super::nanvalue::NanView<'_> { - use super::nanvalue::NanView; - match self { - Self::Null => NanView::Null, - Self::Bool(b) => NanView::Bool(*b), - Self::Int(i) => NanView::Int(*i), - Self::Float(f) => NanView::Float(*f), - Self::BigNum(n) => NanView::BigNum(n), - Self::String(s) => NanView::String(s), - Self::Array(arr) => NanView::Array(&arr.0), - Self::Object(obj) => NanView::Object(&obj.0), + #[inline] + pub fn view(&self) -> NanView<'_> { + self.0.view() + } + + /// Extract the inner NanValue, consuming this Value without running Drop. + #[inline] + pub(crate) fn into_nan(self) -> NanValue { + let nv = self.0; + mem::forget(self); + nv + } + + /// Copy the inner NanValue bits (no refcount bump — only safe for inline types + /// or when the caller manages the lifetime). + #[inline] + pub(crate) fn as_nan(&self) -> NanValue { + self.0 + } + + /// Extract an owned Array from this Value if it is an array. + /// Clones the inner Rc (bumps refcount). + pub fn as_array(&self) -> Option { + match self.view() { + NanView::Array(ha) => { + let ptr = ha as *const HeapArr; + unsafe { Rc::increment_strong_count(ptr) }; + Some(Array(unsafe { Rc::from_raw(ptr) })) + } + _ => None, + } + } + + /// Extract an owned Object from this Value if it is an object. + /// Clones the inner Rc (bumps refcount). + pub fn as_object(&self) -> Option { + match self.view() { + NanView::Object(ho) => { + let ptr = ho as *const HeapObj; + unsafe { Rc::increment_strong_count(ptr) }; + Some(Object(unsafe { Rc::from_raw(ptr) })) + } + _ => None, } } @@ -429,20 +479,20 @@ impl Value { } pub fn string(s: &str, budget: &Budget) -> crate::Result { - String::new(s, budget).map(|s| Value::String(Box::new(s))) + String::new(s, budget).map(|s| Value(NanValue::from_string(Box::new(s)))) } pub fn array(items: Vec, budget: &Budget) -> crate::Result { - Array::new(items, budget).map(Value::Array) + Array::new(items, budget).map(Value::from_array) } pub fn object(pairs: Vec<(String, Self)>, budget: &Budget) -> crate::Result { - Object::new(pairs, budget).map(Value::Object) + Object::new(pairs, budget).map(Value::from_object) } pub fn call(key: &str, arg: Self, budget: &Budget) -> crate::Result { let key_str = String::new(key, budget)?; - Object::new(vec![(key_str, arg)], budget).map(Value::Object) + Object::new(vec![(key_str, arg)], budget).map(Value::from_object) } /// View a value as a single-key call (procedure call form). @@ -1030,9 +1080,8 @@ mod tests { fn value_size() { let size = std::mem::size_of::(); eprintln!(" size_of::() = {} bytes", size); - // Boxed String: all heap types are 8-byte pointers + discriminant - // Should be 16 bytes on 64-bit platforms - assert!(size <= 16, "Value is {size} bytes, expected ≤16"); + // NaN-boxed: Value(NanValue) = 8 bytes + assert_eq!(size, 8, "Value is {size} bytes, expected 8"); } #[test] diff --git a/src/runtime/nanvalue.rs b/src/runtime/nanvalue.rs index dbc4ebc..efbc568 100644 --- a/src/runtime/nanvalue.rs +++ b/src/runtime/nanvalue.rs @@ -341,106 +341,24 @@ impl NanValue { // ── Value conversion ──────────────────────────────────────────── - /// Convert a Value into a NanValue, consuming the Value. + /// Convert a Value into a NanValue, consuming the Value without running Drop. + #[inline] pub fn from_value(val: Value) -> Self { - match val { - Value::Null => Self::null(), - Value::Bool(b) => Self::bool(b), - Value::Int(i) => Self::int(i), - Value::Float(f) => Self::float(f), - Value::BigNum(n) => Self::from_bignum(n), - Value::String(s) => Self::from_string(s), - Value::Array(arr) => Self::from_array(arr.0), - Value::Object(obj) => Self::from_object(obj.0), - } + val.into_nan() } /// Convert to Value by cloning (NanValue retains its reference). + #[inline] pub fn to_value_cloning(self) -> Value { - let t = self.tag(); - if t < TAG_FLOAT_MAX { - return Value::Float(f64::from_bits(self.0)); - } - match t { - TAG_INT => { - let raw = self.0 & I48_MASK; - let i = if raw & SIGN_BIT != 0 { - (raw | SIGN_EXT) as i64 - } else { - raw as i64 - }; - Value::Int(i) - } - TAG_CONST => match self.0 { - CONST_NULL => Value::Null, - CONST_FALSE => Value::Bool(false), - CONST_TRUE => Value::Bool(true), - _ => Value::Null, // unreachable in practice - }, - TAG_BIGNUM => unsafe { - let ptr = (self.0 & PTR_MASK) as *const Number; - Value::BigNum(Box::new((*ptr).clone())) - }, - TAG_STRING => unsafe { - let ptr = (self.0 & PTR_MASK) as *const RStr; - Value::String(Box::new((*ptr).clone())) - }, - TAG_ARRAY => unsafe { - let ptr = (self.0 & PTR_MASK) as *const HeapArr; - Rc::increment_strong_count(ptr); - Value::Array(crate::runtime::data::Array(Rc::from_raw(ptr))) - }, - TAG_OBJECT => unsafe { - let ptr = (self.0 & PTR_MASK) as *const HeapObj; - Rc::increment_strong_count(ptr); - Value::Object(crate::runtime::data::Object(Rc::from_raw(ptr))) - }, - _ => Value::Null, // EMPTY → Null (shouldn't happen) - } + Value(self.clone_heap()) } /// Convert to Value by consuming (transfers ownership, no Rc bump). /// After this call, the NanValue's bits are invalidated — do NOT call /// drop_heap() on it. + #[inline] pub fn into_value(self) -> Value { - let t = self.tag(); - if t < TAG_FLOAT_MAX { - return Value::Float(f64::from_bits(self.0)); - } - match t { - TAG_INT => { - let raw = self.0 & I48_MASK; - let i = if raw & SIGN_BIT != 0 { - (raw | SIGN_EXT) as i64 - } else { - raw as i64 - }; - Value::Int(i) - } - TAG_CONST => match self.0 { - CONST_NULL => Value::Null, - CONST_FALSE => Value::Bool(false), - CONST_TRUE => Value::Bool(true), - _ => Value::Null, - }, - TAG_BIGNUM => unsafe { - let ptr = (self.0 & PTR_MASK) as *mut Number; - Value::BigNum(Box::from_raw(ptr)) - }, - TAG_STRING => unsafe { - let ptr = (self.0 & PTR_MASK) as *mut RStr; - Value::String(Box::from_raw(ptr)) - }, - TAG_ARRAY => unsafe { - let ptr = (self.0 & PTR_MASK) as *const HeapArr; - Value::Array(crate::runtime::data::Array(Rc::from_raw(ptr))) - }, - TAG_OBJECT => unsafe { - let ptr = (self.0 & PTR_MASK) as *const HeapObj; - Value::Object(crate::runtime::data::Object(Rc::from_raw(ptr))) - }, - _ => Value::Null, - } + Value(self) } } @@ -491,23 +409,23 @@ mod tests { let nv = NanValue::null(); assert!(!nv.is_empty()); let val = nv.to_value_cloning(); - assert!(matches!(val, Value::Null)); + assert!(matches!(val.view(), NanView::Null)); } #[test] fn bool_roundtrip() { let t = NanValue::bool(true); let f = NanValue::bool(false); - assert!(matches!(t.to_value_cloning(), Value::Bool(true))); - assert!(matches!(f.to_value_cloning(), Value::Bool(false))); + assert!(matches!(t.to_value_cloning().view(), NanView::Bool(true))); + assert!(matches!(f.to_value_cloning().view(), NanView::Bool(false))); } #[test] fn int_small_roundtrip() { for i in [0i64, 1, -1, 42, -42, 1000, -1000] { let nv = NanValue::int(i); - match nv.to_value_cloning() { - Value::Int(v) => assert_eq!(v, i, "int roundtrip failed for {}", i), + match nv.to_value_cloning().view() { + NanView::Int(v) => assert_eq!(v, i, "int roundtrip failed for {}", i), other => panic!("expected Int({}), got {:?}", i, other), } } @@ -518,25 +436,22 @@ mod tests { // i48 max/min let max48 = I48_MAX; let min48 = I48_MIN; - assert!(matches!(NanValue::int(max48).to_value_cloning(), Value::Int(v) if v == max48)); - assert!(matches!(NanValue::int(min48).to_value_cloning(), Value::Int(v) if v == min48)); + assert!(matches!(NanValue::int(max48).to_value_cloning().view(), NanView::Int(v) if v == max48)); + assert!(matches!(NanValue::int(min48).to_value_cloning().view(), NanView::Int(v) if v == min48)); // Just beyond i48 range → BigNum let overflow = max48 + 1; let nv = NanValue::int(overflow); let val = nv.into_value(); - match &val { - Value::BigNum(_) => {} // correct — promoted to heap - other => panic!("expected BigNum for {}, got {:?}", overflow, other), - } + assert!(matches!(val.view(), NanView::BigNum(_))); } #[test] fn float_roundtrip() { for f in [0.0f64, -0.0, 1.0, -1.0, std::f64::consts::PI, f64::INFINITY, f64::NEG_INFINITY] { let nv = NanValue::float(f); - match nv.to_value_cloning() { - Value::Float(v) => assert_eq!(v.to_bits(), f.to_bits(), "float roundtrip failed for {}", f), + match nv.to_value_cloning().view() { + NanView::Float(v) => assert_eq!(v.to_bits(), f.to_bits(), "float roundtrip failed for {}", f), other => panic!("expected Float({}), got {:?}", f, other), } } @@ -545,8 +460,8 @@ mod tests { #[test] fn nan_canonicalization() { let nv = NanValue::float(f64::NAN); - match nv.to_value_cloning() { - Value::Float(v) => assert!(v.is_nan()), + match nv.to_value_cloning().view() { + NanView::Float(v) => assert!(v.is_nan()), other => panic!("expected Float(NaN), got {:?}", other), } } @@ -580,14 +495,14 @@ mod tests { fn value_to_nanvalue_roundtrip() { let budget = Budget::new(4096); let cases = vec![ - Value::Null, - Value::Bool(true), - Value::Bool(false), - Value::Int(42), - Value::Int(-1), - Value::Float(3.14), + Value::null(), + Value::bool(true), + Value::bool(false), + Value::int(42), + Value::int(-1), + Value::float(3.14), Value::string("hello", &budget).unwrap(), - Value::array(vec![Value::Int(1), Value::Int(2)], &budget).unwrap(), + Value::array(vec![Value::int(1), Value::int(2)], &budget).unwrap(), ]; for val in cases { @@ -603,7 +518,7 @@ mod tests { #[test] fn array_clone_drop() { let budget = Budget::new(4096); - let arr = Value::array(vec![Value::Int(1)], &budget).unwrap(); + let arr = Value::array(vec![Value::int(1)], &budget).unwrap(); let nv = NanValue::from_value(arr); // Clone should bump refcount @@ -621,19 +536,17 @@ mod tests { #[test] fn object_proc_identity() { let budget = Budget::new(4096); - let obj = Value::call("test", Value::Int(1), &budget).unwrap(); - - // Get identity from Value - let val_id = match &obj { - Value::Object(o) => o.rc_identity(), - _ => 0, - }; + let obj = Value::call("test", Value::int(1), &budget).unwrap(); - let nv = NanValue::from_value(obj); + // Get identity via NanValue — should be the HeapObj pointer + let nv = obj.as_nan(); let nan_id = nv.proc_identity(); + assert!(nan_id != 0, "expected non-zero proc identity for object"); - assert_eq!(val_id, nan_id, "proc identity mismatch"); - nv.drop_heap(); + // Clone should have same identity (Rc pointer) + let obj2 = obj.clone(); + let nv2 = obj2.as_nan(); + assert_eq!(nv2.proc_identity(), nan_id, "clone should have same proc identity"); } // ── NanView tests ────────────────────────────────────────────── @@ -651,7 +564,7 @@ mod tests { #[test] fn view_array_no_refcount_bump() { let budget = Budget::new(4096); - let arr = Value::array(vec![Value::Int(10), Value::Int(20)], &budget).unwrap(); + let arr = Value::array(vec![Value::int(10), Value::int(20)], &budget).unwrap(); let nv = NanValue::from_value(arr); // view() should give us a reference to the array data without cloning diff --git a/src/runtime/procs/array.rs b/src/runtime/procs/array.rs index b94f9b3..8bf2642 100644 --- a/src/runtime/procs/array.rs +++ b/src/runtime/procs/array.rs @@ -5,9 +5,9 @@ use crate::{Error, Result}; /// Extract an array from a pre-evaluated arg, or return a type error. fn require_array(arg: &Value, proc: &'static str, index: usize) -> Result { - match arg { - Value::Array(arr) => Ok(arr.clone()), - other => Err(Error::type_err(proc, index, other.clone(), "an array")), + match arg.as_array() { + Some(arr) => Ok(arr), + None => Err(Error::type_err(proc, index, arg.clone(), "an array")), } } diff --git a/src/runtime/procs/mod.rs b/src/runtime/procs/mod.rs index 3094089..2980c1e 100644 --- a/src/runtime/procs/mod.rs +++ b/src/runtime/procs/mod.rs @@ -238,9 +238,9 @@ pub(crate) fn call_mu( caller_scope: Scope, ctx: Option<&crate::compile::IrContext>, ) -> crate::Result { - let items = match mu_arg { - Value::Array(arr) => arr, - _ => return Err(crate::Error::MalformedFnValue( + let items = match mu_arg.as_array() { + Some(arr) => arr, + None => return Err(crate::Error::MalformedFnValue( "mu value is not an array".to_string(), )), };