/*! * VM Value Operations * * Purpose: Arithmetic/logical/comparison helpers and boolean coercions * Responsibilities: execute_binary_op / execute_unary_op / execute_compare_op * Key APIs: execute_binary_op, execute_unary_op, execute_compare_op * Typical Callers: vm_instructions::{execute_binop, execute_unaryop, execute_compare} */ use super::vm::VM; use super::vm_types::{VMError, VMValue}; use crate::mir::{BinaryOp, CompareOp, UnaryOp}; impl VM { /// Try to view a BoxRef as a UTF-8 string using unified semantics fn try_boxref_to_string(&self, b: &dyn crate::box_trait::NyashBox) -> Option { // Avoid recursion via PluginHost<->Loader for PluginBoxV2 during VM add/string ops if b.as_any() .downcast_ref::() .is_some() { return None; } crate::runtime::semantics::coerce_to_string(b) } /// Execute binary operation pub(super) fn execute_binary_op( &self, op: &BinaryOp, left: &VMValue, right: &VMValue, ) -> Result { let debug_bin = std::env::var("NYASH_VM_DEBUG_BIN").ok().as_deref() == Some("1"); if debug_bin { eprintln!("[VM] binop {:?} {:?} {:?}", op, left, right); } if std::env::var("NYASH_GRAMMAR_DIFF").ok().as_deref() == Some("1") { let lty = match left { VMValue::String(_) => "String", VMValue::Integer(_) => "Integer", VMValue::Float(_) => "Float", VMValue::Bool(_) => "Bool", _ => "Other", }; let rty = match right { VMValue::String(_) => "String", VMValue::Integer(_) => "Integer", VMValue::Float(_) => "Float", VMValue::Bool(_) => "Bool", _ => "Other", }; match *op { BinaryOp::Add => { let strat = crate::grammar::engine::get().add_coercion_strategy(); let rule = crate::grammar::engine::get().decide_add_result(lty, rty); eprintln!( "[GRAMMAR-DIFF][VM] add.coercion_strategy={} left={} right={} rule={:?}", strat, lty, rty, rule ); } BinaryOp::Sub => { let strat = crate::grammar::engine::get().sub_coercion_strategy(); let rule = crate::grammar::engine::get().decide_sub_result(lty, rty); eprintln!( "[GRAMMAR-DIFF][VM] sub.coercion_strategy={} left={} right={} rule={:?}", strat, lty, rty, rule ); } BinaryOp::Mul => { let strat = crate::grammar::engine::get().mul_coercion_strategy(); let rule = crate::grammar::engine::get().decide_mul_result(lty, rty); eprintln!( "[GRAMMAR-DIFF][VM] mul.coercion_strategy={} left={} right={} rule={:?}", strat, lty, rty, rule ); } BinaryOp::Div => { let strat = crate::grammar::engine::get().div_coercion_strategy(); let rule = crate::grammar::engine::get().decide_div_result(lty, rty); eprintln!( "[GRAMMAR-DIFF][VM] div.coercion_strategy={} left={} right={} rule={:?}", strat, lty, rty, rule ); } _ => {} } } if matches!(*op, BinaryOp::Add) && std::env::var("NYASH_GRAMMAR_ENFORCE_ADD").ok().as_deref() == Some("1") { let lty = match left { VMValue::String(_) => "String", VMValue::Integer(_) => "Integer", VMValue::Float(_) => "Float", VMValue::Bool(_) => "Bool", _ => "Other", }; let rty = match right { VMValue::String(_) => "String", VMValue::Integer(_) => "Integer", VMValue::Float(_) => "Float", VMValue::Bool(_) => "Bool", _ => "Other", }; if let Some((res, _)) = crate::grammar::engine::get().decide_add_result(lty, rty) { match res { "String" => { // Best-effort toString concat fn vmv_to_string(v: &VMValue) -> String { match v { VMValue::String(s) => s.clone(), VMValue::Integer(i) => i.to_string(), VMValue::Float(f) => f.to_string(), VMValue::Bool(b) => b.to_string(), VMValue::Void => "void".to_string(), VMValue::BoxRef(b) => b.to_string_box().value, VMValue::Future(_) => "".to_string(), } } let ls = vmv_to_string(left); let rs = vmv_to_string(right); return Ok(VMValue::String(format!("{}{}", ls, rs))); } "Integer" => { if let (VMValue::Integer(l), VMValue::Integer(r)) = (left, right) { return Ok(VMValue::Integer(l + r)); } } _ => {} } } } // Fast path: logical AND/OR accept any truthy via as_bool if matches!(*op, BinaryOp::And | BinaryOp::Or) { let l = left.as_bool()?; let r = right.as_bool()?; return Ok(VMValue::Bool(match *op { BinaryOp::And => l && r, BinaryOp::Or => l || r, _ => unreachable!(), })); } match (left, right) { (VMValue::Integer(l), VMValue::Integer(r)) => { let result = match op { BinaryOp::Add => *l + *r, BinaryOp::Sub => *l - *r, BinaryOp::Mul => *l * *r, BinaryOp::Div => { if *r == 0 { return Err(VMError::DivisionByZero); } *l / *r } _ => { return Err(VMError::InvalidInstruction(format!( "Unsupported integer operation: {:?}", op ))) } }; Ok(VMValue::Integer(result)) } (VMValue::String(l), VMValue::Integer(r)) => match op { BinaryOp::Add => Ok(VMValue::String(format!("{}{}", l, r))), _ => Err(VMError::TypeError( "String-integer operations only support addition".to_string(), )), }, (VMValue::String(l), VMValue::Bool(r)) => match op { BinaryOp::Add => Ok(VMValue::String(format!("{}{}", l, r))), _ => Err(VMError::TypeError( "String-bool operations only support addition".to_string(), )), }, (VMValue::String(l), VMValue::String(r)) => match op { BinaryOp::Add => Ok(VMValue::String(format!("{}{}", l, r))), _ => Err(VMError::TypeError( "String operations only support addition".to_string(), )), }, // String + BoxRef concatenation (VMValue::String(l), VMValue::BoxRef(r)) => { let rs = self .try_boxref_to_string(r.as_ref()) .unwrap_or_else(|| r.to_string_box().value); match op { BinaryOp::Add => Ok(VMValue::String(format!("{}{}", l, rs))), _ => Err(VMError::TypeError( "String-BoxRef operations only support addition".to_string(), )), } } // BoxRef + String concatenation (VMValue::BoxRef(l), VMValue::String(r)) => { let ls = self .try_boxref_to_string(l.as_ref()) .unwrap_or_else(|| l.to_string_box().value); match op { BinaryOp::Add => Ok(VMValue::String(format!("{}{}", ls, r))), _ => Err(VMError::TypeError( "BoxRef-String operations only support addition".to_string(), )), } } // Arithmetic with BoxRef(IntegerBox) — support both legacy and new IntegerBox (VMValue::BoxRef(li), VMValue::BoxRef(ri)) if { li.as_any() .downcast_ref::() .is_some() || li .as_any() .downcast_ref::() .is_some() } && { ri.as_any() .downcast_ref::() .is_some() || ri .as_any() .downcast_ref::() .is_some() } => { let l = li .as_any() .downcast_ref::() .map(|x| x.value) .or_else(|| { li.as_any() .downcast_ref::() .map(|x| x.value) }) .unwrap(); let r = ri .as_any() .downcast_ref::() .map(|x| x.value) .or_else(|| { ri.as_any() .downcast_ref::() .map(|x| x.value) }) .unwrap(); let res = match op { BinaryOp::Add => l + r, BinaryOp::Sub => l - r, BinaryOp::Mul => l * r, BinaryOp::Div => { if r == 0 { return Err(VMError::DivisionByZero); } l / r } _ => { return Err(VMError::InvalidInstruction(format!( "Unsupported integer BoxRef operation: {:?}", op ))) } }; Ok(VMValue::Integer(res)) } // BoxRef + BoxRef string-like concatenation (VMValue::BoxRef(li), VMValue::BoxRef(ri)) => { if matches!(*op, BinaryOp::Add) { let ls = self .try_boxref_to_string(li.as_ref()) .unwrap_or_else(|| li.to_string_box().value); let rs = self .try_boxref_to_string(ri.as_ref()) .unwrap_or_else(|| ri.to_string_box().value); return Ok(VMValue::String(format!("{}{}", ls, rs))); } Err(VMError::TypeError( "Unsupported BoxRef+BoxRef operation".to_string(), )) } // Mixed Integer forms (VMValue::BoxRef(li), VMValue::Integer(r)) if li .as_any() .downcast_ref::() .is_some() || li .as_any() .downcast_ref::() .is_some() => { let l = li .as_any() .downcast_ref::() .map(|x| x.value) .or_else(|| { li.as_any() .downcast_ref::() .map(|x| x.value) }) .unwrap(); let res = match op { BinaryOp::Add => l + *r, BinaryOp::Sub => l - *r, BinaryOp::Mul => l * *r, BinaryOp::Div => { if *r == 0 { return Err(VMError::DivisionByZero); } l / *r } _ => { return Err(VMError::InvalidInstruction(format!( "Unsupported integer operation: {:?}", op ))) } }; Ok(VMValue::Integer(res)) } (VMValue::Integer(l), VMValue::BoxRef(ri)) if ri .as_any() .downcast_ref::() .is_some() || ri .as_any() .downcast_ref::() .is_some() => { let r = ri .as_any() .downcast_ref::() .map(|x| x.value) .or_else(|| { ri.as_any() .downcast_ref::() .map(|x| x.value) }) .unwrap(); let res = match op { BinaryOp::Add => *l + r, BinaryOp::Sub => *l - r, BinaryOp::Mul => *l * r, BinaryOp::Div => { if r == 0 { return Err(VMError::DivisionByZero); } *l / r } _ => { return Err(VMError::InvalidInstruction(format!( "Unsupported integer operation: {:?}", op ))) } }; Ok(VMValue::Integer(res)) } // 80/20 fallback: BoxRef(any) numeric via toString().parse::() (VMValue::BoxRef(lb), VMValue::BoxRef(rb)) => { let li = lb.to_string_box().value.trim().parse::().ok(); let ri = rb.to_string_box().value.trim().parse::().ok(); if let (Some(l), Some(r)) = (li, ri) { let res = match op { BinaryOp::Add => l + r, BinaryOp::Sub => l - r, BinaryOp::Mul => l * r, BinaryOp::Div => { if r == 0 { return Err(VMError::DivisionByZero); } l / r } _ => { return Err(VMError::InvalidInstruction(format!( "Unsupported integer operation: {:?}", op ))) } }; if debug_bin { eprintln!("[VM] binop fallback BoxRef-BoxRef -> {}", res); } Ok(VMValue::Integer(res)) } else { Err(VMError::TypeError(format!( "Unsupported binary operation: {:?} on {:?} and {:?}", op, left, right ))) } } (VMValue::BoxRef(lb), VMValue::Integer(r)) => { if let Ok(l) = lb.to_string_box().value.trim().parse::() { let res = match op { BinaryOp::Add => l + *r, BinaryOp::Sub => l - *r, BinaryOp::Mul => l * *r, BinaryOp::Div => { if *r == 0 { return Err(VMError::DivisionByZero); } l / *r } _ => { return Err(VMError::InvalidInstruction(format!( "Unsupported integer operation: {:?}", op ))) } }; if debug_bin { eprintln!("[VM] binop fallback BoxRef-Int -> {}", res); } Ok(VMValue::Integer(res)) } else { Err(VMError::TypeError(format!( "Unsupported binary operation: {:?} on {:?} and {:?}", op, left, right ))) } } (VMValue::Integer(l), VMValue::BoxRef(rb)) => { if let Ok(r) = rb.to_string_box().value.trim().parse::() { let res = match op { BinaryOp::Add => *l + r, BinaryOp::Sub => *l - r, BinaryOp::Mul => *l * r, BinaryOp::Div => { if r == 0 { return Err(VMError::DivisionByZero); } *l / r } _ => { return Err(VMError::InvalidInstruction(format!( "Unsupported integer operation: {:?}", op ))) } }; if debug_bin { eprintln!("[VM] binop fallback Int-BoxRef -> {}", res); } Ok(VMValue::Integer(res)) } else { Err(VMError::TypeError(format!( "Unsupported binary operation: {:?} on {:?} and {:?}", op, left, right ))) } } _ => Err(VMError::TypeError(format!( "Unsupported binary operation: {:?} on {:?} and {:?}", op, left, right ))), } } /// Execute unary operation pub(super) fn execute_unary_op( &self, op: &UnaryOp, operand: &VMValue, ) -> Result { match (op, operand) { (UnaryOp::Neg, VMValue::Integer(i)) => Ok(VMValue::Integer(-i)), (UnaryOp::Not, VMValue::Bool(b)) => Ok(VMValue::Bool(!b)), _ => Err(VMError::TypeError(format!( "Unsupported unary operation: {:?} on {:?}", op, operand ))), } } /// Execute comparison operation pub(super) fn execute_compare_op( &self, op: &CompareOp, left: &VMValue, right: &VMValue, ) -> Result { let debug_cmp = std::env::var("NYASH_VM_DEBUG").ok().as_deref() == Some("1") || std::env::var("NYASH_VM_DEBUG_CMP").ok().as_deref() == Some("1"); if debug_cmp { eprintln!( "[VM] execute_compare_op enter: op={:?}, left={:?}, right={:?}", op, left, right ); } match (left, right) { // Mixed numeric (VMValue::Integer(l), VMValue::Float(r)) => { let l = *l as f64; let r = *r; Ok(match op { CompareOp::Eq => l == r, CompareOp::Ne => l != r, CompareOp::Lt => l < r, CompareOp::Le => l <= r, CompareOp::Gt => l > r, CompareOp::Ge => l >= r, }) } (VMValue::Float(l), VMValue::Integer(r)) => { let l = *l; let r = *r as f64; Ok(match op { CompareOp::Eq => l == r, CompareOp::Ne => l != r, CompareOp::Lt => l < r, CompareOp::Le => l <= r, CompareOp::Gt => l > r, CompareOp::Ge => l >= r, }) } // Bool (VMValue::Bool(l), VMValue::Bool(r)) => Ok(match op { CompareOp::Eq => l == r, CompareOp::Ne => l != r, _ => { return Err(VMError::TypeError(format!( "Unsupported boolean comparison: {:?}", op ))) } }), // Void (VMValue::Void, VMValue::Void) => Ok(match op { CompareOp::Eq => true, CompareOp::Ne => false, _ => return Err(VMError::TypeError("Cannot order Void".to_string())), }), (VMValue::Void, _) | (_, VMValue::Void) => Ok(match op { CompareOp::Eq => false, CompareOp::Ne => true, _ => return Err(VMError::TypeError("Cannot order Void".to_string())), }), // Homogeneous (VMValue::Integer(l), VMValue::Integer(r)) => Ok(match op { CompareOp::Eq => l == r, CompareOp::Ne => l != r, CompareOp::Lt => l < r, CompareOp::Le => l <= r, CompareOp::Gt => l > r, CompareOp::Ge => l >= r, }), (VMValue::Float(l), VMValue::Float(r)) => Ok(match op { CompareOp::Eq => l == r, CompareOp::Ne => l != r, CompareOp::Lt => l < r, CompareOp::Le => l <= r, CompareOp::Gt => l > r, CompareOp::Ge => l >= r, }), (VMValue::String(l), VMValue::String(r)) => Ok(match op { CompareOp::Eq => l == r, CompareOp::Ne => l != r, CompareOp::Lt => l < r, CompareOp::Le => l <= r, CompareOp::Gt => l > r, CompareOp::Ge => l >= r, }), // BoxRef(IntegerBox) comparisons (homogeneous) (VMValue::BoxRef(li), VMValue::BoxRef(ri)) => { if std::env::var("NYASH_VM_DEBUG_CMP").ok().as_deref() == Some("1") { eprintln!( "[VM] arm BoxRef-BoxRef: lt={}, rt={}", li.type_name(), ri.type_name() ); } if std::env::var("NYASH_VM_DEBUG_CMP").ok().as_deref() == Some("1") { eprintln!( "[VM] compare BoxRef vs BoxRef: left_type={}, right_type={}, left_str={}, right_str={}", li.type_name(), ri.type_name(), li.to_string_box().value, ri.to_string_box().value ); } // String-like comparison: internal StringBox or Plugin StringBox fn boxref_to_string(b: &dyn crate::box_trait::NyashBox) -> Option { if let Some(sb) = b.as_any().downcast_ref::() { return Some(sb.value.clone()); } if let Some(pb) = b .as_any() .downcast_ref::() { if pb.box_type == "StringBox" { let host = crate::runtime::get_global_plugin_host(); let s_opt: Option = { if let Ok(ro) = host.read() { if let Ok(val_opt) = ro.invoke_instance_method( "StringBox", "toUtf8", pb.inner.instance_id, &[], ) { if let Some(vb) = val_opt { if let Some(sbb) = vb.as_any() .downcast_ref::() { Some(sbb.value.clone()) } else { None } } else { None } } else { None } } else { None } }; if s_opt.is_some() { return s_opt; } } } None } if let (Some(ls), Some(rs)) = (boxref_to_string(li.as_ref()), boxref_to_string(ri.as_ref())) { return Ok(match op { CompareOp::Eq => ls == rs, CompareOp::Ne => ls != rs, CompareOp::Lt => ls < rs, CompareOp::Le => ls <= rs, CompareOp::Gt => ls > rs, CompareOp::Ge => ls >= rs, }); } // Try integer comparisons via downcast or parse fallback let l_opt = li .as_any() .downcast_ref::() .map(|x| x.value) .or_else(|| { li.as_any() .downcast_ref::() .map(|x| x.value) }) .or_else(|| li.to_string_box().value.parse::().ok()); let r_opt = ri .as_any() .downcast_ref::() .map(|x| x.value) .or_else(|| { ri.as_any() .downcast_ref::() .map(|x| x.value) }) .or_else(|| ri.to_string_box().value.parse::().ok()); if let (Some(l), Some(r)) = (l_opt, r_opt) { return Ok(match op { CompareOp::Eq => l == r, CompareOp::Ne => l != r, CompareOp::Lt => l < r, CompareOp::Le => l <= r, CompareOp::Gt => l > r, CompareOp::Ge => l >= r, }); } Err(VMError::TypeError(format!( "[BoxRef-BoxRef] Unsupported comparison: {:?} on {:?} and {:?}", op, left, right ))) } // Mixed String vs BoxRef (string-like) (VMValue::String(ls), VMValue::BoxRef(ri)) => { let rs_opt = if let Some(sb) = ri.as_any().downcast_ref::() { Some(sb.value.clone()) } else { if let Some(pb) = ri .as_any() .downcast_ref::() { if pb.box_type == "StringBox" { let host = crate::runtime::get_global_plugin_host(); let tmp = if let Ok(ro) = host.read() { if let Ok(val_opt) = ro.invoke_instance_method( "StringBox", "toUtf8", pb.inner.instance_id, &[], ) { if let Some(vb) = val_opt { if let Some(sbb) = vb.as_any() .downcast_ref::() { Some(sbb.value.clone()) } else { None } } else { None } } else { None } } else { None }; tmp } else { None } } else { None } }; if let Some(rs) = rs_opt { return Ok(match op { CompareOp::Eq => *ls == rs, CompareOp::Ne => *ls != rs, CompareOp::Lt => *ls < rs, CompareOp::Le => *ls <= rs, CompareOp::Gt => *ls > rs, CompareOp::Ge => *ls >= rs, }); } Err(VMError::TypeError(format!( "[String-BoxRef] Unsupported comparison: {:?} on {:?} and {:?}", op, left, right ))) } (VMValue::BoxRef(li), VMValue::String(rs)) => { let ls_opt = if let Some(sb) = li.as_any().downcast_ref::() { Some(sb.value.clone()) } else { if let Some(pb) = li .as_any() .downcast_ref::() { if pb.box_type == "StringBox" { let host = crate::runtime::get_global_plugin_host(); let tmp = if let Ok(ro) = host.read() { if let Ok(val_opt) = ro.invoke_instance_method( "StringBox", "toUtf8", pb.inner.instance_id, &[], ) { if let Some(vb) = val_opt { if let Some(sbb) = vb.as_any() .downcast_ref::() { Some(sbb.value.clone()) } else { None } } else { None } } else { None } } else { None }; tmp } else { None } } else { None } }; if let Some(ls) = ls_opt { return Ok(match op { CompareOp::Eq => ls == *rs, CompareOp::Ne => ls != *rs, CompareOp::Lt => ls < *rs, CompareOp::Le => ls <= *rs, CompareOp::Gt => ls > *rs, CompareOp::Ge => ls >= *rs, }); } Err(VMError::TypeError(format!( "[BoxRef-String] Unsupported comparison: {:?} on {:?} and {:?}", op, left, right ))) } // Mixed Integer (BoxRef vs Integer) (VMValue::BoxRef(li), VMValue::Integer(r)) => { let l_opt = li .as_any() .downcast_ref::() .map(|x| x.value) .or_else(|| { li.as_any() .downcast_ref::() .map(|x| x.value) }) .or_else(|| li.to_string_box().value.parse::().ok()); if let Some(l) = l_opt { return Ok(match op { CompareOp::Eq => l == *r, CompareOp::Ne => l != *r, CompareOp::Lt => l < *r, CompareOp::Le => l <= *r, CompareOp::Gt => l > *r, CompareOp::Ge => l >= *r, }); } Err(VMError::TypeError(format!( "[BoxRef-Integer] Unsupported comparison: {:?} on {:?} and {:?}", op, left, right ))) } (VMValue::Integer(l), VMValue::BoxRef(ri)) => { let r_opt = ri .as_any() .downcast_ref::() .map(|x| x.value) .or_else(|| { ri.as_any() .downcast_ref::() .map(|x| x.value) }) .or_else(|| ri.to_string_box().value.parse::().ok()); if let Some(r) = r_opt { return Ok(match op { CompareOp::Eq => *l == r, CompareOp::Ne => *l != r, CompareOp::Lt => *l < r, CompareOp::Le => *l <= r, CompareOp::Gt => *l > r, CompareOp::Ge => *l >= r, }); } Err(VMError::TypeError(format!( "[Integer-BoxRef] Unsupported comparison: {:?} on {:?} and {:?}", op, left, right ))) } _ => { // 80/20 numeric fallback: coerce via to_string when possible fn to_i64(v: &VMValue) -> Option { match v { VMValue::Integer(i) => Some(*i), VMValue::Bool(b) => Some(if *b { 1 } else { 0 }), VMValue::String(s) => s.trim().parse::().ok(), VMValue::Float(f) => Some(*f as i64), VMValue::BoxRef(b) => b.to_string_box().value.trim().parse::().ok(), _ => None, } } if let (Some(l), Some(r)) = (to_i64(left), to_i64(right)) { return Ok(match op { CompareOp::Eq => l == r, CompareOp::Ne => l != r, CompareOp::Lt => l < r, CompareOp::Le => l <= r, CompareOp::Gt => l > r, CompareOp::Ge => l >= r, }); } if std::env::var("NYASH_VM_DEBUG_CMP").ok().as_deref() == Some("1") { let lty = match left { VMValue::BoxRef(b) => format!("BoxRef({})", b.type_name()), other => format!("{:?}", other), }; let rty = match right { VMValue::BoxRef(b) => format!("BoxRef({})", b.type_name()), other => format!("{:?}", other), }; eprintln!( "[VM] compare default arm: op={:?}, left={}, right={}", op, lty, rty ); } Err(VMError::TypeError(format!( "[Default] Unsupported comparison: {:?} on {:?} and {:?}", op, left, right ))) } } } }