diff --git a/tests/e2e_plugin_interop.rs b/tests/e2e_plugin_interop.rs new file mode 100644 index 00000000..e8dfcb4e --- /dev/null +++ b/tests/e2e_plugin_interop.rs @@ -0,0 +1,66 @@ +//! E2E: Interop between builtin and mock plugin boxes via MapBox storage +use std::sync::Arc; + +use nyash_rust::box_factory::BoxFactory; +use nyash_rust::box_factory::builtin::BuiltinGroups; +use nyash_rust::interpreter::{NyashInterpreter, RuntimeError}; +use nyash_rust::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox}; + +#[derive(Debug, Clone)] +struct EchoBox { base: BoxBase, msg: String } +impl EchoBox { fn new(msg: String) -> Self { Self { base: BoxBase::new(), msg } } } + +impl BoxCore for EchoBox { + fn box_id(&self) -> u64 { self.base.id } + fn parent_type_id(&self) -> Option { None } + fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "EchoBox(\"{}\")", self.msg) } + fn as_any(&self) -> &dyn std::any::Any { self } + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self } +} + +impl NyashBox for EchoBox { + fn to_string_box(&self) -> StringBox { StringBox::new(self.msg.clone()) } + fn equals(&self, other: &dyn NyashBox) -> BoolBox { + if let Some(e) = other.as_any().downcast_ref::() { BoolBox::new(self.msg == e.msg) } else { BoolBox::new(false) } + } + fn type_name(&self) -> &'static str { "EchoBox" } + fn clone_box(&self) -> Box { Box::new(self.clone()) } + fn share_box(&self) -> Box { Box::new(self.clone()) } +} + +struct TestPluginFactory; +impl TestPluginFactory { fn new() -> Self { Self } } +impl BoxFactory for TestPluginFactory { + fn create_box(&self, name: &str, args: &[Box]) -> Result, RuntimeError> { + match name { + "EchoBox" => { + let msg = args.get(0).map(|a| a.to_string_box().value).unwrap_or_else(|| "".to_string()); + Ok(Box::new(EchoBox::new(msg))) + } + _ => Err(RuntimeError::InvalidOperation{ message: format!("Unknown Box type: {}", name) }) + } + } + fn box_types(&self) -> Vec<&str> { vec!["EchoBox"] } +} + +fn new_interpreter_with_factory() -> NyashInterpreter { + let mut i = NyashInterpreter::new_with_groups(BuiltinGroups::native_full()); + i.register_box_factory(Arc::new(TestPluginFactory::new())); + i +} + +#[test] +fn e2e_interop_mapbox_store_plugin_box() { + let mut i = new_interpreter_with_factory(); + let code = r#" + m = new MapBox() + e = new EchoBox("ok") + m.set("k", e) + v = m.get("k") + v + "#; + let ast = nyash_rust::parser::NyashParser::parse_from_string(code).expect("parse ok"); + let result = i.execute(ast).expect("exec ok"); + assert_eq!(result.to_string_box().value, "ok"); +} + diff --git a/tests/vm_e2e.rs b/tests/vm_e2e.rs new file mode 100644 index 00000000..4f4a28e0 --- /dev/null +++ b/tests/vm_e2e.rs @@ -0,0 +1,86 @@ +//! VM E2E: Compile Nyash to MIR and execute via VM, with mock plugin factory +use std::sync::Arc; + +use nyash_rust::box_factory::BoxFactory; +use nyash_rust::box_factory::builtin::BuiltinGroups; +use nyash_rust::runtime::NyashRuntimeBuilder; +use nyash_rust::parser::NyashParser; +use nyash_rust::mir::MirCompiler; +use nyash_rust::backend::VM; +use nyash_rust::interpreter::RuntimeError; +use nyash_rust::box_trait::{NyashBox, BoxCore, BoxBase, StringBox, BoolBox}; + +// Minimal AdderBox to validate plugin factory path under VM +#[derive(Debug, Clone)] +struct AdderBox { base: BoxBase, sum: i64 } +impl AdderBox { fn new(a: i64, b: i64) -> Self { Self { base: BoxBase::new(), sum: a + b } } } + +impl BoxCore for AdderBox { + fn box_id(&self) -> u64 { self.base.id } + fn parent_type_id(&self) -> Option { None } + fn fmt_box(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "AdderBox(sum={})", self.sum) } + fn as_any(&self) -> &dyn std::any::Any { self } + fn as_any_mut(&mut self) -> &mut dyn std::any::Any { self } +} + +impl NyashBox for AdderBox { + fn to_string_box(&self) -> StringBox { StringBox::new(self.sum.to_string()) } + fn equals(&self, other: &dyn NyashBox) -> BoolBox { + if let Some(a) = other.as_any().downcast_ref::() { BoolBox::new(self.sum == a.sum) } else { BoolBox::new(false) } + } + fn type_name(&self) -> &'static str { "AdderBox" } + fn clone_box(&self) -> Box { Box::new(self.clone()) } + fn share_box(&self) -> Box { Box::new(self.clone()) } +} + +struct TestPluginFactory; +impl TestPluginFactory { fn new() -> Self { Self } } +impl BoxFactory for TestPluginFactory { + fn create_box(&self, name: &str, args: &[Box]) -> Result, RuntimeError> { + match name { + "AdderBox" => { + if args.len() != 2 { return Err(RuntimeError::InvalidOperation{ message: format!("AdderBox expects 2 args, got {}", args.len()) }); } + let a = args[0].to_string_box().value.parse::().map_err(|_| RuntimeError::TypeError{ message: "AdderBox arg a must be int".into() })?; + let b = args[1].to_string_box().value.parse::().map_err(|_| RuntimeError::TypeError{ message: "AdderBox arg b must be int".into() })?; + Ok(Box::new(AdderBox::new(a, b))) + } + _ => Err(RuntimeError::InvalidOperation{ message: format!("Unknown Box type: {}", name) }) + } + } + fn box_types(&self) -> Vec<&str> { vec!["AdderBox"] } +} + +#[test] +fn vm_e2e_adder_box() { + // Build runtime with builtin + user-defined + mock plugin factory + let runtime = NyashRuntimeBuilder::new() + .with_builtin_groups(BuiltinGroups::native_full()) + .with_factory(Arc::new(nyash_rust::box_factory::user_defined::UserDefinedBoxFactory::new( + nyash_rust::interpreter::SharedState::new(), + ))) + .with_factory(Arc::new(TestPluginFactory::new())) + .build(); + + // Nyash code: construct AdderBox and leave it as final expression + let code = r#" + a = new AdderBox(10, 32) + a + "#; + + // Parse → MIR + let ast = NyashParser::parse_from_string(code).expect("parse ok"); + let mut mir_compiler = MirCompiler::new(); + let compile_result = mir_compiler.compile(ast).expect("mir ok"); + + // Execute via VM using the prepared runtime + let mut vm = VM::with_runtime(runtime); + let result = vm.execute_module(&compile_result.module).expect("vm exec ok"); + + // The VM returns an Option> or a value; we print/debug and check string form + // Here we rely on Display via to_string_box through Debug format + // Try to format the result if available + // For this implementation, result is a generic value; we check debug string contains 42 or to_string equivalent. + let s = format!("{:?}", result); + assert!(s.contains("42") || s.contains("AdderBox"), "unexpected VM result: {}", s); +} +