From 0fd879232cc638f6f56753961328fb92bdc6e119 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Fri, 5 Dec 2025 00:28:55 +0900 Subject: [PATCH 001/314] Add complex test case for mrb_funcall --- mrubyedge/tests/fncall.rs | 107 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 mrubyedge/tests/fncall.rs diff --git a/mrubyedge/tests/fncall.rs b/mrubyedge/tests/fncall.rs new file mode 100644 index 0000000..22fb7ef --- /dev/null +++ b/mrubyedge/tests/fncall.rs @@ -0,0 +1,107 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use std::rc::Rc; + +use helpers::*; +use mrubyedge::yamrb::helpers::mrb_define_cmethod; +use mrubyedge::yamrb::value::RObject; +use mrubyedge::yamrb::vm::VM; +use mrubyedge::Error; + +#[test] +fn fncall_test() { + let code = " +def double(n) + n * 2 +end + "; + let binary = mrbc_compile("fncall", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Rust method that calls mrb_funcall internally + fn rust_method_calling_mrb_funcall(vm: &mut VM, args: &[Rc]) -> Result, Error> { + // Get the first argument (should be an integer) + let n = if args.len() > 0 { + let arg: i64 = args[0].as_ref().try_into()?; + arg + } else { + 0 + }; + + // Call Ruby's double method via mrb_funcall + let args_for_call = vec![Rc::new(RObject::integer(n))]; + let result = mrb_funcall(vm, None, "double", &args_for_call)?; + + Ok(result) + } + + let kernel = vm.object_class.clone(); + mrb_define_cmethod(&mut vm, kernel, "call_double", Box::new(rust_method_calling_mrb_funcall)); + + // Call the Rust method which internally calls mrb_funcall + let args = vec![Rc::new(RObject::integer(5))]; + let result = mrb_funcall(&mut vm, None, "call_double", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 10); +} + +#[test] +fn nested_fncall_test() { + let code = " +def add(a, b) + a + b +end + +def multiply(a, b) + do_multiply(a, b) +end + +complex_calc(2, 3) + "; + let binary = mrbc_compile("nested_fncall", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + + fn rust_method_do_multiply(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let a: i64 = args[0].as_ref().try_into()?; + let b: i64 = args[1].as_ref().try_into()?; + + let result = a * b; + Ok(Rc::new(RObject::integer(result))) + } + + // Rust method that calls multiple Ruby methods + fn complex_calculation(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let a: i64 = args[0].as_ref().try_into()?; + let b: i64 = args[1].as_ref().try_into()?; + + // Call add(a, b) + let add_args = vec![Rc::new(RObject::integer(a)), Rc::new(RObject::integer(b))]; + let sum = mrb_funcall(vm, None, "add", &add_args)?; + + // Call multiply(sum, 3) + let sum_val: i64 = sum.as_ref().try_into()?; + let mul_args = vec![Rc::new(RObject::integer(sum_val)), Rc::new(RObject::integer(3))]; + let result = mrb_funcall(vm, None, "multiply", &mul_args)?; + + Ok(result) + } + + let kernel = vm.object_class.clone(); + mrb_define_cmethod(&mut vm, kernel.clone(), "do_multiply", Box::new(rust_method_do_multiply)); + mrb_define_cmethod(&mut vm, kernel.clone(), "complex_calc", Box::new(complex_calculation)); + + let result = vm.run().unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 15); + + // Test: (2 + 3) * 3 = 15 + let args = vec![Rc::new(RObject::integer(2)), Rc::new(RObject::integer(3))]; + let result = mrb_funcall(&mut vm, None, "complex_calc", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 15); +} From afd275d098e41c2cd2a1ff514dfa9605aa0e7157 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sat, 6 Dec 2025 22:47:34 +0900 Subject: [PATCH 002/314] Add logo --- docs/logo.png | Bin 0 -> 287197 bytes docs/logo.svg | 22 ++++++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 docs/logo.png create mode 100644 docs/logo.svg diff --git a/docs/logo.png b/docs/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..7c765e85414bd87f87ad4d5c70d24930fbde339c GIT binary patch literal 287197 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%0A%WEm;V8puWQu+-;ELiX`0S0^+L+<%^i{SjFvp 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 SktKizU} + + + + + + + + mruby + /edge + \ No newline at end of file From ec18f687fc49542ee1946c68acbbc0fc36ab820b Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 7 Dec 2025 10:59:12 +0900 Subject: [PATCH 003/314] Remove unused samples --- mrubyedge/examples/def.rs | 13 ------------- mrubyedge/examples/fib.rs | 33 --------------------------------- mrubyedge/examples/fn.rs | 28 ---------------------------- 3 files changed, 74 deletions(-) delete mode 100644 mrubyedge/examples/def.rs delete mode 100644 mrubyedge/examples/fib.rs delete mode 100644 mrubyedge/examples/fn.rs diff --git a/mrubyedge/examples/def.rs b/mrubyedge/examples/def.rs deleted file mode 100644 index 50ae905..0000000 --- a/mrubyedge/examples/def.rs +++ /dev/null @@ -1,13 +0,0 @@ -extern crate mrubyedge; -use mrubyedge::rite; -use mrubyedge::yamrb::*; - -fn main() { - // let bin = include_bytes!("./def3.mrb"); - // let mut rite = rite::load(bin).unwrap(); - // // dbg!(&rite); - // let mut vm = vm::VM::open(&mut rite); - - // eprintln!("return value:"); - // eprintln!("{:?}", vm.run().unwrap()); -} diff --git a/mrubyedge/examples/fib.rs b/mrubyedge/examples/fib.rs deleted file mode 100644 index 8f0e1bc..0000000 --- a/mrubyedge/examples/fib.rs +++ /dev/null @@ -1,33 +0,0 @@ -use std::rc::Rc; - -use mrubyedge::yamrb::{helpers::mrb_funcall, value::RObject}; - -extern crate mrubyedge; - -fn main() { - // let bin = include_bytes!("./fib.mrb"); - // //let bin = include_bytes!("./if.mrb"); - // let mut rite = mrubyedge::rite::load(bin).unwrap(); - // // dbg!(&rite); - // let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - // // dbg!(&vm.irep.reps); - - // eprintln!("return value(1):"); - // eprintln!("{:?}", vm.run().unwrap()); - // // dbg!(&vm); - // let args = vec![ - // Rc::new(RObject::integer(25)) - // ]; - // match mrb_funcall(&mut vm, None, "fib", &args) { - // Ok(retval) => { - // eprintln!("return value(2):"); - // dbg!(retval); - // } - // Err(ex) => { - // eprintln!("Error"); - // dbg!(ex); - // } - // }; - - () -} diff --git a/mrubyedge/examples/fn.rs b/mrubyedge/examples/fn.rs deleted file mode 100644 index 65d0a9c..0000000 --- a/mrubyedge/examples/fn.rs +++ /dev/null @@ -1,28 +0,0 @@ -use mrubyedge::yamrb::helpers::mrb_funcall; - -extern crate mrubyedge; - -fn main() { - // let bin = include_bytes!("./def3.mrb"); - // let mut rite = mrubyedge::rite::load(bin).unwrap(); - // // dbg!(&rite); - // let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - - // eprintln!("evaluate value:"); - // eprintln!("{:?}", vm.run().unwrap()); - - // let args = vec![ - // ]; - - // match mrb_funcall(&mut vm, None, "hello", &args) { - // Ok(retval) => { - // dbg!(retval); - // } - // Err(ex) => { - // eprintln!("Error"); - // dbg!(ex); - // } - // }; - - () -} From 58c7d4b1c9051ddd1043bf1d294fb434ebf78a4b Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 7 Dec 2025 11:09:56 +0900 Subject: [PATCH 004/314] Fix commented-out example in mec --- Cargo.lock | 1 + mec/Cargo.toml | 3 +++ mec/examples/rust_from_rbs.rs | 47 ++++++++++++++++++++++++++--------- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4539496..620adda 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -477,6 +477,7 @@ dependencies = [ "mec-mrbc-sys", "nom", "rand", + "syn", ] [[package]] diff --git a/mec/Cargo.toml b/mec/Cargo.toml index abde4e4..9134b56 100644 --- a/mec/Cargo.toml +++ b/mec/Cargo.toml @@ -17,3 +17,6 @@ nom = "7.1.3" askama = "0.12.1" bpaf = "0.9.11" mec-mrbc-sys = "3.3.1" + +[dev-dependencies] +syn = { version = "2.0", features = ["full", "parsing"] } diff --git a/mec/examples/rust_from_rbs.rs b/mec/examples/rust_from_rbs.rs index 2258de1..4b69872 100644 --- a/mec/examples/rust_from_rbs.rs +++ b/mec/examples/rust_from_rbs.rs @@ -3,19 +3,42 @@ use mec::rbs_parser::*; use mec::template::LibRs; fn main() { -// let def = " -// def foo_bar: (Integer) -> Integer -// "; + let def = " +def foo_bar: (Integer) -> Integer +"; -// let ret = parse(def).unwrap(); -// let ftype = ret.1; -// let ftypes = vec![mec::template::RustFnTemplate {func_name: &ftype[0].name,args_decl:"a: i32",args_let_vec:"vec![std::rc::Rc::new(RObject::RInteger(a as i64))]",rettype_decl:"-> i32", str_args_converter: todo!(), handle_retval: todo!(), exported_helper_var: todo!() }]; + let ret = parse(def).unwrap(); + let ftype = ret.1; + let ftypes = vec![mec::template::RustFnTemplate { + func_name: &ftype[0].name, + args_decl: "a: i32", + args_let_vec: "vec![std::rc::Rc::new(RObject::RInteger(a as i64))]", + rettype_decl: "-> i32", + str_args_converter: "// do nothing", + handle_retval: "5471", + exported_helper_var: "", + }]; + let imports = vec![]; -// let lib_rs = LibRs { -// file_basename: "world", -// ftypes: &ftypes, -// ftypes_imports: "", -// }; + let lib_rs = LibRs { + file_basename: "world", + ftypes: &ftypes, + ftypes_imports: &imports, + }; -// println!("{}", lib_rs.render().unwrap()); + let rendered = lib_rs.render().unwrap(); + println!("{}", &rendered); + + // Check if rendered is valid Rust syntax using syn + println!("\n--- Checking if rendered code is valid Rust syntax ---"); + + match syn::parse_file(&rendered) { + Ok(_) => { + println!("✓ Rendered code has valid Rust syntax!"); + } + Err(e) => { + println!("✗ Rendered code has syntax errors:"); + println!("{}", e); + } + } } From aafce344f4a166d8d8c3d0dce080810324992259 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 7 Dec 2025 11:55:18 +0900 Subject: [PATCH 005/314] Port compile wasm subcommand, using compiler-2 --- Cargo.lock | 430 +++++++++--------- mrubyedge-cli/Cargo.toml | 10 +- mrubyedge-cli/examples/hello2.rb | 3 + mrubyedge-cli/src/lib.rs | 3 + mrubyedge-cli/src/main.rs | 8 +- mrubyedge-cli/src/rbs_parser/mod.rs | 328 +++++++++++++ mrubyedge-cli/src/subcommands/wasm.rs | 235 +++++++++- mrubyedge-cli/src/template/cargo_toml.rs | 17 + mrubyedge-cli/src/template/mod.rs | 5 + mrubyedge-cli/src/template/source.rs | 29 ++ mrubyedge-cli/templates/Cargo.toml.debug.tmpl | 16 + mrubyedge-cli/templates/Cargo.toml.tmpl | 18 + mrubyedge-cli/templates/lib.rs.tmpl | 100 ++++ 13 files changed, 975 insertions(+), 227 deletions(-) create mode 100644 mrubyedge-cli/examples/hello2.rb create mode 100644 mrubyedge-cli/src/lib.rs create mode 100644 mrubyedge-cli/src/rbs_parser/mod.rs create mode 100644 mrubyedge-cli/src/template/cargo_toml.rs create mode 100644 mrubyedge-cli/src/template/mod.rs create mode 100644 mrubyedge-cli/src/template/source.rs create mode 100644 mrubyedge-cli/templates/Cargo.toml.debug.tmpl create mode 100644 mrubyedge-cli/templates/Cargo.toml.tmpl create mode 100644 mrubyedge-cli/templates/lib.rs.tmpl diff --git a/Cargo.lock b/Cargo.lock index 620adda..1358cf5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,9 +4,9 @@ version = 4 [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -53,7 +53,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -64,7 +64,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys", ] [[package]] @@ -113,15 +113,15 @@ dependencies = [ [[package]] name = "autocfg" -version = "1.1.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "basic-toml" -version = "0.1.8" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2db21524cad41c5591204d22d75e1970a2d1f71060214ca931dc7d5afe2c14e5" +checksum = "ba62675e8242a4c4e806d12f11d136e626e6c8361d6b829310732241652a178a" dependencies = [ "serde", ] @@ -135,7 +135,27 @@ dependencies = [ "bitflags", "cexpr", "clang-sys", - "itertools", + "itertools 0.13.0", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools 0.13.0", "log", "prettyplease", "proc-macro2", @@ -148,21 +168,21 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.7.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1be3f42a67d6d345ecd59f675f3f012d6974981560836e938c22b424b85ce1be" +checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bpaf" -version = "0.9.11" +version = "0.9.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567fc5f0a754100df11b167b2a247b2366fc1ac18e9b776a07659be00878f681" +checksum = "473976d7a8620bb1e06dcdd184407c2363fe4fec8e983ee03ed9197222634a31" [[package]] name = "bumpalo" -version = "3.14.0" +version = "3.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" +checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" [[package]] name = "cast" @@ -172,10 +192,11 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.9" +version = "1.2.49" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8293772165d9345bdaaa39b45b2109591e63fe5e6fbc23c6ff930a048aa310b" +checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" dependencies = [ + "find-msvc-tools", "shlex", ] @@ -190,9 +211,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "ciborium" @@ -290,7 +311,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -311,14 +332,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -335,27 +356,33 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "either" -version = "1.11.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" dependencies = [ "cfg-if", "libc", @@ -364,18 +391,19 @@ dependencies = [ [[package]] name = "glob" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" [[package]] name = "half" -version = "2.4.1" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -386,9 +414,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "humansize" @@ -401,13 +429,13 @@ dependencies = [ [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -425,48 +453,58 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.66" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" dependencies = [ + "once_cell", "wasm-bindgen", ] [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libloading" -version = "0.8.6" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" dependencies = [ "cfg-if", - "windows-targets", + "windows-link", ] [[package]] name = "libm" -version = "0.2.8" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" [[package]] name = "log" -version = "0.4.20" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "mec" @@ -486,16 +524,16 @@ version = "3.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6f61d2493131defdfa15db9411b996cf257f88303e92ba60b39c3babe169528" dependencies = [ - "bindgen", + "bindgen 0.71.1", "cc", "glob", ] [[package]] name = "memchr" -version = "2.7.1" +version = "2.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" +checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" [[package]] name = "mime" @@ -505,9 +543,9 @@ checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "mime_guess" -version = "2.0.4" +version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4192263c238a5f0d0c6bfd21f336a313a4ce1c450542449ca191bb657b4642ef" +checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" dependencies = [ "mime", "unicase", @@ -525,7 +563,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af58b161e8e8422f33ec82de978e21b0267e56c2387674dfd21a4785944597a1" dependencies = [ - "bindgen", + "bindgen 0.72.1", "cc", "glob", "libc", @@ -533,11 +571,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b2534070d33a4b55643b43d3d9d457b31ceb7e30034d1cd210352bfa0996a9" +version = "1.0.6" dependencies = [ + "criterion", "getrandom", + "mec-mrbc-sys", "plain", "simple_endian", ] @@ -545,10 +583,10 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38983e69b290e06aa04c1c22f63fe706f1e316fb509f44917495ceb8a63bb9" dependencies = [ - "criterion", "getrandom", - "mec-mrbc-sys", "plain", "simple_endian", ] @@ -557,9 +595,13 @@ dependencies = [ name = "mrubyedge-cli" version = "0.9.0-rc1" dependencies = [ + "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.5", + "mrubyedge 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "nom", + "rand", + "syn", ] [[package]] @@ -574,18 +616,18 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "once_cell_polyfill" @@ -595,15 +637,15 @@ checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oorandom" -version = "11.1.3" +version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "plain" @@ -613,9 +655,9 @@ checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "plotters" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -626,30 +668,33 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] [[package]] name = "prettyplease" -version = "0.2.28" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924b9a625d6df5b74b0b3cfbb5669b3f62ddf3d46a677ce12b1945471b4ae5c3" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", "syn", @@ -657,18 +702,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.93" +version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" +checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.35" +version = "1.0.42" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" +checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" dependencies = [ "proc-macro2", ] @@ -705,9 +750,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -715,9 +760,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -725,9 +770,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.12.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" dependencies = [ "aho-corasick", "memchr", @@ -737,9 +782,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" dependencies = [ "aho-corasick", "memchr", @@ -748,21 +793,27 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" [[package]] name = "rustc-hash" -version = "2.1.0" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" + +[[package]] +name = "rustversion" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fb8039b3032c191086b10f11f319a6e99e1e82889c5cc6046f515c9db1d497" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -775,18 +826,28 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.197" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fb1c873e1b9b056a4dc4c0c198b24c3ffa059243875552b2bd0933b1aee4ce2" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.197" +version = "1.0.228" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7eb0b34b42edc17f6b7cac84a52a1c5f0e1bb2227e997ca9011ea3dd34e8610b" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", @@ -795,13 +856,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.117" +version = "1.0.145" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" dependencies = [ "itoa", + "memchr", "ryu", "serde", + "serde_core", ] [[package]] @@ -812,9 +875,9 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "simple_endian" -version = "0.3.2" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "872b1ba2925d84fd02d5a3a0b7196b7b681cda5d64a214c6bb1aafb3827c458f" +checksum = "8d70930bff3e7d43bdedbd86ad3c5ed7b247dcaa761749355915956c50e0b10b" [[package]] name = "strsim" @@ -824,9 +887,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.96" +version = "2.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" +checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" dependencies = [ "proc-macro2", "quote", @@ -845,18 +908,15 @@ dependencies = [ [[package]] name = "unicase" -version = "2.7.0" +version = "2.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" -dependencies = [ - "version_check", -] +checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" [[package]] name = "utf8parse" @@ -864,12 +924,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "version_check" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" - [[package]] name = "walkdir" version = "2.5.0" @@ -882,40 +936,28 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasm-bindgen" -version = "0.2.90" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1223296a201415c7fad14792dbefaace9bd52b62d33453ade1c5b5f07555406" +checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" dependencies = [ "cfg-if", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.90" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcdc935b63408d58a32f8cc9738a0bffd8f05cc7c002086c6ef20b7312ad9dcd" -dependencies = [ - "bumpalo", - "log", "once_cell", - "proc-macro2", - "quote", - "syn", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.90" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e4c238561b2d428924c49815533a8b9121c664599558a5d9ec51f8a1740a999" +checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -923,28 +965,31 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.90" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bae1abb6806dc1ad9e560ed242107c0f6c84335f1749dd4e8ddb012ebd5e25a7" +checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" dependencies = [ + "bumpalo", "proc-macro2", "quote", "syn", - "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.90" +version = "0.2.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d91413b1c31d7539ba5ef2451af3f0b833a005eb27a631cec32bc0635a8602b" +checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.66" +version = "0.3.83" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" dependencies = [ "js-sys", "wasm-bindgen", @@ -952,11 +997,11 @@ dependencies = [ [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -965,15 +1010,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", -] - [[package]] name = "windows-sys" version = "0.61.2" @@ -984,65 +1020,21 @@ dependencies = [ ] [[package]] -name = "windows-targets" -version = "0.52.5" +name = "zerocopy" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "zerocopy-derive", ] [[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.5" +name = "zerocopy-derive" +version = "0.8.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index dc78fb5..71f907d 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -15,6 +15,12 @@ name = "mrbedge" path = "src/main.rs" [dependencies] -clap = { version = ">= 4.5", features = ["derive"] } +clap = { version = "4.5", features = ["derive"] } mruby-compiler2-sys = "0.1.0" -mrubyedge = { version = "1.0.5" } +mrubyedge = { version = "1.0.6" } +rand = "0.8.5" +nom = "7.1.3" +askama = "0.12.1" + +[dev-dependencies] +syn = { version = "2.0", features = ["full", "parsing"] } diff --git a/mrubyedge-cli/examples/hello2.rb b/mrubyedge-cli/examples/hello2.rb new file mode 100644 index 0000000..ecdfb74 --- /dev/null +++ b/mrubyedge-cli/examples/hello2.rb @@ -0,0 +1,3 @@ +def start + puts "Hello, mrubyedge!" +end \ No newline at end of file diff --git a/mrubyedge-cli/src/lib.rs b/mrubyedge-cli/src/lib.rs new file mode 100644 index 0000000..299f828 --- /dev/null +++ b/mrubyedge-cli/src/lib.rs @@ -0,0 +1,3 @@ +pub mod rbs_parser; +pub mod template; +pub mod subcommands; diff --git a/mrubyedge-cli/src/main.rs b/mrubyedge-cli/src/main.rs index 7a4c48c..487472d 100644 --- a/mrubyedge-cli/src/main.rs +++ b/mrubyedge-cli/src/main.rs @@ -1,6 +1,6 @@ use clap::{Parser, Subcommand}; -mod subcommands; +use mrubyedge_cli::subcommands; #[derive(Parser)] #[command(name = "mrbedge")] @@ -21,7 +21,7 @@ enum Commands { /// Run is invoked when rb/mrb file is directly passed to the command Run(subcommands::run::RunArgs), /// Generate WebAssembly binary from Ruby code - Wasm, + Wasm(subcommands::wasm::WasmArgs), /// Compile Ruby script to mrb CompileMrb(subcommands::compile_mrb::CompileMrbArgs), /// Scaffold the package project with a wasm binary @@ -44,8 +44,8 @@ fn main() -> Result<(), Box> { Some(Commands::Run(args)) => { subcommands::run::execute(args)?; } - Some(Commands::Wasm) => { - subcommands::wasm::execute(); + Some(Commands::Wasm(args)) => { + subcommands::wasm::execute(args)?; } Some(Commands::CompileMrb(args)) => { subcommands::compile_mrb::execute(args)?; diff --git a/mrubyedge-cli/src/rbs_parser/mod.rs b/mrubyedge-cli/src/rbs_parser/mod.rs new file mode 100644 index 0000000..cd44d20 --- /dev/null +++ b/mrubyedge-cli/src/rbs_parser/mod.rs @@ -0,0 +1,328 @@ +extern crate nom; + +#[derive(Debug)] +pub struct FuncDef { + pub name: String, + pub argstype: Vec, + pub rettype: String, +} + +impl FuncDef { + pub fn args_decl(&self) -> &str { + if self.argstype.len() == 0 { + return ""; + } + + let converted: Vec = self + .argstype + .iter() + .enumerate() + .map(|(idx, arg)| match arg.as_str() { + "Integer" => format!("a{}: i32", idx), + "Float" => format!("a{}: f32", idx), + "bool" => format!("a{}: bool", idx), + "String" => format!("p{0}: *const u8, l{0}: usize", idx), + _ => { + unimplemented!("unsupported arg type") + } + }) + .collect(); + converted.join(", ").leak() + } + + pub fn args_let_vec(&self) -> &str { + if self.argstype.len() == 0 { + return "vec![]"; + } + + let converted: Vec = self + .argstype + .iter() + .enumerate() + .map(|(idx, arg)| match arg.as_str() { + "Integer" => format!("std::rc::Rc::new(RObject::integer(a{} as i64))", idx), + "Float" => format!("std::rc::Rc::new(RObject::float(a{} as f64))", idx), + "bool" => format!("std::rc::Rc::new(RObject::boolean(a{}))", idx), + "String" => format!("std::rc::Rc::new(RObject::string(a{}.to_string()))", idx), + _ => { + unimplemented!("unsupported arg type") + } + }) + .collect(); + format!("vec![{}]", converted.join(", ")).leak() + } + + pub fn str_args_converter(&self) -> &str { + if self.argstype.len() == 0 { + return ""; + } + let mut buf = String::new(); + + for (idx, arg) in self.argstype.iter().enumerate() { + match arg.as_str() { + "String" => { + buf.push_str(&format!( + " +let a{0} = unsafe {{ + let s = std::slice::from_raw_parts(p{0}, l{0} as usize); + std::str::from_utf8(s).expect(\"invalid utf8\") +}}; +", + idx + )); + } + _ => { + // skip + } + } + } + + buf.leak() + } + + pub fn rettype_decl(&self) -> &str { + match self.rettype.as_str() { + "void" => "-> ()", + "Integer" => "-> i32", + "Float" => "-> f32", + "bool" => "-> bool", + "String" => "-> *const u8", + "SharedMemory" => "-> *mut u8", + _ => { + unimplemented!("unsupported arg type") + } + } + } + + pub fn handle_retval(&self) -> &str { + match self.rettype.as_str() { + "String" => { + let mut buf = String::new(); + buf.push_str("let mut retval: String = retval.as_ref().try_into().unwrap();\n"); + buf.push_str("retval.push('\0');\n"); + buf.push_str(&format!( + "unsafe {{ {} = retval.len() - 1; }}\n", + self.size_helper_var_name() + )); + buf.push_str("retval.as_str().as_ptr()\n"); + buf.leak() + } + _ => "retval.as_ref().try_into().unwrap()", + } + } + + fn size_helper_var_name(&self) -> String { + format!("__{}_size", self.name) + } + + pub fn exported_helper_var(&self) -> &str { + match self.rettype.as_str() { + "String" => format!( + " +#[allow(non_upper_case_globals)] +pub static mut {0}: usize = 0; +#[no_mangle] +pub unsafe fn __get{0}() -> u32 {{ + return {0} as u32; +}} +", + &self.size_helper_var_name() + ) + .leak(), + _ => "", + } + } + + pub fn import_helper_var(&self) -> &str { + match self.rettype.as_str() { + "String" => format!( + " +#[allow(non_upper_case_globals)] +pub static mut {0}: usize = 0; +#[no_mangle] +pub unsafe fn __set{0}(s: u32) {{ + {0} = s as usize; +}} +", + &self.size_helper_var_name() + ) + .leak(), + _ => "", + } + } + + // for function importer + pub fn imported_body(&self) -> &str { + let mut buf = String::new(); + for (i, typ) in self.argstype.iter().enumerate() { + let tmp = match typ.as_str() { + "String" => { + let mut buf = String::new(); + buf.push_str(&format!( + "let a{0}: String = args[{0}].clone().as_ref().try_into().unwrap();\n", + i + )); + buf.push_str(&format!("let p{0} = a{0}.as_str().as_ptr();\n", i)); + buf.push_str(&format!("let l{0} = a{0}.as_str().len();\n", i)); + buf + } + _ => format!( + "let a{0} = args[{0}].clone().as_ref().try_into().unwrap();\n", + i, + ), + }; + buf.push_str(&tmp); + } + let call_arg = self + .argstype + .iter() + .enumerate() + .map(|(i, typ)| match typ.as_str() { + "String" => format!("p{0}, l{0}", i), + _ => format!("a{}", i), + }) + .collect::>() + .join(","); + buf.push_str(&format!( + "let r0 = unsafe {{ {}({}) }};\n", + &self.name, call_arg + )); + + if self.rettype.as_str() == "String" { + buf.push_str(&format!( + " +let s0: String; +unsafe {{ + if {0} == 0 {{ + let mut buf = Vec::::new(); + let mut off: usize = 0; + loop {{ + let b = *(r0.add(off)); + if b == 0 {{ + break; + }} else {{ + buf.push(b); + }} + if off >= 65536 {{ + panic!(\"unterminated string detected\"); + }} + off += 1; + }} + s0 = String::from_utf8_unchecked(buf); + }} else {{ + let off = {0}; + let s = std::slice::from_raw_parts(r0, off); + s0 = String::from_utf8_unchecked(s.to_vec()); + }} +}} +", + self.size_helper_var_name() + )); + } + + let ret_mruby_type = match self.rettype.as_str() { + "Integer" => "RObject::integer(r0 as i64)", + "Float" => "RObject::float(r0 as f64)", + "bool" => "RObject::boolean(r0)", + "String" => "RObject::string(s0)", + "void" => "RObject::nil()", + _ => unimplemented!("unsupported arg type"), + }; + buf.push_str(&format!("Ok(Rc::new({}))\n", ret_mruby_type)); + buf.leak() + } +} + +use nom::branch::alt; +use nom::branch::permutation; +use nom::bytes::complete::tag; +use nom::character::complete::*; +// use nom::combinator::opt; +use nom::error::context; +use nom::error::VerboseError; +use nom::multi::*; +use nom::sequence::tuple; +use nom::IResult; + +type Res = IResult>; + +fn def(input: &str) -> Res<&str, ()> { + context("def", tag("def"))(input).map(|(s, _)| (s, ())) +} + +fn alpha_just_1(input: &str) -> Res<&str, char> { + satisfy(|c| c == '_' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'))(input) +} + +fn alphanumeric_just_1(input: &str) -> Res<&str, char> { + satisfy(|c| { + c == '_' || ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') + })(input) +} + +fn symbol(input: &str) -> Res<&str, String> { + tuple((alpha_just_1, many0(alphanumeric_just_1)))(input).map(|(s, (head, tail))| { + let mut name: String = head.to_string(); + for c in tail.iter() { + name += &c.to_string() + } + (s, name) + }) +} + +fn method(input: &str) -> Res<&str, String> { + tuple((symbol, char(':'), space0))(input).map(|(s, (sym, _, _))| (s, sym)) +} + +fn emptyarg(input: &str) -> Res<&str, Vec> { + tuple((char('('), space0, char(')')))(input).map(|(s, _)| (s, vec![])) +} + +fn contentarg(input: &str) -> Res<&str, Vec> { + tuple(( + char('('), + space0, + symbol, + space0, + many0(tuple((char(','), space0, symbol, space0))), + char(')'), + ))(input) + .map(|(s, (_, _, head, _, rest, _))| { + let mut syms: Vec = rest.into_iter().map(|(_, _, val, _)| val).collect(); + syms.insert(0, head); + (s, syms) + }) +} + +fn arg(input: &str) -> Res<&str, Vec> { + alt((emptyarg, contentarg))(input) +} + +fn ret(input: &str) -> Res<&str, String> { + tuple((tag("->"), space0, symbol))(input).map(|(s, (_, _, sym))| (s, sym)) +} + +fn fntype(input: &str) -> Res<&str, (Vec, String)> { + tuple((arg, space0, ret))(input).map(|(s, (arg, _, ret))| (s, (arg, ret))) +} + +pub fn fn_def(input: &str) -> Res<&str, FuncDef> { + tuple((def, space1, method, fntype))(input).map(|(s, (_, _, name, (argstype, rettype)))| { + ( + s, + FuncDef { + name, + argstype, + rettype, + }, + ) + }) +} + +pub fn parse(input: &str) -> Res<&str, Vec> { + tuple(( + multispace0, + separated_list0(permutation((space0, many1(char('\n')), space0)), fn_def), + ))(input) + .map(|(s, (_, list))| (s, list)) +} diff --git a/mrubyedge-cli/src/subcommands/wasm.rs b/mrubyedge-cli/src/subcommands/wasm.rs index 923065a..85715f4 100644 --- a/mrubyedge-cli/src/subcommands/wasm.rs +++ b/mrubyedge-cli/src/subcommands/wasm.rs @@ -1,3 +1,234 @@ -pub fn execute() { - todo!("WASM command is not yet implemented"); +extern crate rand; +extern crate mruby_compiler2_sys; + +use clap::Args; +use std::{fs::File, io::Read, path::{Path, PathBuf}, process::Command}; + +use askama::Template; +use rand::distributions::{Alphanumeric, DistString}; + +use crate::rbs_parser; +use crate::template; + +const MRUBY_EDGE_DEFAULT_VERSION: &'static str = ">= 1"; + +#[derive(Debug, Clone, Args)] +pub struct WasmArgs { + #[arg(short = 'f', long)] + fnname: Option, + #[arg(short = 'm', long)] + mruby_edge_version: Option, + #[arg(short = 'W', long)] + no_wasi: bool, + #[arg(long)] + skip_cleanup: bool, + #[arg(long)] + debug_mruby_edge: bool, + #[arg(long)] + verbose: bool, + #[arg(long)] + strip_binary: bool, + path: PathBuf, +} + +fn sh_do(sharg: &str, debug: bool) -> Result<(), Box> { + println!("running: `{}`", sharg); + let out = Command::new("/bin/sh").args(["-c", sharg]).output()?; + if debug && out.stdout.len() != 0 { + println!( + "stdout:\n{}", + String::from_utf8_lossy(&out.stdout).to_string().trim() + ); + } + if debug && out.stderr.len() != 0 { + println!( + "stderr:\n{}", + String::from_utf8_lossy(&out.stderr).to_string().trim() + ); + } + if !out.status.success() { + println!("{:?}", out.status); + panic!("failed to execute command"); + } + + Ok(()) +} + +fn file_prefix_of(file: &Path) -> Option { + file.file_name()? + .to_str()? + .split('.') + .next() + .map(|s| s.to_string()) +} + +fn debug_println(debug: bool, msg: &str) { + if debug { + eprintln!("{}", msg); + } +} + +pub fn execute(args: WasmArgs) -> Result<(), Box> { + let mut rng = rand::thread_rng(); + let suffix = Alphanumeric.sample_string(&mut rng, 32); + + let fnname = args.fnname; + let path = args.path; + let mrubyfile = std::fs::canonicalize(&path)?; + let fname = file_prefix_of(mrubyfile.as_path()).unwrap(); + + let pwd = std::env::current_dir()?; + std::env::set_current_dir(std::env::var("TMPDIR").unwrap_or("/tmp".to_string()))?; + + let dirname = format!("work-mrubyedge-{}", suffix); + std::fs::create_dir(&dirname)?; + std::env::set_current_dir(format!("./work-mrubyedge-{}", &suffix))?; + std::fs::create_dir("src")?; + + let code = std::fs::read_to_string(&mrubyfile)?; + let out_file = format!("src/{}.mrb", fname); + + if args.verbose { + unsafe { + let mut context = mruby_compiler2_sys::MRubyCompiler2Context::new(); + context.dump_bytecode(&code)?; + } + } + unsafe { + mruby_compiler2_sys::MRubyCompiler2Context::new() + .compile_to_file(&code, out_file.as_ref())? + } + + let feature = if args.no_wasi { "no-wasi" } else { "default" }; + + if args.debug_mruby_edge { + let cargo_toml = template::cargo_toml::CargoTomlDebug { + mruby_edge_crate_path: "/Users/udzura/ghq/github.com/udzura/mrubyedge/mrubyedge", + mrubyedge_feature: feature, + }; + std::fs::write("Cargo.toml", cargo_toml.render()?)?; + } else { + let cargo_toml = template::cargo_toml::CargoToml { + mrubyedge_version: &args.mruby_edge_version.unwrap_or_else(|| MRUBY_EDGE_DEFAULT_VERSION.to_string()), + mrubyedge_feature: feature, + strip: &args.strip_binary.to_string(), + }; + std::fs::write("Cargo.toml", cargo_toml.render()?)?; + } + + let export_rbs_fname = format!("{}.export.rbs", fname); + let export_rbs = mrubyfile.parent().unwrap().join(&export_rbs_fname); + let cont: String; + + let mut ftypes_imports = Vec::new(); + let import_rbs_fname = format!("{}.import.rbs", fname); + let import_rbs = mrubyfile.parent().unwrap().join(&import_rbs_fname); + if import_rbs.exists() { + debug_println( + args.verbose, + &format!("detected import.rbs: {}", import_rbs.as_path().to_string_lossy()), + ); + let mut f = File::open(import_rbs)?; + let mut s = String::new(); + f.read_to_string(&mut s)?; + + let (_, parsed) = rbs_parser::parse(&s).unwrap(); + let parsed: &mut [rbs_parser::FuncDef] = Vec::leak(parsed); + for def in parsed.iter() { + ftypes_imports.push(template::RustImportFnTemplate { + func_name: &def.name, + args_decl: def.args_decl(), + rettype_decl: def.rettype_decl(), + imported_body: def.imported_body(), + import_helper_var: def.import_helper_var(), + }) + } + } + + if export_rbs.exists() { + debug_println(args.verbose, &format!( + "detected export.rbs: {}", + export_rbs.as_path().to_string_lossy() + )); + let mut f = File::open(export_rbs)?; + let mut s = String::new(); + f.read_to_string(&mut s)?; + + let (_, parsed) = rbs_parser::parse(&s).unwrap(); + let mut ftypes = vec![]; + let parsed: &mut [rbs_parser::FuncDef] = Vec::leak(parsed); + for def in parsed.iter() { + ftypes.push(template::RustFnTemplate { + func_name: &def.name, + args_decl: def.args_decl(), + args_let_vec: def.args_let_vec(), + str_args_converter: def.str_args_converter(), + rettype_decl: def.rettype_decl(), + handle_retval: def.handle_retval(), + exported_helper_var: def.exported_helper_var(), + }) + } + + let lib_rs = template::LibRs { + file_basename: &fname, + ftypes: &&ftypes, + ftypes_imports: &ftypes_imports, + }; + let rendered = lib_rs.render()?; + cont = rendered; + } else { + if fnname.is_none() { + panic!("--fnname FNNAME should be specified when export.rbs does not exist") + } + let fnname = fnname.unwrap(); + + let ftypes = vec![template::RustFnTemplate { + func_name: fnname.to_str().unwrap(), + args_decl: "", + args_let_vec: "vec![]", + str_args_converter: "", + rettype_decl: "-> ()", + handle_retval: "()", + exported_helper_var: "", + }]; + + let lib_rs = template::LibRs { + file_basename: &fname, + ftypes: &&ftypes, + ftypes_imports: &ftypes_imports, + }; + let rendered = lib_rs.render()?; + cont = rendered; + } + debug_println(args.verbose, "[debug] will generate main.rs:"); + debug_println(args.verbose, &format!("{}", &cont)); + std::fs::write("src/lib.rs", cont)?; + + let target = if args.no_wasi { + "wasm32-unknown-unknown" + } else { + "wasm32-wasip1" + }; + + sh_do(&format!("cargo build --target {} --release", target), args.verbose)?; + sh_do(&format!( + "cp ./target/{}/release/mywasm.wasm {}/{}.wasm", + target, + &pwd.to_str().unwrap(), + &fname.to_string() + ), args.verbose)?; + if args.skip_cleanup { + println!( + "debug: working directory for compile wasm is remained in {}", + std::env::current_dir()?.as_os_str().to_str().unwrap() + ); + } else { + sh_do(&format!("cd .. && rm -rf work-mrubyedge-{}", &suffix), args.verbose)?; + } + + std::env::set_current_dir(pwd)?; + + println!("[ok] wasm file is generated: {}.wasm", &fname); + + Ok(()) } diff --git a/mrubyedge-cli/src/template/cargo_toml.rs b/mrubyedge-cli/src/template/cargo_toml.rs new file mode 100644 index 0000000..8ad1c48 --- /dev/null +++ b/mrubyedge-cli/src/template/cargo_toml.rs @@ -0,0 +1,17 @@ +extern crate askama; +use askama::Template; + +#[derive(Template)] +#[template(path = "Cargo.toml.tmpl", escape = "none")] +pub struct CargoToml<'a> { + pub mrubyedge_version: &'a str, + pub mrubyedge_feature: &'a str, + pub strip: &'a str, +} + +#[derive(Template)] +#[template(path = "Cargo.toml.debug.tmpl", escape = "none")] +pub struct CargoTomlDebug<'a> { + pub mruby_edge_crate_path: &'a str, + pub mrubyedge_feature: &'a str, +} diff --git a/mrubyedge-cli/src/template/mod.rs b/mrubyedge-cli/src/template/mod.rs new file mode 100644 index 0000000..28464bd --- /dev/null +++ b/mrubyedge-cli/src/template/mod.rs @@ -0,0 +1,5 @@ +pub mod cargo_toml; +pub mod source; + +pub use cargo_toml::CargoToml; +pub use source::*; diff --git a/mrubyedge-cli/src/template/source.rs b/mrubyedge-cli/src/template/source.rs new file mode 100644 index 0000000..7cea08e --- /dev/null +++ b/mrubyedge-cli/src/template/source.rs @@ -0,0 +1,29 @@ +extern crate askama; +use askama::Template; + +#[derive(Template)] +#[template(path = "lib.rs.tmpl", escape = "none")] +pub struct LibRs<'a> { + pub file_basename: &'a str, + + pub ftypes: &'a [RustFnTemplate<'a>], + pub ftypes_imports: &'a [RustImportFnTemplate<'a>], +} + +pub struct RustFnTemplate<'a> { + pub func_name: &'a str, + pub args_decl: &'a str, + pub args_let_vec: &'a str, + pub str_args_converter: &'a str, + pub rettype_decl: &'a str, + pub handle_retval: &'a str, + pub exported_helper_var: &'a str, +} + +pub struct RustImportFnTemplate<'a> { + pub func_name: &'a str, + pub args_decl: &'a str, + pub imported_body: &'a str, + pub rettype_decl: &'a str, + pub import_helper_var: &'a str, +} diff --git a/mrubyedge-cli/templates/Cargo.toml.debug.tmpl b/mrubyedge-cli/templates/Cargo.toml.debug.tmpl new file mode 100644 index 0000000..22afa0a --- /dev/null +++ b/mrubyedge-cli/templates/Cargo.toml.debug.tmpl @@ -0,0 +1,16 @@ +[package] +name = "mywasm" +version = "1.0.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies.mrubyedge] +# fixme can be changed +path = "{{ mruby_edge_crate_path }}" +default-features = false +features = [ "{{ mrubyedge_feature }}" ] + +[profile.release] +# In debug profile, we do not optimize for size \ No newline at end of file diff --git a/mrubyedge-cli/templates/Cargo.toml.tmpl b/mrubyedge-cli/templates/Cargo.toml.tmpl new file mode 100644 index 0000000..f6468ca --- /dev/null +++ b/mrubyedge-cli/templates/Cargo.toml.tmpl @@ -0,0 +1,18 @@ +[package] +name = "mywasm" +version = "1.0.0" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies.mrubyedge] +version = "{{ mrubyedge_version }}" +default-features = false +features = [ "{{ mrubyedge_feature }}" ] + +[profile.release] +# Tell `rustc` to optimize for small code size. +opt-level = "s" +lto = true +strip = {{ strip }} \ No newline at end of file diff --git a/mrubyedge-cli/templates/lib.rs.tmpl b/mrubyedge-cli/templates/lib.rs.tmpl new file mode 100644 index 0000000..a39b230 --- /dev/null +++ b/mrubyedge-cli/templates/lib.rs.tmpl @@ -0,0 +1,100 @@ +#![allow(unused_variables)] +#![allow(non_snake_case)] +#![allow(unused_imports)] +extern crate mrubyedge; + +use core::mem::MaybeUninit; +use std::rc::Rc; +use std::cell::RefCell; +use std::any::Any; + +use mrubyedge::yamrb::value::*; + +const DATA: &'static [u8] = include_bytes!("./{{ file_basename }}.mrb"); +const MEMORY_INDEX: u32 = 0; +const PAGE_SIZE: usize = 65536; // 64KB + +#[cfg(target_arch = "wasm32")] +#[no_mangle] +pub unsafe fn __mrbe_grow(num_pages: usize) -> *const u8 { + use core::arch::wasm32; + let num_pages = wasm32::memory_grow(MEMORY_INDEX, num_pages); + if num_pages == usize::max_value() { + return usize::max_value() as *const u8; + } + let ptr = (num_pages * PAGE_SIZE) as *const u8; + ptr +} + +extern "C" { +{% for fn in ftypes_imports %} + fn {{ fn.func_name }}({{ fn.args_decl }}) {{ fn.rettype_decl }}; +{% endfor %} +} + +{% for fn in ftypes_imports %} +{{ fn.import_helper_var }} +fn __imported_c_{{ fn.func_name }}(_vm: &mut mrubyedge::yamrb::vm::VM, args: &[Rc]) -> Result, mrubyedge::Error> { +{{ fn.imported_body }} +} +{% endfor %} + +static mut MRUBY_VM: MaybeUninit = MaybeUninit::uninit(); +static mut MRUBY_VM_LOADED: bool = false; + +#[allow(static_mut_refs)] +unsafe fn assume_initialized_VM() -> &'static mut mrubyedge::yamrb::vm::VM { + if !MRUBY_VM_LOADED { + initVM(); + MRUBY_VM_LOADED = true; + } + MRUBY_VM.assume_init_mut() +} + +fn initVM() { + let mut rite = mrubyedge::rite::load(DATA).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + +{% for ifn in ftypes_imports %} + let klass = vm.object_class.clone(); + let method = Box::new(__imported_c_{{ ifn.func_name }}); + mrubyedge::yamrb::helpers::mrb_define_cmethod(&mut vm, klass, "{{ ifn.func_name }}", method); +{% endfor %} + + vm.run().unwrap(); + + unsafe { + MRUBY_VM = core::mem::MaybeUninit::new(vm); + } +} + +{% for fn in ftypes %} + +{{ fn.exported_helper_var }} +#[no_mangle] +pub fn {{ fn.func_name }}({{ fn.args_decl }}) {{ fn.rettype_decl }} { + let mut vm = unsafe { assume_initialized_VM() }; + + {{ fn.str_args_converter }} + + vm.run().unwrap(); + + let args = {{ fn.args_let_vec }}; + let retval: Result, mrubyedge::Error> = + mrubyedge::yamrb::helpers::mrb_funcall( + &mut vm, + None, + "{{ fn.func_name }}", + &args); + + match &retval { + Ok(retval) => { + {{ fn.handle_retval }} + } + Err(ex) => { + dbg!(ex); + panic!("mruby error"); + } + } +} +{% endfor %} \ No newline at end of file From 47d2002887230167608ab72619602e3394b9b7ec Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 7 Dec 2025 12:02:27 +0900 Subject: [PATCH 006/314] Add small test --- mrubyedge-cli/src/template/source.rs | 36 ++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/mrubyedge-cli/src/template/source.rs b/mrubyedge-cli/src/template/source.rs index 7cea08e..96148ef 100644 --- a/mrubyedge-cli/src/template/source.rs +++ b/mrubyedge-cli/src/template/source.rs @@ -1,4 +1,5 @@ extern crate askama; + use askama::Template; #[derive(Template)] @@ -27,3 +28,38 @@ pub struct RustImportFnTemplate<'a> { pub rettype_decl: &'a str, pub import_helper_var: &'a str, } + +#[test] +fn test_lib_rs_template() { + use crate::rbs_parser::parse; + + let def = " +def foo_bar: (Integer) -> Integer +"; + + let ret = parse(def).unwrap(); + let ftype = ret.1; + let ftypes = vec![RustFnTemplate { + func_name: &ftype[0].name, + args_decl: "a: i32", + args_let_vec: "vec![std::rc::Rc::new(RObject::RInteger(a as i64))]", + rettype_decl: "-> i32", + str_args_converter: "// do nothing", + handle_retval: "5471", + exported_helper_var: "", + }]; + let imports = vec![]; + + let lib_rs = LibRs { + file_basename: "world", + ftypes: &ftypes, + ftypes_imports: &imports, + }; + + let rendered = lib_rs.render().unwrap(); + if std::env::var("VERBOSE").is_ok() { + println!("{}", &rendered); + } + + assert!(syn::parse_file(&rendered).is_ok()); +} From e62620b51d33fef749e6893ee5d1f75f2cda4f9e Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 7 Dec 2025 12:03:04 +0900 Subject: [PATCH 007/314] Add test targ --- .github/workflows/mrubyedge.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/mrubyedge.yml b/.github/workflows/mrubyedge.yml index d642d30..beae4aa 100644 --- a/.github/workflows/mrubyedge.yml +++ b/.github/workflows/mrubyedge.yml @@ -6,11 +6,13 @@ on: - master paths: - 'mrubyedge/**' + - 'mrubyedge-cli/**' pull_request: branches: - master paths: - 'mrubyedge/**' + - 'mrubyedge-cli/**' jobs: build: From caa165af5d6e61e6d3cf343f8c638fd9d6cba3bf Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 7 Dec 2025 12:20:26 +0900 Subject: [PATCH 008/314] Misc --- .github/workflows/mrubyedge.yml | 4 ++-- Cargo.lock | 2 +- mrubyedge-cli/Cargo.toml | 2 +- mrubyedge-cli/src/main.rs | 9 +++++++++ mrubyedge/src/lib.rs | 12 +++++++++++- 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/.github/workflows/mrubyedge.yml b/.github/workflows/mrubyedge.yml index beae4aa..11c30a0 100644 --- a/.github/workflows/mrubyedge.yml +++ b/.github/workflows/mrubyedge.yml @@ -33,8 +33,8 @@ jobs: - name: Install C compiler run: sudo apt-get update && sudo apt-get install -y build-essential - name: Run tests for "${{ matrix.BUILD_TARGET }}" profile - run: cargo test --profile ${{ matrix.BUILD_TARGET }} + run: cargo test --workspace --exclude mec --profile ${{ matrix.BUILD_TARGET }} working-directory: mrubyedge - name: Build binaries for "${{ matrix.BUILD_TARGET }}" profile - run: cargo build --profile ${{ matrix.BUILD_TARGET }} + run: cargo build --workspace --exclude mec --profile ${{ matrix.BUILD_TARGET }} working-directory: mrubyedge \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 1358cf5..908b61a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -593,7 +593,7 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "0.9.0-rc1" +version = "1.0.6" dependencies = [ "askama", "clap", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 71f907d..3a2f98f 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "0.9.0-rc1" +version = "1.0.6" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." diff --git a/mrubyedge-cli/src/main.rs b/mrubyedge-cli/src/main.rs index 487472d..07a084e 100644 --- a/mrubyedge-cli/src/main.rs +++ b/mrubyedge-cli/src/main.rs @@ -2,10 +2,19 @@ use clap::{Parser, Subcommand}; use mrubyedge_cli::subcommands; +const LONG_VERSION: &str = concat!( + env!("CARGO_PKG_VERSION"), + ", using mruby/edge ", + env!("CARGO_PKG_VERSION"), + // FIXME: update version macro usage + // mrubyedge::version!(), +); + #[derive(Parser)] #[command(name = "mrbedge")] #[command(about = "mruby/edge command line interface", long_about = None)] #[command(version)] +#[command(long_version = LONG_VERSION)] #[command(args_conflicts_with_subcommands = true)] struct Cli { #[command(subcommand)] diff --git a/mrubyedge/src/lib.rs b/mrubyedge/src/lib.rs index 016e6ea..5619206 100644 --- a/mrubyedge/src/lib.rs +++ b/mrubyedge/src/lib.rs @@ -3,4 +3,14 @@ pub mod error; pub mod rite; pub mod yamrb; -pub use error::Error; \ No newline at end of file +pub use error::Error; + +/// The version of the mrubyedge crate +#[macro_export] +macro_rules! version { + () => { + env!("CARGO_PKG_VERSION") + }; +} + +pub const VERSION: &str = version!(); \ No newline at end of file From eba5b73e07d3732dec4ee44a90bc2c670ff58a93 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 7 Dec 2025 12:34:23 +0900 Subject: [PATCH 009/314] Add debug diff in bindings.rs --- .github/workflows/mrubyedge.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/mrubyedge.yml b/.github/workflows/mrubyedge.yml index 11c30a0..d71e372 100644 --- a/.github/workflows/mrubyedge.yml +++ b/.github/workflows/mrubyedge.yml @@ -33,7 +33,15 @@ jobs: - name: Install C compiler run: sudo apt-get update && sudo apt-get install -y build-essential - name: Run tests for "${{ matrix.BUILD_TARGET }}" profile - run: cargo test --workspace --exclude mec --profile ${{ matrix.BUILD_TARGET }} + run: | + set +e + cargo test --workspace --exclude mec --profile ${{ matrix.BUILD_TARGET }} + TEST_RESULT=$? + if [ $TEST_RESULT != 0 ]; then + echo "Some tests failed. Debug:" + git diff + exit $TEST_RESULT + fi working-directory: mrubyedge - name: Build binaries for "${{ matrix.BUILD_TARGET }}" profile run: cargo build --workspace --exclude mec --profile ${{ matrix.BUILD_TARGET }} From aabfadf673f4ea786adc3b4a11ed72dbc1e1a5ed Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 7 Dec 2025 13:31:25 +0900 Subject: [PATCH 010/314] Bump version to 0.1.1 --- Cargo.lock | 4 ++-- mrubyedge-cli/Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 908b61a..0791eaa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -559,9 +559,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mruby-compiler2-sys" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af58b161e8e8422f33ec82de978e21b0267e56c2387674dfd21a4785944597a1" +checksum = "2b1e90e55600aa830bc7381ce7de6a8fbfd7dc91727dd5116dbb92487e5b47c2" dependencies = [ "bindgen 0.72.1", "cc", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 3a2f98f..6bc4250 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -16,7 +16,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } -mruby-compiler2-sys = "0.1.0" +mruby-compiler2-sys = "0.1.1" mrubyedge = { version = "1.0.6" } rand = "0.8.5" nom = "7.1.3" From 660b6890d39b010ff1c85f49f5d39c6888b8b86a Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 7 Dec 2025 13:39:54 +0900 Subject: [PATCH 011/314] Add files to crate --- mrubyedge-cli/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 6bc4250..3b23e00 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -8,6 +8,7 @@ license = "BSD-3-Clause" include = [ "**/*.rs", "Cargo.toml", + "templates/*", ] [[bin]] From e46a659668231e2a2c3e0c8c93673fe225ca476e Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 7 Dec 2025 13:43:39 +0900 Subject: [PATCH 012/314] Update readme --- README.md | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fbb32c7..5923797 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,21 @@ mruby/edge is yet another mruby-compatible VM implementation, specialized for WebAssembly. +## badges + +- ![crates.io](https://img.shields.io/crates/v/mrubyedge.svg) ![crates.io](https://img.shields.io/crates/v/mrubyedge-cli.svg) +- ![docs.rs](https://docs.rs/mrubyedge/badge.svg) ![docs.rs](https://docs.rs/mrubyedge-cli/badge.svg) + ## crates ### mrubyedge * mruby/edge core VM implementation -### mec +### mrubyedge-cli + +* CLI endpoint for mruby/edge - run, compile to wasm, etc. + +### mec [deprecated] -* a command line client to operate mruby/edge stuffs (e.g. compilation into .wasm file) -* Mruby Edge Compiler +* Old versions of compiler From 1b1985348328effcdebc54603ac40ca1b17414e2 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 7 Dec 2025 13:44:09 +0900 Subject: [PATCH 013/314] Link --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5923797..0a8c477 100644 --- a/README.md +++ b/README.md @@ -9,11 +9,11 @@ mruby/edge is yet another mruby-compatible VM implementation, specialized for We ## crates -### mrubyedge +### [mrubyedge](https://crates.io/crates/mrubyedge) * mruby/edge core VM implementation -### mrubyedge-cli +### [mrubyedge-cli](https://crates.io/crates/mrubyedge-cli) * CLI endpoint for mruby/edge - run, compile to wasm, etc. From 576f8b5aa7a072995d1452eb548820109a89127c Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 8 Dec 2025 23:12:10 +0900 Subject: [PATCH 014/314] Fix class method definition to use the correct class --- mrubyedge/examples/class.rb | 26 ++++++++++++++++++++++++++ mrubyedge/src/yamrb/optable.rs | 11 +++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 mrubyedge/examples/class.rb diff --git a/mrubyedge/examples/class.rb b/mrubyedge/examples/class.rb new file mode 100644 index 0000000..1290232 --- /dev/null +++ b/mrubyedge/examples/class.rb @@ -0,0 +1,26 @@ +class Test1 + def hello + 123 + end +end + +class Test2 + def hello + 456 + end +end + +# class Test3 < Test1 +# def hello +# super + 1 +# end +# end + +def hello + 789 +end + +puts Test1.new.hello +puts Test2.new.hello +puts hello +# puts Test3.new.hello \ No newline at end of file diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 7ede828..613c87e 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -918,6 +918,7 @@ pub(crate) fn op_super(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { RValue::Instance(ins) => ins.class.as_ref(), _ => unreachable!("super must be called on instance"), }; + dbg!(&klass); let superclass = klass.super_class.as_ref().ok_or_else(|| Error::internal("superclass not found"))?; let sc_procs = superclass.procs.borrow(); let method = sc_procs.get(&sym_id) @@ -1464,7 +1465,13 @@ pub(crate) fn op_exec(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let irep = vm.irep.reps[b as usize].clone(); vm.current_irep = irep; vm.current_regs_offset += a as usize; - vm.target_class = recv.get_class(vm); + + // If recv is a Class, set target_class to that class itself + // Otherwise, set target_class to the class of recv + vm.target_class = match &recv.value { + RValue::Class(klass) => klass.clone(), + _ => recv.get_class(vm), + }; Ok(()) } @@ -1490,7 +1497,7 @@ pub(crate) fn op_def(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_tclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; - let klass = vm.object_class.clone(); + let klass = vm.target_class.clone(); let val: RObject = klass.into(); vm.current_regs()[a].replace(val.to_refcount_assigned()); Ok(()) From e0986d3a1b0f29dad9d26179eb59494b57af9e37 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 8 Dec 2025 23:25:31 +0900 Subject: [PATCH 015/314] Fill in nil object when no block is given --- mrubyedge/examples/class.rb | 12 ++++++------ mrubyedge/examples/module.rb | 5 +++++ mrubyedge/src/yamrb/optable.rs | 3 ++- 3 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 mrubyedge/examples/module.rb diff --git a/mrubyedge/examples/class.rb b/mrubyedge/examples/class.rb index 1290232..90e306e 100644 --- a/mrubyedge/examples/class.rb +++ b/mrubyedge/examples/class.rb @@ -10,11 +10,11 @@ def hello end end -# class Test3 < Test1 -# def hello -# super + 1 -# end -# end +class Test3 < Test1 + def hello + super + 1 + end +end def hello 789 @@ -23,4 +23,4 @@ def hello puts Test1.new.hello puts Test2.new.hello puts hello -# puts Test3.new.hello \ No newline at end of file +puts Test3.new.hello \ No newline at end of file diff --git a/mrubyedge/examples/module.rb b/mrubyedge/examples/module.rb new file mode 100644 index 0000000..ba1f0fc --- /dev/null +++ b/mrubyedge/examples/module.rb @@ -0,0 +1,5 @@ +module Test + def hello + 123 + end +end diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 613c87e..27fa010 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -848,6 +848,8 @@ pub(crate) fn do_op_send(vm: &mut VM, recv_index: usize, blk_index: Option Result<(), Error> { RValue::Instance(ins) => ins.class.as_ref(), _ => unreachable!("super must be called on instance"), }; - dbg!(&klass); let superclass = klass.super_class.as_ref().ok_or_else(|| Error::internal("superclass not found"))?; let sc_procs = superclass.procs.borrow(); let method = sc_procs.get(&sym_id) From e5b9d37fc639bd47fe8d6e8be921249496e398b7 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 8 Dec 2025 23:32:03 +0900 Subject: [PATCH 016/314] Add testcase --- mrubyedge/tests/klass.rs | 71 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/mrubyedge/tests/klass.rs b/mrubyedge/tests/klass.rs index 3a54e48..3eaf9cd 100644 --- a/mrubyedge/tests/klass.rs +++ b/mrubyedge/tests/klass.rs @@ -80,4 +80,73 @@ fn attr_accessor_test() { let result: String = mrb_funcall(&mut vm, None, "test_main", &args) .unwrap().as_ref().try_into().unwrap(); assert_eq!(&result, "Hola, attr"); -} \ No newline at end of file +} + +#[test] +fn class_definition_isolation_test() { + let code = " + class Test1 + def hello + 123 + end + end + + class Test2 + def hello + 456 + end + end + + def test_main1 + Test1.new.hello + end + + def test_main2 + Test2.new.hello + end + "; + let binary = mrbc_compile("class_definition_isolation", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let val1: i32 = mrb_funcall(&mut vm, None, "test_main1", &args) + .unwrap().as_ref().try_into().unwrap(); + let val2: i32 = mrb_funcall(&mut vm, None, "test_main2", &args) + .unwrap().as_ref().try_into().unwrap(); + assert_eq!(val1, 123); + assert_eq!(val2, 456); +} + +#[test] +fn class_inheritance_super_test() { + let code = " + class Test1 + def hello + 123 + end + end + + class Test3 < Test1 + def hello + super + 1 + end + end + + def test_main + Test3.new.hello + end + "; + let binary = mrbc_compile("class_inheritance_super", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_main", &args) + .unwrap().as_ref().try_into().unwrap(); + assert_eq!(result, 124); +} From 1a188d03db9940749960d8efcc2f54e390919236 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 9 Dec 2025 00:18:27 +0900 Subject: [PATCH 017/314] First implementation of Module support --- mrubyedge/examples/module.rb | 8 ++-- mrubyedge/src/yamrb/optable.rs | 60 ++++++++++++++++-------- mrubyedge/src/yamrb/prelude/mod.rs | 2 + mrubyedge/src/yamrb/prelude/module.rs | 7 +++ mrubyedge/src/yamrb/value.rs | 67 +++++++++++++++++++++++---- mrubyedge/src/yamrb/vm.rs | 28 ++++++----- mrubyedge/tests/module.rs | 27 +++++++++++ 7 files changed, 156 insertions(+), 43 deletions(-) create mode 100644 mrubyedge/src/yamrb/prelude/module.rs create mode 100644 mrubyedge/tests/module.rs diff --git a/mrubyedge/examples/module.rb b/mrubyedge/examples/module.rb index ba1f0fc..6c4b2b2 100644 --- a/mrubyedge/examples/module.rb +++ b/mrubyedge/examples/module.rb @@ -1,5 +1,7 @@ -module Test - def hello - 123 +module TestModule + def module_method + 42 end end + +p TestModule diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 27fa010..ae360ec 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -421,9 +421,9 @@ pub(crate) fn consume_expr(vm: &mut VM, code: OpCode, operand: &Fetched, pos: us CLASS => { op_class(vm, &operand)?; } - // MODULE => { - // // op_module(vm, &operand)?; - // } + MODULE => { + op_module(vm, &operand)?; + } EXEC => { op_exec(vm, &operand)?; } @@ -1455,6 +1455,16 @@ pub(crate) fn op_class(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { Ok(()) } +pub(crate) fn op_module(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { + let (a, b) = operand.as_bb()?; + let name = vm.current_irep.syms[b as usize].clone(); + let name = name.name; + let module = vm.define_module(&name); + + vm.current_regs()[a as usize].replace(Rc::new(module.into())); + Ok(()) +} + pub(crate) fn op_exec(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; let recv = vm.get_current_regs_cloned(a as usize)?; @@ -1467,30 +1477,40 @@ pub(crate) fn op_exec(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { vm.current_irep = irep; vm.current_regs_offset += a as usize; - // If recv is a Class, set target_class to that class itself - // Otherwise, set target_class to the class of recv + // If recv is a Class or Module, set target_class accordingly vm.target_class = match &recv.value { - RValue::Class(klass) => klass.clone(), - _ => recv.get_class(vm), + RValue::Class(klass) => TargetContext::Class(klass.clone()), + RValue::Module(module) => TargetContext::Module(module.clone()), + _ => TargetContext::Class(recv.get_class(vm)), }; Ok(()) } pub(crate) fn op_def(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; - let klass = vm.get_current_regs_cloned(a as usize)?; + let target = vm.get_current_regs_cloned(a as usize)?; let method = vm.get_current_regs_cloned(a as usize + 1)?; let sym = vm.current_irep.syms[b as usize].clone(); - let klass = klass.as_ref(); - let method = method.as_ref(); - if let (RValue::Class(klass), RValue::Proc(method)) = (&klass.value, &method.value) { - let mut procs = klass.procs.borrow_mut(); - let mut method = method.clone(); - method.sym_id = Some(sym.clone()); - procs.insert(sym.name.clone(), method); - } else { - unreachable!("DEF must be called on class"); + let target_ref = target.as_ref(); + let method_ref = method.as_ref(); + + match (&target_ref.value, &method_ref.value) { + (RValue::Class(klass), RValue::Proc(method)) => { + let mut procs = klass.procs.borrow_mut(); + let mut method = method.clone(); + method.sym_id = Some(sym.clone()); + procs.insert(sym.name.clone(), method); + } + (RValue::Module(module), RValue::Proc(method)) => { + let mut procs = module.procs.borrow_mut(); + let mut method = method.clone(); + method.sym_id = Some(sym.clone()); + procs.insert(sym.name.clone(), method); + } + _ => { + unreachable!("DEF must be called on class or module"); + } } vm.current_regs()[a as usize].replace(RObject::symbol(sym).to_refcount_assigned()); Ok(()) @@ -1498,8 +1518,10 @@ pub(crate) fn op_def(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_tclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; - let klass = vm.target_class.clone(); - let val: RObject = klass.into(); + let val: RObject = match &vm.target_class { + TargetContext::Class(klass) => klass.clone().into(), + TargetContext::Module(module) => module.clone().into(), + }; vm.current_regs()[a].replace(val.to_refcount_assigned()); Ok(()) } diff --git a/mrubyedge/src/yamrb/prelude/mod.rs b/mrubyedge/src/yamrb/prelude/mod.rs index 8dc57a7..71723f0 100644 --- a/mrubyedge/src/yamrb/prelude/mod.rs +++ b/mrubyedge/src/yamrb/prelude/mod.rs @@ -3,6 +3,7 @@ use super::vm::VM; pub mod object; pub mod exception; pub mod class; +pub mod module; pub mod integer; pub mod string; pub mod array; @@ -13,6 +14,7 @@ pub mod shared_memory; pub fn prelude(vm: &mut VM) { object::initialize_object(vm); exception::initialize_exception(vm); + module::initialize_module(vm); class::initialize_class(vm); integer::initialize_integer(vm); string::initialize_string(vm); diff --git a/mrubyedge/src/yamrb/prelude/module.rs b/mrubyedge/src/yamrb/prelude/module.rs new file mode 100644 index 0000000..743bc83 --- /dev/null +++ b/mrubyedge/src/yamrb/prelude/module.rs @@ -0,0 +1,7 @@ +use std::rc::Rc; + +use crate::{yamrb::{value::*, vm::VM}, Error}; + +pub(crate) fn initialize_module(vm: &mut VM) { + let _module_class = vm.define_standard_class("Module"); +} diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index e613c1d..a18bd5a 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -14,6 +14,7 @@ pub enum RType { Integer, Float, Class, + Module, Instance, Proc, Array, @@ -33,6 +34,7 @@ pub enum RValue { Integer(i64), Float(f64), Class(Rc), + Module(Rc), Instance(RInstance), Proc(RProc), Array(RefCell>>), @@ -191,6 +193,14 @@ impl RObject { } } + pub fn module(m: Rc) -> Self { + RObject { + tt: RType::Module, + value: RValue::Module(m), + object_id: (u64::MAX).into(), + } + } + pub fn instance(c: Rc) -> Self { RObject { tt: RType::Instance, @@ -294,6 +304,7 @@ impl RObject { pub fn get_class(&self, vm: &VM) -> Rc { match &self.value { RValue::Class(_) => vm.get_class_by_name("Class"), + RValue::Module(_) => vm.get_class_by_name("Module"), RValue::Instance(i) => i.class.clone(), RValue::Bool(b) => { if *b { @@ -498,34 +509,62 @@ impl TryFrom<&RObject> for *mut u8 { } #[derive(Debug, Clone)] -pub struct RClass { +pub struct RModule { pub sym_id: RSym, - pub super_class: Option>, pub procs: RefCell>, pub consts: RefCell>>, } -impl RClass { - pub fn new(name: &str, super_class: Option>) ->Self { +impl RModule { + pub fn new(name: &str) -> Self { let name = name.to_string(); - RClass { + RModule { sym_id: RSym::new(name), - super_class, procs: RefCell::new(HashMap::new()), consts: RefCell::new(HashMap::new()), } } pub fn getmcnst(&self, name: &str) -> Option> { - let consts = self.consts.borrow(); + let consts = self.consts.borrow(); consts.get(name).map(|v| v.clone()) } - // find_method will search method from self to superclass pub fn find_method(&self, name: &str) -> Option { let procs = self.procs.borrow(); - match procs.get(name) { - Some(p) => Some(p.clone()), + procs.get(name).map(|p| p.clone()) + } +} + +impl From> for RObject { + fn from(value: Rc) -> Self { + RObject::module(value) + } +} + +#[derive(Debug, Clone)] +pub struct RClass { + pub module: Rc, + pub super_class: Option>, +} + +impl RClass { + pub fn new(name: &str, super_class: Option>) -> Self { + let module = Rc::new(RModule::new(name)); + RClass { + module, + super_class, + } + } + + pub fn getmcnst(&self, name: &str) -> Option> { + self.module.getmcnst(name) + } + + // find_method will search method from self to superclass + pub fn find_method(&self, name: &str) -> Option { + match self.module.find_method(name) { + Some(p) => Some(p), None => { match &self.super_class { Some(sc) => sc.find_method(name), @@ -536,6 +575,14 @@ impl RClass { } } +// Provide convenient accessors to module fields +impl std::ops::Deref for RClass { + type Target = RModule; + fn deref(&self) -> &Self::Target { + &self.module + } +} + impl From> for RObject { fn from(value: Rc) -> Self { RObject::class(value) diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index d73790c..722cc6a 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -16,6 +16,12 @@ pub const ENGINE: &'static str = "mruby/edge"; const MAX_REGS_SIZE: usize = 256; +#[derive(Debug, Clone)] +pub enum TargetContext { + Class(Rc), + Module(Rc), +} + pub struct VM { pub irep: Rc, @@ -26,7 +32,7 @@ pub struct VM { pub regs: [Option>; MAX_REGS_SIZE], pub current_regs_offset: usize, pub current_callinfo: Option>, - pub target_class: Rc, + pub target_class: TargetContext, pub exception: Option>, pub flag_preemption: Cell, @@ -76,14 +82,7 @@ impl VM { let consts = HashMap::new(); let builtin_class_table = HashMap::new(); - let object_class = Rc::new( - RClass { - sym_id: "Object".into(), - super_class: None, - procs: RefCell::new(HashMap::new()), - consts: RefCell::new(HashMap::new()), - } - ); + let object_class = Rc::new(RClass::new("Object", None)); let id = 1; // TODO generator let bytecode = Vec::new(); @@ -92,7 +91,7 @@ impl VM { let regs: [Option>; MAX_REGS_SIZE] = [const { None }; MAX_REGS_SIZE]; let current_regs_offset = 0; let current_callinfo = None; - let target_class = object_class.clone(); + let target_class = TargetContext::Class(object_class.clone()); let exception = None; let flag_preemption = Cell::new(false); let fn_table = Vec::new(); @@ -270,6 +269,13 @@ impl VM { class } + pub(crate) fn define_module(&mut self, name: &str) -> Rc { + let module = Rc::new(RModule::new(name)); + let object = RObject::module(module.clone()).to_refcount_assigned(); + self.consts.insert(name.to_string(), object); + module + } + pub(crate) fn define_standard_class(&mut self, name: &'static str) -> Rc { let class = self.define_class(name, None); self.builtin_class_table.insert(name, class.clone()); @@ -365,7 +371,7 @@ pub struct CALLINFO { pub pc_irep: Rc, pub pc: usize, pub current_regs_offset: usize, - pub target_class: Rc, + pub target_class: TargetContext, pub n_args: usize, } diff --git a/mrubyedge/tests/module.rs b/mrubyedge/tests/module.rs new file mode 100644 index 0000000..3e108e1 --- /dev/null +++ b/mrubyedge/tests/module.rs @@ -0,0 +1,27 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn module_definition_test() { + let script = r#" +module TestModule + def module_method + 42 + end +end + +TestModule +"#; + + let binary = mrbc_compile("module_def", script); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + + // Result should be the module itself + assert!(matches!(result.tt, mrubyedge::yamrb::value::RType::Module)); +} + From 6ce6e6f1a95c9aa4c9bb09ceab40a6f55ac00407 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 9 Dec 2025 00:37:18 +0900 Subject: [PATCH 018/314] Support mixed-in module members --- mrubyedge/src/yamrb/value.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index a18bd5a..497ce5e 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -513,6 +513,7 @@ pub struct RModule { pub sym_id: RSym, pub procs: RefCell>, pub consts: RefCell>>, + pub mixed_in_modules: RefCell>>, } impl RModule { @@ -522,6 +523,7 @@ impl RModule { sym_id: RSym::new(name), procs: RefCell::new(HashMap::new()), consts: RefCell::new(HashMap::new()), + mixed_in_modules: RefCell::new(Vec::new()), } } @@ -531,8 +533,22 @@ impl RModule { } pub fn find_method(&self, name: &str) -> Option { + // First check this module's methods let procs = self.procs.borrow(); - procs.get(name).map(|p| p.clone()) + if let Some(p) = procs.get(name) { + return Some(p.clone()); + } + drop(procs); + + // Then check mixed-in modules + let mixed_in = self.mixed_in_modules.borrow(); + for module in mixed_in.iter() { + if let Some(p) = module.find_method(name) { + return Some(p); + } + } + + None } } From 5da3af5527f772ba782c83c450cf1ae4bbfa0d43 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 9 Dec 2025 22:28:45 +0900 Subject: [PATCH 019/314] Module works now --- mrubyedge/examples/module.rb | 7 +++- mrubyedge/src/yamrb/helpers.rs | 22 ++++++++++- mrubyedge/src/yamrb/prelude/class.rs | 3 +- mrubyedge/src/yamrb/prelude/module.rs | 56 ++++++++++++++++++++++++++- 4 files changed, 83 insertions(+), 5 deletions(-) diff --git a/mrubyedge/examples/module.rb b/mrubyedge/examples/module.rb index 6c4b2b2..561afe0 100644 --- a/mrubyedge/examples/module.rb +++ b/mrubyedge/examples/module.rb @@ -4,4 +4,9 @@ def module_method end end -p TestModule +class MyClass + include TestModule +end + +obj = MyClass.new +puts obj.module_method # Output: 42 \ No newline at end of file diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index 84d1314..a12c29b 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -2,7 +2,7 @@ use std::rc::Rc; use crate::Error; -use super::{optable::push_callinfo, value::{RClass, RFn, RObject, RProc, RSym, RValue}, vm::VM}; +use super::{optable::push_callinfo, value::{RClass, RFn, RModule, RObject, RProc, RSym, RValue}, vm::VM}; fn call_block(vm: &mut VM, block: RProc, recv: Rc, args: &[Rc]) -> Result, Error> { push_callinfo(vm, RSym::new("".to_string()), args.len()); @@ -123,3 +123,23 @@ pub fn mrb_define_method(_vm: &mut VM, klass: Rc, name: &str, method: RP let mut procs = klass.procs.borrow_mut(); procs.insert(name.to_string(), method); } + +pub fn mrb_define_module_cmethod(vm: &mut VM, module: Rc, name: &str, cmethod: RFn) { + let index = vm.register_fn(cmethod); + let method = RProc { + is_rb_func: false, + sym_id: Some(RSym::new(name.to_string())), + next: None, + irep: None, + func: Some(index), + environ: None, + block_self: None, + }; + let mut procs = module.procs.borrow_mut(); + procs.insert(name.to_string(), method); +} + +pub fn mrb_define_module_method(_vm: &mut VM, module: Rc, name: &str, method: RProc) { + let mut procs = module.procs.borrow_mut(); + procs.insert(name.to_string(), method); +} diff --git a/mrubyedge/src/yamrb/prelude/class.rs b/mrubyedge/src/yamrb/prelude/class.rs index 397394e..ee4b65d 100644 --- a/mrubyedge/src/yamrb/prelude/class.rs +++ b/mrubyedge/src/yamrb/prelude/class.rs @@ -5,7 +5,8 @@ use crate::{yamrb::{helpers::{mrb_define_cmethod, mrb_funcall}, value::*, vm::VM use super::shared_memory::mrb_shared_memory_new; pub(crate) fn initialize_class(vm: &mut VM) { - let class_class = vm.define_standard_class("Class"); + let module_class = vm.get_class_by_name("Module"); + let class_class = vm.define_standard_class_under("Class", module_class); mrb_define_cmethod(vm, class_class.clone(), "new", Box::new(mrb_class_new)); mrb_define_cmethod(vm, class_class.clone(), "attr_reader", Box::new(mrb_class_attr_reader)); diff --git a/mrubyedge/src/yamrb/prelude/module.rs b/mrubyedge/src/yamrb/prelude/module.rs index 743bc83..8571146 100644 --- a/mrubyedge/src/yamrb/prelude/module.rs +++ b/mrubyedge/src/yamrb/prelude/module.rs @@ -1,7 +1,59 @@ use std::rc::Rc; -use crate::{yamrb::{value::*, vm::VM}, Error}; +use crate::{ + yamrb::{helpers::mrb_define_cmethod, value::*, vm::VM}, + Error, +}; pub(crate) fn initialize_module(vm: &mut VM) { - let _module_class = vm.define_standard_class("Module"); + let module_class = vm.define_standard_class("Module"); + mrb_define_cmethod(vm, module_class, "include", Box::new(mrb_module_include)); +} + +fn mrb_module_include(vm: &mut VM, args: &[Rc]) -> Result, Error> { + if args.is_empty() { + return Err(Error::RuntimeError( + "Module#include expects at least one module".to_string(), + )); + } + + let self_obj = vm.getself()?; + let target_module = match &self_obj.value { + RValue::Class(klass) => klass.module.clone(), + RValue::Module(module) => module.clone(), + _ => { + return Err(Error::RuntimeError( + "Module#include must be called on class or module".to_string(), + )); + } + }; + + let arg0 = &args[0]; + let mixin = match &arg0.value { + RValue::Module(module) => module.clone(), + _ => return Err(Error::RuntimeError( + "Module#include expects module arguments".to_string(), + )), + }; + include_module(&target_module, mixin)?; + + Ok(self_obj) +} + +fn include_module(target: &Rc, mixin: Rc) -> Result<(), Error> { + if Rc::ptr_eq(target, &mixin) { + return Err(Error::RuntimeError("cannot include itself".to_string())); + } + + let already_present = { + let modules = target.mixed_in_modules.borrow(); + modules.iter().any(|m| Rc::ptr_eq(m, &mixin)) + }; + + if already_present { + return Err(Error::RuntimeError("module already included".to_string())); + } + + target.mixed_in_modules.borrow_mut().insert(0, mixin); + Ok(()) } From 04efec57a4d2db11ac377fb949271b61019e1a2b Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 9 Dec 2025 22:57:18 +0900 Subject: [PATCH 020/314] Fix method lookup in nested modules, incase of op_super --- mrubyedge/examples/module_nest.rb | 14 +++++ mrubyedge/src/yamrb/helpers.rs | 25 ++++++--- mrubyedge/src/yamrb/optable.rs | 27 +++++----- mrubyedge/src/yamrb/value.rs | 56 ++++++++++++++++++++ mrubyedge/src/yamrb/vm.rs | 1 + mrubyedge/tests/module.rs | 87 +++++++++++++++++++++++++++++++ 6 files changed, 190 insertions(+), 20 deletions(-) create mode 100644 mrubyedge/examples/module_nest.rb diff --git a/mrubyedge/examples/module_nest.rb b/mrubyedge/examples/module_nest.rb new file mode 100644 index 0000000..9b15b61 --- /dev/null +++ b/mrubyedge/examples/module_nest.rb @@ -0,0 +1,14 @@ +module TestModule + module ChildModule + def module_method + 42 + end + end + + class MyClass + include ChildModule + end +end + +obj = TestModule::MyClass.new +puts obj.module_method # Output: 42 \ No newline at end of file diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index a12c29b..5d755ee 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -2,10 +2,20 @@ use std::rc::Rc; use crate::Error; -use super::{optable::push_callinfo, value::{RClass, RFn, RModule, RObject, RProc, RSym, RValue}, vm::VM}; - -fn call_block(vm: &mut VM, block: RProc, recv: Rc, args: &[Rc]) -> Result, Error> { - push_callinfo(vm, RSym::new("".to_string()), args.len()); +use super::{optable::push_callinfo, value::{resolve_method, RClass, RFn, RModule, RObject, RProc, RSym, RValue}, vm::VM}; + +fn call_block( + vm: &mut VM, + block: RProc, + recv: Rc, + args: &[Rc], + method_info: Option<(RSym, Rc)>, +) -> Result, Error> { + let (method_id, method_owner) = match method_info { + Some((id, owner)) => (id, Some(owner)), + None => (RSym::new("".to_string()), None), + }; + push_callinfo(vm, method_id, args.len(), method_owner); let old_callinfo = vm.current_callinfo.take(); @@ -79,7 +89,7 @@ pub fn mrb_call_block(vm: &mut VM, block: Rc, recv: Option> Some(r) => r, None => block.block_self.clone().ok_or_else(|| Error::RuntimeError("No block self assigned".to_string()))?, }; - call_block(vm, block, recv, args) + call_block(vm, block, recv, args, None) } pub fn mrb_funcall(vm: &mut VM, top_self: Option>, name: &str, args: &[Rc]) -> Result, Error> { @@ -88,10 +98,11 @@ pub fn mrb_funcall(vm: &mut VM, top_self: Option>, name: &str, args: None => vm.getself()?, }; let binding = recv.as_ref().get_class(vm); - let method = binding.as_ref().find_method(name).ok_or_else(|| Error::NoMethodError(name.to_string()))?; + let (owner_module, method) = resolve_method(&binding, name).ok_or_else(|| Error::NoMethodError(name.to_string()))?; if method.is_rb_func { - call_block(vm, method, recv.clone(), args) + let method_id = method.sym_id.clone().unwrap_or_else(|| RSym::new(name.to_string())); + call_block(vm, method, recv.clone(), args, Some((method_id, owner_module))) } else { vm.current_regs_offset += 2; // FIXME: magick number? vm.current_regs()[0].replace(recv.clone()); diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index ae360ec..4a4a94c 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -465,7 +465,7 @@ pub(crate) fn consume_expr(vm: &mut VM, code: OpCode, operand: &Fetched, pos: us Ok(()) } -pub(crate) fn push_callinfo(vm: &mut VM, method_id: RSym, n_args: usize) { +pub(crate) fn push_callinfo(vm: &mut VM, method_id: RSym, n_args: usize, method_owner: Option>) { let callinfo = CALLINFO { prev: vm.current_callinfo.clone(), method_id, @@ -474,6 +474,7 @@ pub(crate) fn push_callinfo(vm: &mut VM, method_id: RSym, n_args: usize) { current_regs_offset: vm.current_regs_offset, n_args, target_class: vm.target_class.clone(), + method_owner, }; vm.current_callinfo = Some(Rc::new(callinfo)); } @@ -855,7 +856,7 @@ pub(crate) fn do_op_send(vm: &mut VM, recv_index: usize, blk_index: Option Result<(), Error> { - push_callinfo(vm, "".into(), 0); + push_callinfo(vm, "".into(), 0, None); vm.pc.set(0); let proc = vm.current_regs()[0].as_ref().cloned().ok_or_else(|| Error::internal("proc not found"))?; @@ -908,21 +909,21 @@ pub(crate) fn op_call(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_super(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; - let sym_id = vm.current_callinfo.as_ref() - .ok_or_else(|| Error::internal("no current callinfo"))? - .method_id.name.clone(); + let callinfo = vm.current_callinfo.as_ref() + .ok_or_else(|| Error::internal("no current callinfo"))?; + let sym_id = callinfo.method_id.name.clone(); + let owner_module = callinfo.method_owner.clone() + .ok_or_else(|| Error::RuntimeError("super called outside of method".to_string()))?; let recv = vm.getself()?; let args = (0..b) .map(|i| vm.get_current_regs_cloned((a + i + 1) as usize).expect("args too short for super")) .collect::>(); let klass = match &recv.value { - RValue::Instance(ins) => ins.class.as_ref(), + RValue::Instance(ins) => ins.class.clone(), _ => unreachable!("super must be called on instance"), }; - let superclass = klass.super_class.as_ref().ok_or_else(|| Error::internal("superclass not found"))?; - let sc_procs = superclass.procs.borrow(); - let method = sc_procs.get(&sym_id) + let (next_owner, method) = resolve_next_method(&klass, &sym_id, &owner_module) .ok_or_else(|| Error::NoMethodError(sym_id.clone()))?; if !method.is_rb_func { let func = vm.get_fn(method.func.unwrap()) @@ -945,7 +946,7 @@ pub(crate) fn op_super(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { } vm.current_regs()[a as usize].replace(recv.clone()); - push_callinfo(vm, method.sym_id.clone().unwrap(), b as usize); + push_callinfo(vm, method.sym_id.clone().unwrap(), b as usize, Some(next_owner)); vm.pc.set(0); vm.current_irep = method.irep.as_ref().ok_or_else(|| Error::internal("empty irep"))?.clone(); @@ -1470,7 +1471,7 @@ pub(crate) fn op_exec(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let recv = vm.get_current_regs_cloned(a as usize)?; vm.current_regs()[a as usize].replace(recv.clone()); - push_callinfo(vm, "".into(), 0); + push_callinfo(vm, "".into(), 0, None); vm.pc.set(0); let irep = vm.irep.reps[b as usize].clone(); diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 497ce5e..ab0b23d 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -552,6 +552,19 @@ impl RModule { } } +fn collect_module_chain(module: &Rc, chain: &mut Vec>, visited: &mut HashSet) { + let key = Rc::as_ptr(module) as usize; + if !visited.insert(key) { + return; + } + + chain.push(module.clone()); + let mixed_in = module.mixed_in_modules.borrow(); + for mixin in mixed_in.iter() { + collect_module_chain(mixin, chain, visited); + } +} + impl From> for RObject { fn from(value: Rc) -> Self { RObject::module(value) @@ -591,6 +604,49 @@ impl RClass { } } +fn collect_class_chain(class: &Rc, chain: &mut Vec>, visited: &mut HashSet) { + collect_module_chain(&class.module, chain, visited); + if let Some(super_class) = &class.super_class { + collect_class_chain(super_class, chain, visited); + } +} + +fn build_lookup_chain(class: &Rc) -> Vec> { + let mut chain = Vec::new(); + let mut visited = HashSet::new(); + collect_class_chain(class, &mut chain, &mut visited); + chain +} + +pub(crate) fn resolve_method(self_class: &Rc, name: &str) -> Option<(Rc, RProc)> { + for module in build_lookup_chain(self_class) { + if let Some(proc) = module.procs.borrow().get(name) { + return Some((module.clone(), proc.clone())); + } + } + None +} + +pub(crate) fn resolve_next_method( + self_class: &Rc, + name: &str, + current_owner: &Rc, +) -> Option<(Rc, RProc)> { + let mut passed = false; + for module in build_lookup_chain(self_class) { + if !passed { + if Rc::ptr_eq(&module, current_owner) { + passed = true; + } + continue; + } + if let Some(proc) = module.procs.borrow().get(name) { + return Some((module.clone(), proc.clone())); + } + } + None +} + // Provide convenient accessors to module fields impl std::ops::Deref for RClass { type Target = RModule; diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 722cc6a..080edf0 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -373,6 +373,7 @@ pub struct CALLINFO { pub current_regs_offset: usize, pub target_class: TargetContext, pub n_args: usize, + pub method_owner: Option>, } #[derive(Debug, Clone)] diff --git a/mrubyedge/tests/module.rs b/mrubyedge/tests/module.rs index 3e108e1..3b56243 100644 --- a/mrubyedge/tests/module.rs +++ b/mrubyedge/tests/module.rs @@ -25,3 +25,90 @@ TestModule assert!(matches!(result.tt, mrubyedge::yamrb::value::RType::Module)); } +#[test] +fn class_can_include_module_method() { + let script = r#" +module Printable + def greet + "hello" + end +end + +class User + include Printable +end + +User.new.greet +"#; + + let binary = mrbc_compile("module_include", script); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + + let value: String = result.as_ref().try_into().expect("greet should return string"); + assert_eq!(value, "hello"); +} + +#[test] +fn module_can_include_module_method() { + let script = r#" +module Core + def core_value + 123 + end +end + +module Superset + include Core +end + +class Wrapper + include Superset +end + +Wrapper.new.core_value +"#; + + let binary = mrbc_compile("module_include_module", script); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + + let value: i64 = result.as_ref().try_into().expect("core_value should return integer"); + assert_eq!(value, 123); +} + +#[test] +fn module_can_include_module_method_2() { + let script = r#" +module Core + def core_value + 123 + end +end + +module Superset + include Core + + def core_value + super + 1 + end +end + +class Wrapper + include Superset +end + +Wrapper.new.core_value +"#; + + let binary = mrbc_compile("module_include_module_2", script); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + + let value: i64 = result.as_ref().try_into().expect("core_value should return integer"); + assert_eq!(value, 124); +} + From 74890f9c10a952bbaf8271b9005dfd963ccb6fed Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 00:27:47 +0900 Subject: [PATCH 021/314] Support module as ns --- mrubyedge/examples/module_nest.rb | 1 + mrubyedge/src/yamrb/optable.rs | 83 +++++++++++++++++++++------- mrubyedge/src/yamrb/prelude/class.rs | 2 +- mrubyedge/src/yamrb/value.rs | 7 ++- mrubyedge/src/yamrb/vm.rs | 24 ++++++-- 5 files changed, 88 insertions(+), 29 deletions(-) diff --git a/mrubyedge/examples/module_nest.rb b/mrubyedge/examples/module_nest.rb index 9b15b61..fba9f51 100644 --- a/mrubyedge/examples/module_nest.rb +++ b/mrubyedge/examples/module_nest.rb @@ -11,4 +11,5 @@ class MyClass end obj = TestModule::MyClass.new +# puts obj puts obj.module_method # Output: 42 \ No newline at end of file diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 4a4a94c..6bd589e 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -625,17 +625,28 @@ pub(crate) fn op_setiv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_getconst(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; - let name = &vm.current_irep.syms[b as usize].name; - let cval = vm.consts.get(name).cloned(); - match cval { - Some(val) => { - vm.current_regs()[a as usize].replace(val); - }, - None => { - return Err(Error::NameError(name.clone())); + let name = vm.current_irep.syms[b as usize].name.clone(); + let mut current = current_namespace(vm); + + // Walk namespace chain upwards until found or reach top-level + loop { + if let Some(ns) = current.clone() { + if let Some(val) = ns.consts.borrow().get(&name).cloned() { + vm.current_regs()[a as usize].replace(val); + return Ok(()); + } + current = ns.parent.borrow().clone(); + } else { + break; } } - Ok(()) + + if let Some(val) = vm.consts.get(&name).cloned() { + vm.current_regs()[a as usize].replace(val); + return Ok(()); + } + + Err(Error::NameError(name)) } pub(crate) fn op_setconst(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { @@ -650,19 +661,21 @@ pub(crate) fn op_getmcnst(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; let recv = vm.get_current_regs_cloned(a as usize)?; let name = vm.current_irep.syms[b as usize].name.clone(); - let cval = match &recv.value { - RValue::Class(klass) => klass.getmcnst(&name).clone(), - _ => unreachable!("getmcnst must be called on class or module") + let mut module = match &recv.value { + RValue::Class(klass) => Some(klass.module.clone()), + RValue::Module(module) => Some(module.clone()), + _ => None, }; - match cval { - Some(val) => { + + while let Some(current) = module.clone() { + if let Some(val) = current.consts.borrow().get(&name).cloned() { vm.current_regs()[a as usize].replace(val); - }, - None => { - return Err(Error::NameError(name.clone())); + return Ok(()); } + module = current.parent.borrow().clone(); } - Ok(()) + + Err(Error::NameError(name.clone())) } pub(crate) fn op_getupvar(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { @@ -1433,6 +1446,17 @@ pub(crate) fn op_oclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { Ok(()) } +fn current_namespace(vm: &mut VM) -> Option> { + match vm.current_regs()[0].as_ref() { + Some(obj) => match &obj.value { + RValue::Class(klass) => Some(klass.module.clone()), + RValue::Module(module) => Some(module.clone()), + _ => None, + }, + None => None, + } +} + pub(crate) fn op_class(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; let superclass = vm.current_regs()[a as usize + 1].as_ref().cloned(); @@ -1449,8 +1473,17 @@ pub(crate) fn op_class(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { vm.object_class.clone() } }; + let parent_module = current_namespace(vm); let name = name.name; - let klass = vm.define_class(&name, Some(superclass)); + let klass = vm.define_class(&name, Some(superclass), parent_module.clone()); + + // register constant under parent namespace (if any) or top-level + let class_value = RObject::class(klass.clone()).to_refcount_assigned(); + if let Some(parent) = parent_module { + parent.consts.borrow_mut().insert(name.clone(), class_value); + } else { + vm.consts.insert(name.clone(), class_value); + } vm.current_regs()[a as usize].replace(Rc::new(klass.into())); Ok(()) @@ -1460,7 +1493,15 @@ pub(crate) fn op_module(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; let name = vm.current_irep.syms[b as usize].clone(); let name = name.name; - let module = vm.define_module(&name); + let parent_module = current_namespace(vm); + let module = vm.define_module(&name, parent_module.clone()); + + let module_value = RObject::module(module.clone()).to_refcount_assigned(); + if let Some(parent) = parent_module { + parent.consts.borrow_mut().insert(name.clone(), module_value); + } else { + vm.consts.insert(name.clone(), module_value); + } vm.current_regs()[a as usize].replace(Rc::new(module.into())); Ok(()) @@ -1474,7 +1515,7 @@ pub(crate) fn op_exec(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { push_callinfo(vm, "".into(), 0, None); vm.pc.set(0); - let irep = vm.irep.reps[b as usize].clone(); + let irep = vm.current_irep.reps[b as usize].clone(); vm.current_irep = irep; vm.current_regs_offset += a as usize; diff --git a/mrubyedge/src/yamrb/prelude/class.rs b/mrubyedge/src/yamrb/prelude/class.rs index ee4b65d..e13884b 100644 --- a/mrubyedge/src/yamrb/prelude/class.rs +++ b/mrubyedge/src/yamrb/prelude/class.rs @@ -139,7 +139,7 @@ fn test_class_attr_accessor() { use crate::yamrb::helpers::*; let mut vm = VM::empty(); - let class = vm.define_class("Test", None); + let class = vm.define_class("Test", None, None); let args = vec![RObject::symbol("foo".into()).to_refcount_assigned()]; vm.current_regs()[0].replace(RObject::class(class.clone()).to_refcount_assigned()); mrb_class_attr_acceccor(&mut vm, &args).expect("mrb_class_attr_acceccor failed"); diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index ab0b23d..a7046de 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -514,6 +514,7 @@ pub struct RModule { pub procs: RefCell>, pub consts: RefCell>>, pub mixed_in_modules: RefCell>>, + pub parent: RefCell>>, } impl RModule { @@ -524,6 +525,7 @@ impl RModule { procs: RefCell::new(HashMap::new()), consts: RefCell::new(HashMap::new()), mixed_in_modules: RefCell::new(Vec::new()), + parent: RefCell::new(None), } } @@ -578,8 +580,11 @@ pub struct RClass { } impl RClass { - pub fn new(name: &str, super_class: Option>) -> Self { + pub fn new(name: &str, super_class: Option>, parent_module: Option>) -> Self { let module = Rc::new(RModule::new(name)); + if let Some(parent) = parent_module { + module.parent.replace(Some(parent)); + } RClass { module, super_class, diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 080edf0..660ebb8 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -22,6 +22,15 @@ pub enum TargetContext { Module(Rc), } +impl TargetContext { + pub fn name(&self) -> String { + match self { + TargetContext::Class(c) => c.sym_id.name.clone(), + TargetContext::Module(m) => m.sym_id.name.clone(), + } + } +} + pub struct VM { pub irep: Rc, @@ -82,7 +91,7 @@ impl VM { let consts = HashMap::new(); let builtin_class_table = HashMap::new(); - let object_class = Rc::new(RClass::new("Object", None)); + let object_class = Rc::new(RClass::new("Object", None, None)); let id = 1; // TODO generator let bytecode = Vec::new(); @@ -256,34 +265,37 @@ impl VM { self.builtin_class_table.get(name).cloned().expect(format!("Class {} not found", name).as_str()) } - pub(crate) fn define_class(&mut self, name: &str, superclass: Option>) -> Rc { + pub(crate) fn define_class(&mut self, name: &str, superclass: Option>, parent_module: Option>) -> Rc { let superclass = match superclass { Some(c) => c, None => self.object_class.clone(), }; let class = Rc::new( - RClass::new(name, Some(superclass)), + RClass::new(name, Some(superclass), parent_module), ); let object = RObject::class(class.clone()).to_refcount_assigned(); self.consts.insert(name.to_string(), object); class } - pub(crate) fn define_module(&mut self, name: &str) -> Rc { + pub(crate) fn define_module(&mut self, name: &str, parent_module: Option>) -> Rc { let module = Rc::new(RModule::new(name)); + if let Some(parent) = parent_module { + module.parent.replace(Some(parent)); + } let object = RObject::module(module.clone()).to_refcount_assigned(); self.consts.insert(name.to_string(), object); module } pub(crate) fn define_standard_class(&mut self, name: &'static str) -> Rc { - let class = self.define_class(name, None); + let class = self.define_class(name, None, None); self.builtin_class_table.insert(name, class.clone()); class } pub(crate) fn define_standard_class_under(&mut self, name: &'static str, sklass: Rc) -> Rc { - let class = self.define_class(name, Some(sklass)); + let class = self.define_class(name, Some(sklass.clone()), Some(sklass.module.clone())); self.builtin_class_table.insert(name, class.clone()); class } From 5f4bc29410f13c2ef4ed3f40b0ce76555d07b242 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 00:40:04 +0900 Subject: [PATCH 022/314] Fix default inspect --- mrubyedge/examples/module_nest.rb | 2 +- mrubyedge/src/yamrb/prelude/object.rs | 4 +++- mrubyedge/src/yamrb/value.rs | 11 +++++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/mrubyedge/examples/module_nest.rb b/mrubyedge/examples/module_nest.rb index fba9f51..20cd740 100644 --- a/mrubyedge/examples/module_nest.rb +++ b/mrubyedge/examples/module_nest.rb @@ -11,5 +11,5 @@ class MyClass end obj = TestModule::MyClass.new -# puts obj +p obj puts obj.module_method # Output: 42 \ No newline at end of file diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index 9e8803d..14b513d 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -130,7 +130,9 @@ pub fn mrb_object_object_id(vm: &mut VM, _args: &[Rc]) -> Result]) -> Result, Error> { let obj = vm.getself()?; - Ok(Rc::new(RObject::string(format!("{:?}", obj)))) + let class = obj.get_class(vm); + let addr = format!("{:018p}", Rc::as_ptr(&obj)); + Ok(Rc::new(RObject::string(format!("#<{}:{}>", class.full_name(), addr)))) } pub fn mrb_object_raise(_vm: &mut VM, args: &[Rc]) -> Result, Error> { diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index a7046de..994c961 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -607,6 +607,17 @@ impl RClass { } } } + + pub fn full_name(&self) -> String { + let mut names = Vec::new(); + let mut current: Option> = Some(self.module.clone()); + while let Some(module) = current { + names.push(module.sym_id.name.clone()); + current = module.parent.borrow().clone(); + } + names.reverse(); + names.join("::") + } } fn collect_class_chain(class: &Rc, chain: &mut Vec>, visited: &mut HashSet) { From ef96e7dee75e859524e653f3386c082752116c2d Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 00:44:26 +0900 Subject: [PATCH 023/314] Add test --- mrubyedge/tests/module.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/mrubyedge/tests/module.rs b/mrubyedge/tests/module.rs index 3b56243..2a077fc 100644 --- a/mrubyedge/tests/module.rs +++ b/mrubyedge/tests/module.rs @@ -50,6 +50,33 @@ User.new.greet assert_eq!(value, "hello"); } +#[test] +fn modules_can_be_used_as_namespace() { + let script = r#" +module Outer + module Printable + def greet + "hello" + end + end + + class User + include Printable + end +end + +Outer::User.new.greet +"#; + + let binary = mrbc_compile("module_include_ns", script); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + + let value: String = result.as_ref().try_into().expect("greet should return string"); + assert_eq!(value, "hello"); +} + #[test] fn module_can_include_module_method() { let script = r#" From ce4dd58bb6f961e820b58267eaaa90f7658b248c Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 00:49:43 +0900 Subject: [PATCH 024/314] Release 1.0.7 --- Cargo.lock | 12 ++++++------ mrubyedge/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0791eaa..d3e421a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -572,21 +572,21 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38983e69b290e06aa04c1c22f63fe706f1e316fb509f44917495ceb8a63bb9" dependencies = [ - "criterion", "getrandom", - "mec-mrbc-sys", "plain", "simple_endian", ] [[package]] name = "mrubyedge" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38983e69b290e06aa04c1c22f63fe706f1e316fb509f44917495ceb8a63bb9" +version = "1.0.7" dependencies = [ + "criterion", "getrandom", + "mec-mrbc-sys", "plain", "simple_endian", ] @@ -598,7 +598,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.0.6", "nom", "rand", "syn", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 663d419..846918e 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.0.6" +version = "1.0.7" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From 971fd2a3e1f374b40b18a735788997134b9dbbd6 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 00:56:13 +0900 Subject: [PATCH 025/314] [WIP] Basic docs --- mrubyedge/src/yamrb/helpers.rs | 58 ++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index 5d755ee..584ce89 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -79,6 +79,18 @@ fn call_block( } } +/// Calls a Ruby block (Proc) with the given receiver and arguments. +/// +/// # Arguments +/// +/// * `vm` - The virtual machine instance +/// * `block` - The block object to call (must be a Proc) +/// * `recv` - Optional receiver object. If None, uses the block's self +/// * `args` - Array of arguments to pass to the block +/// +/// # Returns +/// +/// Returns the result of the block execution or an error if the call fails. pub fn mrb_call_block(vm: &mut VM, block: Rc, recv: Option>, args: &[Rc]) -> Result, Error> { let block = match &block.value { RValue::Proc(p) => p.clone(), @@ -92,6 +104,20 @@ pub fn mrb_call_block(vm: &mut VM, block: Rc, recv: Option> call_block(vm, block, recv, args, None) } +/// Calls a method on an object by name with the given arguments. +/// +/// This is the main function call interface for invoking Ruby methods from Rust code. +/// +/// # Arguments +/// +/// * `vm` - The virtual machine instance +/// * `top_self` - Optional receiver object. If None, uses the "top self" +/// * `name` - The name of the method to call +/// * `args` - Array of arguments to pass to the method +/// +/// # Returns +/// +/// Returns the result of the method call or an error if the method is not found or execution fails. pub fn mrb_funcall(vm: &mut VM, top_self: Option>, name: &str, args: &[Rc]) -> Result, Error> { let recv: Rc = match top_self { Some(obj) => obj, @@ -115,6 +141,14 @@ pub fn mrb_funcall(vm: &mut VM, top_self: Option>, name: &str, args: } } +/// Defines a C method (native Rust function) on a Ruby class. +/// +/// # Arguments +/// +/// * `vm` - The virtual machine instance +/// * `klass` - The class to define the method on +/// * `name` - The name of the method +/// * `cmethod` - The native Rust function to bind as a method pub fn mrb_define_cmethod(vm: &mut VM, klass: Rc, name: &str, cmethod: RFn) { let index = vm.register_fn(cmethod); let method = RProc { @@ -130,11 +164,27 @@ pub fn mrb_define_cmethod(vm: &mut VM, klass: Rc, name: &str, cmethod: R procs.insert(name.to_string(), method); } +/// Defines a Ruby method (RProc) on a Ruby class. +/// +/// # Arguments +/// +/// * `_vm` - The virtual machine instance (unused) +/// * `klass` - The class to define the method on +/// * `name` - The name of the method +/// * `method` - The Ruby proc to bind as a method pub fn mrb_define_method(_vm: &mut VM, klass: Rc, name: &str, method: RProc) { let mut procs = klass.procs.borrow_mut(); procs.insert(name.to_string(), method); } +/// Defines a C method (native Rust function) on a Ruby module. +/// +/// # Arguments +/// +/// * `vm` - The virtual machine instance +/// * `module` - The module to define the method on +/// * `name` - The name of the method +/// * `cmethod` - The native Rust function to bind as a method pub fn mrb_define_module_cmethod(vm: &mut VM, module: Rc, name: &str, cmethod: RFn) { let index = vm.register_fn(cmethod); let method = RProc { @@ -150,6 +200,14 @@ pub fn mrb_define_module_cmethod(vm: &mut VM, module: Rc, name: &str, c procs.insert(name.to_string(), method); } +/// Defines a Ruby method (RProc) on a Ruby module. +/// +/// # Arguments +/// +/// * `_vm` - The virtual machine instance (unused) +/// * `module` - The module to define the method on +/// * `name` - The name of the method +/// * `method` - The Ruby proc to bind as a method pub fn mrb_define_module_method(_vm: &mut VM, module: Rc, name: &str, method: RProc) { let mut procs = module.procs.borrow_mut(); procs.insert(name.to_string(), method); From 44d182b51766f648ef0bc77b54ab761a0ab4dd22 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 07:51:56 +0900 Subject: [PATCH 026/314] Refactor --- mrubyedge/src/yamrb/value.rs | 20 ++++++++++++-------- mrubyedge/src/yamrb/vm.rs | 22 ++++++++++++---------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 994c961..349db04 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -552,6 +552,17 @@ impl RModule { None } + + pub fn full_name(self: &Rc) -> String { + let mut names = Vec::new(); + let mut current: Option> = Some(self.clone()); + while let Some(module) = current { + names.push(module.sym_id.name.clone()); + current = module.parent.borrow().clone(); + } + names.reverse(); + names.join("::") + } } fn collect_module_chain(module: &Rc, chain: &mut Vec>, visited: &mut HashSet) { @@ -609,14 +620,7 @@ impl RClass { } pub fn full_name(&self) -> String { - let mut names = Vec::new(); - let mut current: Option> = Some(self.module.clone()); - while let Some(module) = current { - names.push(module.sym_id.name.clone()); - current = module.parent.borrow().clone(); - } - names.reverse(); - names.join("::") + self.module.full_name() } } diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 660ebb8..6828f83 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -25,8 +25,8 @@ pub enum TargetContext { impl TargetContext { pub fn name(&self) -> String { match self { - TargetContext::Class(c) => c.sym_id.name.clone(), - TargetContext::Module(m) => m.sym_id.name.clone(), + TargetContext::Class(c) => c.full_name(), + TargetContext::Module(m) => m.full_name(), } } } @@ -254,7 +254,7 @@ impl VM { pub(crate) fn register_fn(&mut self, f: RFn) -> usize { self.fn_table.push(Rc::new(f)); - return self.fn_table.len() - 1; + self.fn_table.len() - 1 } pub(crate) fn get_fn(&self, i: usize) -> Option> { @@ -265,7 +265,7 @@ impl VM { self.builtin_class_table.get(name).cloned().expect(format!("Class {} not found", name).as_str()) } - pub(crate) fn define_class(&mut self, name: &str, superclass: Option>, parent_module: Option>) -> Rc { + pub fn define_class(&mut self, name: &str, superclass: Option>, parent_module: Option>) -> Rc { let superclass = match superclass { Some(c) => c, None => self.object_class.clone(), @@ -278,7 +278,7 @@ impl VM { class } - pub(crate) fn define_module(&mut self, name: &str, parent_module: Option>) -> Rc { + pub fn define_module(&mut self, name: &str, parent_module: Option>) -> Rc { let module = Rc::new(RModule::new(name)); if let Some(parent) = parent_module { module.parent.replace(Some(parent)); @@ -397,25 +397,27 @@ pub struct ENV { } impl ENV { - pub fn has_captured(&self) -> bool { + #[allow(unused)] + pub(crate) fn has_captured(&self) -> bool { self.captured.borrow().is_some() } - pub fn capture(&self, regs: &[Option>]) { + #[allow(unused)] + pub(crate) fn capture(&self, regs: &[Option>]) { let mut captured = self.captured.borrow_mut(); captured.replace(regs.iter().map(|r| r.clone()).collect()); } - pub fn capture_no_clone(&self, regs: Vec>>) { + pub(crate) fn capture_no_clone(&self, regs: Vec>>) { let mut captured = self.captured.borrow_mut(); captured.replace(regs); } - pub fn expire(&self) { + pub(crate) fn expire(&self) { self.is_expired.set(true); } - pub fn expired(&self) -> bool { + pub(crate) fn expired(&self) -> bool { self.is_expired.get() } } \ No newline at end of file From 2226a8e330a9b06c66f2c42f4e68e71af9297323 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 11:46:24 +0900 Subject: [PATCH 027/314] Update more docs --- mrubyedge/src/rite/mod.rs | 3 +++ mrubyedge/src/yamrb/mod.rs | 3 +++ mrubyedge/src/yamrb/prelude/mod.rs | 4 ++++ mrubyedge/src/yamrb/value.rs | 17 +++++++++++++++++ mrubyedge/src/yamrb/vm.rs | 21 +++++++++++++++++++++ 5 files changed, 48 insertions(+) diff --git a/mrubyedge/src/rite/mod.rs b/mrubyedge/src/rite/mod.rs index 8c9975c..2f2c6a7 100644 --- a/mrubyedge/src/rite/mod.rs +++ b/mrubyedge/src/rite/mod.rs @@ -1,3 +1,6 @@ +//! Entry point that groups the mruby/rite (mrb) helpers. +//! It understands the binary layout, instruction stream, and marker metadata, +//! exposing higher-level APIs via the `rite` submodule. pub mod binfmt; pub mod insn; pub mod marker; diff --git a/mrubyedge/src/yamrb/mod.rs b/mrubyedge/src/yamrb/mod.rs index e451f6b..cf993d8 100644 --- a/mrubyedge/src/yamrb/mod.rs +++ b/mrubyedge/src/yamrb/mod.rs @@ -1,3 +1,6 @@ +//! Yet Another mruby (yamrb) runtime layer. +//! Provides value representation, opcode tables, helpers, and the VM itself +//! so mruby bytecode can execute inside Rust. pub mod optable; pub mod value; pub mod shared_memory; diff --git a/mrubyedge/src/yamrb/prelude/mod.rs b/mrubyedge/src/yamrb/prelude/mod.rs index 71723f0..6075145 100644 --- a/mrubyedge/src/yamrb/prelude/mod.rs +++ b/mrubyedge/src/yamrb/prelude/mod.rs @@ -1,3 +1,7 @@ +//! Prelude that wires up the built-in Ruby-like standard library for yamrb. +//! Each submodule exposes initializers that install core classes and constants +//! into a [`VM`] so user bytecode starts with the expected environment. + use super::vm::VM; pub mod object; diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 349db04..c9eafe7 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -7,6 +7,7 @@ use crate::Error; use super::vm::{ENV, IREP, VM}; use super::shared_memory::SharedMemory; +/// Tag that identifies each runtime object variant handled by the VM. #[derive(Debug, Clone, Copy)] pub enum RType { Bool, @@ -27,6 +28,7 @@ pub enum RType { Nil, } +/// Actual storage for Ruby values, including boxed objects and immediates. #[derive(Debug, Clone)] pub enum RValue { Bool(bool), @@ -47,6 +49,8 @@ pub enum RValue { Nil, } +/// Canonical representation used when Ruby objects serve as Hash keys. +/// TODO: This will be used to implement Hash#hash and Hash#eql?. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum ValueHasher { Bool(bool), @@ -57,6 +61,7 @@ pub enum ValueHasher { Class(String), } +/// Normalized form used to compare Ruby values for equality in tests and Hashes. #[derive(Debug, Clone, PartialEq)] pub enum ValueEquality { Bool(bool), @@ -72,6 +77,7 @@ pub enum ValueEquality { Nil, } +/// Key-value specific equality helper storing both keys and resolved values. #[derive(Debug, Clone)] pub struct ValueEqualityForKeyValue(HashSet, HashMap); @@ -89,6 +95,7 @@ impl PartialEq for ValueEqualityForKeyValue { } } +/// Heap-allocated Ruby object wrapper containing type tag, value, and object id. #[derive(Debug, Clone)] pub struct RObject { pub tt: RType, @@ -508,6 +515,7 @@ impl TryFrom<&RObject> for *mut u8 { } } +/// Ruby module with methods, constants, and mixin relationships. #[derive(Debug, Clone)] pub struct RModule { pub sym_id: RSym, @@ -584,6 +592,8 @@ impl From> for RObject { } } +/// Ruby class metadata, including its module namespace and optional superclass. +/// Attributes and methods required for Class implementation are stored in the associated RModule. #[derive(Debug, Clone)] pub struct RClass { pub module: Rc, @@ -681,6 +691,7 @@ impl From> for RObject { } } +/// Backing storage for Ruby object instances (instance variables and data). #[derive(Debug, Clone)] pub struct RInstance { pub class: Rc, @@ -689,6 +700,7 @@ pub struct RInstance { pub ref_count: usize, } +/// Ruby procedure (so-called `Proc`) representation (Ruby-defined or native function pointer). #[derive(Debug, Clone)] pub struct RProc { pub is_rb_func: bool, @@ -700,8 +712,10 @@ pub struct RProc { pub block_self: Option>, } +/// Native Rust callable used to implement Ruby methods in the VM. pub type RFn = Box]) -> Result, Error>>; +/// Interned symbol name used across the VM to identify methods and constants. #[derive(Debug, Clone, PartialEq, Eq)] pub struct RSym { pub name: String @@ -723,6 +737,8 @@ impl From<&'static str> for RSym { } } +/// Constant pool entry emitted by mruby bytecode. +/// Strings or binary blobs is supported for now. #[derive(Debug, Clone)] pub enum RPool { Str(String), @@ -738,6 +754,7 @@ impl RPool { } } +/// Runtime exception object storing class, error payload, and backtrace info. #[derive(Debug)] pub struct RException { pub class: Rc, diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 6828f83..43e7879 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -62,12 +62,16 @@ pub struct VM { } impl VM { + /// Builds a VM from a parsed Rite chunk, consuming the bytecode and + /// preparing the VM so it can be executed via [`VM::run`]. pub fn open(rite: &mut Rite) -> VM { let irep = rite_to_irep(rite); let vm = VM::new_by_raw_irep(irep); vm } + /// Returns a VM backed by an empty IREP that immediately executes a + /// `STOP` instruction. Useful for tests or placeholder VMs. pub fn empty() -> VM { let irep = IREP { __id: 0, @@ -85,6 +89,9 @@ impl VM { Self::new_by_raw_irep(irep) } + /// Creates a VM directly from a raw [`IREP`] tree without going through the + /// Rite loader. This wires up the register file, globals, and builtin + /// tables and runs the prelude to seed standard classes. pub fn new_by_raw_irep(irep: IREP) -> VM { let irep = Rc::new(irep); let globals = HashMap::new(); @@ -135,6 +142,9 @@ impl VM { vm } + /// Executes the current IREP until completion, returning the value in + /// register 0 or propagating any raised exception as an error. The + /// top-level `self` is initialized automatically before evaluation. pub fn run(&mut self) -> Result, Box> { let class = self.object_class.clone(); // Insert top_self @@ -244,10 +254,14 @@ impl VM { self.current_regs()[i].take().ok_or_else(|| Error::internal(format!("register {} is not assigned", i))) } + /// Returns the current `self` object from register 0, or an error if it has + /// not been initialized yet. pub fn getself(&mut self) -> Result, Error> { self.get_current_regs_cloned(0) } + /// Retrieves `self` without error handling, panicking if register 0 is + /// empty. Prefer [`VM::getself`] when the value may be absent. pub fn must_getself(&mut self) -> Rc { self.current_regs()[0].clone().expect("self is not assigned") } @@ -261,10 +275,15 @@ impl VM { self.fn_table.get(i).cloned() } + /// Looks up a previously defined builtin class by name. Panics if the + /// class does not exist, which usually signals a missing prelude setup. pub fn get_class_by_name(&self, name: &str) -> Rc { self.builtin_class_table.get(name).cloned().expect(format!("Class {} not found", name).as_str()) } + /// Defines a new class under the optional parent module, inheriting from + /// `superclass` or `Object` by default, and registers it in the constant + /// table. The resulting class object is returned for further mutation. pub fn define_class(&mut self, name: &str, superclass: Option>, parent_module: Option>) -> Rc { let superclass = match superclass { Some(c) => c, @@ -278,6 +297,8 @@ impl VM { class } + /// Defines a new module, optionally nested under another module, and stores + /// it in the VM's constant table so it becomes accessible to Ruby code. pub fn define_module(&mut self, name: &str, parent_module: Option>) -> Rc { let module = Rc::new(RModule::new(name)); if let Some(parent) = parent_module { From 9a4dc545049f5e5a3c5040bb701e6f6e50f1ab55 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 12:22:04 +0900 Subject: [PATCH 028/314] Add toplevel document w/ doctest --- mrubyedge/src/lib.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/mrubyedge/src/lib.rs b/mrubyedge/src/lib.rs index 5619206..3c7a259 100644 --- a/mrubyedge/src/lib.rs +++ b/mrubyedge/src/lib.rs @@ -1,3 +1,69 @@ +//! mruby/edge is a pure-Rust reimplementation of the mruby VM that keeps its +//! core execution engine `no_std`-friendly while striving for behavioral +//! compatibility with upstream mruby. It primarily targets WebAssembly +//! deployments, yet remains embeddable inside ordinary Rust binaries for host +//! tooling or native experimentation. +//! +//! Key goals: +//! - Written in idiomatic Rust with a `no_std` core so it can run in +//! constrained environments. +//! - Behavior compatible with the mruby VM so existing bytecode executes as-is. +//! - First-class WebAssembly target support. +//! - Ergonomic embedding in general Rust applications. +//! +//! Basic initialization follows the pattern shown in `examples/newvm.rs`: +//! +//! ```no_run +//! use mrubyedge::yamrb::{op, vm, value::RSym}; +//! use mrubyedge::rite::insn::{Fetched, OpCode}; +//! +//! fn main() -> Result<(), Box> { +//! let irep = vm::IREP { +//! __id: 0, +//! nlocals: 0, +//! nregs: 7, +//! rlen: 0, +//! code: vec![ +//! op::Op { code: OpCode::LOADI_1, operand: Fetched::B(1), pos: 0, len: 2 }, +//! op::Op { code: OpCode::LOADI_2, operand: Fetched::B(2), pos: 2, len: 2 }, +//! op::Op { code: OpCode::MOVE, operand: Fetched::BB(4, 1), pos: 4, len: 3 }, +//! op::Op { code: OpCode::MOVE, operand: Fetched::BB(5, 2), pos: 7, len: 3 }, +//! op::Op { code: OpCode::ADD, operand: Fetched::B(4), pos: 10, len: 2 }, +//! op::Op { code: OpCode::SSEND, operand: Fetched::BBB(3, 0, 1), pos: 12, len: 4 }, +//! op::Op { code: OpCode::RETURN, operand: Fetched::B(3), pos: 16, len: 2 }, +//! op::Op { code: OpCode::STOP, operand: Fetched::Z, pos: 18, len: 1 }, +//! ], +//! syms: vec![RSym::new("puts".to_string())], +//! pool: Vec::new(), +//! reps: Vec::new(), +//! catch_target_pos: Vec::new(), +//! }; +//! +//! let mut vm = vm::VM::new_by_raw_irep(irep); +//! let value = vm.run()?; +//! println!("{:?}", value); +//! Ok(()) +//! } +//! ``` +//! +//! Loading a precompiled `*.mrb` produced by `mrbc` is also straightforward +//! using `include_bytes!`: +//! +//! ```no_run +//! use mrubyedge::rite; +//! use mrubyedge::yamrb::vm; +//! +//! // Bundle the compiled script at build time. +//! const SCRIPT: &[u8] = include_bytes!("../examples/simple.mrb"); +//! +//! fn run_embedded() -> Result<(), Box> { +//! let mut rite = rite::load(SCRIPT)?; +//! let mut vm = vm::VM::open(&mut rite); +//! let value = vm.run()?; +//! println!("{:?}", value); +//! Ok(()) +//! } +//! ``` pub mod eval; pub mod error; pub mod rite; From 9073c74816ca421391af8a171ce7552a455866c1 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 19:26:00 +0900 Subject: [PATCH 029/314] Introduce singleton class --- mrubyedge/src/yamrb/optable.rs | 4 + mrubyedge/src/yamrb/prelude/shared_memory.rs | 3 +- mrubyedge/src/yamrb/value.rs | 102 +++++++++++++------ mrubyedge/src/yamrb/vm.rs | 1 + 4 files changed, 78 insertions(+), 32 deletions(-) diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 6bd589e..c34c73c 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1362,6 +1362,7 @@ pub(crate) fn op_lambda(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { block_self: Some(vm.getself()?), }), object_id: u64::MAX.into(), + singleton_class: RefCell::new(None), }; vm.current_regs()[a as usize].replace(val.to_refcount_assigned()); Ok(()) @@ -1392,6 +1393,7 @@ pub(crate) fn op_block(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { block_self: Some(vm.getself()?), }), object_id: u64::MAX.into(), + singleton_class: RefCell::new(None), }; vm.current_regs()[a as usize].replace(val.to_refcount_assigned()); Ok(()) @@ -1412,6 +1414,7 @@ pub(crate) fn op_method(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { block_self: None, }), object_id: u64::MAX.into(), + singleton_class: RefCell::new(None), }; vm.current_regs()[a as usize].replace(val.to_refcount_assigned()); Ok(()) @@ -1434,6 +1437,7 @@ fn do_op_range(vm: &mut VM, a: usize, b: usize, exclusive: bool) -> Result<(), E tt: super::value::RType::Range, value: super::value::RValue::Range(val1, val2, exclusive), object_id: u64::MAX.into(), + singleton_class: RefCell::new(None), }; vm.current_regs()[a as usize].replace(val.to_refcount_assigned()); Ok(()) diff --git a/mrubyedge/src/yamrb/prelude/shared_memory.rs b/mrubyedge/src/yamrb/prelude/shared_memory.rs index 9b2a066..5347d25 100644 --- a/mrubyedge/src/yamrb/prelude/shared_memory.rs +++ b/mrubyedge/src/yamrb/prelude/shared_memory.rs @@ -23,7 +23,8 @@ pub fn mrb_shared_memory_new(_vm: &mut VM, args: &[Rc]) -> Result, + + pub singleton_class: RefCell>>, } impl RObject { @@ -109,6 +112,7 @@ impl RObject { tt: RType::Nil, value: RValue::Nil, object_id: 4.into(), + singleton_class: RefCell::new(None), } } @@ -117,6 +121,7 @@ impl RObject { tt: RType::Bool, value: RValue::Bool(b), object_id: (if b { 20 } else { 0 }).into(), + singleton_class: RefCell::new(None), } } @@ -125,6 +130,7 @@ impl RObject { tt: RType::Symbol, value: RValue::Symbol(sym), object_id: 2.into(), // TODO: calc the same id for the same symbol + singleton_class: RefCell::new(None), } } @@ -141,6 +147,7 @@ impl RObject { tt: RType::Integer, value: RValue::Integer(n), object_id: object_id.into(), + singleton_class: RefCell::new(None), } } @@ -149,6 +156,7 @@ impl RObject { tt: RType::Float, value: RValue::Float(f), object_id: (f.to_bits() as u64).into(), + singleton_class: RefCell::new(None), } } @@ -157,6 +165,7 @@ impl RObject { tt: RType::String, value: RValue::String(RefCell::new(s.into_bytes())), object_id: (u64::MAX).into(), + singleton_class: RefCell::new(None), } } @@ -165,6 +174,7 @@ impl RObject { tt: RType::String, value: RValue::String(RefCell::new(v)), object_id: (u64::MAX).into(), + singleton_class: RefCell::new(None), } } @@ -173,6 +183,7 @@ impl RObject { tt: RType::Array, value: RValue::Array(RefCell::new(v)), object_id: (u64::MAX).into(), + singleton_class: RefCell::new(None), } } @@ -181,6 +192,7 @@ impl RObject { tt: RType::Hash, value: RValue::Hash(RefCell::new(h)), object_id: (u64::MAX).into(), + singleton_class: RefCell::new(None), } } @@ -189,6 +201,7 @@ impl RObject { tt: RType::Range, value: RValue::Range(start, end, exclusive), object_id: (u64::MAX).into(), + singleton_class: RefCell::new(None), } } @@ -197,6 +210,7 @@ impl RObject { tt: RType::Class, value: RValue::Class(c), object_id: (u64::MAX).into(), + singleton_class: RefCell::new(None), } } @@ -205,6 +219,7 @@ impl RObject { tt: RType::Module, value: RValue::Module(m), object_id: (u64::MAX).into(), + singleton_class: RefCell::new(None), } } @@ -218,6 +233,7 @@ impl RObject { ref_count: 1, }), object_id: (u64::MAX).into(), + singleton_class: RefCell::new(None), } } @@ -226,6 +242,7 @@ impl RObject { tt: RType::Exception, value: RValue::Exception(e), object_id: (u64::MAX).into(), + singleton_class: RefCell::new(None), } } @@ -269,9 +286,7 @@ impl RObject { RValue::Symbol(s) => Ok(ValueHasher::Symbol(s.name.clone())), RValue::String(s) => Ok(ValueHasher::String(s.borrow().clone())), RValue::Class(c) => Ok(ValueHasher::Class(c.sym_id.name.clone())), - _ => { - Err(Error::TypeMismatch) - } + _ => Err(Error::TypeMismatch), } } @@ -284,27 +299,24 @@ impl RObject { RValue::String(s) => ValueEquality::String(s.borrow().clone()), RValue::Class(c) => ValueEquality::Class(c.sym_id.name.clone()), RValue::Range(s, e, ex) => { - ValueEquality::Range( - Box::new(s.as_eq_value()), - Box::new(e.as_eq_value()), - *ex - ) - }, + ValueEquality::Range(Box::new(s.as_eq_value()), Box::new(e.as_eq_value()), *ex) + } RValue::Array(a) => { let arr = a.borrow().iter().map(|v| v.as_eq_value()).collect(); ValueEquality::Array(arr) - }, + } RValue::Hash(ha) => { let keys: HashSet<_> = ha.borrow().keys().map(|k| k.clone()).collect(); ValueEquality::KeyValue(ValueEqualityForKeyValue( keys, - ha.borrow().iter().map(|(k, (_, v))| (k.clone(), v.as_ref().as_eq_value())).collect(), + ha.borrow() + .iter() + .map(|(k, (_, v))| (k.clone(), v.as_ref().as_eq_value())) + .collect(), )) - }, - RValue::Nil => ValueEquality::Nil, - _ => { - ValueEquality::ObjectID(self.object_id.get()) } + RValue::Nil => ValueEquality::Nil, + _ => ValueEquality::ObjectID(self.object_id.get()), } } @@ -319,7 +331,7 @@ impl RObject { } else { vm.get_class_by_name("FalseClass") } - }, + } RValue::Symbol(_) => vm.get_class_by_name("Symbol"), RValue::Integer(_) => vm.get_class_by_name("Integer"), RValue::Float(_) => vm.get_class_by_name("Float"), @@ -334,6 +346,26 @@ impl RObject { RValue::Nil => vm.get_class_by_name("NilClass"), } } + + fn initialize_or_get_singleton_class(self: &Rc, vm: &mut VM) -> Rc { + if let Some(sclass) = self.singleton_class.borrow().as_ref() { + return sclass.clone(); + } + + let inspect = mrb_funcall(vm, Some(self.clone()), "inspect", &[]); + let class_name: String = match inspect { + Ok(inspect) => inspect + .as_ref() + .try_into() + .unwrap_or_else(|_| "".to_string()), + Err(e) => format!("", e), + }; + + let sclass = Rc::new(RModule::new(&class_name)); + + self.singleton_class.replace(Some(sclass.clone())); + sclass + } } impl TryFrom<&RObject> for i32 { @@ -573,7 +605,11 @@ impl RModule { } } -fn collect_module_chain(module: &Rc, chain: &mut Vec>, visited: &mut HashSet) { +fn collect_module_chain( + module: &Rc, + chain: &mut Vec>, + visited: &mut HashSet, +) { let key = Rc::as_ptr(module) as usize; if !visited.insert(key) { return; @@ -601,7 +637,11 @@ pub struct RClass { } impl RClass { - pub fn new(name: &str, super_class: Option>, parent_module: Option>) -> Self { + pub fn new( + name: &str, + super_class: Option>, + parent_module: Option>, + ) -> Self { let module = Rc::new(RModule::new(name)); if let Some(parent) = parent_module { module.parent.replace(Some(parent)); @@ -620,12 +660,10 @@ impl RClass { pub fn find_method(&self, name: &str) -> Option { match self.module.find_method(name) { Some(p) => Some(p), - None => { - match &self.super_class { - Some(sc) => sc.find_method(name), - None => None, - } - } + None => match &self.super_class { + Some(sc) => sc.find_method(name), + None => None, + }, } } @@ -634,7 +672,11 @@ impl RClass { } } -fn collect_class_chain(class: &Rc, chain: &mut Vec>, visited: &mut HashSet) { +fn collect_class_chain( + class: &Rc, + chain: &mut Vec>, + visited: &mut HashSet, +) { collect_module_chain(&class.module, chain, visited); if let Some(super_class) = &class.super_class { collect_class_chain(super_class, chain, visited); @@ -718,14 +760,12 @@ pub type RFn = Box]) -> Result, Error> /// Interned symbol name used across the VM to identify methods and constants. #[derive(Debug, Clone, PartialEq, Eq)] pub struct RSym { - pub name: String + pub name: String, } impl RSym { pub fn new(name: String) -> Self { - Self { - name - } + Self { name } } } @@ -800,4 +840,4 @@ impl RException { backtrace: Vec::new(), } } -} \ No newline at end of file +} diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 43e7879..a1db00e 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -157,6 +157,7 @@ impl VM { ref_count: 1, }), object_id: 0.into(), + singleton_class: RefCell::new(None), }.to_refcount_assigned(); if self.current_regs()[0].is_none() { self.current_regs()[0].replace(top_self.clone()); From b7d7f6a9266667cf29fe3d84ef85711e2ae88225 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 19:55:22 +0900 Subject: [PATCH 030/314] Support singleton class --- mrubyedge/src/yamrb/helpers.rs | 44 +++++++++++++++++++++++++++++++++- mrubyedge/src/yamrb/optable.rs | 33 +++++++++++++++++++++---- mrubyedge/src/yamrb/value.rs | 19 ++++++++++++--- 3 files changed, 87 insertions(+), 9 deletions(-) diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index 584ce89..2c18b02 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -123,7 +123,7 @@ pub fn mrb_funcall(vm: &mut VM, top_self: Option>, name: &str, args: Some(obj) => obj, None => vm.getself()?, }; - let binding = recv.as_ref().get_class(vm); + let binding = recv.as_ref().get_singleton_class_or_class(vm); let (owner_module, method) = resolve_method(&binding, name).ok_or_else(|| Error::NoMethodError(name.to_string()))?; if method.is_rb_func { @@ -177,6 +177,48 @@ pub fn mrb_define_method(_vm: &mut VM, klass: Rc, name: &str, method: RP procs.insert(name.to_string(), method); } +/// Defines a singleton C method (native Rust function) on a specific Ruby object. +/// +/// Singleton methods are methods defined on individual objects rather than classes. +/// +/// # Arguments +/// +/// * `vm` - The virtual machine instance +/// * `dest` - The object to define the singleton method on +/// * `name` - The name of the method +/// * `cmethod` - The native Rust function to bind as a singleton method +pub fn mrb_define_singleton_cmethod(vm: &mut VM, dest: Rc, name: &str, cmethod: RFn) { + let index = vm.register_fn(cmethod); + let method = RProc { + is_rb_func: false, + sym_id: Some(RSym::new(name.to_string())), + next: None, + irep: None, + func: Some(index), + environ: None, + block_self: None, + }; + let klass = dest.initialize_or_get_singleton_class(vm); + let mut procs = klass.procs.borrow_mut(); + procs.insert(name.to_string(), method); +} + +/// Defines a singleton Ruby method (RProc) on a specific Ruby object. +/// +/// Singleton methods are methods defined on individual objects rather than classes. +/// +/// # Arguments +/// +/// * `vm` - The virtual machine instance +/// * `dest` - The object to define the singleton method on +/// * `name` - The name of the method +/// * `method` - The Ruby proc to bind as a singleton method +pub fn mrb_define_singleton_method(vm: &mut VM, dest: Rc, name: &str, method: RProc) { + let klass = dest.initialize_or_get_singleton_class(vm); + let mut procs = klass.procs.borrow_mut(); + procs.insert(name.to_string(), method); +} + /// Defines a C method (native Rust function) on a Ruby module. /// /// # Arguments diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index c34c73c..7bf932e 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -436,9 +436,9 @@ pub(crate) fn consume_expr(vm: &mut VM, code: OpCode, operand: &Fetched, pos: us // UNDEF => { // // op_undef(vm, &operand)?; // } - // SCLASS => { - // // op_sclass(vm, &operand)?; - // } + SCLASS => { + op_sclass(vm, &operand)?; + } TCLASS => { op_tclass(vm, &operand)?; } @@ -868,7 +868,7 @@ pub(crate) fn do_op_send(vm: &mut VM, recv_index: usize, blk_index: Option Result<(), Error> { method.sym_id = Some(sym.clone()); procs.insert(sym.name.clone(), method); } + (_, RValue::Proc(method)) => { + let robject = target.clone(); + let sclass = robject.get_singleton_class_or_class(vm); + let mut procs = sclass.procs.borrow_mut(); + let mut method = method.clone(); + method.sym_id = Some(sym.clone()); + procs.insert(sym.name.clone(), method); + } _ => { - unreachable!("DEF must be called on class or module"); + unreachable!("DEF must be called with Proc"); } } vm.current_regs()[a as usize].replace(RObject::symbol(sym).to_refcount_assigned()); Ok(()) } +pub(crate) fn op_sclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { + let a = operand.as_b()? as usize; + let val = vm.getself()?; + let singleton_class = val.singleton_class.borrow().clone(); + match singleton_class { + Some(ref sc) => { + let robj = sc.clone().into(); + vm.current_regs()[a].replace(Rc::new(robj)); + return Ok(()); + } + None => {} + } + Ok(()) +} + pub(crate) fn op_tclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; let val: RObject = match &vm.target_class { diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 5cacc1b..a19f6e9 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -103,7 +103,7 @@ pub struct RObject { pub value: RValue, pub object_id: Cell, - pub singleton_class: RefCell>>, + pub singleton_class: RefCell>>, } impl RObject { @@ -320,6 +320,15 @@ impl RObject { } } + pub fn get_singleton_class_or_class(&self, vm: &VM) -> Rc { + self.singleton_class + .borrow() + .as_ref() + .map(|s| s.clone()) + .or_else(|| Some(self.get_class(vm))) + .expect("should have singleton class or class") + } + pub fn get_class(&self, vm: &VM) -> Rc { match &self.value { RValue::Class(_) => vm.get_class_by_name("Class"), @@ -347,7 +356,7 @@ impl RObject { } } - fn initialize_or_get_singleton_class(self: &Rc, vm: &mut VM) -> Rc { + pub(crate) fn initialize_or_get_singleton_class(self: &Rc, vm: &mut VM) -> Rc { if let Some(sclass) = self.singleton_class.borrow().as_ref() { return sclass.clone(); } @@ -361,7 +370,11 @@ impl RObject { Err(e) => format!("", e), }; - let sclass = Rc::new(RModule::new(&class_name)); + let sclass = Rc::new(RClass::new( + &class_name, + Some(self.get_class(vm).clone()), + self.get_class(vm).parent.borrow().clone(), + )); self.singleton_class.replace(Some(sclass.clone())); sclass From e04795a8e71b9746668f34c2d59aef767468bcc7 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 19:55:36 +0900 Subject: [PATCH 031/314] Add example --- mrubyedge/examples/singleton.rb | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 mrubyedge/examples/singleton.rb diff --git a/mrubyedge/examples/singleton.rb b/mrubyedge/examples/singleton.rb new file mode 100644 index 0000000..465442f --- /dev/null +++ b/mrubyedge/examples/singleton.rb @@ -0,0 +1,7 @@ +obj = Object.new + +def obj.singleton_hello + "Hello from singleton class!" +end + +puts obj.singleton_hello \ No newline at end of file From a3b633ea903c1f105a5f600b9a728bf71fc4c674 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 19:57:57 +0900 Subject: [PATCH 032/314] Add minimal test --- mrubyedge/tests/singleton_class.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 mrubyedge/tests/singleton_class.rs diff --git a/mrubyedge/tests/singleton_class.rs b/mrubyedge/tests/singleton_class.rs new file mode 100644 index 0000000..b155294 --- /dev/null +++ b/mrubyedge/tests/singleton_class.rs @@ -0,0 +1,23 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn test_singleton_class() { + let code = " + obj = Object.new + def obj.my_singleton_method + 123 + end + + obj.my_singleton_method + "; + let binary = mrbc_compile("singleton_class", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 123); +} From 1e36a76107d905c312e751c924a077520f4afd48 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 20:13:38 +0900 Subject: [PATCH 033/314] Class requires VM table to initialize --- mrubyedge/src/yamrb/helpers.rs | 7 +++++++ mrubyedge/src/yamrb/optable.rs | 21 +++++++++++---------- mrubyedge/src/yamrb/prelude/class.rs | 3 ++- mrubyedge/src/yamrb/prelude/object.rs | 20 ++++++++++---------- mrubyedge/src/yamrb/value.rs | 12 +++--------- mrubyedge/src/yamrb/vm.rs | 5 ++++- 6 files changed, 37 insertions(+), 31 deletions(-) diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index 2c18b02..8c8ef27 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -177,6 +177,13 @@ pub fn mrb_define_method(_vm: &mut VM, klass: Rc, name: &str, method: RP procs.insert(name.to_string(), method); } +pub fn mrb_define_class_method(vm: &mut VM, klass: Rc, name: &str, method: RProc) { + let robject = RObject::class(klass.clone(), vm); + let sclass = robject.initialize_or_get_singleton_class(vm); + let mut procs = sclass.procs.borrow_mut(); + procs.insert(name.to_string(), method); +} + /// Defines a singleton C method (native Rust function) on a specific Ruby object. /// /// Singleton methods are methods defined on individual objects rather than classes. diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 7bf932e..a994acc 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1445,8 +1445,8 @@ fn do_op_range(vm: &mut VM, a: usize, b: usize, exclusive: bool) -> Result<(), E pub(crate) fn op_oclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; - let val = vm.object_class.clone().into(); - vm.current_regs()[a].replace(Rc::new(val)); + let val = RObject::class(vm.object_class.clone(), vm); + vm.current_regs()[a].replace(val); Ok(()) } @@ -1482,14 +1482,15 @@ pub(crate) fn op_class(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let klass = vm.define_class(&name, Some(superclass), parent_module.clone()); // register constant under parent namespace (if any) or top-level - let class_value = RObject::class(klass.clone()).to_refcount_assigned(); + let class_value = RObject::class(klass.clone(), vm); if let Some(parent) = parent_module { parent.consts.borrow_mut().insert(name.clone(), class_value); } else { vm.consts.insert(name.clone(), class_value); } - vm.current_regs()[a as usize].replace(Rc::new(klass.into())); + let class_value = RObject::class(klass.clone(), vm); + vm.current_regs()[a as usize].replace(class_value); Ok(()) } @@ -1576,8 +1577,8 @@ pub(crate) fn op_sclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let singleton_class = val.singleton_class.borrow().clone(); match singleton_class { Some(ref sc) => { - let robj = sc.clone().into(); - vm.current_regs()[a].replace(Rc::new(robj)); + let robj = RObject::class(sc.clone(), vm); + vm.current_regs()[a].replace(robj); return Ok(()); } None => {} @@ -1587,11 +1588,11 @@ pub(crate) fn op_sclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_tclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; - let val: RObject = match &vm.target_class { - TargetContext::Class(klass) => klass.clone().into(), - TargetContext::Module(module) => module.clone().into(), + let val: Rc = match &vm.target_class { + TargetContext::Class(klass) => RObject::class(klass.clone(), vm), + TargetContext::Module(module) => Rc::new(module.clone().into()), }; - vm.current_regs()[a].replace(val.to_refcount_assigned()); + vm.current_regs()[a].replace(val); Ok(()) } diff --git a/mrubyedge/src/yamrb/prelude/class.rs b/mrubyedge/src/yamrb/prelude/class.rs index e13884b..8b43a4c 100644 --- a/mrubyedge/src/yamrb/prelude/class.rs +++ b/mrubyedge/src/yamrb/prelude/class.rs @@ -141,7 +141,8 @@ fn test_class_attr_accessor() { let mut vm = VM::empty(); let class = vm.define_class("Test", None, None); let args = vec![RObject::symbol("foo".into()).to_refcount_assigned()]; - vm.current_regs()[0].replace(RObject::class(class.clone()).to_refcount_assigned()); + let classobj = RObject::class(class.clone(), &mut vm); + vm.current_regs()[0].replace(classobj.clone()); mrb_class_attr_acceccor(&mut vm, &args).expect("mrb_class_attr_acceccor failed"); let instance = RObject::instance(class).to_refcount_assigned(); diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index 14b513d..2dcc388 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -4,8 +4,8 @@ use crate::{yamrb::{helpers::{mrb_define_cmethod, mrb_funcall}, value::*, vm::VM pub(crate) fn initialize_object(vm: &mut VM) { let object_class = vm.object_class.clone(); - let klass: RObject = object_class.clone().into(); - vm.consts.insert("Object".to_string(), klass.to_refcount_assigned()); + let klass = RObject::class(object_class.clone(), vm); + vm.consts.insert("Object".to_string(), klass); vm.builtin_class_table.insert("Object", object_class.clone()); #[cfg(feature = "wasi")] @@ -354,17 +354,17 @@ fn test_mrb_object_is_equal_hash() { fn test_mrb_object_is_equal_klass() { let mut vm = VM::empty(); - let lhs: RObject = vm.get_class_by_name("String").into(); - let rhs: RObject = RObject::string("String".into()).get_class(&mut vm).into(); - let lhs = lhs.to_refcount_assigned(); - let rhs = rhs.to_refcount_assigned(); + let lhs: Rc = vm.get_class_by_name("String"); + let rhs: Rc = RObject::string("String".into()).get_class(&mut vm); + let lhs = RObject::class(lhs.clone(), &mut vm); + let rhs = RObject::class(rhs.clone(), &mut vm); let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); assert!(ret); - let lhs: RObject = RObject::integer(5471).get_class(&mut vm).into(); - let rhs: RObject = RObject::string("String".into()).get_class(&mut vm).into(); - let lhs = lhs.to_refcount_assigned(); - let rhs = rhs.to_refcount_assigned(); + let lhs: Rc = RObject::integer(5471).get_class(&mut vm); + let rhs: Rc = RObject::string("String".into()).get_class(&mut vm); + let lhs = RObject::class(lhs.clone(), &mut vm); + let rhs = RObject::class(rhs.clone(), &mut vm); let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); assert!(!ret); } diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index a19f6e9..32ce0c8 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -205,13 +205,13 @@ impl RObject { } } - pub fn class(c: Rc) -> Self { - RObject { + pub fn class(c: Rc, vm: &mut VM) -> Rc { + Rc::new(RObject { tt: RType::Class, value: RValue::Class(c), object_id: (u64::MAX).into(), singleton_class: RefCell::new(None), - } + }) } pub fn module(m: Rc) -> Self { @@ -740,12 +740,6 @@ impl std::ops::Deref for RClass { } } -impl From> for RObject { - fn from(value: Rc) -> Self { - RObject::class(value) - } -} - /// Backing storage for Ruby object instances (instance variables and data). #[derive(Debug, Clone)] pub struct RInstance { diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index a1db00e..e860236 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -49,6 +49,7 @@ pub struct VM { // common class pub object_class: Rc, pub builtin_class_table: HashMap<&'static str, Rc>, + pub class_object_table: HashMap>, pub globals: HashMap>, pub consts: HashMap>, @@ -97,6 +98,7 @@ impl VM { let globals = HashMap::new(); let consts = HashMap::new(); let builtin_class_table = HashMap::new(); + let class_object_table = HashMap::new(); let object_class = Rc::new(RClass::new("Object", None, None)); @@ -129,6 +131,7 @@ impl VM { flag_preemption, object_class, builtin_class_table, + class_object_table, globals, consts, upper, @@ -293,7 +296,7 @@ impl VM { let class = Rc::new( RClass::new(name, Some(superclass), parent_module), ); - let object = RObject::class(class.clone()).to_refcount_assigned(); + let object = RObject::class(class.clone(), self); self.consts.insert(name.to_string(), object); class } From a121a9ff42f6e939d777c20ed8fcbbe86ddbe541 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 20:22:19 +0900 Subject: [PATCH 034/314] Reusing class instance --- mrubyedge/src/yamrb/value.rs | 16 ++++++++++++++-- mrubyedge/src/yamrb/vm.rs | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 32ce0c8..70070e4 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -206,12 +206,24 @@ impl RObject { } pub fn class(c: Rc, vm: &mut VM) -> Rc { - Rc::new(RObject { + match vm.class_object_table.get(&c.full_name()) { + Some(robj) => robj.clone(), + None => { + let robj = Self::newclass(c.clone()); + vm.class_object_table + .insert(c.full_name(), robj.clone()); + robj + } + } + } + + fn newclass(c: Rc) -> Rc { + RObject { tt: RType::Class, value: RValue::Class(c), object_id: (u64::MAX).into(), singleton_class: RefCell::new(None), - }) + }.to_refcount_assigned() } pub fn module(m: Rc) -> Self { diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index e860236..69f7163 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -49,7 +49,7 @@ pub struct VM { // common class pub object_class: Rc, pub builtin_class_table: HashMap<&'static str, Rc>, - pub class_object_table: HashMap>, + pub class_object_table: HashMap>, pub globals: HashMap>, pub consts: HashMap>, From 11139aa2f994c2646c7f0eaf61cb446173771f2d Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 20:45:35 +0900 Subject: [PATCH 035/314] Support classes, add overrided new to Array --- mrubyedge/examples/array2.rb | 4 +++ mrubyedge/src/yamrb/helpers.rs | 18 ++++++++--- mrubyedge/src/yamrb/prelude/array.rs | 14 ++++++++- mrubyedge/src/yamrb/prelude/falseclass.rs | 38 +++++++++++++++++++++++ mrubyedge/src/yamrb/prelude/mod.rs | 6 ++++ mrubyedge/src/yamrb/prelude/nilclass.rs | 26 ++++++++++++++++ mrubyedge/src/yamrb/prelude/object.rs | 5 +++ mrubyedge/src/yamrb/prelude/trueclass.rs | 38 +++++++++++++++++++++++ 8 files changed, 144 insertions(+), 5 deletions(-) create mode 100644 mrubyedge/examples/array2.rb create mode 100644 mrubyedge/src/yamrb/prelude/falseclass.rs create mode 100644 mrubyedge/src/yamrb/prelude/nilclass.rs create mode 100644 mrubyedge/src/yamrb/prelude/trueclass.rs diff --git a/mrubyedge/examples/array2.rb b/mrubyedge/examples/array2.rb new file mode 100644 index 0000000..cfb9e5e --- /dev/null +++ b/mrubyedge/examples/array2.rb @@ -0,0 +1,4 @@ +ary = Array.new(5) +p ary[0] +p ary[1] +p ary[4] \ No newline at end of file diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index 8c8ef27..de80dd8 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -177,10 +177,20 @@ pub fn mrb_define_method(_vm: &mut VM, klass: Rc, name: &str, method: RP procs.insert(name.to_string(), method); } -pub fn mrb_define_class_method(vm: &mut VM, klass: Rc, name: &str, method: RProc) { - let robject = RObject::class(klass.clone(), vm); - let sclass = robject.initialize_or_get_singleton_class(vm); - let mut procs = sclass.procs.borrow_mut(); +pub fn mrb_define_class_cmethod(vm: &mut VM, klass: Rc, name: &str, cmethod: RFn) { + let index = vm.register_fn(cmethod); + let method = RProc { + is_rb_func: false, + sym_id: Some(RSym::new(name.to_string())), + next: None, + irep: None, + func: Some(index), + environ: None, + block_self: None, + }; + let class_obj = RObject::class(klass.clone(), vm); + let klass_singleton = class_obj.initialize_or_get_singleton_class(vm); + let mut procs = klass_singleton.procs.borrow_mut(); procs.insert(name.to_string(), method); } diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index 8fe6033..11534ac 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -1,10 +1,12 @@ use std::rc::Rc; -use crate::{yamrb::{helpers::{mrb_call_block, mrb_define_cmethod}, value::{RObject, RValue}, vm::VM}, Error}; +use crate::{Error, yamrb::{helpers::{mrb_call_block, mrb_define_class_cmethod, mrb_define_cmethod}, value::{RObject, RValue}, vm::VM}}; pub(crate) fn initialize_array(vm: &mut VM) { let array_class = vm.define_standard_class("Array"); + mrb_define_class_cmethod(vm, array_class.clone(), "new", Box::new(mrb_array_new)); + mrb_define_cmethod(vm, array_class.clone(), "push", Box::new(mrb_array_push_self)); mrb_define_cmethod(vm, array_class.clone(), "[]", Box::new(mrb_array_get_index_self)); mrb_define_cmethod(vm, array_class.clone(), "[]=", Box::new(mrb_array_set_index_self)); @@ -14,6 +16,16 @@ pub(crate) fn initialize_array(vm: &mut VM) { mrb_define_cmethod(vm, array_class.clone(), "pack", Box::new(mrb_array_pack)); } +pub fn mrb_array_new(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let array = if args.is_empty() { + vec![] + } else { + let size: usize = args[0].as_ref().try_into()?; + vec![Rc::new(RObject::nil()); size] + }; + Ok(Rc::new(RObject::array(array))) +} + fn mrb_array_push_self(vm: &mut VM, args: &[Rc]) -> Result, Error> { let this = vm.getself()?; mrb_array_push(this, args) diff --git a/mrubyedge/src/yamrb/prelude/falseclass.rs b/mrubyedge/src/yamrb/prelude/falseclass.rs new file mode 100644 index 0000000..900bc4b --- /dev/null +++ b/mrubyedge/src/yamrb/prelude/falseclass.rs @@ -0,0 +1,38 @@ +use std::rc::Rc; + +use crate::yamrb::helpers::mrb_define_cmethod; +use crate::Error; + +use crate::yamrb::{value::RObject, vm::VM}; + +pub(crate) fn initialize_falseclass(vm: &mut VM) { + let falseclass = vm.define_standard_class("FalseClass"); + + mrb_define_cmethod(vm, falseclass.clone(), "to_s", Box::new(mrb_falseclass_to_s)); + mrb_define_cmethod(vm, falseclass.clone(), "inspect", Box::new(mrb_falseclass_inspect)); + mrb_define_cmethod(vm, falseclass.clone(), "&", Box::new(mrb_falseclass_and)); + mrb_define_cmethod(vm, falseclass.clone(), "|", Box::new(mrb_falseclass_or)); + mrb_define_cmethod(vm, falseclass.clone(), "^", Box::new(mrb_falseclass_xor)); +} + +fn mrb_falseclass_to_s(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { + Ok(Rc::new(RObject::string("false".to_string()))) +} + +fn mrb_falseclass_inspect(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { + Ok(Rc::new(RObject::string("false".to_string()))) +} + +fn mrb_falseclass_and(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { + Ok(Rc::new(RObject::boolean(false))) +} + +fn mrb_falseclass_or(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let rhs = args[0].clone(); + Ok(Rc::new(RObject::boolean(rhs.is_truthy()))) +} + +fn mrb_falseclass_xor(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let rhs = args[0].clone(); + Ok(Rc::new(RObject::boolean(rhs.is_truthy()))) +} diff --git a/mrubyedge/src/yamrb/prelude/mod.rs b/mrubyedge/src/yamrb/prelude/mod.rs index 6075145..576c160 100644 --- a/mrubyedge/src/yamrb/prelude/mod.rs +++ b/mrubyedge/src/yamrb/prelude/mod.rs @@ -9,6 +9,9 @@ pub mod exception; pub mod class; pub mod module; pub mod integer; +pub mod nilclass; +pub mod trueclass; +pub mod falseclass; pub mod string; pub mod array; pub mod hash; @@ -21,6 +24,9 @@ pub fn prelude(vm: &mut VM) { module::initialize_module(vm); class::initialize_class(vm); integer::initialize_integer(vm); + nilclass::initialize_nilclass(vm); + trueclass::initialize_trueclass(vm); + falseclass::initialize_falseclass(vm); string::initialize_string(vm); array::initialize_array(vm); hash::initialize_hash(vm); diff --git a/mrubyedge/src/yamrb/prelude/nilclass.rs b/mrubyedge/src/yamrb/prelude/nilclass.rs new file mode 100644 index 0000000..b9c5e07 --- /dev/null +++ b/mrubyedge/src/yamrb/prelude/nilclass.rs @@ -0,0 +1,26 @@ +use std::rc::Rc; + +use crate::yamrb::helpers::mrb_define_cmethod; +use crate::Error; + +use crate::yamrb::{value::RObject, vm::VM}; + +pub(crate) fn initialize_nilclass(vm: &mut VM) { + let nilclass = vm.define_standard_class("NilClass"); + + mrb_define_cmethod(vm, nilclass.clone(), "to_s", Box::new(mrb_nilclass_to_s)); + mrb_define_cmethod(vm, nilclass.clone(), "inspect", Box::new(mrb_nilclass_inspect)); + mrb_define_cmethod(vm, nilclass.clone(), "nil?", Box::new(mrb_nilclass_nil_p)); +} + +fn mrb_nilclass_to_s(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { + Ok(Rc::new(RObject::string("".to_string()))) +} + +fn mrb_nilclass_inspect(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { + Ok(Rc::new(RObject::string("nil".to_string()))) +} + +fn mrb_nilclass_nil_p(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { + Ok(Rc::new(RObject::boolean(true))) +} diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index 2dcc388..7c668d2 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -23,6 +23,7 @@ pub(crate) fn initialize_object(vm: &mut VM) { mrb_define_cmethod(vm, object_class.clone(), "to_s", Box::new(mrb_object_to_s)); mrb_define_cmethod(vm, object_class.clone(), "inspect", Box::new(mrb_object_to_s)); mrb_define_cmethod(vm, object_class.clone(), "raise", Box::new(mrb_object_raise)); + mrb_define_cmethod(vm, object_class.clone(), "nil?", Box::new(mrb_object_nil_p)); // define global consts: vm.consts.insert("RUBY_VERSION".to_string(), Rc::new(RObject::string(crate::yamrb::vm::VERSION.to_string()))); @@ -142,6 +143,10 @@ pub fn mrb_object_raise(_vm: &mut VM, args: &[Rc]) -> Result]) -> Result, Error> { + Ok(Rc::new(RObject::boolean(false))) +} + pub fn mrb_object_initialize(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { // Abstract method; do nothing Ok(Rc::new(RObject::nil())) diff --git a/mrubyedge/src/yamrb/prelude/trueclass.rs b/mrubyedge/src/yamrb/prelude/trueclass.rs new file mode 100644 index 0000000..118e658 --- /dev/null +++ b/mrubyedge/src/yamrb/prelude/trueclass.rs @@ -0,0 +1,38 @@ +use std::rc::Rc; + +use crate::yamrb::helpers::mrb_define_cmethod; +use crate::Error; + +use crate::yamrb::{value::RObject, vm::VM}; + +pub(crate) fn initialize_trueclass(vm: &mut VM) { + let trueclass = vm.define_standard_class("TrueClass"); + + mrb_define_cmethod(vm, trueclass.clone(), "to_s", Box::new(mrb_trueclass_to_s)); + mrb_define_cmethod(vm, trueclass.clone(), "inspect", Box::new(mrb_trueclass_inspect)); + mrb_define_cmethod(vm, trueclass.clone(), "&", Box::new(mrb_trueclass_and)); + mrb_define_cmethod(vm, trueclass.clone(), "|", Box::new(mrb_trueclass_or)); + mrb_define_cmethod(vm, trueclass.clone(), "^", Box::new(mrb_trueclass_xor)); +} + +fn mrb_trueclass_to_s(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { + Ok(Rc::new(RObject::string("true".to_string()))) +} + +fn mrb_trueclass_inspect(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { + Ok(Rc::new(RObject::string("true".to_string()))) +} + +fn mrb_trueclass_and(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let rhs = args[0].clone(); + Ok(Rc::new(RObject::boolean(rhs.is_truthy()))) +} + +fn mrb_trueclass_or(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { + Ok(Rc::new(RObject::boolean(true))) +} + +fn mrb_trueclass_xor(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let rhs = args[0].clone(); + Ok(Rc::new(RObject::boolean(!rhs.is_truthy()))) +} From c0adabda580d0593d488db619968f34c5cfc8c96 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 21:55:47 +0900 Subject: [PATCH 036/314] Update initializes of special classes --- mrubyedge/src/yamrb/prelude/class.rs | 19 ------------- mrubyedge/src/yamrb/prelude/hash.rs | 10 +++++-- mrubyedge/src/yamrb/prelude/shared_memory.rs | 3 ++ mrubyedge/src/yamrb/prelude/string.rs | 16 ++++++++++- mrubyedge/tests/hash.rs | 21 ++++++++++++++ mrubyedge/tests/string.rs | 29 ++++++++++++++++++++ 6 files changed, 76 insertions(+), 22 deletions(-) create mode 100644 mrubyedge/tests/string.rs diff --git a/mrubyedge/src/yamrb/prelude/class.rs b/mrubyedge/src/yamrb/prelude/class.rs index 8b43a4c..f6b608c 100644 --- a/mrubyedge/src/yamrb/prelude/class.rs +++ b/mrubyedge/src/yamrb/prelude/class.rs @@ -2,8 +2,6 @@ use std::rc::Rc; use crate::{yamrb::{helpers::{mrb_define_cmethod, mrb_funcall}, value::*, vm::VM}, Error}; -use super::shared_memory::mrb_shared_memory_new; - pub(crate) fn initialize_class(vm: &mut VM) { let module_class = vm.get_class_by_name("Module"); let class_class = vm.define_standard_class_under("Class", module_class); @@ -23,23 +21,6 @@ fn mrb_class_new(vm: &mut VM, args: &[Rc]) -> Result, Error return Err(Error::RuntimeError("Class#new must be called from class".to_string())); } }; - // Classes with special initializers - match class.sym_id.name.as_str() { - "String" => { - todo!("String.new"); - } - "Array" => { - todo!("Array.new"); - } - "Hash" => { - todo!("Hash.new"); - } - "SharedMemory" => { - let sm = mrb_shared_memory_new(vm, args)?; - return Ok(sm); - } - _ => {} - } let obj = RObject::instance(class).to_refcount_assigned(); diff --git a/mrubyedge/src/yamrb/prelude/hash.rs b/mrubyedge/src/yamrb/prelude/hash.rs index 187bd28..7692b89 100644 --- a/mrubyedge/src/yamrb/prelude/hash.rs +++ b/mrubyedge/src/yamrb/prelude/hash.rs @@ -1,10 +1,12 @@ -use std::rc::Rc; +use std::{collections::HashMap, rc::Rc}; -use crate::{yamrb::{helpers::{mrb_call_block, mrb_define_cmethod}, value::{RObject, RValue}, vm::VM}, Error}; +use crate::{Error, yamrb::{helpers::{mrb_call_block, mrb_define_class_cmethod, mrb_define_cmethod}, value::{RObject, RValue}, vm::VM}}; pub(crate) fn initialize_hash(vm: &mut VM) { let hash_class = vm.define_standard_class("Hash"); + mrb_define_class_cmethod(vm, hash_class.clone(), "new", Box::new(mrb_hash_new)); + mrb_define_cmethod(vm, hash_class.clone(), "[]", Box::new(mrb_hash_get_index_self)); mrb_define_cmethod(vm, hash_class.clone(), "[]=", Box::new(mrb_hash_set_index_self)); mrb_define_cmethod(vm, hash_class.clone(), "each", Box::new(mrb_hash_each)); @@ -12,6 +14,10 @@ pub(crate) fn initialize_hash(vm: &mut VM) { mrb_define_cmethod(vm, hash_class.clone(), "length", Box::new(mrb_hash_size)); } +pub fn mrb_hash_new(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { + Ok(Rc::new(RObject::hash(HashMap::new()))) +} + fn mrb_hash_get_index_self(vm: &mut VM, args: &[Rc]) -> Result, Error> { let this = vm.getself()?; mrb_hash_get_index(this, args[0].clone()) diff --git a/mrubyedge/src/yamrb/prelude/shared_memory.rs b/mrubyedge/src/yamrb/prelude/shared_memory.rs index 5347d25..ce4eb53 100644 --- a/mrubyedge/src/yamrb/prelude/shared_memory.rs +++ b/mrubyedge/src/yamrb/prelude/shared_memory.rs @@ -1,6 +1,7 @@ use std::cell::RefCell; use std::rc::Rc; +use crate::yamrb::helpers::mrb_define_class_cmethod; use crate::yamrb::shared_memory::SharedMemory; use crate::yamrb::vm::VM; use crate::{yamrb::{helpers::mrb_define_cmethod, value::{RObject, RValue, RType}}, Error}; @@ -8,6 +9,8 @@ use crate::{yamrb::{helpers::mrb_define_cmethod, value::{RObject, RValue, RType} pub(crate) fn initialize_shared_memory(vm: &mut VM) { let shared_memory_class = vm.define_standard_class("SharedMemory"); + mrb_define_class_cmethod(vm, shared_memory_class.clone(), "new", Box::new(mrb_shared_memory_new)); + mrb_define_cmethod(vm, shared_memory_class.clone(), "to_s", Box::new(mrb_shared_memory_to_string)); mrb_define_cmethod(vm, shared_memory_class.clone(), "offset_in_memory", Box::new(mrb_shared_memory_offset_in_memory)); mrb_define_cmethod(vm, shared_memory_class.clone(), "to_i", Box::new(mrb_shared_memory_offset_in_memory)); diff --git a/mrubyedge/src/yamrb/prelude/string.rs b/mrubyedge/src/yamrb/prelude/string.rs index f8c67d6..c8ea25a 100644 --- a/mrubyedge/src/yamrb/prelude/string.rs +++ b/mrubyedge/src/yamrb/prelude/string.rs @@ -1,6 +1,6 @@ use std::rc::Rc; -use crate::{yamrb::{helpers::mrb_define_cmethod, value::RObject, vm::VM}, Error}; +use crate::{Error, yamrb::{helpers::{mrb_define_class_cmethod, mrb_define_cmethod}, value::RObject, vm::VM}}; use super::array::mrb_array_push; @@ -8,11 +8,25 @@ use super::array::mrb_array_push; pub(crate) fn initialize_string(vm: &mut VM) { let string_class = vm.define_standard_class("String"); + mrb_define_class_cmethod(vm, string_class.clone(), "new", Box::new(mrb_string_new)); + mrb_define_cmethod(vm, string_class.clone(), "unpack", Box::new(mrb_string_unpack)); mrb_define_cmethod(vm, string_class.clone(), "size", Box::new(mrb_string_size)); mrb_define_cmethod(vm, string_class.clone(), "length", Box::new(mrb_string_size)); } +pub fn mrb_string_new(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let mut args = args; + if args.len() > 0 && args.last().unwrap().is_nil() { + args = &args[..args.len() - 1]; + } + if args.is_empty() { + return Ok(Rc::new(RObject::string("".to_string()))); + } + let s: String = args[0].as_ref().try_into()?; + Ok(Rc::new(RObject::string(s))) +} + fn bytes_of(value: &[u8], cursor: usize) -> Result<[u8; N], Error> { if value.len() < cursor + N { return Err(Error::RuntimeError("Not enough bytes".to_string())); diff --git a/mrubyedge/tests/hash.rs b/mrubyedge/tests/hash.rs index 3d2716f..cd87e64 100644 --- a/mrubyedge/tests/hash.rs +++ b/mrubyedge/tests/hash.rs @@ -7,6 +7,27 @@ use helpers::*; use mrubyedge::yamrb::value::RObject; use std::rc::Rc; +#[test] +fn hash_new_test() { + let code = " + def test_hash_new + hash = Hash.new + hash.size + end + "; + let binary = mrbc_compile("hash_new", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_hash_new", &args) + .unwrap(); + let result: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 0); +} + #[test] fn hash_test() { let code = " diff --git a/mrubyedge/tests/string.rs b/mrubyedge/tests/string.rs new file mode 100644 index 0000000..c0fe83a --- /dev/null +++ b/mrubyedge/tests/string.rs @@ -0,0 +1,29 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; + +use helpers::*; +// use mrubyedge::yamrb::value::RObject; +// use std::rc::Rc; + +#[test] +fn string_new_test() { + let code = " + def test_string_new + string = String.new + string.size + end + "; + let binary = mrbc_compile("string_new", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_new", &args) + .unwrap(); + let result: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 0); +} \ No newline at end of file From 6db645c2c6693893b763375956395f58175e4e53 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 22:00:54 +0900 Subject: [PATCH 037/314] Run cargo fmt --- mrubyedge/benches/benchmark.rs | 2 +- mrubyedge/examples/newvm-2.rs | 117 ++++-- mrubyedge/examples/newvm-3.rs | 253 ++++++++++--- mrubyedge/examples/newvm.rs | 68 +++- mrubyedge/examples/runscript.rs | 5 +- mrubyedge/src/error.rs | 2 +- mrubyedge/src/lib.rs | 4 +- mrubyedge/src/rite/binfmt.rs | 2 +- mrubyedge/src/rite/rite.rs | 24 +- mrubyedge/src/yamrb/helpers.rs | 56 ++- mrubyedge/src/yamrb/mod.rs | 6 +- mrubyedge/src/yamrb/op.rs | 4 +- mrubyedge/src/yamrb/optable.rs | 240 +++++++----- mrubyedge/src/yamrb/prelude/array.rs | 74 ++-- mrubyedge/src/yamrb/prelude/class.rs | 82 +++- mrubyedge/src/yamrb/prelude/exception.rs | 16 +- mrubyedge/src/yamrb/prelude/falseclass.rs | 16 +- mrubyedge/src/yamrb/prelude/hash.rs | 60 ++- mrubyedge/src/yamrb/prelude/integer.rs | 9 +- mrubyedge/src/yamrb/prelude/mod.rs | 16 +- mrubyedge/src/yamrb/prelude/module.rs | 10 +- mrubyedge/src/yamrb/prelude/nilclass.rs | 9 +- mrubyedge/src/yamrb/prelude/object.rs | 372 ++++++++++++++----- mrubyedge/src/yamrb/prelude/range.rs | 58 +-- mrubyedge/src/yamrb/prelude/shared_memory.rs | 113 ++++-- mrubyedge/src/yamrb/prelude/string.rs | 57 ++- mrubyedge/src/yamrb/prelude/trueclass.rs | 9 +- mrubyedge/src/yamrb/shared_memory.rs | 2 +- mrubyedge/src/yamrb/value.rs | 6 +- mrubyedge/src/yamrb/vm.rs | 103 +++-- mrubyedge/tests/equal.rs | 47 ++- mrubyedge/tests/fncall.rs | 47 ++- mrubyedge/tests/hash.rs | 58 +-- mrubyedge/tests/helpers/mod.rs | 124 +++---- mrubyedge/tests/iter.rs | 21 +- mrubyedge/tests/iter_closure.rs | 25 +- mrubyedge/tests/klass.rs | 28 +- mrubyedge/tests/module.rs | 23 +- mrubyedge/tests/object.rs | 7 +- mrubyedge/tests/object_id.rs | 12 +- mrubyedge/tests/raise.rs | 36 +- mrubyedge/tests/raise_rust.rs | 35 +- mrubyedge/tests/return.rs | 4 +- mrubyedge/tests/shared_memory.rs | 8 +- mrubyedge/tests/smoke.rs | 119 +++--- mrubyedge/tests/string.rs | 5 +- mrubyedge/tests/unpack.rs | 2 +- 47 files changed, 1670 insertions(+), 726 deletions(-) diff --git a/mrubyedge/benches/benchmark.rs b/mrubyedge/benches/benchmark.rs index e6de641..584e152 100644 --- a/mrubyedge/benches/benchmark.rs +++ b/mrubyedge/benches/benchmark.rs @@ -151,4 +151,4 @@ // criterion_group!(benches, bm0_load, bm0_prelude, bm0_eval, bm2); // criterion_main!(benches); -fn main () {} \ No newline at end of file +fn main() {} diff --git a/mrubyedge/examples/newvm-2.rs b/mrubyedge/examples/newvm-2.rs index eb1f351..2b3ca38 100644 --- a/mrubyedge/examples/newvm-2.rs +++ b/mrubyedge/examples/newvm-2.rs @@ -1,9 +1,9 @@ extern crate mrubyedge; use std::rc::Rc; -use mrubyedge::yamrb::*; -use mrubyedge::rite::insn::OpCode; use mrubyedge::rite::insn::Fetched; +use mrubyedge::rite::insn::OpCode; +use mrubyedge::yamrb::*; // // This is a simple example of a new VM from realworld mrbc result. @@ -18,7 +18,7 @@ fn main() { // 2 004 MOVE R4 R1 ; R1:a // 2 007 MOVE R5 R2 ; R2:b // 2 010 ADD R4 R5 - // 2 012 RETURN R4 + // 2 012 RETURN R4 // let irep1 = vm::IREP { __id: 0, @@ -26,11 +26,36 @@ fn main() { nregs: 7, rlen: 0, code: vec![ - op::Op { code: OpCode::ENTER, operand: Fetched::W(0x80000), pos: 0, len: 4 }, - op::Op { code: OpCode::MOVE, operand: Fetched::BB(4, 1), pos: 4, len: 3 }, - op::Op { code: OpCode::MOVE, operand: Fetched::BB(5, 2), pos: 7, len: 3 }, - op::Op { code: OpCode::ADD, operand: Fetched::B(4), pos: 10, len: 2 }, - op::Op { code: OpCode::RETURN, operand: Fetched::B(4), pos: 12, len: 2 }, + op::Op { + code: OpCode::ENTER, + operand: Fetched::W(0x80000), + pos: 0, + len: 4, + }, + op::Op { + code: OpCode::MOVE, + operand: Fetched::BB(4, 1), + pos: 4, + len: 3, + }, + op::Op { + code: OpCode::MOVE, + operand: Fetched::BB(5, 2), + pos: 7, + len: 3, + }, + op::Op { + code: OpCode::ADD, + operand: Fetched::B(4), + pos: 10, + len: 2, + }, + op::Op { + code: OpCode::RETURN, + operand: Fetched::B(4), + pos: 12, + len: 2, + }, ], syms: Vec::new(), pool: Vec::new(), @@ -43,7 +68,7 @@ fn main() { // R1:c // R2:d // file: examples/def2.rb - // 1 000 TCLASS R3 + // 1 000 TCLASS R3 // 1 002 METHOD R4 I[0] // 1 005 DEF R3 :do_add // 5 008 LOADI R1 100 ; R1:c @@ -51,7 +76,7 @@ fn main() { // 7 014 MOVE R4 R1 ; R1:c // 7 017 MOVE R5 R2 ; R2:d // 7 020 SSEND R3 :do_add n=2 - // 7 024 RETURN R3 + // 7 024 RETURN R3 // 7 026 STOP // let irep0 = vm::IREP { @@ -60,16 +85,66 @@ fn main() { nregs: 7, rlen: 1, code: vec![ - op::Op { code: OpCode::TCLASS, operand: Fetched::B(3), pos: 0, len: 2 }, - op::Op { code: OpCode::METHOD, operand: Fetched::BB(4, 0), pos: 2, len: 3 }, - op::Op { code: OpCode::DEF, operand: Fetched::BB(3, 0), pos: 5, len: 3 }, - op::Op { code: OpCode::LOADI, operand: Fetched::BB(1, 100), pos: 8, len: 3 }, - op::Op { code: OpCode::LOADI, operand: Fetched::BB(2, 200), pos: 11, len: 3 }, - op::Op { code: OpCode::MOVE, operand: Fetched::BB(4, 1), pos: 14, len: 3 }, - op::Op { code: OpCode::MOVE, operand: Fetched::BB(5, 2), pos: 17, len: 3 }, - op::Op { code: OpCode::SSEND, operand: Fetched::BBB(3, 0, 2), pos: 20, len: 4 }, - op::Op { code: OpCode::RETURN, operand: Fetched::B(3), pos: 24, len: 2 }, - op::Op { code: OpCode::STOP, operand: Fetched::Z, pos: 26, len: 1 }, + op::Op { + code: OpCode::TCLASS, + operand: Fetched::B(3), + pos: 0, + len: 2, + }, + op::Op { + code: OpCode::METHOD, + operand: Fetched::BB(4, 0), + pos: 2, + len: 3, + }, + op::Op { + code: OpCode::DEF, + operand: Fetched::BB(3, 0), + pos: 5, + len: 3, + }, + op::Op { + code: OpCode::LOADI, + operand: Fetched::BB(1, 100), + pos: 8, + len: 3, + }, + op::Op { + code: OpCode::LOADI, + operand: Fetched::BB(2, 200), + pos: 11, + len: 3, + }, + op::Op { + code: OpCode::MOVE, + operand: Fetched::BB(4, 1), + pos: 14, + len: 3, + }, + op::Op { + code: OpCode::MOVE, + operand: Fetched::BB(5, 2), + pos: 17, + len: 3, + }, + op::Op { + code: OpCode::SSEND, + operand: Fetched::BBB(3, 0, 2), + pos: 20, + len: 4, + }, + op::Op { + code: OpCode::RETURN, + operand: Fetched::B(3), + pos: 24, + len: 2, + }, + op::Op { + code: OpCode::STOP, + operand: Fetched::Z, + pos: 26, + len: 1, + }, ], syms: vec![value::RSym::new("do_add".to_string())], pool: Vec::new(), @@ -79,4 +154,4 @@ fn main() { let mut vm = vm::VM::new_by_raw_irep(irep0); let ret = vm.run().unwrap(); dbg!(ret); -} \ No newline at end of file +} diff --git a/mrubyedge/examples/newvm-3.rs b/mrubyedge/examples/newvm-3.rs index a888b1f..21b0bfe 100644 --- a/mrubyedge/examples/newvm-3.rs +++ b/mrubyedge/examples/newvm-3.rs @@ -1,9 +1,9 @@ extern crate mrubyedge; use std::rc::Rc; -use mrubyedge::yamrb::*; -use mrubyedge::rite::insn::OpCode; use mrubyedge::rite::insn::Fetched; +use mrubyedge::rite::insn::OpCode; +use mrubyedge::yamrb::*; // // This is a simple example of a new VM from realworld mrbc result. @@ -16,28 +16,28 @@ fn main() { // file: examples/fib.rb // 1 000 ENTER 1:0:0:0:0:0:0 (0x40000) // 2 004 MOVE R3 R1 ; R1:n - // 2 007 LOADI_1 R4 (1) + // 2 007 LOADI_1 R4 (1) // 2 009 LT R3 R4 - // 2 011 JMPNOT R3 022 - // 3 015 LOADI_0 R3 (0) - // 3 017 RETURN R3 + // 2 011 JMPNOT R3 022 + // 3 015 LOADI_0 R3 (0) + // 3 017 RETURN R3 // 3 019 JMP 064 // 4 022 MOVE R3 R1 ; R1:n - // 4 025 LOADI_3 R4 (3) + // 4 025 LOADI_3 R4 (3) // 4 027 LT R3 R4 - // 4 029 JMPNOT R3 040 - // 5 033 LOADI_1 R3 (1) - // 5 035 RETURN R3 + // 4 029 JMPNOT R3 040 + // 5 033 LOADI_1 R3 (1) + // 5 035 RETURN R3 // 5 037 JMP 064 // 7 040 MOVE R4 R1 ; R1:n - // 7 043 SUBI R4 1 + // 7 043 SUBI R4 1 // 7 046 SSEND R3 :fib n=1 // 7 050 MOVE R5 R1 ; R1:n - // 7 053 SUBI R5 2 + // 7 053 SUBI R5 2 // 7 056 SSEND R4 :fib n=1 // 7 060 ADD R3 R4 - // 7 062 RETURN R3 - // 7 064 RETURN R3 + // 7 062 RETURN R3 + // 7 064 RETURN R3 // let irep1 = vm::IREP { __id: 0, @@ -45,30 +45,150 @@ fn main() { nregs: 8, rlen: 0, code: vec![ - op::Op { code: OpCode::ENTER, operand: Fetched::W(0x40000), pos: 0, len: 4 }, - op::Op { code: OpCode::MOVE, operand: Fetched::BB(3, 1), pos: 4, len: 3 }, - op::Op { code: OpCode::LOADI_1, operand: Fetched::B(4), pos: 7, len: 2 }, - op::Op { code: OpCode::LT, operand: Fetched::B(3), pos: 9, len: 2 }, - op::Op { code: OpCode::JMPNOT, operand: Fetched::BS(3, 22-11-4), pos: 11, len: 4 }, - op::Op { code: OpCode::LOADI_0, operand: Fetched::B(3), pos: 15, len: 2 }, - op::Op { code: OpCode::RETURN, operand: Fetched::B(3), pos: 17, len: 2 }, - op::Op { code: OpCode::JMP, operand: Fetched::S(64-19-3), pos: 19, len: 3 }, - op::Op { code: OpCode::MOVE, operand: Fetched::BB(3, 1), pos: 22, len: 3 }, - op::Op { code: OpCode::LOADI_3, operand: Fetched::B(4), pos: 25, len: 2 }, - op::Op { code: OpCode::LT, operand: Fetched::B(3), pos: 27, len: 2 }, - op::Op { code: OpCode::JMPNOT, operand: Fetched::BS(3, 40-29-4), pos: 29, len: 4 }, - op::Op { code: OpCode::LOADI_1, operand: Fetched::B(3), pos: 33, len: 2 }, - op::Op { code: OpCode::RETURN, operand: Fetched::B(3), pos: 35, len: 2 }, - op::Op { code: OpCode::JMP, operand: Fetched::S(64-37-3), pos: 37, len: 3}, - op::Op { code: OpCode::MOVE, operand: Fetched::BB(4, 1), pos: 40, len: 3 }, - op::Op { code: OpCode::SUBI, operand: Fetched::BB(4, 1), pos: 43, len: 3 }, - op::Op { code: OpCode::SSEND, operand: Fetched::BBB(3, 0, 1), pos: 46, len: 4 }, - op::Op { code: OpCode::MOVE, operand: Fetched::BB(5, 1), pos: 50, len: 3 }, - op::Op { code: OpCode::SUBI, operand: Fetched::BB(5, 2), pos: 53, len: 3 }, - op::Op { code: OpCode::SSEND, operand: Fetched::BBB(4, 0, 1), pos: 56, len: 4 }, - op::Op { code: OpCode::ADD, operand: Fetched::B(3), pos: 60, len: 2 }, - op::Op { code: OpCode::RETURN, operand: Fetched::B(3), pos: 62, len: 2 }, - op::Op { code: OpCode::RETURN, operand: Fetched::B(3), pos: 64, len: 2 }, + op::Op { + code: OpCode::ENTER, + operand: Fetched::W(0x40000), + pos: 0, + len: 4, + }, + op::Op { + code: OpCode::MOVE, + operand: Fetched::BB(3, 1), + pos: 4, + len: 3, + }, + op::Op { + code: OpCode::LOADI_1, + operand: Fetched::B(4), + pos: 7, + len: 2, + }, + op::Op { + code: OpCode::LT, + operand: Fetched::B(3), + pos: 9, + len: 2, + }, + op::Op { + code: OpCode::JMPNOT, + operand: Fetched::BS(3, 22 - 11 - 4), + pos: 11, + len: 4, + }, + op::Op { + code: OpCode::LOADI_0, + operand: Fetched::B(3), + pos: 15, + len: 2, + }, + op::Op { + code: OpCode::RETURN, + operand: Fetched::B(3), + pos: 17, + len: 2, + }, + op::Op { + code: OpCode::JMP, + operand: Fetched::S(64 - 19 - 3), + pos: 19, + len: 3, + }, + op::Op { + code: OpCode::MOVE, + operand: Fetched::BB(3, 1), + pos: 22, + len: 3, + }, + op::Op { + code: OpCode::LOADI_3, + operand: Fetched::B(4), + pos: 25, + len: 2, + }, + op::Op { + code: OpCode::LT, + operand: Fetched::B(3), + pos: 27, + len: 2, + }, + op::Op { + code: OpCode::JMPNOT, + operand: Fetched::BS(3, 40 - 29 - 4), + pos: 29, + len: 4, + }, + op::Op { + code: OpCode::LOADI_1, + operand: Fetched::B(3), + pos: 33, + len: 2, + }, + op::Op { + code: OpCode::RETURN, + operand: Fetched::B(3), + pos: 35, + len: 2, + }, + op::Op { + code: OpCode::JMP, + operand: Fetched::S(64 - 37 - 3), + pos: 37, + len: 3, + }, + op::Op { + code: OpCode::MOVE, + operand: Fetched::BB(4, 1), + pos: 40, + len: 3, + }, + op::Op { + code: OpCode::SUBI, + operand: Fetched::BB(4, 1), + pos: 43, + len: 3, + }, + op::Op { + code: OpCode::SSEND, + operand: Fetched::BBB(3, 0, 1), + pos: 46, + len: 4, + }, + op::Op { + code: OpCode::MOVE, + operand: Fetched::BB(5, 1), + pos: 50, + len: 3, + }, + op::Op { + code: OpCode::SUBI, + operand: Fetched::BB(5, 2), + pos: 53, + len: 3, + }, + op::Op { + code: OpCode::SSEND, + operand: Fetched::BBB(4, 0, 1), + pos: 56, + len: 4, + }, + op::Op { + code: OpCode::ADD, + operand: Fetched::B(3), + pos: 60, + len: 2, + }, + op::Op { + code: OpCode::RETURN, + operand: Fetched::B(3), + pos: 62, + len: 2, + }, + op::Op { + code: OpCode::RETURN, + operand: Fetched::B(3), + pos: 64, + len: 2, + }, ], syms: vec![value::RSym::new("fib".to_string())], pool: Vec::new(), @@ -79,12 +199,12 @@ fn main() { // irep0: // irep 0x6000020e4000 nregs=3 nlocals=1 pools=0 syms=1 reps=1 ilen=11 // file: examples/fib.rb - // 1 000 TCLASS R1 + // 1 000 TCLASS R1 // 1 002 METHOD R2 I[0] // 1 005 DEF R1 :fib - // 11 008 LOADI R2 10 + // 11 008 LOADI R2 10 // 11 011 SSEND R1 :fib n=1 - // 11 015 RETURN R1 + // 11 015 RETURN R1 // 11 017 STOP // let irep0 = vm::IREP { @@ -93,13 +213,48 @@ fn main() { nregs: 3, rlen: 1, code: vec![ - op::Op { code: OpCode::TCLASS, operand: Fetched::B(1), pos: 0, len: 2 }, - op::Op { code: OpCode::METHOD, operand: Fetched::BB(2, 0), pos: 2, len: 3 }, - op::Op { code: OpCode::DEF, operand: Fetched::BB(1, 0), pos: 5, len: 3 }, - op::Op { code: OpCode::LOADI, operand: Fetched::BB(2, 25), pos: 8, len: 3 }, - op::Op { code: OpCode::SSEND, operand: Fetched::BBB(1, 0, 1), pos: 11, len: 4 }, - op::Op { code: OpCode::RETURN, operand: Fetched::B(1), pos: 15, len: 2 }, - op::Op { code: OpCode::STOP, operand: Fetched::Z, pos: 17, len: 1 }, + op::Op { + code: OpCode::TCLASS, + operand: Fetched::B(1), + pos: 0, + len: 2, + }, + op::Op { + code: OpCode::METHOD, + operand: Fetched::BB(2, 0), + pos: 2, + len: 3, + }, + op::Op { + code: OpCode::DEF, + operand: Fetched::BB(1, 0), + pos: 5, + len: 3, + }, + op::Op { + code: OpCode::LOADI, + operand: Fetched::BB(2, 25), + pos: 8, + len: 3, + }, + op::Op { + code: OpCode::SSEND, + operand: Fetched::BBB(1, 0, 1), + pos: 11, + len: 4, + }, + op::Op { + code: OpCode::RETURN, + operand: Fetched::B(1), + pos: 15, + len: 2, + }, + op::Op { + code: OpCode::STOP, + operand: Fetched::Z, + pos: 17, + len: 1, + }, ], syms: vec![value::RSym::new("fib".to_string())], pool: Vec::new(), @@ -109,4 +264,4 @@ fn main() { let mut vm = vm::VM::new_by_raw_irep(irep0); let ret = vm.run().unwrap(); dbg!(ret); -} \ No newline at end of file +} diff --git a/mrubyedge/examples/newvm.rs b/mrubyedge/examples/newvm.rs index c865317..fe15305 100644 --- a/mrubyedge/examples/newvm.rs +++ b/mrubyedge/examples/newvm.rs @@ -1,8 +1,8 @@ extern crate mrubyedge; -use mrubyedge::yamrb::*; -use mrubyedge::rite::insn::OpCode; use mrubyedge::rite::insn::Fetched; +use mrubyedge::rite::insn::OpCode; +use mrubyedge::yamrb::*; use value::RSym; // @@ -14,7 +14,7 @@ use value::RSym; // 3 007 MOVE R5 R2 ; R2:y // 3 010 ADD R4 R5 // 3 012 SSEND R3 :puts n=1 -// 3 016 RETURN R3 +// 3 016 RETURN R3 // 3 018 STOP // fn main() { @@ -24,18 +24,56 @@ fn main() { nregs: 7, rlen: 0, code: vec![ - op::Op { code: OpCode::LOADI_1, operand: Fetched::B(1), pos: 0, len: 2 }, - op::Op { code: OpCode::LOADI_2, operand: Fetched::B(2), pos: 2, len: 2 }, - op::Op { code: OpCode::MOVE, operand: Fetched::BB(4, 1), pos: 4, len: 3 }, - op::Op { code: OpCode::MOVE, operand: Fetched::BB(5, 2), pos: 7, len: 3 }, - op::Op { code: OpCode::ADD, operand: Fetched::B(4), pos: 10, len: 2 }, - op::Op { code: OpCode::SSEND, operand: Fetched::BBB(3, 0, 1), pos: 12, len: 4 }, - op::Op { code: OpCode::RETURN, operand: Fetched::B(3), pos: 16, len: 2 }, - op::Op { code: OpCode::STOP, operand: Fetched::Z, pos: 18, len: 1 }, - ], - syms: vec![ - RSym::new("puts".to_string()), + op::Op { + code: OpCode::LOADI_1, + operand: Fetched::B(1), + pos: 0, + len: 2, + }, + op::Op { + code: OpCode::LOADI_2, + operand: Fetched::B(2), + pos: 2, + len: 2, + }, + op::Op { + code: OpCode::MOVE, + operand: Fetched::BB(4, 1), + pos: 4, + len: 3, + }, + op::Op { + code: OpCode::MOVE, + operand: Fetched::BB(5, 2), + pos: 7, + len: 3, + }, + op::Op { + code: OpCode::ADD, + operand: Fetched::B(4), + pos: 10, + len: 2, + }, + op::Op { + code: OpCode::SSEND, + operand: Fetched::BBB(3, 0, 1), + pos: 12, + len: 4, + }, + op::Op { + code: OpCode::RETURN, + operand: Fetched::B(3), + pos: 16, + len: 2, + }, + op::Op { + code: OpCode::STOP, + operand: Fetched::Z, + pos: 18, + len: 1, + }, ], + syms: vec![RSym::new("puts".to_string())], pool: Vec::new(), reps: Vec::new(), catch_target_pos: Vec::new(), @@ -43,4 +81,4 @@ fn main() { let mut vm = vm::VM::new_by_raw_irep(irep); let ret = vm.run().unwrap(); dbg!(ret); -} \ No newline at end of file +} diff --git a/mrubyedge/examples/runscript.rs b/mrubyedge/examples/runscript.rs index 32e622a..1a5d94c 100644 --- a/mrubyedge/examples/runscript.rs +++ b/mrubyedge/examples/runscript.rs @@ -1,5 +1,5 @@ use std::env; -use std::fs::{remove_file, File}; +use std::fs::{File, remove_file}; use std::io::Read; use std::process::Command; @@ -14,7 +14,8 @@ fn main() -> Result<(), std::io::Error> { if is_verbose { mrbc.arg("-v"); } - let result = mrbc.arg("-o") + let result = mrbc + .arg("-o") .arg("/tmp/__tmp__.mrb") .arg(path) .output() diff --git a/mrubyedge/src/error.rs b/mrubyedge/src/error.rs index a54781f..0d1d04f 100644 --- a/mrubyedge/src/error.rs +++ b/mrubyedge/src/error.rs @@ -66,4 +66,4 @@ impl Error { } false } -} \ No newline at end of file +} diff --git a/mrubyedge/src/lib.rs b/mrubyedge/src/lib.rs index 3c7a259..ed4d7f4 100644 --- a/mrubyedge/src/lib.rs +++ b/mrubyedge/src/lib.rs @@ -64,8 +64,8 @@ //! Ok(()) //! } //! ``` -pub mod eval; pub mod error; +pub mod eval; pub mod rite; pub mod yamrb; @@ -79,4 +79,4 @@ macro_rules! version { }; } -pub const VERSION: &str = version!(); \ No newline at end of file +pub const VERSION: &str = version!(); diff --git a/mrubyedge/src/rite/binfmt.rs b/mrubyedge/src/rite/binfmt.rs index 8e66e77..5c9f038 100644 --- a/mrubyedge/src/rite/binfmt.rs +++ b/mrubyedge/src/rite/binfmt.rs @@ -85,4 +85,4 @@ impl IrepCatchHandler { pub fn from_bytes(buf: &[u8]) -> Result { plain::from_bytes(buf).map_err(|_| Error::General).cloned() } -} \ No newline at end of file +} diff --git a/mrubyedge/src/rite/rite.rs b/mrubyedge/src/rite/rite.rs index 54faf61..50f0e9c 100644 --- a/mrubyedge/src/rite/rite.rs +++ b/mrubyedge/src/rite/rite.rs @@ -1,7 +1,7 @@ extern crate simple_endian; -use super::binfmt::*; use super::Error; +use super::binfmt::*; use core::ffi::CStr; use core::mem; @@ -122,7 +122,7 @@ pub fn load<'a>(src: &'a [u8]) -> Result, Error> { } pub fn section_irep_1(head: &[u8]) -> Result<(usize, SectionIrepHeader, Vec>), Error> { -let mut cur = 0; + let mut cur = 0; let irep_header_size = mem::size_of::(); let irep_header = SectionIrepHeader::from_bytes(&head[cur..irep_header_size])?; @@ -161,8 +161,12 @@ let mut cur = 0; as usize, end: be32_to_u32([head[cur + 5], head[cur + 6], head[cur + 7], head[cur + 8]]) as usize, - target: be32_to_u32([head[cur + 9], head[cur + 10], head[cur + 11], head[cur + 12]]) - as usize, + target: be32_to_u32([ + head[cur + 9], + head[cur + 10], + head[cur + 11], + head[cur + 12], + ]) as usize, }; catch_handlers.push(value); cur += mem::size_of::(); @@ -248,13 +252,13 @@ pub fn peek4<'a>(src: &'a [u8]) -> Option<[char; 4]> { return None; } if let [a, b, c, d] = src[0..4] { - let a = char::from_u32(a as u32).unwrap(); - let b = char::from_u32(b as u32).unwrap(); - let c = char::from_u32(c as u32).unwrap(); - let d = char::from_u32(d as u32).unwrap(); - Some([a, b, c, d]) + let a = char::from_u32(a as u32).unwrap(); + let b = char::from_u32(b as u32).unwrap(); + let c = char::from_u32(c as u32).unwrap(); + let d = char::from_u32(d as u32).unwrap(); + Some([a, b, c, d]) } else { - None + None } } diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index de80dd8..21f33a4 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -2,7 +2,11 @@ use std::rc::Rc; use crate::Error; -use super::{optable::push_callinfo, value::{resolve_method, RClass, RFn, RModule, RObject, RProc, RSym, RValue}, vm::VM}; +use super::{ + optable::push_callinfo, + value::{RClass, RFn, RModule, RObject, RProc, RSym, RValue, resolve_method}, + vm::VM, +}; fn call_block( vm: &mut VM, @@ -30,7 +34,11 @@ fn call_block( } vm.pc.set(0); - vm.current_irep = block.irep.as_ref().ok_or_else(|| Error::RuntimeError("No IREP".to_string()))?.clone(); + vm.current_irep = block + .irep + .as_ref() + .ok_or_else(|| Error::RuntimeError("No IREP".to_string()))? + .clone(); vm.upper = block.environ; let res = vm.run(); @@ -64,9 +72,7 @@ fn call_block( } match &res { - Ok(res) => { - Ok(res.clone()) - }, + Ok(res) => Ok(res.clone()), Err(e) => { let err = if let Some(e) = e.downcast_ref::() { e.clone() @@ -76,7 +82,7 @@ fn call_block( }; Err(err) } - } + } } /// Calls a Ruby block (Proc) with the given receiver and arguments. @@ -91,15 +97,22 @@ fn call_block( /// # Returns /// /// Returns the result of the block execution or an error if the call fails. -pub fn mrb_call_block(vm: &mut VM, block: Rc, recv: Option>, args: &[Rc]) -> Result, Error> { +pub fn mrb_call_block( + vm: &mut VM, + block: Rc, + recv: Option>, + args: &[Rc], +) -> Result, Error> { let block = match &block.value { RValue::Proc(p) => p.clone(), _ => panic!("Not a block"), - }; let recv = match recv { Some(r) => r, - None => block.block_self.clone().ok_or_else(|| Error::RuntimeError("No block self assigned".to_string()))?, + None => block + .block_self + .clone() + .ok_or_else(|| Error::RuntimeError("No block self assigned".to_string()))?, }; call_block(vm, block, recv, args, None) } @@ -118,17 +131,32 @@ pub fn mrb_call_block(vm: &mut VM, block: Rc, recv: Option> /// # Returns /// /// Returns the result of the method call or an error if the method is not found or execution fails. -pub fn mrb_funcall(vm: &mut VM, top_self: Option>, name: &str, args: &[Rc]) -> Result, Error> { +pub fn mrb_funcall( + vm: &mut VM, + top_self: Option>, + name: &str, + args: &[Rc], +) -> Result, Error> { let recv: Rc = match top_self { Some(obj) => obj, None => vm.getself()?, }; let binding = recv.as_ref().get_singleton_class_or_class(vm); - let (owner_module, method) = resolve_method(&binding, name).ok_or_else(|| Error::NoMethodError(name.to_string()))?; - + let (owner_module, method) = + resolve_method(&binding, name).ok_or_else(|| Error::NoMethodError(name.to_string()))?; + if method.is_rb_func { - let method_id = method.sym_id.clone().unwrap_or_else(|| RSym::new(name.to_string())); - call_block(vm, method, recv.clone(), args, Some((method_id, owner_module))) + let method_id = method + .sym_id + .clone() + .unwrap_or_else(|| RSym::new(name.to_string())); + call_block( + vm, + method, + recv.clone(), + args, + Some((method_id, owner_module)), + ) } else { vm.current_regs_offset += 2; // FIXME: magick number? vm.current_regs()[0].replace(recv.clone()); diff --git a/mrubyedge/src/yamrb/mod.rs b/mrubyedge/src/yamrb/mod.rs index cf993d8..1867a66 100644 --- a/mrubyedge/src/yamrb/mod.rs +++ b/mrubyedge/src/yamrb/mod.rs @@ -1,11 +1,11 @@ //! Yet Another mruby (yamrb) runtime layer. //! Provides value representation, opcode tables, helpers, and the VM itself //! so mruby bytecode can execute inside Rust. +pub mod helpers; +pub mod op; pub mod optable; -pub mod value; pub mod shared_memory; +pub mod value; pub mod vm; -pub mod op; -pub mod helpers; pub mod prelude; diff --git a/mrubyedge/src/yamrb/op.rs b/mrubyedge/src/yamrb/op.rs index c8b2b6e..d6a8574 100644 --- a/mrubyedge/src/yamrb/op.rs +++ b/mrubyedge/src/yamrb/op.rs @@ -4,7 +4,7 @@ use crate::rite::insn::{Fetched, OpCode}; pub struct Op { pub code: OpCode, pub operand: Fetched, - + pub pos: usize, pub len: usize, } @@ -18,4 +18,4 @@ impl Op { len, } } -} \ No newline at end of file +} diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index a994acc..daecc79 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -4,8 +4,8 @@ use std::cell::RefCell; use std::collections::HashMap; use std::rc::Rc; -use crate::rite::insn::{Fetched, OpCode}; use crate::Error; +use crate::rite::insn::{Fetched, OpCode}; use super::prelude::object::mrb_object_is_equal; use super::{helpers::mrb_funcall, value::*, vm::*}; @@ -139,7 +139,13 @@ const ENTER_K_MASK: u32 = 0b11111 << 2; const ENTER_D_MASK: u32 = 0b1 << 1; const ENTER_B_MASK: u32 = 0b1 << 0; -pub(crate) fn consume_expr(vm: &mut VM, code: OpCode, operand: &Fetched, pos: usize, len: usize) -> Result<(), Error> { +pub(crate) fn consume_expr( + vm: &mut VM, + code: OpCode, + operand: &Fetched, + pos: usize, + len: usize, +) -> Result<(), Error> { use crate::rite::insn::OpCode::*; match code { NOP => { @@ -460,12 +466,19 @@ pub(crate) fn consume_expr(vm: &mut VM, code: OpCode, operand: &Fetched, pos: us STOP => { op_stop(vm, &operand)?; } - _ => { unimplemented!("{:?}: Not supported yet", code)} + _ => { + unimplemented!("{:?}: Not supported yet", code) + } } Ok(()) } -pub(crate) fn push_callinfo(vm: &mut VM, method_id: RSym, n_args: usize, method_owner: Option>) { +pub(crate) fn push_callinfo( + vm: &mut VM, + method_id: RSym, + n_args: usize, + method_owner: Option>, +) { let callinfo = CALLINFO { prev: vm.current_callinfo.clone(), method_id, @@ -580,7 +593,11 @@ pub(crate) fn op_loadf(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_getgv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; let val = vm.current_irep.syms[b as usize].clone(); - let val = vm.globals.get(&val.name).ok_or_else(|| Error::internal(format!("global variable not found {}", val.name)))?.clone(); + let val = vm + .globals + .get(&val.name) + .ok_or_else(|| Error::internal(format!("global variable not found {}", val.name)))? + .clone(); vm.current_regs()[a as usize].replace(val); Ok(()) } @@ -597,10 +614,13 @@ pub(crate) fn op_getiv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; let this = vm.getself()?; let ivar = match &this.value { - RValue::Instance(ins) => ins.ivar.borrow().get( - &vm.current_irep.syms[b as usize].name, - ).ok_or_else(|| Error::internal(format!("symbol not found {}", b)))?.clone(), - _ => unreachable!("getiv must be called on instance") + RValue::Instance(ins) => ins + .ivar + .borrow() + .get(&vm.current_irep.syms[b as usize].name) + .ok_or_else(|| Error::internal(format!("symbol not found {}", b)))? + .clone(), + _ => unreachable!("getiv must be called on instance"), }; vm.current_regs()[a as usize].replace(ivar); Ok(()) @@ -613,12 +633,9 @@ pub(crate) fn op_setiv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { match &this.value { RValue::Instance(ins) => { let mut ivar = ins.ivar.borrow_mut(); - ivar.insert( - vm.current_irep.syms[b as usize].name.clone(), - val, - ) - }, - _ => unreachable!("setiv must be called on instance") + ivar.insert(vm.current_irep.syms[b as usize].name.clone(), val) + } + _ => unreachable!("setiv must be called on instance"), }; Ok(()) } @@ -681,9 +698,15 @@ pub(crate) fn op_getmcnst(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_getupvar(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b, c) = operand.as_bbb()?; let n = c as usize; - let mut environ = vm.upper.as_ref().ok_or_else(|| Error::internal("op_getupvar expects upper env"))?; + let mut environ = vm + .upper + .as_ref() + .ok_or_else(|| Error::internal("op_getupvar expects upper env"))?; for _ in 0..n { - environ = environ.upper.as_ref().ok_or_else(|| Error::internal("op_getupvar failed to find upvar"))?; + environ = environ + .upper + .as_ref() + .ok_or_else(|| Error::internal("op_getupvar failed to find upvar"))?; } let environ = environ.clone(); let up_regs = &vm.regs[environ.current_regs_offset..]; @@ -695,9 +718,12 @@ pub(crate) fn op_getupvar(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { } } else { let captured = environ.captured.borrow(); - let val = &captured.as_ref().ok_or_else(|| Error::internal("captured environment not found"))?[b as usize]; + let val = &captured + .as_ref() + .ok_or_else(|| Error::internal("captured environment not found"))?[b as usize]; let val = val.clone(); - vm.current_regs()[a as usize].replace(val.ok_or_else(|| Error::internal("captured value not found"))?); + vm.current_regs()[a as usize] + .replace(val.ok_or_else(|| Error::internal("captured value not found"))?); } Ok(()) } @@ -705,9 +731,15 @@ pub(crate) fn op_getupvar(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_setupvar(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b, c) = operand.as_bbb()?; let n = c as usize; - let mut environ = vm.upper.as_ref().ok_or_else(|| Error::internal("op_getupvar expects upper env"))?; + let mut environ = vm + .upper + .as_ref() + .ok_or_else(|| Error::internal("op_getupvar expects upper env"))?; for _ in 0..n { - environ = environ.upper.as_ref().ok_or_else(|| Error::internal("op_getupvar failed to find upvar"))?; + environ = environ + .upper + .as_ref() + .ok_or_else(|| Error::internal("op_getupvar failed to find upvar"))?; } let environ = environ.clone(); let current_regs_offset = environ.current_regs_offset; @@ -719,7 +751,9 @@ pub(crate) fn op_setupvar(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { target.replace(val); } else { let mut captured = environ.captured.borrow_mut(); - let captured = captured.as_mut().ok_or_else(|| Error::internal("captured environment not found"))?; + let captured = captured + .as_mut() + .ok_or_else(|| Error::internal("captured environment not found"))?; let target = &mut captured[b as usize]; target.replace(val); } @@ -744,9 +778,9 @@ pub(crate) fn op_setidx(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val = vm.get_current_regs_cloned(a + 2)?; let args = vec![idx, val]; mrb_funcall(vm, Some(recv), "[]=", &args)?; - Ok(()) - } - + Ok(()) +} + pub(crate) fn op_jmp(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Result<(), Error> { let a = operand.as_s()?; let next_pc = calcurate_pc(&vm.current_irep, vm.pc.get(), end_pos + a as usize); @@ -786,7 +820,10 @@ pub(crate) fn op_jmpnil(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Resul pub(crate) fn op_except(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()?; - let val = vm.exception.take().ok_or_else(|| Error::internal("exception not found"))?; + let val = vm + .exception + .take() + .ok_or_else(|| Error::internal("exception not found"))?; let exc = Rc::new(RObject::exception(val)); vm.current_regs()[a as usize].replace(exc); Ok(()) @@ -803,7 +840,7 @@ pub(crate) fn op_rescue(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val = RObject::boolean(is_rescued); vm.current_regs()[b as usize].replace(val.to_refcount_assigned()); } - _ => unreachable!("rescue must be called on exception") + _ => unreachable!("rescue must be called on exception"), }; Ok(()) } @@ -812,14 +849,12 @@ pub(crate) fn op_raiseif(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()?; let val = vm.current_regs()[a as usize].as_ref().cloned(); match val { - Some(val) => { - match &val.value { - RValue::Exception(e) => { - return Err(e.as_ref().error_type.borrow().clone()); - } - _ => {} + Some(val) => match &val.value { + RValue::Exception(e) => { + return Err(e.as_ref().error_type.borrow().clone()); } - } + _ => {} + }, None => {} } Ok(()) @@ -852,12 +887,22 @@ pub(crate) fn op_sendb(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { do_op_send(vm, a as usize, Some(a as usize + c as usize + 1), a, b, c) } -pub(crate) fn do_op_send(vm: &mut VM, recv_index: usize, blk_index: Option, a: u8, b: u8, c: u8) -> Result<(), Error> { +pub(crate) fn do_op_send( + vm: &mut VM, + recv_index: usize, + blk_index: Option, + a: u8, + b: u8, + c: u8, +) -> Result<(), Error> { let block_index = (a + c + 1) as usize; let recv = vm.get_current_regs_cloned(recv_index)?; let mut args = (0..c) - .map(|i| vm.get_current_regs_cloned((a + i + 1) as usize).expect("args too short for required")) + .map(|i| { + vm.get_current_regs_cloned((a + i + 1) as usize) + .expect("args too short for required") + }) .collect::>(); if let Some(blk_index) = blk_index { args.push(vm.get_current_regs_cloned(blk_index)?); @@ -869,13 +914,14 @@ pub(crate) fn do_op_send(vm: &mut VM, recv_index: usize, blk_index: Option Result<(), Error> { push_callinfo(vm, "".into(), 0, None); vm.pc.set(0); - let proc = vm.current_regs()[0].as_ref().cloned().ok_or_else(|| Error::internal("proc not found"))?; + let proc = vm.current_regs()[0] + .as_ref() + .cloned() + .ok_or_else(|| Error::internal("proc not found"))?; match &proc.value { RValue::Proc(proc) => { - vm.current_irep = proc.irep.as_ref().ok_or_else(|| Error::internal("empry irep"))?.clone(); + vm.current_irep = proc + .irep + .as_ref() + .ok_or_else(|| Error::internal("empry irep"))? + .clone(); } _ => unreachable!("call must be called on proc"), } @@ -922,14 +975,21 @@ pub(crate) fn op_call(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_super(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; - let callinfo = vm.current_callinfo.as_ref() + let callinfo = vm + .current_callinfo + .as_ref() .ok_or_else(|| Error::internal("no current callinfo"))?; let sym_id = callinfo.method_id.name.clone(); - let owner_module = callinfo.method_owner.clone() + let owner_module = callinfo + .method_owner + .clone() .ok_or_else(|| Error::RuntimeError("super called outside of method".to_string()))?; let recv = vm.getself()?; let args = (0..b) - .map(|i| vm.get_current_regs_cloned((a + i + 1) as usize).expect("args too short for super")) + .map(|i| { + vm.get_current_regs_cloned((a + i + 1) as usize) + .expect("args too short for super") + }) .collect::>(); let klass = match &recv.value { @@ -939,8 +999,9 @@ pub(crate) fn op_super(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (next_owner, method) = resolve_next_method(&klass, &sym_id, &owner_module) .ok_or_else(|| Error::NoMethodError(sym_id.clone()))?; if !method.is_rb_func { - let func = vm.get_fn(method.func.unwrap()) - .ok_or_else(|| Error::internal(format!("functon registerd but no entry found: {}", &sym_id)))?; + let func = vm.get_fn(method.func.unwrap()).ok_or_else(|| { + Error::internal(format!("functon registerd but no entry found: {}", &sym_id)) + })?; let res = func(vm, &args); for i in (a as usize + 1)..(a as usize + b as usize + 1) { vm.current_regs()[i].take(); @@ -953,16 +1014,24 @@ pub(crate) fn op_super(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { vm.current_regs()[a as usize].replace(Rc::new(RObject::nil())); return Err(e); } - } return Ok(()); } vm.current_regs()[a as usize].replace(recv.clone()); - push_callinfo(vm, method.sym_id.clone().unwrap(), b as usize, Some(next_owner)); + push_callinfo( + vm, + method.sym_id.clone().unwrap(), + b as usize, + Some(next_owner), + ); vm.pc.set(0); - vm.current_irep = method.irep.as_ref().ok_or_else(|| Error::internal("empty irep"))?.clone(); + vm.current_irep = method + .irep + .as_ref() + .ok_or_else(|| Error::internal("empty irep"))? + .clone(); vm.current_regs_offset += a as usize; Ok(()) } @@ -1062,18 +1131,10 @@ pub(crate) fn op_add(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val1 = vm.take_current_regs(a)?; let val2 = vm.get_current_regs_cloned(b)?; let result = match (&val1.value, &val2.value) { - (RValue::Integer(n1), RValue::Integer(n2)) => { - Rc::new(RObject::integer(n1 + n2)) - } - (RValue::Float(n1), RValue::Float(n2)) => { - Rc::new(RObject::float(n1 + n2)) - } - (RValue::Integer(n1), RValue::Float(n2)) => { - Rc::new(RObject::float(*n1 as f64 + n2)) - } - (RValue::Float(n1), RValue::Integer(n2)) => { - Rc::new(RObject::float(n1 + *n2 as f64)) - } + (RValue::Integer(n1), RValue::Integer(n2)) => Rc::new(RObject::integer(n1 + n2)), + (RValue::Float(n1), RValue::Float(n2)) => Rc::new(RObject::float(n1 + n2)), + (RValue::Integer(n1), RValue::Float(n2)) => Rc::new(RObject::float(*n1 as f64 + n2)), + (RValue::Float(n1), RValue::Integer(n2)) => Rc::new(RObject::float(n1 + *n2 as f64)), (RValue::String(n1), RValue::String(n2)) => { let mut n1 = n1.borrow_mut(); let n2 = n2.borrow(); @@ -1096,9 +1157,7 @@ pub(crate) fn op_addi(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val1 = vm.take_current_regs(a as usize)?; let val2 = b as i64; let result = match &val1.value { - RValue::Integer(n1) => { - RObject::integer(*n1 + val2) - } + RValue::Integer(n1) => RObject::integer(*n1 + val2), _ => { unreachable!("addi supports only integer") } @@ -1113,9 +1172,7 @@ pub(crate) fn op_sub(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val1 = vm.take_current_regs(a)?; let val2 = vm.get_current_regs_cloned(b)?; let result = match (&val1.value, &val2.value) { - (RValue::Integer(n1), RValue::Integer(n2)) => { - RObject::integer(n1 - n2) - } + (RValue::Integer(n1), RValue::Integer(n2)) => RObject::integer(n1 - n2), _ => { unreachable!("sub supports only integer") } @@ -1129,9 +1186,7 @@ pub(crate) fn op_subi(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val1 = vm.take_current_regs(a as usize)?; let val2 = b as i64; let result = match &val1.value { - RValue::Integer(n1) => { - RObject::integer(*n1 - val2) - } + RValue::Integer(n1) => RObject::integer(*n1 - val2), _ => { unreachable!("subi supports only integer") } @@ -1146,9 +1201,7 @@ pub(crate) fn op_mul(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val1 = vm.take_current_regs(a)?; let val2 = vm.get_current_regs_cloned(b)?; let result = match (&val1.value, &val2.value) { - (RValue::Integer(n1), RValue::Integer(n2)) => { - RObject::integer(n1 * n2) - } + (RValue::Integer(n1), RValue::Integer(n2)) => RObject::integer(n1 * n2), _ => { unreachable!("mul supports only integer") } @@ -1163,9 +1216,7 @@ pub(crate) fn op_div(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val1 = vm.take_current_regs(a)?; let val2 = vm.get_current_regs_cloned(b)?; let result = match (&val1.value, &val2.value) { - (RValue::Integer(n1), RValue::Integer(n2)) => { - RObject::integer(n1 / n2) - } + (RValue::Integer(n1), RValue::Integer(n2)) => RObject::integer(n1 / n2), _ => { unreachable!("div supports only integer") } @@ -1180,9 +1231,7 @@ pub(crate) fn op_lt(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val1 = vm.take_current_regs(a)?; let val2 = vm.get_current_regs_cloned(b)?; let result = match (&val1.value, &val2.value) { - (RValue::Integer(n1), RValue::Integer(n2)) => { - RObject::boolean(n1 < n2) - } + (RValue::Integer(n1), RValue::Integer(n2)) => RObject::boolean(n1 < n2), _ => { unreachable!("lt supports only integer") } @@ -1197,9 +1246,7 @@ pub(crate) fn op_le(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val1 = vm.take_current_regs(a)?; let val2 = vm.get_current_regs_cloned(b)?; let result = match (&val1.value, &val2.value) { - (RValue::Integer(n1), RValue::Integer(n2)) => { - RObject::boolean(n1 <= n2) - } + (RValue::Integer(n1), RValue::Integer(n2)) => RObject::boolean(n1 <= n2), _ => { unreachable!("le supports only integer") } @@ -1224,9 +1271,7 @@ pub(crate) fn op_gt(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val1 = vm.take_current_regs(a)?; let val2 = vm.get_current_regs_cloned(b)?; let result = match (&val1.value, &val2.value) { - (RValue::Integer(n1), RValue::Integer(n2)) => { - RObject::boolean(n1 > n2) - } + (RValue::Integer(n1), RValue::Integer(n2)) => RObject::boolean(n1 > n2), _ => { unreachable!("gt supports only integer") } @@ -1241,9 +1286,7 @@ pub(crate) fn op_ge(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val1 = vm.take_current_regs(a)?; let val2 = vm.get_current_regs_cloned(b)?; let result = match (&val1.value, &val2.value) { - (RValue::Integer(n1), RValue::Integer(n2)) => { - RObject::boolean(n1 >= n2) - } + (RValue::Integer(n1), RValue::Integer(n2)) => RObject::boolean(n1 >= n2), _ => { unreachable!("ge supports only integer") } @@ -1348,7 +1391,7 @@ pub(crate) fn op_lambda(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { //environ.capture(&vm.current_regs()[0..nregs]); let environ = Rc::new(environ); vm.cur_env.insert(vm.current_irep.__id, environ.clone()); - vm.has_env_ref.insert(vm.current_irep.__id,true); + vm.has_env_ref.insert(vm.current_irep.__id, true); let val = RObject { tt: RType::Proc, @@ -1379,7 +1422,7 @@ pub(crate) fn op_block(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { }; let environ = Rc::new(environ); vm.cur_env.insert(vm.current_irep.__id, environ.clone()); - vm.has_env_ref.insert(vm.current_irep.__id,true); + vm.has_env_ref.insert(vm.current_irep.__id, true); let val = RObject { tt: RType::Proc, @@ -1473,9 +1516,7 @@ pub(crate) fn op_class(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { vm.object_class.clone() } } - None => { - vm.object_class.clone() - } + None => vm.object_class.clone(), }; let parent_module = current_namespace(vm); let name = name.name; @@ -1503,7 +1544,10 @@ pub(crate) fn op_module(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let module_value = RObject::module(module.clone()).to_refcount_assigned(); if let Some(parent) = parent_module { - parent.consts.borrow_mut().insert(name.clone(), module_value); + parent + .consts + .borrow_mut() + .insert(name.clone(), module_value); } else { vm.consts.insert(name.clone(), module_value); } @@ -1541,7 +1585,7 @@ pub(crate) fn op_def(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let target_ref = target.as_ref(); let method_ref = method.as_ref(); - + match (&target_ref.value, &method_ref.value) { (RValue::Class(klass), RValue::Proc(method)) => { let mut procs = klass.procs.borrow_mut(); @@ -1594,9 +1638,9 @@ pub(crate) fn op_tclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { }; vm.current_regs()[a].replace(val); Ok(()) -} +} pub(crate) fn op_stop(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> { vm.flag_preemption.set(true); Ok(()) -} \ No newline at end of file +} diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index 11534ac..1da9f5e 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -1,15 +1,37 @@ use std::rc::Rc; -use crate::{Error, yamrb::{helpers::{mrb_call_block, mrb_define_class_cmethod, mrb_define_cmethod}, value::{RObject, RValue}, vm::VM}}; +use crate::{ + Error, + yamrb::{ + helpers::{mrb_call_block, mrb_define_class_cmethod, mrb_define_cmethod}, + value::{RObject, RValue}, + vm::VM, + }, +}; pub(crate) fn initialize_array(vm: &mut VM) { let array_class = vm.define_standard_class("Array"); mrb_define_class_cmethod(vm, array_class.clone(), "new", Box::new(mrb_array_new)); - mrb_define_cmethod(vm, array_class.clone(), "push", Box::new(mrb_array_push_self)); - mrb_define_cmethod(vm, array_class.clone(), "[]", Box::new(mrb_array_get_index_self)); - mrb_define_cmethod(vm, array_class.clone(), "[]=", Box::new(mrb_array_set_index_self)); + mrb_define_cmethod( + vm, + array_class.clone(), + "push", + Box::new(mrb_array_push_self), + ); + mrb_define_cmethod( + vm, + array_class.clone(), + "[]", + Box::new(mrb_array_get_index_self), + ); + mrb_define_cmethod( + vm, + array_class.clone(), + "[]=", + Box::new(mrb_array_set_index_self), + ); mrb_define_cmethod(vm, array_class.clone(), "each", Box::new(mrb_array_each)); mrb_define_cmethod(vm, array_class.clone(), "size", Box::new(mrb_array_size)); mrb_define_cmethod(vm, array_class.clone(), "length", Box::new(mrb_array_size)); @@ -39,10 +61,10 @@ pub fn mrb_array_push(this: Rc, args: &[Rc]) -> Result { - Err(Error::RuntimeError("Array#push must be called on an Array".to_string())) } + _ => Err(Error::RuntimeError( + "Array#push must be called on an Array".to_string(), + )), } } @@ -55,7 +77,9 @@ pub fn mrb_array_get_index(this: Rc, args: &[Rc]) -> Result a.clone(), _ => { - return Err(Error::RuntimeError("Array#push must be called on an Array".to_string())); + return Err(Error::RuntimeError( + "Array#push must be called on an Array".to_string(), + )); } }; let index: u32 = args[0].as_ref().try_into()?; @@ -77,7 +101,9 @@ pub fn mrb_array_set_index(this: Rc, args: &[Rc]) -> Result { - return Err(Error::RuntimeError("Array#push must be called on an Array".to_string())); + return Err(Error::RuntimeError( + "Array#push must be called on an Array".to_string(), + )); } }; Ok(value.clone()) @@ -95,7 +121,9 @@ fn mrb_array_each(vm: &mut VM, args: &[Rc]) -> Result, Erro } } _ => { - return Err(Error::RuntimeError("Array#each must be called on an Array".to_string())); + return Err(Error::RuntimeError( + "Array#each must be called on an Array".to_string(), + )); } }; Ok(this.clone()) @@ -166,7 +194,9 @@ fn mrb_array_pack(vm: &mut VM, args: &[Rc]) -> Result, Erro } } _ => { - return Err(Error::RuntimeError("Array#pack must be called on an Array".to_string())); + return Err(Error::RuntimeError( + "Array#pack must be called on an Array".to_string(), + )); } }; let value = Rc::new(RObject::string_from_vec(buf)); @@ -187,11 +217,7 @@ fn test_mrb_array_push_and_index() { ]; mrb_array_push(array.clone(), &args).expect("push failed"); - let answers = vec![ - 1, - 2, - 3, - ]; + let answers = vec![1, 2, 3]; for (i, expected) in answers.iter().enumerate() { let args = vec![Rc::new(RObject::integer(i as i64))]; @@ -217,10 +243,7 @@ fn test_mrb_array_set_and_index() { let upd_index = Rc::new(RObject::integer(2)); let newval = Rc::new(RObject::integer(42)); - let args = vec![ - upd_index, - newval, - ]; + let args = vec![upd_index, newval]; mrb_array_set_index(array.clone(), &args).expect("set index failed"); @@ -242,17 +265,12 @@ fn test_mrb_array_pack() { Rc::new(RObject::integer(4)), ])); vm.current_regs()[0].replace(array); - let format = Rc::new(RObject::string( - "c s l q".to_string(), - )); + let format = Rc::new(RObject::string("c s l q".to_string())); let args = vec![format]; let value = mrb_array_pack(&mut vm, &args).expect("pack failed"); let expected: Vec = vec![ - 0x01, - 0x02, 0x00, - 0x03, 0x00, 0x00, 0x00, - 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x01, 0x02, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; let value: Vec = value.as_ref().try_into().expect("value is not string"); for (i, v) in value.iter().enumerate() { @@ -284,4 +302,4 @@ fn test_mrb_array_size() { let ret = helpers::mrb_funcall(&mut vm, Some(data), "size", &[]).expect("size failed"); let ret: i64 = ret.as_ref().try_into().expect("size is not integer"); assert_eq!(ret, 3); -} \ No newline at end of file +} diff --git a/mrubyedge/src/yamrb/prelude/class.rs b/mrubyedge/src/yamrb/prelude/class.rs index f6b608c..62db9eb 100644 --- a/mrubyedge/src/yamrb/prelude/class.rs +++ b/mrubyedge/src/yamrb/prelude/class.rs @@ -1,16 +1,43 @@ use std::rc::Rc; -use crate::{yamrb::{helpers::{mrb_define_cmethod, mrb_funcall}, value::*, vm::VM}, Error}; +use crate::{ + Error, + yamrb::{ + helpers::{mrb_define_cmethod, mrb_funcall}, + value::*, + vm::VM, + }, +}; pub(crate) fn initialize_class(vm: &mut VM) { let module_class = vm.get_class_by_name("Module"); let class_class = vm.define_standard_class_under("Class", module_class); mrb_define_cmethod(vm, class_class.clone(), "new", Box::new(mrb_class_new)); - mrb_define_cmethod(vm, class_class.clone(), "attr_reader", Box::new(mrb_class_attr_reader)); - mrb_define_cmethod(vm, class_class.clone(), "attr_writer", Box::new(mrb_class_attr_writer)); - mrb_define_cmethod(vm, class_class.clone(), "attr_accessor", Box::new(mrb_class_attr_acceccor)); - mrb_define_cmethod(vm, class_class.clone(), "attr", Box::new(mrb_class_attr_acceccor)); + mrb_define_cmethod( + vm, + class_class.clone(), + "attr_reader", + Box::new(mrb_class_attr_reader), + ); + mrb_define_cmethod( + vm, + class_class.clone(), + "attr_writer", + Box::new(mrb_class_attr_writer), + ); + mrb_define_cmethod( + vm, + class_class.clone(), + "attr_accessor", + Box::new(mrb_class_attr_acceccor), + ); + mrb_define_cmethod( + vm, + class_class.clone(), + "attr", + Box::new(mrb_class_attr_acceccor), + ); } fn mrb_class_new(vm: &mut VM, args: &[Rc]) -> Result, Error> { @@ -18,7 +45,9 @@ fn mrb_class_new(vm: &mut VM, args: &[Rc]) -> Result, Error let class = match &class.value { RValue::Class(c) => c.clone(), _ => { - return Err(Error::RuntimeError("Class#new must be called from class".to_string())); + return Err(Error::RuntimeError( + "Class#new must be called from class".to_string(), + )); } }; @@ -34,7 +63,9 @@ fn mrb_class_attr_reader(vm: &mut VM, args: &[Rc]) -> Result c.clone(), _ => { - return Err(Error::RuntimeError("Class#attr_reader must be called from class".to_string())); + return Err(Error::RuntimeError( + "Class#attr_reader must be called from class".to_string(), + )); } }; for arg in args.iter() { @@ -45,14 +76,15 @@ fn mrb_class_attr_reader(vm: &mut VM, args: &[Rc]) -> Result { - match i.ivar.borrow().get(&key) { - Some(v) => v.clone(), - None => Rc::new(RObject::nil()) - } + RValue::Instance(i) => match i.ivar.borrow().get(&key) { + Some(v) => v.clone(), + None => Rc::new(RObject::nil()), }, _ => { - return Err(Error::RuntimeError("attr_reader defined method must be called from instance".to_string())); + return Err(Error::RuntimeError( + "attr_reader defined method must be called from instance" + .to_string(), + )); } }; Ok(value) @@ -63,7 +95,9 @@ fn mrb_class_attr_reader(vm: &mut VM, args: &[Rc]) -> Result { - return Err(Error::RuntimeError("Class#attr_reader must be called with symbols".to_string())); + return Err(Error::RuntimeError( + "Class#attr_reader must be called with symbols".to_string(), + )); } } } @@ -75,7 +109,9 @@ fn mrb_class_attr_writer(vm: &mut VM, args: &[Rc]) -> Result c.clone(), _ => { - return Err(Error::RuntimeError("Class#attr_reader must be called from class".to_string())); + return Err(Error::RuntimeError( + "Class#attr_reader must be called from class".to_string(), + )); } }; for arg in args.iter() { @@ -89,9 +125,12 @@ fn mrb_class_attr_writer(vm: &mut VM, args: &[Rc]) -> Result { i.ivar.borrow_mut().insert(key, value.clone()); - }, + } _ => { - return Err(Error::RuntimeError("attr_reader defined method must be called from instance".to_string())); + return Err(Error::RuntimeError( + "attr_reader defined method must be called from instance" + .to_string(), + )); } }; Ok(value) @@ -103,7 +142,9 @@ fn mrb_class_attr_writer(vm: &mut VM, args: &[Rc]) -> Result { - return Err(Error::RuntimeError("Class#attr_reader must be called with symbols".to_string())); + return Err(Error::RuntimeError( + "Class#attr_reader must be called with symbols".to_string(), + )); } } } @@ -131,7 +172,8 @@ fn test_class_attr_accessor() { let args = vec![RObject::integer(557188).to_refcount_assigned()]; mrb_funcall(&mut vm, Some(instance.clone()), "foo=", &args).expect("call obj.foo = failed"); - let ret = mrb_funcall(&mut vm, Some(instance.clone()), "foo", &[]).expect("call obj.foo failed"); + let ret = + mrb_funcall(&mut vm, Some(instance.clone()), "foo", &[]).expect("call obj.foo failed"); let ret: i64 = ret.as_ref().try_into().expect("obj.foo must be integer"); assert_eq!(ret, 557188); -} \ No newline at end of file +} diff --git a/mrubyedge/src/yamrb/prelude/exception.rs b/mrubyedge/src/yamrb/prelude/exception.rs index 936d769..a0678f8 100644 --- a/mrubyedge/src/yamrb/prelude/exception.rs +++ b/mrubyedge/src/yamrb/prelude/exception.rs @@ -1,13 +1,17 @@ use std::rc::Rc; -use crate::{yamrb::{helpers::mrb_define_cmethod, value::*, vm::VM}, Error}; +use crate::{ + Error, + yamrb::{helpers::mrb_define_cmethod, value::*, vm::VM}, +}; pub(crate) fn initialize_exception(vm: &mut VM) { let exp_class: Rc = vm.define_standard_class("Exception"); let _ = vm.define_standard_class_under("InternalError", exp_class.clone()); // fill in ruby's standard exceptions: - let std_exp_class: Rc = vm.define_standard_class_under("StandardError", exp_class.clone()); + let std_exp_class: Rc = + vm.define_standard_class_under("StandardError", exp_class.clone()); let _ = vm.define_standard_class_under("RuntimeError", std_exp_class.clone()); let _ = vm.define_standard_class_under("NoMemoryError", exp_class.clone()); let _ = vm.define_standard_class_under("ScriptError", exp_class.clone()); @@ -32,9 +36,9 @@ pub fn mrb_exception_message(vm: &mut VM, _args: &[Rc]) -> Result { let message = e.as_ref().message.clone(); Ok(RObject::string(message).to_refcount_assigned()) - }, - _ => { - Err(Error::RuntimeError("Exception#message must be called on an Exception".to_string())) } + _ => Err(Error::RuntimeError( + "Exception#message must be called on an Exception".to_string(), + )), } -} \ No newline at end of file +} diff --git a/mrubyedge/src/yamrb/prelude/falseclass.rs b/mrubyedge/src/yamrb/prelude/falseclass.rs index 900bc4b..5aaf6fb 100644 --- a/mrubyedge/src/yamrb/prelude/falseclass.rs +++ b/mrubyedge/src/yamrb/prelude/falseclass.rs @@ -1,15 +1,25 @@ use std::rc::Rc; -use crate::yamrb::helpers::mrb_define_cmethod; use crate::Error; +use crate::yamrb::helpers::mrb_define_cmethod; use crate::yamrb::{value::RObject, vm::VM}; pub(crate) fn initialize_falseclass(vm: &mut VM) { let falseclass = vm.define_standard_class("FalseClass"); - mrb_define_cmethod(vm, falseclass.clone(), "to_s", Box::new(mrb_falseclass_to_s)); - mrb_define_cmethod(vm, falseclass.clone(), "inspect", Box::new(mrb_falseclass_inspect)); + mrb_define_cmethod( + vm, + falseclass.clone(), + "to_s", + Box::new(mrb_falseclass_to_s), + ); + mrb_define_cmethod( + vm, + falseclass.clone(), + "inspect", + Box::new(mrb_falseclass_inspect), + ); mrb_define_cmethod(vm, falseclass.clone(), "&", Box::new(mrb_falseclass_and)); mrb_define_cmethod(vm, falseclass.clone(), "|", Box::new(mrb_falseclass_or)); mrb_define_cmethod(vm, falseclass.clone(), "^", Box::new(mrb_falseclass_xor)); diff --git a/mrubyedge/src/yamrb/prelude/hash.rs b/mrubyedge/src/yamrb/prelude/hash.rs index 7692b89..b909e09 100644 --- a/mrubyedge/src/yamrb/prelude/hash.rs +++ b/mrubyedge/src/yamrb/prelude/hash.rs @@ -1,14 +1,31 @@ use std::{collections::HashMap, rc::Rc}; -use crate::{Error, yamrb::{helpers::{mrb_call_block, mrb_define_class_cmethod, mrb_define_cmethod}, value::{RObject, RValue}, vm::VM}}; +use crate::{ + Error, + yamrb::{ + helpers::{mrb_call_block, mrb_define_class_cmethod, mrb_define_cmethod}, + value::{RObject, RValue}, + vm::VM, + }, +}; pub(crate) fn initialize_hash(vm: &mut VM) { let hash_class = vm.define_standard_class("Hash"); mrb_define_class_cmethod(vm, hash_class.clone(), "new", Box::new(mrb_hash_new)); - mrb_define_cmethod(vm, hash_class.clone(), "[]", Box::new(mrb_hash_get_index_self)); - mrb_define_cmethod(vm, hash_class.clone(), "[]=", Box::new(mrb_hash_set_index_self)); + mrb_define_cmethod( + vm, + hash_class.clone(), + "[]", + Box::new(mrb_hash_get_index_self), + ); + mrb_define_cmethod( + vm, + hash_class.clone(), + "[]=", + Box::new(mrb_hash_set_index_self), + ); mrb_define_cmethod(vm, hash_class.clone(), "each", Box::new(mrb_hash_each)); mrb_define_cmethod(vm, hash_class.clone(), "size", Box::new(mrb_hash_size)); mrb_define_cmethod(vm, hash_class.clone(), "length", Box::new(mrb_hash_size)); @@ -27,11 +44,13 @@ pub fn mrb_hash_get_index(this: Rc, key: Rc) -> Result a.clone(), _ => { - return Err(Error::RuntimeError("Hash#[] must called on a hash".to_string())); + return Err(Error::RuntimeError( + "Hash#[] must called on a hash".to_string(), + )); } }; let hash = hash.borrow(); - let key = key.as_ref().as_hash_key()?; + let key = key.as_ref().as_hash_key()?; match hash.get(&key).clone() { Some((_, value)) => Ok(value.clone()), None => Ok(Rc::new(RObject::nil())), @@ -45,15 +64,21 @@ fn mrb_hash_set_index_self(vm: &mut VM, args: &[Rc]) -> Result, key: Rc, value: Rc) -> Result, Error> { +pub fn mrb_hash_set_index( + this: Rc, + key: Rc, + value: Rc, +) -> Result, Error> { let hash = match &this.value { RValue::Hash(a) => a, _ => { - return Err(Error::RuntimeError("Hash#[] must called on a hash".to_string())); + return Err(Error::RuntimeError( + "Hash#[] must called on a hash".to_string(), + )); } }; let mut hash = hash.borrow_mut(); - let hashed = key.as_hash_key()?; + let hashed = key.as_hash_key()?; hash.insert(hashed, (key.clone(), value.clone())); Ok(value.clone()) } @@ -70,7 +95,9 @@ fn mrb_hash_each(vm: &mut VM, args: &[Rc]) -> Result, Error } } _ => { - return Err(Error::RuntimeError("Hash#each must be called on a hash".to_string())); + return Err(Error::RuntimeError( + "Hash#each must be called on a hash".to_string(), + )); } }; Ok(this.clone()) @@ -85,8 +112,8 @@ fn test_hashing() { #[test] fn test_mrb_hash_set_and_index() { - use std::collections::HashMap; use crate::yamrb::*; + use std::collections::HashMap; let mut vm = VM::empty(); prelude::prelude(&mut vm); @@ -110,15 +137,18 @@ fn test_mrb_hash_set_and_index() { for (i, key) in keys.iter().enumerate() { let value = mrb_hash_get_index(hash.clone(), key.clone()).expect("getting index failed"); let value: i64 = value.as_ref().try_into().expect("value is not integer"); - let expected: i64 = values[i].as_ref().try_into().expect("expected is not integer"); + let expected: i64 = values[i] + .as_ref() + .try_into() + .expect("expected is not integer"); assert_eq!(value, expected); } } #[test] fn test_mrb_hash_set_and_index_not_found() { - use std::collections::HashMap; use crate::yamrb::*; + use std::collections::HashMap; let mut vm = VM::empty(); prelude::prelude(&mut vm); @@ -139,7 +169,9 @@ fn mrb_hash_size(vm: &mut VM, _args: &[Rc]) -> Result, Erro let hash = match &this.value { RValue::Hash(a) => a, _ => { - return Err(Error::RuntimeError("Hash#size must be called on a hash".to_string())); + return Err(Error::RuntimeError( + "Hash#size must be called on a hash".to_string(), + )); } }; let hash = hash.borrow(); @@ -165,4 +197,4 @@ fn test_mrb_hash_size() { let size = mrb_hash_size(&mut vm, &[]).expect("getting size failed"); let size: i64 = size.as_ref().try_into().expect("size is not integer"); assert_eq!(size, 1); -} \ No newline at end of file +} diff --git a/mrubyedge/src/yamrb/prelude/integer.rs b/mrubyedge/src/yamrb/prelude/integer.rs index 869336c..39b7147 100644 --- a/mrubyedge/src/yamrb/prelude/integer.rs +++ b/mrubyedge/src/yamrb/prelude/integer.rs @@ -1,7 +1,7 @@ use std::rc::Rc; -use crate::yamrb::helpers::mrb_define_cmethod; use crate::Error; +use crate::yamrb::helpers::mrb_define_cmethod; use crate::yamrb::{helpers::mrb_call_block, value::RObject, vm::VM}; @@ -9,7 +9,12 @@ pub(crate) fn initialize_integer(vm: &mut VM) { let integer_class = vm.define_standard_class("Integer"); mrb_define_cmethod(vm, integer_class.clone(), "%", Box::new(mrb_integer_mod)); - mrb_define_cmethod(vm, integer_class.clone(), "times", Box::new(mrb_integer_times)); + mrb_define_cmethod( + vm, + integer_class.clone(), + "times", + Box::new(mrb_integer_times), + ); } fn mrb_integer_times(vm: &mut VM, args: &[Rc]) -> Result, Error> { diff --git a/mrubyedge/src/yamrb/prelude/mod.rs b/mrubyedge/src/yamrb/prelude/mod.rs index 576c160..a2c8e18 100644 --- a/mrubyedge/src/yamrb/prelude/mod.rs +++ b/mrubyedge/src/yamrb/prelude/mod.rs @@ -4,19 +4,19 @@ use super::vm::VM; -pub mod object; -pub mod exception; +pub mod array; pub mod class; -pub mod module; -pub mod integer; -pub mod nilclass; -pub mod trueclass; +pub mod exception; pub mod falseclass; -pub mod string; -pub mod array; pub mod hash; +pub mod integer; +pub mod module; +pub mod nilclass; +pub mod object; pub mod range; pub mod shared_memory; +pub mod string; +pub mod trueclass; pub fn prelude(vm: &mut VM) { object::initialize_object(vm); diff --git a/mrubyedge/src/yamrb/prelude/module.rs b/mrubyedge/src/yamrb/prelude/module.rs index 8571146..87a17ef 100644 --- a/mrubyedge/src/yamrb/prelude/module.rs +++ b/mrubyedge/src/yamrb/prelude/module.rs @@ -1,8 +1,8 @@ use std::rc::Rc; use crate::{ - yamrb::{helpers::mrb_define_cmethod, value::*, vm::VM}, Error, + yamrb::{helpers::mrb_define_cmethod, value::*, vm::VM}, }; pub(crate) fn initialize_module(vm: &mut VM) { @@ -31,9 +31,11 @@ fn mrb_module_include(vm: &mut VM, args: &[Rc]) -> Result, let arg0 = &args[0]; let mixin = match &arg0.value { RValue::Module(module) => module.clone(), - _ => return Err(Error::RuntimeError( - "Module#include expects module arguments".to_string(), - )), + _ => { + return Err(Error::RuntimeError( + "Module#include expects module arguments".to_string(), + )); + } }; include_module(&target_module, mixin)?; diff --git a/mrubyedge/src/yamrb/prelude/nilclass.rs b/mrubyedge/src/yamrb/prelude/nilclass.rs index b9c5e07..a694dd9 100644 --- a/mrubyedge/src/yamrb/prelude/nilclass.rs +++ b/mrubyedge/src/yamrb/prelude/nilclass.rs @@ -1,7 +1,7 @@ use std::rc::Rc; -use crate::yamrb::helpers::mrb_define_cmethod; use crate::Error; +use crate::yamrb::helpers::mrb_define_cmethod; use crate::yamrb::{value::RObject, vm::VM}; @@ -9,7 +9,12 @@ pub(crate) fn initialize_nilclass(vm: &mut VM) { let nilclass = vm.define_standard_class("NilClass"); mrb_define_cmethod(vm, nilclass.clone(), "to_s", Box::new(mrb_nilclass_to_s)); - mrb_define_cmethod(vm, nilclass.clone(), "inspect", Box::new(mrb_nilclass_inspect)); + mrb_define_cmethod( + vm, + nilclass.clone(), + "inspect", + Box::new(mrb_nilclass_inspect), + ); mrb_define_cmethod(vm, nilclass.clone(), "nil?", Box::new(mrb_nilclass_nil_p)); } diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index 7c668d2..f4c9c21 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -1,35 +1,95 @@ use std::rc::Rc; -use crate::{yamrb::{helpers::{mrb_define_cmethod, mrb_funcall}, value::*, vm::VM}, Error}; +use crate::{ + Error, + yamrb::{ + helpers::{mrb_define_cmethod, mrb_funcall}, + value::*, + vm::VM, + }, +}; pub(crate) fn initialize_object(vm: &mut VM) { let object_class = vm.object_class.clone(); let klass = RObject::class(object_class.clone(), vm); vm.consts.insert("Object".to_string(), klass); - vm.builtin_class_table.insert("Object", object_class.clone()); + vm.builtin_class_table + .insert("Object", object_class.clone()); #[cfg(feature = "wasi")] { mrb_define_cmethod(vm, object_class.clone(), "puts", Box::new(mrb_kernel_puts)); mrb_define_cmethod(vm, object_class.clone(), "p", Box::new(mrb_kernel_p)); - mrb_define_cmethod(vm, object_class.clone(), "debug", Box::new(mrb_kernel_debug)); + mrb_define_cmethod( + vm, + object_class.clone(), + "debug", + Box::new(mrb_kernel_debug), + ); } - mrb_define_cmethod(vm, object_class.clone(), "initialize", Box::new(mrb_object_initialize)); - mrb_define_cmethod(vm, object_class.clone(), "==", Box::new(mrb_object_double_eq)); - mrb_define_cmethod(vm, object_class.clone(), "===", Box::new(mrb_object_triple_eq)); - mrb_define_cmethod(vm, object_class.clone(), "object_id", Box::new(mrb_object_object_id)); - mrb_define_cmethod(vm, object_class.clone(), "__id__", Box::new(mrb_object_object_id)); + mrb_define_cmethod( + vm, + object_class.clone(), + "initialize", + Box::new(mrb_object_initialize), + ); + mrb_define_cmethod( + vm, + object_class.clone(), + "==", + Box::new(mrb_object_double_eq), + ); + mrb_define_cmethod( + vm, + object_class.clone(), + "===", + Box::new(mrb_object_triple_eq), + ); + mrb_define_cmethod( + vm, + object_class.clone(), + "object_id", + Box::new(mrb_object_object_id), + ); + mrb_define_cmethod( + vm, + object_class.clone(), + "__id__", + Box::new(mrb_object_object_id), + ); mrb_define_cmethod(vm, object_class.clone(), "to_s", Box::new(mrb_object_to_s)); - mrb_define_cmethod(vm, object_class.clone(), "inspect", Box::new(mrb_object_to_s)); - mrb_define_cmethod(vm, object_class.clone(), "raise", Box::new(mrb_object_raise)); + mrb_define_cmethod( + vm, + object_class.clone(), + "inspect", + Box::new(mrb_object_to_s), + ); + mrb_define_cmethod( + vm, + object_class.clone(), + "raise", + Box::new(mrb_object_raise), + ); mrb_define_cmethod(vm, object_class.clone(), "nil?", Box::new(mrb_object_nil_p)); // define global consts: - vm.consts.insert("RUBY_VERSION".to_string(), Rc::new(RObject::string(crate::yamrb::vm::VERSION.to_string()))); - vm.consts.insert("MRUBY_VERSION".to_string(), Rc::new(RObject::string(crate::yamrb::vm::VERSION.to_string()))); - vm.consts.insert("MRUBY_EDGE_VERSION".to_string(), Rc::new(RObject::string(crate::yamrb::vm::VERSION.to_string()))); - vm.consts.insert("RUBY_ENGINE".to_string(), Rc::new(RObject::string(crate::yamrb::vm::ENGINE.to_string()))); + vm.consts.insert( + "RUBY_VERSION".to_string(), + Rc::new(RObject::string(crate::yamrb::vm::VERSION.to_string())), + ); + vm.consts.insert( + "MRUBY_VERSION".to_string(), + Rc::new(RObject::string(crate::yamrb::vm::VERSION.to_string())), + ); + vm.consts.insert( + "MRUBY_EDGE_VERSION".to_string(), + Rc::new(RObject::string(crate::yamrb::vm::VERSION.to_string())), + ); + vm.consts.insert( + "RUBY_ENGINE".to_string(), + Rc::new(RObject::string(crate::yamrb::vm::ENGINE.to_string())), + ); } #[cfg(feature = "wasi")] @@ -87,37 +147,23 @@ pub fn mrb_object_triple_eq(vm: &mut VM, args: &[Rc]) -> Result { - Ok(Rc::new(RObject::boolean(*i1 == *i2))) - } - (RValue::Float(f1), RValue::Float(f2)) => { - Ok(Rc::new(RObject::boolean(*f1 == *f2))) - } - (RValue::Symbol(sym1), RValue::Symbol(sym2)) => { - Ok(Rc::new(RObject::boolean(sym1 == sym2))) - } - (RValue::String(s1), RValue::String(s2)) => { - Ok(Rc::new(RObject::boolean(s1 == s2))) - } - (RValue::Class(c1), _) => { - match &lhs.value { - RValue::Class(c2) => { - Ok(Rc::new(RObject::boolean(c1.sym_id == c2.sym_id))) - } - _ => { - let c2 = lhs.get_class(vm); - Ok(Rc::new(RObject::boolean(c1.sym_id == c2.sym_id))) - } + (RValue::Integer(i1), RValue::Integer(i2)) => Ok(Rc::new(RObject::boolean(*i1 == *i2))), + (RValue::Float(f1), RValue::Float(f2)) => Ok(Rc::new(RObject::boolean(*f1 == *f2))), + (RValue::Symbol(sym1), RValue::Symbol(sym2)) => Ok(Rc::new(RObject::boolean(sym1 == sym2))), + (RValue::String(s1), RValue::String(s2)) => Ok(Rc::new(RObject::boolean(s1 == s2))), + (RValue::Class(c1), _) => match &lhs.value { + RValue::Class(c2) => Ok(Rc::new(RObject::boolean(c1.sym_id == c2.sym_id))), + _ => { + let c2 = lhs.get_class(vm); + Ok(Rc::new(RObject::boolean(c1.sym_id == c2.sym_id))) } - } + }, (RValue::Range(_s, _e, _v), _) => { let arg = vec![rhs]; mrb_funcall(vm, Some(lhs), "include?", &arg) } // TODO: Implement object id for generic instance - _ => { - Ok(Rc::new(RObject::boolean(false))) - } + _ => Ok(Rc::new(RObject::boolean(false))), } } @@ -133,7 +179,11 @@ pub fn mrb_object_to_s(vm: &mut VM, _args: &[Rc]) -> Result let obj = vm.getself()?; let class = obj.get_class(vm); let addr = format!("{:018p}", Rc::as_ptr(&obj)); - Ok(Rc::new(RObject::string(format!("#<{}:{}>", class.full_name(), addr)))) + Ok(Rc::new(RObject::string(format!( + "#<{}:{}>", + class.full_name(), + addr + )))) } pub fn mrb_object_raise(_vm: &mut VM, args: &[Rc]) -> Result, Error> { @@ -158,72 +208,114 @@ fn test_mrb_object_is_equal() { let lhs = RObject::integer(1).to_refcount_assigned(); let rhs = RObject::integer(1).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(ret); let lhs = RObject::integer(1).to_refcount_assigned(); let rhs = RObject::integer(3).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(!ret); let lhs = RObject::string("mruby/edge is Ruby".into()).to_refcount_assigned(); let rhs = RObject::string("mruby/edge is Ruby".into()).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(ret); let lhs = RObject::string("mruby/edge is Ruby".into()).to_refcount_assigned(); let rhs = RObject::string("mruby/edge is not Ruby".into()).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(!ret); let lhs = RObject::symbol("some".into()).to_refcount_assigned(); let rhs = RObject::symbol("some".into()).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(ret); let lhs = RObject::symbol("some".into()).to_refcount_assigned(); let rhs = RObject::symbol("other".into()).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(!ret); let lhs = RObject::boolean(true).to_refcount_assigned(); let rhs = RObject::boolean(true).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(ret); let lhs = RObject::boolean(false).to_refcount_assigned(); let rhs = RObject::boolean(false).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(ret); let lhs = RObject::boolean(true).to_refcount_assigned(); let rhs = RObject::boolean(false).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(!ret); let lhs = RObject::float(0.1).to_refcount_assigned(); let rhs = RObject::float(0.1).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(ret); let lhs = RObject::float(0.2).to_refcount_assigned(); let rhs = RObject::float(0.1).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(!ret); let lhs = RObject::nil().to_refcount_assigned(); let rhs = RObject::nil().to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(ret); let lhs = RObject::integer(100).to_refcount_assigned(); let rhs = RObject::nil().to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(!ret); let lhs = RObject::integer(100).to_refcount_assigned(); let rhs = RObject::nil().to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(!ret); } @@ -235,14 +327,20 @@ fn test_mrb_object_is_equal_range() { let e = RObject::integer(10).to_refcount_assigned(); let lhs = RObject::range(s.clone(), e.clone(), true).to_refcount_assigned(); let rhs = RObject::range(s.clone(), e.clone(), true).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(ret); let s = RObject::integer(1).to_refcount_assigned(); let e = RObject::integer(10).to_refcount_assigned(); let lhs = RObject::range(s.clone(), e.clone(), true).to_refcount_assigned(); let rhs = RObject::range(s.clone(), e.clone(), false).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(!ret); let s = RObject::integer(1).to_refcount_assigned(); @@ -250,14 +348,20 @@ fn test_mrb_object_is_equal_range() { let e2 = RObject::integer(11).to_refcount_assigned(); let lhs = RObject::range(s.clone(), e.clone(), true).to_refcount_assigned(); let rhs = RObject::range(s.clone(), e2.clone(), true).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(!ret); let s = RObject::string("a".into()).to_refcount_assigned(); let e = RObject::string("z".into()).to_refcount_assigned(); let lhs = RObject::range(s.clone(), e.clone(), true).to_refcount_assigned(); let rhs = RObject::range(s.clone(), e.clone(), true).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(ret); let s = RObject::string("a".into()).to_refcount_assigned(); @@ -265,7 +369,10 @@ fn test_mrb_object_is_equal_range() { let e2 = RObject::string("A".into()).to_refcount_assigned(); let lhs = RObject::range(s.clone(), e.clone(), true).to_refcount_assigned(); let rhs = RObject::range(s.clone(), e2.clone(), true).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(!ret); } @@ -275,7 +382,10 @@ fn test_mrb_object_is_equal_array() { let lhs = RObject::array(vec![]).to_refcount_assigned(); let rhs = RObject::array(vec![]).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(ret); let lhs = vec![ @@ -290,7 +400,10 @@ fn test_mrb_object_is_equal_array() { ]; let lhs = RObject::array(lhs).to_refcount_assigned(); let rhs = RObject::array(rhs).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(ret); let lhs = vec![ @@ -305,53 +418,128 @@ fn test_mrb_object_is_equal_array() { ]; let lhs = RObject::array(lhs).to_refcount_assigned(); let rhs = RObject::array(rhs).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(!ret); } #[test] fn test_mrb_object_is_equal_hash() { - use std::collections::HashMap; use crate::yamrb::prelude::hash::*; + use std::collections::HashMap; let mut vm = VM::empty(); let lhs = RObject::hash(HashMap::new()).to_refcount_assigned(); let rhs = RObject::hash(HashMap::new()).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(ret); let lhs = RObject::hash(HashMap::new()).to_refcount_assigned(); - mrb_hash_set_index(lhs.clone(), RObject::symbol("key1".into()).to_refcount_assigned(), RObject::integer(1).to_refcount_assigned()).expect("set index failed"); - mrb_hash_set_index(lhs.clone(), RObject::symbol("key2".into()).to_refcount_assigned(), RObject::integer(2).to_refcount_assigned()).expect("set index failed"); + mrb_hash_set_index( + lhs.clone(), + RObject::symbol("key1".into()).to_refcount_assigned(), + RObject::integer(1).to_refcount_assigned(), + ) + .expect("set index failed"); + mrb_hash_set_index( + lhs.clone(), + RObject::symbol("key2".into()).to_refcount_assigned(), + RObject::integer(2).to_refcount_assigned(), + ) + .expect("set index failed"); let rhs = RObject::hash(HashMap::new()).to_refcount_assigned(); - mrb_hash_set_index(rhs.clone(), RObject::symbol("key2".into()).to_refcount_assigned(), RObject::integer(2).to_refcount_assigned()).expect("set index failed"); - mrb_hash_set_index(rhs.clone(), RObject::symbol("key1".into()).to_refcount_assigned(), RObject::integer(1).to_refcount_assigned()).expect("set index failed"); + mrb_hash_set_index( + rhs.clone(), + RObject::symbol("key2".into()).to_refcount_assigned(), + RObject::integer(2).to_refcount_assigned(), + ) + .expect("set index failed"); + mrb_hash_set_index( + rhs.clone(), + RObject::symbol("key1".into()).to_refcount_assigned(), + RObject::integer(1).to_refcount_assigned(), + ) + .expect("set index failed"); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(ret); let lhs = RObject::hash(HashMap::new()).to_refcount_assigned(); - mrb_hash_set_index(lhs.clone(), RObject::symbol("key1".into()).to_refcount_assigned(), RObject::integer(1).to_refcount_assigned()).expect("set index failed"); - mrb_hash_set_index(lhs.clone(), RObject::symbol("key2".into()).to_refcount_assigned(), RObject::integer(2).to_refcount_assigned()).expect("set index failed"); + mrb_hash_set_index( + lhs.clone(), + RObject::symbol("key1".into()).to_refcount_assigned(), + RObject::integer(1).to_refcount_assigned(), + ) + .expect("set index failed"); + mrb_hash_set_index( + lhs.clone(), + RObject::symbol("key2".into()).to_refcount_assigned(), + RObject::integer(2).to_refcount_assigned(), + ) + .expect("set index failed"); let rhs = RObject::hash(HashMap::new()).to_refcount_assigned(); - mrb_hash_set_index(rhs.clone(), RObject::symbol("key2".into()).to_refcount_assigned(), RObject::integer(2).to_refcount_assigned()).expect("set index failed"); - mrb_hash_set_index(rhs.clone(), RObject::symbol("key1".into()).to_refcount_assigned(), RObject::integer(3).to_refcount_assigned()).expect("set index failed"); + mrb_hash_set_index( + rhs.clone(), + RObject::symbol("key2".into()).to_refcount_assigned(), + RObject::integer(2).to_refcount_assigned(), + ) + .expect("set index failed"); + mrb_hash_set_index( + rhs.clone(), + RObject::symbol("key1".into()).to_refcount_assigned(), + RObject::integer(3).to_refcount_assigned(), + ) + .expect("set index failed"); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(!ret); let lhs = RObject::hash(HashMap::new()).to_refcount_assigned(); - mrb_hash_set_index(lhs.clone(), RObject::symbol("key1".into()).to_refcount_assigned(), RObject::integer(1).to_refcount_assigned()).expect("set index failed"); - mrb_hash_set_index(lhs.clone(), RObject::symbol("key2".into()).to_refcount_assigned(), RObject::integer(2).to_refcount_assigned()).expect("set index failed"); + mrb_hash_set_index( + lhs.clone(), + RObject::symbol("key1".into()).to_refcount_assigned(), + RObject::integer(1).to_refcount_assigned(), + ) + .expect("set index failed"); + mrb_hash_set_index( + lhs.clone(), + RObject::symbol("key2".into()).to_refcount_assigned(), + RObject::integer(2).to_refcount_assigned(), + ) + .expect("set index failed"); let rhs = RObject::hash(HashMap::new()).to_refcount_assigned(); - mrb_hash_set_index(rhs.clone(), RObject::symbol("key2".into()).to_refcount_assigned(), RObject::integer(2).to_refcount_assigned()).expect("set index failed"); - mrb_hash_set_index(rhs.clone(), RObject::symbol("key1-b".into()).to_refcount_assigned(), RObject::integer(1).to_refcount_assigned()).expect("set index failed"); + mrb_hash_set_index( + rhs.clone(), + RObject::symbol("key2".into()).to_refcount_assigned(), + RObject::integer(2).to_refcount_assigned(), + ) + .expect("set index failed"); + mrb_hash_set_index( + rhs.clone(), + RObject::symbol("key1-b".into()).to_refcount_assigned(), + RObject::integer(1).to_refcount_assigned(), + ) + .expect("set index failed"); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(!ret); } @@ -360,17 +548,23 @@ fn test_mrb_object_is_equal_klass() { let mut vm = VM::empty(); let lhs: Rc = vm.get_class_by_name("String"); - let rhs: Rc = RObject::string("String".into()).get_class(&mut vm); + let rhs: Rc = RObject::string("String".into()).get_class(&mut vm); let lhs = RObject::class(lhs.clone(), &mut vm); let rhs = RObject::class(rhs.clone(), &mut vm); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(ret); let lhs: Rc = RObject::integer(5471).get_class(&mut vm); - let rhs: Rc = RObject::string("String".into()).get_class(&mut vm); + let rhs: Rc = RObject::string("String".into()).get_class(&mut vm); let lhs = RObject::class(lhs.clone(), &mut vm); let rhs = RObject::class(rhs.clone(), &mut vm); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(!ret); } @@ -380,11 +574,17 @@ fn test_mrb_object_is_equal_instance() { let lhs = RObject::instance(vm.object_class.clone()).to_refcount_assigned(); let rhs = lhs.clone(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(ret); let lhs = RObject::instance(vm.object_class.clone()).to_refcount_assigned(); let rhs = RObject::instance(vm.object_class.clone()).to_refcount_assigned(); - let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs).as_ref().try_into().expect("must return bool"); + let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) + .as_ref() + .try_into() + .expect("must return bool"); assert!(!ret); -} \ No newline at end of file +} diff --git a/mrubyedge/src/yamrb/prelude/range.rs b/mrubyedge/src/yamrb/prelude/range.rs index 582b1cd..6e98729 100644 --- a/mrubyedge/src/yamrb/prelude/range.rs +++ b/mrubyedge/src/yamrb/prelude/range.rs @@ -1,11 +1,23 @@ use std::rc::Rc; -use crate::{yamrb::{helpers::{mrb_call_block, mrb_define_cmethod}, value::{RObject, RValue}, vm::VM}, Error}; +use crate::{ + Error, + yamrb::{ + helpers::{mrb_call_block, mrb_define_cmethod}, + value::{RObject, RValue}, + vm::VM, + }, +}; pub(crate) fn initialize_range(vm: &mut VM) { let range_class = vm.define_standard_class("Range"); - - mrb_define_cmethod(vm, range_class.clone(), "include?", Box::new(mrb_range_is_include)); + + mrb_define_cmethod( + vm, + range_class.clone(), + "include?", + Box::new(mrb_range_is_include), + ); mrb_define_cmethod(vm, range_class.clone(), "each", Box::new(mrb_range_each)); } @@ -36,7 +48,9 @@ pub fn mrb_range_is_include(vm: &mut VM, args: &[Rc]) -> Result { - return Err(Error::RuntimeError("Range#include? must be called on a Range".to_string())); + return Err(Error::RuntimeError( + "Range#include? must be called on a Range".to_string(), + )); } } } @@ -45,27 +59,29 @@ pub fn mrb_range_each(vm: &mut VM, args: &[Rc]) -> Result, let this = vm.getself()?; let block = &args[0]; match &this.value { - RValue::Range(start, end, exclusive) => { - match (&start.value, &end.value) { - (RValue::Integer(start), RValue::Integer(end)) => { - let start = *start; - let mut end = *end; - if *exclusive { - end = end - 1; - } - for i in start..=end { - let args = vec![Rc::new(RObject::integer(i))]; - mrb_call_block(vm, block.clone(), None, &args)?; - } + RValue::Range(start, end, exclusive) => match (&start.value, &end.value) { + (RValue::Integer(start), RValue::Integer(end)) => { + let start = *start; + let mut end = *end; + if *exclusive { + end = end - 1; } - _ => { - return Err(Error::RuntimeError("Range#each must be called on a integer Range with block (for now)".to_string())); + for i in start..=end { + let args = vec![Rc::new(RObject::integer(i))]; + mrb_call_block(vm, block.clone(), None, &args)?; } } - } + _ => { + return Err(Error::RuntimeError( + "Range#each must be called on a integer Range with block (for now)".to_string(), + )); + } + }, _ => { - return Err(Error::RuntimeError("Range#each must be called on a Range".to_string())); + return Err(Error::RuntimeError( + "Range#each must be called on a Range".to_string(), + )); } } Ok(this.clone()) -} \ No newline at end of file +} diff --git a/mrubyedge/src/yamrb/prelude/shared_memory.rs b/mrubyedge/src/yamrb/prelude/shared_memory.rs index ce4eb53..f0767dd 100644 --- a/mrubyedge/src/yamrb/prelude/shared_memory.rs +++ b/mrubyedge/src/yamrb/prelude/shared_memory.rs @@ -4,71 +4,124 @@ use std::rc::Rc; use crate::yamrb::helpers::mrb_define_class_cmethod; use crate::yamrb::shared_memory::SharedMemory; use crate::yamrb::vm::VM; -use crate::{yamrb::{helpers::mrb_define_cmethod, value::{RObject, RValue, RType}}, Error}; +use crate::{ + Error, + yamrb::{ + helpers::mrb_define_cmethod, + value::{RObject, RType, RValue}, + }, +}; pub(crate) fn initialize_shared_memory(vm: &mut VM) { let shared_memory_class = vm.define_standard_class("SharedMemory"); - mrb_define_class_cmethod(vm, shared_memory_class.clone(), "new", Box::new(mrb_shared_memory_new)); + mrb_define_class_cmethod( + vm, + shared_memory_class.clone(), + "new", + Box::new(mrb_shared_memory_new), + ); - mrb_define_cmethod(vm, shared_memory_class.clone(), "to_s", Box::new(mrb_shared_memory_to_string)); - mrb_define_cmethod(vm, shared_memory_class.clone(), "offset_in_memory", Box::new(mrb_shared_memory_offset_in_memory)); - mrb_define_cmethod(vm, shared_memory_class.clone(), "to_i", Box::new(mrb_shared_memory_offset_in_memory)); - mrb_define_cmethod(vm, shared_memory_class.clone(), "[]", Box::new(mrb_shared_memory_index_range)); - mrb_define_cmethod(vm, shared_memory_class.clone(), "[]=", Box::new(mrb_shared_memory_set_index_range)); - mrb_define_cmethod(vm, shared_memory_class.clone(), "read_by_size", Box::new(mrb_shared_memory_read_by_size)); + mrb_define_cmethod( + vm, + shared_memory_class.clone(), + "to_s", + Box::new(mrb_shared_memory_to_string), + ); + mrb_define_cmethod( + vm, + shared_memory_class.clone(), + "offset_in_memory", + Box::new(mrb_shared_memory_offset_in_memory), + ); + mrb_define_cmethod( + vm, + shared_memory_class.clone(), + "to_i", + Box::new(mrb_shared_memory_offset_in_memory), + ); + mrb_define_cmethod( + vm, + shared_memory_class.clone(), + "[]", + Box::new(mrb_shared_memory_index_range), + ); + mrb_define_cmethod( + vm, + shared_memory_class.clone(), + "[]=", + Box::new(mrb_shared_memory_set_index_range), + ); + mrb_define_cmethod( + vm, + shared_memory_class.clone(), + "read_by_size", + Box::new(mrb_shared_memory_read_by_size), + ); } pub fn mrb_shared_memory_new(_vm: &mut VM, args: &[Rc]) -> Result, Error> { let size: u64 = args[0].as_ref().try_into().expect("arg[0] must be integer"); let obj = RObject { tt: RType::SharedMemory, - value: RValue::SharedMemory(Rc::new(RefCell::new( - SharedMemory::new(size as usize), - ))), + value: RValue::SharedMemory(Rc::new(RefCell::new(SharedMemory::new(size as usize)))), object_id: u64::MAX.into(), singleton_class: RefCell::new(None), }; Ok(obj.to_refcount_assigned()) } -fn mrb_shared_memory_offset_in_memory(vm: &mut VM, _args: &[Rc]) -> Result, Error> { +fn mrb_shared_memory_offset_in_memory( + vm: &mut VM, + _args: &[Rc], +) -> Result, Error> { let this = vm.getself()?; let sm = match &this.value { RValue::SharedMemory(s) => s, _ => { - return Err(Error::RuntimeError("SharedMemory#to_s must be called on a SharedMemory".to_string())); + return Err(Error::RuntimeError( + "SharedMemory#to_s must be called on a SharedMemory".to_string(), + )); } }; let offset = sm.borrow().offset_in_memory(); Ok(Rc::new(RObject::integer(offset as i64))) } -fn mrb_shared_memory_set_index_range(vm: &mut VM, args: &[Rc]) -> Result, Error> { +fn mrb_shared_memory_set_index_range( + vm: &mut VM, + args: &[Rc], +) -> Result, Error> { let this = vm.getself()?; let (start, end) = match &args[0].as_ref().value { RValue::Range(start, end, exclusive) => { let start: u64 = start.as_ref().try_into()?; let end: u64 = end.as_ref().try_into()?; if *exclusive { - (start, end-1) + (start, end - 1) } else { (start, end) } } _ => { - return Err(Error::RuntimeError("Range should be passed on SharedMemory#[]=".to_string())); + return Err(Error::RuntimeError( + "Range should be passed on SharedMemory#[]=".to_string(), + )); } }; let sm = match &this.value { RValue::SharedMemory(s) => s, _ => { - return Err(Error::RuntimeError("SharedMemory#to_s must be called on a SharedMemory".to_string())); + return Err(Error::RuntimeError( + "SharedMemory#to_s must be called on a SharedMemory".to_string(), + )); } }; let data: Vec = args[1].as_ref().try_into()?; if data.len() != (end - start + 1) as usize { - return Err(Error::RuntimeError("Data length must be equal to range length".to_string())); + return Err(Error::RuntimeError( + "Data length must be equal to range length".to_string(), + )); } let mut sm = sm.borrow_mut(); sm.write(start as usize, &data); @@ -80,7 +133,9 @@ fn mrb_shared_memory_to_string(vm: &mut VM, _args: &[Rc]) -> Result s, _ => { - return Err(Error::RuntimeError("SharedMemory#to_s must be called on a SharedMemory".to_string())); + return Err(Error::RuntimeError( + "SharedMemory#to_s must be called on a SharedMemory".to_string(), + )); } }; let range = sm.borrow().memory.as_ref().to_vec(); @@ -94,19 +149,23 @@ fn mrb_shared_memory_index_range(vm: &mut VM, args: &[Rc]) -> Result { - return Err(Error::RuntimeError("Range should be passed on SharedMemory#[]".to_string())); + return Err(Error::RuntimeError( + "Range should be passed on SharedMemory#[]".to_string(), + )); } }; let sm = match &this.value { RValue::SharedMemory(s) => s, _ => { - return Err(Error::RuntimeError("this value's not a SharedMemory".to_string())); + return Err(Error::RuntimeError( + "this value's not a SharedMemory".to_string(), + )); } }; let range = sm.borrow().memory.as_ref()[(start as usize)..=(end as usize)].to_vec(); @@ -122,7 +181,9 @@ fn mrb_shared_memory_read_by_size(vm: &mut VM, args: &[Rc]) -> Result s, _ => { - return Err(Error::RuntimeError("SharedMemory#to_s must be called on a SharedMemory".to_string())); + return Err(Error::RuntimeError( + "SharedMemory#to_s must be called on a SharedMemory".to_string(), + )); } }; match size { @@ -163,9 +224,7 @@ fn mrb_shared_memory_read_by_size(vm: &mut VM, args: &[Rc]) -> Result { - Err(Error::RuntimeError("Invalid size passed".to_string())) - } + _ => Err(Error::RuntimeError("Invalid size passed".to_string())), } } @@ -225,4 +284,4 @@ fn test_mrb_shared_memory_read_by_size() { let result = mrb_shared_memory_read_by_size(&mut vm, &args).expect("failed to read"); let result: i64 = result.as_ref().try_into().expect("not an integer"); assert_eq!(result, 117835012); -} \ No newline at end of file +} diff --git a/mrubyedge/src/yamrb/prelude/string.rs b/mrubyedge/src/yamrb/prelude/string.rs index c8ea25a..9a4ee9c 100644 --- a/mrubyedge/src/yamrb/prelude/string.rs +++ b/mrubyedge/src/yamrb/prelude/string.rs @@ -1,6 +1,13 @@ use std::rc::Rc; -use crate::{Error, yamrb::{helpers::{mrb_define_class_cmethod, mrb_define_cmethod}, value::RObject, vm::VM}}; +use crate::{ + Error, + yamrb::{ + helpers::{mrb_define_class_cmethod, mrb_define_cmethod}, + value::RObject, + vm::VM, + }, +}; use super::array::mrb_array_push; @@ -10,9 +17,19 @@ pub(crate) fn initialize_string(vm: &mut VM) { mrb_define_class_cmethod(vm, string_class.clone(), "new", Box::new(mrb_string_new)); - mrb_define_cmethod(vm, string_class.clone(), "unpack", Box::new(mrb_string_unpack)); + mrb_define_cmethod( + vm, + string_class.clone(), + "unpack", + Box::new(mrb_string_unpack), + ); mrb_define_cmethod(vm, string_class.clone(), "size", Box::new(mrb_string_size)); - mrb_define_cmethod(vm, string_class.clone(), "length", Box::new(mrb_string_size)); + mrb_define_cmethod( + vm, + string_class.clone(), + "length", + Box::new(mrb_string_size), + ); } pub fn mrb_string_new(_vm: &mut VM, args: &[Rc]) -> Result, Error> { @@ -31,7 +48,9 @@ fn bytes_of(value: &[u8], cursor: usize) -> Result<[u8; N], Erro if value.len() < cursor + N { return Err(Error::RuntimeError("Not enough bytes".to_string())); } - value[cursor..cursor + N].try_into().map_err(|_| Error::RuntimeError(format!("Bit size mismatch: {}", N))) + value[cursor..cursor + N] + .try_into() + .map_err(|_| Error::RuntimeError(format!("Bit size mismatch: {}", N))) } // Represents Ruby's String#unpack method. @@ -117,31 +136,31 @@ fn test_mrb_string_unpack() { let mut vm = VM::empty(); prelude::prelude(&mut vm); - let data = Rc::new(RObject::string_from_vec( - vec![ - 0x01, - 0x02, 0x03, - 0x04, 0x05, 0x06, 0x07, - 0x04, 0x04, 0x03, 0x03, 0x02, 0x02, 0x00, 0x00, - ], - )); - let format = Rc::new(RObject::string( - "c s l q".to_string(), - )); + let data = Rc::new(RObject::string_from_vec(vec![ + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x04, 0x04, 0x03, 0x03, 0x02, 0x02, 0x00, 0x00, + ])); + let format = Rc::new(RObject::string("c s l q".to_string())); let arg = vec![format]; let ret = helpers::mrb_funcall(&mut vm, Some(data), "unpack", &arg).expect("unpack failed"); - + let answers = vec![ 0x01, 0x02 | 0x03 << 8, 0x04 | 0x05 << 8 | 0x06 << 16 | 0x07 << 24, - 0x04 | 0x04 << 8 | 0x03 << 16 | 0x03 << 24 | 0x02 << 32 | 0x02 << 40 | 0x00 << 48 | 0x00 << 56, + 0x04 | 0x04 << 8 + | 0x03 << 16 + | 0x03 << 24 + | 0x02 << 32 + | 0x02 << 40 + | 0x00 << 48 + | 0x00 << 56, ]; for (i, expected) in answers.iter().enumerate() { let args = vec![Rc::new(RObject::integer(i as i64))]; - let value = prelude::array::mrb_array_get_index(ret.clone(), &args).expect("getting index failed"); + let value = + prelude::array::mrb_array_get_index(ret.clone(), &args).expect("getting index failed"); let value: i64 = value.as_ref().try_into().expect("value is not integer"); assert_eq!(value, *expected); } @@ -168,4 +187,4 @@ fn test_mrb_string_size() { let ret = helpers::mrb_funcall(&mut vm, Some(data), "length", &[]).expect("size failed"); let ret: i64 = ret.as_ref().try_into().expect("size is not integer"); assert_eq!(ret, 12); -} \ No newline at end of file +} diff --git a/mrubyedge/src/yamrb/prelude/trueclass.rs b/mrubyedge/src/yamrb/prelude/trueclass.rs index 118e658..67f9968 100644 --- a/mrubyedge/src/yamrb/prelude/trueclass.rs +++ b/mrubyedge/src/yamrb/prelude/trueclass.rs @@ -1,7 +1,7 @@ use std::rc::Rc; -use crate::yamrb::helpers::mrb_define_cmethod; use crate::Error; +use crate::yamrb::helpers::mrb_define_cmethod; use crate::yamrb::{value::RObject, vm::VM}; @@ -9,7 +9,12 @@ pub(crate) fn initialize_trueclass(vm: &mut VM) { let trueclass = vm.define_standard_class("TrueClass"); mrb_define_cmethod(vm, trueclass.clone(), "to_s", Box::new(mrb_trueclass_to_s)); - mrb_define_cmethod(vm, trueclass.clone(), "inspect", Box::new(mrb_trueclass_inspect)); + mrb_define_cmethod( + vm, + trueclass.clone(), + "inspect", + Box::new(mrb_trueclass_inspect), + ); mrb_define_cmethod(vm, trueclass.clone(), "&", Box::new(mrb_trueclass_and)); mrb_define_cmethod(vm, trueclass.clone(), "|", Box::new(mrb_trueclass_or)); mrb_define_cmethod(vm, trueclass.clone(), "^", Box::new(mrb_trueclass_xor)); diff --git a/mrubyedge/src/yamrb/shared_memory.rs b/mrubyedge/src/yamrb/shared_memory.rs index 5ff0f84..12d7568 100644 --- a/mrubyedge/src/yamrb/shared_memory.rs +++ b/mrubyedge/src/yamrb/shared_memory.rs @@ -32,4 +32,4 @@ impl SharedMemory { pub fn read_u8(&self, offset: usize) -> u8 { self.memory[offset] } -} \ No newline at end of file +} diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 70070e4..7612b37 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -210,8 +210,7 @@ impl RObject { Some(robj) => robj.clone(), None => { let robj = Self::newclass(c.clone()); - vm.class_object_table - .insert(c.full_name(), robj.clone()); + vm.class_object_table.insert(c.full_name(), robj.clone()); robj } } @@ -223,7 +222,8 @@ impl RObject { value: RValue::Class(c), object_id: (u64::MAX).into(), singleton_class: RefCell::new(None), - }.to_refcount_assigned() + } + .to_refcount_assigned() } pub fn module(m: Rc) -> Self { diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 69f7163..3deb750 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -1,15 +1,15 @@ use std::cell::{Cell, RefCell}; +use std::collections::HashMap; use std::env; use std::rc::Rc; -use std::collections::HashMap; -use crate::rite::{insn, Irep, Rite}; use crate::Error; +use crate::rite::{Irep, Rite, insn}; -use super::{op, optable::*}; +use super::op::Op; use super::prelude::prelude; use super::value::*; -use super::op::Op; +use super::{op, optable::*}; pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); pub const ENGINE: &'static str = "mruby/edge"; @@ -33,7 +33,7 @@ impl TargetContext { pub struct VM { pub irep: Rc, - + pub id: usize, pub bytecode: Vec, pub current_irep: Rc, @@ -79,9 +79,12 @@ impl VM { nlocals: 0, nregs: 0, rlen: 0, - code: vec![ - op::Op { code: insn::OpCode::STOP, operand: insn::Fetched::Z, pos: 18, len: 1 }, - ], + code: vec![op::Op { + code: insn::OpCode::STOP, + operand: insn::Fetched::Z, + pos: 18, + len: 1, + }], syms: Vec::new(), pool: Vec::new(), reps: Vec::new(), @@ -137,11 +140,11 @@ impl VM { upper, cur_env, has_env_ref, - fn_table + fn_table, }; prelude(&mut vm); - + vm } @@ -151,7 +154,7 @@ impl VM { pub fn run(&mut self) -> Result, Box> { let class = self.object_class.clone(); // Insert top_self - let top_self = RObject{ + let top_self = RObject { tt: RType::Instance, value: RValue::Instance(RInstance { class, @@ -161,14 +164,15 @@ impl VM { }), object_id: 0.into(), singleton_class: RefCell::new(None), - }.to_refcount_assigned(); + } + .to_refcount_assigned(); if self.current_regs()[0].is_none() { self.current_regs()[0].replace(top_self.clone()); } let mut rescued = false; loop { - if ! rescued { + if !rescued { if let Some(_e) = self.exception.clone() { let operand = insn::Fetched::B(0); if let Some(pos) = self.find_next_handler_pos() { @@ -178,7 +182,7 @@ impl VM { } match op_return(self, &operand) { - Ok(_) => {}, + Ok(_) => {} Err(_) => { // use assigned expection through break; @@ -198,17 +202,22 @@ impl VM { // reached end of the IREP break; } - let op = self.current_irep.code.get(pc).ok_or_else( - || Error::internal("end of opcode reached") - )?; + let op = self + .current_irep + .code + .get(pc) + .ok_or_else(|| Error::internal("end of opcode reached"))?; let operand = op.operand; self.pc.set(pc + 1); if env::var("MRUBYEDGE_DEBUG").is_ok() { - eprintln!("{:?}: {:?} (pos={} len={})", &op.code, &op.operand, op.pos, op.len); + eprintln!( + "{:?}: {:?} (pos={} len={})", + &op.code, &op.operand, op.pos, op.len + ); } match consume_expr(self, op.code, &operand, op.pos, op.len) { - Ok(_) => {}, + Ok(_) => {} Err(e) => { let exception = RException::from_error(self, &e); self.exception = Some(Rc::new(exception)); @@ -229,7 +238,7 @@ impl VM { let retval = match self.current_regs()[0].take() { Some(v) => Ok(v), - None => Ok(Rc::new(RObject::nil())) + None => Ok(Rc::new(RObject::nil())), }; self.current_regs()[0].replace(top_self.clone()); @@ -251,11 +260,15 @@ impl VM { } pub(crate) fn get_current_regs_cloned(&mut self, i: usize) -> Result, Error> { - self.current_regs()[i].clone().ok_or_else(|| Error::internal(format!("register {} is not assigned", i))) + self.current_regs()[i] + .clone() + .ok_or_else(|| Error::internal(format!("register {} is not assigned", i))) } pub(crate) fn take_current_regs(&mut self, i: usize) -> Result, Error> { - self.current_regs()[i].take().ok_or_else(|| Error::internal(format!("register {} is not assigned", i))) + self.current_regs()[i] + .take() + .ok_or_else(|| Error::internal(format!("register {} is not assigned", i))) } /// Returns the current `self` object from register 0, or an error if it has @@ -267,14 +280,16 @@ impl VM { /// Retrieves `self` without error handling, panicking if register 0 is /// empty. Prefer [`VM::getself`] when the value may be absent. pub fn must_getself(&mut self) -> Rc { - self.current_regs()[0].clone().expect("self is not assigned") + self.current_regs()[0] + .clone() + .expect("self is not assigned") } pub(crate) fn register_fn(&mut self, f: RFn) -> usize { self.fn_table.push(Rc::new(f)); self.fn_table.len() - 1 } - + pub(crate) fn get_fn(&self, i: usize) -> Option> { self.fn_table.get(i).cloned() } @@ -282,20 +297,26 @@ impl VM { /// Looks up a previously defined builtin class by name. Panics if the /// class does not exist, which usually signals a missing prelude setup. pub fn get_class_by_name(&self, name: &str) -> Rc { - self.builtin_class_table.get(name).cloned().expect(format!("Class {} not found", name).as_str()) + self.builtin_class_table + .get(name) + .cloned() + .expect(format!("Class {} not found", name).as_str()) } /// Defines a new class under the optional parent module, inheriting from /// `superclass` or `Object` by default, and registers it in the constant /// table. The resulting class object is returned for further mutation. - pub fn define_class(&mut self, name: &str, superclass: Option>, parent_module: Option>) -> Rc { + pub fn define_class( + &mut self, + name: &str, + superclass: Option>, + parent_module: Option>, + ) -> Rc { let superclass = match superclass { Some(c) => c, None => self.object_class.clone(), }; - let class = Rc::new( - RClass::new(name, Some(superclass), parent_module), - ); + let class = Rc::new(RClass::new(name, Some(superclass), parent_module)); let object = RObject::class(class.clone(), self); self.consts.insert(name.to_string(), object); class @@ -319,7 +340,11 @@ impl VM { class } - pub(crate) fn define_standard_class_under(&mut self, name: &'static str, sklass: Rc) -> Rc { + pub(crate) fn define_standard_class_under( + &mut self, + name: &'static str, + sklass: Rc, + ) -> Rc { let class = self.define_class(name, Some(sklass.clone()), Some(sklass.module.clone())); self.builtin_class_table.insert(name, class.clone()); class @@ -343,7 +368,7 @@ fn load_irep_1(reps: &mut [Irep], pos: usize) -> (IREP, usize) { let irep = &mut reps[pos]; let mut irep1 = IREP { __id: pos, - nlocals: irep.nlocals(), + nlocals: irep.nlocals(), nregs: irep.nregs(), rlen: irep.rlen(), code: Vec::new(), @@ -353,15 +378,23 @@ fn load_irep_1(reps: &mut [Irep], pos: usize) -> (IREP, usize) { catch_target_pos: Vec::new(), }; for sym in irep.syms.iter() { - irep1.syms.push(RSym::new(sym.to_string_lossy().to_string())); + irep1 + .syms + .push(RSym::new(sym.to_string_lossy().to_string())); } for str in irep.strvals.iter() { - irep1.pool.push(RPool::Str(str.to_string_lossy().to_string())); + irep1 + .pool + .push(RPool::Str(str.to_string_lossy().to_string())); } let code = interpret_insn(&mut irep.insn); for ch in irep.catch_handlers.iter() { let pos = ch.target; - let (i, _) = code.iter().enumerate().find(|(_, op)| op.pos == pos).expect("catch handler mismatch"); + let (i, _) = code + .iter() + .enumerate() + .find(|(_, op)| op.pos == pos) + .expect("catch handler mismatch"); irep1.catch_target_pos.push(i); } irep1.catch_target_pos.sort(); @@ -445,4 +478,4 @@ impl ENV { pub(crate) fn expired(&self) -> bool { self.is_expired.get() } -} \ No newline at end of file +} diff --git a/mrubyedge/tests/equal.rs b/mrubyedge/tests/equal.rs index 73bb920..3719236 100644 --- a/mrubyedge/tests/equal.rs +++ b/mrubyedge/tests/equal.rs @@ -34,24 +34,49 @@ fn equal_test() { vm.run().unwrap(); // Assert - let args = vec![RObject::integer(1).to_refcount_assigned(), RObject::integer(2).to_refcount_assigned()]; - let result: bool = mrb_funcall(&mut vm, None, "check_eq_1", &args).unwrap().as_ref().try_into().unwrap(); + let args = vec![ + RObject::integer(1).to_refcount_assigned(), + RObject::integer(2).to_refcount_assigned(), + ]; + let result: bool = mrb_funcall(&mut vm, None, "check_eq_1", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert!(result); - let args = vec![RObject::string("foo".into()).to_refcount_assigned(), RObject::string("bar".into()).to_refcount_assigned()]; - let result: bool = mrb_funcall(&mut vm, None, "check_eq_2", &args).unwrap().as_ref().try_into().unwrap(); + let args = vec![ + RObject::string("foo".into()).to_refcount_assigned(), + RObject::string("bar".into()).to_refcount_assigned(), + ]; + let result: bool = mrb_funcall(&mut vm, None, "check_eq_2", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert!(result); - let args = vec![RObject::symbol("foo".into()).to_refcount_assigned(), RObject::symbol("bar".into()).to_refcount_assigned()]; - let result: bool = mrb_funcall(&mut vm, None, "check_eq_3", &args).unwrap().as_ref().try_into().unwrap(); + let args = vec![ + RObject::symbol("foo".into()).to_refcount_assigned(), + RObject::symbol("bar".into()).to_refcount_assigned(), + ]; + let result: bool = mrb_funcall(&mut vm, None, "check_eq_3", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert!(result); let args = vec![ - RObject::symbol("foo".into()).to_refcount_assigned(), - RObject::integer(1).to_refcount_assigned(), - RObject::symbol("bar".into()).to_refcount_assigned(), - RObject::string("str".into()).to_refcount_assigned(), + RObject::symbol("foo".into()).to_refcount_assigned(), + RObject::integer(1).to_refcount_assigned(), + RObject::symbol("bar".into()).to_refcount_assigned(), + RObject::string("str".into()).to_refcount_assigned(), ]; - let result: bool = mrb_funcall(&mut vm, None, "check_eq_4", &args).unwrap().as_ref().try_into().unwrap(); + let result: bool = mrb_funcall(&mut vm, None, "check_eq_4", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert!(result); } diff --git a/mrubyedge/tests/fncall.rs b/mrubyedge/tests/fncall.rs index 22fb7ef..3a81ab7 100644 --- a/mrubyedge/tests/fncall.rs +++ b/mrubyedge/tests/fncall.rs @@ -5,10 +5,10 @@ mod helpers; use std::rc::Rc; use helpers::*; +use mrubyedge::Error; use mrubyedge::yamrb::helpers::mrb_define_cmethod; use mrubyedge::yamrb::value::RObject; use mrubyedge::yamrb::vm::VM; -use mrubyedge::Error; #[test] fn fncall_test() { @@ -23,7 +23,10 @@ end vm.run().unwrap(); // Rust method that calls mrb_funcall internally - fn rust_method_calling_mrb_funcall(vm: &mut VM, args: &[Rc]) -> Result, Error> { + fn rust_method_calling_mrb_funcall( + vm: &mut VM, + args: &[Rc], + ) -> Result, Error> { // Get the first argument (should be an integer) let n = if args.len() > 0 { let arg: i64 = args[0].as_ref().try_into()?; @@ -31,16 +34,21 @@ end } else { 0 }; - + // Call Ruby's double method via mrb_funcall let args_for_call = vec![Rc::new(RObject::integer(n))]; let result = mrb_funcall(vm, None, "double", &args_for_call)?; - + Ok(result) } let kernel = vm.object_class.clone(); - mrb_define_cmethod(&mut vm, kernel, "call_double", Box::new(rust_method_calling_mrb_funcall)); + mrb_define_cmethod( + &mut vm, + kernel, + "call_double", + Box::new(rust_method_calling_mrb_funcall), + ); // Call the Rust method which internally calls mrb_funcall let args = vec![Rc::new(RObject::integer(5))]; @@ -69,31 +77,44 @@ complex_calc(2, 3) fn rust_method_do_multiply(_vm: &mut VM, args: &[Rc]) -> Result, Error> { let a: i64 = args[0].as_ref().try_into()?; let b: i64 = args[1].as_ref().try_into()?; - + let result = a * b; Ok(Rc::new(RObject::integer(result))) - } + } // Rust method that calls multiple Ruby methods fn complex_calculation(vm: &mut VM, args: &[Rc]) -> Result, Error> { let a: i64 = args[0].as_ref().try_into()?; let b: i64 = args[1].as_ref().try_into()?; - + // Call add(a, b) let add_args = vec![Rc::new(RObject::integer(a)), Rc::new(RObject::integer(b))]; let sum = mrb_funcall(vm, None, "add", &add_args)?; - + // Call multiply(sum, 3) let sum_val: i64 = sum.as_ref().try_into()?; - let mul_args = vec![Rc::new(RObject::integer(sum_val)), Rc::new(RObject::integer(3))]; + let mul_args = vec![ + Rc::new(RObject::integer(sum_val)), + Rc::new(RObject::integer(3)), + ]; let result = mrb_funcall(vm, None, "multiply", &mul_args)?; - + Ok(result) } let kernel = vm.object_class.clone(); - mrb_define_cmethod(&mut vm, kernel.clone(), "do_multiply", Box::new(rust_method_do_multiply)); - mrb_define_cmethod(&mut vm, kernel.clone(), "complex_calc", Box::new(complex_calculation)); + mrb_define_cmethod( + &mut vm, + kernel.clone(), + "do_multiply", + Box::new(rust_method_do_multiply), + ); + mrb_define_cmethod( + &mut vm, + kernel.clone(), + "complex_calc", + Box::new(complex_calculation), + ); let result = vm.run().unwrap(); let result: i64 = result.as_ref().try_into().unwrap(); diff --git a/mrubyedge/tests/hash.rs b/mrubyedge/tests/hash.rs index cd87e64..eaa3f11 100644 --- a/mrubyedge/tests/hash.rs +++ b/mrubyedge/tests/hash.rs @@ -22,8 +22,7 @@ fn hash_new_test() { // Assert let args = vec![]; - let result = mrb_funcall(&mut vm, None, "test_hash_new", &args) - .unwrap(); + let result = mrb_funcall(&mut vm, None, "test_hash_new", &args).unwrap(); let result: i32 = result.as_ref().try_into().unwrap(); assert_eq!(result, 0); } @@ -45,13 +44,16 @@ fn hash_test() { // Assert let args = vec![]; let result: i32 = mrb_funcall(&mut vm, None, "test_hash", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 42); } #[test] fn hash_2_test() { - let code = " + let code = " $hash = {} def test_hash_set(key, value) @@ -62,26 +64,30 @@ fn hash_2_test() { $hash[key] end "; - let binary = mrbc_compile("hash_2", code); - let mut rite = mrubyedge::rite::load(&binary).unwrap(); - let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - vm.run().unwrap(); + let binary = mrbc_compile("hash_2", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); - // Assert - let args = vec![ - Rc::new(RObject::symbol("bar".into())), - Rc::new(RObject::integer(54)), - ]; - let result: i32 = mrb_funcall(&mut vm, None, "test_hash_set", &args) - .unwrap().as_ref().try_into().unwrap(); - assert_eq!(result, 54); + // Assert + let args = vec![ + Rc::new(RObject::symbol("bar".into())), + Rc::new(RObject::integer(54)), + ]; + let result: i32 = mrb_funcall(&mut vm, None, "test_hash_set", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 54); - let args = vec![ - Rc::new(RObject::symbol("bar".into())), - ]; - let result: i32 = mrb_funcall(&mut vm, None, "test_hash_get", &args) - .unwrap().as_ref().try_into().unwrap(); - assert_eq!(result, 54); + let args = vec![Rc::new(RObject::symbol("bar".into()))]; + let result: i32 = mrb_funcall(&mut vm, None, "test_hash_get", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 54); } #[test] @@ -108,8 +114,7 @@ fn hash_each_test() { // Assert let args = vec![]; - let value = mrb_funcall(&mut vm, None, "test_hash_1", &args) - .unwrap(); + let value = mrb_funcall(&mut vm, None, "test_hash_1", &args).unwrap(); let value: i64 = value.as_ref().try_into().unwrap(); assert_eq!(value, 6); } @@ -137,10 +142,9 @@ fn hash_each_test_2() { // Assert let args = vec![]; - let value = mrb_funcall(&mut vm, None, "test_hash_1", &args) - .unwrap(); + let value = mrb_funcall(&mut vm, None, "test_hash_1", &args).unwrap(); let value: String = value.as_ref().try_into().unwrap(); assert!(value.contains("foo")); assert!(value.contains("bar")); assert!(value.contains("baz")); -} \ No newline at end of file +} diff --git a/mrubyedge/tests/helpers/mod.rs b/mrubyedge/tests/helpers/mod.rs index c34afa4..a88051c 100644 --- a/mrubyedge/tests/helpers/mod.rs +++ b/mrubyedge/tests/helpers/mod.rs @@ -7,38 +7,38 @@ use mrubyedge::yamrb::value::RObject; pub use mrubyedge::yamrb::helpers::mrb_funcall; macro_rules! mrbc_compile_ { - ($fname:expr, $code:expr) => { - { - use std::{ffi::CStr, fs::File, io::Write}; - - let mut src = std::env::temp_dir(); - src.push(format!("{}.{}.mrb", $fname, std::process::id())); - let mut f = File::create(&src).expect("cannot open srs file"); - f.write($code.as_bytes()).expect("cannot create src file"); - f.flush().unwrap(); - - let mut src0 = src.as_os_str().to_string_lossy().into_owned(); - src0.push('\0'); - - let mut dest = std::env::temp_dir(); - dest.push(format!("{}.{}.mrb", $fname, std::process::id())); - let mut dest0 = dest.as_os_str().to_string_lossy().into_owned(); - dest0.push('\0'); - - let args = [ - CStr::from_bytes_with_nul(b"mrbc\0").unwrap().as_ptr(), - // CStr::from_bytes_with_nul(b"-v\0").unwrap().as_ptr(), - CStr::from_bytes_with_nul(b"-o\0").unwrap().as_ptr(), - CStr::from_bytes_with_nul(dest0.as_bytes()).unwrap().as_ptr(), - CStr::from_bytes_with_nul(src0.as_bytes()).unwrap().as_ptr(), - ]; - unsafe { - mec_mrbc_sys::mrbc_main(args.len() as i32, args.as_ptr() as *mut *mut i8); - } - - dest + ($fname:expr, $code:expr) => {{ + use std::{ffi::CStr, fs::File, io::Write}; + + let mut src = std::env::temp_dir(); + src.push(format!("{}.{}.mrb", $fname, std::process::id())); + let mut f = File::create(&src).expect("cannot open srs file"); + f.write($code.as_bytes()).expect("cannot create src file"); + f.flush().unwrap(); + + let mut src0 = src.as_os_str().to_string_lossy().into_owned(); + src0.push('\0'); + + let mut dest = std::env::temp_dir(); + dest.push(format!("{}.{}.mrb", $fname, std::process::id())); + let mut dest0 = dest.as_os_str().to_string_lossy().into_owned(); + dest0.push('\0'); + + let args = [ + CStr::from_bytes_with_nul(b"mrbc\0").unwrap().as_ptr(), + // CStr::from_bytes_with_nul(b"-v\0").unwrap().as_ptr(), + CStr::from_bytes_with_nul(b"-o\0").unwrap().as_ptr(), + CStr::from_bytes_with_nul(dest0.as_bytes()) + .unwrap() + .as_ptr(), + CStr::from_bytes_with_nul(src0.as_bytes()).unwrap().as_ptr(), + ]; + unsafe { + mec_mrbc_sys::mrbc_main(args.len() as i32, args.as_ptr() as *mut *mut i8); } - }; + + dest + }}; } pub(crate) fn mrbc_compile(fname: &'static str, code: &'static str) -> Vec { @@ -48,38 +48,38 @@ pub(crate) fn mrbc_compile(fname: &'static str, code: &'static str) -> Vec { } macro_rules! mrbc_compile_debug_ { - ($fname:expr, $code:expr) => { - { - use std::{ffi::CStr, fs::File, io::Write}; - - let mut src = std::env::temp_dir(); - src.push(format!("{}.{}.mrb", $fname, std::process::id())); - let mut f = File::create(&src).expect("cannot open srs file"); - f.write($code.as_bytes()).expect("cannot create src file"); - f.flush().unwrap(); - - let mut src0 = src.as_os_str().to_string_lossy().into_owned(); - src0.push('\0'); - - let mut dest = std::env::temp_dir(); - dest.push(format!("{}.{}.mrb", $fname, std::process::id())); - let mut dest0 = dest.as_os_str().to_string_lossy().into_owned(); - dest0.push('\0'); - - let args = [ - CStr::from_bytes_with_nul(b"mrbc\0").unwrap().as_ptr(), - CStr::from_bytes_with_nul(b"-v\0").unwrap().as_ptr(), - CStr::from_bytes_with_nul(b"-o\0").unwrap().as_ptr(), - CStr::from_bytes_with_nul(dest0.as_bytes()).unwrap().as_ptr(), - CStr::from_bytes_with_nul(src0.as_bytes()).unwrap().as_ptr(), - ]; - unsafe { - mec_mrbc_sys::mrbc_main(args.len() as i32, args.as_ptr() as *mut *mut i8); - } - - dest + ($fname:expr, $code:expr) => {{ + use std::{ffi::CStr, fs::File, io::Write}; + + let mut src = std::env::temp_dir(); + src.push(format!("{}.{}.mrb", $fname, std::process::id())); + let mut f = File::create(&src).expect("cannot open srs file"); + f.write($code.as_bytes()).expect("cannot create src file"); + f.flush().unwrap(); + + let mut src0 = src.as_os_str().to_string_lossy().into_owned(); + src0.push('\0'); + + let mut dest = std::env::temp_dir(); + dest.push(format!("{}.{}.mrb", $fname, std::process::id())); + let mut dest0 = dest.as_os_str().to_string_lossy().into_owned(); + dest0.push('\0'); + + let args = [ + CStr::from_bytes_with_nul(b"mrbc\0").unwrap().as_ptr(), + CStr::from_bytes_with_nul(b"-v\0").unwrap().as_ptr(), + CStr::from_bytes_with_nul(b"-o\0").unwrap().as_ptr(), + CStr::from_bytes_with_nul(dest0.as_bytes()) + .unwrap() + .as_ptr(), + CStr::from_bytes_with_nul(src0.as_bytes()).unwrap().as_ptr(), + ]; + unsafe { + mec_mrbc_sys::mrbc_main(args.len() as i32, args.as_ptr() as *mut *mut i8); } - }; + + dest + }}; } pub(crate) fn mrbc_compile_debug(fname: &'static str, code: &'static str) -> Vec { diff --git a/mrubyedge/tests/iter.rs b/mrubyedge/tests/iter.rs index 8497936..e5b8c38 100644 --- a/mrubyedge/tests/iter.rs +++ b/mrubyedge/tests/iter.rs @@ -23,7 +23,10 @@ fn times_test() { // Assert let args = vec![]; let result: i32 = mrb_funcall(&mut vm, None, "test_times", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 6); } @@ -44,8 +47,7 @@ fn times_self_test() { // Assert let args = vec![]; - mrb_funcall(&mut vm, None, "test_times", &args) - .unwrap(); + mrb_funcall(&mut vm, None, "test_times", &args).unwrap(); assert!(true); } @@ -69,8 +71,7 @@ fn times_self_2_test() { // Assert let args = vec![]; - mrb_funcall(&mut vm, None, "test_times", &args) - .unwrap(); + mrb_funcall(&mut vm, None, "test_times", &args).unwrap(); assert!(true); } @@ -93,7 +94,10 @@ fn range_each_test() { // Assert let args = vec![]; let result: i32 = mrb_funcall(&mut vm, None, "test_each", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 110); } @@ -116,6 +120,9 @@ fn array_each_test() { // Assert let args = vec![]; let result: i32 = mrb_funcall(&mut vm, None, "test_each", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 22222); } diff --git a/mrubyedge/tests/iter_closure.rs b/mrubyedge/tests/iter_closure.rs index 929a066..f4ede3c 100644 --- a/mrubyedge/tests/iter_closure.rs +++ b/mrubyedge/tests/iter_closure.rs @@ -23,7 +23,10 @@ fn times_test_c() { // Assert let args = vec![]; let result: i32 = mrb_funcall(&mut vm, None, "test_times", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 6); } @@ -46,7 +49,10 @@ fn range_each_test_c() { // Assert let args = vec![]; let result: i32 = mrb_funcall(&mut vm, None, "test_each", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 110); } @@ -69,7 +75,10 @@ fn array_each_test_c() { // Assert let args = vec![]; let result: i32 = mrb_funcall(&mut vm, None, "test_each", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 22222); } @@ -95,7 +104,10 @@ fn array_each_nested_test_c() { // Assert let args = vec![]; let result: i32 = mrb_funcall(&mut vm, None, "do_times_nest", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 2000); } @@ -122,6 +134,9 @@ fn expired_closure_test() { // Assert let args = vec![]; let result: i32 = mrb_funcall(&mut vm, None, "do_times", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 3); } diff --git a/mrubyedge/tests/klass.rs b/mrubyedge/tests/klass.rs index 3eaf9cd..44de0aa 100644 --- a/mrubyedge/tests/klass.rs +++ b/mrubyedge/tests/klass.rs @@ -29,7 +29,10 @@ fn attr_reader_test() { // Assert let args = vec![]; let result: i32 = mrb_funcall(&mut vm, None, "test_main", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 123); } @@ -52,8 +55,7 @@ fn attr_reader_2_test() { // Assert let args = vec![]; - let result = mrb_funcall(&mut vm, None, "test_main", &args) - .unwrap(); + let result = mrb_funcall(&mut vm, None, "test_main", &args).unwrap(); assert!(result.as_ref().is_nil()); } @@ -78,7 +80,10 @@ fn attr_accessor_test() { // Assert let args = vec![]; let result: String = mrb_funcall(&mut vm, None, "test_main", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(&result, "Hola, attr"); } @@ -113,9 +118,15 @@ fn class_definition_isolation_test() { // Assert let args = vec![]; let val1: i32 = mrb_funcall(&mut vm, None, "test_main1", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); let val2: i32 = mrb_funcall(&mut vm, None, "test_main2", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(val1, 123); assert_eq!(val2, 456); } @@ -147,6 +158,9 @@ fn class_inheritance_super_test() { // Assert let args = vec![]; let result: i32 = mrb_funcall(&mut vm, None, "test_main", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 124); } diff --git a/mrubyedge/tests/module.rs b/mrubyedge/tests/module.rs index 2a077fc..a8057d5 100644 --- a/mrubyedge/tests/module.rs +++ b/mrubyedge/tests/module.rs @@ -20,7 +20,7 @@ TestModule let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); let result = vm.run().unwrap(); - + // Result should be the module itself assert!(matches!(result.tt, mrubyedge::yamrb::value::RType::Module)); } @@ -46,7 +46,10 @@ User.new.greet let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); let result = vm.run().unwrap(); - let value: String = result.as_ref().try_into().expect("greet should return string"); + let value: String = result + .as_ref() + .try_into() + .expect("greet should return string"); assert_eq!(value, "hello"); } @@ -73,7 +76,10 @@ Outer::User.new.greet let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); let result = vm.run().unwrap(); - let value: String = result.as_ref().try_into().expect("greet should return string"); + let value: String = result + .as_ref() + .try_into() + .expect("greet should return string"); assert_eq!(value, "hello"); } @@ -102,7 +108,10 @@ Wrapper.new.core_value let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); let result = vm.run().unwrap(); - let value: i64 = result.as_ref().try_into().expect("core_value should return integer"); + let value: i64 = result + .as_ref() + .try_into() + .expect("core_value should return integer"); assert_eq!(value, 123); } @@ -135,7 +144,9 @@ Wrapper.new.core_value let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); let result = vm.run().unwrap(); - let value: i64 = result.as_ref().try_into().expect("core_value should return integer"); + let value: i64 = result + .as_ref() + .try_into() + .expect("core_value should return integer"); assert_eq!(value, 124); } - diff --git a/mrubyedge/tests/object.rs b/mrubyedge/tests/object.rs index 5cb6269..3deb9fa 100644 --- a/mrubyedge/tests/object.rs +++ b/mrubyedge/tests/object.rs @@ -26,6 +26,9 @@ fn object_test() { // Assert let args = vec![]; let result: i32 = mrb_funcall(&mut vm, None, "test_main", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 1); -} \ No newline at end of file +} diff --git a/mrubyedge/tests/object_id.rs b/mrubyedge/tests/object_id.rs index d36afd5..7161b7b 100644 --- a/mrubyedge/tests/object_id.rs +++ b/mrubyedge/tests/object_id.rs @@ -20,7 +20,11 @@ fn object_id_test() { // Assert let args = vec![]; - let result: bool = mrb_funcall(&mut vm, None, "check_id", &args).unwrap().as_ref().try_into().unwrap(); + let result: bool = mrb_funcall(&mut vm, None, "check_id", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert!(result); } @@ -42,6 +46,10 @@ fn object_id_2_test() { // Assert let args = vec![]; - let result: bool = mrb_funcall(&mut vm, None, "check_id", &args).unwrap().as_ref().try_into().unwrap(); + let result: bool = mrb_funcall(&mut vm, None, "check_id", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert!(!result); } diff --git a/mrubyedge/tests/raise.rs b/mrubyedge/tests/raise.rs index f99ad6f..d3c9997 100644 --- a/mrubyedge/tests/raise.rs +++ b/mrubyedge/tests/raise.rs @@ -18,8 +18,7 @@ fn raise_test() { // Assert let args = vec![]; - let result = mrb_funcall(&mut vm, None, "test_raise", &args) - .err(); + let result = mrb_funcall(&mut vm, None, "test_raise", &args).err(); assert_eq!(&result.unwrap().message(), "Intentional Error"); } @@ -43,8 +42,7 @@ fn raise_nest_test() { // Assert let args = vec![]; - let result = mrb_funcall(&mut vm, None, "test_raise", &args) - .err(); + let result = mrb_funcall(&mut vm, None, "test_raise", &args).err(); assert_eq!(&result.unwrap().message(), "Intentional Error 2"); } @@ -67,12 +65,16 @@ fn raise_nest_test_toplevel() { let binary = mrbc_compile("raise_nest_toplevel", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - + // Assert let result = vm.run().err(); assert_eq!( - &result.unwrap().downcast_ref::().unwrap().message(), - "Intentional Error 0", + &result + .unwrap() + .downcast_ref::() + .unwrap() + .message(), + "Intentional Error 0", ); } @@ -101,8 +103,7 @@ fn raise_nest_nest_test() { // Assert let args = vec![]; - let result = mrb_funcall(&mut vm, None, "test_raise", &args) - .err(); + let result = mrb_funcall(&mut vm, None, "test_raise", &args).err(); assert_eq!(&result.unwrap().message(), "Intentional Error 2b"); } @@ -126,7 +127,10 @@ fn rescue_test() { // Assert let args = vec![]; let result: String = mrb_funcall(&mut vm, None, "test_raise", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(&result, "rescue: Intentional Error 3"); } @@ -155,7 +159,10 @@ fn rescue_nest_test() { // Assert let args = vec![]; let result: String = mrb_funcall(&mut vm, None, "test_raise_parent", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(&result, "rescue: Intentional Error 4"); } @@ -189,6 +196,9 @@ fn rescue_nest_nest_test() { // Assert let args = vec![]; let result: String = mrb_funcall(&mut vm, None, "test_raise_parent", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(&result, "rescue: Intentional Error 4b"); -} \ No newline at end of file +} diff --git a/mrubyedge/tests/raise_rust.rs b/mrubyedge/tests/raise_rust.rs index 6e4ed9f..79dd17d 100644 --- a/mrubyedge/tests/raise_rust.rs +++ b/mrubyedge/tests/raise_rust.rs @@ -3,19 +3,19 @@ extern crate mrubyedge; mod helpers; use helpers::*; -use std::rc::Rc; +use mrubyedge::Error; use mrubyedge::yamrb::helpers::mrb_define_cmethod; -use mrubyedge::yamrb::vm::*; use mrubyedge::yamrb::value::*; -use mrubyedge::Error; +use mrubyedge::yamrb::vm::*; +use std::rc::Rc; fn prelude_dummy_error_func(vm: &mut VM) { - let klass = vm.object_class.clone(); - mrb_define_cmethod(vm, klass, "dummy_raise", Box::new(mrb_test_dummy_raise)); + let klass = vm.object_class.clone(); + mrb_define_cmethod(vm, klass, "dummy_raise", Box::new(mrb_test_dummy_raise)); } fn mrb_test_dummy_raise(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { - Err(Error::RuntimeError("Intentional Rust Error".to_string())) + Err(Error::RuntimeError("Intentional Rust Error".to_string())) } #[test] @@ -33,8 +33,7 @@ fn rust_raise_test() { // Assert let args = vec![]; - let result = mrb_funcall(&mut vm, None, "test_raise", &args) - .err(); + let result = mrb_funcall(&mut vm, None, "test_raise", &args).err(); assert_eq!(&result.unwrap().message(), "Intentional Rust Error"); } @@ -57,8 +56,7 @@ fn rust_raise_nest_test() { // Assert let args = vec![]; - let result = mrb_funcall(&mut vm, None, "shim", &args) - .err(); + let result = mrb_funcall(&mut vm, None, "shim", &args).err(); assert_eq!(&result.unwrap().message(), "Intentional Rust Error"); } @@ -80,7 +78,10 @@ fn rust_raise_rescue_test() { // Assert let args = vec![]; let result: String = mrb_funcall(&mut vm, None, "test_raise", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(&result, "rescued: Intentional Rust Error"); } @@ -101,7 +102,10 @@ fn rust_nomethod_rescue_test() { // Assert let args = vec![]; let result: String = mrb_funcall(&mut vm, None, "test_raise", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(&result, "rescued: Method not found: dummy_nomethod"); } @@ -122,6 +126,9 @@ fn rust_noname_rescue_test() { // Assert let args = vec![]; let result: String = mrb_funcall(&mut vm, None, "test_raise", &args) - .unwrap().as_ref().try_into().unwrap(); + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(&result, "rescued: Cannot found name: NoName"); -} \ No newline at end of file +} diff --git a/mrubyedge/tests/return.rs b/mrubyedge/tests/return.rs index 5e92b11..51e691b 100644 --- a/mrubyedge/tests/return.rs +++ b/mrubyedge/tests/return.rs @@ -22,13 +22,13 @@ end // Assert let args = vec![Rc::new(RObject::integer(10))]; - let result = mrb_funcall(&mut vm, None, "fib", &args).unwrap(); + let result = mrb_funcall(&mut vm, None, "fib", &args).unwrap(); let result: i64 = result.as_ref().try_into().unwrap(); assert_eq!(result, 89); // Assert 2 let args = vec![Rc::new(RObject::integer(1))]; - let result = mrb_funcall(&mut vm, None, "fib", &args).unwrap(); + let result = mrb_funcall(&mut vm, None, "fib", &args).unwrap(); let result: i64 = result.as_ref().try_into().unwrap(); assert_eq!(result, 1); } diff --git a/mrubyedge/tests/shared_memory.rs b/mrubyedge/tests/shared_memory.rs index 7bac916..10cd94d 100644 --- a/mrubyedge/tests/shared_memory.rs +++ b/mrubyedge/tests/shared_memory.rs @@ -23,10 +23,10 @@ end"; // Assert let args = vec![]; - let result1 = mrb_funcall(&mut vm, None, "get_memory", &args).unwrap(); + let result1 = mrb_funcall(&mut vm, None, "get_memory", &args).unwrap(); assert!(result1.as_ref().get_class(&mut vm).as_ref().sym_id.name == "SharedMemory"); - let result2 = mrb_funcall(&mut vm, None, "read_array_from_memory", &args).unwrap(); + let result2 = mrb_funcall(&mut vm, None, "read_array_from_memory", &args).unwrap(); let result2: i64 = result2.as_ref().try_into().unwrap(); assert_eq!(result2, 0); } @@ -47,7 +47,7 @@ end"; // Assert let args = vec![]; - let result = mrb_funcall(&mut vm, None, "read_array_from_memory", &args).unwrap(); + let result = mrb_funcall(&mut vm, None, "read_array_from_memory", &args).unwrap(); let result: i64 = result.as_ref().try_into().unwrap(); assert_eq!(result, 123); } @@ -73,7 +73,7 @@ end"; let args = vec![]; let _ = mrb_funcall(&mut vm, None, "update_memory", &args).unwrap(); - let result = mrb_funcall(&mut vm, None, "read_array_from_memory", &args).unwrap(); + let result = mrb_funcall(&mut vm, None, "read_array_from_memory", &args).unwrap(); let result: i64 = result.as_ref().try_into().unwrap(); assert_eq!(result, 0x01 + 0x10 + 0x20 + 0x30); } diff --git a/mrubyedge/tests/smoke.rs b/mrubyedge/tests/smoke.rs index 518505b..f659874 100644 --- a/mrubyedge/tests/smoke.rs +++ b/mrubyedge/tests/smoke.rs @@ -15,11 +15,12 @@ fn smoke_test() { vm.run().unwrap(); // Assert - let args = vec![ - int(1), - int(2), - ]; - let result: i32 = mrb_funcall(&mut vm, None, "add", &args).unwrap().as_ref().try_into().unwrap(); + let args = vec![int(1), int(2)]; + let result: i32 = mrb_funcall(&mut vm, None, "add", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 3); } @@ -37,7 +38,11 @@ fn p_test() { // Assert let args = vec![]; - let result: () = mrb_funcall(&mut vm, None, "hello", &args).unwrap().as_ref().try_into().unwrap(); + let result: () = mrb_funcall(&mut vm, None, "hello", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, ()); } @@ -60,34 +65,44 @@ fn fib_test() { vm.run().unwrap(); // Assert - let args = vec![ - int(1), - ]; - let result: i32 = mrb_funcall(&mut vm, None, "fib", &args).unwrap().as_ref().try_into().unwrap(); + let args = vec![int(1)]; + let result: i32 = mrb_funcall(&mut vm, None, "fib", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 1); - let args = vec![ - int(2), - ]; - let result: i32 = mrb_funcall(&mut vm, None, "fib", &args).unwrap().as_ref().try_into().unwrap(); + let args = vec![int(2)]; + let result: i32 = mrb_funcall(&mut vm, None, "fib", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 1); - let args = vec![ - int(3), - ]; - let result: i32 = mrb_funcall(&mut vm, None, "fib", &args).unwrap().as_ref().try_into().unwrap(); + let args = vec![int(3)]; + let result: i32 = mrb_funcall(&mut vm, None, "fib", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 2); - let args = vec![ - int(10), - ]; - let result: i32 = mrb_funcall(&mut vm, None, "fib", &args).unwrap().as_ref().try_into().unwrap(); + let args = vec![int(10)]; + let result: i32 = mrb_funcall(&mut vm, None, "fib", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 55); - let args = vec![ - int(15), - ]; - let result: i32 = mrb_funcall(&mut vm, None, "fib", &args).unwrap().as_ref().try_into().unwrap(); + let args = vec![int(15)]; + let result: i32 = mrb_funcall(&mut vm, None, "fib", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 610); } @@ -111,33 +126,43 @@ fn fib2_test() { vm.run().unwrap(); // Assert - let args = vec![ - int(1), - ]; - let result: i32 = mrb_funcall(&mut vm, None, "fib", &args).unwrap().as_ref().try_into().unwrap(); + let args = vec![int(1)]; + let result: i32 = mrb_funcall(&mut vm, None, "fib", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 1); - let args = vec![ - int(2), - ]; - let result: i32 = mrb_funcall(&mut vm, None, "fib", &args).unwrap().as_ref().try_into().unwrap(); + let args = vec![int(2)]; + let result: i32 = mrb_funcall(&mut vm, None, "fib", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 1); - let args = vec![ - int(3), - ]; - let result: i32 = mrb_funcall(&mut vm, None, "fib", &args).unwrap().as_ref().try_into().unwrap(); + let args = vec![int(3)]; + let result: i32 = mrb_funcall(&mut vm, None, "fib", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 2); - let args = vec![ - int(10), - ]; - let result: i32 = mrb_funcall(&mut vm, None, "fib", &args).unwrap().as_ref().try_into().unwrap(); + let args = vec![int(10)]; + let result: i32 = mrb_funcall(&mut vm, None, "fib", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 55); - let args = vec![ - int(15), - ]; - let result: i32 = mrb_funcall(&mut vm, None, "fib", &args).unwrap().as_ref().try_into().unwrap(); + let args = vec![int(15)]; + let result: i32 = mrb_funcall(&mut vm, None, "fib", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, 610); -} \ No newline at end of file +} diff --git a/mrubyedge/tests/string.rs b/mrubyedge/tests/string.rs index c0fe83a..609a3ad 100644 --- a/mrubyedge/tests/string.rs +++ b/mrubyedge/tests/string.rs @@ -22,8 +22,7 @@ fn string_new_test() { // Assert let args = vec![]; - let result = mrb_funcall(&mut vm, None, "test_string_new", &args) - .unwrap(); + let result = mrb_funcall(&mut vm, None, "test_string_new", &args).unwrap(); let result: i32 = result.as_ref().try_into().unwrap(); assert_eq!(result, 0); -} \ No newline at end of file +} diff --git a/mrubyedge/tests/unpack.rs b/mrubyedge/tests/unpack.rs index c68f662..400c771 100644 --- a/mrubyedge/tests/unpack.rs +++ b/mrubyedge/tests/unpack.rs @@ -19,7 +19,7 @@ end"; // Assert let args = vec![]; - let result = mrb_funcall(&mut vm, None, "sum_unpack", &args).unwrap(); + let result = mrb_funcall(&mut vm, None, "sum_unpack", &args).unwrap(); let result: i64 = result.as_ref().try_into().unwrap(); assert_eq!(result, 10); } From e83ff1383dae0391baf608e9c3f61a4d467c5050 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 22:03:40 +0900 Subject: [PATCH 038/314] cargo clippy --fix --- mrubyedge/src/error.rs | 2 +- mrubyedge/src/eval.rs | 2 +- mrubyedge/src/rite/insn.rs | 2 +- mrubyedge/src/rite/rite.rs | 2 +- mrubyedge/src/yamrb/helpers.rs | 7 +- mrubyedge/src/yamrb/optable.rs | 192 ++++++++++++-------------- mrubyedge/src/yamrb/prelude/class.rs | 2 +- mrubyedge/src/yamrb/prelude/hash.rs | 2 +- mrubyedge/src/yamrb/prelude/range.rs | 8 +- mrubyedge/src/yamrb/prelude/string.rs | 2 +- mrubyedge/src/yamrb/value.rs | 34 ++--- mrubyedge/src/yamrb/vm.rs | 21 ++- 12 files changed, 132 insertions(+), 144 deletions(-) diff --git a/mrubyedge/src/error.rs b/mrubyedge/src/error.rs index 0d1d04f..26a5d08 100644 --- a/mrubyedge/src/error.rs +++ b/mrubyedge/src/error.rs @@ -55,7 +55,7 @@ impl Error { } pub fn is_a(&self, vm: &mut VM, other: Rc) -> bool { - let mut klass: Option> = RClass::from_error(vm, &self).into(); + let mut klass: Option> = RClass::from_error(vm, self).into(); let target_klass_name = other.sym_id.name.as_str(); while let Some(k) = klass { diff --git a/mrubyedge/src/eval.rs b/mrubyedge/src/eval.rs index faca37b..db1fe07 100644 --- a/mrubyedge/src/eval.rs +++ b/mrubyedge/src/eval.rs @@ -4,7 +4,7 @@ use crate::rite::insn::{self, OpCode}; #[cfg(not(target_arch = "wasm32"))] pub fn debug_eval_insn(mut insns: &[u8]) -> Result<(), crate::Error> { let ps: usize = 0; - while insns.len() > 0 { + while !insns.is_empty() { let op = insns[ps]; let opcode: OpCode = op.try_into()?; let fetched = insn::FETCH_TABLE[op as usize](&mut insns)?; diff --git a/mrubyedge/src/rite/insn.rs b/mrubyedge/src/rite/insn.rs index a6b0d2c..414015c 100644 --- a/mrubyedge/src/rite/insn.rs +++ b/mrubyedge/src/rite/insn.rs @@ -447,7 +447,7 @@ impl Debug for OpCode { } fn fetch_z(bin: &mut &[u8]) -> Result { - if bin.len() < 1 { + if bin.is_empty() { return Err(Error::internal("byte code too short")); } *bin = &bin[1..]; diff --git a/mrubyedge/src/rite/rite.rs b/mrubyedge/src/rite/rite.rs index 50f0e9c..8af66d9 100644 --- a/mrubyedge/src/rite/rite.rs +++ b/mrubyedge/src/rite/rite.rs @@ -246,7 +246,7 @@ pub fn section_skip(head: &[u8]) -> Result { Ok(be32_to_u32(header.size) as usize) } -pub fn peek4<'a>(src: &'a [u8]) -> Option<[char; 4]> { +pub fn peek4(src: &[u8]) -> Option<[char; 4]> { if src.len() < 4 { // EoD return None; diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index 21f33a4..3916446 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -65,11 +65,10 @@ fn call_block( vm.current_regs_offset = ci.current_regs_offset; vm.target_class = ci.target_class.clone(); } - if let Some(upper) = vm.upper.take() { - if let Some(upper) = &upper.as_ref().upper { + if let Some(upper) = vm.upper.take() + && let Some(upper) = &upper.as_ref().upper { vm.upper.replace(upper.clone()); } - } match &res { Ok(res) => Ok(res.clone()), @@ -162,7 +161,7 @@ pub fn mrb_funcall( vm.current_regs()[0].replace(recv.clone()); let func = vm.fn_table[method.func.unwrap()].clone(); - let res = func(vm, &args); + let res = func(vm, args); vm.current_regs_offset -= 2; res diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index daecc79..3c8db2c 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -149,73 +149,73 @@ pub(crate) fn consume_expr( use crate::rite::insn::OpCode::*; match code { NOP => { - op_nop(vm, &operand)?; + op_nop(vm, operand)?; } MOVE => { - op_move(vm, &operand)?; + op_move(vm, operand)?; } LOADL => { - op_loadl(vm, &operand)?; + op_loadl(vm, operand)?; } LOADI => { - op_loadi(vm, &operand)?; + op_loadi(vm, operand)?; } LOADINEG => { - op_loadineg(vm, &operand)?; + op_loadineg(vm, operand)?; } LOADI__1 => { - op_loadi_n(vm, -1, &operand)?; + op_loadi_n(vm, -1, operand)?; } LOADI_0 => { - op_loadi_n(vm, 0, &operand)?; + op_loadi_n(vm, 0, operand)?; } LOADI_1 => { - op_loadi_n(vm, 1, &operand)?; + op_loadi_n(vm, 1, operand)?; } LOADI_2 => { - op_loadi_n(vm, 2, &operand)?; + op_loadi_n(vm, 2, operand)?; } LOADI_3 => { - op_loadi_n(vm, 3, &operand)?; + op_loadi_n(vm, 3, operand)?; } LOADI_4 => { - op_loadi_n(vm, 4, &operand)?; + op_loadi_n(vm, 4, operand)?; } LOADI_5 => { - op_loadi_n(vm, 5, &operand)?; + op_loadi_n(vm, 5, operand)?; } LOADI_6 => { - op_loadi_n(vm, 6, &operand)?; + op_loadi_n(vm, 6, operand)?; } LOADI_7 => { - op_loadi_n(vm, 7, &operand)?; + op_loadi_n(vm, 7, operand)?; } LOADI16 => { - op_loadi16(vm, &operand)?; + op_loadi16(vm, operand)?; } LOADI32 => { - op_loadi32(vm, &operand)?; + op_loadi32(vm, operand)?; } LOADSYM => { - op_loadsym(vm, &operand)?; + op_loadsym(vm, operand)?; } LOADNIL => { - op_loadnil(vm, &operand)?; + op_loadnil(vm, operand)?; } LOADSELF => { - op_loadself(vm, &operand)?; + op_loadself(vm, operand)?; } LOADT => { - op_loadt(vm, &operand)?; + op_loadt(vm, operand)?; } LOADF => { - op_loadf(vm, &operand)?; + op_loadf(vm, operand)?; } GETGV => { - op_getgv(vm, &operand)?; + op_getgv(vm, operand)?; } SETGV => { - op_setgv(vm, &operand)?; + op_setgv(vm, operand)?; } // GETSV => { // // op_getsv(vm, &operand)?; @@ -224,10 +224,10 @@ pub(crate) fn consume_expr( // // op_setsv(vm, &operand)?; // } GETIV => { - op_getiv(vm, &operand)?; + op_getiv(vm, operand)?; } SETIV => { - op_setiv(vm, &operand)?; + op_setiv(vm, operand)?; } // GETCV => { // // op_getcv(vm, &operand)?; @@ -236,76 +236,76 @@ pub(crate) fn consume_expr( // // op_setcv(vm, &operand)?; // } GETCONST => { - op_getconst(vm, &operand)?; + op_getconst(vm, operand)?; } SETCONST => { - op_setconst(vm, &operand)?; + op_setconst(vm, operand)?; } GETMCNST => { - op_getmcnst(vm, &operand)?; + op_getmcnst(vm, operand)?; } // SETMCNST => { // // op_setmcnst(vm, &operand)?; // } GETUPVAR => { - op_getupvar(vm, &operand)?; + op_getupvar(vm, operand)?; } SETUPVAR => { - op_setupvar(vm, &operand)?; + op_setupvar(vm, operand)?; } GETIDX => { - op_getidx(vm, &operand)?; + op_getidx(vm, operand)?; } SETIDX => { - op_setidx(vm, &operand)?; + op_setidx(vm, operand)?; } JMP => { - op_jmp(vm, &operand, pos + len)?; + op_jmp(vm, operand, pos + len)?; } JMPIF => { - op_jmpif(vm, &operand, pos + len)?; + op_jmpif(vm, operand, pos + len)?; } JMPNOT => { - op_jmpnot(vm, &operand, pos + len)?; + op_jmpnot(vm, operand, pos + len)?; } JMPNIL => { - op_jmpnil(vm, &operand, pos + len)?; + op_jmpnil(vm, operand, pos + len)?; } // JMPUW => { // // op_jmpuw(vm, &operand)?; // } EXCEPT => { - op_except(vm, &operand)?; + op_except(vm, operand)?; } RESCUE => { - op_rescue(vm, &operand)?; + op_rescue(vm, operand)?; } RAISEIF => { - op_raiseif(vm, &operand)?; + op_raiseif(vm, operand)?; } SSEND => { - op_ssend(vm, &operand)?; + op_ssend(vm, operand)?; } SSENDB => { - op_ssendb(vm, &operand)?; + op_ssendb(vm, operand)?; } SEND => { - op_send(vm, &operand)?; + op_send(vm, operand)?; } SENDB => { - op_sendb(vm, &operand)?; + op_sendb(vm, operand)?; } CALL => { - op_call(vm, &operand)?; + op_call(vm, operand)?; } SUPER => { - op_super(vm, &operand)?; + op_super(vm, operand)?; } // ARGARY => { // // op_argary(vm, &operand)?; // } ENTER => { - op_enter(vm, &operand)?; + op_enter(vm, operand)?; } // KEY_P => { // // op_key_p(vm, &operand)?; @@ -317,7 +317,7 @@ pub(crate) fn consume_expr( // // op_karg(vm, &operand)?; // } RETURN => { - op_return(vm, &operand)?; + op_return(vm, operand)?; } // RETURN_BLK => { // // op_return_blk(vm, &operand)?; @@ -329,43 +329,43 @@ pub(crate) fn consume_expr( // // op_blkpush(vm, &operand)?; // } ADD => { - op_add(vm, &operand)?; + op_add(vm, operand)?; } ADDI => { - op_addi(vm, &operand)?; + op_addi(vm, operand)?; } SUB => { - op_sub(vm, &operand)?; + op_sub(vm, operand)?; } SUBI => { - op_subi(vm, &operand)?; + op_subi(vm, operand)?; } MUL => { - op_mul(vm, &operand)?; + op_mul(vm, operand)?; } DIV => { - op_div(vm, &operand)?; + op_div(vm, operand)?; } EQ => { - op_eq(vm, &operand)?; + op_eq(vm, operand)?; } LT => { - op_lt(vm, &operand)?; + op_lt(vm, operand)?; } LE => { - op_le(vm, &operand)?; + op_le(vm, operand)?; } GT => { - op_gt(vm, &operand)?; + op_gt(vm, operand)?; } GE => { - op_ge(vm, &operand)?; + op_ge(vm, operand)?; } ARRAY => { - op_array(vm, &operand)?; + op_array(vm, operand)?; } ARRAY2 => { - op_array2(vm, &operand)?; + op_array2(vm, operand)?; } // ARYCAT => { // // op_arycat(vm, &operand)?; @@ -389,16 +389,16 @@ pub(crate) fn consume_expr( // // op_intern(vm, &operand)?; // } SYMBOL => { - op_symbol(vm, &operand)?; + op_symbol(vm, operand)?; } STRING => { - op_string(vm, &operand)?; + op_string(vm, operand)?; } STRCAT => { - op_strcat(vm, &operand)?; + op_strcat(vm, operand)?; } HASH => { - op_hash(vm, &operand)?; + op_hash(vm, operand)?; } // HASHADD => { // // op_hashadd(vm, &operand)?; @@ -407,34 +407,34 @@ pub(crate) fn consume_expr( // // op_hashcat(vm, &operand)?; // } LAMBDA => { - op_lambda(vm, &operand)?; + op_lambda(vm, operand)?; } BLOCK => { - op_block(vm, &operand)?; + op_block(vm, operand)?; } METHOD => { - op_method(vm, &operand)?; + op_method(vm, operand)?; } RANGE_INC => { - op_range_inc(vm, &operand)?; + op_range_inc(vm, operand)?; } RANGE_EXC => { - op_range_exc(vm, &operand)?; + op_range_exc(vm, operand)?; } OCLASS => { - op_oclass(vm, &operand)?; + op_oclass(vm, operand)?; } CLASS => { - op_class(vm, &operand)?; + op_class(vm, operand)?; } MODULE => { - op_module(vm, &operand)?; + op_module(vm, operand)?; } EXEC => { - op_exec(vm, &operand)?; + op_exec(vm, operand)?; } DEF => { - op_def(vm, &operand)?; + op_def(vm, operand)?; } // ALIAS => { // // op_alias(vm, &operand)?; @@ -443,10 +443,10 @@ pub(crate) fn consume_expr( // // op_undef(vm, &operand)?; // } SCLASS => { - op_sclass(vm, &operand)?; + op_sclass(vm, operand)?; } TCLASS => { - op_tclass(vm, &operand)?; + op_tclass(vm, operand)?; } // DEBUG => { // // op_debug(vm, &operand)?; @@ -464,7 +464,7 @@ pub(crate) fn consume_expr( // // op_ext3(vm, &operand)?; // } STOP => { - op_stop(vm, &operand)?; + op_stop(vm, operand)?; } _ => { unimplemented!("{:?}: Not supported yet", code) @@ -848,14 +848,8 @@ pub(crate) fn op_rescue(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_raiseif(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()?; let val = vm.current_regs()[a as usize].as_ref().cloned(); - match val { - Some(val) => match &val.value { - RValue::Exception(e) => { - return Err(e.as_ref().error_type.borrow().clone()); - } - _ => {} - }, - None => {} + if let Some(val) = val && let RValue::Exception(e) = &val.value { + return Err(e.as_ref().error_type.borrow().clone()); } Ok(()) } @@ -1056,7 +1050,7 @@ impl From for EnterArgInfo { m2: (val & ENTER_M2_MASK) >> 7, k: (val & ENTER_K_MASK) >> 2, d: (val & ENTER_D_MASK) >> 1, - b: (val & ENTER_B_MASK) >> 0, + b: (val & ENTER_B_MASK), } } } @@ -1081,14 +1075,13 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let old_irep = vm.current_irep.clone(); let nregs = old_irep.nregs; - let regs0_cloned: Vec<_> = vm.current_regs()[0..nregs].iter().cloned().collect(); - if let Some(_) = vm.has_env_ref.get(&vm.current_irep.__id) { - if let Some(environ) = vm.cur_env.get(&vm.current_irep.__id) { + let regs0_cloned: Vec<_> = vm.current_regs()[0..nregs].to_vec(); + if vm.has_env_ref.get(&vm.current_irep.__id).is_some() + && let Some(environ) = vm.cur_env.get(&vm.current_irep.__id) { environ.capture_no_clone(regs0_cloned); environ.as_ref().expire(); vm.has_env_ref.remove(&vm.current_irep.__id); } - } let regs0 = vm.current_regs(); if let Some(regs_a) = regs0[a].take() { @@ -1309,9 +1302,9 @@ fn do_op_array(vm: &mut VM, this: usize, start: usize, n: usize) -> Result<(), E let mut ary = Vec::with_capacity(n); for i in 0..n { if this == start && i == 0 { - ary.push(vm.take_current_regs(start as usize)?); + ary.push(vm.take_current_regs(start)?); } else { - ary.push(vm.get_current_regs_cloned((start + i) as usize)?); + ary.push(vm.get_current_regs_cloned(start + i )?); } } let val = RObject::array(ary); @@ -1374,7 +1367,7 @@ pub(crate) fn op_hash(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { hash.insert(key.as_hash_key()?, (key, val)); } let val = RObject::hash(hash); - vm.current_regs()[a as usize].replace(Rc::new(val)); + vm.current_regs()[a ].replace(Rc::new(val)); Ok(()) } @@ -1482,7 +1475,7 @@ fn do_op_range(vm: &mut VM, a: usize, b: usize, exclusive: bool) -> Result<(), E object_id: u64::MAX.into(), singleton_class: RefCell::new(None), }; - vm.current_regs()[a as usize].replace(val.to_refcount_assigned()); + vm.current_regs()[a ].replace(val.to_refcount_assigned()); Ok(()) } @@ -1619,13 +1612,10 @@ pub(crate) fn op_sclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; let val = vm.getself()?; let singleton_class = val.singleton_class.borrow().clone(); - match singleton_class { - Some(ref sc) => { - let robj = RObject::class(sc.clone(), vm); - vm.current_regs()[a].replace(robj); - return Ok(()); - } - None => {} + if let Some(ref sc) = singleton_class { + let robj = RObject::class(sc.clone(), vm); + vm.current_regs()[a].replace(robj); + return Ok(()); } Ok(()) } diff --git a/mrubyedge/src/yamrb/prelude/class.rs b/mrubyedge/src/yamrb/prelude/class.rs index 62db9eb..d3d8b47 100644 --- a/mrubyedge/src/yamrb/prelude/class.rs +++ b/mrubyedge/src/yamrb/prelude/class.rs @@ -89,7 +89,7 @@ fn mrb_class_attr_reader(vm: &mut VM, args: &[Rc]) -> Result { // skip diff --git a/mrubyedge/src/yamrb/prelude/hash.rs b/mrubyedge/src/yamrb/prelude/hash.rs index b909e09..947aa76 100644 --- a/mrubyedge/src/yamrb/prelude/hash.rs +++ b/mrubyedge/src/yamrb/prelude/hash.rs @@ -51,7 +51,7 @@ pub fn mrb_hash_get_index(this: Rc, key: Rc) -> Result Ok(value.clone()), None => Ok(Rc::new(RObject::nil())), } diff --git a/mrubyedge/src/yamrb/prelude/range.rs b/mrubyedge/src/yamrb/prelude/range.rs index 6e98729..a8796d4 100644 --- a/mrubyedge/src/yamrb/prelude/range.rs +++ b/mrubyedge/src/yamrb/prelude/range.rs @@ -43,14 +43,14 @@ pub fn mrb_range_is_include(vm: &mut VM, args: &[Rc]) -> Result { - return Ok(Rc::new(RObject::boolean(false))); + Ok(Rc::new(RObject::boolean(false))) } } } _ => { - return Err(Error::RuntimeError( + Err(Error::RuntimeError( "Range#include? must be called on a Range".to_string(), - )); + )) } } } @@ -64,7 +64,7 @@ pub fn mrb_range_each(vm: &mut VM, args: &[Rc]) -> Result, let start = *start; let mut end = *end; if *exclusive { - end = end - 1; + end -= 1; } for i in start..=end { let args = vec![Rc::new(RObject::integer(i))]; diff --git a/mrubyedge/src/yamrb/prelude/string.rs b/mrubyedge/src/yamrb/prelude/string.rs index 9a4ee9c..e2bb599 100644 --- a/mrubyedge/src/yamrb/prelude/string.rs +++ b/mrubyedge/src/yamrb/prelude/string.rs @@ -34,7 +34,7 @@ pub(crate) fn initialize_string(vm: &mut VM) { pub fn mrb_string_new(_vm: &mut VM, args: &[Rc]) -> Result, Error> { let mut args = args; - if args.len() > 0 && args.last().unwrap().is_nil() { + if !args.is_empty() && args.last().unwrap().is_nil() { args = &args[..args.len() - 1]; } if args.is_empty() { diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 7612b37..bc88da7 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -92,7 +92,7 @@ impl PartialEq for ValueEqualityForKeyValue { return false; } } - return true; + true } } @@ -155,7 +155,7 @@ impl RObject { RObject { tt: RType::Float, value: RValue::Float(f), - object_id: (f.to_bits() as u64).into(), + object_id: f.to_bits().into(), singleton_class: RefCell::new(None), } } @@ -318,7 +318,7 @@ impl RObject { ValueEquality::Array(arr) } RValue::Hash(ha) => { - let keys: HashSet<_> = ha.borrow().keys().map(|k| k.clone()).collect(); + let keys: HashSet<_> = ha.borrow().keys().cloned().collect(); ValueEquality::KeyValue(ValueEqualityForKeyValue( keys, ha.borrow() @@ -406,7 +406,7 @@ impl TryFrom<&RObject> for i32 { Ok(0) } } - RValue::Float(f) => return Ok(f as i32), + RValue::Float(f) => Ok(f as i32), _ => Err(Error::TypeMismatch), } } @@ -425,7 +425,7 @@ impl TryFrom<&RObject> for u32 { Ok(0) } } - RValue::Float(f) => return Ok(f as u32), + RValue::Float(f) => Ok(f as u32), _ => Err(Error::TypeMismatch), } } @@ -444,7 +444,7 @@ impl TryFrom<&RObject> for i64 { Ok(0) } } - RValue::Float(f) => return Ok(f as i64), + RValue::Float(f) => Ok(f as i64), _ => Err(Error::TypeMismatch), } } @@ -463,7 +463,7 @@ impl TryFrom<&RObject> for u64 { Ok(0) } } - RValue::Float(f) => return Ok(f as u64), + RValue::Float(f) => Ok(f as u64), _ => Err(Error::TypeMismatch), } } @@ -482,7 +482,7 @@ impl TryFrom<&RObject> for usize { Ok(0) } } - RValue::Float(f) => return Ok(f as usize), + RValue::Float(f) => Ok(f as usize), _ => Err(Error::TypeMismatch), } } @@ -501,7 +501,7 @@ impl TryFrom<&RObject> for f32 { Ok(0.0) } } - RValue::Float(f) => return Ok(f as f32), + RValue::Float(f) => Ok(f as f32), _ => Err(Error::TypeMismatch), } } @@ -596,7 +596,7 @@ impl RModule { pub fn getmcnst(&self, name: &str) -> Option> { let consts = self.consts.borrow(); - consts.get(name).map(|v| v.clone()) + consts.get(name).cloned() } pub fn find_method(&self, name: &str) -> Option { @@ -826,25 +826,25 @@ impl RClass { pub fn from_error(vm: &mut VM, e: &Error) -> Rc { match e { Error::General => { - return vm.get_class_by_name("Exception"); + vm.get_class_by_name("Exception") } Error::Internal(_) => { - return vm.get_class_by_name("InternalError"); + vm.get_class_by_name("InternalError") } Error::InvalidOpCode => { - return vm.get_class_by_name("LoadError"); + vm.get_class_by_name("LoadError") } Error::RuntimeError(_) => { - return vm.get_class_by_name("RuntimeError"); + vm.get_class_by_name("RuntimeError") } Error::TypeMismatch => { - return vm.get_class_by_name("LoadError"); + vm.get_class_by_name("LoadError") } Error::NoMethodError(_) => { - return vm.get_class_by_name("NoMethodError"); + vm.get_class_by_name("NoMethodError") } Error::NameError(_) => { - return vm.get_class_by_name("NameError"); + vm.get_class_by_name("NameError") } } } diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 3deb750..afce88c 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -11,8 +11,8 @@ use super::prelude::prelude; use super::value::*; use super::{op, optable::*}; -pub const VERSION: &'static str = env!("CARGO_PKG_VERSION"); -pub const ENGINE: &'static str = "mruby/edge"; +pub const VERSION: &str = env!("CARGO_PKG_VERSION"); +pub const ENGINE: &str = "mruby/edge"; const MAX_REGS_SIZE: usize = 256; @@ -67,8 +67,8 @@ impl VM { /// preparing the VM so it can be executed via [`VM::run`]. pub fn open(rite: &mut Rite) -> VM { let irep = rite_to_irep(rite); - let vm = VM::new_by_raw_irep(irep); - vm + + VM::new_by_raw_irep(irep) } /// Returns a VM backed by an empty IREP that immediately executes a @@ -172,8 +172,8 @@ impl VM { let mut rescued = false; loop { - if !rescued { - if let Some(_e) = self.exception.clone() { + if !rescued + && let Some(_e) = self.exception.clone() { let operand = insn::Fetched::B(0); if let Some(pos) = self.find_next_handler_pos() { self.pc.set(pos); @@ -194,7 +194,6 @@ impl VM { continue; } } - } rescued = false; let pc = self.pc.get(); @@ -300,7 +299,7 @@ impl VM { self.builtin_class_table .get(name) .cloned() - .expect(format!("Class {} not found", name).as_str()) + .unwrap_or_else(|| panic!("Class {} not found", name)) } /// Defines a new class under the optional parent module, inheriting from @@ -354,7 +353,7 @@ impl VM { fn interpret_insn(mut insns: &[u8]) -> Vec { let mut pos: usize = 0; let mut ops = Vec::new(); - while insns.len() > 0 { + while !insns.is_empty() { let op = insns[0]; let opcode: insn::OpCode = op.try_into().unwrap(); let fetched = insn::FETCH_TABLE[op as usize](&mut insns).unwrap(); @@ -387,7 +386,7 @@ fn load_irep_1(reps: &mut [Irep], pos: usize) -> (IREP, usize) { .pool .push(RPool::Str(str.to_string_lossy().to_string())); } - let code = interpret_insn(&mut irep.insn); + let code = interpret_insn(irep.insn); for ch in irep.catch_handlers.iter() { let pos = ch.target; let (i, _) = code @@ -463,7 +462,7 @@ impl ENV { #[allow(unused)] pub(crate) fn capture(&self, regs: &[Option>]) { let mut captured = self.captured.borrow_mut(); - captured.replace(regs.iter().map(|r| r.clone()).collect()); + captured.replace(regs.to_vec()); } pub(crate) fn capture_no_clone(&self, regs: Vec>>) { From 4fa5b07cfff052933d2c2ce6b7dd3efbb527c51a Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 22:29:53 +0900 Subject: [PATCH 039/314] Fix clippy warnings in several files --- mrubyedge/examples/dump.rs | 2 +- mrubyedge/src/error.rs | 22 +++++------ mrubyedge/src/rite/insn.rs | 8 +++- mrubyedge/src/rite/mod.rs | 3 +- mrubyedge/src/rite/rite.rs | 55 ++++++++++++--------------- mrubyedge/src/yamrb/optable.rs | 41 +++++++++----------- mrubyedge/src/yamrb/prelude/array.rs | 10 ++++- mrubyedge/src/yamrb/prelude/hash.rs | 4 +- mrubyedge/src/yamrb/prelude/object.rs | 6 +-- mrubyedge/src/yamrb/prelude/string.rs | 10 +---- mrubyedge/src/yamrb/value.rs | 39 ++++++------------- 11 files changed, 92 insertions(+), 108 deletions(-) diff --git a/mrubyedge/examples/dump.rs b/mrubyedge/examples/dump.rs index 3a35695..e62a79c 100644 --- a/mrubyedge/examples/dump.rs +++ b/mrubyedge/examples/dump.rs @@ -6,7 +6,7 @@ extern crate mrubyedge; fn main() -> Result<(), std::io::Error> { let args: Vec = env::args().skip(1).collect(); - let path = args.get(0).expect("Usage: $0 "); + let path = args.first().expect("Usage: $0 "); let mut file = File::open(path)?; let mut bin = Vec::::new(); diff --git a/mrubyedge/src/error.rs b/mrubyedge/src/error.rs index 26a5d08..28faf9b 100644 --- a/mrubyedge/src/error.rs +++ b/mrubyedge/src/error.rs @@ -18,7 +18,7 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "error nr {}", self) + write!(f, "error nr {:?}", self) } } @@ -42,16 +42,16 @@ impl Error { } pub fn is_instance_of(&self, other: Rc) -> bool { - match (self, other.sym_id.name.as_str()) { - (Error::General, "StandardError") => true, - (Error::Internal(_), "InternalError") => true, - (Error::InvalidOpCode, "StandardError") => true, - (Error::RuntimeError(_), "RuntimeError") => true, - (Error::TypeMismatch, "StandardError") => true, - (Error::NoMethodError(_), "NoMethodError") => true, - (Error::NameError(_), "NameError") => true, - _ => false, - } + matches!( + (self, other.sym_id.name.as_str()), + (Error::General, "StandardError") + | (Error::Internal(_), "InternalError") + | (Error::InvalidOpCode, "StandardError") + | (Error::RuntimeError(_), "RuntimeError") + | (Error::TypeMismatch, "StandardError") + | (Error::NoMethodError(_), "NoMethodError") + | (Error::NameError(_), "NameError") + ) } pub fn is_a(&self, vm: &mut VM, other: Rc) -> bool { diff --git a/mrubyedge/src/rite/insn.rs b/mrubyedge/src/rite/insn.rs index 414015c..fb3380c 100644 --- a/mrubyedge/src/rite/insn.rs +++ b/mrubyedge/src/rite/insn.rs @@ -85,6 +85,10 @@ impl Fetched { Fetched::W(_) => 3, } } + + pub fn is_empty(&self) -> bool { + matches!(self, Fetched::Z) + } } // from mruby 3.2.0 op.h @@ -539,7 +543,9 @@ const BSS: fn(&mut &[u8]) -> Result = fetch_bss; const S: fn(&mut &[u8]) -> Result = fetch_s; const W: fn(&mut &[u8]) -> Result = fetch_w; -pub const FETCH_TABLE: [fn(&mut &[u8]) -> Result; OpCode::NumberOfOpcode as usize] = [ +type FetchFn = fn(&mut &[u8]) -> Result; + +pub const FETCH_TABLE: [FetchFn; OpCode::NumberOfOpcode as usize] = [ Z, BB, BB, BB, BB, B, B, B, B, B, B, B, B, B, BS, BSS, BB, B, B, B, B, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BB, BBB, BBB, B, B, S, BS, BS, BS, S, B, BB, B, BBB, BBB, BBB, BBB, Z, BB, BS, W, BB, Z, BB, B, B, B, BS, B, BB, B, BB, B, B, B, B, B, B, B, BB, BBB, B, BB, B, BBB, BBB, diff --git a/mrubyedge/src/rite/mod.rs b/mrubyedge/src/rite/mod.rs index 2f2c6a7..1d041e5 100644 --- a/mrubyedge/src/rite/mod.rs +++ b/mrubyedge/src/rite/mod.rs @@ -4,6 +4,7 @@ pub mod binfmt; pub mod insn; pub mod marker; +#[allow(clippy::module_inception)] // FIXME rename pub mod rite; pub use rite::*; @@ -24,7 +25,7 @@ pub enum Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "error nr {}", self) + write!(f, "error nr {:?}", self) } } diff --git a/mrubyedge/src/rite/rite.rs b/mrubyedge/src/rite/rite.rs index 8af66d9..e8b0f12 100644 --- a/mrubyedge/src/rite/rite.rs +++ b/mrubyedge/src/rite/rite.rs @@ -84,36 +84,31 @@ pub fn load<'a>(src: &'a [u8]) -> Result, Error> { return Err(Error::TooShort); } - loop { - match peek4(head) { - Some(chrs) => match chrs { - IREP => { - let (cur, irep_header, irep) = section_irep_1(head)?; - rite.irep_header = irep_header; - rite.irep = irep; - head = &head[cur..]; - } - LVAR => { - let (cur, lvar) = section_lvar(head)?; - rite.lvar = Some(lvar); - head = &head[cur..]; - } - DBG => { - let cur = section_skip(head)?; - head = &head[cur..]; - } - END => { - let cur = section_end(head)?; - head = &head[cur..]; - } - _ => { - dbg!(chrs); - dbg!(head); - return Err(Error::InvalidFormat); - } - }, - None => { - break; + while let Some(chrs) = peek4(head) { + match chrs { + IREP => { + let (cur, irep_header, irep) = section_irep_1(head)?; + rite.irep_header = irep_header; + rite.irep = irep; + head = &head[cur..]; + } + LVAR => { + let (cur, lvar) = section_lvar(head)?; + rite.lvar = Some(lvar); + head = &head[cur..]; + } + DBG => { + let cur = section_skip(head)?; + head = &head[cur..]; + } + END => { + let cur = section_end(head)?; + head = &head[cur..]; + } + _ => { + eprintln!("{:?}", chrs); + eprint!("{:?}", head); + return Err(Error::InvalidFormat); } } } diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 3c8db2c..ec169c1 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -646,16 +646,12 @@ pub(crate) fn op_getconst(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let mut current = current_namespace(vm); // Walk namespace chain upwards until found or reach top-level - loop { - if let Some(ns) = current.clone() { - if let Some(val) = ns.consts.borrow().get(&name).cloned() { - vm.current_regs()[a as usize].replace(val); - return Ok(()); - } - current = ns.parent.borrow().clone(); - } else { - break; + while let Some(ns) = current.clone() { + if let Some(val) = ns.consts.borrow().get(&name).cloned() { + vm.current_regs()[a as usize].replace(val); + return Ok(()); } + current = ns.parent.borrow().clone(); } if let Some(val) = vm.consts.get(&name).cloned() { @@ -848,7 +844,9 @@ pub(crate) fn op_rescue(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_raiseif(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()?; let val = vm.current_regs()[a as usize].as_ref().cloned(); - if let Some(val) = val && let RValue::Exception(e) = &val.value { + if let Some(val) = val + && let RValue::Exception(e) = &val.value + { return Err(e.as_ref().error_type.borrow().clone()); } Ok(()) @@ -1076,21 +1074,20 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let nregs = old_irep.nregs; let regs0_cloned: Vec<_> = vm.current_regs()[0..nregs].to_vec(); - if vm.has_env_ref.get(&vm.current_irep.__id).is_some() - && let Some(environ) = vm.cur_env.get(&vm.current_irep.__id) { - environ.capture_no_clone(regs0_cloned); - environ.as_ref().expire(); - vm.has_env_ref.remove(&vm.current_irep.__id); - } + if let Some(environ) = vm.cur_env.get(&vm.current_irep.__id) { + environ.capture_no_clone(regs0_cloned); + environ.as_ref().expire(); + vm.has_env_ref.remove(&vm.current_irep.__id); + } let regs0 = vm.current_regs(); if let Some(regs_a) = regs0[a].take() { regs0[0].replace(regs_a); } if nregs > 0 { - for i in 1..=nregs { - regs0[i].take(); - } + regs0[1..=nregs].iter_mut().for_each(|reg| { + reg.take(); + }); } let ci = vm.current_callinfo.take(); @@ -1304,7 +1301,7 @@ fn do_op_array(vm: &mut VM, this: usize, start: usize, n: usize) -> Result<(), E if this == start && i == 0 { ary.push(vm.take_current_regs(start)?); } else { - ary.push(vm.get_current_regs_cloned(start + i )?); + ary.push(vm.get_current_regs_cloned(start + i)?); } } let val = RObject::array(ary); @@ -1367,7 +1364,7 @@ pub(crate) fn op_hash(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { hash.insert(key.as_hash_key()?, (key, val)); } let val = RObject::hash(hash); - vm.current_regs()[a ].replace(Rc::new(val)); + vm.current_regs()[a].replace(Rc::new(val)); Ok(()) } @@ -1475,7 +1472,7 @@ fn do_op_range(vm: &mut VM, a: usize, b: usize, exclusive: bool) -> Result<(), E object_id: u64::MAX.into(), singleton_class: RefCell::new(None), }; - vm.current_regs()[a ].replace(val.to_refcount_assigned()); + vm.current_regs()[a].replace(val.to_refcount_assigned()); Ok(()) } diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index 1da9f5e..f914d17 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -43,7 +43,13 @@ pub fn mrb_array_new(_vm: &mut VM, args: &[Rc]) -> Result, vec![] } else { let size: usize = args[0].as_ref().try_into()?; - vec![Rc::new(RObject::nil()); size] + { + let mut v = Vec::with_capacity(size); + for _ in 0..size { + v.push(Rc::new(RObject::nil())); + } + v + } }; Ok(Rc::new(RObject::array(array))) } @@ -217,7 +223,7 @@ fn test_mrb_array_push_and_index() { ]; mrb_array_push(array.clone(), &args).expect("push failed"); - let answers = vec![1, 2, 3]; + let answers = [1, 2, 3]; for (i, expected) in answers.iter().enumerate() { let args = vec![Rc::new(RObject::integer(i as i64))]; diff --git a/mrubyedge/src/yamrb/prelude/hash.rs b/mrubyedge/src/yamrb/prelude/hash.rs index 947aa76..e31821c 100644 --- a/mrubyedge/src/yamrb/prelude/hash.rs +++ b/mrubyedge/src/yamrb/prelude/hash.rs @@ -118,12 +118,12 @@ fn test_mrb_hash_set_and_index() { prelude::prelude(&mut vm); let hash = Rc::new(RObject::hash(HashMap::new())); - let keys = vec![ + let keys = [ Rc::new(RObject::string("key".to_string())), Rc::new(RObject::integer(1234)), Rc::new(RObject::symbol("key2".into())), ]; - let values = vec![ + let values = [ Rc::new(RObject::integer(1)), Rc::new(RObject::integer(2)), Rc::new(RObject::integer(42)), diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index f4c9c21..e3791a4 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -548,7 +548,7 @@ fn test_mrb_object_is_equal_klass() { let mut vm = VM::empty(); let lhs: Rc = vm.get_class_by_name("String"); - let rhs: Rc = RObject::string("String".into()).get_class(&mut vm); + let rhs: Rc = RObject::string("String".into()).get_class(&vm); let lhs = RObject::class(lhs.clone(), &mut vm); let rhs = RObject::class(rhs.clone(), &mut vm); let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) @@ -557,8 +557,8 @@ fn test_mrb_object_is_equal_klass() { .expect("must return bool"); assert!(ret); - let lhs: Rc = RObject::integer(5471).get_class(&mut vm); - let rhs: Rc = RObject::string("String".into()).get_class(&mut vm); + let lhs: Rc = RObject::integer(5471).get_class(&vm); + let rhs: Rc = RObject::string("String".into()).get_class(&vm); let lhs = RObject::class(lhs.clone(), &mut vm); let rhs = RObject::class(rhs.clone(), &mut vm); let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) diff --git a/mrubyedge/src/yamrb/prelude/string.rs b/mrubyedge/src/yamrb/prelude/string.rs index e2bb599..95cac6a 100644 --- a/mrubyedge/src/yamrb/prelude/string.rs +++ b/mrubyedge/src/yamrb/prelude/string.rs @@ -144,17 +144,11 @@ fn test_mrb_string_unpack() { let ret = helpers::mrb_funcall(&mut vm, Some(data), "unpack", &arg).expect("unpack failed"); - let answers = vec![ + let answers = [ 0x01, 0x02 | 0x03 << 8, 0x04 | 0x05 << 8 | 0x06 << 16 | 0x07 << 24, - 0x04 | 0x04 << 8 - | 0x03 << 16 - | 0x03 << 24 - | 0x02 << 32 - | 0x02 << 40 - | 0x00 << 48 - | 0x00 << 56, + (0x04 | 0x04 << 8 | 0x03 << 16 | 0x03 << 24 | 0x02 << 32 | 0x02 << 40), ]; for (i, expected) in answers.iter().enumerate() { diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index bc88da7..119ca16 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -29,6 +29,8 @@ pub enum RType { Nil, } +type RHash = HashMap, Rc)>; + /// Actual storage for Ruby values, including boxed objects and immediates. #[derive(Debug, Clone)] pub enum RValue { @@ -41,7 +43,7 @@ pub enum RValue { Instance(RInstance), Proc(RProc), Array(RefCell>>), - Hash(RefCell, Rc)>>), + Hash(RefCell), String(RefCell>), Range(Rc, Rc, bool), SharedMemory(Rc>), @@ -138,7 +140,7 @@ impl RObject { let object_id = if n >= (i32::MAX as i64) { u64::MAX } else if n <= (i32::MIN as i64) { - u64::MAX + i64::MIN as u64 } else { n as u64 * 2 + 1 }; @@ -283,10 +285,7 @@ impl RObject { } pub fn is_nil(&self) -> bool { - match self.tt { - RType::Nil => true, - _ => false, - } + matches!(self.tt, RType::Nil) } // TODO: implment Object#hash @@ -825,27 +824,13 @@ pub struct RException { impl RClass { pub fn from_error(vm: &mut VM, e: &Error) -> Rc { match e { - Error::General => { - vm.get_class_by_name("Exception") - } - Error::Internal(_) => { - vm.get_class_by_name("InternalError") - } - Error::InvalidOpCode => { - vm.get_class_by_name("LoadError") - } - Error::RuntimeError(_) => { - vm.get_class_by_name("RuntimeError") - } - Error::TypeMismatch => { - vm.get_class_by_name("LoadError") - } - Error::NoMethodError(_) => { - vm.get_class_by_name("NoMethodError") - } - Error::NameError(_) => { - vm.get_class_by_name("NameError") - } + Error::General => vm.get_class_by_name("Exception"), + Error::Internal(_) => vm.get_class_by_name("InternalError"), + Error::InvalidOpCode => vm.get_class_by_name("LoadError"), + Error::RuntimeError(_) => vm.get_class_by_name("RuntimeError"), + Error::TypeMismatch => vm.get_class_by_name("LoadError"), + Error::NoMethodError(_) => vm.get_class_by_name("NoMethodError"), + Error::NameError(_) => vm.get_class_by_name("NameError"), } } } From 7f4992b409c6aa63c67037b43158c4e7d2383053 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 22:31:42 +0900 Subject: [PATCH 040/314] Format2 --- mrubyedge/src/yamrb/helpers.rs | 7 +++--- mrubyedge/src/yamrb/prelude/range.rs | 12 +++------ mrubyedge/src/yamrb/vm.rs | 37 ++++++++++++++-------------- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index 3916446..f01567d 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -66,9 +66,10 @@ fn call_block( vm.target_class = ci.target_class.clone(); } if let Some(upper) = vm.upper.take() - && let Some(upper) = &upper.as_ref().upper { - vm.upper.replace(upper.clone()); - } + && let Some(upper) = &upper.as_ref().upper + { + vm.upper.replace(upper.clone()); + } match &res { Ok(res) => Ok(res.clone()), diff --git a/mrubyedge/src/yamrb/prelude/range.rs b/mrubyedge/src/yamrb/prelude/range.rs index a8796d4..191f3bd 100644 --- a/mrubyedge/src/yamrb/prelude/range.rs +++ b/mrubyedge/src/yamrb/prelude/range.rs @@ -42,16 +42,12 @@ pub fn mrb_range_is_include(vm: &mut VM, args: &[Rc]) -> Result { - Ok(Rc::new(RObject::boolean(false))) - } + _ => Ok(Rc::new(RObject::boolean(false))), } } - _ => { - Err(Error::RuntimeError( - "Range#include? must be called on a Range".to_string(), - )) - } + _ => Err(Error::RuntimeError( + "Range#include? must be called on a Range".to_string(), + )), } } diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index afce88c..3225fe1 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -67,7 +67,7 @@ impl VM { /// preparing the VM so it can be executed via [`VM::run`]. pub fn open(rite: &mut Rite) -> VM { let irep = rite_to_irep(rite); - + VM::new_by_raw_irep(irep) } @@ -172,28 +172,27 @@ impl VM { let mut rescued = false; loop { - if !rescued - && let Some(_e) = self.exception.clone() { - let operand = insn::Fetched::B(0); - if let Some(pos) = self.find_next_handler_pos() { - self.pc.set(pos); - rescued = true; - continue; - } + if !rescued && let Some(_e) = self.exception.clone() { + let operand = insn::Fetched::B(0); + if let Some(pos) = self.find_next_handler_pos() { + self.pc.set(pos); + rescued = true; + continue; + } - match op_return(self, &operand) { - Ok(_) => {} - Err(_) => { - // use assigned expection through - break; - } - } - if self.flag_preemption.get() { + match op_return(self, &operand) { + Ok(_) => {} + Err(_) => { + // use assigned expection through break; - } else { - continue; } } + if self.flag_preemption.get() { + break; + } else { + continue; + } + } rescued = false; let pc = self.pc.get(); From 4057b0b2fb5cfc7ae0367e29acb9713d4019950a Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 22:33:12 +0900 Subject: [PATCH 041/314] Format... --- mrubyedge-cli/src/lib.rs | 2 +- mrubyedge-cli/src/main.rs | 2 +- mrubyedge-cli/src/rbs_parser/mod.rs | 4 +- mrubyedge-cli/src/subcommands/compile_mrb.rs | 2 +- mrubyedge-cli/src/subcommands/mod.rs | 4 +- mrubyedge-cli/src/subcommands/wasm.rs | 56 ++++++++++++++------ 6 files changed, 46 insertions(+), 24 deletions(-) diff --git a/mrubyedge-cli/src/lib.rs b/mrubyedge-cli/src/lib.rs index 299f828..fa5f3f2 100644 --- a/mrubyedge-cli/src/lib.rs +++ b/mrubyedge-cli/src/lib.rs @@ -1,3 +1,3 @@ pub mod rbs_parser; -pub mod template; pub mod subcommands; +pub mod template; diff --git a/mrubyedge-cli/src/main.rs b/mrubyedge-cli/src/main.rs index 07a084e..d995025 100644 --- a/mrubyedge-cli/src/main.rs +++ b/mrubyedge-cli/src/main.rs @@ -46,7 +46,7 @@ enum ScaffoldType { Npm, } -fn main() -> Result<(), Box> { +fn main() -> Result<(), Box> { let cli = Cli::parse(); match cli.command { diff --git a/mrubyedge-cli/src/rbs_parser/mod.rs b/mrubyedge-cli/src/rbs_parser/mod.rs index cd44d20..03dd27d 100644 --- a/mrubyedge-cli/src/rbs_parser/mod.rs +++ b/mrubyedge-cli/src/rbs_parser/mod.rs @@ -238,11 +238,11 @@ use nom::branch::permutation; use nom::bytes::complete::tag; use nom::character::complete::*; // use nom::combinator::opt; -use nom::error::context; +use nom::IResult; use nom::error::VerboseError; +use nom::error::context; use nom::multi::*; use nom::sequence::tuple; -use nom::IResult; type Res = IResult>; diff --git a/mrubyedge-cli/src/subcommands/compile_mrb.rs b/mrubyedge-cli/src/subcommands/compile_mrb.rs index fc457d5..4bf3e03 100644 --- a/mrubyedge-cli/src/subcommands/compile_mrb.rs +++ b/mrubyedge-cli/src/subcommands/compile_mrb.rs @@ -40,6 +40,6 @@ pub fn execute(args: CompileMrbArgs) -> Result<(), Box> { ctx.compile_to_file(&buf, &output)?; } } - + Ok(()) } diff --git a/mrubyedge-cli/src/subcommands/mod.rs b/mrubyedge-cli/src/subcommands/mod.rs index ba345c0..3abd289 100644 --- a/mrubyedge-cli/src/subcommands/mod.rs +++ b/mrubyedge-cli/src/subcommands/mod.rs @@ -1,4 +1,4 @@ -pub mod run; -pub mod wasm; pub mod compile_mrb; +pub mod run; pub mod scaffold; +pub mod wasm; diff --git a/mrubyedge-cli/src/subcommands/wasm.rs b/mrubyedge-cli/src/subcommands/wasm.rs index 85715f4..28b44c5 100644 --- a/mrubyedge-cli/src/subcommands/wasm.rs +++ b/mrubyedge-cli/src/subcommands/wasm.rs @@ -1,8 +1,13 @@ -extern crate rand; extern crate mruby_compiler2_sys; +extern crate rand; use clap::Args; -use std::{fs::File, io::Read, path::{Path, PathBuf}, process::Command}; +use std::{ + fs::File, + io::Read, + path::{Path, PathBuf}, + process::Command, +}; use askama::Template; use rand::distributions::{Alphanumeric, DistString}; @@ -96,7 +101,7 @@ pub fn execute(args: WasmArgs) -> Result<(), Box> { } unsafe { mruby_compiler2_sys::MRubyCompiler2Context::new() - .compile_to_file(&code, out_file.as_ref())? + .compile_to_file(&code, out_file.as_ref())? } let feature = if args.no_wasi { "no-wasi" } else { "default" }; @@ -109,7 +114,9 @@ pub fn execute(args: WasmArgs) -> Result<(), Box> { std::fs::write("Cargo.toml", cargo_toml.render()?)?; } else { let cargo_toml = template::cargo_toml::CargoToml { - mrubyedge_version: &args.mruby_edge_version.unwrap_or_else(|| MRUBY_EDGE_DEFAULT_VERSION.to_string()), + mrubyedge_version: &args + .mruby_edge_version + .unwrap_or_else(|| MRUBY_EDGE_DEFAULT_VERSION.to_string()), mrubyedge_feature: feature, strip: &args.strip_binary.to_string(), }; @@ -126,7 +133,10 @@ pub fn execute(args: WasmArgs) -> Result<(), Box> { if import_rbs.exists() { debug_println( args.verbose, - &format!("detected import.rbs: {}", import_rbs.as_path().to_string_lossy()), + &format!( + "detected import.rbs: {}", + import_rbs.as_path().to_string_lossy() + ), ); let mut f = File::open(import_rbs)?; let mut s = String::new(); @@ -146,10 +156,13 @@ pub fn execute(args: WasmArgs) -> Result<(), Box> { } if export_rbs.exists() { - debug_println(args.verbose, &format!( - "detected export.rbs: {}", - export_rbs.as_path().to_string_lossy() - )); + debug_println( + args.verbose, + &format!( + "detected export.rbs: {}", + export_rbs.as_path().to_string_lossy() + ), + ); let mut f = File::open(export_rbs)?; let mut s = String::new(); f.read_to_string(&mut s)?; @@ -210,20 +223,29 @@ pub fn execute(args: WasmArgs) -> Result<(), Box> { "wasm32-wasip1" }; - sh_do(&format!("cargo build --target {} --release", target), args.verbose)?; - sh_do(&format!( - "cp ./target/{}/release/mywasm.wasm {}/{}.wasm", - target, - &pwd.to_str().unwrap(), - &fname.to_string() - ), args.verbose)?; + sh_do( + &format!("cargo build --target {} --release", target), + args.verbose, + )?; + sh_do( + &format!( + "cp ./target/{}/release/mywasm.wasm {}/{}.wasm", + target, + &pwd.to_str().unwrap(), + &fname.to_string() + ), + args.verbose, + )?; if args.skip_cleanup { println!( "debug: working directory for compile wasm is remained in {}", std::env::current_dir()?.as_os_str().to_str().unwrap() ); } else { - sh_do(&format!("cd .. && rm -rf work-mrubyedge-{}", &suffix), args.verbose)?; + sh_do( + &format!("cd .. && rm -rf work-mrubyedge-{}", &suffix), + args.verbose, + )?; } std::env::set_current_dir(pwd)?; From 66c795e2349fd3f98ce7c59ca9962b32e8493823 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 22:35:16 +0900 Subject: [PATCH 042/314] Update CI --- .../{mrubyedge.yml => build-test-fmt.yml} | 9 +++-- .github/workflows/clippy.yml | 33 +++++++++++++++++++ 2 files changed, 39 insertions(+), 3 deletions(-) rename .github/workflows/{mrubyedge.yml => build-test-fmt.yml} (87%) create mode 100644 .github/workflows/clippy.yml diff --git a/.github/workflows/mrubyedge.yml b/.github/workflows/build-test-fmt.yml similarity index 87% rename from .github/workflows/mrubyedge.yml rename to .github/workflows/build-test-fmt.yml index d71e372..00d0d3e 100644 --- a/.github/workflows/mrubyedge.yml +++ b/.github/workflows/build-test-fmt.yml @@ -32,6 +32,11 @@ jobs: key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - name: Install C compiler run: sudo apt-get update && sudo apt-get install -y build-essential + - name: Run formatter + run: | + set -e + cargo fmt -p mrubyedge --check + cargo fmt -p mrubyedge-cli --check - name: Run tests for "${{ matrix.BUILD_TARGET }}" profile run: | set +e @@ -42,7 +47,5 @@ jobs: git diff exit $TEST_RESULT fi - working-directory: mrubyedge - name: Build binaries for "${{ matrix.BUILD_TARGET }}" profile - run: cargo build --workspace --exclude mec --profile ${{ matrix.BUILD_TARGET }} - working-directory: mrubyedge \ No newline at end of file + run: cargo build --workspace --exclude mec --profile ${{ matrix.BUILD_TARGET }} \ No newline at end of file diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml new file mode 100644 index 0000000..6de97b7 --- /dev/null +++ b/.github/workflows/clippy.yml @@ -0,0 +1,33 @@ +name: mruby/edge CI + +on: + push: + branches: + - master + paths: + - 'mrubyedge/**' + - 'mrubyedge-cli/**' + pull_request: + branches: + - master + paths: + - 'mrubyedge/**' + - 'mrubyedge-cli/**' + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Cache + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: Install C compiler + run: sudo apt-get update && sudo apt-get install -y build-essential + - name: Run linter + run: cargo clippy -p mrubyedge \ No newline at end of file From 509479cc0dd0a79f4bfcafce72fbbc29d7fdbb04 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 22:39:24 +0900 Subject: [PATCH 043/314] Add warnings on tests --- mrubyedge/tests/fncall.rs | 2 +- mrubyedge/tests/helpers/mod.rs | 12 ++++++------ mrubyedge/tests/iter.rs | 2 -- mrubyedge/tests/shared_memory.rs | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/mrubyedge/tests/fncall.rs b/mrubyedge/tests/fncall.rs index 3a81ab7..aef0eab 100644 --- a/mrubyedge/tests/fncall.rs +++ b/mrubyedge/tests/fncall.rs @@ -28,7 +28,7 @@ end args: &[Rc], ) -> Result, Error> { // Get the first argument (should be an integer) - let n = if args.len() > 0 { + let n = if !args.is_empty() { let arg: i64 = args[0].as_ref().try_into()?; arg } else { diff --git a/mrubyedge/tests/helpers/mod.rs b/mrubyedge/tests/helpers/mod.rs index a88051c..818fae1 100644 --- a/mrubyedge/tests/helpers/mod.rs +++ b/mrubyedge/tests/helpers/mod.rs @@ -13,7 +13,8 @@ macro_rules! mrbc_compile_ { let mut src = std::env::temp_dir(); src.push(format!("{}.{}.mrb", $fname, std::process::id())); let mut f = File::create(&src).expect("cannot open srs file"); - f.write($code.as_bytes()).expect("cannot create src file"); + f.write_all($code.as_bytes()) + .expect("cannot create src file"); f.flush().unwrap(); let mut src0 = src.as_os_str().to_string_lossy().into_owned(); @@ -43,8 +44,7 @@ macro_rules! mrbc_compile_ { pub(crate) fn mrbc_compile(fname: &'static str, code: &'static str) -> Vec { let dest = mrbc_compile_!(fname, code); - let binary = std::fs::read(dest).unwrap(); - binary + std::fs::read(dest).unwrap() } macro_rules! mrbc_compile_debug_ { @@ -54,7 +54,8 @@ macro_rules! mrbc_compile_debug_ { let mut src = std::env::temp_dir(); src.push(format!("{}.{}.mrb", $fname, std::process::id())); let mut f = File::create(&src).expect("cannot open srs file"); - f.write($code.as_bytes()).expect("cannot create src file"); + f.write_all($code.as_bytes()) + .expect("cannot create src file"); f.flush().unwrap(); let mut src0 = src.as_os_str().to_string_lossy().into_owned(); @@ -84,8 +85,7 @@ macro_rules! mrbc_compile_debug_ { pub(crate) fn mrbc_compile_debug(fname: &'static str, code: &'static str) -> Vec { let dest = mrbc_compile_debug_!(fname, code); - let binary = std::fs::read(dest).unwrap(); - binary + std::fs::read(dest).unwrap() } pub(crate) fn int(n: i64) -> Rc { diff --git a/mrubyedge/tests/iter.rs b/mrubyedge/tests/iter.rs index e5b8c38..c783bc9 100644 --- a/mrubyedge/tests/iter.rs +++ b/mrubyedge/tests/iter.rs @@ -48,7 +48,6 @@ fn times_self_test() { // Assert let args = vec![]; mrb_funcall(&mut vm, None, "test_times", &args).unwrap(); - assert!(true); } #[test] @@ -72,7 +71,6 @@ fn times_self_2_test() { // Assert let args = vec![]; mrb_funcall(&mut vm, None, "test_times", &args).unwrap(); - assert!(true); } #[test] diff --git a/mrubyedge/tests/shared_memory.rs b/mrubyedge/tests/shared_memory.rs index 10cd94d..59adff3 100644 --- a/mrubyedge/tests/shared_memory.rs +++ b/mrubyedge/tests/shared_memory.rs @@ -24,7 +24,7 @@ end"; // Assert let args = vec![]; let result1 = mrb_funcall(&mut vm, None, "get_memory", &args).unwrap(); - assert!(result1.as_ref().get_class(&mut vm).as_ref().sym_id.name == "SharedMemory"); + assert!(result1.as_ref().get_class(&vm).as_ref().sym_id.name == "SharedMemory"); let result2 = mrb_funcall(&mut vm, None, "read_array_from_memory", &args).unwrap(); let result2: i64 = result2.as_ref().try_into().unwrap(); From c0e5662e1950bcfa47947443cf6d1f28b7274e0c Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 22:41:26 +0900 Subject: [PATCH 044/314] One settings --- .github/workflows/clippy.yml | 33 ------------------- .../{build-test-fmt.yml => mrubyedge.yml} | 19 ++++++++++- 2 files changed, 18 insertions(+), 34 deletions(-) delete mode 100644 .github/workflows/clippy.yml rename .github/workflows/{build-test-fmt.yml => mrubyedge.yml} (73%) diff --git a/.github/workflows/clippy.yml b/.github/workflows/clippy.yml deleted file mode 100644 index 6de97b7..0000000 --- a/.github/workflows/clippy.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: mruby/edge CI - -on: - push: - branches: - - master - paths: - - 'mrubyedge/**' - - 'mrubyedge-cli/**' - pull_request: - branches: - - master - paths: - - 'mrubyedge/**' - - 'mrubyedge-cli/**' - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - name: Cache - uses: actions/cache@v4 - with: - path: | - ~/.cargo/registry - ~/.cargo/git - target - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - - name: Install C compiler - run: sudo apt-get update && sudo apt-get install -y build-essential - - name: Run linter - run: cargo clippy -p mrubyedge \ No newline at end of file diff --git a/.github/workflows/build-test-fmt.yml b/.github/workflows/mrubyedge.yml similarity index 73% rename from .github/workflows/build-test-fmt.yml rename to .github/workflows/mrubyedge.yml index 00d0d3e..3b9468a 100644 --- a/.github/workflows/build-test-fmt.yml +++ b/.github/workflows/mrubyedge.yml @@ -48,4 +48,21 @@ jobs: exit $TEST_RESULT fi - name: Build binaries for "${{ matrix.BUILD_TARGET }}" profile - run: cargo build --workspace --exclude mec --profile ${{ matrix.BUILD_TARGET }} \ No newline at end of file + run: cargo build --workspace --exclude mec --profile ${{ matrix.BUILD_TARGET }} + + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + - name: Cache + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} + - name: Install C compiler + run: sudo apt-get update && sudo apt-get install -y build-essential + - name: Run linter + run: cargo clippy -p mrubyedge \ No newline at end of file From dded3a2692170a9371659435d95c1f03cce6a151 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 22:44:23 +0900 Subject: [PATCH 045/314] Update mrubyedge --- Cargo.lock | 2 +- mrubyedge/Cargo.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d3e421a..92431f1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -582,7 +582,7 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.7" +version = "1.0.8" dependencies = [ "criterion", "getrandom", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 846918e..0d693b9 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.0.7" +version = "1.0.8" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" @@ -22,6 +22,6 @@ name = "benchmark" harness = false [features] -default = [ "wasi" ] -wasi = [ "dep:getrandom" ] +default = ["wasi"] +wasi = ["dep:getrandom"] no-wasi = [] From 84960faa42612f7e1ad99a8be50dfc153aca682d Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 10 Dec 2025 22:46:22 +0900 Subject: [PATCH 046/314] Bump again --- Cargo.lock | 14 +++++++------- mrubyedge-cli/Cargo.toml | 10 +++------- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 92431f1..3134603 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -571,11 +571,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d38983e69b290e06aa04c1c22f63fe706f1e316fb509f44917495ceb8a63bb9" +version = "1.0.8" dependencies = [ + "criterion", "getrandom", + "mec-mrbc-sys", "plain", "simple_endian", ] @@ -583,22 +583,22 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef054892d1144fe548e1b12062affc992aebc393182e1a1c0b5f5f82df311c47" dependencies = [ - "criterion", "getrandom", - "mec-mrbc-sys", "plain", "simple_endian", ] [[package]] name = "mrubyedge-cli" -version = "1.0.6" +version = "1.0.8" dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.6", + "mrubyedge 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 3b23e00..fdede55 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,15 +1,11 @@ [package] name = "mrubyedge-cli" -version = "1.0.6" +version = "1.0.8" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." license = "BSD-3-Clause" -include = [ - "**/*.rs", - "Cargo.toml", - "templates/*", -] +include = ["**/*.rs", "Cargo.toml", "templates/*"] [[bin]] name = "mrbedge" @@ -18,7 +14,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } mruby-compiler2-sys = "0.1.1" -mrubyedge = { version = "1.0.6" } +mrubyedge = { version = "1.0.8" } rand = "0.8.5" nom = "7.1.3" askama = "0.12.1" From eaa19155e00f5317ec9b0129e57efc4508d78b20 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 10:26:00 +0900 Subject: [PATCH 047/314] Add alias and unde examples irep 0x103730080 nregs=3 nlocals=1 pools=0 syms=3 reps=2 ilen=25 file: examples/alias.rb 2 000 TCLASS R1 2 002 METHOD R2 I[0] 2 005 DEF R1 :initialize (R2) 6 008 TCLASS R1 6 010 METHOD R2 I[1] 6 013 DEF R1 :greet (R2) 10 016 ALIAS :say_hello greet 11 019 UNDEF :greet 11 021 LOADNIL R1 (nil) 11 023 RETURN R1 --- mrubyedge/examples/alias.rb | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 mrubyedge/examples/alias.rb diff --git a/mrubyedge/examples/alias.rb b/mrubyedge/examples/alias.rb new file mode 100644 index 0000000..06ec1e7 --- /dev/null +++ b/mrubyedge/examples/alias.rb @@ -0,0 +1,15 @@ +class For + def initialize(name) + @name = name + end + + def greet + puts "Hello, #{@name}!" + end + + alias say_hello greet + undef greet +end + +f = For.new("World") +f.say_hello \ No newline at end of file From 1ac4acc4abf047566f00095ec83ace946d12211f Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 10:37:58 +0900 Subject: [PATCH 048/314] Support ALIAS --- mrubyedge/examples/alias.rb | 19 +++++++++++++++-- mrubyedge/src/yamrb/optable.rs | 38 +++++++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 5 deletions(-) diff --git a/mrubyedge/examples/alias.rb b/mrubyedge/examples/alias.rb index 06ec1e7..74bb8a5 100644 --- a/mrubyedge/examples/alias.rb +++ b/mrubyedge/examples/alias.rb @@ -1,4 +1,19 @@ -class For +class Alias1 + def initialize(name) + @name = name + end + + def greet + puts "Hello, #{@name}!" + end + + alias say_hello greet +end + +f = Alias1.new("World of alias") +f.say_hello + +class Alias2 def initialize(name) @name = name end @@ -11,5 +26,5 @@ def greet undef greet end -f = For.new("World") +f = Alias2.new("World of undef") f.say_hello \ No newline at end of file diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index ec169c1..cf6536e 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -436,9 +436,9 @@ pub(crate) fn consume_expr( DEF => { op_def(vm, operand)?; } - // ALIAS => { - // // op_alias(vm, &operand)?; - // } + ALIAS => { + op_alias(vm, operand)?; + } // UNDEF => { // // op_undef(vm, &operand)?; // } @@ -1605,6 +1605,38 @@ pub(crate) fn op_def(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { Ok(()) } +pub(crate) fn op_alias(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { + let (a, b) = operand.as_bb()?; + let new_name = vm.current_irep.syms[a as usize].clone(); + let old_name = vm.current_irep.syms[b as usize].clone(); + + let owner = vm.target_class.clone(); + let (owner_module, method) = match &owner { + TargetContext::Class(klass) => { + let (owner_module, method) = resolve_method(klass, &old_name.name) + .ok_or_else(|| Error::NoMethodError(old_name.name.clone()))?; + (owner_module, method) + } + TargetContext::Module(module) => { + let method = module + .procs + .borrow() + .get(&old_name.name) + .cloned() + .ok_or_else(|| Error::NoMethodError(old_name.name.clone()))?; + (module.clone(), method) + } + }; + + let mut new_method = method.clone(); + new_method.sym_id = Some(new_name.clone()); + + let mut procs = owner_module.procs.borrow_mut(); + procs.insert(new_name.name.clone(), new_method); + + Ok(()) +} + pub(crate) fn op_sclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; let val = vm.getself()?; From 0b50ced121d141d0f7d53df37ec55c4cf5863943 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 10:39:56 +0900 Subject: [PATCH 049/314] Support UNDEF --- mrubyedge/examples/alias.rb | 8 +++++++- mrubyedge/src/yamrb/optable.rs | 24 +++++++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/mrubyedge/examples/alias.rb b/mrubyedge/examples/alias.rb index 74bb8a5..4b119b8 100644 --- a/mrubyedge/examples/alias.rb +++ b/mrubyedge/examples/alias.rb @@ -27,4 +27,10 @@ def greet end f = Alias2.new("World of undef") -f.say_hello \ No newline at end of file +f.say_hello + +begin + f.greet +rescue NoMethodError => e + puts e.message +end \ No newline at end of file diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index cf6536e..3e093fc 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -439,9 +439,9 @@ pub(crate) fn consume_expr( ALIAS => { op_alias(vm, operand)?; } - // UNDEF => { - // // op_undef(vm, &operand)?; - // } + UNDEF => { + op_undef(vm, &operand)?; + } SCLASS => { op_sclass(vm, operand)?; } @@ -1637,6 +1637,24 @@ pub(crate) fn op_alias(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { Ok(()) } +pub(crate) fn op_undef(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { + let a = operand.as_b()?; + let sym = vm.current_irep.syms[a as usize].clone(); + + let owner = vm.target_class.clone(); + match &owner { + TargetContext::Class(klass) => { + let mut procs = klass.procs.borrow_mut(); + procs.remove(&sym.name); + } + TargetContext::Module(module) => { + let mut procs = module.procs.borrow_mut(); + procs.remove(&sym.name); + } + }; + Ok(()) +} + pub(crate) fn op_sclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; let val = vm.getself()?; From dc8818725876a90414d5e46337995715c5fa369a Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 10:45:59 +0900 Subject: [PATCH 050/314] Tests --- mrubyedge/tests/alias.rs | 79 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 mrubyedge/tests/alias.rs diff --git a/mrubyedge/tests/alias.rs b/mrubyedge/tests/alias.rs new file mode 100644 index 0000000..ac8157c --- /dev/null +++ b/mrubyedge/tests/alias.rs @@ -0,0 +1,79 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn alias_test() { + let code = " + class Hello + def sample + 42 + end + alias alias_sample sample + end + def test_main + w = Hello.new + w.alias_sample + end + "; + let binary = mrbc_compile("alias", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_main", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 42); +} + +#[test] +fn undef_test() { + let code = " + class Hello + def sample + \"Hola\" + end + alias alias_sample sample + undef sample + end + def test_main_1 + w = Hello.new + w.alias_sample + end + def test_main_2 + w = Hello.new + begin + w.sample + rescue NoMethodError => e + e.message + end + end + "; + let binary = mrbc_compile("undef", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result: String = mrb_funcall(&mut vm, None, "test_main_1", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, "Hola"); + + let result: String = mrb_funcall(&mut vm, None, "test_main_2", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, "Method not found: sample"); +} From 60504ffceb5dc7db7637b2bf9b8ff05d6bfbc8f8 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 11:29:56 +0900 Subject: [PATCH 051/314] Add samples file: examples/array3.rb 1 000 LOADI_1 R8 (1) 1 002 LOADI_2 R9 (2) 1 004 ARRAY R1 R8 2 ; R1:array 2 008 LOADNIL R8 (nil) 2 010 MOVE R9 R1 ; R1:array 2 013 ARYCAT R8 R9 2 015 AREF R2 R8 0 ; R2:a1 2 019 AREF R3 R8 1 ; R3:a2 3 023 MOVE R9 R2 ; R2:a1 3 026 SSEND R8 :p n=1 4 030 MOVE R9 R3 ; R3:a2 4 033 SSEND R8 :p n=1 6 037 SYMBOL R8 L[0] ; foo 6 040 SYMBOL R9 L[1] ; bar 6 043 SYMBOL R10 L[2] ; buz 6 046 SYMBOL R11 L[3] ; quz 6 049 ARRAY R4 R8 4 ; R4:array2 7 053 LOADNIL R8 (nil) 7 055 MOVE R9 R4 ; R4:array2 7 058 ARYCAT R8 R9 7 060 AREF R5 R8 0 ; R5:a11 7 064 AREF R6 R8 1 ; R6:a12 7 068 MOVE R9 R8 7 071 APOST R9 2 0 7 075 MOVE R7 R9 ; R7:rest 8 078 MOVE R9 R5 ; R5:a11 8 081 SSEND R8 :p n=1 9 085 MOVE R9 R6 ; R6:a12 9 088 SSEND R8 :p n=1 10 092 MOVE R9 R7 ; R7:rest 10 095 SSEND R8 :p n=1 10 099 RETURN R8 10 101 STOP --- mrubyedge/examples/args.rb | 7 +++++++ mrubyedge/examples/args2.rb | 7 +++++++ mrubyedge/examples/array3.rb | 10 ++++++++++ 3 files changed, 24 insertions(+) create mode 100644 mrubyedge/examples/args.rb create mode 100644 mrubyedge/examples/args2.rb create mode 100644 mrubyedge/examples/array3.rb diff --git a/mrubyedge/examples/args.rb b/mrubyedge/examples/args.rb new file mode 100644 index 0000000..b177b64 --- /dev/null +++ b/mrubyedge/examples/args.rb @@ -0,0 +1,7 @@ +def splat_it(x, *args) + args.each do |arg| + p arg + end +end + +splat_it(10, 20, 30) \ No newline at end of file diff --git a/mrubyedge/examples/args2.rb b/mrubyedge/examples/args2.rb new file mode 100644 index 0000000..b978208 --- /dev/null +++ b/mrubyedge/examples/args2.rb @@ -0,0 +1,7 @@ +def destruct_it(x, foo: 42, bar: 99) + p x + p foo + p bar +end + +destruct_it(10, foo: 20, bar: 30) \ No newline at end of file diff --git a/mrubyedge/examples/array3.rb b/mrubyedge/examples/array3.rb new file mode 100644 index 0000000..1765aa6 --- /dev/null +++ b/mrubyedge/examples/array3.rb @@ -0,0 +1,10 @@ +array = [1, 2] +a1, a2 = *array +p a1 # => 1 +p a2 # => 2 + +array2 = %i(foo bar buz quz) +a11, a12, *rest = *array2 +p a11 # => :foo +p a12 # => :bar +p rest # => [:buz, :quz] \ No newline at end of file From 3fad67373f1174183cbb3681cea0bb6b2bf8e752 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 11:44:15 +0900 Subject: [PATCH 052/314] Support array operations and array destructuring assignment --- mrubyedge/src/yamrb/optable.rs | 95 ++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 10 deletions(-) diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 3e093fc..6a38682 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -367,24 +367,24 @@ pub(crate) fn consume_expr( ARRAY2 => { op_array2(vm, operand)?; } - // ARYCAT => { - // // op_arycat(vm, &operand)?; - // } + ARYCAT => { + op_arycat(vm, operand)?; + } // ARYPUSH => { // // op_arypush(vm, &operand)?; // } // ARYSPLAT => { // // op_arysplat(vm, &operand)?; // } - // AREF => { - // // op_aref(vm, &operand)?; - // } + AREF => { + op_aref(vm, operand)?; + } // ASET => { // // op_aset(vm, &operand)?; // } - // APOST => { - // // op_apost(vm, &operand)?; - // } + APOST => { + op_apost(vm, operand)?; + } // INTERN => { // // op_intern(vm, &operand)?; // } @@ -440,7 +440,7 @@ pub(crate) fn consume_expr( op_alias(vm, operand)?; } UNDEF => { - op_undef(vm, &operand)?; + op_undef(vm, operand)?; } SCLASS => { op_sclass(vm, operand)?; @@ -1309,6 +1309,81 @@ fn do_op_array(vm: &mut VM, this: usize, start: usize, n: usize) -> Result<(), E Ok(()) } +pub(crate) fn op_arycat(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { + let a = operand.as_b()? as usize; + let b = a + 1; + let val1 = vm.get_current_regs_cloned(a)?; + let val2 = vm.take_current_regs(b)?; + match (&val1.value, &val2.value) { + (RValue::Array(ary1), RValue::Array(ary2)) => { + let mut ary1 = ary1.borrow_mut(); + let ary2 = ary2.borrow(); + for item in ary2.iter() { + ary1.push(item.clone()); + } + } + (RValue::Nil, RValue::Array(ary2)) => { + let mut ary1 = Vec::new(); + let ary2 = ary2.borrow(); + for item in ary2.iter() { + ary1.push(item.clone()); + } + let val = RObject::array(ary1); + vm.current_regs()[a].replace(Rc::new(val)); + } + _ => { + unreachable!("arycat supports only array") + } + }; + Ok(()) +} + +pub(crate) fn op_aref(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { + let (a, b, c) = operand.as_bbb()?; + let array = vm.get_current_regs_cloned(b as usize)?; + let index = c as usize; + match &array.value { + RValue::Array(ary) => { + let ary = ary.borrow(); + let val = ary + .get(index) + .cloned() + .unwrap_or_else(|| Rc::new(RObject::nil())); + vm.current_regs()[a as usize].replace(val); + } + _ => { + unreachable!("aref supports only array") + } + }; + Ok(()) +} + +pub(crate) fn op_apost(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { + let (a, b, c) = operand.as_bbb()?; + if c != 0 { + return Err(Error::internal( + "apost with 3 operands is not supported yet", + )); + } + let array = vm.get_current_regs_cloned(a as usize)?; + let n = b as usize; + match &array.value { + RValue::Array(ary) => { + let mut dest = Vec::new(); + let ary = ary.borrow(); + for i in n..ary.len() { + dest.push(ary[i].clone()); + } + let newval = RObject::array(dest).to_refcount_assigned(); + vm.current_regs()[a as usize].replace(newval); + } + _ => { + unreachable!("apost supports only array") + } + }; + Ok(()) +} + pub(crate) fn op_symbol(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; let symstr = vm.current_irep.pool[b as usize].as_str().to_string(); From 37f540b75b244db10011fca6daa4af8bd0e65586 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 11:52:53 +0900 Subject: [PATCH 053/314] Support some inspecting --- mrubyedge/src/yamrb/prelude/array.rs | 26 ++++++++++++++++++++++++- mrubyedge/src/yamrb/prelude/integer.rs | 11 +++++++++++ mrubyedge/src/yamrb/prelude/mod.rs | 2 ++ mrubyedge/src/yamrb/prelude/object.rs | 4 ++++ mrubyedge/src/yamrb/prelude/string.rs | 13 +++++++++++++ mrubyedge/src/yamrb/prelude/symbol.rs | 27 ++++++++++++++++++++++++++ mrubyedge/src/yamrb/value.rs | 1 + 7 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 mrubyedge/src/yamrb/prelude/symbol.rs diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index f914d17..71ee8dc 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use crate::{ Error, yamrb::{ - helpers::{mrb_call_block, mrb_define_class_cmethod, mrb_define_cmethod}, + helpers::{self, mrb_call_block, mrb_define_class_cmethod, mrb_define_cmethod}, value::{RObject, RValue}, vm::VM, }, @@ -36,6 +36,30 @@ pub(crate) fn initialize_array(vm: &mut VM) { mrb_define_cmethod(vm, array_class.clone(), "size", Box::new(mrb_array_size)); mrb_define_cmethod(vm, array_class.clone(), "length", Box::new(mrb_array_size)); mrb_define_cmethod(vm, array_class.clone(), "pack", Box::new(mrb_array_pack)); + mrb_define_cmethod( + vm, + array_class.clone(), + "inspect", + Box::new(mrb_array_inspect), + ); +} + +pub fn mrb_array_inspect(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let array: Vec> = this.as_ref().try_into()?; + let mut s = String::new(); + s.push('['); + for (i, elem) in array.iter().enumerate() { + let elem_str: String = helpers::mrb_funcall(vm, Some(elem.clone()), "inspect", &[])? + .as_ref() + .try_into()?; + s.push_str(&elem_str); + if i + 1 < array.len() { + s.push_str(", "); + } + } + s.push(']'); + Ok(Rc::new(RObject::string(s))) } pub fn mrb_array_new(_vm: &mut VM, args: &[Rc]) -> Result, Error> { diff --git a/mrubyedge/src/yamrb/prelude/integer.rs b/mrubyedge/src/yamrb/prelude/integer.rs index 39b7147..8d54e08 100644 --- a/mrubyedge/src/yamrb/prelude/integer.rs +++ b/mrubyedge/src/yamrb/prelude/integer.rs @@ -15,6 +15,17 @@ pub(crate) fn initialize_integer(vm: &mut VM) { "times", Box::new(mrb_integer_times), ); + mrb_define_cmethod( + vm, + integer_class.clone(), + "inspect", + Box::new(mrb_integer_inspect), + ); +} + +fn mrb_integer_inspect(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: i64 = vm.getself()?.as_ref().try_into()?; + Ok(Rc::new(RObject::string(this.to_string()))) } fn mrb_integer_times(vm: &mut VM, args: &[Rc]) -> Result, Error> { diff --git a/mrubyedge/src/yamrb/prelude/mod.rs b/mrubyedge/src/yamrb/prelude/mod.rs index a2c8e18..895fda4 100644 --- a/mrubyedge/src/yamrb/prelude/mod.rs +++ b/mrubyedge/src/yamrb/prelude/mod.rs @@ -16,6 +16,7 @@ pub mod object; pub mod range; pub mod shared_memory; pub mod string; +pub mod symbol; pub mod trueclass; pub fn prelude(vm: &mut VM) { @@ -27,6 +28,7 @@ pub fn prelude(vm: &mut VM) { nilclass::initialize_nilclass(vm); trueclass::initialize_trueclass(vm); falseclass::initialize_falseclass(vm); + symbol::initialize_symbol(vm); string::initialize_string(vm); array::initialize_array(vm); hash::initialize_hash(vm); diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index e3791a4..c6c9ba3 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -92,6 +92,10 @@ pub(crate) fn initialize_object(vm: &mut VM) { ); } +pub fn mrb_self(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + vm.getself() +} + #[cfg(feature = "wasi")] pub fn mrb_kernel_puts(vm: &mut VM, args: &[Rc]) -> Result, Error> { let msg = args[0].clone(); diff --git a/mrubyedge/src/yamrb/prelude/string.rs b/mrubyedge/src/yamrb/prelude/string.rs index 95cac6a..97ebf30 100644 --- a/mrubyedge/src/yamrb/prelude/string.rs +++ b/mrubyedge/src/yamrb/prelude/string.rs @@ -4,6 +4,7 @@ use crate::{ Error, yamrb::{ helpers::{mrb_define_class_cmethod, mrb_define_cmethod}, + prelude::object, value::RObject, vm::VM, }, @@ -30,6 +31,18 @@ pub(crate) fn initialize_string(vm: &mut VM) { "length", Box::new(mrb_string_size), ); + mrb_define_cmethod( + vm, + string_class.clone(), + "inspect", + Box::new(mrb_string_inspect), + ); + mrb_define_cmethod(vm, string_class.clone(), "to_s", Box::new(object::mrb_self)); +} + +pub fn mrb_string_inspect(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + Ok(Rc::new(RObject::string(format!("\"{}\"", this)))) } pub fn mrb_string_new(_vm: &mut VM, args: &[Rc]) -> Result, Error> { diff --git a/mrubyedge/src/yamrb/prelude/symbol.rs b/mrubyedge/src/yamrb/prelude/symbol.rs new file mode 100644 index 0000000..4aa4229 --- /dev/null +++ b/mrubyedge/src/yamrb/prelude/symbol.rs @@ -0,0 +1,27 @@ +use std::rc::Rc; + +use crate::Error; +use crate::yamrb::helpers::mrb_define_cmethod; + +use crate::yamrb::{value::RObject, vm::VM}; + +pub(crate) fn initialize_symbol(vm: &mut VM) { + let symbol_class = vm.define_standard_class("Symbol"); + mrb_define_cmethod(vm, symbol_class.clone(), "to_s", Box::new(mrb_symbol_to_s)); + mrb_define_cmethod( + vm, + symbol_class.clone(), + "inspect", + Box::new(mrb_symbol_inspect), + ); +} + +fn mrb_symbol_inspect(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + Ok(Rc::new(RObject::string(format!(":{}", this)))) +} + +fn mrb_symbol_to_s(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let symbol: String = vm.getself()?.as_ref().try_into()?; + Ok(Rc::new(RObject::string(symbol))) +} diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 119ca16..196ba0c 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -525,6 +525,7 @@ impl TryFrom<&RObject> for String { fn try_from(value: &RObject) -> Result { match &value.value { RValue::String(s) => Ok(String::from_utf8_lossy(&s.borrow()).to_string()), + RValue::Symbol(sym) => Ok(sym.name.clone()), v => Ok(format!("{:?}", v)), } } From 5cd8a262252089ce8646e986f9f3a9ba1497eb72 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 12:01:35 +0900 Subject: [PATCH 054/314] Add test, fix neg integer --- mrubyedge/src/yamrb/value.rs | 2 +- mrubyedge/tests/assign.rs | 58 ++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 mrubyedge/tests/assign.rs diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 196ba0c..0263784 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -142,7 +142,7 @@ impl RObject { } else if n <= (i32::MIN as i64) { i64::MIN as u64 } else { - n as u64 * 2 + 1 + (n * 2) as u64 + 1 }; RObject { diff --git a/mrubyedge/tests/assign.rs b/mrubyedge/tests/assign.rs new file mode 100644 index 0000000..a9f2932 --- /dev/null +++ b/mrubyedge/tests/assign.rs @@ -0,0 +1,58 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn assign_test() { + let code = " + def test_main + ary = [10, 20, 30] + a, b, c = *ary + a + b + c + end + "; + let binary = mrbc_compile("assign1", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_main", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 60); +} + +#[test] +fn assign_post_test() { + let code = " + def test_main + ary = [10, 20, 30, 40] + a, b, *rest = *ary + ans = a + b + p rest + rest.each do |v| + ans -= v + end + ans + end + "; + let binary = mrbc_compile_debug("assign2", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_main", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, -40); +} From 73dc31e244ca269b4ec3537f4badf1d5e4cb5ca3 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 21:31:51 +0900 Subject: [PATCH 055/314] Add general documentation to lib.rs --- mrubyedge-cli/src/lib.rs | 155 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 155 insertions(+) diff --git a/mrubyedge-cli/src/lib.rs b/mrubyedge-cli/src/lib.rs index fa5f3f2..2b61a8f 100644 --- a/mrubyedge-cli/src/lib.rs +++ b/mrubyedge-cli/src/lib.rs @@ -1,3 +1,158 @@ +//! # mrubyedge-cli +//! +//! Command-line interface for mruby/edge - a lightweight, WebAssembly-focused mruby VM implementation. +//! +//! ## About mruby/edge +//! +//! mruby/edge is an mruby-compatible virtual machine implementation written in Rust, +//! specifically designed for WebAssembly environments. It aims to provide: +//! +//! - **WebAssembly-first design**: Optimized for running Ruby code in browsers and edge computing environments +//! - **Lightweight runtime**: Minimal footprint and binary size suitable for constrained environments +//! - **mruby compatibility**: Executes mruby bytecode (`.mrb` files) and Ruby source code +//! - **Rust safety**: Built with Rust for memory safety and reliability +//! +//! ## Installation +//! +//! Install mrubyedge-cli using cargo: +//! +//! ```sh +//! cargo install mrubyedge-cli +//! ``` +//! +//! Or build from source: +//! +//! ```sh +//! git clone https://github.com/mrubyedge/mrubyedge.git +//! cd mrubyedge +//! cargo build --release -p mrubyedge-cli +//! ``` +//! +//! ## Getting Started +//! +//! Create a simple Ruby script `hello.rb`: +//! +//! ```ruby +//! puts "Hello from mruby/edge!" +//! puts RUBY_ENGINE +//! ``` +//! +//! Run it with mrbedge: +//! +//! ```sh +//! mrbedge hello.rb +//! # or explicitly +//! mrbedge run hello.rb +//! ``` +//! +//! ## Main Features +//! +//! ### `run` - Execute Ruby Scripts +//! +//! The `run` subcommand executes Ruby source files (`.rb`) or compiled mruby bytecode (`.mrb`). +//! +//! **Usage:** +//! ```sh +//! mrbedge run +//! # or simply +//! mrbedge +//! ``` +//! +//! **Examples:** +//! ```sh +//! # Run Ruby source +//! mrbedge run script.rb +//! +//! # Run compiled bytecode +//! mrbedge run script.mrb +//! ``` +//! +//! ### `compile-mrb` - Compile Ruby to Bytecode +//! +//! Compiles Ruby source code into mruby bytecode format for faster loading and distribution. +//! +//! **Usage:** +//! ```sh +//! mrbedge compile-mrb -o +//! ``` +//! +//! **Examples:** +//! ```sh +//! # Compile a single file +//! mrbedge compile-mrb app.rb -o app.mrb +//! +//! # Run the compiled bytecode +//! mrbedge run app.mrb +//! ``` +//! +//! ### `wasm` - Generate WebAssembly Modules +//! +//! Compiles Ruby code directly into a standalone WebAssembly module that can run in browsers +//! or any WebAssembly runtime. +//! +//! **Usage:** +//! ```sh +//! mrbedge wasm -o +//! ``` +//! +//! **Examples:** +//! ```sh +//! # Generate WebAssembly module +//! mrbedge wasm app.rb -o app.wasm +//! +//! # Use in browser or Node.js +//! # The generated WASM can be loaded and executed in any WASM runtime +//! ``` +//! +//! **Use Cases:** +//! - Serverless edge computing +//! - Browser-based applications +//! - Microservices with minimal overhead +//! - Cross-platform portable executables +//! +//! **WASI Support:** +//! +//! The `wasm` command can generate both WASI-enabled and non-WASI WebAssembly binaries. +//! By default, it produces WASI-enabled modules. To disable WASI support, use the `--no-wasi` flag. +//! +//! **Import/Export Functions:** +//! +//! You can specify WebAssembly function imports and exports using RBS (Ruby Signature) files. +//! Place RBS files with specific naming conventions alongside your Ruby script: +//! +//! For a Ruby script named `foo.rb`: +//! - **`foo.import.rbs`**: Defines external functions to import from the WebAssembly host +//! - **`foo.export.rbs`**: Defines Ruby functions to export as WebAssembly functions +//! +//! **Example:** +//! ```ruby +//! # app.rb +//! def calculate(x, y) +//! x + y +//! end +//! ``` +//! +//! ```rbs +//! # app.export.rbs +//! def calculate: (Integer, Integer) -> Integer +//! ``` +//! +//! ```rbs +//! # app.import.rbs +//! def external_log: (String) -> void +//! ``` +//! +//! The generated WebAssembly module will expose `Kernel#calculate` and can call `Kernel#external_log` +//! from the host environment. +//! +//! NOTE: Inlined RBS for imports and exports annotations will be supported in future releases. +//! +//! ## Additional Resources +//! +//! - [GitHub Repository](https://github.com/mrubyedge/mrubyedge) +//! - [API Documentation](https://docs.rs/mrubyedge-cli) +//! - [Core VM Documentation](https://docs.rs/mrubyedge) + pub mod rbs_parser; pub mod subcommands; pub mod template; From e8ef34d62f149a0fc0d5dd20cc4cff21123371e8 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 21:32:52 +0900 Subject: [PATCH 056/314] Bump versions --- Cargo.lock | 14 +++++++------- mrubyedge-cli/Cargo.toml | 2 +- mrubyedge/Cargo.toml | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3134603..db22253 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -572,33 +572,33 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef054892d1144fe548e1b12062affc992aebc393182e1a1c0b5f5f82df311c47" dependencies = [ - "criterion", "getrandom", - "mec-mrbc-sys", "plain", "simple_endian", ] [[package]] name = "mrubyedge" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef054892d1144fe548e1b12062affc992aebc393182e1a1c0b5f5f82df311c47" +version = "1.0.9" dependencies = [ + "criterion", "getrandom", + "mec-mrbc-sys", "plain", "simple_endian", ] [[package]] name = "mrubyedge-cli" -version = "1.0.8" +version = "1.0.9" dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.0.8", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index fdede55..3eaa866 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.0.8" +version = "1.0.9" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 0d693b9..4c44ba3 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.0.8" +version = "1.0.9" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From 290ba8ffd525f41ab6d0ffbf551cf03d43f0c128 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 21:33:39 +0900 Subject: [PATCH 057/314] Dep to 1.0.9 --- Cargo.lock | 12 ++++++------ mrubyedge-cli/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db22253..25e05ee 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -571,11 +571,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef054892d1144fe548e1b12062affc992aebc393182e1a1c0b5f5f82df311c47" +version = "1.0.9" dependencies = [ + "criterion", "getrandom", + "mec-mrbc-sys", "plain", "simple_endian", ] @@ -583,10 +583,10 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff9fa8f4061050e95db2b4b7ed71e5bf9a5319b95a72c7437dc2e25d76a40112" dependencies = [ - "criterion", "getrandom", - "mec-mrbc-sys", "plain", "simple_endian", ] @@ -598,7 +598,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.8", + "mrubyedge 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 3eaa866..249fc02 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } mruby-compiler2-sys = "0.1.1" -mrubyedge = { version = "1.0.8" } +mrubyedge = { version = "1.0.9" } rand = "0.8.5" nom = "7.1.3" askama = "0.12.1" From 22342de58e43154fc1f5cc758818f46faa951816 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 21:41:12 +0900 Subject: [PATCH 058/314] Add regexp feature --- Cargo.lock | 1 + mrubyedge/Cargo.toml | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 25e05ee..266d653 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -577,6 +577,7 @@ dependencies = [ "getrandom", "mec-mrbc-sys", "plain", + "regex", "simple_endian", ] diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 4c44ba3..9046cac 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -11,6 +11,9 @@ license = "BSD-3-Clause" [dependencies] getrandom = { version = "0.2.14", optional = true } plain = "0.2.3" +regex = { version = "1.12.2", default-features = false, features = [ + "std", +], optional = true } simple_endian = "0.3" [dev-dependencies] @@ -24,4 +27,5 @@ harness = false [features] default = ["wasi"] wasi = ["dep:getrandom"] +mruby-regexp = ["dep:regex"] no-wasi = [] From 6dba6f8414009d9e8d418db7022d5b677850639a Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 22:03:02 +0900 Subject: [PATCH 059/314] Initialize regexp instance --- mrubyedge/src/error.rs | 3 ++ mrubyedge/src/yamrb/optable.rs | 10 +++++ mrubyedge/src/yamrb/prelude/exception.rs | 2 + mrubyedge/src/yamrb/prelude/mod.rs | 6 +++ mrubyedge/src/yamrb/prelude/regexp.rs | 56 ++++++++++++++++++++++++ mrubyedge/src/yamrb/value.rs | 21 ++++++--- mrubyedge/src/yamrb/vm.rs | 1 - 7 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 mrubyedge/src/yamrb/prelude/regexp.rs diff --git a/mrubyedge/src/error.rs b/mrubyedge/src/error.rs index 28faf9b..f19bca5 100644 --- a/mrubyedge/src/error.rs +++ b/mrubyedge/src/error.rs @@ -11,6 +11,7 @@ pub enum Error { Internal(String), InvalidOpCode, RuntimeError(String), + ArgumentError(String), TypeMismatch, NoMethodError(String), NameError(String), @@ -35,6 +36,7 @@ impl Error { Error::Internal(msg) => format!("[Internal Error] {}", msg.clone()), Error::InvalidOpCode => "Invalid opcode".to_string(), Error::RuntimeError(msg) => msg.clone(), + Error::ArgumentError(msg) => format!("Invalid argument: {}", msg), Error::TypeMismatch => "Type mismatch".to_string(), Error::NoMethodError(msg) => format!("Method not found: {}", msg), Error::NameError(msg) => format!("Cannot found name: {}", msg), @@ -48,6 +50,7 @@ impl Error { | (Error::Internal(_), "InternalError") | (Error::InvalidOpCode, "StandardError") | (Error::RuntimeError(_), "RuntimeError") + | (Error::ArgumentError(_), "ArgumentError") | (Error::TypeMismatch, "StandardError") | (Error::NoMethodError(_), "NoMethodError") | (Error::NameError(_), "NameError") diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 6a38682..b7f521f 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -620,6 +620,12 @@ pub(crate) fn op_getiv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { .get(&vm.current_irep.syms[b as usize].name) .ok_or_else(|| Error::internal(format!("symbol not found {}", b)))? .clone(), + RValue::Data(data) => data + .ivar + .borrow() + .get(&vm.current_irep.syms[b as usize].name) + .ok_or_else(|| Error::internal(format!("symbol not found {}", b)))? + .clone(), _ => unreachable!("getiv must be called on instance"), }; vm.current_regs()[a as usize].replace(ivar); @@ -635,6 +641,10 @@ pub(crate) fn op_setiv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let mut ivar = ins.ivar.borrow_mut(); ivar.insert(vm.current_irep.syms[b as usize].name.clone(), val) } + RValue::Data(data) => { + let mut ivar = data.ivar.borrow_mut(); + ivar.insert(vm.current_irep.syms[b as usize].name.clone(), val) + } _ => unreachable!("setiv must be called on instance"), }; Ok(()) diff --git a/mrubyedge/src/yamrb/prelude/exception.rs b/mrubyedge/src/yamrb/prelude/exception.rs index a0678f8..c7295c0 100644 --- a/mrubyedge/src/yamrb/prelude/exception.rs +++ b/mrubyedge/src/yamrb/prelude/exception.rs @@ -13,6 +13,8 @@ pub(crate) fn initialize_exception(vm: &mut VM) { let std_exp_class: Rc = vm.define_standard_class_under("StandardError", exp_class.clone()); let _ = vm.define_standard_class_under("RuntimeError", std_exp_class.clone()); + let _ = vm.define_standard_class_under("TypeError", std_exp_class.clone()); + let _ = vm.define_standard_class_under("ArgumentError", std_exp_class.clone()); let _ = vm.define_standard_class_under("NoMemoryError", exp_class.clone()); let _ = vm.define_standard_class_under("ScriptError", exp_class.clone()); let _ = vm.define_standard_class_under("LoadError", exp_class.clone()); diff --git a/mrubyedge/src/yamrb/prelude/mod.rs b/mrubyedge/src/yamrb/prelude/mod.rs index 895fda4..707b730 100644 --- a/mrubyedge/src/yamrb/prelude/mod.rs +++ b/mrubyedge/src/yamrb/prelude/mod.rs @@ -19,6 +19,9 @@ pub mod string; pub mod symbol; pub mod trueclass; +#[cfg(feature = "mruby-regexp")] +pub mod regexp; + pub fn prelude(vm: &mut VM) { object::initialize_object(vm); exception::initialize_exception(vm); @@ -34,4 +37,7 @@ pub fn prelude(vm: &mut VM) { hash::initialize_hash(vm); range::initialize_range(vm); shared_memory::initialize_shared_memory(vm); + + #[cfg(feature = "mruby-regexp")] + regexp::initialize_regexp(vm); } diff --git a/mrubyedge/src/yamrb/prelude/regexp.rs b/mrubyedge/src/yamrb/prelude/regexp.rs new file mode 100644 index 0000000..2518f84 --- /dev/null +++ b/mrubyedge/src/yamrb/prelude/regexp.rs @@ -0,0 +1,56 @@ +#[cfg(feature = "mruby-regexp")] +use std::rc::Rc; +use std::{ + cell::{Cell, RefCell}, + collections::HashMap, +}; + +use crate::{ + Error, + yamrb::{ + helpers::mrb_define_class_cmethod, + value::{RData, RObject, RValue}, + vm::VM, + }, +}; + +pub(crate) fn initialize_regexp(vm: &mut VM) { + let regexp_class = vm.define_standard_class("Regexp"); + + mrb_define_class_cmethod(vm, regexp_class, "new", Box::new(mrb_regexp_new)); +} + +pub struct Regexp { + pattern: String, +} + +pub fn mrb_regexp_new(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let pattern_obj = args[0].clone(); + match &pattern_obj.value { + RValue::String(pattern) => { + // For simplicity, we only support literal patterns without options. + let pattern_str = pattern.clone().borrow().to_owned(); + let regexp = Regexp { + pattern: String::from_utf8(pattern_str).map_err(|e| { + Error::RuntimeError(format!("Invalid regexp expression: {:?}", e)) + })?, + }; + let regexp_data = Rc::new(RData { + class: vm.get_class_by_name("Regexp"), + ivar: RefCell::new(HashMap::new()), + data: RefCell::new(Some(Rc::new(Box::new(regexp) as Box))), + ref_count: 1, + }); + Ok(RObject { + tt: crate::yamrb::value::RType::Data, + value: RValue::Data(regexp_data), + object_id: Cell::new(0), + singleton_class: RefCell::new(None), + } + .to_refcount_assigned()) + } + _ => Err(Error::RuntimeError( + "Regexp.new requires a string pattern".to_string(), + )), + } +} diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 0263784..e8ba479 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -1,3 +1,4 @@ +use std::any::Any; use std::cell::Cell; use std::collections::HashSet; use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc}; @@ -47,7 +48,7 @@ pub enum RValue { String(RefCell>), Range(Rc, Rc, bool), SharedMemory(Rc>), - Data, + Data(Rc), Exception(Rc), Nil, } @@ -243,7 +244,6 @@ impl RObject { value: RValue::Instance(RInstance { class: c, ivar: RefCell::new(HashMap::new()), - data: Vec::new(), ref_count: 1, }), object_id: (u64::MAX).into(), @@ -361,7 +361,7 @@ impl RObject { RValue::String(_) => vm.get_class_by_name("String"), RValue::Range(_, _, _) => vm.get_class_by_name("Range"), RValue::SharedMemory(_) => vm.get_class_by_name("SharedMemory"), - RValue::Data => todo!("return ...? class"), + RValue::Data(_) => todo!("return ...? class"), RValue::Exception(e) => e.class.clone(), RValue::Nil => vm.get_class_by_name("NilClass"), } @@ -752,12 +752,22 @@ impl std::ops::Deref for RClass { } } -/// Backing storage for Ruby object instances (instance variables and data). +/// Backing storage for Ruby object instances (instance variables). #[derive(Debug, Clone)] pub struct RInstance { pub class: Rc, pub ivar: RefCell>>, - pub data: Vec, + pub ref_count: usize, +} + +type RDataContainer = Box; + +/// Backing storage for Ruby object instances (instance variables w/ data). +#[derive(Debug, Clone)] +pub struct RData { + pub class: Rc, + pub ivar: RefCell>>, + pub data: RefCell>>, pub ref_count: usize, } @@ -829,6 +839,7 @@ impl RClass { Error::Internal(_) => vm.get_class_by_name("InternalError"), Error::InvalidOpCode => vm.get_class_by_name("LoadError"), Error::RuntimeError(_) => vm.get_class_by_name("RuntimeError"), + Error::ArgumentError(_) => vm.get_class_by_name("ArgumentError"), Error::TypeMismatch => vm.get_class_by_name("LoadError"), Error::NoMethodError(_) => vm.get_class_by_name("NoMethodError"), Error::NameError(_) => vm.get_class_by_name("NameError"), diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 3225fe1..f9b77b4 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -159,7 +159,6 @@ impl VM { value: RValue::Instance(RInstance { class, ivar: RefCell::new(HashMap::new()), - data: Vec::new(), ref_count: 1, }), object_id: 0.into(), From 8e84a827688af56373c5618413f1db5f8e86f91e Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 22:30:56 +0900 Subject: [PATCH 060/314] Support so very basic match --- mrubyedge/examples/regexp.rb | 10 +++ mrubyedge/src/yamrb/prelude/regexp.rs | 108 +++++++++++++++++++++++++- mrubyedge/src/yamrb/value.rs | 2 +- mrubyedge/src/yamrb/vm.rs | 12 ++- 4 files changed, 125 insertions(+), 7 deletions(-) create mode 100644 mrubyedge/examples/regexp.rb diff --git a/mrubyedge/examples/regexp.rb b/mrubyedge/examples/regexp.rb new file mode 100644 index 0000000..f98b725 --- /dev/null +++ b/mrubyedge/examples/regexp.rb @@ -0,0 +1,10 @@ +re = /ruby/ +target = "mrubyedge" +if m = (target =~ re) + puts "matched: #{m}" +end + +target2 = "micropython" +if re !~ target2 + puts "not matched" +end \ No newline at end of file diff --git a/mrubyedge/src/yamrb/prelude/regexp.rs b/mrubyedge/src/yamrb/prelude/regexp.rs index 2518f84..2e00b08 100644 --- a/mrubyedge/src/yamrb/prelude/regexp.rs +++ b/mrubyedge/src/yamrb/prelude/regexp.rs @@ -5,10 +5,12 @@ use std::{ collections::HashMap, }; +use regex::Regex; + use crate::{ Error, yamrb::{ - helpers::mrb_define_class_cmethod, + helpers::{mrb_define_class_cmethod, mrb_define_cmethod, mrb_funcall}, value::{RData, RObject, RValue}, vm::VM, }, @@ -17,20 +19,74 @@ use crate::{ pub(crate) fn initialize_regexp(vm: &mut VM) { let regexp_class = vm.define_standard_class("Regexp"); - mrb_define_class_cmethod(vm, regexp_class, "new", Box::new(mrb_regexp_new)); + mrb_define_class_cmethod(vm, regexp_class.clone(), "new", Box::new(mrb_regexp_new)); + mrb_define_class_cmethod( + vm, + regexp_class.clone(), + "compile", + Box::new(mrb_regexp_new), + ); + + mrb_define_cmethod(vm, regexp_class.clone(), "=~", Box::new(mrb_regexp_match)); + mrb_define_cmethod( + vm, + regexp_class.clone(), + "!~", + Box::new(mrb_regexp_not_match), + ); + + // Additional counterpart Regexp methods to String + let string_class = vm.get_class_by_name("String"); + mrb_define_cmethod( + vm, + string_class.clone(), + "=~", + Box::new(mrb_string_regexp_match), + ); + mrb_define_cmethod( + vm, + string_class.clone(), + "!~", + Box::new(mrb_string_regexp_not_match), + ); } -pub struct Regexp { +pub struct RRegexp { pattern: String, } +fn get_regexp_from_object(obj: &Rc) -> Result { + let pattern_str: String = match &obj.value { + RValue::Data(data) => { + let borrow = data.data.borrow(); + let any_ref = borrow + .as_ref() + .ok_or_else(|| Error::RuntimeError("Invalid Regexp data".to_string()))?; + let regexp = any_ref + .downcast_ref::() + .ok_or_else(|| Error::RuntimeError("Invalid Regexp data".to_string()))?; + regexp.pattern.clone() + } + _ => { + return Err(Error::RuntimeError( + "Regexp#=~ must be called on a Regexp".to_string(), + )); + } + }; + + let pattern = Regex::new(&pattern_str).map_err(|e| { + Error::RuntimeError(format!("Invalid regexp pattern in Regexp#=~: {:?}", e)) + })?; + Ok(pattern) +} + pub fn mrb_regexp_new(vm: &mut VM, args: &[Rc]) -> Result, Error> { let pattern_obj = args[0].clone(); match &pattern_obj.value { RValue::String(pattern) => { // For simplicity, we only support literal patterns without options. let pattern_str = pattern.clone().borrow().to_owned(); - let regexp = Regexp { + let regexp = RRegexp { pattern: String::from_utf8(pattern_str).map_err(|e| { Error::RuntimeError(format!("Invalid regexp expression: {:?}", e)) })?, @@ -54,3 +110,47 @@ pub fn mrb_regexp_new(vm: &mut VM, args: &[Rc]) -> Result, )), } } + +fn mrb_regexp_match(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let regexp_obj = vm.getself()?; + let target_obj = args[0].clone(); + + let regexp = get_regexp_from_object(®exp_obj)?; + + let target_str = match &target_obj.value { + RValue::String(s) => s.clone().borrow().to_owned(), + _ => { + return Err(Error::RuntimeError( + "Regexp#=~ requires a string argument".to_string(), + )); + } + }; + + let haystack = String::from_utf8(target_str).map_err(|e| { + Error::RuntimeError(format!("Invalid string argument for Regexp#=~: {:?}", e)) + })?; + + match regexp.find(&haystack) { + Some(matched) => Ok(RObject::integer(matched.start() as i64).to_refcount_assigned()), + None => Ok(RObject::nil().to_refcount_assigned()), + } +} + +fn mrb_regexp_not_match(vm: &mut VM, args: &[Rc]) -> Result, Error> { + match mrb_regexp_match(vm, args)? { + res if res.is_nil() => Ok(RObject::boolean(true).to_refcount_assigned()), + _ => Ok(RObject::boolean(false).to_refcount_assigned()), + } +} + +fn mrb_string_regexp_match(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let string_obj = vm.getself()?; + let regexp_obj = args[0].clone(); + mrb_funcall(vm, Some(regexp_obj), "=~", &[string_obj]) +} + +fn mrb_string_regexp_not_match(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let string_obj = vm.getself()?; + let regexp_obj = args[0].clone(); + mrb_funcall(vm, Some(regexp_obj), "!~", &[string_obj]) +} diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index e8ba479..fa039ce 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -361,7 +361,7 @@ impl RObject { RValue::String(_) => vm.get_class_by_name("String"), RValue::Range(_, _, _) => vm.get_class_by_name("Range"), RValue::SharedMemory(_) => vm.get_class_by_name("SharedMemory"), - RValue::Data(_) => todo!("return ...? class"), + RValue::Data(d) => d.class.clone(), RValue::Exception(e) => e.class.clone(), RValue::Nil => vm.get_class_by_name("NilClass"), } diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index f9b77b4..ddc03a1 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -315,7 +315,11 @@ impl VM { }; let class = Rc::new(RClass::new(name, Some(superclass), parent_module)); let object = RObject::class(class.clone(), self); - self.consts.insert(name.to_string(), object); + self.consts.insert(name.to_string(), object.clone()); + self.object_class + .consts + .borrow_mut() + .insert(name.to_string(), object); class } @@ -327,7 +331,11 @@ impl VM { module.parent.replace(Some(parent)); } let object = RObject::module(module.clone()).to_refcount_assigned(); - self.consts.insert(name.to_string(), object); + self.consts.insert(name.to_string(), object.clone()); + self.object_class + .consts + .borrow_mut() + .insert(name.to_string(), object); module } From 21d9385b192c8150271d49a0901dcd368775eb2c Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 22:55:25 +0900 Subject: [PATCH 061/314] Add... complex example --- mrubyedge/examples/regexp.rb | 10 ++ mrubyedge/src/yamrb/optable.rs | 11 ++ mrubyedge/src/yamrb/prelude/regexp.rs | 144 ++++++++++++++++++++++++-- 3 files changed, 156 insertions(+), 9 deletions(-) diff --git a/mrubyedge/examples/regexp.rb b/mrubyedge/examples/regexp.rb index f98b725..eae2330 100644 --- a/mrubyedge/examples/regexp.rb +++ b/mrubyedge/examples/regexp.rb @@ -7,4 +7,14 @@ target2 = "micropython" if re !~ target2 puts "not matched" +end + +re3 = /(m?ruby).*?(m?ruby).*?(m?ruby(?:ists)?)/ +target3 = "mruby/edge is a mruby for embedded systems, built for rubyists." +matched = re3.match(target3) +if matched + puts "matched: #{matched[0]}" + puts "matched: #{matched[1]}" + puts "matched: #{matched[2]}" + puts "matched: #{matched[3]}" end \ No newline at end of file diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index b7f521f..510b3a8 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1431,6 +1431,17 @@ pub(crate) fn op_strcat(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { s1.push(*c); } } + (RValue::String(s1), _) => { + let mut s1 = s1.borrow_mut(); + let s2 = mrb_funcall(vm, Some(val2.clone()), "to_s", &[])?; + let s2 = match &s2.value { + RValue::String(s) => s.borrow(), + _ => unreachable!("to_s must return string"), + }; + for c in s2.to_vec().iter() { + s1.push(*c); + } + } _ => { unreachable!("strcat supports only string") } diff --git a/mrubyedge/src/yamrb/prelude/regexp.rs b/mrubyedge/src/yamrb/prelude/regexp.rs index 2e00b08..9c3ae9d 100644 --- a/mrubyedge/src/yamrb/prelude/regexp.rs +++ b/mrubyedge/src/yamrb/prelude/regexp.rs @@ -27,12 +27,31 @@ pub(crate) fn initialize_regexp(vm: &mut VM) { Box::new(mrb_regexp_new), ); - mrb_define_cmethod(vm, regexp_class.clone(), "=~", Box::new(mrb_regexp_match)); + mrb_define_cmethod( + vm, + regexp_class.clone(), + "=~", + Box::new(mrb_regexp_match_tilda), + ); mrb_define_cmethod( vm, regexp_class.clone(), "!~", - Box::new(mrb_regexp_not_match), + Box::new(mrb_regexp_not_match_tilda), + ); + mrb_define_cmethod( + vm, + regexp_class.clone(), + "match", + Box::new(mrb_regexp_match), + ); + + let matchdata_class = vm.define_standard_class("MatchData"); + mrb_define_cmethod( + vm, + matchdata_class.clone(), + "[]", + Box::new(mrb_matchdata_index), ); // Additional counterpart Regexp methods to String @@ -41,13 +60,13 @@ pub(crate) fn initialize_regexp(vm: &mut VM) { vm, string_class.clone(), "=~", - Box::new(mrb_string_regexp_match), + Box::new(mrb_string_regexp_match_tilda), ); mrb_define_cmethod( vm, string_class.clone(), "!~", - Box::new(mrb_string_regexp_not_match), + Box::new(mrb_string_regexp_not_match_tilda), ); } @@ -55,6 +74,11 @@ pub struct RRegexp { pattern: String, } +pub struct RMatchData { + captures: Vec<(usize, usize)>, + haystack: String, +} + fn get_regexp_from_object(obj: &Rc) -> Result { let pattern_str: String = match &obj.value { RValue::Data(data) => { @@ -111,7 +135,7 @@ pub fn mrb_regexp_new(vm: &mut VM, args: &[Rc]) -> Result, } } -fn mrb_regexp_match(vm: &mut VM, args: &[Rc]) -> Result, Error> { +fn mrb_regexp_match_tilda(vm: &mut VM, args: &[Rc]) -> Result, Error> { let regexp_obj = vm.getself()?; let target_obj = args[0].clone(); @@ -136,20 +160,122 @@ fn mrb_regexp_match(vm: &mut VM, args: &[Rc]) -> Result, Er } } -fn mrb_regexp_not_match(vm: &mut VM, args: &[Rc]) -> Result, Error> { - match mrb_regexp_match(vm, args)? { +fn mrb_regexp_not_match_tilda(vm: &mut VM, args: &[Rc]) -> Result, Error> { + match mrb_regexp_match_tilda(vm, args)? { res if res.is_nil() => Ok(RObject::boolean(true).to_refcount_assigned()), _ => Ok(RObject::boolean(false).to_refcount_assigned()), } } -fn mrb_string_regexp_match(vm: &mut VM, args: &[Rc]) -> Result, Error> { +fn mrb_regexp_match(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let regexp_obj = vm.getself()?; + let target_obj = args[0].clone(); + + let regexp = get_regexp_from_object(®exp_obj)?; + + let target_str = match &target_obj.value { + RValue::String(s) => s.clone().borrow().to_owned(), + _ => { + return Err(Error::RuntimeError( + "Regexp#match requires a string argument".to_string(), + )); + } + }; + + let haystack = String::from_utf8(target_str).map_err(|e| { + Error::RuntimeError(format!("Invalid string argument for Regexp#match: {:?}", e)) + })?; + + match regexp.captures(&haystack) { + Some(captures) => { + let mut caps_vec = Vec::new(); + for cap in captures.iter() { + if let Some(m) = cap { + caps_vec.push((m.start(), m.end())); + } else { + caps_vec.push((usize::MAX, usize::MAX)); // Indicate no match + } + } + let matchdata = RMatchData { + captures: caps_vec, + haystack, + }; + let matchdata_data = Rc::new(RData { + class: vm.get_class_by_name("MatchData"), + ivar: RefCell::new(HashMap::new()), + data: RefCell::new(Some(Rc::new(Box::new(matchdata) as Box))), + ref_count: 1, + }); + Ok(RObject { + tt: crate::yamrb::value::RType::Data, + value: RValue::Data(matchdata_data), + object_id: Cell::new(0), + singleton_class: RefCell::new(None), + } + .to_refcount_assigned()) + } + None => Ok(RObject::nil().to_refcount_assigned()), + } +} + +fn mrb_matchdata_index(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let matchdata_obj = vm.getself()?; + let index_obj = args[0].clone(); + + let index = match &index_obj.value { + RValue::Integer(i) => *i as isize, + _ => { + return Err(Error::ArgumentError( + "MatchData#[] requires an integer index".to_string(), + )); + } + }; + + let matchdata = match &matchdata_obj.value { + RValue::Data(data) => { + let borrow = data.data.borrow(); + let any_ref = borrow + .as_ref() + .ok_or_else(|| Error::RuntimeError("Invalid MatchData data".to_string()))?; + any_ref.clone() + } + _ => { + return Err(Error::RuntimeError( + "MatchData#[] must be called on a MatchData".to_string(), + )); + } + }; + let matchdata = matchdata + .downcast_ref::() + .ok_or_else(|| Error::RuntimeError("Invalid MatchData data".to_string()))?; + + let cap_index = if index < 0 { + (matchdata.captures.len() as isize + index) as usize + } else { + index as usize + }; + + if cap_index >= matchdata.captures.len() { + return Ok(RObject::nil().to_refcount_assigned()); + } + let (start, end) = matchdata.captures[cap_index]; + if start == usize::MAX && end == usize::MAX { + return Ok(RObject::nil().to_refcount_assigned()); + } + let matched_str = &matchdata.haystack[start..end]; + Ok(RObject::string(matched_str.to_string()).to_refcount_assigned()) +} + +fn mrb_string_regexp_match_tilda(vm: &mut VM, args: &[Rc]) -> Result, Error> { let string_obj = vm.getself()?; let regexp_obj = args[0].clone(); mrb_funcall(vm, Some(regexp_obj), "=~", &[string_obj]) } -fn mrb_string_regexp_not_match(vm: &mut VM, args: &[Rc]) -> Result, Error> { +fn mrb_string_regexp_not_match_tilda( + vm: &mut VM, + args: &[Rc], +) -> Result, Error> { let string_obj = vm.getself()?; let regexp_obj = args[0].clone(); mrb_funcall(vm, Some(regexp_obj), "!~", &[string_obj]) From f5d0d4cc979498b30ec8a0988e960ddfe7a556b8 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 22:58:24 +0900 Subject: [PATCH 062/314] Some inspect --- mrubyedge/examples/regexp.rb | 4 +++- mrubyedge/src/yamrb/prelude/regexp.rs | 29 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/mrubyedge/examples/regexp.rb b/mrubyedge/examples/regexp.rb index eae2330..2c47fa9 100644 --- a/mrubyedge/examples/regexp.rb +++ b/mrubyedge/examples/regexp.rb @@ -8,6 +8,7 @@ if re !~ target2 puts "not matched" end +p re re3 = /(m?ruby).*?(m?ruby).*?(m?ruby(?:ists)?)/ target3 = "mruby/edge is a mruby for embedded systems, built for rubyists." @@ -17,4 +18,5 @@ puts "matched: #{matched[1]}" puts "matched: #{matched[2]}" puts "matched: #{matched[3]}" -end \ No newline at end of file +end +p matched \ No newline at end of file diff --git a/mrubyedge/src/yamrb/prelude/regexp.rs b/mrubyedge/src/yamrb/prelude/regexp.rs index 9c3ae9d..1a83e18 100644 --- a/mrubyedge/src/yamrb/prelude/regexp.rs +++ b/mrubyedge/src/yamrb/prelude/regexp.rs @@ -45,6 +45,12 @@ pub(crate) fn initialize_regexp(vm: &mut VM) { "match", Box::new(mrb_regexp_match), ); + mrb_define_cmethod( + vm, + regexp_class.clone(), + "inspect", + Box::new(mrb_regexp_inspect), + ); let matchdata_class = vm.define_standard_class("MatchData"); mrb_define_cmethod( @@ -218,6 +224,29 @@ fn mrb_regexp_match(vm: &mut VM, args: &[Rc]) -> Result, Er } } +fn mrb_regexp_inspect(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let regexp_obj = vm.getself()?; + let pattern_str: String = match ®exp_obj.value { + RValue::Data(data) => { + let borrow = data.data.borrow(); + let any_ref = borrow + .as_ref() + .ok_or_else(|| Error::RuntimeError("Invalid Regexp data".to_string()))?; + let regexp = any_ref + .downcast_ref::() + .ok_or_else(|| Error::RuntimeError("Invalid Regexp data".to_string()))?; + regexp.pattern.clone() + } + _ => { + return Err(Error::RuntimeError( + "Regexp#inspect must be called on a Regexp".to_string(), + )); + } + }; + let inspect_str = format!("/{}/", pattern_str); + Ok(RObject::string(inspect_str).to_refcount_assigned()) +} + fn mrb_matchdata_index(vm: &mut VM, args: &[Rc]) -> Result, Error> { let matchdata_obj = vm.getself()?; let index_obj = args[0].clone(); From ca91e72fb287a7014f64e3640938d88ae5ce6dae Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 23:09:22 +0900 Subject: [PATCH 063/314] Tests --- .github/workflows/mrubyedge.yml | 4 +- mrubyedge/src/yamrb/prelude/regexp.rs | 2 +- mrubyedge/tests/regexp.rs | 148 ++++++++++++++++++++++++++ 3 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 mrubyedge/tests/regexp.rs diff --git a/.github/workflows/mrubyedge.yml b/.github/workflows/mrubyedge.yml index 3b9468a..5b72780 100644 --- a/.github/workflows/mrubyedge.yml +++ b/.github/workflows/mrubyedge.yml @@ -40,7 +40,7 @@ jobs: - name: Run tests for "${{ matrix.BUILD_TARGET }}" profile run: | set +e - cargo test --workspace --exclude mec --profile ${{ matrix.BUILD_TARGET }} + cargo test --workspace --exclude mec --features mruby-regexp --profile ${{ matrix.BUILD_TARGET }} TEST_RESULT=$? if [ $TEST_RESULT != 0 ]; then echo "Some tests failed. Debug:" @@ -48,7 +48,7 @@ jobs: exit $TEST_RESULT fi - name: Build binaries for "${{ matrix.BUILD_TARGET }}" profile - run: cargo build --workspace --exclude mec --profile ${{ matrix.BUILD_TARGET }} + run: cargo build --workspace --exclude mec --features mruby-regexp --profile ${{ matrix.BUILD_TARGET }} lint: runs-on: ubuntu-latest diff --git a/mrubyedge/src/yamrb/prelude/regexp.rs b/mrubyedge/src/yamrb/prelude/regexp.rs index 1a83e18..6803beb 100644 --- a/mrubyedge/src/yamrb/prelude/regexp.rs +++ b/mrubyedge/src/yamrb/prelude/regexp.rs @@ -1,4 +1,4 @@ -#[cfg(feature = "mruby-regexp")] +#![cfg(feature = "mruby-regexp")] use std::rc::Rc; use std::{ cell::{Cell, RefCell}, diff --git a/mrubyedge/tests/regexp.rs b/mrubyedge/tests/regexp.rs new file mode 100644 index 0000000..dd8f2a5 --- /dev/null +++ b/mrubyedge/tests/regexp.rs @@ -0,0 +1,148 @@ +#![cfg(feature = "mruby-regexp")] +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; + +use helpers::*; + +#[test] +fn regexp_match_operator_test() { + let code = r#" + def test_regexp_match + re = /ruby/ + target = "mrubyedge" + result = target =~ re + result + end + "#; + let binary = mrbc_compile("regexp_match", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_regexp_match", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 1); // "ruby" starts at index 1 in "mrubyedge" +} + +#[test] +fn regexp_not_match_operator_test() { + let code = r#" + def test_regexp_not_match + re = /ruby/ + target = "micropython" + result = re !~ target + result + end + "#; + let binary = mrbc_compile("regexp_not_match", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_regexp_not_match", &args).unwrap(); + assert!(result.is_truthy()); +} + +#[test] +fn regexp_match_method_test() { + let code = r#" + def test_regexp_match_method + re = /(m?ruby).*?(m?ruby).*?(m?ruby(?:ists)?)/ + target = "mruby/edge is a mruby for embedded systems, built for rubyists." + matched = re.match(target) + matched + end + "#; + let binary = mrbc_compile("regexp_match_method", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_regexp_match_method", &args).unwrap(); + + // Check that we got a match object (not nil) + assert!(!matches!( + &result.value, + mrubyedge::yamrb::value::RValue::Nil + )); +} + +#[test] +fn regexp_match_captures_test() { + let code = r#" + def test_regexp_captures + re = /(m?ruby).*?(m?ruby).*?(m?ruby(?:ists)?)/ + target = "mruby/edge is a mruby for embedded systems, built for rubyists." + matched = re.match(target) + [matched[0], matched[1], matched[2], matched[3]] + end + "#; + let binary = mrbc_compile("regexp_captures", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_regexp_captures", &args).unwrap(); + + // Verify it's an array + if let mrubyedge::yamrb::value::RValue::Array(arr) = &result.value { + assert_eq!(arr.borrow().len(), 4); + + // Check first capture (full match) + let capture0: String = arr.borrow()[0].as_ref().try_into().unwrap(); + assert_eq!( + capture0, + "mruby/edge is a mruby for embedded systems, built for rubyists" + ); + + // Check first group + let capture1: String = arr.borrow()[1].as_ref().try_into().unwrap(); + assert_eq!(capture1, "mruby"); + + // Check second group + let capture2: String = arr.borrow()[2].as_ref().try_into().unwrap(); + assert_eq!(capture2, "mruby"); + + // Check third group + let capture3: String = arr.borrow()[3].as_ref().try_into().unwrap(); + assert_eq!(capture3, "rubyists"); + } else { + panic!("Expected array result"); + } +} + +#[test] +fn regexp_no_match_test() { + let code = r#" + def test_regexp_no_match + re = /python/ + target = "mrubyedge" + result = target =~ re + result + end + "#; + let binary = mrbc_compile("regexp_no_match", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_regexp_no_match", &args).unwrap(); + + // Should return nil when no match + assert!(matches!( + &result.value, + mrubyedge::yamrb::value::RValue::Nil + )); +} From 09bdb3f8d4731042fbf6fae42690ee2c2b35d96d Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 23:17:08 +0900 Subject: [PATCH 064/314] Bump version, tell AR to activate feature flag for workspace --- Cargo.lock | 14 +++++++------- mrubyedge/Cargo.toml | 2 +- rust-analyzer.json | 5 +++++ 3 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 rust-analyzer.json diff --git a/Cargo.lock b/Cargo.lock index 266d653..1a46f61 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -572,23 +572,23 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff9fa8f4061050e95db2b4b7ed71e5bf9a5319b95a72c7437dc2e25d76a40112" dependencies = [ - "criterion", "getrandom", - "mec-mrbc-sys", "plain", - "regex", "simple_endian", ] [[package]] name = "mrubyedge" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff9fa8f4061050e95db2b4b7ed71e5bf9a5319b95a72c7437dc2e25d76a40112" +version = "1.0.10" dependencies = [ + "criterion", "getrandom", + "mec-mrbc-sys", "plain", + "regex", "simple_endian", ] @@ -599,7 +599,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.0.9", "nom", "rand", "syn", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 9046cac..34c9129 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.0.9" +version = "1.0.10" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" diff --git a/rust-analyzer.json b/rust-analyzer.json new file mode 100644 index 0000000..4aee5ce --- /dev/null +++ b/rust-analyzer.json @@ -0,0 +1,5 @@ +{ + "rust-analyzer.cargo.features": [ + "mruby-regexp" + ] +} \ No newline at end of file From f36b04b8c25fdd7c826a4691f93b12672a07ef63 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 23:19:01 +0900 Subject: [PATCH 065/314] Use newer version of mrubyedge in mrubyedge-cli --- Cargo.lock | 15 ++++++++------- mrubyedge-cli/Cargo.toml | 4 ++-- 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1a46f61..f735958 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -571,22 +571,23 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff9fa8f4061050e95db2b4b7ed71e5bf9a5319b95a72c7437dc2e25d76a40112" +version = "1.0.10" dependencies = [ + "criterion", "getrandom", + "mec-mrbc-sys", "plain", + "regex", "simple_endian", ] [[package]] name = "mrubyedge" version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01f539c27cbe88a87158b9c366354d03fb5f81b5fecfdb0af04b588e45f76ad3" dependencies = [ - "criterion", "getrandom", - "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -594,12 +595,12 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.0.9" +version = "1.0.10" dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.9", + "mrubyedge 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 249fc02..fe0333c 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.0.9" +version = "1.0.10" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } mruby-compiler2-sys = "0.1.1" -mrubyedge = { version = "1.0.9" } +mrubyedge = { version = "1.0.10", features = ["default", "mruby-regexp"] } rand = "0.8.5" nom = "7.1.3" askama = "0.12.1" From b21d69fc76f38d90b8d259a9680878f07402a02a Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 14 Dec 2025 23:28:07 +0900 Subject: [PATCH 066/314] Support features flag for wasm subcommand to customize wasm --- mrubyedge-cli/examples/regexp.rb | 24 +++++++++++++++++++ mrubyedge-cli/src/subcommands/wasm.rs | 21 +++++++++++++--- mrubyedge-cli/templates/Cargo.toml.debug.tmpl | 2 +- mrubyedge-cli/templates/Cargo.toml.tmpl | 2 +- 4 files changed, 44 insertions(+), 5 deletions(-) create mode 100644 mrubyedge-cli/examples/regexp.rb diff --git a/mrubyedge-cli/examples/regexp.rb b/mrubyedge-cli/examples/regexp.rb new file mode 100644 index 0000000..3c5b760 --- /dev/null +++ b/mrubyedge-cli/examples/regexp.rb @@ -0,0 +1,24 @@ +def _start + re = /ruby/ + target = "mrubyedge" + if m = (target =~ re) + puts "matched: #{m}" + end + + target2 = "micropython" + if re !~ target2 + puts "not matched" + end + p re + + re3 = /(m?ruby).*?(m?ruby).*?(m?ruby(?:ists)?)/ + target3 = "mruby/edge is a mruby for embedded systems, built for rubyists." + matched = re3.match(target3) + if matched + puts "matched: #{matched[0]}" + puts "matched: #{matched[1]}" + puts "matched: #{matched[2]}" + puts "matched: #{matched[3]}" + end + p matched +end \ No newline at end of file diff --git a/mrubyedge-cli/src/subcommands/wasm.rs b/mrubyedge-cli/src/subcommands/wasm.rs index 28b44c5..2cedc96 100644 --- a/mrubyedge-cli/src/subcommands/wasm.rs +++ b/mrubyedge-cli/src/subcommands/wasm.rs @@ -23,6 +23,8 @@ pub struct WasmArgs { fnname: Option, #[arg(short = 'm', long)] mruby_edge_version: Option, + #[arg(short = 'F', long)] + features: Vec, #[arg(short = 'W', long)] no_wasi: bool, #[arg(long)] @@ -104,12 +106,25 @@ pub fn execute(args: WasmArgs) -> Result<(), Box> { .compile_to_file(&code, out_file.as_ref())? } - let feature = if args.no_wasi { "no-wasi" } else { "default" }; + let mut features = Vec::new(); + if args.no_wasi { + features.push("no-wasi"); + } else { + features.push("wasi"); + } + for f in args.features.iter() { + features.push(f.as_str()); + } + let mrubyedge_feature = features + .iter() + .map(|s| format!("\"{}\"", s)) + .collect::>() + .join(", "); if args.debug_mruby_edge { let cargo_toml = template::cargo_toml::CargoTomlDebug { mruby_edge_crate_path: "/Users/udzura/ghq/github.com/udzura/mrubyedge/mrubyedge", - mrubyedge_feature: feature, + mrubyedge_feature: &mrubyedge_feature, }; std::fs::write("Cargo.toml", cargo_toml.render()?)?; } else { @@ -117,7 +132,7 @@ pub fn execute(args: WasmArgs) -> Result<(), Box> { mrubyedge_version: &args .mruby_edge_version .unwrap_or_else(|| MRUBY_EDGE_DEFAULT_VERSION.to_string()), - mrubyedge_feature: feature, + mrubyedge_feature: &mrubyedge_feature, strip: &args.strip_binary.to_string(), }; std::fs::write("Cargo.toml", cargo_toml.render()?)?; diff --git a/mrubyedge-cli/templates/Cargo.toml.debug.tmpl b/mrubyedge-cli/templates/Cargo.toml.debug.tmpl index 22afa0a..96af9c7 100644 --- a/mrubyedge-cli/templates/Cargo.toml.debug.tmpl +++ b/mrubyedge-cli/templates/Cargo.toml.debug.tmpl @@ -10,7 +10,7 @@ crate-type = ["cdylib", "rlib"] # fixme can be changed path = "{{ mruby_edge_crate_path }}" default-features = false -features = [ "{{ mrubyedge_feature }}" ] +features = [ {{ mrubyedge_feature }} ] [profile.release] # In debug profile, we do not optimize for size \ No newline at end of file diff --git a/mrubyedge-cli/templates/Cargo.toml.tmpl b/mrubyedge-cli/templates/Cargo.toml.tmpl index f6468ca..9ea1256 100644 --- a/mrubyedge-cli/templates/Cargo.toml.tmpl +++ b/mrubyedge-cli/templates/Cargo.toml.tmpl @@ -9,7 +9,7 @@ crate-type = ["cdylib", "rlib"] [dependencies.mrubyedge] version = "{{ mrubyedge_version }}" default-features = false -features = [ "{{ mrubyedge_feature }}" ] +features = [ {{ mrubyedge_feature }} ] [profile.release] # Tell `rustc` to optimize for small code size. From c63fc02e9abbe48b53cefbd80f2e3823f89a50d5 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 15 Dec 2025 00:06:09 +0900 Subject: [PATCH 067/314] Add READMEs --- mrubyedge-cli/README.md | 167 ++++++++++++++++++++++++++++++++++++++++ mrubyedge/README.md | 100 ++++++++++++++++++++++++ 2 files changed, 267 insertions(+) create mode 100644 mrubyedge-cli/README.md create mode 100644 mrubyedge/README.md diff --git a/mrubyedge-cli/README.md b/mrubyedge-cli/README.md new file mode 100644 index 0000000..8bda3f4 --- /dev/null +++ b/mrubyedge-cli/README.md @@ -0,0 +1,167 @@ +# mrubyedge-cli + +[![crates.io](https://img.shields.io/crates/v/mrubyedge-cli.svg)](https://crates.io/crates/mrubyedge-cli) +[![docs.rs](https://docs.rs/mrubyedge-cli/badge.svg)](https://docs.rs/mrubyedge-cli) + +Command-line interface for mruby/edge - a lightweight, WebAssembly-focused mruby VM implementation. + +## About mruby/edge + +mruby/edge is an mruby-compatible virtual machine implementation written in Rust, specifically designed for WebAssembly environments. It aims to provide: + +- **WebAssembly-first design**: Optimized for running Ruby code in browsers and edge computing environments +- **Lightweight runtime**: Minimal footprint and binary size suitable for constrained environments +- **mruby compatibility**: Executes mruby bytecode (`.mrb` files) and Ruby source code +- **Rust safety**: Built with Rust for memory safety and reliability + +## Installation + +Install mrubyedge-cli using cargo: + +```sh +cargo install mrubyedge-cli +``` + +Or build from source: + +```sh +git clone https://github.com/mrubyedge/mrubyedge.git +cd mrubyedge +cargo build --release -p mrubyedge-cli +``` + +The binary will be available as `mrbedge`. + +## Getting Started + +Create a simple Ruby script `hello.rb`: + +```ruby +puts "Hello from mruby/edge!" +puts RUBY_ENGINE +``` + +Run it with mrbedge: + +```sh +mrbedge hello.rb +# or explicitly +mrbedge run hello.rb +``` + +## Main Features + +### `run` - Execute Ruby Scripts + +The `run` subcommand executes Ruby source files (`.rb`) or compiled mruby bytecode (`.mrb`). + +**Usage:** +```sh +mrbedge run +# or simply +mrbedge +``` + +**Examples:** +```sh +# Run Ruby source +mrbedge run script.rb + +# Run compiled bytecode +mrbedge run script.mrb +``` + +### `compile-mrb` - Compile Ruby to Bytecode + +Compiles Ruby source code into mruby bytecode format for faster loading and distribution. + +**Usage:** +```sh +mrbedge compile-mrb -o +``` + +**Examples:** +```sh +# Compile a single file +mrbedge compile-mrb app.rb -o app.mrb + +# Run the compiled bytecode +mrbedge run app.mrb +``` + +**Benefits:** +- Faster startup time (no parsing overhead) +- Smaller distribution size +- Protection of source code + +### `wasm` - Generate WebAssembly Modules + +Compiles Ruby code directly into a standalone WebAssembly module that can run in browsers or any WebAssembly runtime. + +**Usage:** +```sh +mrbedge wasm -o +``` + +**Examples:** +```sh +# Generate WebAssembly module +mrbedge wasm app.rb -o app.wasm + +# Use in browser or Node.js +# The generated WASM can be loaded and executed in any WASM runtime +``` + +**Use Cases:** +- Serverless edge computing +- Browser-based applications +- Microservices with minimal overhead +- Cross-platform portable executables + +#### WASI Support + +The `wasm` command can generate both WASI-enabled and non-WASI WebAssembly binaries. By default, it produces WASI-enabled modules. To disable WASI support, use the `--no-wasi` flag. + +- **WASI-enabled**: Supports file system access, environment variables, and standard I/O +- **Non-WASI**: Minimal pure WebAssembly suitable for browser environments with custom imports + +#### Import/Export Functions + +You can specify WebAssembly function imports and exports using RBS (Ruby Signature) files. Place RBS files with specific naming conventions alongside your Ruby script: + +For a Ruby script named `foo.rb`: +- **`foo.import.rbs`**: Defines external functions to import from the WebAssembly host +- **`foo.export.rbs`**: Defines Ruby functions to export as WebAssembly functions + +**Example:** + +```ruby +# app.rb +def calculate(x, y) + x + y +end +``` + +```rbs +# app.export.rbs +def calculate: (Integer, Integer) -> Integer +``` + +```rbs +# app.import.rbs +def external_log: (String) -> void +``` + +The generated WebAssembly module will expose `calculate` and can call `external_log` from the host environment. + +> **Note**: Inline RBS annotations for imports and exports will be supported in future releases. + +## Additional Resources + +- [GitHub Repository](https://github.com/mrubyedge/mrubyedge) +- [API Documentation](https://docs.rs/mrubyedge-cli) +- [Core VM Documentation](https://docs.rs/mrubyedge) + +## License + +See the [LICENSE](../LICENSE) file in the repository root. diff --git a/mrubyedge/README.md b/mrubyedge/README.md new file mode 100644 index 0000000..e38f0fa --- /dev/null +++ b/mrubyedge/README.md @@ -0,0 +1,100 @@ +# mrubyedge + +[![crates.io](https://img.shields.io/crates/v/mrubyedge.svg)](https://crates.io/crates/mrubyedge) +[![docs.rs](https://docs.rs/mrubyedge/badge.svg)](https://docs.rs/mrubyedge) + +A pure-Rust reimplementation of the mruby VM that keeps its core execution engine `no_std`-friendly while striving for behavioral compatibility with upstream mruby. + +## Overview + +mruby/edge is an mruby-compatible virtual machine implementation written in Rust, specifically designed for WebAssembly environments and embedded systems. It aims to provide: + +- **WebAssembly-first design**: Optimized for running Ruby code in browsers and edge computing environments +- **Lightweight runtime**: Minimal footprint and binary size suitable for constrained environments +- **`no_std` core**: Can run in environments without standard library support +- **mruby compatibility**: Executes mruby bytecode (`.mrb` files) and Ruby source code +- **Rust safety**: Built with Rust for memory safety and reliability + +## Installation + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +mrubyedge = "1.0" +``` + +## Usage + +### Running Precompiled Bytecode + +Load and execute a precompiled `*.mrb` file produced by `mrbc`: + +```rust +use mrubyedge::rite; +use mrubyedge::yamrb::vm; + +// Bundle the compiled script at build time +const SCRIPT: &[u8] = include_bytes!("./examples/simple.mrb"); + +fn main() -> Result<(), Box> { + let mut rite = rite::load(SCRIPT)?; + let mut vm = vm::VM::open(&mut rite); + let value = vm.run()?; + println!("{:?}", value); + Ok(()) +} +``` + +### Creating VMs Programmatically + +You can also construct IREP (internal representation) structures directly: + +```rust +use mrubyedge::yamrb::{op, vm, value::RSym}; +use mrubyedge::rite::insn::{Fetched, OpCode}; + +fn main() -> Result<(), Box> { + let irep = vm::IREP { + __id: 0, + nlocals: 0, + nregs: 7, + rlen: 0, + code: vec![ + op::Op { code: OpCode::LOADI_1, operand: Fetched::B(1), pos: 0, len: 2 }, + op::Op { code: OpCode::LOADI_2, operand: Fetched::B(2), pos: 2, len: 2 }, + op::Op { code: OpCode::ADD, operand: Fetched::B(1), pos: 4, len: 2 }, + op::Op { code: OpCode::STOP, operand: Fetched::Z, pos: 6, len: 1 }, + ], + syms: vec![], + pool: Vec::new(), + reps: Vec::new(), + catch_target_pos: Vec::new(), + }; + + let mut vm = vm::VM::new_by_raw_irep(irep); + let value = vm.run()?; + println!("{:?}", value); + Ok(()) +} +``` + +## Use Cases + +- **Embedded Systems**: Run Ruby in resource-constrained devices +- **WebAssembly Applications**: Deploy Ruby code in browsers and serverless environments +- **Edge Computing**: Lightweight Ruby runtime for edge nodes +- **Rust Integration**: Embed Ruby scripting in Rust applications + +## CLI Tool + +For a command-line interface to compile and run Ruby scripts, see [mrubyedge-cli](../mrubyedge-cli). + +## Documentation + +- [API Documentation](https://docs.rs/mrubyedge) +- [GitHub Repository](https://github.com/mrubyedge/mrubyedge) + +## License + +See the [LICENSE](../LICENSE) file in the repository root. From 7e6442794eb6ff04e2c387a9c1e8883e82c3231a Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 15 Dec 2025 00:10:08 +0900 Subject: [PATCH 068/314] Update top --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 0a8c477..1437ad6 100644 --- a/README.md +++ b/README.md @@ -9,12 +9,12 @@ mruby/edge is yet another mruby-compatible VM implementation, specialized for We ## crates -### [mrubyedge](https://crates.io/crates/mrubyedge) +### [mrubyedge](./mrubyedge) [![crates.io](https://img.shields.io/crates/v/mrubyedge.svg)](https://crates.io/crates/mrubyedge) [![docs.rs](https://docs.rs/mrubyedge/badge.svg)](https://docs.rs/mrubyedge) -* mruby/edge core VM implementation -### [mrubyedge-cli](https://crates.io/crates/mrubyedge-cli) +* mruby/edge core VM implementation +### [mrubyedge-cli](./mrubyedge-cli) [![crates.io](https://img.shields.io/crates/v/mrubyedge-cli.svg)](https://crates.io/crates/mrubyedge-cli) [![docs.rs](https://docs.rs/mrubyedge-cli/badge.svg)](https://docs.rs/mrubyedge-cli) * CLI endpoint for mruby/edge - run, compile to wasm, etc. ### mec [deprecated] From 9fb1fc7e8cffe7999cac4559c255012db3215964 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 16 Dec 2025 00:11:06 +0900 Subject: [PATCH 069/314] Add so basic test cases --- mrubyedge/tests/klass.rs | 62 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/mrubyedge/tests/klass.rs b/mrubyedge/tests/klass.rs index 44de0aa..c9a3459 100644 --- a/mrubyedge/tests/klass.rs +++ b/mrubyedge/tests/klass.rs @@ -164,3 +164,65 @@ fn class_inheritance_super_test() { .unwrap(); assert_eq!(result, 124); } + +#[test] +fn class_define_class_method_test() { + let code = " + class Test + def self.hello + 123 + end + end + + def test_main + Test.hello + end + "; + let binary = mrbc_compile("class_define_class_method", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_main", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 123); +} + +#[test] +fn class_inheritance_class_method_test() { + let code = " + class Test1 + def self.hello + 123 + end + end + + class Test2 < Test1 + def self.hello + super + 1 + end + end + + def test_main + Test2.hello + end + "; + let binary = mrbc_compile_debug("class_inheritance_class_method", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_main", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 124); +} From 963ed673f6cd9cd018c452b1a6e925326e44b7ce Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 16 Dec 2025 00:25:30 +0900 Subject: [PATCH 070/314] WIP --- mrubyedge/src/yamrb/helpers.rs | 9 ++++++--- mrubyedge/src/yamrb/optable.rs | 4 ++-- mrubyedge/src/yamrb/value.rs | 25 +++++++++++-------------- mrubyedge/tests/klass.rs | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index f01567d..e02fc85 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -141,7 +141,7 @@ pub fn mrb_funcall( Some(obj) => obj, None => vm.getself()?, }; - let binding = recv.as_ref().get_singleton_class_or_class(vm); + let binding = recv.initialize_or_get_singleton_class(vm); let (owner_module, method) = resolve_method(&binding, name).ok_or_else(|| Error::NoMethodError(name.to_string()))?; @@ -216,8 +216,11 @@ pub fn mrb_define_class_cmethod(vm: &mut VM, klass: Rc, name: &str, cmet environ: None, block_self: None, }; - let class_obj = RObject::class(klass.clone(), vm); - let klass_singleton = class_obj.initialize_or_get_singleton_class(vm); + let klass_singleton = klass + .singleton_class_ref + .borrow() + .clone() + .expect("Singleton class not initialized"); let mut procs = klass_singleton.procs.borrow_mut(); procs.insert(name.to_string(), method); } diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 510b3a8..59ccc96 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -915,7 +915,7 @@ pub(crate) fn do_op_send( } let method_id = vm.current_irep.syms[b as usize].clone(); - let klass = recv.get_singleton_class_or_class(vm); + let klass = recv.initialize_or_get_singleton_class(vm); let (owner_module, method) = resolve_method(&klass, &method_id.name) .ok_or_else(|| Error::NoMethodError(method_id.name.clone()))?; @@ -1687,7 +1687,7 @@ pub(crate) fn op_def(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { } (_, RValue::Proc(method)) => { let robject = target.clone(); - let sclass = robject.get_singleton_class_or_class(vm); + let sclass = robject.initialize_or_get_singleton_class(vm); let mut procs = sclass.procs.borrow_mut(); let mut method = method.clone(); method.sym_id = Some(sym.clone()); diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index fa039ce..7ae38f7 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -212,21 +212,24 @@ impl RObject { match vm.class_object_table.get(&c.full_name()) { Some(robj) => robj.clone(), None => { - let robj = Self::newclass(c.clone()); + let robj = Self::newclass(c.clone(), vm); vm.class_object_table.insert(c.full_name(), robj.clone()); robj } } } - fn newclass(c: Rc) -> Rc { - RObject { + fn newclass(c: Rc, vm: &mut VM) -> Rc { + let obj = RObject { tt: RType::Class, - value: RValue::Class(c), + value: RValue::Class(c.clone()), object_id: (u64::MAX).into(), singleton_class: RefCell::new(None), } - .to_refcount_assigned() + .to_refcount_assigned(); + let sclass = obj.initialize_or_get_singleton_class(vm); + c.singleton_class_ref.borrow_mut().replace(sclass.clone()); + obj } pub fn module(m: Rc) -> Self { @@ -331,15 +334,6 @@ impl RObject { } } - pub fn get_singleton_class_or_class(&self, vm: &VM) -> Rc { - self.singleton_class - .borrow() - .as_ref() - .map(|s| s.clone()) - .or_else(|| Some(self.get_class(vm))) - .expect("should have singleton class or class") - } - pub fn get_class(&self, vm: &VM) -> Rc { match &self.value { RValue::Class(_) => vm.get_class_by_name("Class"), @@ -659,6 +653,7 @@ impl From> for RObject { pub struct RClass { pub module: Rc, pub super_class: Option>, + pub singleton_class_ref: RefCell>>, } impl RClass { @@ -668,12 +663,14 @@ impl RClass { parent_module: Option>, ) -> Self { let module = Rc::new(RModule::new(name)); + let singleton_class_ref = RefCell::new(None); if let Some(parent) = parent_module { module.parent.replace(Some(parent)); } RClass { module, super_class, + singleton_class_ref, } } diff --git a/mrubyedge/tests/klass.rs b/mrubyedge/tests/klass.rs index c9a3459..356ebe1 100644 --- a/mrubyedge/tests/klass.rs +++ b/mrubyedge/tests/klass.rs @@ -178,7 +178,7 @@ fn class_define_class_method_test() { Test.hello end "; - let binary = mrbc_compile("class_define_class_method", code); + let binary = mrbc_compile_debug("class_define_class_method", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); vm.run().unwrap(); From 034224d211fe85f1e69fe4c21465e167e8ae0a32 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 16 Dec 2025 23:39:50 +0900 Subject: [PATCH 071/314] Class singleton works --- mrubyedge/examples/class2.rb | 11 +++ mrubyedge/examples/class3.rb | 20 ++++++ mrubyedge/src/yamrb/helpers.rs | 34 +++++++-- mrubyedge/src/yamrb/optable.rs | 29 +++++--- mrubyedge/src/yamrb/prelude/class.rs | 5 +- mrubyedge/src/yamrb/prelude/exception.rs | 36 +++++----- mrubyedge/src/yamrb/value.rs | 91 +++++++++++++++++++----- mrubyedge/src/yamrb/vm.rs | 27 ++++++- 8 files changed, 202 insertions(+), 51 deletions(-) create mode 100644 mrubyedge/examples/class2.rb create mode 100644 mrubyedge/examples/class3.rb diff --git a/mrubyedge/examples/class2.rb b/mrubyedge/examples/class2.rb new file mode 100644 index 0000000..ade6d06 --- /dev/null +++ b/mrubyedge/examples/class2.rb @@ -0,0 +1,11 @@ +class Test + def self.hello + 123 + end +end + +def test_main + Test.hello +end + +p test_main \ No newline at end of file diff --git a/mrubyedge/examples/class3.rb b/mrubyedge/examples/class3.rb new file mode 100644 index 0000000..b58e017 --- /dev/null +++ b/mrubyedge/examples/class3.rb @@ -0,0 +1,20 @@ +class Test + def self.hello + 123 + end + + def self.hello2 + 10000 + end +end + +class Test2 < Test + def self.hello + super + 1 + end + + attr_reader :value +end + +p Test2.hello +p Test2.hello2 \ No newline at end of file diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index e02fc85..3d6e98c 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -169,6 +169,34 @@ pub fn mrb_funcall( } } +pub fn mrb_call_inspect(vm: &mut VM, recv: Rc) -> Result, Error> { + let binding = recv.get_class(vm); + let (owner_module, method) = resolve_method(&binding, "inspect") + .ok_or_else(|| Error::NoMethodError("inspect".to_string()))?; + if method.is_rb_func { + let method_id = method + .sym_id + .clone() + .unwrap_or_else(|| RSym::new("inspect".to_string())); + call_block( + vm, + method, + recv.clone(), + &[], + Some((method_id, owner_module)), + ) + } else { + vm.current_regs_offset += 2; // FIXME: magick number? + vm.current_regs()[0].replace(recv.clone()); + + let func = vm.fn_table[method.func.unwrap()].clone(); + let res = func(vm, &[]); + vm.current_regs_offset -= 2; + + res + } +} + /// Defines a C method (native Rust function) on a Ruby class. /// /// # Arguments @@ -216,11 +244,7 @@ pub fn mrb_define_class_cmethod(vm: &mut VM, klass: Rc, name: &str, cmet environ: None, block_self: None, }; - let klass_singleton = klass - .singleton_class_ref - .borrow() - .clone() - .expect("Singleton class not initialized"); + let klass_singleton = RObject::class_singleton(klass, vm); let mut procs = klass_singleton.procs.borrow_mut(); procs.insert(name.to_string(), method); } diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 59ccc96..d806754 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -915,7 +915,12 @@ pub(crate) fn do_op_send( } let method_id = vm.current_irep.syms[b as usize].clone(); - let klass = recv.initialize_or_get_singleton_class(vm); + let klass = recv.get_class(vm); + let klass = if klass.is_singleton { + klass + } else { + recv.initialize_or_get_singleton_class(vm) + }; let (owner_module, method) = resolve_method(&klass, &method_id.name) .ok_or_else(|| Error::NoMethodError(method_id.name.clone()))?; @@ -996,7 +1001,7 @@ pub(crate) fn op_super(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let klass = match &recv.value { RValue::Instance(ins) => ins.class.clone(), - _ => unreachable!("super must be called on instance"), + _ => recv.initialize_or_get_singleton_class(vm), }; let (next_owner, method) = resolve_next_method(&klass, &sym_id, &owner_module) .ok_or_else(|| Error::NoMethodError(sym_id.clone()))?; @@ -1610,6 +1615,7 @@ pub(crate) fn op_class(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { // register constant under parent namespace (if any) or top-level let class_value = RObject::class(klass.clone(), vm); + class_value.initialize_or_get_singleton_class_for_class(vm); if let Some(parent) = parent_module { parent.consts.borrow_mut().insert(name.clone(), class_value); } else { @@ -1687,7 +1693,12 @@ pub(crate) fn op_def(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { } (_, RValue::Proc(method)) => { let robject = target.clone(); - let sclass = robject.initialize_or_get_singleton_class(vm); + let current_class = robject.get_class(vm); + let sclass = if current_class.is_singleton { + current_class + } else { + robject.initialize_or_get_singleton_class(vm) + }; let mut procs = sclass.procs.borrow_mut(); let mut method = method.clone(); method.sym_id = Some(sym.clone()); @@ -1754,12 +1765,12 @@ pub(crate) fn op_undef(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_sclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; let val = vm.getself()?; - let singleton_class = val.singleton_class.borrow().clone(); - if let Some(ref sc) = singleton_class { - let robj = RObject::class(sc.clone(), vm); - vm.current_regs()[a].replace(robj); - return Ok(()); - } + let singleton_class = match val.tt { + RType::Class | RType::Module => val.initialize_or_get_singleton_class_for_class(vm), + _ => val.initialize_or_get_singleton_class(vm), + }; + let robj = RObject::class(singleton_class.clone(), vm); + vm.current_regs()[a].replace(robj); Ok(()) } diff --git a/mrubyedge/src/yamrb/prelude/class.rs b/mrubyedge/src/yamrb/prelude/class.rs index d3d8b47..8dce524 100644 --- a/mrubyedge/src/yamrb/prelude/class.rs +++ b/mrubyedge/src/yamrb/prelude/class.rs @@ -11,7 +11,10 @@ use crate::{ pub(crate) fn initialize_class(vm: &mut VM) { let module_class = vm.get_class_by_name("Module"); - let class_class = vm.define_standard_class_under("Class", module_class); + let class_class = vm.define_standard_class_with_superclass("Class", module_class); + + // Create singleton class for Object class + RObject::class(vm.object_class.clone(), vm).initialize_or_get_singleton_class_for_class(vm); mrb_define_cmethod(vm, class_class.clone(), "new", Box::new(mrb_class_new)); mrb_define_cmethod( diff --git a/mrubyedge/src/yamrb/prelude/exception.rs b/mrubyedge/src/yamrb/prelude/exception.rs index c7295c0..d4927b7 100644 --- a/mrubyedge/src/yamrb/prelude/exception.rs +++ b/mrubyedge/src/yamrb/prelude/exception.rs @@ -7,27 +7,27 @@ use crate::{ pub(crate) fn initialize_exception(vm: &mut VM) { let exp_class: Rc = vm.define_standard_class("Exception"); - let _ = vm.define_standard_class_under("InternalError", exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("InternalError", exp_class.clone()); // fill in ruby's standard exceptions: let std_exp_class: Rc = - vm.define_standard_class_under("StandardError", exp_class.clone()); - let _ = vm.define_standard_class_under("RuntimeError", std_exp_class.clone()); - let _ = vm.define_standard_class_under("TypeError", std_exp_class.clone()); - let _ = vm.define_standard_class_under("ArgumentError", std_exp_class.clone()); - let _ = vm.define_standard_class_under("NoMemoryError", exp_class.clone()); - let _ = vm.define_standard_class_under("ScriptError", exp_class.clone()); - let _ = vm.define_standard_class_under("LoadError", exp_class.clone()); - let _ = vm.define_standard_class_under("NotImplementedError", std_exp_class.clone()); - let _ = vm.define_standard_class_under("SyntaxError", exp_class.clone()); - let _ = vm.define_standard_class_under("SecurityError", std_exp_class.clone()); - let _ = vm.define_standard_class_under("SignalException", exp_class.clone()); - let _ = vm.define_standard_class_under("Interrupt", exp_class.clone()); - let _ = vm.define_standard_class_under("SystemExit", exp_class.clone()); - let _ = vm.define_standard_class_under("SystemStackError", exp_class.clone()); - let _ = vm.define_standard_class_under("SystemCallError", std_exp_class.clone()); - let _ = vm.define_standard_class_under("NoMethodError", std_exp_class.clone()); - let _ = vm.define_standard_class_under("NameError", std_exp_class.clone()); + vm.define_standard_class_with_superclass("StandardError", exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("RuntimeError", std_exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("TypeError", std_exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("ArgumentError", std_exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("NoMemoryError", exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("ScriptError", exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("LoadError", exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("NotImplementedError", std_exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("SyntaxError", exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("SecurityError", std_exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("SignalException", exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("Interrupt", exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("SystemExit", exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("SystemStackError", exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("SystemCallError", std_exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("NoMethodError", std_exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("NameError", std_exp_class.clone()); mrb_define_cmethod(vm, exp_class, "message", Box::new(mrb_exception_message)); } diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 7ae38f7..d8036d0 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -4,7 +4,7 @@ use std::collections::HashSet; use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc}; use crate::Error; -use crate::yamrb::helpers::mrb_funcall; +use crate::yamrb::helpers::mrb_call_inspect; use super::shared_memory::SharedMemory; use super::vm::{ENV, IREP, VM}; @@ -212,24 +212,26 @@ impl RObject { match vm.class_object_table.get(&c.full_name()) { Some(robj) => robj.clone(), None => { - let robj = Self::newclass(c.clone(), vm); + let robj = Self::newclass(c.clone()); vm.class_object_table.insert(c.full_name(), robj.clone()); robj } } } - fn newclass(c: Rc, vm: &mut VM) -> Rc { - let obj = RObject { + fn newclass(c: Rc) -> Rc { + RObject { tt: RType::Class, value: RValue::Class(c.clone()), object_id: (u64::MAX).into(), singleton_class: RefCell::new(None), } - .to_refcount_assigned(); - let sclass = obj.initialize_or_get_singleton_class(vm); - c.singleton_class_ref.borrow_mut().replace(sclass.clone()); - obj + .to_refcount_assigned() + } + + pub fn class_singleton(c: Rc, vm: &mut VM) -> Rc { + let class_obj = Self::class(c.clone(), vm); + class_obj.initialize_or_get_singleton_class_for_class(vm) } pub fn module(m: Rc) -> Self { @@ -366,16 +368,18 @@ impl RObject { return sclass.clone(); } - let inspect = mrb_funcall(vm, Some(self.clone()), "inspect", &[]); - let class_name: String = match inspect { - Ok(inspect) => inspect - .as_ref() - .try_into() - .unwrap_or_else(|_| "".to_string()), - Err(e) => format!("", e), + let class_name = { + let inspect = mrb_call_inspect(vm, self.clone()); + match inspect { + Ok(inspect) => inspect + .as_ref() + .try_into() + .unwrap_or_else(|_| "".to_string()), + Err(e) => format!("", e), + } }; - let sclass = Rc::new(RClass::new( + let sclass = Rc::new(RClass::new_singleton( &class_name, Some(self.get_class(vm).clone()), self.get_class(vm).parent.borrow().clone(), @@ -384,6 +388,41 @@ impl RObject { self.singleton_class.replace(Some(sclass.clone())); sclass } + + pub(crate) fn initialize_or_get_singleton_class_for_class( + self: &Rc, + vm: &mut VM, + ) -> Rc { + if self.singleton_class.borrow().is_some() { + return self.singleton_class.borrow().as_ref().unwrap().clone(); + } + + let class = match &self.value { + RValue::Class(c) => c.clone(), + _ => panic!("Not called on a class"), + }; + let class_name = format!("#", class.full_name()); + let super_class = match &class.super_class { + Some(parent) => { + let parent_obj = RObject::class(parent.clone(), vm); + parent_obj.initialize_or_get_singleton_class_for_class(vm) + } + None => vm.get_class_by_name("Class"), + }; + + let sclass = Rc::new(RClass::new_singleton( + &class_name, + Some(super_class), + self.get_class(vm).parent.borrow().clone(), + )); + + self.singleton_class.replace(Some(sclass.clone())); + class + .singleton_class_ref + .borrow_mut() + .replace(sclass.clone()); + sclass + } } impl TryFrom<&RObject> for i32 { @@ -654,6 +693,7 @@ pub struct RClass { pub module: Rc, pub super_class: Option>, pub singleton_class_ref: RefCell>>, + pub is_singleton: bool, } impl RClass { @@ -671,6 +711,25 @@ impl RClass { module, super_class, singleton_class_ref, + is_singleton: false, + } + } + + pub fn new_singleton( + name: &str, + super_class: Option>, + parent_module: Option>, + ) -> Self { + let module = Rc::new(RModule::new(name)); + let singleton_class_ref = RefCell::new(None); + if let Some(parent) = parent_module { + module.parent.replace(Some(parent)); + } + RClass { + module, + super_class, + singleton_class_ref, + is_singleton: true, } } diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index ddc03a1..48e3cf2 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -345,12 +345,35 @@ impl VM { class } + pub(crate) fn define_standard_class_with_superclass( + &mut self, + name: &'static str, + superclass: Rc, + ) -> Rc { + let class = self.define_class(name, Some(superclass.clone()), None); + self.builtin_class_table.insert(name, class.clone()); + class + } + + #[allow(dead_code)] pub(crate) fn define_standard_class_under( &mut self, name: &'static str, - sklass: Rc, + parent: Rc, + ) -> Rc { + let class = self.define_class(name, None, Some(parent)); + self.builtin_class_table.insert(name, class.clone()); + class + } + + #[allow(dead_code)] + pub(crate) fn define_standard_class_with_superclass_under( + &mut self, + name: &'static str, + superclass: Rc, + parent: Rc, ) -> Rc { - let class = self.define_class(name, Some(sklass.clone()), Some(sklass.module.clone())); + let class = self.define_class(name, Some(superclass.clone()), Some(parent)); self.builtin_class_table.insert(name, class.clone()); class } From d6cbf19d8843c88fec496aa486c76be96f16bc27 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 16 Dec 2025 23:47:30 +0900 Subject: [PATCH 072/314] Dont make unecessary singleton --- mrubyedge/src/yamrb/helpers.rs | 4 ++-- mrubyedge/src/yamrb/optable.rs | 13 ++++++++----- mrubyedge/src/yamrb/value.rs | 7 +++++++ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index 3d6e98c..e28f420 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -142,8 +142,8 @@ pub fn mrb_funcall( None => vm.getself()?, }; let binding = recv.initialize_or_get_singleton_class(vm); - let (owner_module, method) = - resolve_method(&binding, name).ok_or_else(|| Error::NoMethodError(name.to_string()))?; + let (owner_module, method) = resolve_method(&binding, name) + .ok_or_else(|| Error::NoMethodError(format!("{} for {}", name, binding.full_name())))?; if method.is_rb_func { let method_id = method diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index d806754..ad5f9c4 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -919,10 +919,11 @@ pub(crate) fn do_op_send( let klass = if klass.is_singleton { klass } else { - recv.initialize_or_get_singleton_class(vm) + recv.singleton_or_this_class(vm) }; - let (owner_module, method) = resolve_method(&klass, &method_id.name) - .ok_or_else(|| Error::NoMethodError(method_id.name.clone()))?; + let (owner_module, method) = resolve_method(&klass, &method_id.name).ok_or_else(|| { + Error::NoMethodError(format!("{} for {}", method_id.name, klass.full_name())) + })?; vm.current_regs()[a as usize].replace(recv.clone()); if !method.is_rb_func { @@ -1003,8 +1004,10 @@ pub(crate) fn op_super(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { RValue::Instance(ins) => ins.class.clone(), _ => recv.initialize_or_get_singleton_class(vm), }; - let (next_owner, method) = resolve_next_method(&klass, &sym_id, &owner_module) - .ok_or_else(|| Error::NoMethodError(sym_id.clone()))?; + let (next_owner, method) = + resolve_next_method(&klass, &sym_id, &owner_module).ok_or_else(|| { + Error::NoMethodError(format!("{} for {}", sym_id.clone(), klass.full_name())) + })?; if !method.is_rb_func { let func = vm.get_fn(method.func.unwrap()).ok_or_else(|| { Error::internal(format!("functon registerd but no entry found: {}", &sym_id)) diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index d8036d0..739cb48 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -423,6 +423,13 @@ impl RObject { .replace(sclass.clone()); sclass } + + pub fn singleton_or_this_class(self: &Rc, vm: &mut VM) -> Rc { + if let Some(sclass) = self.singleton_class.borrow().as_ref() { + return sclass.clone(); + } + self.get_class(vm) + } } impl TryFrom<&RObject> for i32 { From 460deccbbeba2ad9a2cbb1b446ad9eb4ebb4cbaa Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 16 Dec 2025 23:48:17 +0900 Subject: [PATCH 073/314] Test case for NoMethodError --- mrubyedge/tests/alias.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrubyedge/tests/alias.rs b/mrubyedge/tests/alias.rs index ac8157c..da3ffa7 100644 --- a/mrubyedge/tests/alias.rs +++ b/mrubyedge/tests/alias.rs @@ -75,5 +75,5 @@ fn undef_test() { .as_ref() .try_into() .unwrap(); - assert_eq!(result, "Method not found: sample"); + assert!(result.contains("Method not found: sample")); } From c6d7bebbee62a6597af487292e693996e8a4cf7d Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 16 Dec 2025 23:49:31 +0900 Subject: [PATCH 074/314] Message2 --- mrubyedge/tests/raise_rust.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrubyedge/tests/raise_rust.rs b/mrubyedge/tests/raise_rust.rs index 79dd17d..fc6acc8 100644 --- a/mrubyedge/tests/raise_rust.rs +++ b/mrubyedge/tests/raise_rust.rs @@ -106,7 +106,7 @@ fn rust_nomethod_rescue_test() { .as_ref() .try_into() .unwrap(); - assert_eq!(&result, "rescued: Method not found: dummy_nomethod"); + assert!(result.contains("rescued: Method not found: dummy_nomethod")); } #[test] From afba929526a59ed83ee5087c9d672372e01d66c0 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 17 Dec 2025 00:18:40 +0900 Subject: [PATCH 075/314] Fix SCLASS definition --- mrubyedge/src/yamrb/helpers.rs | 2 +- mrubyedge/src/yamrb/optable.rs | 31 +++++++++++++++++++++++++++++-- mrubyedge/src/yamrb/value.rs | 5 +++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index e28f420..ba0001b 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -141,7 +141,7 @@ pub fn mrb_funcall( Some(obj) => obj, None => vm.getself()?, }; - let binding = recv.initialize_or_get_singleton_class(vm); + let binding = recv.singleton_or_this_class(vm); let (owner_module, method) = resolve_method(&binding, name) .ok_or_else(|| Error::NoMethodError(format!("{} for {}", name, binding.full_name())))?; diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index ad5f9c4..85703df 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -865,7 +865,26 @@ pub(crate) fn op_raiseif(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_move(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; let val = vm.get_current_regs_cloned(b as usize)?; - vm.current_regs()[a as usize].replace(val); + + dbg!( + "new", + val.singleton_class.borrow().is_some(), + val.tt, + &val.object_id + ); + let old = vm.current_regs()[a as usize].replace(val); + match old { + Some(v) => { + dbg!( + "old", + v.singleton_class.borrow().is_some(), + v.tt, + &v.object_id + ); + } + None => { /* nothing to do */ } + } + Ok(()) } @@ -941,6 +960,12 @@ pub(crate) fn do_op_send( match res { Ok(val) => { + dbg!( + "retval", + val.singleton_class.borrow().is_some(), + val.tt, + &val.object_id + ); vm.current_regs()[a as usize].replace(val); } Err(e) => { @@ -1767,7 +1792,9 @@ pub(crate) fn op_undef(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_sclass(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; - let val = vm.getself()?; + let val = vm.current_regs()[a] + .take() + .expect("SCLASS: operand too short"); let singleton_class = match val.tt { RType::Class | RType::Module => val.initialize_or_get_singleton_class_for_class(vm), _ => val.initialize_or_get_singleton_class(vm), diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 739cb48..ac9b31e 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -386,6 +386,11 @@ impl RObject { )); self.singleton_class.replace(Some(sclass.clone())); + eprintln!( + "Created singleton class: {} / {}", + class_name, + self.object_id.get() + ); sclass } From 560e892370030b01b6c54d51927e56c993d3966b Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 17 Dec 2025 00:24:11 +0900 Subject: [PATCH 076/314] Remove dbg! --- mrubyedge/src/yamrb/optable.rs | 26 +------------------------- 1 file changed, 1 insertion(+), 25 deletions(-) diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 85703df..c227306 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -866,25 +866,7 @@ pub(crate) fn op_move(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; let val = vm.get_current_regs_cloned(b as usize)?; - dbg!( - "new", - val.singleton_class.borrow().is_some(), - val.tt, - &val.object_id - ); - let old = vm.current_regs()[a as usize].replace(val); - match old { - Some(v) => { - dbg!( - "old", - v.singleton_class.borrow().is_some(), - v.tt, - &v.object_id - ); - } - None => { /* nothing to do */ } - } - + let _old = vm.current_regs()[a as usize].replace(val); Ok(()) } @@ -960,12 +942,6 @@ pub(crate) fn do_op_send( match res { Ok(val) => { - dbg!( - "retval", - val.singleton_class.borrow().is_some(), - val.tt, - &val.object_id - ); vm.current_regs()[a as usize].replace(val); } Err(e) => { From 4f1f7f3d2042c55b524392d774e134267c122e01 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 17 Dec 2025 00:25:33 +0900 Subject: [PATCH 077/314] Remove clone --- mrubyedge/src/yamrb/value.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index ac9b31e..63089e9 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -222,7 +222,7 @@ impl RObject { fn newclass(c: Rc) -> Rc { RObject { tt: RType::Class, - value: RValue::Class(c.clone()), + value: RValue::Class(c), object_id: (u64::MAX).into(), singleton_class: RefCell::new(None), } From 27f9e508075c6c6b814edc04772934a3560008a7 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 17 Dec 2025 00:29:30 +0900 Subject: [PATCH 078/314] No use mrbc_compile_debug --- mrubyedge/src/yamrb/value.rs | 2 +- mrubyedge/tests/assign.rs | 2 +- mrubyedge/tests/klass.rs | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 63089e9..5f527c3 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -230,7 +230,7 @@ impl RObject { } pub fn class_singleton(c: Rc, vm: &mut VM) -> Rc { - let class_obj = Self::class(c.clone(), vm); + let class_obj = Self::class(c, vm); class_obj.initialize_or_get_singleton_class_for_class(vm) } diff --git a/mrubyedge/tests/assign.rs b/mrubyedge/tests/assign.rs index a9f2932..2cdb847 100644 --- a/mrubyedge/tests/assign.rs +++ b/mrubyedge/tests/assign.rs @@ -42,7 +42,7 @@ fn assign_post_test() { ans end "; - let binary = mrbc_compile_debug("assign2", code); + let binary = mrbc_compile("assign2", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); vm.run().unwrap(); diff --git a/mrubyedge/tests/klass.rs b/mrubyedge/tests/klass.rs index 356ebe1..6f4e01f 100644 --- a/mrubyedge/tests/klass.rs +++ b/mrubyedge/tests/klass.rs @@ -178,7 +178,7 @@ fn class_define_class_method_test() { Test.hello end "; - let binary = mrbc_compile_debug("class_define_class_method", code); + let binary = mrbc_compile("class_define_class_method", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); vm.run().unwrap(); @@ -212,7 +212,7 @@ fn class_inheritance_class_method_test() { Test2.hello end "; - let binary = mrbc_compile_debug("class_inheritance_class_method", code); + let binary = mrbc_compile("class_inheritance_class_method", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); vm.run().unwrap(); From 9ff3a78656c3e2a548fe6e6b6d43f5ea5eb2bfc3 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 17 Dec 2025 00:47:21 +0900 Subject: [PATCH 079/314] Every instance has ivar --- mrubyedge/src/yamrb/optable.rs | 41 +++---------- mrubyedge/src/yamrb/prelude/class.rs | 26 +-------- mrubyedge/src/yamrb/prelude/shared_memory.rs | 2 + mrubyedge/src/yamrb/value.rs | 60 ++++++++++++++------ mrubyedge/src/yamrb/vm.rs | 2 +- 5 files changed, 56 insertions(+), 75 deletions(-) diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index c227306..9441545 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -613,22 +613,8 @@ pub(crate) fn op_setgv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_getiv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; let this = vm.getself()?; - let ivar = match &this.value { - RValue::Instance(ins) => ins - .ivar - .borrow() - .get(&vm.current_irep.syms[b as usize].name) - .ok_or_else(|| Error::internal(format!("symbol not found {}", b)))? - .clone(), - RValue::Data(data) => data - .ivar - .borrow() - .get(&vm.current_irep.syms[b as usize].name) - .ok_or_else(|| Error::internal(format!("symbol not found {}", b)))? - .clone(), - _ => unreachable!("getiv must be called on instance"), - }; - vm.current_regs()[a as usize].replace(ivar); + let key = vm.current_irep.syms[b as usize].name.clone(); + vm.current_regs()[a as usize].replace(this.get_ivar(&key)); Ok(()) } @@ -636,17 +622,8 @@ pub(crate) fn op_setiv(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; let this = vm.getself()?; let val = vm.get_current_regs_cloned(a as usize)?; - match &this.value { - RValue::Instance(ins) => { - let mut ivar = ins.ivar.borrow_mut(); - ivar.insert(vm.current_irep.syms[b as usize].name.clone(), val) - } - RValue::Data(data) => { - let mut ivar = data.ivar.borrow_mut(); - ivar.insert(vm.current_irep.syms[b as usize].name.clone(), val) - } - _ => unreachable!("setiv must be called on instance"), - }; + let key = vm.current_irep.syms[b as usize].name.clone(); + this.set_ivar(&key, val.clone()); Ok(()) } @@ -1501,6 +1478,7 @@ pub(crate) fn op_lambda(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { }), object_id: u64::MAX.into(), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), }; vm.current_regs()[a as usize].replace(val.to_refcount_assigned()); Ok(()) @@ -1532,6 +1510,7 @@ pub(crate) fn op_block(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { }), object_id: u64::MAX.into(), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), }; vm.current_regs()[a as usize].replace(val.to_refcount_assigned()); Ok(()) @@ -1553,6 +1532,7 @@ pub(crate) fn op_method(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { }), object_id: u64::MAX.into(), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), }; vm.current_regs()[a as usize].replace(val.to_refcount_assigned()); Ok(()) @@ -1571,12 +1551,7 @@ pub(crate) fn op_range_exc(vm: &mut VM, operand: &Fetched) -> Result<(), Error> fn do_op_range(vm: &mut VM, a: usize, b: usize, exclusive: bool) -> Result<(), Error> { let val1 = vm.get_current_regs_cloned(a)?; let val2 = vm.get_current_regs_cloned(b)?; - let val = RObject { - tt: super::value::RType::Range, - value: super::value::RValue::Range(val1, val2, exclusive), - object_id: u64::MAX.into(), - singleton_class: RefCell::new(None), - }; + let val = RObject::range(val1, val2, exclusive); vm.current_regs()[a].replace(val.to_refcount_assigned()); Ok(()) } diff --git a/mrubyedge/src/yamrb/prelude/class.rs b/mrubyedge/src/yamrb/prelude/class.rs index 8dce524..e8077bc 100644 --- a/mrubyedge/src/yamrb/prelude/class.rs +++ b/mrubyedge/src/yamrb/prelude/class.rs @@ -78,19 +78,7 @@ fn mrb_class_attr_reader(vm: &mut VM, args: &[Rc]) -> Result]| { let this = vm.getself()?; let key = format!("@{}", sym_id); - let value = match &this.value { - RValue::Instance(i) => match i.ivar.borrow().get(&key) { - Some(v) => v.clone(), - None => Rc::new(RObject::nil()), - }, - _ => { - return Err(Error::RuntimeError( - "attr_reader defined method must be called from instance" - .to_string(), - )); - } - }; - Ok(value) + Ok(this.get_ivar(&key)) }; mrb_define_cmethod(vm, class.clone(), sym_id, Box::new(method)); } @@ -125,17 +113,7 @@ fn mrb_class_attr_writer(vm: &mut VM, args: &[Rc]) -> Result { - i.ivar.borrow_mut().insert(key, value.clone()); - } - _ => { - return Err(Error::RuntimeError( - "attr_reader defined method must be called from instance" - .to_string(), - )); - } - }; + this.set_ivar(&key, value.clone()); Ok(value) }; let sym_id = format!("{}=", sym_id); diff --git a/mrubyedge/src/yamrb/prelude/shared_memory.rs b/mrubyedge/src/yamrb/prelude/shared_memory.rs index f0767dd..b70388d 100644 --- a/mrubyedge/src/yamrb/prelude/shared_memory.rs +++ b/mrubyedge/src/yamrb/prelude/shared_memory.rs @@ -1,4 +1,5 @@ use std::cell::RefCell; +use std::collections::HashMap; use std::rc::Rc; use crate::yamrb::helpers::mrb_define_class_cmethod; @@ -67,6 +68,7 @@ pub fn mrb_shared_memory_new(_vm: &mut VM, args: &[Rc]) -> Result, pub singleton_class: RefCell>>, + + pub ivar: RefCell>>, } +const UNSET_OBJECT_ID: u64 = u64::MAX; + impl RObject { pub fn nil() -> Self { RObject { @@ -116,6 +120,7 @@ impl RObject { value: RValue::Nil, object_id: 4.into(), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), } } @@ -125,6 +130,7 @@ impl RObject { value: RValue::Bool(b), object_id: (if b { 20 } else { 0 }).into(), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), } } @@ -134,14 +140,13 @@ impl RObject { value: RValue::Symbol(sym), object_id: 2.into(), // TODO: calc the same id for the same symbol singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), } } pub fn integer(n: i64) -> Self { - let object_id = if n >= (i32::MAX as i64) { - u64::MAX - } else if n <= (i32::MIN as i64) { - i64::MIN as u64 + let object_id = if (i32::MAX as i64) <= n || n <= (i32::MIN as i64) { + UNSET_OBJECT_ID } else { (n * 2) as u64 + 1 }; @@ -151,6 +156,7 @@ impl RObject { value: RValue::Integer(n), object_id: object_id.into(), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), } } @@ -160,6 +166,7 @@ impl RObject { value: RValue::Float(f), object_id: f.to_bits().into(), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), } } @@ -167,8 +174,9 @@ impl RObject { RObject { tt: RType::String, value: RValue::String(RefCell::new(s.into_bytes())), - object_id: (u64::MAX).into(), + object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), } } @@ -176,8 +184,9 @@ impl RObject { RObject { tt: RType::String, value: RValue::String(RefCell::new(v)), - object_id: (u64::MAX).into(), + object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), } } @@ -185,8 +194,9 @@ impl RObject { RObject { tt: RType::Array, value: RValue::Array(RefCell::new(v)), - object_id: (u64::MAX).into(), + object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), } } @@ -194,8 +204,9 @@ impl RObject { RObject { tt: RType::Hash, value: RValue::Hash(RefCell::new(h)), - object_id: (u64::MAX).into(), + object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), } } @@ -203,8 +214,9 @@ impl RObject { RObject { tt: RType::Range, value: RValue::Range(start, end, exclusive), - object_id: (u64::MAX).into(), + object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), } } @@ -223,8 +235,9 @@ impl RObject { RObject { tt: RType::Class, value: RValue::Class(c), - object_id: (u64::MAX).into(), + object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), } .to_refcount_assigned() } @@ -238,8 +251,9 @@ impl RObject { RObject { tt: RType::Module, value: RValue::Module(m), - object_id: (u64::MAX).into(), + object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), } } @@ -248,11 +262,11 @@ impl RObject { tt: RType::Instance, value: RValue::Instance(RInstance { class: c, - ivar: RefCell::new(HashMap::new()), ref_count: 1, }), - object_id: (u64::MAX).into(), + object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), } } @@ -260,15 +274,16 @@ impl RObject { RObject { tt: RType::Exception, value: RValue::Exception(e), - object_id: (u64::MAX).into(), + object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), } } pub fn to_refcount_assigned(self) -> Rc { let rc = Rc::new(self); let id = Rc::as_ptr(&rc) as u64; - if rc.object_id.get() == u64::MAX { + if rc.object_id.get() == UNSET_OBJECT_ID { rc.object_id.set(id); } rc @@ -293,6 +308,19 @@ impl RObject { matches!(self.tt, RType::Nil) } + pub fn set_ivar(&self, key: &str, value: Rc) { + self.ivar.borrow_mut().insert(key.to_string(), value); + } + + pub fn get_ivar(&self, key: &str) -> Rc { + self.ivar + .borrow() + .get(key) + .cloned() + .or_else(|| Some(RObject::nil().to_refcount_assigned())) + .unwrap() + } + // TODO: implment Object#hash pub fn as_hash_key(&self) -> Result { match &self.value { @@ -824,7 +852,6 @@ impl std::ops::Deref for RClass { #[derive(Debug, Clone)] pub struct RInstance { pub class: Rc, - pub ivar: RefCell>>, pub ref_count: usize, } @@ -834,7 +861,6 @@ type RDataContainer = Box; #[derive(Debug, Clone)] pub struct RData { pub class: Rc, - pub ivar: RefCell>>, pub data: RefCell>>, pub ref_count: usize, } diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 48e3cf2..4bd9c50 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -158,11 +158,11 @@ impl VM { tt: RType::Instance, value: RValue::Instance(RInstance { class, - ivar: RefCell::new(HashMap::new()), ref_count: 1, }), object_id: 0.into(), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), } .to_refcount_assigned(); if self.current_regs()[0].is_none() { From 38b889c5440b8bd40d00cb75bc6592e02f2b2fe7 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 17 Dec 2025 00:51:59 +0900 Subject: [PATCH 080/314] Add testcase --- mrubyedge/tests/klass.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/mrubyedge/tests/klass.rs b/mrubyedge/tests/klass.rs index 6f4e01f..d84856f 100644 --- a/mrubyedge/tests/klass.rs +++ b/mrubyedge/tests/klass.rs @@ -226,3 +226,41 @@ fn class_inheritance_class_method_test() { .unwrap(); assert_eq!(result, 124); } + +#[test] +fn class_can_have_singleton_instance_variables() { + let code = r#" + class Hello + def self.set_world(value) + @world = value + end + + def self.get_world + @world + end + end + + def test_main_0 + Hello.get_world + end + + def test_main_1 + Hello.set_world("hello") + Hello.get_world + end + "#; + let binary = mrbc_compile("class_singleton_ivar", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_main_0", &args).unwrap(); + assert!(result.as_ref().is_nil()); + + let result = mrb_funcall(&mut vm, None, "test_main_1", &args).unwrap(); + let value: String = result + .as_ref() + .try_into() + .expect("get_world should return string"); + assert_eq!(value, "hello"); +} From 18f3deb8922795e136ad589a7806c11cf9858e79 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 17 Dec 2025 01:24:24 +0900 Subject: [PATCH 081/314] Fix regexp --- mrubyedge/src/yamrb/prelude/regexp.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mrubyedge/src/yamrb/prelude/regexp.rs b/mrubyedge/src/yamrb/prelude/regexp.rs index 6803beb..2123f6e 100644 --- a/mrubyedge/src/yamrb/prelude/regexp.rs +++ b/mrubyedge/src/yamrb/prelude/regexp.rs @@ -123,7 +123,6 @@ pub fn mrb_regexp_new(vm: &mut VM, args: &[Rc]) -> Result, }; let regexp_data = Rc::new(RData { class: vm.get_class_by_name("Regexp"), - ivar: RefCell::new(HashMap::new()), data: RefCell::new(Some(Rc::new(Box::new(regexp) as Box))), ref_count: 1, }); @@ -132,6 +131,7 @@ pub fn mrb_regexp_new(vm: &mut VM, args: &[Rc]) -> Result, value: RValue::Data(regexp_data), object_id: Cell::new(0), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), } .to_refcount_assigned()) } @@ -208,7 +208,6 @@ fn mrb_regexp_match(vm: &mut VM, args: &[Rc]) -> Result, Er }; let matchdata_data = Rc::new(RData { class: vm.get_class_by_name("MatchData"), - ivar: RefCell::new(HashMap::new()), data: RefCell::new(Some(Rc::new(Box::new(matchdata) as Box))), ref_count: 1, }); @@ -217,6 +216,7 @@ fn mrb_regexp_match(vm: &mut VM, args: &[Rc]) -> Result, Er value: RValue::Data(matchdata_data), object_id: Cell::new(0), singleton_class: RefCell::new(None), + ivar: RefCell::new(HashMap::new()), } .to_refcount_assigned()) } From eced3ef92cf4810e2590a61240d90c4e039195a5 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 17 Dec 2025 01:06:46 +0900 Subject: [PATCH 082/314] Add so basic proc wrapper --- mrubyedge/examples/proc.rb | 14 ++++++++++++++ mrubyedge/src/yamrb/prelude/mod.rs | 2 ++ mrubyedge/src/yamrb/prelude/object.rs | 18 +++++++++++++++++ mrubyedge/src/yamrb/prelude/proc.rs | 28 +++++++++++++++++++++++++++ 4 files changed, 62 insertions(+) create mode 100644 mrubyedge/examples/proc.rb create mode 100644 mrubyedge/src/yamrb/prelude/proc.rs diff --git a/mrubyedge/examples/proc.rb b/mrubyedge/examples/proc.rb new file mode 100644 index 0000000..1200b4d --- /dev/null +++ b/mrubyedge/examples/proc.rb @@ -0,0 +1,14 @@ +test = lambda { puts "Hello, world!" } +test.call + +test2 = ->(a) { puts "Hello again! #{a}" } +test2.call("MrubyEdge") + +test3 = Proc.new { |a, b| puts "Hello from Proc! #{a}, #{b}" } +test3.call("Foo", "Bar") + +value = 10 +incrementer = Proc.new { |x| value = x + value } +puts incrementer.call(5) +puts incrementer.call(10) +puts incrementer.call(100) \ No newline at end of file diff --git a/mrubyedge/src/yamrb/prelude/mod.rs b/mrubyedge/src/yamrb/prelude/mod.rs index 707b730..fbfc7f9 100644 --- a/mrubyedge/src/yamrb/prelude/mod.rs +++ b/mrubyedge/src/yamrb/prelude/mod.rs @@ -13,6 +13,7 @@ pub mod integer; pub mod module; pub mod nilclass; pub mod object; +pub mod proc; pub mod range; pub mod shared_memory; pub mod string; @@ -32,6 +33,7 @@ pub fn prelude(vm: &mut VM) { trueclass::initialize_trueclass(vm); falseclass::initialize_falseclass(vm); symbol::initialize_symbol(vm); + proc::initialize_proc(vm); string::initialize_string(vm); array::initialize_array(vm); hash::initialize_hash(vm); diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index c6c9ba3..c782ce9 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -72,6 +72,13 @@ pub(crate) fn initialize_object(vm: &mut VM) { Box::new(mrb_object_raise), ); mrb_define_cmethod(vm, object_class.clone(), "nil?", Box::new(mrb_object_nil_p)); + mrb_define_cmethod( + vm, + object_class.clone(), + "lambda", + Box::new(mrb_object_lambda), + ); + mrb_define_cmethod(vm, object_class, "proc", Box::new(mrb_object_lambda)); // define global consts: vm.consts.insert( @@ -206,6 +213,17 @@ pub fn mrb_object_initialize(_vm: &mut VM, _args: &[Rc]) -> Result]) -> Result, Error> { + let proc = args[args.len() - 1].clone(); + if matches!(proc.value, RValue::Proc(_)) { + Ok(proc) + } else { + Err(Error::RuntimeError( + "Object#lambda expects a Proc as the last argument".to_string(), + )) + } +} + #[test] fn test_mrb_object_is_equal() { let mut vm = VM::empty(); diff --git a/mrubyedge/src/yamrb/prelude/proc.rs b/mrubyedge/src/yamrb/prelude/proc.rs new file mode 100644 index 0000000..379b82e --- /dev/null +++ b/mrubyedge/src/yamrb/prelude/proc.rs @@ -0,0 +1,28 @@ +use std::rc::Rc; + +use crate::{ + Error, + yamrb::{ + helpers::{mrb_call_block, mrb_define_class_cmethod, mrb_define_cmethod}, + value::*, + vm::VM, + }, +}; + +pub(crate) fn initialize_proc(vm: &mut VM) { + let proc_class = vm.define_standard_class("Proc"); + + mrb_define_class_cmethod(vm, proc_class.clone(), "new", Box::new(mrb_proc_new)); + + mrb_define_cmethod(vm, proc_class.clone(), "call", Box::new(mrb_proc_call)); +} + +fn mrb_proc_new(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let block = args[0].clone(); + Ok(block) +} + +pub fn mrb_proc_call(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + mrb_call_block(vm, this.clone(), None, args) +} From c276dd024da19948cf9afc50244cd02416544616 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 17 Dec 2025 01:20:24 +0900 Subject: [PATCH 083/314] Add router testcase --- mrubyedge/examples/proc2.rb | 22 ++++ mrubyedge/examples/runscript.rs | 6 +- mrubyedge/tests/proc.rs | 214 ++++++++++++++++++++++++++++++++ 3 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 mrubyedge/examples/proc2.rb create mode 100644 mrubyedge/tests/proc.rs diff --git a/mrubyedge/examples/proc2.rb b/mrubyedge/examples/proc2.rb new file mode 100644 index 0000000..f77ad6a --- /dev/null +++ b/mrubyedge/examples/proc2.rb @@ -0,0 +1,22 @@ +class Router + def self.get path, &block + puts "Registered GET #{path}" + @routes ||= {} + @routes[path] = block + end + + def self.request path + if @routes && @routes[path] + @routes[path].call(path) + else + puts "No route for #{path}" + end + end +end + +Router.get "/home" do |path| + puts "Inside /home route: #{path}" +end + +Router.request "/home" +Router.request "/about" \ No newline at end of file diff --git a/mrubyedge/examples/runscript.rs b/mrubyedge/examples/runscript.rs index 1a5d94c..50e1226 100644 --- a/mrubyedge/examples/runscript.rs +++ b/mrubyedge/examples/runscript.rs @@ -32,9 +32,9 @@ fn main() -> Result<(), std::io::Error> { let mut rite = mrubyedge::rite::load(&bin).unwrap(); // dbg!(&rite); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - if is_verbose { - dbg!(&vm.irep); - } + //if is_verbose { + // dbg!(&vm.irep); + //} let res = vm.run().unwrap(); remove_file("/tmp/__tmp__.mrb")?; diff --git a/mrubyedge/tests/proc.rs b/mrubyedge/tests/proc.rs new file mode 100644 index 0000000..a039635 --- /dev/null +++ b/mrubyedge/tests/proc.rs @@ -0,0 +1,214 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn lambda_test() { + let code = r#" + def test_lambda + test = lambda { 42 } + test.call + end + "#; + let binary = mrbc_compile("lambda", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result: i64 = mrb_funcall(&mut vm, None, "test_lambda", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 42); +} + +#[test] +fn lambda_with_arg_test() { + let code = r#" + def test_lambda_arg + test = ->(a) { a * 2 } + test.call(21) + end + "#; + let binary = mrbc_compile("lambda_arg", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result: i64 = mrb_funcall(&mut vm, None, "test_lambda_arg", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 42); +} + +#[test] +fn proc_new_test() { + let code = r#" + def test_proc_new + test = Proc.new { |a, b| a + b } + test.call(10, 32) + end + "#; + let binary = mrbc_compile("proc_new", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result: i64 = mrb_funcall(&mut vm, None, "test_proc_new", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 42); +} + +#[test] +fn proc_closure_test() { + let code = r#" + def test_proc_closure + value = 10 + incrementer = Proc.new { |x| value = x + value } + result1 = incrementer.call(5) + result2 = incrementer.call(10) + result3 = incrementer.call(100) + [result1, result2, result3] + end + "#; + let binary = mrbc_compile("proc_closure", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_proc_closure", &args).unwrap(); + + if let mrubyedge::yamrb::value::RValue::Array(arr) = &result.value { + let arr = arr.borrow(); + assert_eq!(arr.len(), 3); + + let r1: i64 = arr[0].as_ref().try_into().unwrap(); + let r2: i64 = arr[1].as_ref().try_into().unwrap(); + let r3: i64 = arr[2].as_ref().try_into().unwrap(); + + assert_eq!(r1, 15); // 10 + 5 + assert_eq!(r2, 25); // 15 + 10 + assert_eq!(r3, 125); // 25 + 100 + } else { + panic!("Expected array result"); + } +} + +#[test] +fn proc_block_test() { + let code = r#" + def accept_block(&block) + block.call("test value") + end + + def test_proc_block + accept_block do |x| + x.size + end + end + "#; + let binary = mrbc_compile("proc_block", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result: i64 = mrb_funcall(&mut vm, None, "test_proc_block", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 10); +} + +#[test] +fn proc_class_variable_test() { + let code = r#" + class Router + def self.get path, &block + @routes ||= {} + @routes[path] = block + end + + def self.request path + if @routes && @routes[path] + @routes[path].call(path) + else + nil + end + end + end + + def test_router + Router.get "/home" do |path| + path.size + end + + result1 = Router.request "/home" + result2 = Router.request "/about" + [result1, result2] + end + "#; + let binary = mrbc_compile("router", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_router", &args).unwrap(); + + if let mrubyedge::yamrb::value::RValue::Array(arr) = &result.value { + let arr = arr.borrow(); + assert_eq!(arr.len(), 2); + + // First result should be 5 (length of "/home") + let r1: i64 = arr[0].as_ref().try_into().unwrap(); + assert_eq!(r1, 5); + + // Second result should be nil + assert!(matches!(&arr[1].value, mrubyedge::yamrb::value::RValue::Nil)); + } else { + panic!("Expected array result"); + } +} + +#[test] +fn proc_call_method_test() { + let code = r#" + def test_proc_call + my_proc = Proc.new { |x| x * 2 } + my_proc.call(21) + end + "#; + let binary = mrbc_compile("proc_call", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result: i64 = mrb_funcall(&mut vm, None, "test_proc_call", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 42); +} From 040c252eb10b2ea948a5743deaaa95206a43f755 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 17 Dec 2025 01:29:30 +0900 Subject: [PATCH 084/314] fmt --- mrubyedge/tests/proc.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/mrubyedge/tests/proc.rs b/mrubyedge/tests/proc.rs index a039635..df0f935 100644 --- a/mrubyedge/tests/proc.rs +++ b/mrubyedge/tests/proc.rs @@ -93,17 +93,17 @@ fn proc_closure_test() { // Assert let args = vec![]; let result = mrb_funcall(&mut vm, None, "test_proc_closure", &args).unwrap(); - + if let mrubyedge::yamrb::value::RValue::Array(arr) = &result.value { let arr = arr.borrow(); assert_eq!(arr.len(), 3); - + let r1: i64 = arr[0].as_ref().try_into().unwrap(); let r2: i64 = arr[1].as_ref().try_into().unwrap(); let r3: i64 = arr[2].as_ref().try_into().unwrap(); - - assert_eq!(r1, 15); // 10 + 5 - assert_eq!(r2, 25); // 15 + 10 + + assert_eq!(r1, 15); // 10 + 5 + assert_eq!(r2, 25); // 15 + 10 assert_eq!(r3, 125); // 25 + 100 } else { panic!("Expected array result"); @@ -174,17 +174,20 @@ fn proc_class_variable_test() { // Assert let args = vec![]; let result = mrb_funcall(&mut vm, None, "test_router", &args).unwrap(); - + if let mrubyedge::yamrb::value::RValue::Array(arr) = &result.value { let arr = arr.borrow(); assert_eq!(arr.len(), 2); - + // First result should be 5 (length of "/home") let r1: i64 = arr[0].as_ref().try_into().unwrap(); assert_eq!(r1, 5); - + // Second result should be nil - assert!(matches!(&arr[1].value, mrubyedge::yamrb::value::RValue::Nil)); + assert!(matches!( + &arr[1].value, + mrubyedge::yamrb::value::RValue::Nil + )); } else { panic!("Expected array result"); } From 9dc075056e8a76d23a35b9306858263e90fb7356 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 17 Dec 2025 01:33:49 +0900 Subject: [PATCH 085/314] Bump --- Cargo.lock | 12 ++++++------ mrubyedge/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f735958..55e5efc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -572,10 +572,10 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01f539c27cbe88a87158b9c366354d03fb5f81b5fecfdb0af04b588e45f76ad3" dependencies = [ - "criterion", "getrandom", - "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -583,11 +583,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01f539c27cbe88a87158b9c366354d03fb5f81b5fecfdb0af04b588e45f76ad3" +version = "1.0.11" dependencies = [ + "criterion", "getrandom", + "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -600,7 +600,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.0.10", "nom", "rand", "syn", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 34c9129..29d0393 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.0.10" +version = "1.0.11" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From 23bf8695aa38386b3f86ce5ab1c02b207c79ac10 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 17 Dec 2025 01:42:39 +0900 Subject: [PATCH 086/314] Bump --- Cargo.lock | 18 +++++++++--------- mrubyedge-cli/Cargo.toml | 6 +++--- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 55e5efc..ed0ffb0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -559,9 +559,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mruby-compiler2-sys" -version = "0.1.1" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b1e90e55600aa830bc7381ce7de6a8fbfd7dc91727dd5116dbb92487e5b47c2" +checksum = "4250fa0ca67c667619da5fcfb9c1763ed8fa73108438e3db698f5b7687f33abc" dependencies = [ "bindgen 0.72.1", "cc", @@ -571,11 +571,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01f539c27cbe88a87158b9c366354d03fb5f81b5fecfdb0af04b588e45f76ad3" +version = "1.0.11" dependencies = [ + "criterion", "getrandom", + "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -584,10 +584,10 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb4e5b3db926932706d40c2d3f0af54bb9f6ba0a2b039f6ea237d95fa7e0fe3" dependencies = [ - "criterion", "getrandom", - "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -595,12 +595,12 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.0.10" +version = "1.0.11" dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.10", + "mrubyedge 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index fe0333c..b8bcda7 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.0.10" +version = "1.0.11" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -13,8 +13,8 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } -mruby-compiler2-sys = "0.1.1" -mrubyedge = { version = "1.0.10", features = ["default", "mruby-regexp"] } +mruby-compiler2-sys = "0.2.0" +mrubyedge = { version = "1.0.11", features = ["default", "mruby-regexp"] } rand = "0.8.5" nom = "7.1.3" askama = "0.12.1" From b5bce6e96774ae1e783df8aa3259942892179175 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 17 Dec 2025 22:38:34 +0900 Subject: [PATCH 087/314] Fix magic number... --- mrubyedge/examples/break.rb | 6 +++++- mrubyedge/src/yamrb/helpers.rs | 9 ++++++--- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/mrubyedge/examples/break.rb b/mrubyedge/examples/break.rb index 20486c9..1fe4bd0 100644 --- a/mrubyedge/examples/break.rb +++ b/mrubyedge/examples/break.rb @@ -37,4 +37,8 @@ def while_break_2 end return end -end \ No newline at end of file +end + +p times_break +p times_break_2 +p while_break \ No newline at end of file diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index ba0001b..35a816c 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -158,12 +158,15 @@ pub fn mrb_funcall( Some((method_id, owner_module)), ) } else { - vm.current_regs_offset += 2; // FIXME: magick number? - vm.current_regs()[0].replace(recv.clone()); + let prev = vm.current_regs()[0].replace(recv.clone()); let func = vm.fn_table[method.func.unwrap()].clone(); let res = func(vm, args); - vm.current_regs_offset -= 2; + if let Some(prev) = prev { + vm.current_regs()[0].replace(prev); + } else { + vm.current_regs()[0].take(); + } res } From b61d4b7b3c6070da2ea0cc44d3ff6cd5943deebd Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 17 Dec 2025 22:51:51 +0900 Subject: [PATCH 088/314] Fix pack/unpack C/c pair --- mrubyedge/src/yamrb/prelude/string.rs | 4 +-- mrubyedge/tests/pack.rs | 52 +++++++++++++++++++++++++++ mrubyedge/tests/unpack.rs | 25 ------------- 3 files changed, 54 insertions(+), 27 deletions(-) create mode 100644 mrubyedge/tests/pack.rs delete mode 100644 mrubyedge/tests/unpack.rs diff --git a/mrubyedge/src/yamrb/prelude/string.rs b/mrubyedge/src/yamrb/prelude/string.rs index 97ebf30..303729b 100644 --- a/mrubyedge/src/yamrb/prelude/string.rs +++ b/mrubyedge/src/yamrb/prelude/string.rs @@ -119,12 +119,12 @@ fn mrb_string_unpack(vm: &mut VM, args: &[Rc]) -> Result, E value as i64 } b'C' => { - let value = i8::from_le_bytes(bytes_of::<1>(&value, cursor)?); + let value = u8::from_le_bytes(bytes_of::<1>(&value, cursor)?); cursor += 1; value as i64 } b'c' => { - let value = u8::from_le_bytes(bytes_of::<1>(&value, cursor)?); + let value = i8::from_le_bytes(bytes_of::<1>(&value, cursor)?); cursor += 1; value as i64 } diff --git a/mrubyedge/tests/pack.rs b/mrubyedge/tests/pack.rs new file mode 100644 index 0000000..bcdca7d --- /dev/null +++ b/mrubyedge/tests/pack.rs @@ -0,0 +1,52 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use std::rc::Rc; + +use helpers::*; +use mrubyedge::yamrb::{prelude::array::mrb_array_get_index, value::RObject}; + +#[test] +fn pack_unpack_test() { + let code = " +def pack_unpack + data = [100, 150, 200, 250].pack('C C C C') + result = data.unpack('C C C C') + result +end"; + let binary = mrbc_compile("pack_unpack", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "pack_unpack", &args).unwrap(); + for (i, expected) in [100, 150, 200, 250].iter().enumerate() { + let args = vec![Rc::new(RObject::integer(i as i64))]; + let value = mrb_array_get_index(result.clone(), &args).expect("getting index failed"); + let value: i64 = value.as_ref().try_into().expect("value is not integer"); + assert_eq!(value, *expected); + } +} + +#[test] +fn unpack_test() { + let code = " +def sum_unpack + data = \"\\x01\\x02\\x03\\x04\" + result = data.unpack('c c c c') + result[0] + result[1] + result[2] + result[3] +end"; + let binary = mrbc_compile("shared_memory", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // Assert + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "sum_unpack", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 10); +} diff --git a/mrubyedge/tests/unpack.rs b/mrubyedge/tests/unpack.rs deleted file mode 100644 index 400c771..0000000 --- a/mrubyedge/tests/unpack.rs +++ /dev/null @@ -1,25 +0,0 @@ -extern crate mec_mrbc_sys; -extern crate mrubyedge; - -mod helpers; -use helpers::*; - -#[test] -fn unpack_test() { - let code = " -def sum_unpack - data = \"\\x01\\x02\\x03\\x04\" - result = data.unpack('c c c c') - result[0] + result[1] + result[2] + result[3] -end"; - let binary = mrbc_compile("shared_memory", code); - let mut rite = mrubyedge::rite::load(&binary).unwrap(); - let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - vm.run().unwrap(); - - // Assert - let args = vec![]; - let result = mrb_funcall(&mut vm, None, "sum_unpack", &args).unwrap(); - let result: i64 = result.as_ref().try_into().unwrap(); - assert_eq!(result, 10); -} From 98ed2361912cef7aceb6800944e01d6ec10f99e1 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 17 Dec 2025 23:18:28 +0900 Subject: [PATCH 089/314] Define Kernel#wasm? --- mrubyedge/src/yamrb/prelude/object.rs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index c782ce9..f241d30 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -78,7 +78,12 @@ pub(crate) fn initialize_object(vm: &mut VM) { "lambda", Box::new(mrb_object_lambda), ); - mrb_define_cmethod(vm, object_class, "proc", Box::new(mrb_object_lambda)); + mrb_define_cmethod( + vm, + object_class.clone(), + "proc", + Box::new(mrb_object_lambda), + ); // define global consts: vm.consts.insert( @@ -97,6 +102,7 @@ pub(crate) fn initialize_object(vm: &mut VM) { "RUBY_ENGINE".to_string(), Rc::new(RObject::string(crate::yamrb::vm::ENGINE.to_string())), ); + mrb_define_cmethod(vm, object_class.clone(), "wasm?", Box::new(mrb_is_wasm)); } pub fn mrb_self(vm: &mut VM, _args: &[Rc]) -> Result, Error> { @@ -224,6 +230,11 @@ pub fn mrb_object_lambda(_vm: &mut VM, args: &[Rc]) -> Result]) -> Result, Error> { + let is_wasm = cfg!(target_arch = "wasm32"); + Ok(Rc::new(RObject::boolean(is_wasm))) +} + #[test] fn test_mrb_object_is_equal() { let mut vm = VM::empty(); From e5cc93541651201cc0353c7291149122c096a357 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 17 Dec 2025 23:21:02 +0900 Subject: [PATCH 090/314] Bump --- Cargo.lock | 18 +++++++++--------- mrubyedge-cli/Cargo.toml | 4 ++-- mrubyedge/Cargo.toml | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed0ffb0..75a35d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -559,9 +559,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mruby-compiler2-sys" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4250fa0ca67c667619da5fcfb9c1763ed8fa73108438e3db698f5b7687f33abc" +checksum = "feba2d9bebead1e86da7c36f229bd92f7a81e193dab29d982815a2a776811f19" dependencies = [ "bindgen 0.72.1", "cc", @@ -572,10 +572,10 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb4e5b3db926932706d40c2d3f0af54bb9f6ba0a2b039f6ea237d95fa7e0fe3" dependencies = [ - "criterion", "getrandom", - "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -583,11 +583,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb4e5b3db926932706d40c2d3f0af54bb9f6ba0a2b039f6ea237d95fa7e0fe3" +version = "1.0.12" dependencies = [ + "criterion", "getrandom", + "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -595,12 +595,12 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.0.11" +version = "1.0.12" dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.0.11", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index b8bcda7..530e2dc 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.0.11" +version = "1.0.12" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -13,7 +13,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } -mruby-compiler2-sys = "0.2.0" +mruby-compiler2-sys = "0.2.2" mrubyedge = { version = "1.0.11", features = ["default", "mruby-regexp"] } rand = "0.8.5" nom = "7.1.3" diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 29d0393..65ca354 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.0.11" +version = "1.0.12" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From a011a15be674e025e9ae73fea89304e115b5b1c1 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 17 Dec 2025 23:21:52 +0900 Subject: [PATCH 091/314] Release mrubyedge 1.0.12 --- Cargo.lock | 12 ++++++------ mrubyedge-cli/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 75a35d9..f1d40e8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -571,11 +571,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb4e5b3db926932706d40c2d3f0af54bb9f6ba0a2b039f6ea237d95fa7e0fe3" +version = "1.0.12" dependencies = [ + "criterion", "getrandom", + "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -584,10 +584,10 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b20b3a61b50f381ff7e47641124a9c377b24a97653df4696d15d43e5a6d38846" dependencies = [ - "criterion", "getrandom", - "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -600,7 +600,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.11", + "mrubyedge 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 530e2dc..cb430e6 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } mruby-compiler2-sys = "0.2.2" -mrubyedge = { version = "1.0.11", features = ["default", "mruby-regexp"] } +mrubyedge = { version = "1.0.12", features = ["default", "mruby-regexp"] } rand = "0.8.5" nom = "7.1.3" askama = "0.12.1" From 5722b84fa1b0648bedf4ee1a87f5fec1ff1b227f Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 18 Dec 2025 00:04:47 +0900 Subject: [PATCH 092/314] Support -o option in wasm subcommand to specify output wasm file path --- mrubyedge-cli/src/subcommands/wasm.rs | 63 ++++++++++++++------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/mrubyedge-cli/src/subcommands/wasm.rs b/mrubyedge-cli/src/subcommands/wasm.rs index 2cedc96..8a25943 100644 --- a/mrubyedge-cli/src/subcommands/wasm.rs +++ b/mrubyedge-cli/src/subcommands/wasm.rs @@ -3,7 +3,7 @@ extern crate rand; use clap::Args; use std::{ - fs::File, + fs::{File, rename}, io::Read, path::{Path, PathBuf}, process::Command, @@ -15,18 +15,20 @@ use rand::distributions::{Alphanumeric, DistString}; use crate::rbs_parser; use crate::template; -const MRUBY_EDGE_DEFAULT_VERSION: &'static str = ">= 1"; +const MRUBY_EDGE_DEFAULT_VERSION: &str = ">= 1"; #[derive(Debug, Clone, Args)] pub struct WasmArgs { #[arg(short = 'f', long)] - fnname: Option, + fnname: Option, #[arg(short = 'm', long)] mruby_edge_version: Option, #[arg(short = 'F', long)] features: Vec, #[arg(short = 'W', long)] no_wasi: bool, + #[arg(short = 'o', long)] + out_path: Option, #[arg(long)] skip_cleanup: bool, #[arg(long)] @@ -41,13 +43,13 @@ pub struct WasmArgs { fn sh_do(sharg: &str, debug: bool) -> Result<(), Box> { println!("running: `{}`", sharg); let out = Command::new("/bin/sh").args(["-c", sharg]).output()?; - if debug && out.stdout.len() != 0 { + if debug && !out.stdout.is_empty() { println!( "stdout:\n{}", String::from_utf8_lossy(&out.stdout).to_string().trim() ); } - if debug && out.stderr.len() != 0 { + if debug && !out.stderr.is_empty() { println!( "stderr:\n{}", String::from_utf8_lossy(&out.stderr).to_string().trim() @@ -140,7 +142,6 @@ pub fn execute(args: WasmArgs) -> Result<(), Box> { let export_rbs_fname = format!("{}.export.rbs", fname); let export_rbs = mrubyfile.parent().unwrap().join(&export_rbs_fname); - let cont: String; let mut ftypes_imports = Vec::new(); let import_rbs_fname = format!("{}.import.rbs", fname); @@ -170,7 +171,7 @@ pub fn execute(args: WasmArgs) -> Result<(), Box> { } } - if export_rbs.exists() { + let cont = if export_rbs.exists() { debug_println( args.verbose, &format!( @@ -199,11 +200,11 @@ pub fn execute(args: WasmArgs) -> Result<(), Box> { let lib_rs = template::LibRs { file_basename: &fname, - ftypes: &&ftypes, + ftypes: &ftypes, ftypes_imports: &ftypes_imports, }; - let rendered = lib_rs.render()?; - cont = rendered; + + lib_rs.render()? } else { if fnname.is_none() { panic!("--fnname FNNAME should be specified when export.rbs does not exist") @@ -211,7 +212,7 @@ pub fn execute(args: WasmArgs) -> Result<(), Box> { let fnname = fnname.unwrap(); let ftypes = vec![template::RustFnTemplate { - func_name: fnname.to_str().unwrap(), + func_name: &fnname, args_decl: "", args_let_vec: "vec![]", str_args_converter: "", @@ -222,14 +223,14 @@ pub fn execute(args: WasmArgs) -> Result<(), Box> { let lib_rs = template::LibRs { file_basename: &fname, - ftypes: &&ftypes, + ftypes: &ftypes, ftypes_imports: &ftypes_imports, }; - let rendered = lib_rs.render()?; - cont = rendered; - } + + lib_rs.render()? + }; debug_println(args.verbose, "[debug] will generate main.rs:"); - debug_println(args.verbose, &format!("{}", &cont)); + debug_println(args.verbose, &cont); std::fs::write("src/lib.rs", cont)?; let target = if args.no_wasi { @@ -242,30 +243,32 @@ pub fn execute(args: WasmArgs) -> Result<(), Box> { &format!("cargo build --target {} --release", target), args.verbose, )?; - sh_do( - &format!( - "cp ./target/{}/release/mywasm.wasm {}/{}.wasm", - target, - &pwd.to_str().unwrap(), - &fname.to_string() - ), - args.verbose, - )?; + + let output_path = if let Some(out_path) = &args.out_path { + std::fs::canonicalize(out_path).unwrap_or_else(|_| pwd.join(out_path)) + } else { + pwd.join(format!("{}.wasm", &fname)) + }; + + let from = format!("./target/{}/release/mywasm.wasm", target); + let to = output_path.to_str().expect("Invalid output path"); + rename(from, to)?; if args.skip_cleanup { println!( "debug: working directory for compile wasm is remained in {}", std::env::current_dir()?.as_os_str().to_str().unwrap() ); } else { - sh_do( - &format!("cd .. && rm -rf work-mrubyedge-{}", &suffix), - args.verbose, - )?; + std::env::set_current_dir("..")?; + sh_do(&format!("rm -rf {}", &dirname), args.verbose)?; } std::env::set_current_dir(pwd)?; - println!("[ok] wasm file is generated: {}.wasm", &fname); + println!( + "[ok] wasm file is generated: {}", + &output_path.to_string_lossy() + ); Ok(()) } From d356ad1de9fa6b2e7f87408d8e3517beedf005f2 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 18 Dec 2025 00:35:34 +0900 Subject: [PATCH 093/314] Support yield (very simple implementation) --- mrubyedge/examples/breaking.rb | 2 +- mrubyedge/src/yamrb/optable.rs | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/mrubyedge/examples/breaking.rb b/mrubyedge/examples/breaking.rb index a84c8a1..af201c1 100644 --- a/mrubyedge/examples/breaking.rb +++ b/mrubyedge/examples/breaking.rb @@ -5,5 +5,5 @@ def onetimes onetimes do puts "dummy" - break + # break end \ No newline at end of file diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 9441545..13e345f 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -325,9 +325,9 @@ pub(crate) fn consume_expr( // BREAK => { // // op_break(vm, &operand)?; // } - // BLKPUSH => { - // // op_blkpush(vm, &operand)?; - // } + BLKPUSH => { + op_blkpush(vm, operand)?; + } ADD => { op_add(vm, operand)?; } @@ -1111,6 +1111,14 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { Ok(()) } +pub(crate) fn op_blkpush(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { + let (a, _s) = operand.as_bs()?; + let n = dbg!(vm.current_callinfo.as_ref().unwrap().n_args); + let block = vm.get_current_regs_cloned(n + 1)?; + vm.current_regs()[a as usize].replace(block); + Ok(()) +} + pub(crate) fn op_add(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; let b = a + 1; From 4a6ede0089e3ec00be760c698ba86cb8e7b2411b Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 18 Dec 2025 09:42:13 +0900 Subject: [PATCH 094/314] Implement very basic pattern --- mrubyedge/examples/breaking.rb | 6 +-- mrubyedge/src/yamrb/helpers.rs | 10 +++- mrubyedge/src/yamrb/optable.rs | 73 +++++++++++++++++++++++--- mrubyedge/src/yamrb/prelude/array.rs | 2 +- mrubyedge/src/yamrb/prelude/hash.rs | 2 +- mrubyedge/src/yamrb/prelude/integer.rs | 2 +- mrubyedge/src/yamrb/prelude/proc.rs | 2 +- mrubyedge/src/yamrb/prelude/range.rs | 2 +- mrubyedge/src/yamrb/vm.rs | 43 +++++++++++++++ 9 files changed, 125 insertions(+), 17 deletions(-) diff --git a/mrubyedge/examples/breaking.rb b/mrubyedge/examples/breaking.rb index af201c1..fea5737 100644 --- a/mrubyedge/examples/breaking.rb +++ b/mrubyedge/examples/breaking.rb @@ -3,7 +3,7 @@ def onetimes puts "whoa?" end -onetimes do +p(onetimes do puts "dummy" - # break -end \ No newline at end of file + break 42 +end) \ No newline at end of file diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index 35a816c..3f4f65b 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -14,12 +14,13 @@ fn call_block( recv: Rc, args: &[Rc], method_info: Option<(RSym, Rc)>, + return_register: usize, ) -> Result, Error> { let (method_id, method_owner) = match method_info { Some((id, owner)) => (id, Some(owner)), None => (RSym::new("".to_string()), None), }; - push_callinfo(vm, method_id, args.len(), method_owner); + push_callinfo(vm, method_id, args.len(), method_owner, return_register); let old_callinfo = vm.current_callinfo.take(); @@ -102,6 +103,7 @@ pub fn mrb_call_block( block: Rc, recv: Option>, args: &[Rc], + return_register: usize, ) -> Result, Error> { let block = match &block.value { RValue::Proc(p) => p.clone(), @@ -114,7 +116,7 @@ pub fn mrb_call_block( .clone() .ok_or_else(|| Error::RuntimeError("No block self assigned".to_string()))?, }; - call_block(vm, block, recv, args, None) + call_block(vm, block, recv, args, None, return_register) } /// Calls a method on an object by name with the given arguments. @@ -156,11 +158,14 @@ pub fn mrb_funcall( recv.clone(), args, Some((method_id, owner_module)), + 0, // unused ) } else { let prev = vm.current_regs()[0].replace(recv.clone()); + vm.break_level += 1; // Enter new break level let func = vm.fn_table[method.func.unwrap()].clone(); + vm.break_level -= 1; // Exit break level after call let res = func(vm, args); if let Some(prev) = prev { vm.current_regs()[0].replace(prev); @@ -187,6 +192,7 @@ pub fn mrb_call_inspect(vm: &mut VM, recv: Rc) -> Result, E recv.clone(), &[], Some((method_id, owner_module)), + 0, // unused ) } else { vm.current_regs_offset += 2; // FIXME: magick number? diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 13e345f..bcae138 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -322,9 +322,9 @@ pub(crate) fn consume_expr( // RETURN_BLK => { // // op_return_blk(vm, &operand)?; // } - // BREAK => { - // // op_break(vm, &operand)?; - // } + BREAK => { + op_break(vm, operand)?; + } BLKPUSH => { op_blkpush(vm, operand)?; } @@ -478,6 +478,7 @@ pub(crate) fn push_callinfo( method_id: RSym, n_args: usize, method_owner: Option>, + return_reg: usize, ) { let callinfo = CALLINFO { prev: vm.current_callinfo.clone(), @@ -486,9 +487,11 @@ pub(crate) fn push_callinfo( pc: vm.pc.get(), current_regs_offset: vm.current_regs_offset, n_args, + return_reg, target_class: vm.target_class.clone(), method_owner, }; + vm.break_level += 1; vm.current_callinfo = Some(Rc::new(callinfo)); } @@ -930,7 +933,7 @@ pub(crate) fn do_op_send( return Ok(()); } - push_callinfo(vm, method_id, c as usize, Some(owner_module)); + push_callinfo(vm, method_id, c as usize, Some(owner_module), a as usize); vm.pc.set(0); vm.current_irep = method.irep.ok_or_else(|| Error::internal("empry irep"))?; @@ -939,7 +942,7 @@ pub(crate) fn do_op_send( } pub(crate) fn op_call(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> { - push_callinfo(vm, "".into(), 0, None); + push_callinfo(vm, "".into(), 0, None, 0); vm.pc.set(0); let proc = vm.current_regs()[0] @@ -1012,6 +1015,7 @@ pub(crate) fn op_super(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { method.sym_id.clone().unwrap(), b as usize, Some(next_owner), + a as usize, ); vm.pc.set(0); @@ -1096,6 +1100,52 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { vm.flag_preemption.set(true); return Ok(()); } + vm.break_level -= 1; + + let ci = ci.unwrap(); + if let Some(prev) = &ci.prev { + vm.current_callinfo.replace(prev.clone()); + } + vm.current_irep = ci.pc_irep.clone(); + vm.pc.set(ci.pc); + vm.current_regs_offset = ci.current_regs_offset; + vm.target_class = ci.target_class.clone(); + if vm.current_regs()[0].is_none() { + todo!("debug"); + } + Ok(()) +} + +// TODO: Dont repeat yourself +pub(crate) fn return_without_value(vm: &mut VM) -> Result<(), Error> { + let old_irep = vm.current_irep.clone(); + let nregs = old_irep.nregs; + + let regs0_cloned: Vec<_> = vm.current_regs()[0..nregs].to_vec(); + if let Some(environ) = vm.cur_env.get(&vm.current_irep.__id) { + environ.capture_no_clone(regs0_cloned); + environ.as_ref().expire(); + vm.has_env_ref.remove(&vm.current_irep.__id); + } + + let regs0 = vm.current_regs(); + if nregs > 0 { + regs0[1..=nregs].iter_mut().for_each(|reg| { + reg.take(); + }); + } + + let ci = vm.current_callinfo.take(); + if ci.is_none() { + // When called from mrb_funcall, return error if there's an exception + if let Some(e) = &vm.exception { + return Err(e.error_type.borrow().clone()); + } + // For normal completion, set preemption flag and terminate + vm.flag_preemption.set(true); + return Ok(()); + } + vm.break_level -= 1; let ci = ci.unwrap(); if let Some(prev) = &ci.prev { @@ -1111,9 +1161,18 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { Ok(()) } +pub(crate) fn op_break(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { + let a = operand.as_b()? as usize; + let val = vm.get_current_regs_cloned(a)?; + vm.break_value.borrow_mut().replace(val); + vm.break_target_level.set(Some(vm.break_level)); + + return_without_value(vm) +} + pub(crate) fn op_blkpush(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, _s) = operand.as_bs()?; - let n = dbg!(vm.current_callinfo.as_ref().unwrap().n_args); + let n = vm.current_callinfo.as_ref().unwrap().n_args; let block = vm.get_current_regs_cloned(n + 1)?; vm.current_regs()[a as usize].replace(block); Ok(()) @@ -1640,7 +1699,7 @@ pub(crate) fn op_exec(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let recv = vm.get_current_regs_cloned(a as usize)?; vm.current_regs()[a as usize].replace(recv.clone()); - push_callinfo(vm, "".into(), 0, None); + push_callinfo(vm, "".into(), 0, None, a as usize); vm.pc.set(0); let irep = vm.current_irep.reps[b as usize].clone(); diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index 71ee8dc..351ab3f 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -147,7 +147,7 @@ fn mrb_array_each(vm: &mut VM, args: &[Rc]) -> Result, Erro let a = a.borrow(); for elem in a.iter() { let args = vec![elem.clone()]; - mrb_call_block(vm, block.clone(), None, &args)?; + mrb_call_block(vm, block.clone(), None, &args, 0)?; } } _ => { diff --git a/mrubyedge/src/yamrb/prelude/hash.rs b/mrubyedge/src/yamrb/prelude/hash.rs index e31821c..31c256d 100644 --- a/mrubyedge/src/yamrb/prelude/hash.rs +++ b/mrubyedge/src/yamrb/prelude/hash.rs @@ -91,7 +91,7 @@ fn mrb_hash_each(vm: &mut VM, args: &[Rc]) -> Result, Error let hash = hash.borrow(); for (_, (key, value)) in hash.iter() { let args = vec![key.clone(), value.clone()]; - mrb_call_block(vm, block.clone(), None, &args)?; + mrb_call_block(vm, block.clone(), None, &args, 0)?; } } _ => { diff --git a/mrubyedge/src/yamrb/prelude/integer.rs b/mrubyedge/src/yamrb/prelude/integer.rs index 8d54e08..01aeabb 100644 --- a/mrubyedge/src/yamrb/prelude/integer.rs +++ b/mrubyedge/src/yamrb/prelude/integer.rs @@ -33,7 +33,7 @@ fn mrb_integer_times(vm: &mut VM, args: &[Rc]) -> Result, E for i in 0..this { let block = args[0].clone(); let args = vec![Rc::new(RObject::integer(i))]; - mrb_call_block(vm, block, None, &args)?; + mrb_call_block(vm, block, None, &args, 0)?; } vm.getself() } diff --git a/mrubyedge/src/yamrb/prelude/proc.rs b/mrubyedge/src/yamrb/prelude/proc.rs index 379b82e..280c038 100644 --- a/mrubyedge/src/yamrb/prelude/proc.rs +++ b/mrubyedge/src/yamrb/prelude/proc.rs @@ -24,5 +24,5 @@ fn mrb_proc_new(_vm: &mut VM, args: &[Rc]) -> Result, Error pub fn mrb_proc_call(vm: &mut VM, args: &[Rc]) -> Result, Error> { let this = vm.getself()?; - mrb_call_block(vm, this.clone(), None, args) + mrb_call_block(vm, this.clone(), None, args, 0) } diff --git a/mrubyedge/src/yamrb/prelude/range.rs b/mrubyedge/src/yamrb/prelude/range.rs index 191f3bd..12eb5b7 100644 --- a/mrubyedge/src/yamrb/prelude/range.rs +++ b/mrubyedge/src/yamrb/prelude/range.rs @@ -64,7 +64,7 @@ pub fn mrb_range_each(vm: &mut VM, args: &[Rc]) -> Result, } for i in start..=end { let args = vec![Rc::new(RObject::integer(i))]; - mrb_call_block(vm, block.clone(), None, &args)?; + mrb_call_block(vm, block.clone(), None, &args, 0)?; } } _ => { diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 4bd9c50..89ededf 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -54,6 +54,10 @@ pub struct VM { pub globals: HashMap>, pub consts: HashMap>, + pub break_level: usize, + pub break_value: RefCell>>, + pub break_target_level: Cell>, + pub upper: Option>, // TODO: using fixed array? pub cur_env: HashMap>, @@ -116,6 +120,9 @@ impl VM { let exception = None; let flag_preemption = Cell::new(false); let fn_table = Vec::new(); + let break_level = 0; + let break_value = RefCell::new(None); + let break_target_level = Cell::new(None); let upper = None; let cur_env = HashMap::new(); let has_env_ref = HashMap::new(); @@ -137,6 +144,9 @@ impl VM { class_object_table, globals, consts, + break_level, + break_value, + break_target_level, upper, cur_env, has_env_ref, @@ -213,6 +223,38 @@ impl VM { &op.code, &op.operand, op.pos, op.len ); } + + if self.break_target_level.get().is_some() { + let target_level = self.break_target_level.get().unwrap(); + // dbg!(("breaking", self.break_level, target_level)); + if self.break_level == target_level { + self.break_target_level.set(None); + let val = self + .break_value + .borrow_mut() + .take() + .unwrap_or_else(|| Rc::new(RObject::nil())); + + let return_reg = match self.current_callinfo.as_ref() { + Some(ci) => ci.return_reg, + None => 0, + }; + return_without_value(self)?; + // TODO: getting return target register for send... + self.current_regs()[return_reg].replace(val); + if self.flag_preemption.get() { + break; + } else { + continue; + } + } else if self.break_level < target_level { + eprintln!("[BUG] break target level mismatch"); + } else { + return_without_value(self)?; + continue; + } + } + match consume_expr(self, op.code, &operand, op.pos, op.len) { Ok(_) => {} Err(e) => { @@ -471,6 +513,7 @@ pub struct CALLINFO { pub current_regs_offset: usize, pub target_class: TargetContext, pub n_args: usize, + pub return_reg: usize, pub method_owner: Option>, } From 7a67c643c0a5b90789d21daea514f788ca21b065 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 23 Dec 2025 00:30:56 +0900 Subject: [PATCH 095/314] WIP: implemented breaks --- mrubyedge/examples/breaking2.rb | 8 +++ mrubyedge/examples/return_block.rb | 13 ++++ mrubyedge/examples/return_block_2.rb | 16 +++++ mrubyedge/examples/runscript.rs | 10 ++- mrubyedge/src/error.rs | 7 +- mrubyedge/src/yamrb/helpers.rs | 41 ++++++++++- mrubyedge/src/yamrb/optable.rs | 88 ++++++++++++++++++++++-- mrubyedge/src/yamrb/prelude/exception.rs | 3 + mrubyedge/src/yamrb/value.rs | 13 ++-- mrubyedge/src/yamrb/vm.rs | 86 ++++++++++++++--------- 10 files changed, 237 insertions(+), 48 deletions(-) create mode 100644 mrubyedge/examples/breaking2.rb create mode 100644 mrubyedge/examples/return_block.rb create mode 100644 mrubyedge/examples/return_block_2.rb diff --git a/mrubyedge/examples/breaking2.rb b/mrubyedge/examples/breaking2.rb new file mode 100644 index 0000000..dcc9427 --- /dev/null +++ b/mrubyedge/examples/breaking2.rb @@ -0,0 +1,8 @@ +ret = 4.times do |i| + puts "loop #{i}" + if i > 1 + break 9999 + end +end + +p ret \ No newline at end of file diff --git a/mrubyedge/examples/return_block.rb b/mrubyedge/examples/return_block.rb new file mode 100644 index 0000000..c4103bb --- /dev/null +++ b/mrubyedge/examples/return_block.rb @@ -0,0 +1,13 @@ +def outer + inner do + return 5471 + end + return :unreachable +end + +def inner + yield + return :unreachable +end + +p outer \ No newline at end of file diff --git a/mrubyedge/examples/return_block_2.rb b/mrubyedge/examples/return_block_2.rb new file mode 100644 index 0000000..719fd22 --- /dev/null +++ b/mrubyedge/examples/return_block_2.rb @@ -0,0 +1,16 @@ +blk = proc { + return p(1234) +} + +def inner(&b) + b.call + return :unreachable +end + +def outer(&b) + inner(&b) + return :unreachable +end + +# ??? +p outer(&blk) \ No newline at end of file diff --git a/mrubyedge/examples/runscript.rs b/mrubyedge/examples/runscript.rs index 50e1226..11b130f 100644 --- a/mrubyedge/examples/runscript.rs +++ b/mrubyedge/examples/runscript.rs @@ -38,7 +38,15 @@ fn main() -> Result<(), std::io::Error> { let res = vm.run().unwrap(); remove_file("/tmp/__tmp__.mrb")?; - eprintln!("return value: {:?}", res); + match res.as_ref().tt { + mrubyedge::yamrb::value::RType::Instance => { + eprintln!( + "return value: Instance, object_id = {}", + res.as_ref().object_id.get() + ); + } + _ => eprintln!("return value: {:?}", res), + }; // dbg!(&vm); Ok(()) } diff --git a/mrubyedge/src/error.rs b/mrubyedge/src/error.rs index f19bca5..0b5160a 100644 --- a/mrubyedge/src/error.rs +++ b/mrubyedge/src/error.rs @@ -3,9 +3,10 @@ use std::fmt; use std::rc::Rc; use crate::yamrb::value::RClass; +use crate::yamrb::value::RObject; use crate::yamrb::vm::VM; -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Debug, Clone, PartialEq)] pub enum Error { General, Internal(String), @@ -15,6 +16,8 @@ pub enum Error { TypeMismatch, NoMethodError(String), NameError(String), + + Break(Rc), } impl fmt::Display for Error { @@ -40,6 +43,8 @@ impl Error { Error::TypeMismatch => "Type mismatch".to_string(), Error::NoMethodError(msg) => format!("Method not found: {}", msg), Error::NameError(msg) => format!("Cannot found name: {}", msg), + + Error::Break(_) => "[Break]".to_string(), } } diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index 3f4f65b..5ffbc35 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -1,6 +1,6 @@ use std::rc::Rc; -use crate::Error; +use crate::{Error, yamrb::vm::BreadCrumb}; use super::{ optable::push_callinfo, @@ -116,7 +116,24 @@ pub fn mrb_call_block( .clone() .ok_or_else(|| Error::RuntimeError("No block self assigned".to_string()))?, }; - call_block(vm, block, recv, args, None, return_register) + //dbg!(&vm.current_breadcrumb); + let upper = vm.current_breadcrumb.take(); + let new_breadcrumb = Rc::new(BreadCrumb { + upper, + event: "block_call", + return_reg: None, + }); + eprintln!("pile on {}", new_breadcrumb.event); + vm.current_breadcrumb.replace(new_breadcrumb); + //dbg!(&vm.current_breadcrumb); + let res = call_block(vm, block, recv, args, None, return_register); + //dbg!(&vm.current_breadcrumb); + let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); + if let Some(upper) = &cur.as_ref().upper { + eprintln!("returning to {}", upper.event); + vm.current_breadcrumb.replace(upper.clone()); + } + res } /// Calls a method on an object by name with the given arguments. @@ -147,7 +164,17 @@ pub fn mrb_funcall( let (owner_module, method) = resolve_method(&binding, name) .ok_or_else(|| Error::NoMethodError(format!("{} for {}", name, binding.full_name())))?; - if method.is_rb_func { + let upper = vm.current_breadcrumb.take(); + let new_breadcrumb = Rc::new(BreadCrumb { + upper, + event: "funcall", + return_reg: None, + }); + eprintln!("pile on {}", new_breadcrumb.event); + vm.current_breadcrumb.replace(new_breadcrumb); + //dbg!(&vm.current_breadcrumb); + + let res = if method.is_rb_func { let method_id = method .sym_id .clone() @@ -174,7 +201,15 @@ pub fn mrb_funcall( } res + }; + //dbg!(&vm.current_breadcrumb); + let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); + if let Some(upper) = &cur.as_ref().upper { + eprintln!("returning to {}", upper.event); + vm.current_breadcrumb.replace(upper.clone()); } + + res } pub fn mrb_call_inspect(vm: &mut VM, recv: Rc) -> Result, Error> { diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index bcae138..f912a81 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -6,6 +6,7 @@ use std::rc::Rc; use crate::Error; use crate::rite::insn::{Fetched, OpCode}; +use crate::yamrb::prelude::class; use super::prelude::object::mrb_object_is_equal; use super::{helpers::mrb_funcall, value::*, vm::*}; @@ -495,11 +496,26 @@ pub(crate) fn push_callinfo( vm.current_callinfo = Some(Rc::new(callinfo)); } +pub(crate) fn pop_callinfo(vm: &mut VM) { + let ci = vm.current_callinfo.take(); + if ci.is_none() { + unreachable!("callinfo underflow"); + } + + let ci = ci.unwrap(); + if let Some(prev) = &ci.prev { + vm.current_callinfo.replace(prev.clone()); + } + vm.current_irep = ci.pc_irep.clone(); + vm.pc.set(ci.pc); + vm.current_regs_offset = ci.current_regs_offset; + vm.target_class = ci.target_class.clone(); +} + fn calcurate_pc(irep: &IREP, pc: usize, original_pc: usize) -> usize { let mut next_pc = pc; loop { let op = irep.code.get(next_pc).expect("cannot fetch op anymore"); - // dbg!((&op, original_pc)); if op.pos == original_pc { break; } @@ -906,6 +922,16 @@ pub(crate) fn do_op_send( Error::NoMethodError(format!("{} for {}", method_id.name, klass.full_name())) })?; + let upper = vm.current_breadcrumb.take(); + let new_breadcrumb = Rc::new(BreadCrumb { + upper, + event: "do_op_send", + return_reg: Some(a as usize), + }); + //eprintln!("pile on {}", new_breadcrumb.event); + vm.current_breadcrumb.replace(new_breadcrumb); + //dbg!(&vm.current_breadcrumb); + vm.current_regs()[a as usize].replace(recv.clone()); if !method.is_rb_func { let func = vm @@ -913,8 +939,12 @@ pub(crate) fn do_op_send( .ok_or_else(|| Error::internal("function not found"))?; vm.current_regs_offset += a as usize; + // push_callinfo(vm, method_id, c as usize, Some(owner_module), a as usize); + let res = func(vm, &args); + // pop_callinfo(vm); + vm.current_regs_offset -= a as usize; for i in (a as usize + 1)..block_index { vm.current_regs()[i].take(); @@ -942,6 +972,13 @@ pub(crate) fn do_op_send( } pub(crate) fn op_call(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> { + let upper = vm.current_breadcrumb.take(); + let new_breadcrumb = Rc::new(BreadCrumb { + upper, + event: "op_call", + return_reg: None, + }); + vm.current_breadcrumb.replace(new_breadcrumb); push_callinfo(vm, "".into(), 0, None, 0); vm.pc.set(0); @@ -1009,6 +1046,14 @@ pub(crate) fn op_super(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { return Ok(()); } + let upper = vm.current_breadcrumb.take(); + let new_breadcrumb = Rc::new(BreadCrumb { + upper, + event: "super", + return_reg: None, + }); + vm.current_breadcrumb.replace(new_breadcrumb); + vm.current_regs()[a as usize].replace(recv.clone()); push_callinfo( vm, @@ -1092,6 +1137,12 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let ci = vm.current_callinfo.take(); if ci.is_none() { + //dbg!(&vm.current_breadcrumb); + let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); + if let Some(upper) = &cur.as_ref().upper { + //eprintln!("returning to {}", upper.event); + vm.current_breadcrumb.replace(upper.clone()); + } // When called from mrb_funcall, return error if there's an exception if let Some(e) = &vm.exception { return Err(e.error_type.borrow().clone()); @@ -1113,6 +1164,13 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { if vm.current_regs()[0].is_none() { todo!("debug"); } + + //dbg!(&vm.current_breadcrumb); + let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); + if let Some(upper) = &cur.as_ref().upper { + //eprintln!("returning to {}", upper.event); + vm.current_breadcrumb.replace(upper.clone()); + } Ok(()) } @@ -1137,6 +1195,13 @@ pub(crate) fn return_without_value(vm: &mut VM) -> Result<(), Error> { let ci = vm.current_callinfo.take(); if ci.is_none() { + //dbg!(&vm.current_breadcrumb); + + let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); + if let Some(upper) = &cur.as_ref().upper { + //eprintln!("returning to {}", upper.event); + vm.current_breadcrumb.replace(upper.clone()); + } // When called from mrb_funcall, return error if there's an exception if let Some(e) = &vm.exception { return Err(e.error_type.borrow().clone()); @@ -1158,16 +1223,23 @@ pub(crate) fn return_without_value(vm: &mut VM) -> Result<(), Error> { if vm.current_regs()[0].is_none() { todo!("debug"); } + + let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); + if let Some(upper) = &cur.as_ref().upper { + //eprintln!("returning to {}", upper.event); + vm.current_breadcrumb.replace(upper.clone()); + } Ok(()) } pub(crate) fn op_break(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; let val = vm.get_current_regs_cloned(a)?; - vm.break_value.borrow_mut().replace(val); - vm.break_target_level.set(Some(vm.break_level)); + // vm.break_value.borrow_mut().replace(val.clone()); + // vm.break_target_level.set(Some(vm.break_level)); - return_without_value(vm) + Err(Error::Break(val)) + // return_without_value(vm) } pub(crate) fn op_blkpush(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { @@ -1698,6 +1770,14 @@ pub(crate) fn op_exec(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; let recv = vm.get_current_regs_cloned(a as usize)?; + let upper = vm.current_breadcrumb.take(); + let new_breadcrumb = Rc::new(BreadCrumb { + upper, + event: "exec", + return_reg: None, + }); + vm.current_breadcrumb.replace(new_breadcrumb); + vm.current_regs()[a as usize].replace(recv.clone()); push_callinfo(vm, "".into(), 0, None, a as usize); diff --git a/mrubyedge/src/yamrb/prelude/exception.rs b/mrubyedge/src/yamrb/prelude/exception.rs index d4927b7..b75e05e 100644 --- a/mrubyedge/src/yamrb/prelude/exception.rs +++ b/mrubyedge/src/yamrb/prelude/exception.rs @@ -29,6 +29,9 @@ pub(crate) fn initialize_exception(vm: &mut VM) { let _ = vm.define_standard_class_with_superclass("NoMethodError", std_exp_class.clone()); let _ = vm.define_standard_class_with_superclass("NameError", std_exp_class.clone()); + // Dummy class for 'break' control flow + let _ = vm.define_standard_class("_Break"); + mrb_define_cmethod(vm, exp_class, "message", Box::new(mrb_exception_message)); } diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 134a8ec..dba31a1 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -414,11 +414,6 @@ impl RObject { )); self.singleton_class.replace(Some(sclass.clone())); - eprintln!( - "Created singleton class: {} / {}", - class_name, - self.object_id.get() - ); sclass } @@ -645,6 +640,12 @@ impl TryFrom<&RObject> for *mut u8 { } } +impl PartialEq for RObject { + fn eq(&self, other: &Self) -> bool { + self.object_id.get() == other.object_id.get() + } +} + /// Ruby module with methods, constants, and mixin relationships. #[derive(Debug, Clone)] pub struct RModule { @@ -937,6 +938,8 @@ impl RClass { Error::TypeMismatch => vm.get_class_by_name("LoadError"), Error::NoMethodError(_) => vm.get_class_by_name("NoMethodError"), Error::NameError(_) => vm.get_class_by_name("NameError"), + + Error::Break(_) => vm.get_class_by_name("_Break"), } } } diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 89ededf..3eca0b0 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -31,6 +31,13 @@ impl TargetContext { } } +#[derive(Debug)] +pub struct BreadCrumb { + pub event: &'static str, // TODO: be enum + pub return_reg: Option, + pub upper: Option>, +} + pub struct VM { pub irep: Rc, @@ -41,6 +48,7 @@ pub struct VM { pub regs: [Option>; MAX_REGS_SIZE], pub current_regs_offset: usize, pub current_callinfo: Option>, + pub current_breadcrumb: Option>, pub target_class: TargetContext, pub exception: Option>, @@ -116,6 +124,11 @@ impl VM { let regs: [Option>; MAX_REGS_SIZE] = [const { None }; MAX_REGS_SIZE]; let current_regs_offset = 0; let current_callinfo = None; + let current_breadcrumb = Some(Rc::new(BreadCrumb { + upper: None, + event: "root", + return_reg: None, + })); let target_class = TargetContext::Class(object_class.clone()); let exception = None; let flag_preemption = Cell::new(false); @@ -136,6 +149,7 @@ impl VM { regs, current_regs_offset, current_callinfo, + current_breadcrumb, target_class, exception, flag_preemption, @@ -162,6 +176,14 @@ impl VM { /// register 0 or propagating any raised exception as an error. The /// top-level `self` is initialized automatically before evaluation. pub fn run(&mut self) -> Result, Box> { + let upper = self.current_breadcrumb.take(); + let new_breadcrumb = Rc::new(BreadCrumb { + upper, + event: "run", + return_reg: None, + }); + self.current_breadcrumb.replace(new_breadcrumb); + let class = self.object_class.clone(); // Insert top_self let top_self = RObject { @@ -181,7 +203,7 @@ impl VM { let mut rescued = false; loop { - if !rescued && let Some(_e) = self.exception.clone() { + if !rescued && let Some(e) = self.exception.clone() { let operand = insn::Fetched::B(0); if let Some(pos) = self.find_next_handler_pos() { self.pc.set(pos); @@ -189,11 +211,36 @@ impl VM { continue; } + let retreg = match self.current_breadcrumb.as_ref() { + Some(bc) if bc.event == "do_op_send" => { + let retreg = bc.as_ref().return_reg.unwrap_or(0); + //dbg!(("return to reg {:?}", retreg)); + retreg + } + _ => 0, + }; + match op_return(self, &operand) { Ok(_) => {} Err(_) => { - // use assigned expection through - break; + if self.break_value.borrow_mut().is_some() + && matches!(e.error_type.borrow().clone(), Error::Break(_)) + { + let retval = self.break_value.borrow_mut().take(); + //dbg!("return break val"); + self.break_level -= 1; // handle as return + self.current_regs()[retreg] + .replace(retval.expect("break value missing")); + self.exception.take(); + + // self.break_level -= 1; + } else { + if let Error::Break(brkval) = e.error_type.borrow().clone() { + //dbg!("set break val to VM"); + self.break_value.borrow_mut().replace(brkval.clone()); + } + break; + } } } if self.flag_preemption.get() { @@ -224,37 +271,6 @@ impl VM { ); } - if self.break_target_level.get().is_some() { - let target_level = self.break_target_level.get().unwrap(); - // dbg!(("breaking", self.break_level, target_level)); - if self.break_level == target_level { - self.break_target_level.set(None); - let val = self - .break_value - .borrow_mut() - .take() - .unwrap_or_else(|| Rc::new(RObject::nil())); - - let return_reg = match self.current_callinfo.as_ref() { - Some(ci) => ci.return_reg, - None => 0, - }; - return_without_value(self)?; - // TODO: getting return target register for send... - self.current_regs()[return_reg].replace(val); - if self.flag_preemption.get() { - break; - } else { - continue; - } - } else if self.break_level < target_level { - eprintln!("[BUG] break target level mismatch"); - } else { - return_without_value(self)?; - continue; - } - } - match consume_expr(self, op.code, &operand, op.pos, op.len) { Ok(_) => {} Err(e) => { @@ -272,6 +288,7 @@ impl VM { self.flag_preemption.set(false); if let Some(e) = self.exception.clone() { + //dbg!(&self.current_breadcrumb); return Err(e.error_type.borrow().clone().into()); } @@ -281,6 +298,7 @@ impl VM { }; self.current_regs()[0].replace(top_self.clone()); + //dbg!(&self.current_breadcrumb); retval } From 33d81067ceb97e82b319238c6b121bd556703579 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 23 Dec 2025 01:05:43 +0900 Subject: [PATCH 096/314] Fix --- mrubyedge/src/yamrb/helpers.rs | 16 +++++++++++----- mrubyedge/src/yamrb/optable.rs | 6 +++--- mrubyedge/src/yamrb/vm.rs | 18 ++++++++++++++---- mrubyedge/tests/break.rs | 14 ++++++++++++++ 4 files changed, 42 insertions(+), 12 deletions(-) create mode 100644 mrubyedge/tests/break.rs diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index 5ffbc35..53a15d2 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -44,6 +44,13 @@ fn call_block( let res = vm.run(); + // consume breadcrumb for RUN itself + let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); + if let Some(upper) = &cur.as_ref().upper { + //eprintln!("returning to {}", upper.event); + vm.current_breadcrumb.replace(upper.clone()); + } + if let Some(prev) = prev_self { vm.current_regs()[0].replace(prev); } else { @@ -123,14 +130,13 @@ pub fn mrb_call_block( event: "block_call", return_reg: None, }); - eprintln!("pile on {}", new_breadcrumb.event); + //eprintln!("pile on {}", new_breadcrumb.event); vm.current_breadcrumb.replace(new_breadcrumb); //dbg!(&vm.current_breadcrumb); let res = call_block(vm, block, recv, args, None, return_register); - //dbg!(&vm.current_breadcrumb); let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); if let Some(upper) = &cur.as_ref().upper { - eprintln!("returning to {}", upper.event); + //eprintln!("returning to {}", upper.event); vm.current_breadcrumb.replace(upper.clone()); } res @@ -170,7 +176,7 @@ pub fn mrb_funcall( event: "funcall", return_reg: None, }); - eprintln!("pile on {}", new_breadcrumb.event); + //("pile on {}", new_breadcrumb.event); vm.current_breadcrumb.replace(new_breadcrumb); //dbg!(&vm.current_breadcrumb); @@ -205,7 +211,7 @@ pub fn mrb_funcall( //dbg!(&vm.current_breadcrumb); let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); if let Some(upper) = &cur.as_ref().upper { - eprintln!("returning to {}", upper.event); + //eprintln!("returning to {}", upper.event); vm.current_breadcrumb.replace(upper.clone()); } diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index f912a81..19dd9b1 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1137,14 +1137,14 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let ci = vm.current_callinfo.take(); if ci.is_none() { - //dbg!(&vm.current_breadcrumb); let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); if let Some(upper) = &cur.as_ref().upper { - //eprintln!("returning to {}", upper.event); + eprintln!("returning to {}", upper.event); vm.current_breadcrumb.replace(upper.clone()); } // When called from mrb_funcall, return error if there's an exception if let Some(e) = &vm.exception { + //eprintln!("err..."); return Err(e.error_type.borrow().clone()); } // For normal completion, set preemption flag and terminate @@ -1168,7 +1168,7 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { //dbg!(&vm.current_breadcrumb); let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); if let Some(upper) = &cur.as_ref().upper { - //eprintln!("returning to {}", upper.event); + eprintln!("returning to {}", upper.event); vm.current_breadcrumb.replace(upper.clone()); } Ok(()) diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 3eca0b0..46fcd2a 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -211,15 +211,16 @@ impl VM { continue; } + dbg!("return break val on upper"); + // dbg!(&self.current_breadcrumb); let retreg = match self.current_breadcrumb.as_ref() { Some(bc) if bc.event == "do_op_send" => { let retreg = bc.as_ref().return_reg.unwrap_or(0); - //dbg!(("return to reg {:?}", retreg)); + // dbg!(("return to reg {:?}", retreg)); retreg } _ => 0, }; - match op_return(self, &operand) { Ok(_) => {} Err(_) => { @@ -227,7 +228,16 @@ impl VM { && matches!(e.error_type.borrow().clone(), Error::Break(_)) { let retval = self.break_value.borrow_mut().take(); - //dbg!("return break val"); + // dbg!(&self.current_breadcrumb); + // let retreg = match self.current_breadcrumb.as_ref() { + // Some(bc) if bc.event == "do_op_send" => { + // let retreg = bc.as_ref().return_reg.unwrap_or(0); + // dbg!(("return to reg {:?}", retreg)); + // retreg + // } + // _ => retreg, + // }; + self.break_level -= 1; // handle as return self.current_regs()[retreg] .replace(retval.expect("break value missing")); @@ -236,7 +246,7 @@ impl VM { // self.break_level -= 1; } else { if let Error::Break(brkval) = e.error_type.borrow().clone() { - //dbg!("set break val to VM"); + // dbg!("set break val to VM"); self.break_value.borrow_mut().replace(brkval.clone()); } break; diff --git a/mrubyedge/tests/break.rs b/mrubyedge/tests/break.rs new file mode 100644 index 0000000..2cc1f98 --- /dev/null +++ b/mrubyedge/tests/break.rs @@ -0,0 +1,14 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use std::rc::Rc; + +use helpers::*; +use mrubyedge::yamrb::value::RObject; + +#[test] +fn break_test() {} + +#[test] +fn break_test2() {} From c32b245e0ce5a5cfeac25b41ade5ae97f3eb2ca3 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 24 Dec 2025 23:40:23 +0900 Subject: [PATCH 097/314] Worked in some case FIXME: break or exception destroys register state --- mrubyedge/examples/breaking.rb | 1 + mrubyedge/examples/breaking2.rb | 7 ++ mrubyedge/examples/breaking3.rb | 35 +++++++ mrubyedge/src/yamrb/helpers.rs | 17 ++-- mrubyedge/src/yamrb/optable.rs | 89 +++++----------- mrubyedge/src/yamrb/prelude/proc.rs | 15 ++- mrubyedge/src/yamrb/value.rs | 66 ++++++++++++ mrubyedge/src/yamrb/vm.rs | 115 ++++++++++++++++----- mrubyedge/tests/break.rs | 153 +++++++++++++++++++++++++++- 9 files changed, 389 insertions(+), 109 deletions(-) create mode 100644 mrubyedge/examples/breaking3.rb diff --git a/mrubyedge/examples/breaking.rb b/mrubyedge/examples/breaking.rb index fea5737..154f16b 100644 --- a/mrubyedge/examples/breaking.rb +++ b/mrubyedge/examples/breaking.rb @@ -5,5 +5,6 @@ def onetimes p(onetimes do puts "dummy" + __debug__vm_info break 42 end) \ No newline at end of file diff --git a/mrubyedge/examples/breaking2.rb b/mrubyedge/examples/breaking2.rb index dcc9427..931332a 100644 --- a/mrubyedge/examples/breaking2.rb +++ b/mrubyedge/examples/breaking2.rb @@ -1,6 +1,13 @@ +def puts2(a) + a +end + ret = 4.times do |i| puts "loop #{i}" + puts2 "dummy" + #i.inspect if i > 1 + __debug__vm_info break 9999 end end diff --git a/mrubyedge/examples/breaking3.rb b/mrubyedge/examples/breaking3.rb new file mode 100644 index 0000000..1ae720c --- /dev/null +++ b/mrubyedge/examples/breaking3.rb @@ -0,0 +1,35 @@ +def myyield + yield 1 + yield 1 + yield 1 + yield 1 + yield 1 +end + +def myyield2 + yield 2 + yield 2 + yield 2 + yield 2 + yield 2 +end + +def test_break + x = 0 + y = 0 + myyield do |i| + puts "loop #{x}, #{y}" + #__debug__vm_info + myyield2 do |j| + y += j + #__debug__vm_info + break if y >= 6 + end + puts "loop #{x}, #{y}" + #__debug__vm_info + x += 1 + end + [x, y] +end + +p test_break \ No newline at end of file diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index 53a15d2..0838beb 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -1,6 +1,6 @@ use std::rc::Rc; -use crate::{Error, yamrb::vm::BreadCrumb}; +use crate::{Error, yamrb::vm::Breadcrumb}; use super::{ optable::push_callinfo, @@ -42,14 +42,7 @@ fn call_block( .clone(); vm.upper = block.environ; - let res = vm.run(); - - // consume breadcrumb for RUN itself - let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); - if let Some(upper) = &cur.as_ref().upper { - //eprintln!("returning to {}", upper.event); - vm.current_breadcrumb.replace(upper.clone()); - } + let res = vm.run_internal(); if let Some(prev) = prev_self { vm.current_regs()[0].replace(prev); @@ -125,9 +118,10 @@ pub fn mrb_call_block( }; //dbg!(&vm.current_breadcrumb); let upper = vm.current_breadcrumb.take(); - let new_breadcrumb = Rc::new(BreadCrumb { + let new_breadcrumb = Rc::new(Breadcrumb { upper, event: "block_call", + caller: None, return_reg: None, }); //eprintln!("pile on {}", new_breadcrumb.event); @@ -171,9 +165,10 @@ pub fn mrb_funcall( .ok_or_else(|| Error::NoMethodError(format!("{} for {}", name, binding.full_name())))?; let upper = vm.current_breadcrumb.take(); - let new_breadcrumb = Rc::new(BreadCrumb { + let new_breadcrumb = Rc::new(Breadcrumb { upper, event: "funcall", + caller: Some(name.to_string()), return_reg: None, }); //("pile on {}", new_breadcrumb.event); diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 19dd9b1..e6fe12d 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -894,6 +894,14 @@ pub(crate) fn do_op_send( b: u8, c: u8, ) -> Result<(), Error> { + let method_id = vm.current_irep.syms[b as usize].clone(); + if &method_id.name == "__debug__vm_info" { + // Special debug method to dump VM info + vm.debug_dump_to_stdout(10); + vm.current_regs()[a as usize].replace(Rc::new(RObject::nil())); + return Ok(()); + } + let block_index = (a + c + 1) as usize; let recv = vm.get_current_regs_cloned(recv_index)?; @@ -911,7 +919,6 @@ pub(crate) fn do_op_send( args.push(Rc::new(RObject::nil())); } - let method_id = vm.current_irep.syms[b as usize].clone(); let klass = recv.get_class(vm); let klass = if klass.is_singleton { klass @@ -923,9 +930,10 @@ pub(crate) fn do_op_send( })?; let upper = vm.current_breadcrumb.take(); - let new_breadcrumb = Rc::new(BreadCrumb { + let new_breadcrumb = Rc::new(Breadcrumb { upper, event: "do_op_send", + caller: Some(method_id.name.clone()), return_reg: Some(a as usize), }); //eprintln!("pile on {}", new_breadcrumb.event); @@ -943,8 +951,6 @@ pub(crate) fn do_op_send( let res = func(vm, &args); - // pop_callinfo(vm); - vm.current_regs_offset -= a as usize; for i in (a as usize + 1)..block_index { vm.current_regs()[i].take(); @@ -953,6 +959,13 @@ pub(crate) fn do_op_send( match res { Ok(val) => { vm.current_regs()[a as usize].replace(val); + let cur = vm + .current_breadcrumb + .take() + .expect("send should push breadcrumb"); + let upper = cur.upper.clone(); + vm.current_breadcrumb + .replace(upper.expect("should have upper breadcrumb")); } Err(e) => { vm.current_regs()[a as usize].replace(Rc::new(RObject::nil())); @@ -973,9 +986,10 @@ pub(crate) fn do_op_send( pub(crate) fn op_call(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> { let upper = vm.current_breadcrumb.take(); - let new_breadcrumb = Rc::new(BreadCrumb { + let new_breadcrumb = Rc::new(Breadcrumb { upper, event: "op_call", + caller: Some("".into()), return_reg: None, }); vm.current_breadcrumb.replace(new_breadcrumb); @@ -1047,9 +1061,10 @@ pub(crate) fn op_super(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { } let upper = vm.current_breadcrumb.take(); - let new_breadcrumb = Rc::new(BreadCrumb { + let new_breadcrumb = Rc::new(Breadcrumb { upper, event: "super", + caller: Some(format!("super({})", sym_id)), return_reg: None, }); vm.current_breadcrumb.replace(new_breadcrumb); @@ -1114,6 +1129,7 @@ pub(crate) fn op_enter(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { } pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { + dbg!("in op_return"); let a = operand.as_b()? as usize; let old_irep = vm.current_irep.clone(); let nregs = old_irep.nregs; @@ -1174,64 +1190,6 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { Ok(()) } -// TODO: Dont repeat yourself -pub(crate) fn return_without_value(vm: &mut VM) -> Result<(), Error> { - let old_irep = vm.current_irep.clone(); - let nregs = old_irep.nregs; - - let regs0_cloned: Vec<_> = vm.current_regs()[0..nregs].to_vec(); - if let Some(environ) = vm.cur_env.get(&vm.current_irep.__id) { - environ.capture_no_clone(regs0_cloned); - environ.as_ref().expire(); - vm.has_env_ref.remove(&vm.current_irep.__id); - } - - let regs0 = vm.current_regs(); - if nregs > 0 { - regs0[1..=nregs].iter_mut().for_each(|reg| { - reg.take(); - }); - } - - let ci = vm.current_callinfo.take(); - if ci.is_none() { - //dbg!(&vm.current_breadcrumb); - - let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); - if let Some(upper) = &cur.as_ref().upper { - //eprintln!("returning to {}", upper.event); - vm.current_breadcrumb.replace(upper.clone()); - } - // When called from mrb_funcall, return error if there's an exception - if let Some(e) = &vm.exception { - return Err(e.error_type.borrow().clone()); - } - // For normal completion, set preemption flag and terminate - vm.flag_preemption.set(true); - return Ok(()); - } - vm.break_level -= 1; - - let ci = ci.unwrap(); - if let Some(prev) = &ci.prev { - vm.current_callinfo.replace(prev.clone()); - } - vm.current_irep = ci.pc_irep.clone(); - vm.pc.set(ci.pc); - vm.current_regs_offset = ci.current_regs_offset; - vm.target_class = ci.target_class.clone(); - if vm.current_regs()[0].is_none() { - todo!("debug"); - } - - let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); - if let Some(upper) = &cur.as_ref().upper { - //eprintln!("returning to {}", upper.event); - vm.current_breadcrumb.replace(upper.clone()); - } - Ok(()) -} - pub(crate) fn op_break(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; let val = vm.get_current_regs_cloned(a)?; @@ -1771,9 +1729,10 @@ pub(crate) fn op_exec(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let recv = vm.get_current_regs_cloned(a as usize)?; let upper = vm.current_breadcrumb.take(); - let new_breadcrumb = Rc::new(BreadCrumb { + let new_breadcrumb = Rc::new(Breadcrumb { upper, event: "exec", + caller: Some("".into()), return_reg: None, }); vm.current_breadcrumb.replace(new_breadcrumb); diff --git a/mrubyedge/src/yamrb/prelude/proc.rs b/mrubyedge/src/yamrb/prelude/proc.rs index 280c038..7761de4 100644 --- a/mrubyedge/src/yamrb/prelude/proc.rs +++ b/mrubyedge/src/yamrb/prelude/proc.rs @@ -5,7 +5,7 @@ use crate::{ yamrb::{ helpers::{mrb_call_block, mrb_define_class_cmethod, mrb_define_cmethod}, value::*, - vm::VM, + vm::{Breadcrumb, VM}, }, }; @@ -23,6 +23,19 @@ fn mrb_proc_new(_vm: &mut VM, args: &[Rc]) -> Result, Error } pub fn mrb_proc_call(vm: &mut VM, args: &[Rc]) -> Result, Error> { + // handle Proc#call as special + let cur = vm + .current_breadcrumb + .take() + .expect("empty breadcrumb on call"); + let new_breadcrumb = Rc::new(Breadcrumb { + upper: cur.upper.clone(), + caller: Some("Proc#call".to_string()), + event: "_proc_call_via_method", + return_reg: cur.return_reg, + }); + vm.current_breadcrumb.replace(new_breadcrumb); + let this = vm.getself()?; mrb_call_block(vm, this.clone(), None, args, 0) } diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index dba31a1..1068717 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -479,6 +479,72 @@ impl TryFrom<&RObject> for i32 { } } +impl TryFrom<&RObject> for (i32, i32) { + type Error = Error; + + fn try_from(value: &RObject) -> Result { + match &value.value { + RValue::Array(ar) => { + let vec = ar.borrow(); + if vec.len() != 2 { + return Err(Error::ArgumentError( + "expected array of length 2".to_string(), + )); + } + let first: i32 = vec[0].as_ref().try_into()?; + let second: i32 = vec[1].as_ref().try_into()?; + Ok((first, second)) + } + _ => Err(Error::TypeMismatch), + } + } +} + +impl TryFrom<&RObject> for (i32, i32, i32) { + type Error = Error; + + fn try_from(value: &RObject) -> Result { + match &value.value { + RValue::Array(ar) => { + let vec = ar.borrow(); + if vec.len() != 3 { + return Err(Error::ArgumentError( + "expected array of length 3".to_string(), + )); + } + let first: i32 = vec[0].as_ref().try_into()?; + let second: i32 = vec[1].as_ref().try_into()?; + let third: i32 = vec[2].as_ref().try_into()?; + Ok((first, second, third)) + } + _ => Err(Error::TypeMismatch), + } + } +} + +impl TryFrom<&RObject> for (i32, i32, i32, i32) { + type Error = Error; + + fn try_from(value: &RObject) -> Result { + match &value.value { + RValue::Array(ar) => { + let vec = ar.borrow(); + if vec.len() != 4 { + return Err(Error::ArgumentError( + "expected array of length 4".to_string(), + )); + } + let first: i32 = vec[0].as_ref().try_into()?; + let second: i32 = vec[1].as_ref().try_into()?; + let third: i32 = vec[2].as_ref().try_into()?; + let fourth: i32 = vec[3].as_ref().try_into()?; + Ok((first, second, third, fourth)) + } + _ => Err(Error::TypeMismatch), + } + } +} + impl TryFrom<&RObject> for u32 { type Error = Error; diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 46fcd2a..57a0f56 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -5,6 +5,7 @@ use std::rc::Rc; use crate::Error; use crate::rite::{Irep, Rite, insn}; +use crate::yamrb::helpers::mrb_call_inspect; use super::op::Op; use super::prelude::prelude; @@ -32,10 +33,30 @@ impl TargetContext { } #[derive(Debug)] -pub struct BreadCrumb { +pub struct Breadcrumb { pub event: &'static str, // TODO: be enum + pub caller: Option, pub return_reg: Option, - pub upper: Option>, + pub upper: Option>, +} + +impl Breadcrumb { + pub fn display_breadcrumb_for_debug(&self, level: usize, max_level: usize) -> bool { + if level > max_level { + return false; + } + eprintln!( + "{}- Breadcrumb: event='{}', caller={}, return_reg={:?}", + " ".repeat(level), + self.event, + self.caller.as_deref().unwrap_or("(none)"), + self.return_reg + ); + if let Some(upper) = &self.upper { + upper.display_breadcrumb_for_debug(level + 1, max_level); + } + true + } } pub struct VM { @@ -48,7 +69,7 @@ pub struct VM { pub regs: [Option>; MAX_REGS_SIZE], pub current_regs_offset: usize, pub current_callinfo: Option>, - pub current_breadcrumb: Option>, + pub current_breadcrumb: Option>, pub target_class: TargetContext, pub exception: Option>, @@ -124,9 +145,10 @@ impl VM { let regs: [Option>; MAX_REGS_SIZE] = [const { None }; MAX_REGS_SIZE]; let current_regs_offset = 0; let current_callinfo = None; - let current_breadcrumb = Some(Rc::new(BreadCrumb { + let current_breadcrumb = Some(Rc::new(Breadcrumb { upper: None, event: "root", + caller: None, return_reg: None, })); let target_class = TargetContext::Class(object_class.clone()); @@ -177,13 +199,30 @@ impl VM { /// top-level `self` is initialized automatically before evaluation. pub fn run(&mut self) -> Result, Box> { let upper = self.current_breadcrumb.take(); - let new_breadcrumb = Rc::new(BreadCrumb { + let new_breadcrumb = Rc::new(Breadcrumb { upper, event: "run", + caller: None, + return_reg: None, + }); + self.current_breadcrumb.replace(new_breadcrumb); + self.__run() + } + + /// Internal run method that manages breadcrumb stack for internal calls. + pub fn run_internal(&mut self) -> Result, Box> { + let upper = self.current_breadcrumb.take(); + let new_breadcrumb = Rc::new(Breadcrumb { + upper, + event: "run_internal", + caller: None, return_reg: None, }); self.current_breadcrumb.replace(new_breadcrumb); + self.__run() + } + fn __run(&mut self) -> Result, Box> { let class = self.object_class.clone(); // Insert top_self let top_self = RObject { @@ -211,44 +250,31 @@ impl VM { continue; } - dbg!("return break val on upper"); - // dbg!(&self.current_breadcrumb); let retreg = match self.current_breadcrumb.as_ref() { Some(bc) if bc.event == "do_op_send" => { let retreg = bc.as_ref().return_reg.unwrap_or(0); // dbg!(("return to reg {:?}", retreg)); - retreg + Some(retreg) } - _ => 0, + _ => None, }; match op_return(self, &operand) { Ok(_) => {} Err(_) => { - if self.break_value.borrow_mut().is_some() - && matches!(e.error_type.borrow().clone(), Error::Break(_)) + if let Some(retreg) = retreg + && let Error::Break(brkval) = e.error_type.borrow().clone() { - let retval = self.break_value.borrow_mut().take(); - // dbg!(&self.current_breadcrumb); - // let retreg = match self.current_breadcrumb.as_ref() { - // Some(bc) if bc.event == "do_op_send" => { - // let retreg = bc.as_ref().return_reg.unwrap_or(0); - // dbg!(("return to reg {:?}", retreg)); - // retreg - // } - // _ => retreg, - // }; - + dbg!("return brkval"); self.break_level -= 1; // handle as return - self.current_regs()[retreg] - .replace(retval.expect("break value missing")); + self.current_regs()[retreg].replace(brkval); self.exception.take(); // self.break_level -= 1; } else { - if let Error::Break(brkval) = e.error_type.borrow().clone() { - // dbg!("set break val to VM"); - self.break_value.borrow_mut().replace(brkval.clone()); - } + // if let Error::Break(brkval) = e.error_type.borrow().clone() { + // // dbg!("set break val to VM"); + // self.break_value.borrow_mut().replace(brkval.clone()); + // } break; } } @@ -447,6 +473,39 @@ impl VM { self.builtin_class_table.insert(name, class.clone()); class } + + pub fn debug_dump_to_stdout(&mut self, max_breadcrumb_level: usize) { + eprintln!("=== VM Dump ==="); + eprintln!("ID: {}", self.id); + eprintln!("PC: {}", self.pc.get()); + eprintln!("Current IREP ID: {}", self.current_irep.__id); + eprintln!("Current Regs Offset: {}", self.current_regs_offset); + eprintln!("Current Regs:"); + let size = self.current_regs().len(); + for i in 0..size { + let reg = &self.get_current_regs_cloned(i).ok(); + if let Some(obj) = reg { + let inspect: String = mrb_call_inspect(self, obj.clone()) + .unwrap() + .as_ref() + .try_into() + .unwrap_or_else(|_| "(uninspectable)".into()); + eprintln!(" R{}: {}", i, inspect); + } else { + break; + } + } + // eprintln!("Current CallInfo: {:?}", self.current_callinfo); + eprintln!("Target Class: {}", self.target_class.name()); + // eprintln!("Exception: {:?}", self.exception); + eprintln!("--- Breadcrumb ---"); + if let Some(bc) = &self.current_breadcrumb { + bc.display_breadcrumb_for_debug(0, max_breadcrumb_level); + } else { + eprintln!("(none)"); + } + eprintln!("=== End of VM Dump ==="); + } } fn interpret_insn(mut insns: &[u8]) -> Vec { diff --git a/mrubyedge/tests/break.rs b/mrubyedge/tests/break.rs index 2cc1f98..1228966 100644 --- a/mrubyedge/tests/break.rs +++ b/mrubyedge/tests/break.rs @@ -2,13 +2,158 @@ extern crate mec_mrbc_sys; extern crate mrubyedge; mod helpers; -use std::rc::Rc; - use helpers::*; -use mrubyedge::yamrb::value::RObject; #[test] -fn break_test() {} +fn break_test() { + let code = " + def myyield + yield 1 + yield 1 + yield 1 + yield 1 + yield 1 + end + + def test_break + i = 0 + myyield do + i += 1 + break i if i >= 3 + end + end + "; + let binary = mrbc_compile("break_test", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + // Assert + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_break", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 3); +} + +#[test] +fn break_test_with_c_func() { + let code = " + def test_break + i = 0 + 10.times do + i += 1 + break i if i >= 5 + end + end + "; + let binary = mrbc_compile("break_test_with_c_func", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + // Assert + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_break", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 5); +} + +#[test] +fn break_test_with_c_func_2() { + let code = " + def test_break + i = 0 + 10.times do + puts \"loop #{i}\" + i += 1 + break i if i >= 5 + end + end + "; + let binary = mrbc_compile("break_test_with_c_func_2", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + // Assert + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_break", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 5); +} + +#[test] +fn break_test_nested() { + let code = " + def myyield + yield 1 + yield 1 + yield 1 + yield 1 + yield 1 + end + + def myyield2 + yield 2 + yield 2 + yield 2 + yield 2 + yield 2 + end + + def test_break + x = 0 + y = 0 + myyield do |i| + y = 0 + myyield2 do |j| + y += j + break if y >= 6 + end + puts \"loop #{x}, #{y}\" + x += 1 + end + [x, y] + end + "; + let binary = mrbc_compile("break_test_with_c_func_nested", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + // Assert + let args = vec![]; + let result: (i32, i32) = mrb_funcall(&mut vm, None, "test_break", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, (5, 6)); +} + +#[test] +fn break_test_toplevel() { + let code = " + i = 0 + v = 10.times do + i += 2 + break i if i >= 10 + end + v + "; + let binary = mrbc_compile("break_test_toplevel", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + + // Assert + let result: i32 = vm.run().unwrap().as_ref().try_into().unwrap(); + assert_eq!(result, 10); +} #[test] fn break_test2() {} From 6a446439327f733456be32330954721c2be9ac8d Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 24 Dec 2025 23:45:26 +0900 Subject: [PATCH 098/314] Erace debug output --- mrubyedge/src/yamrb/helpers.rs | 8 -------- mrubyedge/src/yamrb/optable.rs | 12 +----------- mrubyedge/src/yamrb/vm.rs | 10 ---------- 3 files changed, 1 insertion(+), 29 deletions(-) diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index 0838beb..b10196e 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -116,7 +116,6 @@ pub fn mrb_call_block( .clone() .ok_or_else(|| Error::RuntimeError("No block self assigned".to_string()))?, }; - //dbg!(&vm.current_breadcrumb); let upper = vm.current_breadcrumb.take(); let new_breadcrumb = Rc::new(Breadcrumb { upper, @@ -124,13 +123,10 @@ pub fn mrb_call_block( caller: None, return_reg: None, }); - //eprintln!("pile on {}", new_breadcrumb.event); vm.current_breadcrumb.replace(new_breadcrumb); - //dbg!(&vm.current_breadcrumb); let res = call_block(vm, block, recv, args, None, return_register); let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); if let Some(upper) = &cur.as_ref().upper { - //eprintln!("returning to {}", upper.event); vm.current_breadcrumb.replace(upper.clone()); } res @@ -171,9 +167,7 @@ pub fn mrb_funcall( caller: Some(name.to_string()), return_reg: None, }); - //("pile on {}", new_breadcrumb.event); vm.current_breadcrumb.replace(new_breadcrumb); - //dbg!(&vm.current_breadcrumb); let res = if method.is_rb_func { let method_id = method @@ -203,10 +197,8 @@ pub fn mrb_funcall( res }; - //dbg!(&vm.current_breadcrumb); let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); if let Some(upper) = &cur.as_ref().upper { - //eprintln!("returning to {}", upper.event); vm.current_breadcrumb.replace(upper.clone()); } diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index e6fe12d..fa4118f 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -526,7 +526,6 @@ fn calcurate_pc(irep: &IREP, pc: usize, original_pc: usize) -> usize { pub(crate) fn op_nop(_vm: &mut VM, _operand: &Fetched) -> Result<(), Error> { // NOOP - // eprintln!("[debug] nop"); Ok(()) } @@ -936,9 +935,7 @@ pub(crate) fn do_op_send( caller: Some(method_id.name.clone()), return_reg: Some(a as usize), }); - //eprintln!("pile on {}", new_breadcrumb.event); vm.current_breadcrumb.replace(new_breadcrumb); - //dbg!(&vm.current_breadcrumb); vm.current_regs()[a as usize].replace(recv.clone()); if !method.is_rb_func { @@ -947,8 +944,6 @@ pub(crate) fn do_op_send( .ok_or_else(|| Error::internal("function not found"))?; vm.current_regs_offset += a as usize; - // push_callinfo(vm, method_id, c as usize, Some(owner_module), a as usize); - let res = func(vm, &args); vm.current_regs_offset -= a as usize; @@ -1129,7 +1124,6 @@ pub(crate) fn op_enter(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { } pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { - dbg!("in op_return"); let a = operand.as_b()? as usize; let old_irep = vm.current_irep.clone(); let nregs = old_irep.nregs; @@ -1155,12 +1149,10 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { if ci.is_none() { let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); if let Some(upper) = &cur.as_ref().upper { - eprintln!("returning to {}", upper.event); vm.current_breadcrumb.replace(upper.clone()); } // When called from mrb_funcall, return error if there's an exception if let Some(e) = &vm.exception { - //eprintln!("err..."); return Err(e.error_type.borrow().clone()); } // For normal completion, set preemption flag and terminate @@ -1178,13 +1170,11 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { vm.current_regs_offset = ci.current_regs_offset; vm.target_class = ci.target_class.clone(); if vm.current_regs()[0].is_none() { - todo!("debug"); + unreachable!("debug"); } - //dbg!(&vm.current_breadcrumb); let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); if let Some(upper) = &cur.as_ref().upper { - eprintln!("returning to {}", upper.event); vm.current_breadcrumb.replace(upper.clone()); } Ok(()) diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 57a0f56..0d968a5 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -253,7 +253,6 @@ impl VM { let retreg = match self.current_breadcrumb.as_ref() { Some(bc) if bc.event == "do_op_send" => { let retreg = bc.as_ref().return_reg.unwrap_or(0); - // dbg!(("return to reg {:?}", retreg)); Some(retreg) } _ => None, @@ -264,17 +263,10 @@ impl VM { if let Some(retreg) = retreg && let Error::Break(brkval) = e.error_type.borrow().clone() { - dbg!("return brkval"); self.break_level -= 1; // handle as return self.current_regs()[retreg].replace(brkval); self.exception.take(); - - // self.break_level -= 1; } else { - // if let Error::Break(brkval) = e.error_type.borrow().clone() { - // // dbg!("set break val to VM"); - // self.break_value.borrow_mut().replace(brkval.clone()); - // } break; } } @@ -324,7 +316,6 @@ impl VM { self.flag_preemption.set(false); if let Some(e) = self.exception.clone() { - //dbg!(&self.current_breadcrumb); return Err(e.error_type.borrow().clone().into()); } @@ -334,7 +325,6 @@ impl VM { }; self.current_regs()[0].replace(top_self.clone()); - //dbg!(&self.current_breadcrumb); retval } From ed01cbc828eae2ed7bb06c32e82a894c49a431f1 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 24 Dec 2025 23:46:50 +0900 Subject: [PATCH 099/314] Fix warn --- mrubyedge/src/yamrb/optable.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index fa4118f..3850e24 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -6,7 +6,6 @@ use std::rc::Rc; use crate::Error; use crate::rite::insn::{Fetched, OpCode}; -use crate::yamrb::prelude::class; use super::prelude::object::mrb_object_is_equal; use super::{helpers::mrb_funcall, value::*, vm::*}; @@ -496,6 +495,7 @@ pub(crate) fn push_callinfo( vm.current_callinfo = Some(Rc::new(callinfo)); } +#[allow(dead_code)] pub(crate) fn pop_callinfo(vm: &mut VM) { let ci = vm.current_callinfo.take(); if ci.is_none() { From 014e54e3c0d918be7401c4c9a9c745fb4cbc2d17 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 24 Dec 2025 23:48:21 +0900 Subject: [PATCH 100/314] Remove unused members --- mrubyedge/src/yamrb/helpers.rs | 3 --- mrubyedge/src/yamrb/optable.rs | 2 -- mrubyedge/src/yamrb/vm.rs | 11 ----------- 3 files changed, 16 deletions(-) diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index b10196e..3be571c 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -184,10 +184,7 @@ pub fn mrb_funcall( ) } else { let prev = vm.current_regs()[0].replace(recv.clone()); - - vm.break_level += 1; // Enter new break level let func = vm.fn_table[method.func.unwrap()].clone(); - vm.break_level -= 1; // Exit break level after call let res = func(vm, args); if let Some(prev) = prev { vm.current_regs()[0].replace(prev); diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 3850e24..85195e1 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -491,7 +491,6 @@ pub(crate) fn push_callinfo( target_class: vm.target_class.clone(), method_owner, }; - vm.break_level += 1; vm.current_callinfo = Some(Rc::new(callinfo)); } @@ -1159,7 +1158,6 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { vm.flag_preemption.set(true); return Ok(()); } - vm.break_level -= 1; let ci = ci.unwrap(); if let Some(prev) = &ci.prev { diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 0d968a5..f657830 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -83,10 +83,6 @@ pub struct VM { pub globals: HashMap>, pub consts: HashMap>, - pub break_level: usize, - pub break_value: RefCell>>, - pub break_target_level: Cell>, - pub upper: Option>, // TODO: using fixed array? pub cur_env: HashMap>, @@ -155,9 +151,6 @@ impl VM { let exception = None; let flag_preemption = Cell::new(false); let fn_table = Vec::new(); - let break_level = 0; - let break_value = RefCell::new(None); - let break_target_level = Cell::new(None); let upper = None; let cur_env = HashMap::new(); let has_env_ref = HashMap::new(); @@ -180,9 +173,6 @@ impl VM { class_object_table, globals, consts, - break_level, - break_value, - break_target_level, upper, cur_env, has_env_ref, @@ -263,7 +253,6 @@ impl VM { if let Some(retreg) = retreg && let Error::Break(brkval) = e.error_type.borrow().clone() { - self.break_level -= 1; // handle as return self.current_regs()[retreg].replace(brkval); self.exception.take(); } else { From 6571b41383ceb8cfa0f1b9bde26b1471335d36fb Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 24 Dec 2025 23:51:42 +0900 Subject: [PATCH 101/314] More cleanup --- mrubyedge/src/yamrb/optable.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 85195e1..1ee9c84 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1181,11 +1181,7 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_break(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; let val = vm.get_current_regs_cloned(a)?; - // vm.break_value.borrow_mut().replace(val.clone()); - // vm.break_target_level.set(Some(vm.break_level)); - Err(Error::Break(val)) - // return_without_value(vm) } pub(crate) fn op_blkpush(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { From 6f980a4362f032bee33629cb26bca82afe0b3ea8 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 24 Dec 2025 23:53:02 +0900 Subject: [PATCH 102/314] Empty test --- mrubyedge/tests/break.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/mrubyedge/tests/break.rs b/mrubyedge/tests/break.rs index 1228966..32770f3 100644 --- a/mrubyedge/tests/break.rs +++ b/mrubyedge/tests/break.rs @@ -154,6 +154,3 @@ fn break_test_toplevel() { let result: i32 = vm.run().unwrap().as_ref().try_into().unwrap(); assert_eq!(result, 10); } - -#[test] -fn break_test2() {} From 2893ad0d08529ea3cc63b04d01f95ad2b366566c Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 25 Dec 2025 01:30:29 +0900 Subject: [PATCH 103/314] Fix unneeded? register cleanup on return --- mrubyedge/examples/breaking4.rb | 23 +++++++++++++++++++++++ mrubyedge/src/yamrb/optable.rs | 14 +++++++++----- mrubyedge/src/yamrb/vm.rs | 6 +++--- 3 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 mrubyedge/examples/breaking4.rb diff --git a/mrubyedge/examples/breaking4.rb b/mrubyedge/examples/breaking4.rb new file mode 100644 index 0000000..1f8f6e6 --- /dev/null +++ b/mrubyedge/examples/breaking4.rb @@ -0,0 +1,23 @@ +$a1 = [0, 1, 2] +$a2 = [0, 2, 4, 6, 8] + +def test_break + x = 0 + y = 0 + 3.times do |i| + puts "loop #{x}, #{y}" + #__debug__vm_info + $a2.each do |j| + y += j + puts " inner loop #{x}, #{y}" + #__debug__vm_info + break if j >= 5 + end + puts "loop #{x}, #{y}" + #__debug__vm_info + x += i + end + [x, y] +end + +p test_break \ No newline at end of file diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 1ee9c84..30b3d3e 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1126,6 +1126,7 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; let old_irep = vm.current_irep.clone(); let nregs = old_irep.nregs; + // let no_return = vm.current_callinfo.is_some(); let regs0_cloned: Vec<_> = vm.current_regs()[0..nregs].to_vec(); if let Some(environ) = vm.cur_env.get(&vm.current_irep.__id) { @@ -1138,11 +1139,12 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { if let Some(regs_a) = regs0[a].take() { regs0[0].replace(regs_a); } - if nregs > 0 { - regs0[1..=nregs].iter_mut().for_each(|reg| { - reg.take(); - }); - } + // TODO: inspect if this is needed + // if nregs > 0 && no_return { + // regs0[1..=nregs].iter_mut().for_each(|reg| { + // reg.take(); + // }); + // } let ci = vm.current_callinfo.take(); if ci.is_none() { @@ -1151,6 +1153,7 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { vm.current_breadcrumb.replace(upper.clone()); } // When called from mrb_funcall, return error if there's an exception + if let Some(e) = &vm.exception { return Err(e.error_type.borrow().clone()); } @@ -1181,6 +1184,7 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_break(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; let val = vm.get_current_regs_cloned(a)?; + Err(Error::Break(val)) } diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index f657830..19b8532 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -459,10 +459,10 @@ impl VM { eprintln!("PC: {}", self.pc.get()); eprintln!("Current IREP ID: {}", self.current_irep.__id); eprintln!("Current Regs Offset: {}", self.current_regs_offset); - eprintln!("Current Regs:"); - let size = self.current_regs().len(); + eprintln!("Regs:"); + let size = self.regs.len(); for i in 0..size { - let reg = &self.get_current_regs_cloned(i).ok(); + let reg = self.regs.get(i).unwrap(); if let Some(obj) = reg { let inspect: String = mrb_call_inspect(self, obj.clone()) .unwrap() From 87eecc339d2d036837340de1e9569a4545a120fb Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 25 Dec 2025 01:33:37 +0900 Subject: [PATCH 104/314] Add test case --- mrubyedge/tests/break.rs | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/mrubyedge/tests/break.rs b/mrubyedge/tests/break.rs index 32770f3..c7d0748 100644 --- a/mrubyedge/tests/break.rs +++ b/mrubyedge/tests/break.rs @@ -136,6 +136,38 @@ fn break_test_nested() { assert_eq!(result, (5, 6)); } +#[test] +fn break_test_nested_with_closure() { + let code = " + $a = [0, 2, 4, 6, 8] + + def test_break + x = 0 + y = 0 + 3.times do |i| + $a.each do |j| + y += j + break if j >= 5 + end + x += i + end + [x, y] + end + "; + let binary = mrbc_compile("break_test_nested_with_closure", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + // Assert + let args = vec![]; + let result: (i32, i32) = mrb_funcall(&mut vm, None, "test_break", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, (3, 36)); +} + #[test] fn break_test_toplevel() { let code = " From f5f919e95bc83526e1e239fea917517686b37023 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 25 Dec 2025 22:26:27 +0900 Subject: [PATCH 105/314] Modify samples --- mrubyedge/examples/return_block.rb | 48 ++++++++++++++++++- .../{return_block_2.rb => return_block2.rb} | 0 mrubyedge/src/yamrb/optable.rs | 2 +- 3 files changed, 47 insertions(+), 3 deletions(-) rename mrubyedge/examples/{return_block_2.rb => return_block2.rb} (100%) diff --git a/mrubyedge/examples/return_block.rb b/mrubyedge/examples/return_block.rb index c4103bb..c6472d2 100644 --- a/mrubyedge/examples/return_block.rb +++ b/mrubyedge/examples/return_block.rb @@ -1,13 +1,57 @@ def outer inner do - return 5471 + inner do + __debug__vm_info + return 5471 + end end + puts "unreachable" return :unreachable end +def outer2 + puts "hoge" + inner do + :a + end + + inner do + inner do + puts "hoge" + [:a].size + 1.times do + 2.times do + __debug__vm_info + return 5471 + end + end + end + end + puts "unreachable" + return :unreachable +end + +def outer3 + k = 0 + 3.times do |i| + k += i + 4.times do |j| + k += j + __debug__vm_info + return k if k > 10 + end + end + 9999 +end + def inner yield + puts "unreachable" return :unreachable end -p outer \ No newline at end of file +def main + p outer3 +end + +main \ No newline at end of file diff --git a/mrubyedge/examples/return_block_2.rb b/mrubyedge/examples/return_block2.rb similarity index 100% rename from mrubyedge/examples/return_block_2.rb rename to mrubyedge/examples/return_block2.rb diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 30b3d3e..84fc1df 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -895,7 +895,7 @@ pub(crate) fn do_op_send( let method_id = vm.current_irep.syms[b as usize].clone(); if &method_id.name == "__debug__vm_info" { // Special debug method to dump VM info - vm.debug_dump_to_stdout(10); + vm.debug_dump_to_stdout(32); vm.current_regs()[a as usize].replace(Rc::new(RObject::nil())); return Ok(()); } From ba7acaf2868e3b73b07f2f43ce59d2716b60b5e1 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 25 Dec 2025 22:58:29 +0900 Subject: [PATCH 106/314] Enhance debug and inspection methods --- mrubyedge/examples/hash.rb | 7 +++++++ mrubyedge/examples/return_block.rb | 2 +- mrubyedge/src/yamrb/helpers.rs | 6 +----- mrubyedge/src/yamrb/prelude/class.rs | 21 ++++++++++++++++++++ mrubyedge/src/yamrb/prelude/hash.rs | 29 +++++++++++++++++++++++++++- mrubyedge/src/yamrb/vm.rs | 23 +++++++++++++++------- 6 files changed, 74 insertions(+), 14 deletions(-) create mode 100644 mrubyedge/examples/hash.rb diff --git a/mrubyedge/examples/hash.rb b/mrubyedge/examples/hash.rb new file mode 100644 index 0000000..630a9e4 --- /dev/null +++ b/mrubyedge/examples/hash.rb @@ -0,0 +1,7 @@ +ha = { + "keys": ["a", "b", "c"], + "values": [1, 2, 3], + hoge: "fuga" +} + +puts ha.inspect \ No newline at end of file diff --git a/mrubyedge/examples/return_block.rb b/mrubyedge/examples/return_block.rb index c6472d2..26a8023 100644 --- a/mrubyedge/examples/return_block.rb +++ b/mrubyedge/examples/return_block.rb @@ -51,7 +51,7 @@ def inner end def main - p outer3 + p outer end main \ No newline at end of file diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index 3be571c..ef9edbb 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -220,14 +220,10 @@ pub fn mrb_call_inspect(vm: &mut VM, recv: Rc) -> Result, E 0, // unused ) } else { - vm.current_regs_offset += 2; // FIXME: magick number? vm.current_regs()[0].replace(recv.clone()); let func = vm.fn_table[method.func.unwrap()].clone(); - let res = func(vm, &[]); - vm.current_regs_offset -= 2; - - res + func(vm, &[]) } } diff --git a/mrubyedge/src/yamrb/prelude/class.rs b/mrubyedge/src/yamrb/prelude/class.rs index e8077bc..71055e6 100644 --- a/mrubyedge/src/yamrb/prelude/class.rs +++ b/mrubyedge/src/yamrb/prelude/class.rs @@ -11,6 +11,13 @@ use crate::{ pub(crate) fn initialize_class(vm: &mut VM) { let module_class = vm.get_class_by_name("Module"); + mrb_define_cmethod( + vm, + module_class.clone(), + "inspect", + Box::new(mrb_module_inspect), + ); + let class_class = vm.define_standard_class_with_superclass("Class", module_class); // Create singleton class for Object class @@ -137,6 +144,20 @@ fn mrb_class_attr_acceccor(vm: &mut VM, args: &[Rc]) -> Result]) -> Result, Error> { + let class = vm.getself()?; + let class_name = match &class.value { + RValue::Class(c) => c.full_name(), + RValue::Module(m) => m.full_name(), + _ => { + return Err(Error::RuntimeError( + "Module#inspect must be called from module or class".to_string(), + )); + } + }; + Ok(Rc::new(RObject::string(class_name))) +} + #[test] fn test_class_attr_accessor() { use crate::yamrb::helpers::*; diff --git a/mrubyedge/src/yamrb/prelude/hash.rs b/mrubyedge/src/yamrb/prelude/hash.rs index 31c256d..7053419 100644 --- a/mrubyedge/src/yamrb/prelude/hash.rs +++ b/mrubyedge/src/yamrb/prelude/hash.rs @@ -3,7 +3,7 @@ use std::{collections::HashMap, rc::Rc}; use crate::{ Error, yamrb::{ - helpers::{mrb_call_block, mrb_define_class_cmethod, mrb_define_cmethod}, + helpers::{mrb_call_block, mrb_call_inspect, mrb_define_class_cmethod, mrb_define_cmethod}, value::{RObject, RValue}, vm::VM, }, @@ -29,6 +29,12 @@ pub(crate) fn initialize_hash(vm: &mut VM) { mrb_define_cmethod(vm, hash_class.clone(), "each", Box::new(mrb_hash_each)); mrb_define_cmethod(vm, hash_class.clone(), "size", Box::new(mrb_hash_size)); mrb_define_cmethod(vm, hash_class.clone(), "length", Box::new(mrb_hash_size)); + mrb_define_cmethod( + vm, + hash_class.clone(), + "inspect", + Box::new(mrb_hash_inspect), + ); } pub fn mrb_hash_new(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { @@ -103,6 +109,27 @@ fn mrb_hash_each(vm: &mut VM, args: &[Rc]) -> Result, Error Ok(this.clone()) } +fn mrb_hash_inspect(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let hash = match &this.value { + RValue::Hash(h) => h, + _ => { + return Err(Error::RuntimeError( + "Hash#inspect must be called on a hash".to_string(), + )); + } + }; + let hash = hash.borrow(); + let mut parts: Vec = Vec::new(); + for (_, (key, value)) in hash.iter() { + let key_inspect: String = mrb_call_inspect(vm, key.clone())?.as_ref().try_into()?; + let value_inspect: String = mrb_call_inspect(vm, value.clone())?.as_ref().try_into()?; + parts.push(format!("{}=>{}", key_inspect, value_inspect)); + } + let inspect = format!("{{{}}}", parts.join(", ")); + Ok(Rc::new(RObject::string(inspect))) +} + #[test] fn test_hashing() { let vec1 = RObject::string("key".to_string()); diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 19b8532..3a27ac5 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -273,7 +273,7 @@ impl VM { // reached end of the IREP break; } - let op = self + let op = *self .current_irep .code .get(pc) @@ -281,10 +281,14 @@ impl VM { let operand = op.operand; self.pc.set(pc + 1); - if env::var("MRUBYEDGE_DEBUG").is_ok() { + if let Ok(v) = env::var("MRUBYEDGE_DEBUG") { + let level: i32 = v.parse().unwrap_or(1); + if level >= 2 { + self.debug_dump_to_stdout(32); + } eprintln!( "{:?}: {:?} (pos={} len={})", - &op.code, &op.operand, op.pos, op.len + op.code, &operand, op.pos, op.len ); } @@ -458,18 +462,23 @@ impl VM { eprintln!("ID: {}", self.id); eprintln!("PC: {}", self.pc.get()); eprintln!("Current IREP ID: {}", self.current_irep.__id); - eprintln!("Current Regs Offset: {}", self.current_regs_offset); + let current_regs_offset = self.current_regs_offset; + eprintln!("Current Regs Offset: {}", current_regs_offset); eprintln!("Regs:"); let size = self.regs.len(); for i in 0..size { - let reg = self.regs.get(i).unwrap(); + let reg = self.regs.get(i).unwrap().clone(); if let Some(obj) = reg { - let inspect: String = mrb_call_inspect(self, obj.clone()) + let inspect: String = mrb_call_inspect(self, obj) .unwrap() .as_ref() .try_into() .unwrap_or_else(|_| "(uninspectable)".into()); - eprintln!(" R{}: {}", i, inspect); + if i < current_regs_offset { + eprintln!(" R{}(--): {}", i, inspect); + } else { + eprintln!(" R{}(R{}): {}", i, i - current_regs_offset, inspect); + } } else { break; } From b8c1368d8248c46a4ff2061c23edc794c2093869 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 25 Dec 2025 23:07:17 +0900 Subject: [PATCH 107/314] Add ReturnBlock as special exception --- mrubyedge/src/error.rs | 2 ++ mrubyedge/src/yamrb/optable.rs | 13 ++++++++++--- mrubyedge/src/yamrb/prelude/exception.rs | 1 + mrubyedge/src/yamrb/value.rs | 1 + 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/mrubyedge/src/error.rs b/mrubyedge/src/error.rs index 0b5160a..28a3c44 100644 --- a/mrubyedge/src/error.rs +++ b/mrubyedge/src/error.rs @@ -18,6 +18,7 @@ pub enum Error { NameError(String), Break(Rc), + BlockReturn(Rc), } impl fmt::Display for Error { @@ -45,6 +46,7 @@ impl Error { Error::NameError(msg) => format!("Cannot found name: {}", msg), Error::Break(_) => "[Break]".to_string(), + Error::BlockReturn(_) => "[BlockReturn]".to_string(), } } diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 84fc1df..2eb6ee0 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -319,9 +319,9 @@ pub(crate) fn consume_expr( RETURN => { op_return(vm, operand)?; } - // RETURN_BLK => { - // // op_return_blk(vm, &operand)?; - // } + RETURN_BLK => { + op_return_blk(vm, operand)?; + } BREAK => { op_break(vm, operand)?; } @@ -1181,6 +1181,13 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { Ok(()) } +pub(crate) fn op_return_blk(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { + let a = operand.as_b()? as usize; + let val = vm.get_current_regs_cloned(a)?; + + Err(Error::BlockReturn(val)) +} + pub(crate) fn op_break(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; let val = vm.get_current_regs_cloned(a)?; diff --git a/mrubyedge/src/yamrb/prelude/exception.rs b/mrubyedge/src/yamrb/prelude/exception.rs index b75e05e..2c2b7a9 100644 --- a/mrubyedge/src/yamrb/prelude/exception.rs +++ b/mrubyedge/src/yamrb/prelude/exception.rs @@ -31,6 +31,7 @@ pub(crate) fn initialize_exception(vm: &mut VM) { // Dummy class for 'break' control flow let _ = vm.define_standard_class("_Break"); + let _ = vm.define_standard_class("_BlockReturn"); mrb_define_cmethod(vm, exp_class, "message", Box::new(mrb_exception_message)); } diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 1068717..f818379 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -1006,6 +1006,7 @@ impl RClass { Error::NameError(_) => vm.get_class_by_name("NameError"), Error::Break(_) => vm.get_class_by_name("_Break"), + Error::BlockReturn(_) => vm.get_class_by_name("_BlockReturn"), } } } From 57b5d0313e1874128b2cc70c847ec53fb6983190 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Fri, 26 Dec 2025 00:45:28 +0900 Subject: [PATCH 108/314] Fix dump bug --- mrubyedge/src/yamrb/vm.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 3a27ac5..59262ae 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -479,6 +479,8 @@ impl VM { } else { eprintln!(" R{}(R{}): {}", i, i - current_regs_offset, inspect); } + } else if i < current_regs_offset { + eprintln!(" R{}(--): ", i); } else { break; } From 94957c483d92752c2844d5e243f54d6c979d5fc5 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Fri, 26 Dec 2025 00:46:06 +0900 Subject: [PATCH 109/314] Fix example --- mrubyedge/examples/return_block.rb | 67 +++++++++++++++--------------- 1 file changed, 33 insertions(+), 34 deletions(-) diff --git a/mrubyedge/examples/return_block.rb b/mrubyedge/examples/return_block.rb index 26a8023..a6ec35c 100644 --- a/mrubyedge/examples/return_block.rb +++ b/mrubyedge/examples/return_block.rb @@ -1,7 +1,6 @@ def outer inner do inner do - __debug__vm_info return 5471 end end @@ -9,40 +8,40 @@ def outer return :unreachable end -def outer2 - puts "hoge" - inner do - :a - end +# def outer2 +# puts "hoge" +# inner do +# :a +# end - inner do - inner do - puts "hoge" - [:a].size - 1.times do - 2.times do - __debug__vm_info - return 5471 - end - end - end - end - puts "unreachable" - return :unreachable -end +# inner do +# inner do +# puts "hoge" +# [:a].size +# 1.times do +# 2.times do +# __debug__vm_info +# return 5471 +# end +# end +# end +# end +# puts "unreachable" +# return :unreachable +# end -def outer3 - k = 0 - 3.times do |i| - k += i - 4.times do |j| - k += j - __debug__vm_info - return k if k > 10 - end - end - 9999 -end +# def outer3 +# k = 0 +# 3.times do |i| +# k += i +# 4.times do |j| +# k += j +# __debug__vm_info +# return k if k > 10 +# end +# end +# 9999 +# end def inner yield @@ -51,7 +50,7 @@ def inner end def main - p outer + puts outer end main \ No newline at end of file From c86857c659b63f8e57ad96138e151fd3e6c21a3d Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 29 Dec 2025 01:31:10 +0900 Subject: [PATCH 110/314] Support 1 nested pattern --- mrubyedge/examples/return_block.rb | 56 ------------------- mrubyedge/examples/return_block0.rb | 34 ++++++++++++ mrubyedge/examples/return_block1.rb | 83 +++++++++++++++++++++++++++++ mrubyedge/src/yamrb/vm.rs | 51 +++++++++++++++--- 4 files changed, 160 insertions(+), 64 deletions(-) delete mode 100644 mrubyedge/examples/return_block.rb create mode 100644 mrubyedge/examples/return_block0.rb create mode 100644 mrubyedge/examples/return_block1.rb diff --git a/mrubyedge/examples/return_block.rb b/mrubyedge/examples/return_block.rb deleted file mode 100644 index a6ec35c..0000000 --- a/mrubyedge/examples/return_block.rb +++ /dev/null @@ -1,56 +0,0 @@ -def outer - inner do - inner do - return 5471 - end - end - puts "unreachable" - return :unreachable -end - -# def outer2 -# puts "hoge" -# inner do -# :a -# end - -# inner do -# inner do -# puts "hoge" -# [:a].size -# 1.times do -# 2.times do -# __debug__vm_info -# return 5471 -# end -# end -# end -# end -# puts "unreachable" -# return :unreachable -# end - -# def outer3 -# k = 0 -# 3.times do |i| -# k += i -# 4.times do |j| -# k += j -# __debug__vm_info -# return k if k > 10 -# end -# end -# 9999 -# end - -def inner - yield - puts "unreachable" - return :unreachable -end - -def main - puts outer -end - -main \ No newline at end of file diff --git a/mrubyedge/examples/return_block0.rb b/mrubyedge/examples/return_block0.rb new file mode 100644 index 0000000..ee39612 --- /dev/null +++ b/mrubyedge/examples/return_block0.rb @@ -0,0 +1,34 @@ +def outer + puts "start outer" + inner do + return 5471 + puts "unreachable inner" + end + puts "unreachable outer" + :unreachable +end + +def outer2 + puts "start outer2" + 1.times do + puts "start times" + return 5472 + puts "unreachable: after times" + end + puts "unreachable outer2" +end + +def inner + yield + puts "unreachable" + return :unreachable +end + +def main + puts "=== outer ===" + puts outer + puts "=== outer2 ===" + puts outer2 +end + +main \ No newline at end of file diff --git a/mrubyedge/examples/return_block1.rb b/mrubyedge/examples/return_block1.rb new file mode 100644 index 0000000..cb63e61 --- /dev/null +++ b/mrubyedge/examples/return_block1.rb @@ -0,0 +1,83 @@ +def outer + inner do + inner do + return 5471 + end + end + puts "unreachable" + return :unreachable +end + +def outer2 + puts "start outer2" + 1.times do + inner do + puts "start inner" + return 5471 + end + puts "unreachable: after inner" + end + puts "unreachable: after times" +end + +def outer3 + k = 0 + [0, 1, 2].each do |i| + k += i + 4.times do |j| + k += j + #__debug__vm_info + return k if k > 10 + end + end + 9999 +end + +def outer4 + puts "start outer4" + inner do + 1.times do + puts "start times" + return 5471 + end + puts "unreachable: after times" + end + puts "unreachable: after inner" +end + +def outer5 + puts "start outer5" + inner do + 1.times do + puts "start times" + 1.times do + puts "start inner inner" + return 5471 + puts "unreachable: after return in inner inner" + end + end + puts "unreachable: after times" + end + puts "unreachable: after inner" +end + +def inner + yield + puts "unreachable" + return :unreachable +end + +def main + puts "=== outer ===" + puts outer + puts "=== outer2 ===" + puts outer2 + puts "=== outer3 ===" + puts outer3 + puts "=== outer4 ===" + puts outer4 + puts "=== outer5 ===" + puts outer5 #??? +end + +main \ No newline at end of file diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 59262ae..97613f9 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -234,19 +234,46 @@ impl VM { loop { if !rescued && let Some(e) = self.exception.clone() { let operand = insn::Fetched::B(0); + let mut retreg = None; if let Some(pos) = self.find_next_handler_pos() { self.pc.set(pos); rescued = true; continue; } - let retreg = match self.current_breadcrumb.as_ref() { - Some(bc) if bc.event == "do_op_send" => { - let retreg = bc.as_ref().return_reg.unwrap_or(0); - Some(retreg) - } - _ => None, - }; + // if matches!(e.error_type.borrow().clone(), Error::BlockReturn(_)) { + // eprintln!("Still Uncaught BlockReturn:"); + // self.debug_dump_to_stdout(16); + // } + + if let Error::BlockReturn(v) = e.error_type.borrow().clone() + && let Some(bc) = self.current_breadcrumb.as_ref().cloned() + && bc.event == "do_op_send" + && let Some(upper_bc) = bc.upper.as_ref() + && upper_bc.event == "do_op_send" + { + self.debug_dump_to_stdout(16); + // dbg!("Unwind BlockReturn through do_op_send"); + op_return(self, &operand).expect("[bug]cannot return"); + let retreg = upper_bc + .as_ref() + .return_reg + .expect("upper breadcrumb has return target reg"); + self.exception.take(); + // self.current_breadcrumb = upper_bc.upper.clone(); + self.current_regs()[retreg].replace(v); + continue; + } + + if matches!(e.error_type.borrow().clone(), Error::Break(_)) { + retreg = match self.current_breadcrumb.as_ref() { + Some(bc) if bc.event == "do_op_send" => { + let retreg = bc.as_ref().return_reg.unwrap_or(0); + Some(retreg) + } + _ => None, + }; + } match op_return(self, &operand) { Ok(_) => {} Err(_) => { @@ -460,9 +487,17 @@ impl VM { pub fn debug_dump_to_stdout(&mut self, max_breadcrumb_level: usize) { eprintln!("=== VM Dump ==="); eprintln!("ID: {}", self.id); - eprintln!("PC: {}", self.pc.get()); eprintln!("Current IREP ID: {}", self.current_irep.__id); + eprintln!("PC: {}", self.pc.get()); let current_regs_offset = self.current_regs_offset; + eprintln!("IREPs:"); + self.current_irep + .code + .iter() + .enumerate() + .for_each(|(i, op)| { + eprintln!("{:04} {:?}: {:?}", i, op.code, op.operand); + }); eprintln!("Current Regs Offset: {}", current_regs_offset); eprintln!("Regs:"); let size = self.regs.len(); From 5b7d11e96b912575c8393f0aebc1cd367bee7715 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 29 Dec 2025 14:52:28 +0900 Subject: [PATCH 111/314] Block return works now --- mrubyedge/examples/return_block1.rb | 6 ++-- mrubyedge/src/error.rs | 4 +-- mrubyedge/src/yamrb/optable.rs | 41 +++++++++++++++++---------- mrubyedge/src/yamrb/value.rs | 2 +- mrubyedge/src/yamrb/vm.rs | 44 ++++++++++++++++------------- 5 files changed, 56 insertions(+), 41 deletions(-) diff --git a/mrubyedge/examples/return_block1.rb b/mrubyedge/examples/return_block1.rb index cb63e61..f1acc99 100644 --- a/mrubyedge/examples/return_block1.rb +++ b/mrubyedge/examples/return_block1.rb @@ -13,7 +13,7 @@ def outer2 1.times do inner do puts "start inner" - return 5471 + return 5472 end puts "unreachable: after inner" end @@ -38,7 +38,7 @@ def outer4 inner do 1.times do puts "start times" - return 5471 + return 5474 end puts "unreachable: after times" end @@ -52,7 +52,7 @@ def outer5 puts "start times" 1.times do puts "start inner inner" - return 5471 + return 5475 puts "unreachable: after return in inner inner" end end diff --git a/mrubyedge/src/error.rs b/mrubyedge/src/error.rs index 28a3c44..5286417 100644 --- a/mrubyedge/src/error.rs +++ b/mrubyedge/src/error.rs @@ -18,7 +18,7 @@ pub enum Error { NameError(String), Break(Rc), - BlockReturn(Rc), + BlockReturn(usize, Rc), } impl fmt::Display for Error { @@ -46,7 +46,7 @@ impl Error { Error::NameError(msg) => format!("Cannot found name: {}", msg), Error::Break(_) => "[Break]".to_string(), - Error::BlockReturn(_) => "[BlockReturn]".to_string(), + Error::BlockReturn(_, _) => "[BlockReturn]".to_string(), } } diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 2eb6ee0..8369183 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1184,8 +1184,12 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_return_blk(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; let val = vm.get_current_regs_cloned(a)?; + let target_irep_id = vm + .get_outermost_env() + .expect("not found outermost env") + .__irep_id; - Err(Error::BlockReturn(val)) + Err(Error::BlockReturn(target_irep_id, val)) } pub(crate) fn op_break(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { @@ -1546,6 +1550,7 @@ pub(crate) fn op_lambda(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; let irep = Some(vm.current_irep.reps[b as usize].clone()); let environ = ENV { + __irep_id: vm.current_irep.__id, upper: vm.upper.clone(), current_regs_offset: vm.current_regs_offset, is_expired: Cell::new(false), @@ -1580,6 +1585,7 @@ pub(crate) fn op_block(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; let irep = Some(vm.current_irep.reps[b as usize].clone()); let environ = ENV { + __irep_id: vm.current_irep.__id, upper: vm.upper.clone(), current_regs_offset: vm.current_regs_offset, is_expired: Cell::new(false), @@ -1755,23 +1761,33 @@ pub(crate) fn op_def(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let method = vm.get_current_regs_cloned(a as usize + 1)?; let sym = vm.current_irep.syms[b as usize].clone(); - let target_ref = target.as_ref(); let method_ref = method.as_ref(); - match (&target_ref.value, &method_ref.value) { - (RValue::Class(klass), RValue::Proc(method)) => { - let mut procs = klass.procs.borrow_mut(); - let mut method = method.clone(); + // まずmethodをmatchで取得して定義 + let method = match &method_ref.value { + RValue::Proc(proc) => { + let mut method = proc.clone(); + method.environ = None; // method cannot trace the upper environment method.sym_id = Some(sym.clone()); + Ok(method) + } + _ => Err(Error::ArgumentError( + "def operand 2 must be Proc (method)".to_string(), + )), + }?; + + // その後でreceiverに定義 + let target_ref = target.as_ref(); + match &target_ref.value { + RValue::Class(klass) => { + let mut procs = klass.procs.borrow_mut(); procs.insert(sym.name.clone(), method); } - (RValue::Module(module), RValue::Proc(method)) => { + RValue::Module(module) => { let mut procs = module.procs.borrow_mut(); - let mut method = method.clone(); - method.sym_id = Some(sym.clone()); procs.insert(sym.name.clone(), method); } - (_, RValue::Proc(method)) => { + _ => { let robject = target.clone(); let current_class = robject.get_class(vm); let sclass = if current_class.is_singleton { @@ -1780,13 +1796,8 @@ pub(crate) fn op_def(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { robject.initialize_or_get_singleton_class(vm) }; let mut procs = sclass.procs.borrow_mut(); - let mut method = method.clone(); - method.sym_id = Some(sym.clone()); procs.insert(sym.name.clone(), method); } - _ => { - unreachable!("DEF must be called with Proc"); - } } vm.current_regs()[a as usize].replace(RObject::symbol(sym).to_refcount_assigned()); Ok(()) diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index f818379..cc56471 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -1006,7 +1006,7 @@ impl RClass { Error::NameError(_) => vm.get_class_by_name("NameError"), Error::Break(_) => vm.get_class_by_name("_Break"), - Error::BlockReturn(_) => vm.get_class_by_name("_BlockReturn"), + Error::BlockReturn(_, _) => vm.get_class_by_name("_BlockReturn"), } } } diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 97613f9..6086397 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -241,27 +241,14 @@ impl VM { continue; } - // if matches!(e.error_type.borrow().clone(), Error::BlockReturn(_)) { - // eprintln!("Still Uncaught BlockReturn:"); - // self.debug_dump_to_stdout(16); - // } - - if let Error::BlockReturn(v) = e.error_type.borrow().clone() - && let Some(bc) = self.current_breadcrumb.as_ref().cloned() - && bc.event == "do_op_send" - && let Some(upper_bc) = bc.upper.as_ref() - && upper_bc.event == "do_op_send" + if let Error::BlockReturn(id, v) = e.error_type.borrow().clone() + && self.current_irep.__id == id { - self.debug_dump_to_stdout(16); - // dbg!("Unwind BlockReturn through do_op_send"); - op_return(self, &operand).expect("[bug]cannot return"); - let retreg = upper_bc - .as_ref() - .return_reg - .expect("upper breadcrumb has return target reg"); + // reached caller method's IREP, just return + let operand = insn::Fetched::B(16); // FIXME: just very far reg + self.current_regs()[16].replace(v); self.exception.take(); - // self.current_breadcrumb = upper_bc.upper.clone(); - self.current_regs()[retreg].replace(v); + op_return(self, &operand).expect("[bug]cannot return"); continue; } @@ -522,7 +509,12 @@ impl VM { } // eprintln!("Current CallInfo: {:?}", self.current_callinfo); eprintln!("Target Class: {}", self.target_class.name()); - // eprintln!("Exception: {:?}", self.exception); + eprintln!( + "Exception: {:?}", + self.exception + .as_deref() + .map(|e| e.error_type.borrow().clone()) + ); eprintln!("--- Breadcrumb ---"); if let Some(bc) = &self.current_breadcrumb { bc.display_breadcrumb_for_debug(0, max_breadcrumb_level); @@ -531,6 +523,17 @@ impl VM { } eprintln!("=== End of VM Dump ==="); } + + pub fn get_outermost_env(&self) -> Option> { + let mut env = self.upper.clone(); + while let Some(e) = env.clone() { + if e.upper.is_none() { + return env; + } + env = e.upper.clone(); + } + env + } } fn interpret_insn(mut insns: &[u8]) -> Vec { @@ -631,6 +634,7 @@ pub struct CALLINFO { #[derive(Debug, Clone)] pub struct ENV { + pub __irep_id: usize, pub upper: Option>, pub captured: RefCell>>>>, pub current_regs_offset: usize, From 27813ae2e392b92d51824d4e237234177e70bc54 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 29 Dec 2025 15:14:28 +0900 Subject: [PATCH 112/314] Add test cases for block return --- mrubyedge/tests/return_block.rs | 214 ++++++++++++++++++++++++++++++++ 1 file changed, 214 insertions(+) create mode 100644 mrubyedge/tests/return_block.rs diff --git a/mrubyedge/tests/return_block.rs b/mrubyedge/tests/return_block.rs new file mode 100644 index 0000000..5ce9b4e --- /dev/null +++ b/mrubyedge/tests/return_block.rs @@ -0,0 +1,214 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn return_block_simple() { + let code = " + def outer + inner do + return 5471 + end + :unreachable + end + + def inner + yield + return :unreachable + end + "; + let binary = mrbc_compile("return_block_simple", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + // Assert + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "outer", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 5471); +} + +#[test] +fn return_block_with_c_func() { + let code = " + def outer2 + 1.times do + return 5472 + end + :unreachable + end + "; + let binary = mrbc_compile("return_block_with_c_func", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + // Assert + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "outer2", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 5472); +} + +#[test] +fn return_block_nested() { + let code = " + def outer + inner do + inner do + return 5473 + end + end + :unreachable + end + + def inner + yield + :unreachable + end + "; + let binary = mrbc_compile("return_block_nested", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + // Assert + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "outer", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 5473); +} + +#[test] +fn return_block_nested_with_c_func() { + let code = " + def outer2 + 1.times do + inner do + return 5474 + end + end + :unreachable + end + + def inner + yield + :unreachable + end + "; + let binary = mrbc_compile("return_block_nested_with_c_func", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + // Assert + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "outer2", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 5474); +} + +#[test] +fn return_block_nested_each_times() { + let code = " + def outer3 + k = 0 + [0, 1, 2].each do |i| + k += i + 4.times do |j| + k += j + return k if k > 10 + end + end + 9999 + end + "; + let binary = mrbc_compile("return_block_nested_each_times", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + // Assert + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "outer3", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + // k = 0+0 = 0, then 0+0, 0+1, 0+2, 0+3 = 6, then 6+1 = 7, then 7+0, 7+1, 7+2, 7+3 = 13 > 10 + assert_eq!(result, 13); +} + +#[test] +fn return_block_c_func_in_yield() { + let code = " + def outer4 + inner do + 1.times do + return 5475 + end + end + :unreachable + end + + def inner + yield + :unreachable + end + "; + let binary = mrbc_compile("return_block_c_func_in_yield", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + // Assert + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "outer4", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 5475); +} + +#[test] +fn return_block_deeply_nested_c_func() { + let code = " + def outer5 + inner do + 1.times do + 1.times do + return 5476 + end + end + end + :unreachable + end + + def inner + yield + :unreachable + end + "; + let binary = mrbc_compile("return_block_deeply_nested_c_func", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + // Assert + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "outer5", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 5476); +} From aeb8370e0aeb6c88b8019bb5e52fd1322697849f Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 29 Dec 2025 15:17:01 +0900 Subject: [PATCH 113/314] Fix remaining japanese comment --- mrubyedge/src/yamrb/optable.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 8369183..0645be7 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1763,7 +1763,7 @@ pub(crate) fn op_def(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let method_ref = method.as_ref(); - // まずmethodをmatchで取得して定義 + // First, extract and prepare the method from the Proc let method = match &method_ref.value { RValue::Proc(proc) => { let mut method = proc.clone(); @@ -1776,7 +1776,7 @@ pub(crate) fn op_def(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { )), }?; - // その後でreceiverに定義 + // Then, define it on the receiver let target_ref = target.as_ref(); match &target_ref.value { RValue::Class(klass) => { From 031c693fcf4d7c232254bf896b97f921bdc5e18c Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 29 Dec 2025 15:24:39 +0900 Subject: [PATCH 114/314] Support -e option --- mrubyedge-cli/src/subcommands/run.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/mrubyedge-cli/src/subcommands/run.rs b/mrubyedge-cli/src/subcommands/run.rs index 258de8c..f8f3ff8 100644 --- a/mrubyedge-cli/src/subcommands/run.rs +++ b/mrubyedge-cli/src/subcommands/run.rs @@ -10,14 +10,29 @@ pub struct RunArgs { #[arg(long)] pub dump_insns: bool, + /// Execute the given Ruby code string + #[arg(short = 'e', long = "eval", value_name = "CODE")] + pub eval: Option, + /// Ruby source file or mrb binary to run - pub file: PathBuf, + pub file: Option, } pub fn execute(args: RunArgs) -> Result<(), Box> { - let mut buf = Vec::new(); - File::open(&args.file)?.read_to_end(&mut buf)?; - let is_mrb_direct = &buf[0..4] == &['R' as u8, 'I' as u8, 'T' as u8, 'E' as u8][..]; + let buf = if let Some(code) = &args.eval { + // Execute code from -e option + code.clone().into_bytes() + } else if let Some(file) = args.file { + // Read from file + let mut buf = Vec::new(); + File::open(&file)?.read_to_end(&mut buf)?; + buf + } else { + return Err("Either -e option or file path must be provided".into()); + }; + + let is_mrb_direct = + args.eval.is_none() && buf.len() >= 4 && buf[0..4] == [b'R', b'I', b'T', b'E'][..]; unsafe { let mrb_bin = if is_mrb_direct { buf.to_vec() From 3c44925fe553a6e010662d39b1c6c8ce0b18c639 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 29 Dec 2025 15:32:36 +0900 Subject: [PATCH 115/314] Feature out debug functionalities from default --- mrubyedge/Cargo.toml | 1 + mrubyedge/src/yamrb/vm.rs | 69 +++++++++++++++++++++------------------ rust-analyzer.json | 3 +- 3 files changed, 41 insertions(+), 32 deletions(-) diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 65ca354..44c75dc 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -28,4 +28,5 @@ harness = false default = ["wasi"] wasi = ["dep:getrandom"] mruby-regexp = ["dep:regex"] +mrubyedge-debug = ["wasi"] no-wasi = [] diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 3a27ac5..caade61 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -5,7 +5,6 @@ use std::rc::Rc; use crate::Error; use crate::rite::{Irep, Rite, insn}; -use crate::yamrb::helpers::mrb_call_inspect; use super::op::Op; use super::prelude::prelude; @@ -41,6 +40,7 @@ pub struct Breadcrumb { } impl Breadcrumb { + #[cfg(feature = "mrubyedge-debug")] pub fn display_breadcrumb_for_debug(&self, level: usize, max_level: usize) -> bool { if level > max_level { return false; @@ -281,6 +281,7 @@ impl VM { let operand = op.operand; self.pc.set(pc + 1); + #[cfg(feature = "mrubyedge-debug")] if let Ok(v) = env::var("MRUBYEDGE_DEBUG") { let level: i32 = v.parse().unwrap_or(1); if level >= 2 { @@ -457,42 +458,48 @@ impl VM { class } + #[allow(unused)] pub fn debug_dump_to_stdout(&mut self, max_breadcrumb_level: usize) { - eprintln!("=== VM Dump ==="); - eprintln!("ID: {}", self.id); - eprintln!("PC: {}", self.pc.get()); - eprintln!("Current IREP ID: {}", self.current_irep.__id); - let current_regs_offset = self.current_regs_offset; - eprintln!("Current Regs Offset: {}", current_regs_offset); - eprintln!("Regs:"); - let size = self.regs.len(); - for i in 0..size { - let reg = self.regs.get(i).unwrap().clone(); - if let Some(obj) = reg { - let inspect: String = mrb_call_inspect(self, obj) - .unwrap() - .as_ref() - .try_into() - .unwrap_or_else(|_| "(uninspectable)".into()); - if i < current_regs_offset { - eprintln!(" R{}(--): {}", i, inspect); + #[cfg(feature = "mrubyedge-debug")] + { + use crate::yamrb::helpers::mrb_call_inspect; + + eprintln!("=== VM Dump ==="); + eprintln!("ID: {}", self.id); + eprintln!("PC: {}", self.pc.get()); + eprintln!("Current IREP ID: {}", self.current_irep.__id); + let current_regs_offset = self.current_regs_offset; + eprintln!("Current Regs Offset: {}", current_regs_offset); + eprintln!("Regs:"); + let size = self.regs.len(); + for i in 0..size { + let reg = self.regs.get(i).unwrap().clone(); + if let Some(obj) = reg { + let inspect: String = mrb_call_inspect(self, obj) + .unwrap() + .as_ref() + .try_into() + .unwrap_or_else(|_| "(uninspectable)".into()); + if i < current_regs_offset { + eprintln!(" R{}(--): {}", i, inspect); + } else { + eprintln!(" R{}(R{}): {}", i, i - current_regs_offset, inspect); + } } else { - eprintln!(" R{}(R{}): {}", i, i - current_regs_offset, inspect); + break; } + } + // eprintln!("Current CallInfo: {:?}", self.current_callinfo); + eprintln!("Target Class: {}", self.target_class.name()); + // eprintln!("Exception: {:?}", self.exception); + eprintln!("--- Breadcrumb ---"); + if let Some(bc) = &self.current_breadcrumb { + bc.display_breadcrumb_for_debug(0, max_breadcrumb_level); } else { - break; + eprintln!("(none)"); } + eprintln!("=== End of VM Dump ==="); } - // eprintln!("Current CallInfo: {:?}", self.current_callinfo); - eprintln!("Target Class: {}", self.target_class.name()); - // eprintln!("Exception: {:?}", self.exception); - eprintln!("--- Breadcrumb ---"); - if let Some(bc) = &self.current_breadcrumb { - bc.display_breadcrumb_for_debug(0, max_breadcrumb_level); - } else { - eprintln!("(none)"); - } - eprintln!("=== End of VM Dump ==="); } } diff --git a/rust-analyzer.json b/rust-analyzer.json index 4aee5ce..b9bb1f7 100644 --- a/rust-analyzer.json +++ b/rust-analyzer.json @@ -1,5 +1,6 @@ { "rust-analyzer.cargo.features": [ - "mruby-regexp" + "mruby-regexp", + "mrubyedge-debug" ] } \ No newline at end of file From b89d7d6b5f26771f020b3da670bfde339c7d14f6 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 29 Dec 2025 15:40:27 +0900 Subject: [PATCH 116/314] Add to test target --- .github/workflows/mrubyedge.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/mrubyedge.yml b/.github/workflows/mrubyedge.yml index 5b72780..bb6e729 100644 --- a/.github/workflows/mrubyedge.yml +++ b/.github/workflows/mrubyedge.yml @@ -40,7 +40,10 @@ jobs: - name: Run tests for "${{ matrix.BUILD_TARGET }}" profile run: | set +e - cargo test --workspace --exclude mec --features mruby-regexp --profile ${{ matrix.BUILD_TARGET }} + cargo test --workspace \ + --exclude mec \ + --features "mrubyedge-debug,mruby-regexp" \ + --profile ${{ matrix.BUILD_TARGET }} TEST_RESULT=$? if [ $TEST_RESULT != 0 ]; then echo "Some tests failed. Debug:" @@ -48,7 +51,11 @@ jobs: exit $TEST_RESULT fi - name: Build binaries for "${{ matrix.BUILD_TARGET }}" profile - run: cargo build --workspace --exclude mec --features mruby-regexp --profile ${{ matrix.BUILD_TARGET }} + run: | + cargo build --workspace \ + --exclude mec \ + --features "mrubyedge-debug,mruby-regexp" \ + --profile ${{ matrix.BUILD_TARGET }} lint: runs-on: ubuntu-latest From 9e2661069836a0b6198d474b097be10f34e4bf88 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 29 Dec 2025 15:43:26 +0900 Subject: [PATCH 117/314] Bump version to 1.0.13 --- Cargo.lock | 12 ++++++------ mrubyedge/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f1d40e8..c589bad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -572,10 +572,10 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b20b3a61b50f381ff7e47641124a9c377b24a97653df4696d15d43e5a6d38846" dependencies = [ - "criterion", "getrandom", - "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -583,11 +583,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b20b3a61b50f381ff7e47641124a9c377b24a97653df4696d15d43e5a6d38846" +version = "1.0.13" dependencies = [ + "criterion", "getrandom", + "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -600,7 +600,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.12 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.0.12", "nom", "rand", "syn", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 44c75dc..c7eb519 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.0.12" +version = "1.0.13" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From 6ef6662272eb878885a973492056bf36a83e5b14 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 29 Dec 2025 15:45:09 +0900 Subject: [PATCH 118/314] Bump version to 1.0.13 (cli) --- Cargo.lock | 14 +++++++------- mrubyedge-cli/Cargo.toml | 4 ++-- mrubyedge-cli/src/main.rs | 4 +--- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c589bad..fd30a63 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -571,11 +571,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b20b3a61b50f381ff7e47641124a9c377b24a97653df4696d15d43e5a6d38846" +version = "1.0.13" dependencies = [ + "criterion", "getrandom", + "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -584,10 +584,10 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fff76ad9eb0e1718e47933679cddae83b3b9a4bc32245f7545c9bb7a8b57a0b" dependencies = [ - "criterion", "getrandom", - "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -595,12 +595,12 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.0.12" +version = "1.0.13" dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.12", + "mrubyedge 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index cb430e6..00ba36f 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.0.12" +version = "1.0.13" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } mruby-compiler2-sys = "0.2.2" -mrubyedge = { version = "1.0.12", features = ["default", "mruby-regexp"] } +mrubyedge = { version = "1.0.13", features = ["default", "mruby-regexp"] } rand = "0.8.5" nom = "7.1.3" askama = "0.12.1" diff --git a/mrubyedge-cli/src/main.rs b/mrubyedge-cli/src/main.rs index d995025..a94d412 100644 --- a/mrubyedge-cli/src/main.rs +++ b/mrubyedge-cli/src/main.rs @@ -5,9 +5,7 @@ use mrubyedge_cli::subcommands; const LONG_VERSION: &str = concat!( env!("CARGO_PKG_VERSION"), ", using mruby/edge ", - env!("CARGO_PKG_VERSION"), - // FIXME: update version macro usage - // mrubyedge::version!(), + mrubyedge::version!(), ); #[derive(Parser)] From 30473127072ac6b02cb15f36badd15e0c81ab147 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 29 Dec 2025 20:58:34 +0900 Subject: [PATCH 119/314] Small fix in namespaced def --- mrubyedge/Cargo.toml | 2 +- mrubyedge/src/yamrb/helpers.rs | 9 ++++++++ mrubyedge/src/yamrb/optable.rs | 16 ++++++++----- mrubyedge/src/yamrb/prelude/object.rs | 9 ++++---- mrubyedge/src/yamrb/value.rs | 4 ++++ mrubyedge/src/yamrb/vm.rs | 33 +++++++++++++++++++-------- 6 files changed, 51 insertions(+), 22 deletions(-) diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index c7eb519..071f730 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -25,7 +25,7 @@ name = "benchmark" harness = false [features] -default = ["wasi"] +default = ["wasi", "mrubyedge-debug"] wasi = ["dep:getrandom"] mruby-regexp = ["dep:regex"] mrubyedge-debug = ["wasi"] diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index ef9edbb..1b87326 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -227,6 +227,15 @@ pub fn mrb_call_inspect(vm: &mut VM, recv: Rc) -> Result, E } } +pub fn mrb_call_p(vm: &mut VM, recv: Rc) { + let inspect = mrb_call_inspect(vm, recv).expect("failed to call inspect"); + let inspect: String = inspect + .as_ref() + .try_into() + .expect("failed to convert to string"); + eprintln!("{}", inspect); +} + /// Defines a C method (native Rust function) on a Ruby class. /// /// # Arguments diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 0645be7..ebb7b2d 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -902,7 +902,11 @@ pub(crate) fn do_op_send( let block_index = (a + c + 1) as usize; - let recv = vm.get_current_regs_cloned(recv_index)?; + let recv = if recv_index == 0 { + vm.getself()? + } else { + vm.get_current_regs_cloned(recv_index)? + }; let mut args = (0..c) .map(|i| { vm.get_current_regs_cloned((a + i + 1) as usize) @@ -1694,12 +1698,14 @@ pub(crate) fn op_class(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let class_value = RObject::class(klass.clone(), vm); class_value.initialize_or_get_singleton_class_for_class(vm); if let Some(parent) = parent_module { - parent.consts.borrow_mut().insert(name.clone(), class_value); + parent + .consts + .borrow_mut() + .insert(name.clone(), class_value.clone()); } else { - vm.consts.insert(name.clone(), class_value); + vm.consts.insert(name.clone(), class_value.clone()); } - let class_value = RObject::class(klass.clone(), vm); vm.current_regs()[a as usize].replace(class_value); Ok(()) } @@ -1737,8 +1743,6 @@ pub(crate) fn op_exec(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { return_reg: None, }); vm.current_breadcrumb.replace(new_breadcrumb); - - vm.current_regs()[a as usize].replace(recv.clone()); push_callinfo(vm, "".into(), 0, None, a as usize); vm.pc.set(0); diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index f241d30..419bff0 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -194,13 +194,12 @@ pub fn mrb_object_object_id(vm: &mut VM, _args: &[Rc]) -> Result]) -> Result, Error> { let obj = vm.getself()?; + if obj.is_main() { + return Ok(RObject::string("main".to_string()).to_refcount_assigned()); + } let class = obj.get_class(vm); let addr = format!("{:018p}", Rc::as_ptr(&obj)); - Ok(Rc::new(RObject::string(format!( - "#<{}:{}>", - class.full_name(), - addr - )))) + Ok(RObject::string(format!("#<{}:{}>", class.full_name(), addr)).to_refcount_assigned()) } pub fn mrb_object_raise(_vm: &mut VM, args: &[Rc]) -> Result, Error> { diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index cc56471..0c70086 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -308,6 +308,10 @@ impl RObject { matches!(self.tt, RType::Nil) } + pub fn is_main(&self) -> bool { + self.object_id.get() == 0 + } + pub fn set_ivar(&self, key: &str, value: Rc) { self.ivar.borrow_mut().insert(key.to_string(), value); } diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 0198ef1..1b4f9af 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -245,7 +245,7 @@ impl VM { && self.current_irep.__id == id { // reached caller method's IREP, just return - let operand = insn::Fetched::B(16); // FIXME: just very far reg + let operand = insn::Fetched::B(16); // FIXME: just a bit far reg self.current_regs()[16].replace(v); self.exception.take(); op_return(self, &operand).expect("[bug]cannot return"); @@ -407,13 +407,20 @@ impl VM { Some(c) => c, None => self.object_class.clone(), }; - let class = Rc::new(RClass::new(name, Some(superclass), parent_module)); + let class = Rc::new(RClass::new(name, Some(superclass), parent_module.clone())); let object = RObject::class(class.clone(), self); self.consts.insert(name.to_string(), object.clone()); - self.object_class - .consts - .borrow_mut() - .insert(name.to_string(), object); + if let Some(parent) = parent_module { + parent + .consts + .borrow_mut() + .insert(name.to_string(), object.clone()); + } else { + self.object_class + .consts + .borrow_mut() + .insert(name.to_string(), object); + } class } @@ -496,17 +503,23 @@ impl VM { for i in 0..size { let reg = self.regs.get(i).unwrap().clone(); if let Some(obj) = reg { - let inspect: String = mrb_call_inspect(self, obj) + let inspect: String = mrb_call_inspect(self, obj.clone()) .unwrap() .as_ref() .try_into() .unwrap_or_else(|_| "(uninspectable)".into()); if i < current_regs_offset { - eprintln!(" R{}(--): {}", i, inspect); + eprintln!(" R{}(--): {}(oid={})", i, inspect, obj.object_id.get()); } else { - eprintln!(" R{}(R{}): {}", i, i - current_regs_offset, inspect); + eprintln!( + " R{}(R{}): {}(oid={})", + i, + i - current_regs_offset, + inspect, + obj.object_id.get() + ); } - } else if i < current_regs_offset { + } else if i < 16 || i < current_regs_offset { eprintln!(" R{}(--): ", i); } else { break; From fda38052bd73ebb61799a671660ccd9a93611bc8 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 29 Dec 2025 21:27:58 +0900 Subject: [PATCH 120/314] Fix not to inspect destroys VM self register --- mrubyedge/src/yamrb/helpers.rs | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index 1b87326..ad2ab9c 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -220,10 +220,15 @@ pub fn mrb_call_inspect(vm: &mut VM, recv: Rc) -> Result, E 0, // unused ) } else { - vm.current_regs()[0].replace(recv.clone()); - + let old = vm.current_regs()[0].replace(recv.clone()); let func = vm.fn_table[method.func.unwrap()].clone(); - func(vm, &[]) + let res = func(vm, &[]); + if let Some(old) = old { + vm.current_regs()[0].replace(old); + } else { + vm.current_regs()[0].take(); + } + res } } @@ -365,3 +370,26 @@ pub fn mrb_define_module_method(_vm: &mut VM, module: Rc, name: &str, m let mut procs = module.procs.borrow_mut(); procs.insert(name.to_string(), method); } + +#[test] +fn test_mrb_inspect() -> Result<(), Box> { + let mut vm = VM::empty(); + let old_top_self = RObject::integer(1).to_refcount_assigned(); + vm.current_regs()[0].replace(old_top_self.clone()); + + let class_a = vm.define_class("A", None, None); + let class_a = RObject::class(class_a, &mut vm); + let obj_a = mrb_funcall(&mut vm, Some(class_a), "new", &[])?; + let res = mrb_call_inspect(&mut vm, obj_a.clone()).unwrap(); + let res_str: String = res.as_ref().try_into().unwrap(); + assert_eq!( + res_str, + "#" + ); + + // assert not to brake registers + let updated = vm.get_current_regs_cloned(0)?; + assert_eq!(updated.object_id.get(), old_top_self.object_id.get()); + + Ok(()) +} From b17e1cbd8d6eacbf528b1b16fbf5f4f74754f7fa Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 29 Dec 2025 21:33:13 +0900 Subject: [PATCH 121/314] Bump --- Cargo.lock | 12 ++++++------ mrubyedge/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fd30a63..f5e4da8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -572,10 +572,10 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fff76ad9eb0e1718e47933679cddae83b3b9a4bc32245f7545c9bb7a8b57a0b" dependencies = [ - "criterion", "getrandom", - "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -583,11 +583,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fff76ad9eb0e1718e47933679cddae83b3b9a4bc32245f7545c9bb7a8b57a0b" +version = "1.0.14" dependencies = [ + "criterion", "getrandom", + "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -600,7 +600,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.13 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.0.13", "nom", "rand", "syn", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 071f730..9126e80 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.0.13" +version = "1.0.14" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From f382406e1dbc2e829ab08f58add0f6daa22c85db Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 29 Dec 2025 21:34:07 +0900 Subject: [PATCH 122/314] Bump version to 1.0.14 (cli) --- Cargo.lock | 14 +++++++------- mrubyedge-cli/Cargo.toml | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f5e4da8..e1048cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -571,11 +571,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fff76ad9eb0e1718e47933679cddae83b3b9a4bc32245f7545c9bb7a8b57a0b" +version = "1.0.14" dependencies = [ + "criterion", "getrandom", + "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -584,10 +584,10 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c07e5f42e50a8f00d79758fe3868660ffad097eca7fbc9ad878abf91549e21c" dependencies = [ - "criterion", "getrandom", - "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -595,12 +595,12 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.0.13" +version = "1.0.14" dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.13", + "mrubyedge 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 00ba36f..3303fe1 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.0.13" +version = "1.0.14" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } mruby-compiler2-sys = "0.2.2" -mrubyedge = { version = "1.0.13", features = ["default", "mruby-regexp"] } +mrubyedge = { version = "1.0.14", features = ["default", "mruby-regexp"] } rand = "0.8.5" nom = "7.1.3" askama = "0.12.1" From 3503602122331956a1c03855d075050c3fa15e3b Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 30 Dec 2025 17:31:36 +0900 Subject: [PATCH 123/314] Add get_const_by_name --- mrubyedge/src/yamrb/value.rs | 5 +++++ mrubyedge/src/yamrb/vm.rs | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 0c70086..3d87b7c 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -743,6 +743,11 @@ impl RModule { consts.get(name).cloned() } + // Alias + pub fn get_const_by_name(&self, name: &str) -> Option> { + self.getmcnst(name) + } + pub fn find_method(&self, name: &str) -> Option { // First check this module's methods let procs = self.procs.borrow(); diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 1b4f9af..ea7ae59 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -394,6 +394,10 @@ impl VM { .unwrap_or_else(|| panic!("Class {} not found", name)) } + pub fn get_const_by_name(&self, name: &str) -> Option> { + self.consts.get(name).cloned() + } + /// Defines a new class under the optional parent module, inheriting from /// `superclass` or `Object` by default, and registers it in the constant /// table. The resulting class object is returned for further mutation. From 7e82fde065c028bbb6053b28464de81cd160b965 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 30 Dec 2025 20:07:55 +0900 Subject: [PATCH 124/314] Support Module#ancestors, Class#ancestors --- mrubyedge/examples/is_a.rb | 16 +++++++++ mrubyedge/src/yamrb/prelude/class.rs | 18 ++++++++++ mrubyedge/src/yamrb/prelude/module.rs | 30 +++++++++++++++- mrubyedge/src/yamrb/value.rs | 51 +++++++++++++++++++++++---- mrubyedge/src/yamrb/vm.rs | 3 ++ 5 files changed, 110 insertions(+), 8 deletions(-) create mode 100644 mrubyedge/examples/is_a.rb diff --git a/mrubyedge/examples/is_a.rb b/mrubyedge/examples/is_a.rb new file mode 100644 index 0000000..26a9026 --- /dev/null +++ b/mrubyedge/examples/is_a.rb @@ -0,0 +1,16 @@ +class X +end + +module Z +end + +module W + include Z +end + +class Y < X + include W +end + +p W.ancestors +p Y.ancestors \ No newline at end of file diff --git a/mrubyedge/src/yamrb/prelude/class.rs b/mrubyedge/src/yamrb/prelude/class.rs index 71055e6..51b295b 100644 --- a/mrubyedge/src/yamrb/prelude/class.rs +++ b/mrubyedge/src/yamrb/prelude/class.rs @@ -48,6 +48,7 @@ pub(crate) fn initialize_class(vm: &mut VM) { "attr", Box::new(mrb_class_attr_acceccor), ); + mrb_define_cmethod(vm, class_class, "ancestors", Box::new(mrb_class_ancestors)); } fn mrb_class_new(vm: &mut VM, args: &[Rc]) -> Result, Error> { @@ -144,6 +145,23 @@ fn mrb_class_attr_acceccor(vm: &mut VM, args: &[Rc]) -> Result]) -> Result, Error> { + let self_module = vm.getself()?; + let target_class = match &self_module.value { + RValue::Class(class) => class.clone(), + _ => { + return Err(Error::RuntimeError( + "Module#ancestors must be called on class or module".to_string(), + )); + } + }; + let ancestors: Vec> = build_lookup_chain(&target_class) + .iter() + .map(|m| RObject::class_or_module(m.clone(), vm)) + .collect(); + Ok(RObject::array(ancestors).to_refcount_assigned()) +} + fn mrb_module_inspect(vm: &mut VM, _args: &[Rc]) -> Result, Error> { let class = vm.getself()?; let class_name = match &class.value { diff --git a/mrubyedge/src/yamrb/prelude/module.rs b/mrubyedge/src/yamrb/prelude/module.rs index 87a17ef..a30fe0b 100644 --- a/mrubyedge/src/yamrb/prelude/module.rs +++ b/mrubyedge/src/yamrb/prelude/module.rs @@ -7,7 +7,18 @@ use crate::{ pub(crate) fn initialize_module(vm: &mut VM) { let module_class = vm.define_standard_class("Module"); - mrb_define_cmethod(vm, module_class, "include", Box::new(mrb_module_include)); + mrb_define_cmethod( + vm, + module_class.clone(), + "include", + Box::new(mrb_module_include), + ); + mrb_define_cmethod( + vm, + module_class, + "ancestors", + Box::new(mrb_module_ancestors), + ); } fn mrb_module_include(vm: &mut VM, args: &[Rc]) -> Result, Error> { @@ -59,3 +70,20 @@ fn include_module(target: &Rc, mixin: Rc) -> Result<(), Error> target.mixed_in_modules.borrow_mut().insert(0, mixin); Ok(()) } + +fn mrb_module_ancestors(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let self_module = vm.getself()?; + let target_module = match &self_module.value { + RValue::Module(module) => module.clone(), + _ => { + return Err(Error::RuntimeError( + "Module#ancestors must be called on class or module".to_string(), + )); + } + }; + let ancestors: Vec> = build_module_lookup_chain(&target_module) + .iter() + .map(|m| RObject::module(m.clone()).to_refcount_assigned()) + .collect(); + Ok(RObject::array(ancestors).to_refcount_assigned()) +} diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 3d87b7c..c2a00b2 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -1,6 +1,7 @@ use std::any::Any; use std::cell::Cell; use std::collections::HashSet; +use std::rc::Weak; use std::{cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc}; use crate::Error; @@ -257,6 +258,19 @@ impl RObject { } } + pub fn class_or_module(c: Rc, vm: &mut VM) -> Rc { + match c.as_ref().underlying.borrow().as_ref() { + Some(weak_class) => { + if let Some(class) = weak_class.upgrade() { + RObject::class(class, vm) + } else { + panic!("[BUG] Class weak reference is dead"); + } + } + None => Rc::new(RObject::module(c.clone())), + } + } + pub fn instance(c: Rc) -> Self { RObject { tt: RType::Instance, @@ -411,11 +425,13 @@ impl RObject { } }; + let parent_module = self.get_class(vm).parent.borrow().clone(); let sclass = Rc::new(RClass::new_singleton( &class_name, Some(self.get_class(vm).clone()), - self.get_class(vm).parent.borrow().clone(), + parent_module.clone(), )); + sclass.update_module_weakref(); self.singleton_class.replace(Some(sclass.clone())); sclass @@ -442,11 +458,13 @@ impl RObject { None => vm.get_class_by_name("Class"), }; + let parent_module = self.get_class(vm).parent.borrow().clone(); let sclass = Rc::new(RClass::new_singleton( &class_name, Some(super_class), - self.get_class(vm).parent.borrow().clone(), + parent_module.clone(), )); + sclass.update_module_weakref(); self.singleton_class.replace(Some(sclass.clone())); class @@ -724,6 +742,8 @@ pub struct RModule { pub consts: RefCell>>, pub mixed_in_modules: RefCell>>, pub parent: RefCell>>, + + pub underlying: RefCell>>, } impl RModule { @@ -735,6 +755,7 @@ impl RModule { consts: RefCell::new(HashMap::new()), mixed_in_modules: RefCell::new(Vec::new()), parent: RefCell::new(None), + underlying: RefCell::new(None), } } @@ -823,12 +844,13 @@ impl RClass { if let Some(parent) = parent_module { module.parent.replace(Some(parent)); } - RClass { + let class = RClass { module, super_class, singleton_class_ref, is_singleton: false, - } + }; + class } pub fn new_singleton( @@ -841,12 +863,13 @@ impl RClass { if let Some(parent) = parent_module { module.parent.replace(Some(parent)); } - RClass { + let class = RClass { module, super_class, singleton_class_ref, is_singleton: true, - } + }; + class } pub fn getmcnst(&self, name: &str) -> Option> { @@ -867,6 +890,13 @@ impl RClass { pub fn full_name(&self) -> String { self.module.full_name() } + + pub(crate) fn update_module_weakref(self: &Rc) { + self.module + .underlying + .borrow_mut() + .replace(Rc::downgrade(self)); + } } fn collect_class_chain( @@ -880,13 +910,20 @@ fn collect_class_chain( } } -fn build_lookup_chain(class: &Rc) -> Vec> { +pub(crate) fn build_lookup_chain(class: &Rc) -> Vec> { let mut chain = Vec::new(); let mut visited = HashSet::new(); collect_class_chain(class, &mut chain, &mut visited); chain } +pub(crate) fn build_module_lookup_chain(module: &Rc) -> Vec> { + let mut chain = Vec::new(); + let mut visited = HashSet::new(); + collect_module_chain(module, &mut chain, &mut visited); + chain +} + pub(crate) fn resolve_method(self_class: &Rc, name: &str) -> Option<(Rc, RProc)> { for module in build_lookup_chain(self_class) { if let Some(proc) = module.procs.borrow().get(name) { diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index ea7ae59..8c46dcc 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -133,6 +133,7 @@ impl VM { let class_object_table = HashMap::new(); let object_class = Rc::new(RClass::new("Object", None, None)); + object_class.update_module_weakref(); let id = 1; // TODO generator let bytecode = Vec::new(); @@ -412,6 +413,8 @@ impl VM { None => self.object_class.clone(), }; let class = Rc::new(RClass::new(name, Some(superclass), parent_module.clone())); + class.update_module_weakref(); + let object = RObject::class(class.clone(), self); self.consts.insert(name.to_string(), object.clone()); if let Some(parent) = parent_module { From 5da693a336e344e46facaa20f3b0e65af3f7e35d Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 30 Dec 2025 20:21:25 +0900 Subject: [PATCH 125/314] Support Object#is_a? method in mrubyedge --- mrubyedge/examples/is_a.rb | 12 +++++++++++- mrubyedge/src/yamrb/prelude/object.rs | 27 +++++++++++++++++++++++++++ mrubyedge/src/yamrb/value.rs | 26 ++++++++++++++++++++------ 3 files changed, 58 insertions(+), 7 deletions(-) diff --git a/mrubyedge/examples/is_a.rb b/mrubyedge/examples/is_a.rb index 26a9026..b1aab69 100644 --- a/mrubyedge/examples/is_a.rb +++ b/mrubyedge/examples/is_a.rb @@ -12,5 +12,15 @@ class Y < X include W end +class V +end + p W.ancestors -p Y.ancestors \ No newline at end of file +p Y.ancestors + +o = Y.new +p o.is_a?(X) # => true +p o.is_a?(Y) # => true +p o.is_a?(Z) # => true +p o.is_a?(W) # => true +p o.is_a?(V) # => false \ No newline at end of file diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index 419bff0..c78100f 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -84,6 +84,7 @@ pub(crate) fn initialize_object(vm: &mut VM) { "proc", Box::new(mrb_object_lambda), ); + mrb_define_cmethod(vm, object_class.clone(), "is_a?", Box::new(mrb_object_is_a)); // define global consts: vm.consts.insert( @@ -229,6 +230,32 @@ pub fn mrb_object_lambda(_vm: &mut VM, args: &[Rc]) -> Result]) -> Result, Error> { + let obj = vm.getself()?; + let class_arg = &args[0]; + let is_a = match &class_arg.value { + RValue::Class(c) => mrb_is_a(vm, obj, c.clone()), + RValue::Module(m) => mrb_is_a(vm, obj, m.clone()), + _ => { + return Err(Error::ArgumentError( + "Object#is_a? expects a Class or Module".to_string(), + )); + } + }; + Ok(Rc::new(RObject::boolean(is_a))) +} + +pub fn mrb_is_a(vm: &mut VM, obj: Rc, class: impl AsModule) -> bool { + let obj_class = obj.get_class(vm); + let target_module = class.as_module(); + for module in build_lookup_chain(&obj_class).iter() { + if Rc::ptr_eq(module, &target_module) { + return true; + } + } + false +} + fn mrb_is_wasm(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { let is_wasm = cfg!(target_arch = "wasm32"); Ok(Rc::new(RObject::boolean(is_wasm))) diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index c2a00b2..71f8b46 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -800,6 +800,22 @@ impl RModule { } } +pub trait AsModule { + fn as_module(&self) -> Rc; +} + +impl AsModule for Rc { + fn as_module(&self) -> Rc { + self.clone() + } +} + +impl AsModule for Rc { + fn as_module(&self) -> Rc { + self.module.clone() + } +} + fn collect_module_chain( module: &Rc, chain: &mut Vec>, @@ -844,13 +860,12 @@ impl RClass { if let Some(parent) = parent_module { module.parent.replace(Some(parent)); } - let class = RClass { + RClass { module, super_class, singleton_class_ref, is_singleton: false, - }; - class + } } pub fn new_singleton( @@ -863,13 +878,12 @@ impl RClass { if let Some(parent) = parent_module { module.parent.replace(Some(parent)); } - let class = RClass { + RClass { module, super_class, singleton_class_ref, is_singleton: true, - }; - class + } } pub fn getmcnst(&self, name: &str) -> Option> { From ece39f1213b4b7ab38f90c5c5db837cc9656baa9 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 30 Dec 2025 20:22:18 +0900 Subject: [PATCH 126/314] Alias --- mrubyedge/src/yamrb/prelude/object.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index c78100f..35c42de 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -85,6 +85,12 @@ pub(crate) fn initialize_object(vm: &mut VM) { Box::new(mrb_object_lambda), ); mrb_define_cmethod(vm, object_class.clone(), "is_a?", Box::new(mrb_object_is_a)); + mrb_define_cmethod( + vm, + object_class.clone(), + "kind_of?", + Box::new(mrb_object_is_a), + ); // define global consts: vm.consts.insert( From 6ad8353e13a477e8628ab46cf02c7374d36ec471 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 30 Dec 2025 20:27:05 +0900 Subject: [PATCH 127/314] Add test cases --- mrubyedge/tests/is_a.rs | 168 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 mrubyedge/tests/is_a.rs diff --git a/mrubyedge/tests/is_a.rs b/mrubyedge/tests/is_a.rs new file mode 100644 index 0000000..cf53f01 --- /dev/null +++ b/mrubyedge/tests/is_a.rs @@ -0,0 +1,168 @@ +#![allow(clippy::bool_assert_comparison)] +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn is_a_with_inheritance_and_modules_test() { + let script = r#" + class X + end + + module Z + end + + module W + include Z + end + + class Y < X + include W + end + + class V + end + + def test_main + o = Y.new + [ + o.is_a?(X), + o.is_a?(Y), + o.is_a?(Z), + o.is_a?(W), + o.is_a?(V) + ] + end + + test_main + "#; + + let binary = mrbc_compile("is_a_test", script); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + + let values: Vec> = + result.as_ref().try_into().unwrap(); + + // o.is_a?(X) => true + let val1: bool = values[0].as_ref().try_into().unwrap(); + assert_eq!(val1, true); + + // o.is_a?(Y) => true + let val2: bool = values[1].as_ref().try_into().unwrap(); + assert_eq!(val2, true); + + // o.is_a?(Z) => true + let val3: bool = values[2].as_ref().try_into().unwrap(); + assert_eq!(val3, true); + + // o.is_a?(W) => true + let val4: bool = values[3].as_ref().try_into().unwrap(); + assert_eq!(val4, true); + + // o.is_a?(V) => false + let val5: bool = values[4].as_ref().try_into().unwrap(); + assert_eq!(val5, false); +} + +#[test] +fn is_a_with_ancestors_test() { + let script = r#" + module Z + end + + module W + include Z + end + + class Y + include W + end + + def test_ancestors + Y.ancestors + end + + test_ancestors + "#; + + let binary = mrbc_compile("is_a_ancestors", script); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + + // Just verify it runs successfully and returns an array + let values: Vec> = + result.as_ref().try_into().unwrap(); + + // Should have at least Y, W, Z, Object, etc. + assert!(values.len() >= 3); +} + +#[test] +fn is_a_basic_types_test() { + let script = r#" + def test_basic + [ + 1.is_a?(Integer), + "hello".is_a?(String), + [1,2,3].is_a?(Array), + true.is_a?(TrueClass), + false.is_a?(FalseClass), + nil.is_a?(NilClass) + ] + end + + test_basic + "#; + + let binary = mrbc_compile("is_a_basic", script); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + + let values: Vec> = + result.as_ref().try_into().unwrap(); + + // All should be true + for (i, val) in values.iter().enumerate() { + let bool_val: bool = val.as_ref().try_into().unwrap(); + assert_eq!(bool_val, true, "Test case {} failed", i); + } +} + +#[test] +fn is_a_object_superclass_test() { + let script = r#" + class MyClass + end + + def test_object + o = MyClass.new + [ + o.is_a?(MyClass), + o.is_a?(Object) + ] + end + + test_object + "#; + + let binary = mrbc_compile("is_a_object", script); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + + let values: Vec> = + result.as_ref().try_into().unwrap(); + + // Both should be true + let val1: bool = values[0].as_ref().try_into().unwrap(); + assert_eq!(val1, true); + + let val2: bool = values[1].as_ref().try_into().unwrap(); + assert_eq!(val2, true); +} From e4d3c3e72b43b22686193f81c7efa09826db802d Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 31 Dec 2025 00:40:33 +0900 Subject: [PATCH 128/314] Add Object#class --- mrubyedge/src/yamrb/prelude/object.rs | 12 +++++++++++ mrubyedge/src/yamrb/prelude/shared_memory.rs | 22 ++++++++++++++++++++ mrubyedge/src/yamrb/value.rs | 15 +++++++++++++ 3 files changed, 49 insertions(+) diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index 35c42de..e69d188 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -91,6 +91,12 @@ pub(crate) fn initialize_object(vm: &mut VM) { "kind_of?", Box::new(mrb_object_is_a), ); + mrb_define_cmethod( + vm, + object_class.clone(), + "class", + Box::new(mrb_object_class), + ); // define global consts: vm.consts.insert( @@ -251,6 +257,12 @@ fn mrb_object_is_a(vm: &mut VM, args: &[Rc]) -> Result, Err Ok(Rc::new(RObject::boolean(is_a))) } +fn mrb_object_class(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let obj = vm.getself()?; + let class = obj.get_class(vm); + Ok(RObject::class_or_module(class.as_module(), vm)) +} + pub fn mrb_is_a(vm: &mut VM, obj: Rc, class: impl AsModule) -> bool { let obj_class = obj.get_class(vm); let target_module = class.as_module(); diff --git a/mrubyedge/src/yamrb/prelude/shared_memory.rs b/mrubyedge/src/yamrb/prelude/shared_memory.rs index b70388d..9c6358d 100644 --- a/mrubyedge/src/yamrb/prelude/shared_memory.rs +++ b/mrubyedge/src/yamrb/prelude/shared_memory.rs @@ -53,6 +53,12 @@ pub(crate) fn initialize_shared_memory(vm: &mut VM) { "[]=", Box::new(mrb_shared_memory_set_index_range), ); + mrb_define_cmethod( + vm, + shared_memory_class.clone(), + "replace", + Box::new(mrb_shared_memory_replace), + ); mrb_define_cmethod( vm, shared_memory_class.clone(), @@ -174,6 +180,22 @@ fn mrb_shared_memory_index_range(vm: &mut VM, args: &[Rc]) -> Result]) -> Result, Error> { + let this = vm.getself()?; + let sm = match &this.value { + RValue::SharedMemory(s) => s, + _ => { + return Err(Error::RuntimeError( + "SharedMemory#write_all must be called on a SharedMemory".to_string(), + )); + } + }; + let data: Vec = args[0].as_ref().try_into()?; + let mut sm = sm.borrow_mut(); + sm.write(0, &data); + Ok(this.clone()) +} + // SharedMemory#read_by_size(size: Integer, offset: Integer) -> Integer fn mrb_shared_memory_read_by_size(vm: &mut VM, args: &[Rc]) -> Result, Error> { let this = vm.getself()?; diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 71f8b46..0510370 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -709,6 +709,21 @@ impl TryFrom<&RObject> for Vec> { } } +impl TryFrom<&RObject> for Vec<(Rc, Rc)> { + type Error = Error; + + fn try_from(value: &RObject) -> Result { + match &value.value { + RValue::Hash(h) => Ok(h + .borrow() + .values() + .map(|(k, v)| (k.clone(), v.clone())) + .collect()), + _ => Err(Error::TypeMismatch), + } + } +} + impl TryFrom<&RObject> for () { type Error = Error; From d60f3a76bc40e5bfa2f7ebf0812cc94d3ade2a76 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 31 Dec 2025 00:46:50 +0900 Subject: [PATCH 129/314] bump version to 1.0.15 --- Cargo.lock | 51 ++++++++++++++++++++++++++++++++++++-------- mrubyedge/Cargo.toml | 6 +++--- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e1048cc..2c5619a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -389,6 +389,18 @@ dependencies = [ "wasi", ] +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasip2", +] + [[package]] name = "glob" version = "0.3.3" @@ -572,10 +584,10 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c07e5f42e50a8f00d79758fe3868660ffad097eca7fbc9ad878abf91549e21c" dependencies = [ - "criterion", - "getrandom", - "mec-mrbc-sys", + "getrandom 0.2.16", "plain", "regex", "simple_endian", @@ -583,11 +595,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c07e5f42e50a8f00d79758fe3868660ffad097eca7fbc9ad878abf91549e21c" +version = "1.0.15" dependencies = [ - "getrandom", + "criterion", + "getrandom 0.3.4", + "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -600,7 +612,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.0.14", "nom", "rand", "syn", @@ -720,6 +732,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "rand" version = "0.8.5" @@ -747,7 +765,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.16", ] [[package]] @@ -942,6 +960,15 @@ version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" +[[package]] +name = "wasip2" +version = "1.0.1+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +dependencies = [ + "wit-bindgen", +] + [[package]] name = "wasm-bindgen" version = "0.2.106" @@ -1021,6 +1048,12 @@ dependencies = [ "windows-link", ] +[[package]] +name = "wit-bindgen" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" + [[package]] name = "zerocopy" version = "0.8.31" diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 9126e80..f6695e0 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.0.14" +version = "1.0.15" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" @@ -9,12 +9,12 @@ license = "BSD-3-Clause" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -getrandom = { version = "0.2.14", optional = true } +getrandom = { version = "0.3.4", optional = true } plain = "0.2.3" regex = { version = "1.12.2", default-features = false, features = [ "std", ], optional = true } -simple_endian = "0.3" +simple_endian = "0.3.3" [dev-dependencies] criterion = "0.5.1" From 5c810b4925c85c0351ecc90acd2e8c66f143dbb7 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 31 Dec 2025 00:48:29 +0900 Subject: [PATCH 130/314] Bump --- Cargo.lock | 16 ++++++++-------- mrubyedge-cli/Cargo.toml | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2c5619a..73168c9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -583,11 +583,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c07e5f42e50a8f00d79758fe3868660ffad097eca7fbc9ad878abf91549e21c" +version = "1.0.15" dependencies = [ - "getrandom 0.2.16", + "criterion", + "getrandom 0.3.4", + "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -596,10 +596,10 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce329c1a18fa10ba5d6508c247c27cfc89cb8da2d730302d8cdb995995a25a9" dependencies = [ - "criterion", "getrandom 0.3.4", - "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -607,12 +607,12 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.0.14" +version = "1.0.15" dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.14", + "mrubyedge 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 3303fe1..397ea68 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.0.14" +version = "1.0.15" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } mruby-compiler2-sys = "0.2.2" -mrubyedge = { version = "1.0.14", features = ["default", "mruby-regexp"] } +mrubyedge = { version = "1.0.15", features = ["default", "mruby-regexp"] } rand = "0.8.5" nom = "7.1.3" askama = "0.12.1" From 6a789d051f0d9ba9cee70749ebd9da66c812487a Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 6 Jan 2026 18:57:15 +0900 Subject: [PATCH 131/314] Remove mec directory --- Cargo.toml | 2 +- mec/Cargo.toml | 22 -- mec/examples/bench1.export.rbs | 1 - mec/examples/bench1.rb | 16 -- mec/examples/bool.export.rbs | 7 - mec/examples/bool.import.rbs | 7 - mec/examples/bool.rb | 7 - mec/examples/counter.export.rbs | 3 - mec/examples/counter.html | 25 -- mec/examples/counter.rb | 10 - mec/examples/fib.export.rbs | 1 - mec/examples/fib.import.rbs | 0 mec/examples/fib.rb | 9 - mec/examples/fib2.export.rbs | 1 - mec/examples/fib2.rb | 10 - mec/examples/hello.rb | 3 - mec/examples/object.export.rbs | 1 - mec/examples/object.rb | 25 -- mec/examples/object2.export.rbs | 1 - mec/examples/object2.rb | 18 -- mec/examples/plus.export.rbs | 1 - mec/examples/plus.rb | 3 - mec/examples/print.rb | 6 - mec/examples/random.export.rbs | 1 - mec/examples/random.rb | 3 - mec/examples/rbs_parser.rs | 18 -- mec/examples/rust_from_rbs.rs | 44 ---- mec/examples/shared_memory.export.rbs | 3 - mec/examples/shared_memory.html | 23 -- mec/examples/shared_memory.rb | 10 - mec/examples/str.export.rbs | 7 - mec/examples/str.import.rbs | 5 - mec/examples/str.rb | 12 - mec/examples/time.export.rbs | 1 - mec/examples/time.import.rbs | 1 - mec/examples/time.rb | 5 - mec/examples/version.rb | 4 - mec/examples/wasmbots/bot.export.rbs | 7 - mec/examples/wasmbots/bot.import.rbs | 5 - mec/examples/wasmbots/bot.rb | 173 -------------- mec/src/lib.rs | 2 - mec/src/main.rs | 271 --------------------- mec/src/rbs_parser/mod.rs | 328 -------------------------- mec/src/template/cargo_toml.rs | 17 -- mec/src/template/mod.rs | 5 - mec/src/template/source.rs | 29 --- mec/templates/Cargo.toml.debug.tmpl | 17 -- mec/templates/Cargo.toml.tmpl | 17 -- mec/templates/lib.rs.tmpl | 100 -------- 49 files changed, 1 insertion(+), 1286 deletions(-) delete mode 100644 mec/Cargo.toml delete mode 100644 mec/examples/bench1.export.rbs delete mode 100644 mec/examples/bench1.rb delete mode 100644 mec/examples/bool.export.rbs delete mode 100644 mec/examples/bool.import.rbs delete mode 100644 mec/examples/bool.rb delete mode 100644 mec/examples/counter.export.rbs delete mode 100644 mec/examples/counter.html delete mode 100644 mec/examples/counter.rb delete mode 100644 mec/examples/fib.export.rbs delete mode 100644 mec/examples/fib.import.rbs delete mode 100644 mec/examples/fib.rb delete mode 100644 mec/examples/fib2.export.rbs delete mode 100644 mec/examples/fib2.rb delete mode 100644 mec/examples/hello.rb delete mode 100644 mec/examples/object.export.rbs delete mode 100644 mec/examples/object.rb delete mode 100644 mec/examples/object2.export.rbs delete mode 100644 mec/examples/object2.rb delete mode 100644 mec/examples/plus.export.rbs delete mode 100644 mec/examples/plus.rb delete mode 100644 mec/examples/print.rb delete mode 100644 mec/examples/random.export.rbs delete mode 100644 mec/examples/random.rb delete mode 100644 mec/examples/rbs_parser.rs delete mode 100644 mec/examples/rust_from_rbs.rs delete mode 100644 mec/examples/shared_memory.export.rbs delete mode 100644 mec/examples/shared_memory.html delete mode 100644 mec/examples/shared_memory.rb delete mode 100644 mec/examples/str.export.rbs delete mode 100644 mec/examples/str.import.rbs delete mode 100644 mec/examples/str.rb delete mode 100644 mec/examples/time.export.rbs delete mode 100644 mec/examples/time.import.rbs delete mode 100644 mec/examples/time.rb delete mode 100644 mec/examples/version.rb delete mode 100644 mec/examples/wasmbots/bot.export.rbs delete mode 100644 mec/examples/wasmbots/bot.import.rbs delete mode 100644 mec/examples/wasmbots/bot.rb delete mode 100644 mec/src/lib.rs delete mode 100644 mec/src/main.rs delete mode 100644 mec/src/rbs_parser/mod.rs delete mode 100644 mec/src/template/cargo_toml.rs delete mode 100644 mec/src/template/mod.rs delete mode 100644 mec/src/template/source.rs delete mode 100644 mec/templates/Cargo.toml.debug.tmpl delete mode 100644 mec/templates/Cargo.toml.tmpl delete mode 100644 mec/templates/lib.rs.tmpl diff --git a/Cargo.toml b/Cargo.toml index 247a842..3b62b1e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,5 +2,5 @@ resolver = "2" members = [ - "mrubyedge", "mec", "mrubyedge-cli", + "mrubyedge", "mrubyedge-cli", ] diff --git a/mec/Cargo.toml b/mec/Cargo.toml deleted file mode 100644 index 9134b56..0000000 --- a/mec/Cargo.toml +++ /dev/null @@ -1,22 +0,0 @@ -[package] -name = "mec" -version = "1.0.5" -edition = "2021" -authors = ["Uchio Kondo "] -description = "mruby/edge compiler cli endpoint" -license = "BSD-3-Clause" -include = [ - "**/*.rs", - "Cargo.toml", - "templates/*", -] - -[dependencies] -rand = "0.8.5" -nom = "7.1.3" -askama = "0.12.1" -bpaf = "0.9.11" -mec-mrbc-sys = "3.3.1" - -[dev-dependencies] -syn = { version = "2.0", features = ["full", "parsing"] } diff --git a/mec/examples/bench1.export.rbs b/mec/examples/bench1.export.rbs deleted file mode 100644 index 36ffe8f..0000000 --- a/mec/examples/bench1.export.rbs +++ /dev/null @@ -1 +0,0 @@ -def bench: (Integer) -> void diff --git a/mec/examples/bench1.rb b/mec/examples/bench1.rb deleted file mode 100644 index 798d7c2..0000000 --- a/mec/examples/bench1.rb +++ /dev/null @@ -1,16 +0,0 @@ -def fib(n) - if n < 1 - return 0 - elsif n < 3 - return 1 - else - return fib(n-1)+fib(n-2) - end -end - -def bench(num) - start = Time.now.to_i - fib(num) - fin = Time.now.to_i - p (fin - start) -end diff --git a/mec/examples/bool.export.rbs b/mec/examples/bool.export.rbs deleted file mode 100644 index 5dd0670..0000000 --- a/mec/examples/bool.export.rbs +++ /dev/null @@ -1,7 +0,0 @@ -def clientInitialize: () -> void - -def setup: (Integer) -> String - -def receiveGameParams: (Integer) -> bool - -def tick: (Integer) -> void \ No newline at end of file diff --git a/mec/examples/bool.import.rbs b/mec/examples/bool.import.rbs deleted file mode 100644 index 87a165f..0000000 --- a/mec/examples/bool.import.rbs +++ /dev/null @@ -1,7 +0,0 @@ -def shutdown: () -> void - -def logFunction: (Integer, String) -> void - -def getRandomInt: (Integer, Integer) -> Integer - -def test_bool: (bool) -> bool \ No newline at end of file diff --git a/mec/examples/bool.rb b/mec/examples/bool.rb deleted file mode 100644 index 19ac235..0000000 --- a/mec/examples/bool.rb +++ /dev/null @@ -1,7 +0,0 @@ -def is_truthy - true -end - -def is_false - false -end \ No newline at end of file diff --git a/mec/examples/counter.export.rbs b/mec/examples/counter.export.rbs deleted file mode 100644 index 2b5a380..0000000 --- a/mec/examples/counter.export.rbs +++ /dev/null @@ -1,3 +0,0 @@ -def countup: () -> void - -def current_count: () -> Integer \ No newline at end of file diff --git a/mec/examples/counter.html b/mec/examples/counter.html deleted file mode 100644 index 74e5185..0000000 --- a/mec/examples/counter.html +++ /dev/null @@ -1,25 +0,0 @@ - - - - WASM Example - - -

WASM Example

- - - \ No newline at end of file diff --git a/mec/examples/counter.rb b/mec/examples/counter.rb deleted file mode 100644 index ecc5b81..0000000 --- a/mec/examples/counter.rb +++ /dev/null @@ -1,10 +0,0 @@ -$count = 0 - -def countup - $count += 1 - nil -end - -def current_count - $count -end \ No newline at end of file diff --git a/mec/examples/fib.export.rbs b/mec/examples/fib.export.rbs deleted file mode 100644 index fdbdb4c..0000000 --- a/mec/examples/fib.export.rbs +++ /dev/null @@ -1 +0,0 @@ -def fib: (Integer) -> Integer diff --git a/mec/examples/fib.import.rbs b/mec/examples/fib.import.rbs deleted file mode 100644 index e69de29..0000000 diff --git a/mec/examples/fib.rb b/mec/examples/fib.rb deleted file mode 100644 index a82751e..0000000 --- a/mec/examples/fib.rb +++ /dev/null @@ -1,9 +0,0 @@ -def fib(n) - if n < 1 - return 0 - elsif n < 3 - return 1 - else - return fib(n-1)+fib(n-2) - end -end diff --git a/mec/examples/fib2.export.rbs b/mec/examples/fib2.export.rbs deleted file mode 100644 index fdbdb4c..0000000 --- a/mec/examples/fib2.export.rbs +++ /dev/null @@ -1 +0,0 @@ -def fib: (Integer) -> Integer diff --git a/mec/examples/fib2.rb b/mec/examples/fib2.rb deleted file mode 100644 index 63fcea3..0000000 --- a/mec/examples/fib2.rb +++ /dev/null @@ -1,10 +0,0 @@ -def fib(n) - case n - when 0 - 0 - when 1..2 - 2 - else - fib(n - 1) + fib(n - 2) - end -end diff --git a/mec/examples/hello.rb b/mec/examples/hello.rb deleted file mode 100644 index 6995664..0000000 --- a/mec/examples/hello.rb +++ /dev/null @@ -1,3 +0,0 @@ -def world - puts "My own wasm" -end diff --git a/mec/examples/object.export.rbs b/mec/examples/object.export.rbs deleted file mode 100644 index f691be2..0000000 --- a/mec/examples/object.export.rbs +++ /dev/null @@ -1 +0,0 @@ -def __main__: () -> Integer diff --git a/mec/examples/object.rb b/mec/examples/object.rb deleted file mode 100644 index 5ba983c..0000000 --- a/mec/examples/object.rb +++ /dev/null @@ -1,25 +0,0 @@ -class MyMRubyClass - def initialize(value) - @value = value - end - - def update(value) - @value = value * 2 - end - - def value - @value - end - - def print_self - puts "Value: #{value}" - end -end - -def __main__ - obj = MyMRubyClass.new(123) - obj.print_self - obj.update(456) - obj.print_self - return obj.value -end diff --git a/mec/examples/object2.export.rbs b/mec/examples/object2.export.rbs deleted file mode 100644 index f691be2..0000000 --- a/mec/examples/object2.export.rbs +++ /dev/null @@ -1 +0,0 @@ -def __main__: () -> Integer diff --git a/mec/examples/object2.rb b/mec/examples/object2.rb deleted file mode 100644 index ec268b1..0000000 --- a/mec/examples/object2.rb +++ /dev/null @@ -1,18 +0,0 @@ -class MyMRubyClass - attr_accessor :value - - def print_self - puts "Value: #{value}" - end -end - -def __main__ - obj = MyMRubyClass.new - obj.value = 456 - obj.print_self - obj.value = 5471 - obj.print_self - obj.value = 557188 - obj.print_self - return obj.value -end diff --git a/mec/examples/plus.export.rbs b/mec/examples/plus.export.rbs deleted file mode 100644 index b7df61c..0000000 --- a/mec/examples/plus.export.rbs +++ /dev/null @@ -1 +0,0 @@ -def plus: (Integer, Integer) -> Integer \ No newline at end of file diff --git a/mec/examples/plus.rb b/mec/examples/plus.rb deleted file mode 100644 index ae61688..0000000 --- a/mec/examples/plus.rb +++ /dev/null @@ -1,3 +0,0 @@ -def plus(a, b) - a + b -end diff --git a/mec/examples/print.rb b/mec/examples/print.rb deleted file mode 100644 index fc360fb..0000000 --- a/mec/examples/print.rb +++ /dev/null @@ -1,6 +0,0 @@ -def calc_and_print(a, b) - val = a * b - str = "answer is = #{val}" - print_in_browser(str) -end - diff --git a/mec/examples/random.export.rbs b/mec/examples/random.export.rbs deleted file mode 100644 index d432a78..0000000 --- a/mec/examples/random.export.rbs +++ /dev/null @@ -1 +0,0 @@ -def test_random: () -> Integer diff --git a/mec/examples/random.rb b/mec/examples/random.rb deleted file mode 100644 index 6686b88..0000000 --- a/mec/examples/random.rb +++ /dev/null @@ -1,3 +0,0 @@ -def test_random - Random.rand(10) -end diff --git a/mec/examples/rbs_parser.rs b/mec/examples/rbs_parser.rs deleted file mode 100644 index 5a97cf3..0000000 --- a/mec/examples/rbs_parser.rs +++ /dev/null @@ -1,18 +0,0 @@ -use mec::rbs_parser::*; - -fn main() { - let def = " -def hoge: (String) -> Integer -def foo_bar: (Integer, Integer) -> Integer - -def fooBar: (Integer, Float, Integer) -> void - -def poyo123: () -> void -"; - - let ret = parse(def).unwrap(); - let rest = ret.0; - let ftype = ret.1; - dbg!(ftype); - dbg!(rest); -} diff --git a/mec/examples/rust_from_rbs.rs b/mec/examples/rust_from_rbs.rs deleted file mode 100644 index 4b69872..0000000 --- a/mec/examples/rust_from_rbs.rs +++ /dev/null @@ -1,44 +0,0 @@ -use askama::Template; -use mec::rbs_parser::*; -use mec::template::LibRs; - -fn main() { - let def = " -def foo_bar: (Integer) -> Integer -"; - - let ret = parse(def).unwrap(); - let ftype = ret.1; - let ftypes = vec![mec::template::RustFnTemplate { - func_name: &ftype[0].name, - args_decl: "a: i32", - args_let_vec: "vec![std::rc::Rc::new(RObject::RInteger(a as i64))]", - rettype_decl: "-> i32", - str_args_converter: "// do nothing", - handle_retval: "5471", - exported_helper_var: "", - }]; - let imports = vec![]; - - let lib_rs = LibRs { - file_basename: "world", - ftypes: &ftypes, - ftypes_imports: &imports, - }; - - let rendered = lib_rs.render().unwrap(); - println!("{}", &rendered); - - // Check if rendered is valid Rust syntax using syn - println!("\n--- Checking if rendered code is valid Rust syntax ---"); - - match syn::parse_file(&rendered) { - Ok(_) => { - println!("✓ Rendered code has valid Rust syntax!"); - } - Err(e) => { - println!("✗ Rendered code has syntax errors:"); - println!("{}", e); - } - } -} diff --git a/mec/examples/shared_memory.export.rbs b/mec/examples/shared_memory.export.rbs deleted file mode 100644 index 88c5712..0000000 --- a/mec/examples/shared_memory.export.rbs +++ /dev/null @@ -1,3 +0,0 @@ -def get_memory: () -> SharedMemory - -def read_array_from_memory: () -> Integer diff --git a/mec/examples/shared_memory.html b/mec/examples/shared_memory.html deleted file mode 100644 index 48d098e..0000000 --- a/mec/examples/shared_memory.html +++ /dev/null @@ -1,23 +0,0 @@ - - - - WASM Example - - -

WASM Example

- - - \ No newline at end of file diff --git a/mec/examples/shared_memory.rb b/mec/examples/shared_memory.rb deleted file mode 100644 index a1281cb..0000000 --- a/mec/examples/shared_memory.rb +++ /dev/null @@ -1,10 +0,0 @@ -$memory = SharedMemory.new(8192) - -def get_memory - $memory -end - -def read_array_from_memory - result = $memory[0..4].unpack('c c c c') - result[0] + result[1] + result[2] + result[3] -end \ No newline at end of file diff --git a/mec/examples/str.export.rbs b/mec/examples/str.export.rbs deleted file mode 100644 index 7fbf7b8..0000000 --- a/mec/examples/str.export.rbs +++ /dev/null @@ -1,7 +0,0 @@ -def showstring: (String) -> void - -def showstring2: (String, String) -> void - -def getstring: () -> String - -def usejs: () -> void diff --git a/mec/examples/str.import.rbs b/mec/examples/str.import.rbs deleted file mode 100644 index df23ad6..0000000 --- a/mec/examples/str.import.rbs +++ /dev/null @@ -1,5 +0,0 @@ -def consolelog: (String) -> void - -def consolelog2: (String, String) -> void - -def putstring: () -> String diff --git a/mec/examples/str.rb b/mec/examples/str.rb deleted file mode 100644 index 9790aa0..0000000 --- a/mec/examples/str.rb +++ /dev/null @@ -1,12 +0,0 @@ -def showstring(bar) - consolelog bar -end - -def getstring - "mruby/edge String!" -end - -def usejs - str = putstring - consolelog str -end diff --git a/mec/examples/time.export.rbs b/mec/examples/time.export.rbs deleted file mode 100644 index d0253fc..0000000 --- a/mec/examples/time.export.rbs +++ /dev/null @@ -1 +0,0 @@ -def getnow: () -> void diff --git a/mec/examples/time.import.rbs b/mec/examples/time.import.rbs deleted file mode 100644 index 4d12839..0000000 --- a/mec/examples/time.import.rbs +++ /dev/null @@ -1 +0,0 @@ -def getnowfromreal: () -> Integer diff --git a/mec/examples/time.rb b/mec/examples/time.rb deleted file mode 100644 index 9130667..0000000 --- a/mec/examples/time.rb +++ /dev/null @@ -1,5 +0,0 @@ -def getnow - v = getnowfromreal - p v - v -end diff --git a/mec/examples/version.rb b/mec/examples/version.rb deleted file mode 100644 index 38efc79..0000000 --- a/mec/examples/version.rb +++ /dev/null @@ -1,4 +0,0 @@ -def main - puts "version: #{RUBY_VERSION}" - puts "engine: #{RUBY_ENGINE}" -end \ No newline at end of file diff --git a/mec/examples/wasmbots/bot.export.rbs b/mec/examples/wasmbots/bot.export.rbs deleted file mode 100644 index f172b24..0000000 --- a/mec/examples/wasmbots/bot.export.rbs +++ /dev/null @@ -1,7 +0,0 @@ -def clientInitialize: () -> void - -def setup: (Integer) -> SharedMemory - -def receiveGameParams: (Integer) -> bool - -def tick: (Integer) -> void \ No newline at end of file diff --git a/mec/examples/wasmbots/bot.import.rbs b/mec/examples/wasmbots/bot.import.rbs deleted file mode 100644 index ad93297..0000000 --- a/mec/examples/wasmbots/bot.import.rbs +++ /dev/null @@ -1,5 +0,0 @@ -def shutdown: () -> void - -def logFunction: (Integer, String) -> void - -def getRandomInt: (Integer, Integer) -> Integer \ No newline at end of file diff --git a/mec/examples/wasmbots/bot.rb b/mec/examples/wasmbots/bot.rb deleted file mode 100644 index 34b2af4..0000000 --- a/mec/examples/wasmbots/bot.rb +++ /dev/null @@ -1,173 +0,0 @@ -$memory = nil - -## wasmbots framework part - -LOGLEVEL_INFO = 2 -LOGLEVEL_WARN = 1 -LOGLEVEL_ERROR = 0 - -# pub enum WasmBotsError { -# EndOfFile, -# InvalidData, -# EndOfMessageList, -# } -ERROR_EOF = 0 -ERROR_INVALID = 1 -ERROR_END_OF_MESSAGE_LIST = 2 - -# pub enum MessageType { -# _Error, -# InitialParameters, -# PresentCircumstances, -# Wait, -# Resign, -# MoveTo, -# Open, -# Close, -# } -MESSAGE_TYPE_ERROR = 1 -MESSAGE_TYPE_INITIAL_PARAMETERS = 2 -MESSAGE_TYPE_PRESENT_CIRCUMSTANCES = 3 -MESSAGE_TYPE_WAIT = 4 -MESSAGE_TYPE_RESIGN = 5 -MESSAGE_TYPE_MOVE_TO = 6 -MESSAGE_TYPE_OPEN = 7 -MESSAGE_TYPE_CLOSE = 8 - -# pub enum MoveResult { -# Succeeded = 0, -# Failed = 1, -# Invalid = 2, -# Error = 3, -# } -MOVE_RESULT_SUCCEEDED = 0 -MOVE_RESULT_FAILED = 1 -MOVE_RESULT_INVALID = 2 -MOVE_RESULT_ERROR = 3 - -# pub enum TileType { -# Void = 0, -# Floor = 1, -# OpenDoor = 2, -# ClosedDoor = 3, -# Wall = 4, -# } -TILE_TYPE_VOID = 0 -TILE_TYPE_FLOOR = 1 -TILE_TYPE_OPEN_DOOR = 2 -TILE_TYPE_CLOSED_DOOR = 3 -TILE_TYPE_WALL = 4 - -# pub enum Direction { -# North = 0, -# Northeast = 1, -# East = 2, -# Southeast = 3, -# South = 4, -# Southwest = 5, -# West = 6, -# Northwest = 7, -# } -DIRECTION_NORTH = 0 -DIRECTION_NORTHEAST = 1 -DIRECTION_EAST = 2 -DIRECTION_SOUTHEAST = 3 -DIRECTION_SOUTH = 4 -DIRECTION_SOUTHWEST = 5 -DIRECTION_WEST = 6 -DIRECTION_NORTHWEST = 7 - -class PresentCircumstances - def initialize(last_tick_duration, last_move_result, hit_points, surroundings) - @last_tick_duration = last_tick_duration - @last_move_result = last_move_result - @hit_points = hit_points - @surroundings = surroundings - end - attr_reader :last_tick_duration, :last_move_result, :hit_points, :surroundings -end - - -def clientInitialize - logFunction(LOGLEVEL_INFO, "Hello, world! This is made by #{RUBY_ENGINE}") -end - -def setup(requested_size) - logFunction(LOGLEVEL_INFO, "received setup with size: #{requested_size}") - - $memory = SharedMemory.new(requested_size) - name = "mruby/edge wasmbot" - $memory[0..17] = name - # $memory[18..25] = "\0" * 8 - $memory[26..31] = [0, 2, 0].pack("S S S") - $memory -end - -def receiveGameParams(offset) - param = $memory[offset..(offset+10)].unpack("S S S S C C C") - logFunction(LOGLEVEL_INFO, "param version: #{param[0]}") - logFunction(LOGLEVEL_INFO, "param engine version: #{param[1]}.#{param[2]}.#{param[3]}") - logFunction(LOGLEVEL_INFO, "param diagonal_movement: #{param[4]}") - logFunction(LOGLEVEL_INFO, "param player_stride: #{param[5]}") - logFunction(LOGLEVEL_INFO, "param player_open_reach: #{param[6]}") - true -end - -def tick(offset) - param_pre = $memory[offset..(offset+8)].unpack("I C S S") - - surroundings_len = param_pre[3] - surroundings = [] - - surroundings_len.times do |i| - ptr = offset + 9 + i - tile = $memory[ptr..ptr].unpack("C") - surroundings.push tile[0] - end - curcumstances = PresentCircumstances.new(param_pre[0], param_pre[1], param_pre[2], surroundings) - $brain.on_tick(curcumstances) -rescue Exception => e - logFunction(LOGLEVEL_ERROR, "error: #{e.message}") - logFunction("stopped") - $memory[0..0] = [MESSAGE_TYPE_RESIGN].pack("C") -end - -## main class - -class Brain - def initialize - @turn = 0 - end - attr_reader :turn - - def write_move!(direction) - logFunction(LOGLEVEL_INFO, "direction: #{direction}") - $memory[0..2] = [MESSAGE_TYPE_MOVE_TO, direction, 1].pack("C C C") - end - - def on_tick(curcumstances) - if turn > 100 - logFunction(LOGLEVEL_ERROR, "turn > 100, stopped") - $memory[0..0] = [MESSAGE_TYPE_RESIGN].pack("C") - return - end - #logFunction("hit point: #{curcumstances.hit_points}") - - mod = @turn % 4 - direction = case mod - when 0 - DIRECTION_NORTH - when 1 - DIRECTION_EAST - when 2 - DIRECTION_SOUTH - else - DIRECTION_WEST - end - - write_move!(direction) - @turn += 1 - end -end - -$brain = Brain.new diff --git a/mec/src/lib.rs b/mec/src/lib.rs deleted file mode 100644 index 31dca36..0000000 --- a/mec/src/lib.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod rbs_parser; -pub mod template; diff --git a/mec/src/main.rs b/mec/src/main.rs deleted file mode 100644 index 4e2bcba..0000000 --- a/mec/src/main.rs +++ /dev/null @@ -1,271 +0,0 @@ -extern crate bpaf; -extern crate rand; -extern crate mec_mrbc_sys; - -const MRUBY_EDGE_DEFAULT_VERSION: &'static str = "1"; -const VERSION: &'static str = env!("CARGO_PKG_VERSION"); - -use std::{ffi::CStr, fs::File, io::Read, path::{Path, PathBuf}, process::Command, str}; - -use askama::Template; -use bpaf::{any, construct, long, Parser}; -use rand::distributions::{Alphanumeric, DistString}; - -use mec::template::{cargo_toml::CargoTomlDebug, CargoToml, LibRs}; - -#[derive(Debug, Clone)] -struct ParsedOpt { - fnname: Option, - mruby_edge_version: Option, - no_wasi: bool, - skip_cleanup: bool, - debug_mruby_edge: bool, - verbose: bool, - strip_binary: bool, - path: PathBuf, -} - -fn sh_do(sharg: &str, debug: bool) -> Result<(), Box> { - println!("running: `{}`", sharg); - let out = Command::new("/bin/sh").args(["-c", sharg]).output()?; - if debug && out.stdout.len() != 0 { - println!( - "stdout:\n{}", - String::from_utf8_lossy(&out.stdout).to_string().trim() - ); - } - if debug && out.stderr.len() != 0 { - println!( - "stderr:\n{}", - String::from_utf8_lossy(&out.stderr).to_string().trim() - ); - } - if !out.status.success() { - println!("{:?}", out.status); - panic!("failed to execute command"); - } - - Ok(()) -} - -fn file_prefix_of(file: &Path) -> Option { - file.file_name()? - .to_str()? - .split('.') - .next() - .map(|s| s.to_string()) -} - -fn debug_println(debug: bool, msg: &str) { - if debug { - eprintln!("{}", msg); - } -} - -fn not_help_or_version_flag(buf: PathBuf) -> Option { - let x = buf.to_str().unwrap(); - (x != "--help" && x != "-h" && x != "--version" && x != "-V").then_some(buf) -} - -fn main() -> Result<(), Box> { - let fnname = long("fnname").short('f').argument::("FNNAME").optional(); - let mruby_edge_version = long("mruby-edge-version").short('m').argument::("MRBE_VERSION").optional(); - let skip_cleanup = long("skip-cleanup").switch(); - let path = any::("MRUBY_FILE", not_help_or_version_flag); - let no_wasi = long("no-wasi").short('W').switch(); - let debug_mruby_edge = long("debug-mruby-edge").switch(); - let verbose = long("verbose").switch(); - let strip_binary = long("strip-binary").short('S').switch(); - let opts: ParsedOpt = construct!(ParsedOpt { - fnname, - mruby_edge_version, - no_wasi, - skip_cleanup, - debug_mruby_edge, - verbose, - strip_binary, - path, - }) - .to_options() - .descr("mec - An mruby/edge compilation cli") - .fallback_to_usage() - .version(VERSION) - .run(); - - let mut rng = rand::thread_rng(); - let suffix = Alphanumeric.sample_string(&mut rng, 32); - - let fnname = opts.fnname; - let path = opts.path; - - let mrubyfile = std::fs::canonicalize(path)?; - let fname = file_prefix_of(mrubyfile.as_path()).unwrap(); - - let pwd = std::env::current_dir()?; - std::env::set_current_dir(std::env::var("TMPDIR").unwrap_or("/tmp".to_string()))?; - - let dirname = format!("work-mrubyedge-{}", suffix); - std::fs::create_dir(&dirname)?; - std::env::set_current_dir(format!("./work-mrubyedge-{}", &suffix))?; - std::fs::create_dir("src")?; - - sh_do(&format!("cp {} src/", mrubyfile.to_str().unwrap()), opts.verbose)?; - - let out_file = format!("src/{}.mrb\0", fname); - let in_file = format!("src/{}.rb\0", fname); - if opts.verbose { - let args = [ - CStr::from_bytes_with_nul(b"mrbc\0").unwrap().as_ptr(), - CStr::from_bytes_with_nul(b"-v\0").unwrap().as_ptr(), - CStr::from_bytes_with_nul(b"-o\0").unwrap().as_ptr(), - CStr::from_bytes_with_nul(out_file.as_bytes()).unwrap().as_ptr(), - CStr::from_bytes_with_nul(in_file.as_bytes()).unwrap().as_ptr(), - ]; - - unsafe { - mec_mrbc_sys::mrbc_main(args.len() as i32, args.as_ptr() as *mut *mut i8); - } - } else { - let args = [ - CStr::from_bytes_with_nul(b"mrbc\0").unwrap().as_ptr(), - CStr::from_bytes_with_nul(b"-o\0").unwrap().as_ptr(), - CStr::from_bytes_with_nul(out_file.as_bytes()).unwrap().as_ptr(), - CStr::from_bytes_with_nul(in_file.as_bytes()).unwrap().as_ptr(), - ]; - - unsafe { - mec_mrbc_sys::mrbc_main(args.len() as i32, args.as_ptr() as *mut *mut i8); - } - } - - let feature = if opts.no_wasi { "no-wasi" } else { "default" }; - - if opts.debug_mruby_edge { - let cargo_toml = CargoTomlDebug { - mruby_edge_crate_path: "/Users/udzura/ghq/github.com/udzura/mrubyedge/mrubyedge", - mrubyedge_feature: feature, - }; - std::fs::write("Cargo.toml", cargo_toml.render()?)?; - } else { - let cargo_toml = CargoToml { - mrubyedge_version: &opts.mruby_edge_version.unwrap_or_else(|| MRUBY_EDGE_DEFAULT_VERSION.to_string()), - mrubyedge_feature: feature, - strip: &opts.strip_binary.to_string(), - }; - std::fs::write("Cargo.toml", cargo_toml.render()?)?; - } - - let export_rbs_fname = format!("{}.export.rbs", fname); - let export_rbs = mrubyfile.parent().unwrap().join(&export_rbs_fname); - let cont: String; - - let mut ftypes_imports = Vec::new(); - let import_rbs_fname = format!("{}.import.rbs", fname); - let import_rbs = mrubyfile.parent().unwrap().join(&import_rbs_fname); - if import_rbs.exists() { - debug_println( - opts.verbose, - &format!("detected import.rbs: {}", import_rbs.as_path().to_string_lossy()), - ); - let mut f = File::open(import_rbs)?; - let mut s = String::new(); - f.read_to_string(&mut s)?; - - let (_, parsed) = mec::rbs_parser::parse(&s).unwrap(); - for def in parsed.leak().iter() { - ftypes_imports.push(mec::template::RustImportFnTemplate { - func_name: &def.name, - args_decl: def.args_decl(), - rettype_decl: def.rettype_decl(), - imported_body: def.imported_body(), - import_helper_var: def.import_helper_var(), - }) - } - } - - if export_rbs.exists() { - debug_println(opts.verbose, &format!( - "detected export.rbs: {}", - export_rbs.as_path().to_string_lossy() - )); - let mut f = File::open(export_rbs)?; - let mut s = String::new(); - f.read_to_string(&mut s)?; - - let (_, parsed) = mec::rbs_parser::parse(&s).unwrap(); - let mut ftypes = vec![]; - for def in parsed.leak().iter() { - ftypes.push(mec::template::RustFnTemplate { - func_name: &def.name, - args_decl: def.args_decl(), - args_let_vec: def.args_let_vec(), - str_args_converter: def.str_args_converter(), - rettype_decl: def.rettype_decl(), - handle_retval: def.handle_retval(), - exported_helper_var: def.exported_helper_var(), - }) - } - - let lib_rs = LibRs { - file_basename: &fname, - ftypes: &&ftypes, - ftypes_imports: &ftypes_imports, - }; - let rendered = lib_rs.render()?; - cont = rendered; - } else { - if fnname.is_none() { - panic!("--fnname FNNAME should be specified when export.rbs does not exist") - } - let fnname = fnname.unwrap(); - - let ftypes = vec![mec::template::RustFnTemplate { - func_name: fnname.to_str().unwrap(), - args_decl: "", - args_let_vec: "vec![]", - str_args_converter: "", - rettype_decl: "-> ()", - handle_retval: "()", - exported_helper_var: "", - }]; - - let lib_rs = LibRs { - file_basename: &fname, - ftypes: &&ftypes, - ftypes_imports: &ftypes_imports, - }; - let rendered = lib_rs.render()?; - cont = rendered; - } - debug_println(opts.verbose, "[debug] will generate main.rs:"); - debug_println(opts.verbose, &format!("{}", &cont)); - std::fs::write("src/lib.rs", cont)?; - - let target = if opts.no_wasi { - "wasm32-unknown-unknown" - } else { - "wasm32-wasip1" - }; - - sh_do(&format!("cargo build --target {} --release", target), opts.verbose)?; - sh_do(&format!( - "cp ./target/{}/release/mywasm.wasm {}/{}.wasm", - target, - &pwd.to_str().unwrap(), - &fname.to_string() - ), opts.verbose)?; - if opts.skip_cleanup { - println!( - "debug: working directory for compile wasm is remained in {}", - std::env::current_dir()?.as_os_str().to_str().unwrap() - ); - } else { - sh_do(&format!("cd .. && rm -rf work-mrubyedge-{}", &suffix), opts.verbose)?; - } - - std::env::set_current_dir(pwd)?; - - println!("[ok] wasm file is generated: {}.wasm", &fname); - - Ok(()) -} diff --git a/mec/src/rbs_parser/mod.rs b/mec/src/rbs_parser/mod.rs deleted file mode 100644 index cd44d20..0000000 --- a/mec/src/rbs_parser/mod.rs +++ /dev/null @@ -1,328 +0,0 @@ -extern crate nom; - -#[derive(Debug)] -pub struct FuncDef { - pub name: String, - pub argstype: Vec, - pub rettype: String, -} - -impl FuncDef { - pub fn args_decl(&self) -> &str { - if self.argstype.len() == 0 { - return ""; - } - - let converted: Vec = self - .argstype - .iter() - .enumerate() - .map(|(idx, arg)| match arg.as_str() { - "Integer" => format!("a{}: i32", idx), - "Float" => format!("a{}: f32", idx), - "bool" => format!("a{}: bool", idx), - "String" => format!("p{0}: *const u8, l{0}: usize", idx), - _ => { - unimplemented!("unsupported arg type") - } - }) - .collect(); - converted.join(", ").leak() - } - - pub fn args_let_vec(&self) -> &str { - if self.argstype.len() == 0 { - return "vec![]"; - } - - let converted: Vec = self - .argstype - .iter() - .enumerate() - .map(|(idx, arg)| match arg.as_str() { - "Integer" => format!("std::rc::Rc::new(RObject::integer(a{} as i64))", idx), - "Float" => format!("std::rc::Rc::new(RObject::float(a{} as f64))", idx), - "bool" => format!("std::rc::Rc::new(RObject::boolean(a{}))", idx), - "String" => format!("std::rc::Rc::new(RObject::string(a{}.to_string()))", idx), - _ => { - unimplemented!("unsupported arg type") - } - }) - .collect(); - format!("vec![{}]", converted.join(", ")).leak() - } - - pub fn str_args_converter(&self) -> &str { - if self.argstype.len() == 0 { - return ""; - } - let mut buf = String::new(); - - for (idx, arg) in self.argstype.iter().enumerate() { - match arg.as_str() { - "String" => { - buf.push_str(&format!( - " -let a{0} = unsafe {{ - let s = std::slice::from_raw_parts(p{0}, l{0} as usize); - std::str::from_utf8(s).expect(\"invalid utf8\") -}}; -", - idx - )); - } - _ => { - // skip - } - } - } - - buf.leak() - } - - pub fn rettype_decl(&self) -> &str { - match self.rettype.as_str() { - "void" => "-> ()", - "Integer" => "-> i32", - "Float" => "-> f32", - "bool" => "-> bool", - "String" => "-> *const u8", - "SharedMemory" => "-> *mut u8", - _ => { - unimplemented!("unsupported arg type") - } - } - } - - pub fn handle_retval(&self) -> &str { - match self.rettype.as_str() { - "String" => { - let mut buf = String::new(); - buf.push_str("let mut retval: String = retval.as_ref().try_into().unwrap();\n"); - buf.push_str("retval.push('\0');\n"); - buf.push_str(&format!( - "unsafe {{ {} = retval.len() - 1; }}\n", - self.size_helper_var_name() - )); - buf.push_str("retval.as_str().as_ptr()\n"); - buf.leak() - } - _ => "retval.as_ref().try_into().unwrap()", - } - } - - fn size_helper_var_name(&self) -> String { - format!("__{}_size", self.name) - } - - pub fn exported_helper_var(&self) -> &str { - match self.rettype.as_str() { - "String" => format!( - " -#[allow(non_upper_case_globals)] -pub static mut {0}: usize = 0; -#[no_mangle] -pub unsafe fn __get{0}() -> u32 {{ - return {0} as u32; -}} -", - &self.size_helper_var_name() - ) - .leak(), - _ => "", - } - } - - pub fn import_helper_var(&self) -> &str { - match self.rettype.as_str() { - "String" => format!( - " -#[allow(non_upper_case_globals)] -pub static mut {0}: usize = 0; -#[no_mangle] -pub unsafe fn __set{0}(s: u32) {{ - {0} = s as usize; -}} -", - &self.size_helper_var_name() - ) - .leak(), - _ => "", - } - } - - // for function importer - pub fn imported_body(&self) -> &str { - let mut buf = String::new(); - for (i, typ) in self.argstype.iter().enumerate() { - let tmp = match typ.as_str() { - "String" => { - let mut buf = String::new(); - buf.push_str(&format!( - "let a{0}: String = args[{0}].clone().as_ref().try_into().unwrap();\n", - i - )); - buf.push_str(&format!("let p{0} = a{0}.as_str().as_ptr();\n", i)); - buf.push_str(&format!("let l{0} = a{0}.as_str().len();\n", i)); - buf - } - _ => format!( - "let a{0} = args[{0}].clone().as_ref().try_into().unwrap();\n", - i, - ), - }; - buf.push_str(&tmp); - } - let call_arg = self - .argstype - .iter() - .enumerate() - .map(|(i, typ)| match typ.as_str() { - "String" => format!("p{0}, l{0}", i), - _ => format!("a{}", i), - }) - .collect::>() - .join(","); - buf.push_str(&format!( - "let r0 = unsafe {{ {}({}) }};\n", - &self.name, call_arg - )); - - if self.rettype.as_str() == "String" { - buf.push_str(&format!( - " -let s0: String; -unsafe {{ - if {0} == 0 {{ - let mut buf = Vec::::new(); - let mut off: usize = 0; - loop {{ - let b = *(r0.add(off)); - if b == 0 {{ - break; - }} else {{ - buf.push(b); - }} - if off >= 65536 {{ - panic!(\"unterminated string detected\"); - }} - off += 1; - }} - s0 = String::from_utf8_unchecked(buf); - }} else {{ - let off = {0}; - let s = std::slice::from_raw_parts(r0, off); - s0 = String::from_utf8_unchecked(s.to_vec()); - }} -}} -", - self.size_helper_var_name() - )); - } - - let ret_mruby_type = match self.rettype.as_str() { - "Integer" => "RObject::integer(r0 as i64)", - "Float" => "RObject::float(r0 as f64)", - "bool" => "RObject::boolean(r0)", - "String" => "RObject::string(s0)", - "void" => "RObject::nil()", - _ => unimplemented!("unsupported arg type"), - }; - buf.push_str(&format!("Ok(Rc::new({}))\n", ret_mruby_type)); - buf.leak() - } -} - -use nom::branch::alt; -use nom::branch::permutation; -use nom::bytes::complete::tag; -use nom::character::complete::*; -// use nom::combinator::opt; -use nom::error::context; -use nom::error::VerboseError; -use nom::multi::*; -use nom::sequence::tuple; -use nom::IResult; - -type Res = IResult>; - -fn def(input: &str) -> Res<&str, ()> { - context("def", tag("def"))(input).map(|(s, _)| (s, ())) -} - -fn alpha_just_1(input: &str) -> Res<&str, char> { - satisfy(|c| c == '_' || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z'))(input) -} - -fn alphanumeric_just_1(input: &str) -> Res<&str, char> { - satisfy(|c| { - c == '_' || ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') - })(input) -} - -fn symbol(input: &str) -> Res<&str, String> { - tuple((alpha_just_1, many0(alphanumeric_just_1)))(input).map(|(s, (head, tail))| { - let mut name: String = head.to_string(); - for c in tail.iter() { - name += &c.to_string() - } - (s, name) - }) -} - -fn method(input: &str) -> Res<&str, String> { - tuple((symbol, char(':'), space0))(input).map(|(s, (sym, _, _))| (s, sym)) -} - -fn emptyarg(input: &str) -> Res<&str, Vec> { - tuple((char('('), space0, char(')')))(input).map(|(s, _)| (s, vec![])) -} - -fn contentarg(input: &str) -> Res<&str, Vec> { - tuple(( - char('('), - space0, - symbol, - space0, - many0(tuple((char(','), space0, symbol, space0))), - char(')'), - ))(input) - .map(|(s, (_, _, head, _, rest, _))| { - let mut syms: Vec = rest.into_iter().map(|(_, _, val, _)| val).collect(); - syms.insert(0, head); - (s, syms) - }) -} - -fn arg(input: &str) -> Res<&str, Vec> { - alt((emptyarg, contentarg))(input) -} - -fn ret(input: &str) -> Res<&str, String> { - tuple((tag("->"), space0, symbol))(input).map(|(s, (_, _, sym))| (s, sym)) -} - -fn fntype(input: &str) -> Res<&str, (Vec, String)> { - tuple((arg, space0, ret))(input).map(|(s, (arg, _, ret))| (s, (arg, ret))) -} - -pub fn fn_def(input: &str) -> Res<&str, FuncDef> { - tuple((def, space1, method, fntype))(input).map(|(s, (_, _, name, (argstype, rettype)))| { - ( - s, - FuncDef { - name, - argstype, - rettype, - }, - ) - }) -} - -pub fn parse(input: &str) -> Res<&str, Vec> { - tuple(( - multispace0, - separated_list0(permutation((space0, many1(char('\n')), space0)), fn_def), - ))(input) - .map(|(s, (_, list))| (s, list)) -} diff --git a/mec/src/template/cargo_toml.rs b/mec/src/template/cargo_toml.rs deleted file mode 100644 index 8ad1c48..0000000 --- a/mec/src/template/cargo_toml.rs +++ /dev/null @@ -1,17 +0,0 @@ -extern crate askama; -use askama::Template; - -#[derive(Template)] -#[template(path = "Cargo.toml.tmpl", escape = "none")] -pub struct CargoToml<'a> { - pub mrubyedge_version: &'a str, - pub mrubyedge_feature: &'a str, - pub strip: &'a str, -} - -#[derive(Template)] -#[template(path = "Cargo.toml.debug.tmpl", escape = "none")] -pub struct CargoTomlDebug<'a> { - pub mruby_edge_crate_path: &'a str, - pub mrubyedge_feature: &'a str, -} diff --git a/mec/src/template/mod.rs b/mec/src/template/mod.rs deleted file mode 100644 index 28464bd..0000000 --- a/mec/src/template/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod cargo_toml; -pub mod source; - -pub use cargo_toml::CargoToml; -pub use source::*; diff --git a/mec/src/template/source.rs b/mec/src/template/source.rs deleted file mode 100644 index 7cea08e..0000000 --- a/mec/src/template/source.rs +++ /dev/null @@ -1,29 +0,0 @@ -extern crate askama; -use askama::Template; - -#[derive(Template)] -#[template(path = "lib.rs.tmpl", escape = "none")] -pub struct LibRs<'a> { - pub file_basename: &'a str, - - pub ftypes: &'a [RustFnTemplate<'a>], - pub ftypes_imports: &'a [RustImportFnTemplate<'a>], -} - -pub struct RustFnTemplate<'a> { - pub func_name: &'a str, - pub args_decl: &'a str, - pub args_let_vec: &'a str, - pub str_args_converter: &'a str, - pub rettype_decl: &'a str, - pub handle_retval: &'a str, - pub exported_helper_var: &'a str, -} - -pub struct RustImportFnTemplate<'a> { - pub func_name: &'a str, - pub args_decl: &'a str, - pub imported_body: &'a str, - pub rettype_decl: &'a str, - pub import_helper_var: &'a str, -} diff --git a/mec/templates/Cargo.toml.debug.tmpl b/mec/templates/Cargo.toml.debug.tmpl deleted file mode 100644 index 6807f5c..0000000 --- a/mec/templates/Cargo.toml.debug.tmpl +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "mywasm" -version = "1.0.0" -edition = "2021" - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies.mrubyedge] -# fixme can be changed -path = "{{ mruby_edge_crate_path }}" -default-features = false -features = [ "{{ mrubyedge_feature }}" ] - -[profile.release] -# Tell `rustc` to optimize for small code size. -opt-level = "s" \ No newline at end of file diff --git a/mec/templates/Cargo.toml.tmpl b/mec/templates/Cargo.toml.tmpl deleted file mode 100644 index ec55015..0000000 --- a/mec/templates/Cargo.toml.tmpl +++ /dev/null @@ -1,17 +0,0 @@ -[package] -name = "mywasm" -version = "1.0.0" -edition = "2021" - -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies.mrubyedge] -version = "{{ mrubyedge_version }}" -default-features = false -features = [ "{{ mrubyedge_feature }}" ] - -[profile.release] -# Tell `rustc` to optimize for small code size. -opt-level = "s" -strip = {{ strip }} \ No newline at end of file diff --git a/mec/templates/lib.rs.tmpl b/mec/templates/lib.rs.tmpl deleted file mode 100644 index a39b230..0000000 --- a/mec/templates/lib.rs.tmpl +++ /dev/null @@ -1,100 +0,0 @@ -#![allow(unused_variables)] -#![allow(non_snake_case)] -#![allow(unused_imports)] -extern crate mrubyedge; - -use core::mem::MaybeUninit; -use std::rc::Rc; -use std::cell::RefCell; -use std::any::Any; - -use mrubyedge::yamrb::value::*; - -const DATA: &'static [u8] = include_bytes!("./{{ file_basename }}.mrb"); -const MEMORY_INDEX: u32 = 0; -const PAGE_SIZE: usize = 65536; // 64KB - -#[cfg(target_arch = "wasm32")] -#[no_mangle] -pub unsafe fn __mrbe_grow(num_pages: usize) -> *const u8 { - use core::arch::wasm32; - let num_pages = wasm32::memory_grow(MEMORY_INDEX, num_pages); - if num_pages == usize::max_value() { - return usize::max_value() as *const u8; - } - let ptr = (num_pages * PAGE_SIZE) as *const u8; - ptr -} - -extern "C" { -{% for fn in ftypes_imports %} - fn {{ fn.func_name }}({{ fn.args_decl }}) {{ fn.rettype_decl }}; -{% endfor %} -} - -{% for fn in ftypes_imports %} -{{ fn.import_helper_var }} -fn __imported_c_{{ fn.func_name }}(_vm: &mut mrubyedge::yamrb::vm::VM, args: &[Rc]) -> Result, mrubyedge::Error> { -{{ fn.imported_body }} -} -{% endfor %} - -static mut MRUBY_VM: MaybeUninit = MaybeUninit::uninit(); -static mut MRUBY_VM_LOADED: bool = false; - -#[allow(static_mut_refs)] -unsafe fn assume_initialized_VM() -> &'static mut mrubyedge::yamrb::vm::VM { - if !MRUBY_VM_LOADED { - initVM(); - MRUBY_VM_LOADED = true; - } - MRUBY_VM.assume_init_mut() -} - -fn initVM() { - let mut rite = mrubyedge::rite::load(DATA).unwrap(); - let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - -{% for ifn in ftypes_imports %} - let klass = vm.object_class.clone(); - let method = Box::new(__imported_c_{{ ifn.func_name }}); - mrubyedge::yamrb::helpers::mrb_define_cmethod(&mut vm, klass, "{{ ifn.func_name }}", method); -{% endfor %} - - vm.run().unwrap(); - - unsafe { - MRUBY_VM = core::mem::MaybeUninit::new(vm); - } -} - -{% for fn in ftypes %} - -{{ fn.exported_helper_var }} -#[no_mangle] -pub fn {{ fn.func_name }}({{ fn.args_decl }}) {{ fn.rettype_decl }} { - let mut vm = unsafe { assume_initialized_VM() }; - - {{ fn.str_args_converter }} - - vm.run().unwrap(); - - let args = {{ fn.args_let_vec }}; - let retval: Result, mrubyedge::Error> = - mrubyedge::yamrb::helpers::mrb_funcall( - &mut vm, - None, - "{{ fn.func_name }}", - &args); - - match &retval { - Ok(retval) => { - {{ fn.handle_retval }} - } - Err(ex) => { - dbg!(ex); - panic!("mruby error"); - } - } -} -{% endfor %} \ No newline at end of file From 5cd0d6e48bb7a6cac37d7f97feefca9e92500aa3 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 7 Jan 2026 01:18:34 +0900 Subject: [PATCH 132/314] Add karg boilerplate functions + handle n|k<<4 in op_send variants --- Cargo.lock | 18 -------------- mrubyedge/src/yamrb/optable.rs | 43 ++++++++++++++++++++++++---------- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 73168c9..4f8f924 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -172,12 +172,6 @@ version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" -[[package]] -name = "bpaf" -version = "0.9.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "473976d7a8620bb1e06dcdd184407c2363fe4fec8e983ee03ed9197222634a31" - [[package]] name = "bumpalo" version = "3.19.0" @@ -518,18 +512,6 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" -[[package]] -name = "mec" -version = "1.0.5" -dependencies = [ - "askama", - "bpaf", - "mec-mrbc-sys", - "nom", - "rand", - "syn", -] - [[package]] name = "mec-mrbc-sys" version = "3.3.1" diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index ebb7b2d..c646539 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -307,15 +307,15 @@ pub(crate) fn consume_expr( ENTER => { op_enter(vm, operand)?; } - // KEY_P => { - // // op_key_p(vm, &operand)?; - // } - // KEYEND => { - // // op_keyend(vm, &operand)?; - // } - // KARG => { - // // op_karg(vm, &operand)?; - // } + KEY_P => { + op_key_p(vm, &operand)?; + } + KEYEND => { + op_keyend(vm, &operand)?; + } + KARG => { + op_karg(vm, &operand)?; + } RETURN => { op_return(vm, operand)?; } @@ -871,7 +871,9 @@ pub(crate) fn op_ssend(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_ssendb(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b, c) = operand.as_bbb()?; - do_op_send(vm, 0, Some(a as usize + c as usize + 1), a, b, c) + let n: usize = (c & 0x0f) as usize; + let k: usize = (c >> 4) as usize; + do_op_send(vm, 0, Some(a as usize + n + k * 2 + 1), a, b, c) } pub(crate) fn op_send(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { @@ -881,7 +883,9 @@ pub(crate) fn op_send(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_sendb(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b, c) = operand.as_bbb()?; - do_op_send(vm, a as usize, Some(a as usize + c as usize + 1), a, b, c) + let n: usize = (c & 0x0f) as usize; + let k: usize = (c >> 4) as usize; + do_op_send(vm, a as usize, Some(a as usize + n + k * 2 + 1), a, b, c) } pub(crate) fn do_op_send( @@ -892,6 +896,9 @@ pub(crate) fn do_op_send( b: u8, c: u8, ) -> Result<(), Error> { + let n: usize = (c & 0x0f) as usize; + let k: usize = (c >> 4) as usize; + let method_id = vm.current_irep.syms[b as usize].clone(); if &method_id.name == "__debug__vm_info" { // Special debug method to dump VM info @@ -900,7 +907,7 @@ pub(crate) fn do_op_send( return Ok(()); } - let block_index = (a + c + 1) as usize; + let block_index = a as usize + n + k * 2 + 1; let recv = if recv_index == 0 { vm.getself()? @@ -1126,6 +1133,18 @@ pub(crate) fn op_enter(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { Ok(()) } +pub(crate) fn op_key_p(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { + todo!() +} + +pub(crate) fn op_keyend(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { + todo!() +} + +pub(crate) fn op_karg(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { + todo!() +} + pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()? as usize; let old_irep = vm.current_irep.clone(); From f5c72c53f8ba7056707963a6ac2321f73e06e68f Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 8 Jan 2026 19:00:50 +0900 Subject: [PATCH 133/314] Support keyword arguments --- mrubyedge/src/yamrb/optable.rs | 93 ++++++++++++++++++++++++++++++---- mrubyedge/src/yamrb/value.rs | 10 +++- mrubyedge/src/yamrb/vm.rs | 12 +++++ 3 files changed, 103 insertions(+), 12 deletions(-) diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index c646539..30bbb3d 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -308,13 +308,13 @@ pub(crate) fn consume_expr( op_enter(vm, operand)?; } KEY_P => { - op_key_p(vm, &operand)?; + op_key_p(vm, operand)?; } KEYEND => { - op_keyend(vm, &operand)?; + op_keyend(vm, operand)?; } KARG => { - op_karg(vm, &operand)?; + op_karg(vm, operand)?; } RETURN => { op_return(vm, operand)?; @@ -914,12 +914,25 @@ pub(crate) fn do_op_send( } else { vm.get_current_regs_cloned(recv_index)? }; - let mut args = (0..c) + let mut args = (0..n) .map(|i| { - vm.get_current_regs_cloned((a + i + 1) as usize) + vm.get_current_regs_cloned(a as usize + i + 1) .expect("args too short for required") }) .collect::>(); + + let mut map = HashMap::new(); + for i in 0..k { + let key = vm + .get_current_regs_cloned(a as usize + n + i * 2 + 1)? + .intern()?; + let val = vm + .get_current_regs_cloned(a as usize + n + i * 2 + 2)? + .clone(); + map.insert(key, val); + } + vm.kargs.borrow_mut().replace(map); + if let Some(blk_index) = blk_index { args.push(vm.get_current_regs_cloned(blk_index)?); } else { @@ -981,7 +994,7 @@ pub(crate) fn do_op_send( return Ok(()); } - push_callinfo(vm, method_id, c as usize, Some(owner_module), a as usize); + push_callinfo(vm, method_id, n, Some(owner_module), a as usize); vm.pc.set(0); vm.current_irep = method.irep.ok_or_else(|| Error::internal("empry irep"))?; @@ -1126,23 +1139,76 @@ pub(crate) fn op_enter(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { match vm.current_regs()[i + 1].as_ref() { Some(_) => {} None => { - unreachable!("argument {} not passed", i + 1); + return Err(Error::ArgumentError(format!( + "argument {} not passed", + i + 1 + ))); } } } + let args = vm.kargs.borrow_mut().take(); + if args.is_none() { + return Err(Error::ArgumentError( + "keyword arguments not passed".to_string(), + )); + } + let upper = vm.current_kargs.borrow_mut().take(); + + let current_arg = KArgs { + args: RefCell::new(args.unwrap()), + upper, + }; + vm.current_kargs.borrow_mut().replace(Rc::new(current_arg)); + Ok(()) } pub(crate) fn op_key_p(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { - todo!() + let (a, b) = operand.as_bb()?; + let val = { + let key = vm.current_irep.syms[b as usize].clone(); + let kargs = vm.current_kargs.borrow(); + let kargs = kargs + .as_ref() + .ok_or_else(|| Error::internal("no kargs found"))?; + RObject::boolean(kargs.args.borrow().contains_key(&key)) + }; + vm.current_regs()[a as usize].replace(val.to_refcount_assigned()); + Ok(()) } -pub(crate) fn op_keyend(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { - todo!() +pub(crate) fn op_keyend(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> { + match vm.current_kargs.borrow().as_deref() { + Some(kargs) => { + let is_empty = kargs.args.borrow().is_empty(); + if is_empty { + Ok(()) + } else { + Err(Error::ArgumentError( + "unexpected keyword arguments".to_string(), + )) + } + } + None => Err(Error::internal("no kargs found")), + } } pub(crate) fn op_karg(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { - todo!() + let (a, b) = operand.as_bb()?; + let val = { + let key = vm.current_irep.syms[b as usize].clone(); + let kargs = vm.current_kargs.borrow(); + let kargs = kargs + .as_ref() + .ok_or_else(|| Error::internal("no kargs found"))?; + + let mut args = kargs.args.borrow_mut(); + args.remove(&key).ok_or_else(|| { + Error::ArgumentError(format!("keyword argument '{}' not found", key.name)) + })? + }; + vm.current_regs()[a as usize].replace(val); + Ok(()) } pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { @@ -1197,6 +1263,11 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { unreachable!("debug"); } + let old_kargs = vm.current_kargs.borrow_mut().take(); + if let Some(upper) = old_kargs.as_ref().and_then(|kargs| kargs.upper.clone()) { + vm.current_kargs.borrow_mut().replace(upper); + } + let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); if let Some(upper) = &cur.as_ref().upper { vm.current_breadcrumb.replace(upper.clone()); diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 0510370..46dff0a 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -480,6 +480,14 @@ impl RObject { } self.get_class(vm) } + + pub fn intern(&self) -> Result { + match &self.value { + RValue::String(s) => Ok(RSym::new(String::from_utf8_lossy(&s.borrow()).to_string())), + RValue::Symbol(s) => Ok(s.clone()), + _ => Err(Error::TypeMismatch), + } + } } impl TryFrom<&RObject> for i32 { @@ -1023,7 +1031,7 @@ pub struct RProc { pub type RFn = Box]) -> Result, Error>>; /// Interned symbol name used across the VM to identify methods and constants. -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct RSym { pub name: String, } diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 8c46dcc..2d24a0c 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -39,6 +39,12 @@ pub struct Breadcrumb { pub upper: Option>, } +#[derive(Debug)] +pub struct KArgs { + pub args: RefCell>>, + pub upper: Option>, +} + impl Breadcrumb { #[cfg(feature = "mrubyedge-debug")] pub fn display_breadcrumb_for_debug(&self, level: usize, max_level: usize) -> bool { @@ -70,6 +76,8 @@ pub struct VM { pub current_regs_offset: usize, pub current_callinfo: Option>, pub current_breadcrumb: Option>, + pub kargs: RefCell>>>, + pub current_kargs: RefCell>>, pub target_class: TargetContext, pub exception: Option>, @@ -148,6 +156,8 @@ impl VM { caller: None, return_reg: None, })); + let kargs = RefCell::new(None); + let current_kargs = RefCell::new(None); let target_class = TargetContext::Class(object_class.clone()); let exception = None; let flag_preemption = Cell::new(false); @@ -166,6 +176,8 @@ impl VM { current_regs_offset, current_callinfo, current_breadcrumb, + kargs, + current_kargs, target_class, exception, flag_preemption, From aeaf09153a577165c3814ea0f8ca833e9dcb73c6 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 8 Jan 2026 21:46:23 +0900 Subject: [PATCH 134/314] Basic kwargs --- mrubyedge/src/yamrb/optable.rs | 23 ++++---- mrubyedge/tests/kargs.rs | 95 ++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 11 deletions(-) create mode 100644 mrubyedge/tests/kargs.rs diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 30bbb3d..ca075d7 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1107,6 +1107,7 @@ pub(crate) fn op_super(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { } #[allow(dead_code)] +#[derive(Debug)] struct EnterArgInfo { m1: u32, o: u32, @@ -1146,17 +1147,17 @@ pub(crate) fn op_enter(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { } } } - let args = vm.kargs.borrow_mut().take(); - if args.is_none() { - return Err(Error::ArgumentError( - "keyword arguments not passed".to_string(), - )); - } - let upper = vm.current_kargs.borrow_mut().take(); - - let current_arg = KArgs { - args: RefCell::new(args.unwrap()), - upper, + let current_arg = if let Some(args) = vm.kargs.borrow_mut().take() { + let upper = vm.current_kargs.borrow_mut().take(); + KArgs { + args: RefCell::new(args), + upper, + } + } else { + KArgs { + args: RefCell::new(HashMap::new()), + upper: None, + } }; vm.current_kargs.borrow_mut().replace(Rc::new(current_arg)); diff --git a/mrubyedge/tests/kargs.rs b/mrubyedge/tests/kargs.rs new file mode 100644 index 0000000..b5ad219 --- /dev/null +++ b/mrubyedge/tests/kargs.rs @@ -0,0 +1,95 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use std::rc::Rc; + +use helpers::*; +use mrubyedge::yamrb::value::RObject; + +#[test] +fn basic_keyword_args_test() { + let code = " + def greet(name, greeting: 'Hello') + greeting + ', ' + name + end + + greet('Bob', greeting: 'Hi') + "; + let binary = mrbc_compile("basic_kargs", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(&result_str, "Hi, Bob"); +} + +#[test] +fn multiple_keyword_args_test() { + let code = " + def destruct_it(x, foo: 42, bar: 99) + x + foo + bar + end + + destruct_it(10, foo: 20, bar: 30) + "; + let binary = mrbc_compile("multiple_kargs", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 10 + 20 + 30); +} + +#[test] +fn keyword_args_string_symbol_test() { + let code = " + def format_text(text, prefix: '', suffix: '') + prefix + text + suffix + end + + result1 = format_text('Hello') + result2 = format_text('Hello', prefix: '>> ') + result3 = format_text('Hello', suffix: ' <<') + result4 = format_text('Hello', prefix: '[', suffix: ']') + [result1, result2, result3, result4] + "; + let binary = mrbc_compile("string_kargs", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_array: Vec> = result.as_ref().try_into().unwrap(); + let mut expected_array = vec!["Hello", ">> Hello", "Hello <<", "[Hello]"]; + for obj in result_array { + let s: String = obj.as_ref().try_into().unwrap(); + let expected = expected_array.remove(0); + assert_eq!(&s, expected); + } +} + +#[test] +fn keyword_args_nested_call_test() { + let code = " + def inner(value, multiplier: 2) + value * multiplier + end + + def outer(x, factor: 3) + result1 = inner(x) + result2 = inner(x + 1, multiplier: factor) + result1 + result2 + end + + [ + outer(5), + outer(5, factor: 4) + ] + "; + let binary = mrbc_compile("nested_kargs", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let (got1, got2) = result.as_ref().try_into().unwrap(); + assert_eq!(got1, 28); // 5 * 2 + 6 * 3 + assert_eq!(got2, 34); // 5 * 2 + 6 * 4 +} From 740b1919d50889b4a49cd72d4dcfeb4e710ff889 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 8 Jan 2026 22:02:16 +0900 Subject: [PATCH 135/314] Support kwargs in Rust method --- mrubyedge/src/yamrb/optable.rs | 46 +++++++++++++++++++++------------- mrubyedge/src/yamrb/vm.rs | 12 +++++++++ mrubyedge/tests/kargs.rs | 42 +++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+), 17 deletions(-) diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index ca075d7..52366ee 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -962,6 +962,8 @@ pub(crate) fn do_op_send( vm.current_regs()[a as usize].replace(recv.clone()); if !method.is_rb_func { + kwarg_op_enter(vm); + let func = vm .get_fn(method.func.unwrap()) .ok_or_else(|| Error::internal("function not found"))?; @@ -969,6 +971,8 @@ pub(crate) fn do_op_send( let res = func(vm, &args); + kwarg_op_return(vm); + vm.current_regs_offset -= a as usize; for i in (a as usize + 1)..block_index { vm.current_regs()[i].take(); @@ -1002,6 +1006,29 @@ pub(crate) fn do_op_send( Ok(()) } +fn kwarg_op_enter(vm: &mut VM) { + let current_arg = if let Some(args) = vm.kargs.borrow_mut().take() { + let upper = vm.current_kargs.borrow_mut().take(); + KArgs { + args: RefCell::new(args), + upper, + } + } else { + KArgs { + args: RefCell::new(HashMap::new()), + upper: None, + } + }; + vm.current_kargs.borrow_mut().replace(Rc::new(current_arg)); +} + +fn kwarg_op_return(vm: &mut VM) { + let old_kargs = vm.current_kargs.borrow_mut().take(); + if let Some(upper) = old_kargs.as_ref().and_then(|kargs| kargs.upper.clone()) { + vm.current_kargs.borrow_mut().replace(upper); + } +} + pub(crate) fn op_call(vm: &mut VM, _operand: &Fetched) -> Result<(), Error> { let upper = vm.current_breadcrumb.take(); let new_breadcrumb = Rc::new(Breadcrumb { @@ -1147,19 +1174,7 @@ pub(crate) fn op_enter(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { } } } - let current_arg = if let Some(args) = vm.kargs.borrow_mut().take() { - let upper = vm.current_kargs.borrow_mut().take(); - KArgs { - args: RefCell::new(args), - upper, - } - } else { - KArgs { - args: RefCell::new(HashMap::new()), - upper: None, - } - }; - vm.current_kargs.borrow_mut().replace(Rc::new(current_arg)); + kwarg_op_enter(vm); Ok(()) } @@ -1264,10 +1279,7 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { unreachable!("debug"); } - let old_kargs = vm.current_kargs.borrow_mut().take(); - if let Some(upper) = old_kargs.as_ref().and_then(|kargs| kargs.upper.clone()) { - vm.current_kargs.borrow_mut().replace(upper); - } + kwarg_op_return(vm); let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); if let Some(upper) = &cur.as_ref().upper { diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 2d24a0c..2cd07d3 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -389,6 +389,18 @@ impl VM { .expect("self is not assigned") } + pub fn get_kwargs(&self) -> Option>> { + let kwargs = self.current_kargs.borrow().clone(); + kwargs.map(|kargs| { + kargs + .args + .borrow() + .iter() + .map(|(k, v)| (k.name.clone(), v.clone())) + .collect() + }) + } + pub(crate) fn register_fn(&mut self, f: RFn) -> usize { self.fn_table.push(Rc::new(f)); self.fn_table.len() - 1 diff --git a/mrubyedge/tests/kargs.rs b/mrubyedge/tests/kargs.rs index b5ad219..c7eb4d8 100644 --- a/mrubyedge/tests/kargs.rs +++ b/mrubyedge/tests/kargs.rs @@ -5,7 +5,10 @@ mod helpers; use std::rc::Rc; use helpers::*; +use mrubyedge::Error; +use mrubyedge::yamrb::helpers::mrb_define_cmethod; use mrubyedge::yamrb::value::RObject; +use mrubyedge::yamrb::vm::VM; #[test] fn basic_keyword_args_test() { @@ -93,3 +96,42 @@ fn keyword_args_nested_call_test() { assert_eq!(got1, 28); // 5 * 2 + 6 * 3 assert_eq!(got2, 34); // 5 * 2 + 6 * 4 } + +#[test] +fn keyword_args_c_definition_test() { + fn test_mrb_multiply(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let a: i32 = args + .first() + .ok_or_else(|| Error::ArgumentError("missing positional argument 'a'".to_string()))? + .as_ref() + .try_into()?; + let kwargs = vm.get_kwargs(); + match kwargs { + Some(kargs) => { + let b_obj = kargs.get("b").ok_or_else(|| { + Error::ArgumentError("missing keyword argument 'b'".to_string()) + })?; + let c_obj = kargs.get("c").ok_or_else(|| { + Error::ArgumentError("missing keyword argument 'c'".to_string()) + })?; + let b: i32 = b_obj.as_ref().try_into()?; + let c: i32 = c_obj.as_ref().try_into()?; + Ok(Rc::new(RObject::integer((a * b * c) as i64))) + } + None => Err(Error::ArgumentError( + "missing keyword arguments".to_string(), + )), + } + } + + let code = "multiply(7, b: 3, c: 11)"; + let binary = mrbc_compile("cdef_kargs", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let kernel = vm.object_class.clone(); + mrb_define_cmethod(&mut vm, kernel, "multiply", Box::new(test_mrb_multiply)); + + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 7 * 3 * 11); +} From f99cb94e9d3db8bde57075cb32f2a9b8bc61bc04 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 8 Jan 2026 22:07:57 +0900 Subject: [PATCH 136/314] Bump --- Cargo.lock | 12 ++++++------ mrubyedge/Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4f8f924..e5be119 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -566,10 +566,10 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce329c1a18fa10ba5d6508c247c27cfc89cb8da2d730302d8cdb995995a25a9" dependencies = [ - "criterion", "getrandom 0.3.4", - "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -577,11 +577,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce329c1a18fa10ba5d6508c247c27cfc89cb8da2d730302d8cdb995995a25a9" +version = "1.0.16" dependencies = [ + "criterion", "getrandom 0.3.4", + "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -594,7 +594,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.15 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.0.15", "nom", "rand", "syn", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index f6695e0..1d2b59c 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.0.15" +version = "1.0.16" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From ef6c75a37023c7d6b7f7310ede68abe071315397 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 8 Jan 2026 22:08:46 +0900 Subject: [PATCH 137/314] Bump (2) --- Cargo.lock | 14 +++++++------- mrubyedge-cli/Cargo.toml | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e5be119..f1e5384 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -565,11 +565,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ce329c1a18fa10ba5d6508c247c27cfc89cb8da2d730302d8cdb995995a25a9" +version = "1.0.16" dependencies = [ + "criterion", "getrandom 0.3.4", + "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -578,10 +578,10 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2643719054e1018cd821d13d4382da0f4f2617d341e39d274806c5a3278c936" dependencies = [ - "criterion", "getrandom 0.3.4", - "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -589,12 +589,12 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.0.15" +version = "1.0.16" dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.15", + "mrubyedge 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 397ea68..608017d 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.0.15" +version = "1.0.16" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } mruby-compiler2-sys = "0.2.2" -mrubyedge = { version = "1.0.15", features = ["default", "mruby-regexp"] } +mrubyedge = { version = "1.0.16", features = ["default", "mruby-regexp"] } rand = "0.8.5" nom = "7.1.3" askama = "0.12.1" From 69c6af88032a9bebd9245688f052699b7e7f1cfc Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Fri, 9 Jan 2026 21:44:02 +0900 Subject: [PATCH 138/314] Add static errors for multithreaded environments --- Cargo.lock | 12 ++++++------ mrubyedge/Cargo.toml | 2 +- mrubyedge/src/error.rs | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f1e5384..e0b9803 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -566,10 +566,10 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2643719054e1018cd821d13d4382da0f4f2617d341e39d274806c5a3278c936" dependencies = [ - "criterion", "getrandom 0.3.4", - "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -577,11 +577,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2643719054e1018cd821d13d4382da0f4f2617d341e39d274806c5a3278c936" +version = "1.0.17-rc1" dependencies = [ + "criterion", "getrandom 0.3.4", + "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -594,7 +594,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.0.16", "nom", "rand", "syn", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 1d2b59c..2ebb2c4 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.0.16" +version = "1.0.17-rc1" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" diff --git a/mrubyedge/src/error.rs b/mrubyedge/src/error.rs index 5286417..6a6b66a 100644 --- a/mrubyedge/src/error.rs +++ b/mrubyedge/src/error.rs @@ -77,3 +77,38 @@ impl Error { false } } + +/// Used for asynchronous or multithreaded context +#[derive(Debug, Clone, PartialEq)] +pub enum StaticError { + General(String), +} + +impl fmt::Display for StaticError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "error nr {:?}", self) + } +} + +impl error::Error for StaticError {} + +unsafe impl Sync for StaticError {} +unsafe impl Send for StaticError {} + +impl From for StaticError { + fn from(err: Error) -> StaticError { + match err { + Error::General => StaticError::General("General error".to_string()), + Error::Internal(msg) => StaticError::General(format!("[Internal Error] {}", msg)), + Error::InvalidOpCode => StaticError::General("Invalid opcode".to_string()), + Error::RuntimeError(msg) => StaticError::General(msg), + Error::ArgumentError(msg) => StaticError::General(format!("Invalid argument: {}", msg)), + Error::TypeMismatch => StaticError::General("Type mismatch".to_string()), + Error::NoMethodError(msg) => StaticError::General(format!("Method not found: {}", msg)), + Error::NameError(msg) => StaticError::General(format!("Cannot found name: {}", msg)), + + Error::Break(_) => StaticError::General("[Break]".to_string()), + Error::BlockReturn(_, _) => StaticError::General("[BlockReturn]".to_string()), + } + } +} From 87da3687b5ef3a66966ce12483278c85b6b28ecb Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 11 Jan 2026 13:59:22 +0900 Subject: [PATCH 139/314] Support SharedMemory#leak --- mrubyedge/src/yamrb/shared_memory.rs | 6 ++++++ mrubyedge/src/yamrb/value.rs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/mrubyedge/src/yamrb/shared_memory.rs b/mrubyedge/src/yamrb/shared_memory.rs index 12d7568..4b2b6d6 100644 --- a/mrubyedge/src/yamrb/shared_memory.rs +++ b/mrubyedge/src/yamrb/shared_memory.rs @@ -32,4 +32,10 @@ impl SharedMemory { pub fn read_u8(&self, offset: usize) -> u8 { self.memory[offset] } + + pub fn leak(&mut self) -> *mut u8 { + let data = self.memory.as_ref().to_vec(); + let prt = Vec::leak(data); + prt.as_mut_ptr() + } } diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 46dff0a..2aa2094 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -745,7 +745,7 @@ impl TryFrom<&RObject> for *mut u8 { fn try_from(value: &RObject) -> Result { match &value.value { - RValue::SharedMemory(sm) => Ok(sm.borrow_mut().as_mut_ptr()), + RValue::SharedMemory(sm) => Ok(sm.borrow_mut().leak()), _ => Err(Error::TypeMismatch), } } From 21018ee2d9e24051211f58183daa01aacc2b87f7 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 11 Jan 2026 16:39:45 +0900 Subject: [PATCH 140/314] Add hint comments --- mrubyedge/src/yamrb/value.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 2aa2094..f98fc44 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -743,6 +743,9 @@ impl TryFrom<&RObject> for () { impl TryFrom<&RObject> for *mut u8 { type Error = Error; + /// HINT: SharedMemory's memory region cannot be cleared when the reference count drops to zero. + /// Even if it leaks, Cloudflare Workers deallocate memory on a per-Wasm instance basis + /// every time the request is done; so it does not cause memory leaks. fn try_from(value: &RObject) -> Result { match &value.value { RValue::SharedMemory(sm) => Ok(sm.borrow_mut().leak()), From af8bfed1b575062519e15abf6742f30afb8a01b9 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 11 Jan 2026 16:46:21 +0900 Subject: [PATCH 141/314] New version --- Cargo.lock | 2 +- mrubyedge/Cargo.toml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e0b9803..29df417 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -577,7 +577,7 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.17-rc1" +version = "1.0.17" dependencies = [ "criterion", "getrandom 0.3.4", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 2ebb2c4..5d61624 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.0.17-rc1" +version = "1.0.17" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" @@ -26,7 +26,8 @@ harness = false [features] default = ["wasi", "mrubyedge-debug"] -wasi = ["dep:getrandom"] +wasi = [] mruby-regexp = ["dep:regex"] mrubyedge-debug = ["wasi"] +mruby-random = ["wasi", "dep:getrandom"] no-wasi = [] From b323b77fcb83a377171b154513d0c97f3488c713 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 11 Jan 2026 16:46:53 +0900 Subject: [PATCH 142/314] Bump --- Cargo.lock | 15 +++++++-------- mrubyedge-cli/Cargo.toml | 4 ++-- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 29df417..6da0dc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -565,11 +565,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2643719054e1018cd821d13d4382da0f4f2617d341e39d274806c5a3278c936" +version = "1.0.17" dependencies = [ + "criterion", "getrandom 0.3.4", + "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -578,10 +578,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46975f4a96c600821d0a16fd7f78f8f9e4ef277717ec65957aa7b8c4abec7dd7" dependencies = [ - "criterion", - "getrandom 0.3.4", - "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -589,12 +588,12 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.0.16" +version = "1.0.17" dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.16", + "mrubyedge 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 608017d..93c877a 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.0.16" +version = "1.0.17" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } mruby-compiler2-sys = "0.2.2" -mrubyedge = { version = "1.0.16", features = ["default", "mruby-regexp"] } +mrubyedge = { version = "1.0.17", features = ["default", "mruby-regexp"] } rand = "0.8.5" nom = "7.1.3" askama = "0.12.1" From 0025b69e9e7676c8df834884dfc3a30fb5d05a5f Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 11 Jan 2026 17:13:47 +0900 Subject: [PATCH 143/314] Fix into result for shared_memory --- mrubyedge/src/yamrb/shared_memory.rs | 3 +++ mrubyedge/src/yamrb/value.rs | 5 +---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mrubyedge/src/yamrb/shared_memory.rs b/mrubyedge/src/yamrb/shared_memory.rs index 4b2b6d6..9205c7b 100644 --- a/mrubyedge/src/yamrb/shared_memory.rs +++ b/mrubyedge/src/yamrb/shared_memory.rs @@ -33,6 +33,9 @@ impl SharedMemory { self.memory[offset] } + /// HINT: SharedMemory's memory region cannot be cleared when the reference count drops to zero. + /// Even if it leaks, Cloudflare Workers deallocate memory on a per-Wasm instance basis + /// every time the request is done; so it does not cause memory leaks. pub fn leak(&mut self) -> *mut u8 { let data = self.memory.as_ref().to_vec(); let prt = Vec::leak(data); diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index f98fc44..46dff0a 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -743,12 +743,9 @@ impl TryFrom<&RObject> for () { impl TryFrom<&RObject> for *mut u8 { type Error = Error; - /// HINT: SharedMemory's memory region cannot be cleared when the reference count drops to zero. - /// Even if it leaks, Cloudflare Workers deallocate memory on a per-Wasm instance basis - /// every time the request is done; so it does not cause memory leaks. fn try_from(value: &RObject) -> Result { match &value.value { - RValue::SharedMemory(sm) => Ok(sm.borrow_mut().leak()), + RValue::SharedMemory(sm) => Ok(sm.borrow_mut().as_mut_ptr()), _ => Err(Error::TypeMismatch), } } From 47323fbf018bbd02e45079718f14004d835ada44 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 11 Jan 2026 17:20:53 +0900 Subject: [PATCH 144/314] Bump again... --- Cargo.lock | 14 +++++++------- mrubyedge/Cargo.toml | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6da0dc5..379ed08 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -566,10 +566,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46975f4a96c600821d0a16fd7f78f8f9e4ef277717ec65957aa7b8c4abec7dd7" dependencies = [ - "criterion", - "getrandom 0.3.4", - "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -577,10 +576,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46975f4a96c600821d0a16fd7f78f8f9e4ef277717ec65957aa7b8c4abec7dd7" +version = "1.0.18" dependencies = [ + "criterion", + "getrandom 0.3.4", + "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -593,7 +593,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.0.17", "nom", "rand", "syn", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 5d61624..d765a22 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.0.17" +version = "1.0.18" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From a1705377060949121760016f4bd0eeacded75f00 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 11 Jan 2026 17:21:24 +0900 Subject: [PATCH 145/314] Bump... --- Cargo.lock | 16 ++++++++-------- mrubyedge-cli/Cargo.toml | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 379ed08..ca1e950 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -565,10 +565,11 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46975f4a96c600821d0a16fd7f78f8f9e4ef277717ec65957aa7b8c4abec7dd7" +version = "1.0.18" dependencies = [ + "criterion", + "getrandom 0.3.4", + "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -577,10 +578,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "557cc8dd1929612cab34a71fafc6aa68114a56147465484bf09eeb228cafa005" dependencies = [ - "criterion", - "getrandom 0.3.4", - "mec-mrbc-sys", "plain", "regex", "simple_endian", @@ -588,12 +588,12 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.0.17" +version = "1.0.18" dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.17", + "mrubyedge 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 93c877a..d3f200d 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.0.17" +version = "1.0.18" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } mruby-compiler2-sys = "0.2.2" -mrubyedge = { version = "1.0.17", features = ["default", "mruby-regexp"] } +mrubyedge = { version = "1.0.18", features = ["default", "mruby-regexp"] } rand = "0.8.5" nom = "7.1.3" askama = "0.12.1" From d3e9e1df2600ffc679d6f7d4c7f2119dd4b1ffe4 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 13 Jan 2026 22:47:16 +0900 Subject: [PATCH 146/314] Introduce RHashMap and RHashSet wrappers for hashbrown types --- Cargo.lock | 8 + mrubyedge/Cargo.toml | 10 +- mrubyedge/benches/hashmap_comparison.rs | 200 +++++++++++++++++++ mrubyedge/src/yamrb/optable.rs | 14 +- mrubyedge/src/yamrb/prelude/hash.rs | 15 +- mrubyedge/src/yamrb/prelude/object.rs | 17 +- mrubyedge/src/yamrb/prelude/regexp.rs | 11 +- mrubyedge/src/yamrb/prelude/shared_memory.rs | 5 +- mrubyedge/src/yamrb/value.rs | 68 ++++--- mrubyedge/src/yamrb/vm.rs | 34 ++-- 10 files changed, 300 insertions(+), 82 deletions(-) create mode 100644 mrubyedge/benches/hashmap_comparison.rs diff --git a/Cargo.lock b/Cargo.lock index ca1e950..741d4d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -372,6 +372,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "getrandom" version = "0.2.16" @@ -568,8 +574,10 @@ name = "mrubyedge" version = "1.0.18" dependencies = [ "criterion", + "fnv", "getrandom 0.3.4", "mec-mrbc-sys", + "once_cell", "plain", "regex", "simple_endian", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index d765a22..539e292 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -9,6 +9,7 @@ license = "BSD-3-Clause" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +fnv = { version = "1.0.7", optional = true } getrandom = { version = "0.3.4", optional = true } plain = "0.2.3" regex = { version = "1.12.2", default-features = false, features = [ @@ -19,14 +20,21 @@ simple_endian = "0.3.3" [dev-dependencies] criterion = "0.5.1" mec-mrbc-sys = "3.3.1" +fnv = "1.0.7" +once_cell = "1.20.2" + +# [[bench]] +# name = "benchmark" +# harness = false [[bench]] -name = "benchmark" +name = "hashmap_comparison" harness = false [features] default = ["wasi", "mrubyedge-debug"] wasi = [] +mruby-hash-fnv = ["dep:fnv"] mruby-regexp = ["dep:regex"] mrubyedge-debug = ["wasi"] mruby-random = ["wasi", "dep:getrandom"] diff --git a/mrubyedge/benches/hashmap_comparison.rs b/mrubyedge/benches/hashmap_comparison.rs new file mode 100644 index 0000000..a754bc6 --- /dev/null +++ b/mrubyedge/benches/hashmap_comparison.rs @@ -0,0 +1,200 @@ +use criterion::{Criterion, black_box, criterion_group, criterion_main}; +use fnv::FnvHashMap; +use once_cell::sync::Lazy; +use std::collections::HashMap; + +// 事前に生成された静的な文字列を使用 +macro_rules! generate_static_keys { + ($name:ident, $len:expr, $count:expr) => { + static $name: Lazy> = Lazy::new(|| { + (0..$count) + .map(|i| { + let base = format!("k{}", i); + if base.len() >= $len { + base.chars().take($len).collect() + } else { + format!("{:0> = Lazy::new(|| (0..10000).map(|i| format!("v{}", i)).collect()); + +// 1 char keys +fn bench_default_1char(c: &mut Criterion) { + c.bench_function("default HashMap 10000 entries (1 char keys)", |b| { + b.iter(|| { + let mut map = HashMap::new(); + for i in 0..10000 { + map.insert( + black_box(KEYS_1CHAR[i].clone()), + black_box(VALUES[i].clone()), + ); + } + map + }) + }); +} + +fn bench_fnv_1char(c: &mut Criterion) { + c.bench_function("FNV HashMap 10000 entries (1 char keys)", |b| { + b.iter(|| { + let mut map = FnvHashMap::default(); + for i in 0..10000 { + map.insert( + black_box(KEYS_1CHAR[i].clone()), + black_box(VALUES[i].clone()), + ); + } + map + }) + }); +} + +// 5 char keys +fn bench_default_5char(c: &mut Criterion) { + c.bench_function("default HashMap 10000 entries (5 char keys)", |b| { + b.iter(|| { + let mut map = HashMap::new(); + for i in 0..10000 { + map.insert( + black_box(KEYS_5CHAR[i].clone()), + black_box(VALUES[i].clone()), + ); + } + map + }) + }); +} + +fn bench_fnv_5char(c: &mut Criterion) { + c.bench_function("FNV HashMap 10000 entries (5 char keys)", |b| { + b.iter(|| { + let mut map = FnvHashMap::default(); + for i in 0..10000 { + map.insert( + black_box(KEYS_5CHAR[i].clone()), + black_box(VALUES[i].clone()), + ); + } + map + }) + }); +} + +// 10 char keys +fn bench_default_10char(c: &mut Criterion) { + c.bench_function("default HashMap 10000 entries (10 char keys)", |b| { + b.iter(|| { + let mut map = HashMap::new(); + for i in 0..10000 { + map.insert( + black_box(KEYS_10CHAR[i].clone()), + black_box(VALUES[i].clone()), + ); + } + map + }) + }); +} + +fn bench_fnv_10char(c: &mut Criterion) { + c.bench_function("FNV HashMap 10000 entries (10 char keys)", |b| { + b.iter(|| { + let mut map = FnvHashMap::default(); + for i in 0..10000 { + map.insert( + black_box(KEYS_10CHAR[i].clone()), + black_box(VALUES[i].clone()), + ); + } + map + }) + }); +} + +// 20 char keys +fn bench_default_20char(c: &mut Criterion) { + c.bench_function("default HashMap 10000 entries (20 char keys)", |b| { + b.iter(|| { + let mut map = HashMap::new(); + for i in 0..10000 { + map.insert( + black_box(KEYS_20CHAR[i].clone()), + black_box(VALUES[i].clone()), + ); + } + map + }) + }); +} + +fn bench_fnv_20char(c: &mut Criterion) { + c.bench_function("FNV HashMap 10000 entries (20 char keys)", |b| { + b.iter(|| { + let mut map = FnvHashMap::default(); + for i in 0..10000 { + map.insert( + black_box(KEYS_20CHAR[i].clone()), + black_box(VALUES[i].clone()), + ); + } + map + }) + }); +} + +// 50 char keys +fn bench_default_50char(c: &mut Criterion) { + c.bench_function("default HashMap 10000 entries (50 char keys)", |b| { + b.iter(|| { + let mut map = HashMap::new(); + for i in 0..10000 { + map.insert( + black_box(KEYS_50CHAR[i].clone()), + black_box(VALUES[i].clone()), + ); + } + map + }) + }); +} + +fn bench_fnv_50char(c: &mut Criterion) { + c.bench_function("FNV HashMap 10000 entries (50 char keys)", |b| { + b.iter(|| { + let mut map = FnvHashMap::default(); + for i in 0..10000 { + map.insert( + black_box(KEYS_50CHAR[i].clone()), + black_box(VALUES[i].clone()), + ); + } + map + }) + }); +} + +criterion_group!( + benches, + bench_fnv_1char, + bench_default_1char, + bench_fnv_5char, + bench_default_5char, + bench_fnv_10char, + bench_default_10char, + bench_fnv_20char, + bench_default_20char, + bench_fnv_50char, + bench_default_50char, +); +criterion_main!(benches); diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 52366ee..69d0ba0 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1,11 +1,11 @@ use std::cell::Cell; use std::cell::RefCell; -use std::collections::HashMap; use std::rc::Rc; use crate::Error; use crate::rite::insn::{Fetched, OpCode}; +use crate::yamrb::value::RHashMap; use super::prelude::object::mrb_object_is_equal; use super::{helpers::mrb_funcall, value::*, vm::*}; @@ -921,7 +921,7 @@ pub(crate) fn do_op_send( }) .collect::>(); - let mut map = HashMap::new(); + let mut map = RHashMap::default(); for i in 0..k { let key = vm .get_current_regs_cloned(a as usize + n + i * 2 + 1)? @@ -1015,7 +1015,7 @@ fn kwarg_op_enter(vm: &mut VM) { } } else { KArgs { - args: RefCell::new(HashMap::new()), + args: RefCell::new(RHashMap::default()), upper: None, } }; @@ -1642,7 +1642,7 @@ pub(crate) fn op_hash(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; let a = a as usize; let b = b as usize; - let mut hash = HashMap::new(); + let mut hash = RHashMap::default(); for i in 0..b { let key = vm.get_current_regs_cloned(a + i * 2)?; let val = vm.get_current_regs_cloned(a + i * 2 + 1)?; @@ -1682,7 +1682,7 @@ pub(crate) fn op_lambda(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { }), object_id: u64::MAX.into(), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), }; vm.current_regs()[a as usize].replace(val.to_refcount_assigned()); Ok(()) @@ -1715,7 +1715,7 @@ pub(crate) fn op_block(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { }), object_id: u64::MAX.into(), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), }; vm.current_regs()[a as usize].replace(val.to_refcount_assigned()); Ok(()) @@ -1737,7 +1737,7 @@ pub(crate) fn op_method(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { }), object_id: u64::MAX.into(), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), }; vm.current_regs()[a as usize].replace(val.to_refcount_assigned()); Ok(()) diff --git a/mrubyedge/src/yamrb/prelude/hash.rs b/mrubyedge/src/yamrb/prelude/hash.rs index 7053419..8d6ad79 100644 --- a/mrubyedge/src/yamrb/prelude/hash.rs +++ b/mrubyedge/src/yamrb/prelude/hash.rs @@ -1,10 +1,10 @@ -use std::{collections::HashMap, rc::Rc}; +use std::rc::Rc; use crate::{ Error, yamrb::{ helpers::{mrb_call_block, mrb_call_inspect, mrb_define_class_cmethod, mrb_define_cmethod}, - value::{RObject, RValue}, + value::{RHashMap, RObject, RValue}, vm::VM, }, }; @@ -38,7 +38,7 @@ pub(crate) fn initialize_hash(vm: &mut VM) { } pub fn mrb_hash_new(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { - Ok(Rc::new(RObject::hash(HashMap::new()))) + Ok(Rc::new(RObject::hash(RHashMap::default()))) } fn mrb_hash_get_index_self(vm: &mut VM, args: &[Rc]) -> Result, Error> { @@ -140,11 +140,10 @@ fn test_hashing() { #[test] fn test_mrb_hash_set_and_index() { use crate::yamrb::*; - use std::collections::HashMap; let mut vm = VM::empty(); prelude::prelude(&mut vm); - let hash = Rc::new(RObject::hash(HashMap::new())); + let hash = Rc::new(RObject::hash(RHashMap::default())); let keys = [ Rc::new(RObject::string("key".to_string())), Rc::new(RObject::integer(1234)), @@ -175,11 +174,10 @@ fn test_mrb_hash_set_and_index() { #[test] fn test_mrb_hash_set_and_index_not_found() { use crate::yamrb::*; - use std::collections::HashMap; let mut vm = VM::empty(); prelude::prelude(&mut vm); - let hash = Rc::new(RObject::hash(HashMap::new())); + let hash = Rc::new(RObject::hash(RHashMap::default())); let key = Rc::new(RObject::string("key".to_string())); let value = Rc::new(RObject::integer(42)); @@ -207,10 +205,9 @@ fn mrb_hash_size(vm: &mut VM, _args: &[Rc]) -> Result, Erro #[test] fn test_mrb_hash_size() { - use std::collections::HashMap; let mut vm = VM::empty(); - let hash = Rc::new(RObject::hash(HashMap::new())); + let hash = Rc::new(RObject::hash(RHashMap::default())); let key = Rc::new(RObject::string("key".to_string())); let value = Rc::new(RObject::integer(42)); vm.current_regs()[0].replace(hash.clone()); diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index e69d188..63f9589 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -505,19 +505,18 @@ fn test_mrb_object_is_equal_array() { #[test] fn test_mrb_object_is_equal_hash() { use crate::yamrb::prelude::hash::*; - use std::collections::HashMap; let mut vm = VM::empty(); - let lhs = RObject::hash(HashMap::new()).to_refcount_assigned(); - let rhs = RObject::hash(HashMap::new()).to_refcount_assigned(); + let lhs = RObject::hash(RHashMap::default()).to_refcount_assigned(); + let rhs = RObject::hash(RHashMap::default()).to_refcount_assigned(); let ret: bool = mrb_object_is_equal(&mut vm, lhs, rhs) .as_ref() .try_into() .expect("must return bool"); assert!(ret); - let lhs = RObject::hash(HashMap::new()).to_refcount_assigned(); + let lhs = RObject::hash(RHashMap::default()).to_refcount_assigned(); mrb_hash_set_index( lhs.clone(), RObject::symbol("key1".into()).to_refcount_assigned(), @@ -531,7 +530,7 @@ fn test_mrb_object_is_equal_hash() { ) .expect("set index failed"); - let rhs = RObject::hash(HashMap::new()).to_refcount_assigned(); + let rhs = RObject::hash(RHashMap::default()).to_refcount_assigned(); mrb_hash_set_index( rhs.clone(), RObject::symbol("key2".into()).to_refcount_assigned(), @@ -551,7 +550,7 @@ fn test_mrb_object_is_equal_hash() { .expect("must return bool"); assert!(ret); - let lhs = RObject::hash(HashMap::new()).to_refcount_assigned(); + let lhs = RObject::hash(RHashMap::default()).to_refcount_assigned(); mrb_hash_set_index( lhs.clone(), RObject::symbol("key1".into()).to_refcount_assigned(), @@ -565,7 +564,7 @@ fn test_mrb_object_is_equal_hash() { ) .expect("set index failed"); - let rhs = RObject::hash(HashMap::new()).to_refcount_assigned(); + let rhs = RObject::hash(RHashMap::default()).to_refcount_assigned(); mrb_hash_set_index( rhs.clone(), RObject::symbol("key2".into()).to_refcount_assigned(), @@ -585,7 +584,7 @@ fn test_mrb_object_is_equal_hash() { .expect("must return bool"); assert!(!ret); - let lhs = RObject::hash(HashMap::new()).to_refcount_assigned(); + let lhs = RObject::hash(RHashMap::default()).to_refcount_assigned(); mrb_hash_set_index( lhs.clone(), RObject::symbol("key1".into()).to_refcount_assigned(), @@ -599,7 +598,7 @@ fn test_mrb_object_is_equal_hash() { ) .expect("set index failed"); - let rhs = RObject::hash(HashMap::new()).to_refcount_assigned(); + let rhs = RObject::hash(RHashMap::default()).to_refcount_assigned(); mrb_hash_set_index( rhs.clone(), RObject::symbol("key2".into()).to_refcount_assigned(), diff --git a/mrubyedge/src/yamrb/prelude/regexp.rs b/mrubyedge/src/yamrb/prelude/regexp.rs index 2123f6e..497f5c5 100644 --- a/mrubyedge/src/yamrb/prelude/regexp.rs +++ b/mrubyedge/src/yamrb/prelude/regexp.rs @@ -1,9 +1,6 @@ #![cfg(feature = "mruby-regexp")] +use std::cell::{Cell, RefCell}; use std::rc::Rc; -use std::{ - cell::{Cell, RefCell}, - collections::HashMap, -}; use regex::Regex; @@ -11,7 +8,7 @@ use crate::{ Error, yamrb::{ helpers::{mrb_define_class_cmethod, mrb_define_cmethod, mrb_funcall}, - value::{RData, RObject, RValue}, + value::{RData, RHashMap, RObject, RValue}, vm::VM, }, }; @@ -131,7 +128,7 @@ pub fn mrb_regexp_new(vm: &mut VM, args: &[Rc]) -> Result, value: RValue::Data(regexp_data), object_id: Cell::new(0), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), } .to_refcount_assigned()) } @@ -216,7 +213,7 @@ fn mrb_regexp_match(vm: &mut VM, args: &[Rc]) -> Result, Er value: RValue::Data(matchdata_data), object_id: Cell::new(0), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), } .to_refcount_assigned()) } diff --git a/mrubyedge/src/yamrb/prelude/shared_memory.rs b/mrubyedge/src/yamrb/prelude/shared_memory.rs index 9c6358d..3eb4aed 100644 --- a/mrubyedge/src/yamrb/prelude/shared_memory.rs +++ b/mrubyedge/src/yamrb/prelude/shared_memory.rs @@ -1,5 +1,4 @@ use std::cell::RefCell; -use std::collections::HashMap; use std::rc::Rc; use crate::yamrb::helpers::mrb_define_class_cmethod; @@ -9,7 +8,7 @@ use crate::{ Error, yamrb::{ helpers::mrb_define_cmethod, - value::{RObject, RType, RValue}, + value::{RHashMap, RObject, RType, RValue}, }, }; @@ -74,7 +73,7 @@ pub fn mrb_shared_memory_new(_vm: &mut VM, args: &[Rc]) -> Result, Rc)>; +#[cfg(feature = "mruby-hash-fnv")] +pub type RHashMap = fnv::FnvHashMap; +#[cfg(feature = "mruby-hash-fnv")] +pub type RHashSet = fnv::FnvHashSet; +#[cfg(feature = "mruby-hash-fnv")] +pub type RHash = fnv::FnvHashMap, Rc)>; +#[cfg(not(feature = "mruby-hash-fnv"))] +pub type RHashMap = std::collections::HashMap; +#[cfg(not(feature = "mruby-hash-fnv"))] +pub type RHashSet = std::collections::HashSet; +#[cfg(not(feature = "mruby-hash-fnv"))] +pub type RHash = std::collections::HashMap, Rc)>; /// Actual storage for Ruby values, including boxed objects and immediates. #[derive(Debug, Clone)] @@ -84,7 +94,7 @@ pub enum ValueEquality { /// Key-value specific equality helper storing both keys and resolved values. #[derive(Debug, Clone)] -pub struct ValueEqualityForKeyValue(HashSet, HashMap); +pub struct ValueEqualityForKeyValue(RHashSet, RHashMap); impl PartialEq for ValueEqualityForKeyValue { fn eq(&self, other: &Self) -> bool { @@ -109,7 +119,7 @@ pub struct RObject { pub singleton_class: RefCell>>, - pub ivar: RefCell>>, + pub ivar: RefCell>>, } const UNSET_OBJECT_ID: u64 = u64::MAX; @@ -121,7 +131,7 @@ impl RObject { value: RValue::Nil, object_id: 4.into(), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), } } @@ -131,7 +141,7 @@ impl RObject { value: RValue::Bool(b), object_id: (if b { 20 } else { 0 }).into(), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), } } @@ -141,7 +151,7 @@ impl RObject { value: RValue::Symbol(sym), object_id: 2.into(), // TODO: calc the same id for the same symbol singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), } } @@ -157,7 +167,7 @@ impl RObject { value: RValue::Integer(n), object_id: object_id.into(), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), } } @@ -167,7 +177,7 @@ impl RObject { value: RValue::Float(f), object_id: f.to_bits().into(), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), } } @@ -177,7 +187,7 @@ impl RObject { value: RValue::String(RefCell::new(s.into_bytes())), object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), } } @@ -187,7 +197,7 @@ impl RObject { value: RValue::String(RefCell::new(v)), object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), } } @@ -197,17 +207,17 @@ impl RObject { value: RValue::Array(RefCell::new(v)), object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), } } - pub fn hash(h: HashMap, Rc)>) -> Self { + pub fn hash(h: RHash) -> Self { RObject { tt: RType::Hash, value: RValue::Hash(RefCell::new(h)), object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), } } @@ -217,7 +227,7 @@ impl RObject { value: RValue::Range(start, end, exclusive), object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), } } @@ -238,7 +248,7 @@ impl RObject { value: RValue::Class(c), object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), } .to_refcount_assigned() } @@ -254,7 +264,7 @@ impl RObject { value: RValue::Module(m), object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), } } @@ -280,7 +290,7 @@ impl RObject { }), object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), } } @@ -290,7 +300,7 @@ impl RObject { value: RValue::Exception(e), object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), } } @@ -368,7 +378,7 @@ impl RObject { ValueEquality::Array(arr) } RValue::Hash(ha) => { - let keys: HashSet<_> = ha.borrow().keys().cloned().collect(); + let keys: RHashSet<_> = ha.borrow().keys().cloned().collect(); ValueEquality::KeyValue(ValueEqualityForKeyValue( keys, ha.borrow() @@ -761,8 +771,8 @@ impl PartialEq for RObject { #[derive(Debug, Clone)] pub struct RModule { pub sym_id: RSym, - pub procs: RefCell>, - pub consts: RefCell>>, + pub procs: RefCell>, + pub consts: RefCell>>, pub mixed_in_modules: RefCell>>, pub parent: RefCell>>, @@ -774,8 +784,8 @@ impl RModule { let name = name.to_string(); RModule { sym_id: RSym::new(name), - procs: RefCell::new(HashMap::new()), - consts: RefCell::new(HashMap::new()), + procs: RefCell::new(RHashMap::default()), + consts: RefCell::new(RHashMap::default()), mixed_in_modules: RefCell::new(Vec::new()), parent: RefCell::new(None), underlying: RefCell::new(None), @@ -842,7 +852,7 @@ impl AsModule for Rc { fn collect_module_chain( module: &Rc, chain: &mut Vec>, - visited: &mut HashSet, + visited: &mut RHashSet, ) { let key = Rc::as_ptr(module) as usize; if !visited.insert(key) { @@ -939,7 +949,7 @@ impl RClass { fn collect_class_chain( class: &Rc, chain: &mut Vec>, - visited: &mut HashSet, + visited: &mut RHashSet, ) { collect_module_chain(&class.module, chain, visited); if let Some(super_class) = &class.super_class { @@ -949,14 +959,14 @@ fn collect_class_chain( pub(crate) fn build_lookup_chain(class: &Rc) -> Vec> { let mut chain = Vec::new(); - let mut visited = HashSet::new(); + let mut visited = RHashSet::default(); collect_class_chain(class, &mut chain, &mut visited); chain } pub(crate) fn build_module_lookup_chain(module: &Rc) -> Vec> { let mut chain = Vec::new(); - let mut visited = HashSet::new(); + let mut visited = RHashSet::default(); collect_module_chain(module, &mut chain, &mut visited); chain } diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 2cd07d3..9198521 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -1,5 +1,4 @@ use std::cell::{Cell, RefCell}; -use std::collections::HashMap; use std::env; use std::rc::Rc; @@ -8,6 +7,7 @@ use crate::rite::{Irep, Rite, insn}; use super::op::Op; use super::prelude::prelude; +use super::value::RHashMap; use super::value::*; use super::{op, optable::*}; @@ -41,7 +41,7 @@ pub struct Breadcrumb { #[derive(Debug)] pub struct KArgs { - pub args: RefCell>>, + pub args: RefCell>>, pub upper: Option>, } @@ -76,7 +76,7 @@ pub struct VM { pub current_regs_offset: usize, pub current_callinfo: Option>, pub current_breadcrumb: Option>, - pub kargs: RefCell>>>, + pub kargs: RefCell>>>, pub current_kargs: RefCell>>, pub target_class: TargetContext, pub exception: Option>, @@ -85,16 +85,16 @@ pub struct VM { // common class pub object_class: Rc, - pub builtin_class_table: HashMap<&'static str, Rc>, - pub class_object_table: HashMap>, + pub builtin_class_table: RHashMap<&'static str, Rc>, + pub class_object_table: RHashMap>, - pub globals: HashMap>, - pub consts: HashMap>, + pub globals: RHashMap>, + pub consts: RHashMap>, pub upper: Option>, // TODO: using fixed array? - pub cur_env: HashMap>, - pub has_env_ref: HashMap, + pub cur_env: RHashMap>, + pub has_env_ref: RHashMap, pub fn_table: Vec>, } @@ -135,10 +135,10 @@ impl VM { /// tables and runs the prelude to seed standard classes. pub fn new_by_raw_irep(irep: IREP) -> VM { let irep = Rc::new(irep); - let globals = HashMap::new(); - let consts = HashMap::new(); - let builtin_class_table = HashMap::new(); - let class_object_table = HashMap::new(); + let globals = RHashMap::default(); + let consts = RHashMap::default(); + let builtin_class_table = RHashMap::default(); + let class_object_table = RHashMap::default(); let object_class = Rc::new(RClass::new("Object", None, None)); object_class.update_module_weakref(); @@ -163,8 +163,8 @@ impl VM { let flag_preemption = Cell::new(false); let fn_table = Vec::new(); let upper = None; - let cur_env = HashMap::new(); - let has_env_ref = HashMap::new(); + let cur_env = RHashMap::default(); + let has_env_ref = RHashMap::default(); let mut vm = VM { id, @@ -236,7 +236,7 @@ impl VM { }), object_id: 0.into(), singleton_class: RefCell::new(None), - ivar: RefCell::new(HashMap::new()), + ivar: RefCell::new(RHashMap::default()), } .to_refcount_assigned(); if self.current_regs()[0].is_none() { @@ -389,7 +389,7 @@ impl VM { .expect("self is not assigned") } - pub fn get_kwargs(&self) -> Option>> { + pub fn get_kwargs(&self) -> Option>> { let kwargs = self.current_kargs.borrow().clone(); kwargs.map(|kargs| { kargs From 1f6a7e3287831221bb62397e182fc71a7553c470 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 13 Jan 2026 22:50:13 +0900 Subject: [PATCH 147/314] Activate fnv on CI --- .github/workflows/mrubyedge.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/mrubyedge.yml b/.github/workflows/mrubyedge.yml index bb6e729..5d0e529 100644 --- a/.github/workflows/mrubyedge.yml +++ b/.github/workflows/mrubyedge.yml @@ -20,6 +20,7 @@ jobs: strategy: matrix: BUILD_TARGET: [release] # refers to a cargo profile + ENABLE_FNV_HASH: ["", ",mruby-hash-fnv"] steps: - uses: actions/checkout@v5 - name: Cache @@ -37,12 +38,12 @@ jobs: set -e cargo fmt -p mrubyedge --check cargo fmt -p mrubyedge-cli --check - - name: Run tests for "${{ matrix.BUILD_TARGET }}" profile + - name: Run tests for "${{ matrix.BUILD_TARGET }}${{ matrix.ENABLE_FNV_HASH }}" profile run: | set +e cargo test --workspace \ --exclude mec \ - --features "mrubyedge-debug,mruby-regexp" \ + --features "mrubyedge-debug,mruby-regexp${{ matrix.ENABLE_FNV_HASH }}" \ --profile ${{ matrix.BUILD_TARGET }} TEST_RESULT=$? if [ $TEST_RESULT != 0 ]; then @@ -50,11 +51,11 @@ jobs: git diff exit $TEST_RESULT fi - - name: Build binaries for "${{ matrix.BUILD_TARGET }}" profile + - name: Build binaries for "${{ matrix.BUILD_TARGET }}${{ matrix.ENABLE_FNV_HASH }}" profile run: | cargo build --workspace \ --exclude mec \ - --features "mrubyedge-debug,mruby-regexp" \ + --features "mrubyedge-debug,mruby-regexp${{ matrix.ENABLE_FNV_HASH }}" \ --profile ${{ matrix.BUILD_TARGET }} lint: From 725e22806add9ef925b49c0e98c36fd07b35705f Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 13 Jan 2026 23:11:24 +0900 Subject: [PATCH 148/314] Add microbench ruby code --- Cargo.lock | 1 + mrubyedge/Cargo.toml | 7 +- mrubyedge/benches/benchmark.rs | 192 +++++++-------------------------- mrubyedge/benches/hash_ops.rb | 18 ++++ 4 files changed, 61 insertions(+), 157 deletions(-) create mode 100644 mrubyedge/benches/hash_ops.rb diff --git a/Cargo.lock b/Cargo.lock index 741d4d1..ada9e4d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -577,6 +577,7 @@ dependencies = [ "fnv", "getrandom 0.3.4", "mec-mrbc-sys", + "mruby-compiler2-sys", "once_cell", "plain", "regex", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 539e292..1c9ff7b 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -22,10 +22,11 @@ criterion = "0.5.1" mec-mrbc-sys = "3.3.1" fnv = "1.0.7" once_cell = "1.20.2" +mruby-compiler2-sys = "0.2.2" -# [[bench]] -# name = "benchmark" -# harness = false +[[bench]] +name = "benchmark" +harness = false [[bench]] name = "hashmap_comparison" diff --git a/mrubyedge/benches/benchmark.rs b/mrubyedge/benches/benchmark.rs index 584e152..4262cef 100644 --- a/mrubyedge/benches/benchmark.rs +++ b/mrubyedge/benches/benchmark.rs @@ -1,154 +1,38 @@ -// use criterion::{criterion_group, criterion_main, Criterion}; -// use mrubyedge::*; - -// use std::{any::Any, cell::RefCell, rc::Rc}; - -// fn bm0_load(c: &mut Criterion) { -// let bin = include_bytes!("./fib.mrb"); -// c.bench_function("Load time", |b| { -// b.iter(|| { -// let _ = mrubyedge::rite::load(bin).unwrap(); -// }) -// }); -// } - -// fn bm0_prelude(c: &mut Criterion) { -// let bin = include_bytes!("./fib.mrb"); -// let rite = mrubyedge::rite::load(bin).unwrap(); -// let mut vm = mrubyedge::vm::VM::open(rite); -// c.bench_function("Prelude time", |b| { -// b.iter(|| { -// vm.prelude().unwrap(); -// }) -// }); -// } - -// fn bm0_eval(c: &mut Criterion) { -// let bin = include_bytes!("./fib.mrb"); -// let rite = mrubyedge::rite::load(bin).unwrap(); -// let mut vm = mrubyedge::vm::VM::open(rite); -// vm.prelude().unwrap(); -// c.bench_function("Eval time", |b| { -// b.iter(|| { -// vm.eval_insn().unwrap(); -// }) -// }); -// } - -// fn bm2(c: &mut Criterion) { -// let bin = include_bytes!("./fib.mrb"); -// let rite = mrubyedge::rite::load(bin).unwrap(); -// let mut vm = mrubyedge::vm::VM::open(rite); -// vm.prelude().unwrap(); -// vm.eval_insn().unwrap(); - -// let objclass_sym = vm.target_class.unwrap() as usize; -// let top_self = vm::RObject::RInstance { -// class_index: objclass_sym, -// data: Rc::new(RefCell::new(Box::new(()) as Box)), -// }; -// let args1 = vec![Rc::new(vm::RObject::RInteger(1))]; -// let args5 = vec![Rc::new(vm::RObject::RInteger(5))]; -// // let args10 = vec![Rc::new(vm::RObject::RInteger(10))]; -// // let args15 = vec![Rc::new(vm::RObject::RInteger(15))]; -// // let args20 = vec![Rc::new(vm::RObject::RInteger(20))]; - -// c.bench_function("Fib 1", |b| { -// b.iter(|| { -// match mrb_helper::mrb_funcall(&mut vm, &top_self, "fib".to_string(), &args1) { -// Ok(_) => { -// // OK -// } -// Err(ex) => { -// dbg!(ex); -// } -// }; -// }) -// }); - -// c.bench_function("Fib 5", |b| { -// b.iter(|| { -// match mrb_helper::mrb_funcall(&mut vm, &top_self, "fib".to_string(), &args5) { -// Ok(_) => { -// // OK -// } -// Err(ex) => { -// dbg!(ex); -// } -// }; -// }) -// }); - -// // c.bench_function("Fib 10", |b| { -// // b.iter(|| { -// // match mrb_helper::mrb_funcall(&mut vm, &top_self, "fib".to_string(), &args10) { -// // Ok(_) => { -// // // OK -// // } -// // Err(ex) => { -// // dbg!(ex); -// // } -// // }; -// // }) -// // }); - -// // c.bench_function("Fib 15", |b| { -// // b.iter(|| { -// // match mrb_helper::mrb_funcall(&mut vm, &top_self, "fib".to_string(), &args15) { -// // Ok(_) => { -// // // OK -// // } -// // Err(ex) => { -// // dbg!(ex); -// // } -// // }; -// // }) -// // }); - -// // c.bench_function("Fib 20", |b| { -// // b.iter(|| { -// // match mrb_helper::mrb_funcall(&mut vm, &top_self, "fib".to_string(), &args20) { -// // Ok(_) => { -// // // OK -// // } -// // Err(ex) => { -// // dbg!(ex); -// // } -// // }; -// // }) -// // }); -// } - -// // fn bm3(c: &mut Criterion) { -// // let bin = include_bytes!("./long.mrb"); -// // let rite = mrubyedge::rite::load(bin).unwrap(); -// // let mut vm = mrubyedge::vm::VM::open(rite); -// // vm.prelude().unwrap(); -// // vm.eval_insn().unwrap(); - -// // let objclass_sym = vm.target_class.unwrap() as usize; -// // let top_self = vm::RObject::RInstance { -// // class_index: objclass_sym, -// // data: Rc::new(RefCell::new(Box::new(()) as Box)), -// // }; -// // let args = vec![]; - -// // c.bench_function("Long inst", |b| { -// // b.iter(|| { -// // match mrb_helper::mrb_funcall(&mut vm, &top_self, "long".to_string(), &args) { -// // Ok(_) => { -// // // OK -// // } -// // Err(ex) => { -// // dbg!(ex); -// // } -// // }; -// // }) -// // }); -// // } - -// // criterion_group!(benches, bm1, bm2, bm3); -// criterion_group!(benches, bm0_load, bm0_prelude, bm0_eval, bm2); -// criterion_main!(benches); - -fn main() {} +use criterion::{Criterion, black_box, criterion_group, criterion_main}; +use mrubyedge::yamrb::vm::VM; + +fn bench_hash_operations(c: &mut Criterion) { + let ruby_code = r#" + def hash_ops + h = {} + + 1000.times do |i| + j = i % 5 + h["k_hello_#{j}"] = "v_hello" + end + + 1000.times do |i| + j = i % 5 + h["k_hello_#{j}"] + end + end + + hash_ops + "#; + + let bin = unsafe { + let mut context = mruby_compiler2_sys::MRubyCompiler2Context::new(); + context.compile(ruby_code).unwrap() + }; + + c.bench_function("Hash operations (1000 inserts + 1000 gets)", |b| { + b.iter(|| { + let mut rite = mrubyedge::rite::load(&bin).unwrap(); + let mut vm = VM::open(&mut rite); + black_box(vm.run().expect("VM run failed")); + }) + }); +} + +criterion_group!(benches, bench_hash_operations); +criterion_main!(benches); diff --git a/mrubyedge/benches/hash_ops.rb b/mrubyedge/benches/hash_ops.rb new file mode 100644 index 0000000..e278075 --- /dev/null +++ b/mrubyedge/benches/hash_ops.rb @@ -0,0 +1,18 @@ +def hash_ops + h = {} + + # 100回挿入 + 100.times do |i| + h["k#{i}"] = "v#{i}" + end + + # 100回取得 + result = [] + 100.times do |i| + result << h["k#{i}"] + end + + result +end + +hash_ops From b262faf1392ca8834b8d151bea4fa4a8fdfb08a4 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 14 Jan 2026 00:11:39 +0900 Subject: [PATCH 149/314] RC1 --- Cargo.lock | 20 ++++++++++---------- mrubyedge/Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ada9e4d..1274159 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -572,13 +572,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "557cc8dd1929612cab34a71fafc6aa68114a56147465484bf09eeb228cafa005" dependencies = [ - "criterion", - "fnv", - "getrandom 0.3.4", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "regex", "simple_endian", @@ -586,10 +582,14 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "557cc8dd1929612cab34a71fafc6aa68114a56147465484bf09eeb228cafa005" +version = "1.0.19-rc1" dependencies = [ + "criterion", + "fnv", + "getrandom 0.3.4", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "regex", "simple_endian", @@ -602,7 +602,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.0.18", "nom", "rand", "syn", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 1c9ff7b..1125c7f 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.0.18" +version = "1.0.19-rc1" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From 945d7c377f00daf443e06de3f0f021c70a2db861 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sat, 31 Jan 2026 21:21:26 +0900 Subject: [PATCH 150/314] Support variable number of arguments in method calls --- mrubyedge/examples/args.rb | 5 +++-- mrubyedge/src/yamrb/optable.rs | 13 +++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/mrubyedge/examples/args.rb b/mrubyedge/examples/args.rb index b177b64..5ad1524 100644 --- a/mrubyedge/examples/args.rb +++ b/mrubyedge/examples/args.rb @@ -1,7 +1,8 @@ -def splat_it(x, *args) +def splat_it(x, y, *args) args.each do |arg| p arg end end -splat_it(10, 20, 30) \ No newline at end of file +splat_it(10, 20, 30) +splat_it(10, 20, 30, 40, 50) \ No newline at end of file diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 69d0ba0..73cdf50 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1174,6 +1174,19 @@ pub(crate) fn op_enter(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { } } } + let splat_arg = arg_info.r as usize; + if splat_arg == 1 { + let total_args = vm.current_callinfo.as_ref().map_or(0, |ci| ci.n_args); + let passed_args = total_args.saturating_sub(m1_argc); + let mut array = Vec::new(); + for i in 0..passed_args { + if let Some(val) = vm.current_regs()[m1_argc + i + 1].take() { + array.push(val); + } + } + let splat = RObject::array(array); + vm.current_regs()[m1_argc + 1].replace(splat.to_refcount_assigned()); + } kwarg_op_enter(vm); Ok(()) From 9be663f9038a84d05a2410e23c08a8f8e8f1dc04 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sat, 31 Jan 2026 21:25:05 +0900 Subject: [PATCH 151/314] Add basic tests --- mrubyedge/tests/args.rs | 64 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 mrubyedge/tests/args.rs diff --git a/mrubyedge/tests/args.rs b/mrubyedge/tests/args.rs new file mode 100644 index 0000000..5710905 --- /dev/null +++ b/mrubyedge/tests/args.rs @@ -0,0 +1,64 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn basic_splat_args_test() { + let code = " +def sum(*args) + total = 0 + args.each do |arg| + total = total + arg + end + total +end + +sum(1, 2, 3, 4, 5) + "; + let binary = mrbc_compile("basic_splat", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 15); +} + +#[test] +fn splat_args_with_regular_args_test() { + let code = " +def splat_it(x, y, *args) + total = x + y + args.each do |arg| + total = total + arg + end + total +end + +splat_it(10, 20, 30, 40, 50) + "; + let binary = mrbc_compile("splat_with_regular", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 150); +} + +#[test] +fn splat_args_empty_test() { + let code = " +def splat_it(x, y, *args) + args.size +end + +splat_it(10, 20) + "; + let binary = mrbc_compile("splat_empty", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 0); +} From 9a171de9413a4998a4ecdf9d77ca47dee635b92d Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sat, 31 Jan 2026 22:04:27 +0900 Subject: [PATCH 152/314] Support kwarg splat --- mrubyedge/examples/args3.rb | 11 ++++++ mrubyedge/src/yamrb/optable.rs | 54 ++++++++++++++++++++++++----- mrubyedge/src/yamrb/prelude/hash.rs | 29 ++++++++++++++++ mrubyedge/src/yamrb/vm.rs | 1 + 4 files changed, 87 insertions(+), 8 deletions(-) create mode 100644 mrubyedge/examples/args3.rb diff --git a/mrubyedge/examples/args3.rb b/mrubyedge/examples/args3.rb new file mode 100644 index 0000000..e9e554c --- /dev/null +++ b/mrubyedge/examples/args3.rb @@ -0,0 +1,11 @@ +def splat_it(x, y, *args, buz: -1, **kwargs) + args.each do |arg| + p arg + end + p buz: buz + p kwargs +end + +splat_it(10, 20, 30) +splat_it(10, 20, 30, 40, 50, foo: 60) +splat_it(10, 20, 30, 40, 50, foo: 60, buz: 70) \ No newline at end of file diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 73cdf50..38fb601 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -5,9 +5,10 @@ use std::rc::Rc; use crate::Error; use crate::rite::insn::{Fetched, OpCode}; -use crate::yamrb::value::RHashMap; +use super::prelude::hash::mrb_hash_delete; use super::prelude::object::mrb_object_is_equal; +use super::value::RHashMap; use super::{helpers::mrb_funcall, value::*, vm::*}; // OpCodes of mruby 3.2.0 from mruby/op.h: @@ -962,7 +963,7 @@ pub(crate) fn do_op_send( vm.current_regs()[a as usize].replace(recv.clone()); if !method.is_rb_func { - kwarg_op_enter(vm); + kwarg_op_enter(vm, 0); let func = vm .get_fn(method.func.unwrap()) @@ -1006,16 +1007,19 @@ pub(crate) fn do_op_send( Ok(()) } -fn kwarg_op_enter(vm: &mut VM) { +fn kwarg_op_enter(vm: &mut VM, rest_pos: usize) { + let kwrest_reg = Cell::new(rest_pos); let current_arg = if let Some(args) = vm.kargs.borrow_mut().take() { let upper = vm.current_kargs.borrow_mut().take(); KArgs { args: RefCell::new(args), + kwrest_reg, upper, } } else { KArgs { args: RefCell::new(RHashMap::default()), + kwrest_reg, upper: None, } }; @@ -1185,23 +1189,57 @@ pub(crate) fn op_enter(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { } } let splat = RObject::array(array); - vm.current_regs()[m1_argc + 1].replace(splat.to_refcount_assigned()); + vm.current_regs()[m1_argc + splat_arg].replace(splat.to_refcount_assigned()); + } + let kwrest_arg = arg_info.d as usize; + let kwrest_pos = if kwrest_arg == 1 { + m1_argc + splat_arg + kwrest_arg + } else { + 0 + }; + kwarg_op_enter(vm, kwrest_pos); + if kwrest_arg == 1 { + let mut map = RHashMap::default(); + for (k, v) in vm + .get_kwargs() + .ok_or_else(|| Error::RuntimeError("kwargs not defined".to_string()))? + .iter() + { + let k = RObject::symbol(RSym::new(k.clone())).to_refcount_assigned(); + map.insert(k.as_hash_key()?, (k, v.clone())); + } + + let kwrest = RObject::hash(map); + vm.current_regs()[kwrest_pos].replace(kwrest.to_refcount_assigned()); } - kwarg_op_enter(vm); Ok(()) } pub(crate) fn op_key_p(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; - let val = { - let key = vm.current_irep.syms[b as usize].clone(); + let key = vm.current_irep.syms[b as usize].clone(); + let key_robj = RObject::symbol(key.clone()).to_refcount_assigned(); + + let (val, kwrest_pos) = { let kargs = vm.current_kargs.borrow(); let kargs = kargs .as_ref() .ok_or_else(|| Error::internal("no kargs found"))?; - RObject::boolean(kargs.args.borrow().contains_key(&key)) + + let kwrest_pos = kargs.kwrest_reg.get(); + + ( + RObject::boolean(kargs.args.borrow().contains_key(&key)), + kwrest_pos, + ) }; + + if kwrest_pos != 0 { + let kwrest = vm.get_current_regs_cloned(kwrest_pos)?; + mrb_hash_delete(kwrest, key_robj)?; + } + vm.current_regs()[a as usize].replace(val.to_refcount_assigned()); Ok(()) } diff --git a/mrubyedge/src/yamrb/prelude/hash.rs b/mrubyedge/src/yamrb/prelude/hash.rs index 8d6ad79..9f4dc11 100644 --- a/mrubyedge/src/yamrb/prelude/hash.rs +++ b/mrubyedge/src/yamrb/prelude/hash.rs @@ -26,6 +26,12 @@ pub(crate) fn initialize_hash(vm: &mut VM) { "[]=", Box::new(mrb_hash_set_index_self), ); + mrb_define_cmethod( + vm, + hash_class.clone(), + "delete", + Box::new(mrb_hash_delete_self), + ); mrb_define_cmethod(vm, hash_class.clone(), "each", Box::new(mrb_hash_each)); mrb_define_cmethod(vm, hash_class.clone(), "size", Box::new(mrb_hash_size)); mrb_define_cmethod(vm, hash_class.clone(), "length", Box::new(mrb_hash_size)); @@ -89,6 +95,29 @@ pub fn mrb_hash_set_index( Ok(value.clone()) } +pub fn mrb_hash_delete(this: Rc, key: Rc) -> Result, Error> { + let hash = match &this.value { + RValue::Hash(a) => a, + _ => { + return Err(Error::RuntimeError( + "Hash#delete must called on a hash".to_string(), + )); + } + }; + let mut hash = hash.borrow_mut(); + let hashed = key.as_hash_key()?; + match hash.remove(&hashed) { + Some((_, value)) => Ok(value.clone()), + None => Ok(Rc::new(RObject::nil())), + } +} + +fn mrb_hash_delete_self(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let key = args[0].clone(); + mrb_hash_delete(this, key) +} + fn mrb_hash_each(vm: &mut VM, args: &[Rc]) -> Result, Error> { let this = vm.getself()?; let block = &args[0]; diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 9198521..6b97b8f 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -42,6 +42,7 @@ pub struct Breadcrumb { #[derive(Debug)] pub struct KArgs { pub args: RefCell>>, + pub kwrest_reg: Cell, pub upper: Option>, } From f896407fbeb8dedca84d82b4ada009193616616e Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sat, 31 Jan 2026 22:11:31 +0900 Subject: [PATCH 153/314] Add test cases for kwargs splat --- mrubyedge/src/yamrb/prelude/integer.rs | 6 ++ mrubyedge/tests/kargs.rs | 94 ++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) diff --git a/mrubyedge/src/yamrb/prelude/integer.rs b/mrubyedge/src/yamrb/prelude/integer.rs index 01aeabb..bdb53a2 100644 --- a/mrubyedge/src/yamrb/prelude/integer.rs +++ b/mrubyedge/src/yamrb/prelude/integer.rs @@ -21,6 +21,12 @@ pub(crate) fn initialize_integer(vm: &mut VM) { "inspect", Box::new(mrb_integer_inspect), ); + mrb_define_cmethod( + vm, + integer_class.clone(), + "to_s", + Box::new(mrb_integer_inspect), + ); } fn mrb_integer_inspect(vm: &mut VM, _args: &[Rc]) -> Result, Error> { diff --git a/mrubyedge/tests/kargs.rs b/mrubyedge/tests/kargs.rs index c7eb4d8..e13a645 100644 --- a/mrubyedge/tests/kargs.rs +++ b/mrubyedge/tests/kargs.rs @@ -135,3 +135,97 @@ fn keyword_args_c_definition_test() { let result_int: i32 = result.as_ref().try_into().unwrap(); assert_eq!(result_int, 7 * 3 * 11); } + +#[test] +fn keyword_splat_basic_test() { + let code = " +def process_options(**options) + options.length +end + +process_options(foo: 1, bar: 2, baz: 3) + "; + let binary = mrbc_compile("kwsplat_basic", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 3); +} + +#[test] +fn keyword_splat_empty_test() { + let code = " +def process_options(**options) + options.length +end + +process_options() + "; + let binary = mrbc_compile("kwsplat_empty", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 0); +} + +#[test] +fn keyword_splat_access_test() { + let code = " +def get_value(**options) + options[:name] +end + +get_value(name: 'Alice', age: 30) + "; + let binary = mrbc_compile("kwsplat_access", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(&result_str, "Alice"); +} + +#[test] +fn keyword_splat_with_regular_kwargs_test() { + let code = " +def configure(mode: 'default', **options) + result = mode + ':' + options.each do |key, value| + result = result + ' ' + key.to_s + '=' + value.to_s + end + result +end + +configure(mode: 'production', host: 'localhost', port: 8080) + "; + let binary = mrbc_compile("kwsplat_mixed", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_str: String = result.as_ref().try_into().unwrap(); + assert!(result_str.starts_with("production:")); + assert!(result_str.contains("host=localhost")); + assert!(result_str.contains("port=8080")); +} + +#[test] +fn keyword_splat_with_positional_and_splat_args_test() { + let code = " +def complex_method(x, *args, required: 100, **kwargs) + sum = x + required + args.each { |a| sum = sum + a * 10 } + kwargs.each { |k, v| sum = sum + v * 15 } + sum +end + +complex_method(10, 20, 30, required: 5, foo: 15, bar: 25) + "; + let binary = mrbc_compile("kwsplat_complex", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 10 + 5 + 20 * 10 + 30 * 10 + 15 * 15 + 25 * 15); // 1050 +} From 6c40cc3004c37c179758a85982ccf9eedc21b080 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sat, 31 Jan 2026 22:37:42 +0900 Subject: [PATCH 154/314] Truly support method_missing with proper arguments --- mrubyedge/examples/method_missing.rb | 8 ++++++++ mrubyedge/src/yamrb/optable.rs | 25 +++++++++++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 mrubyedge/examples/method_missing.rb diff --git a/mrubyedge/examples/method_missing.rb b/mrubyedge/examples/method_missing.rb new file mode 100644 index 0000000..cb6dedd --- /dev/null +++ b/mrubyedge/examples/method_missing.rb @@ -0,0 +1,8 @@ +class Foo + def method_missing(name, *args, **kwargs) + puts "Called #{name} with #{args.inspect}, #{kwargs.inspect}" + end +end + +foo = Foo.new +foo.bar(1, 2, 3, a: 4, b: 5) \ No newline at end of file diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 38fb601..5afa0cf 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -5,6 +5,7 @@ use std::rc::Rc; use crate::Error; use crate::rite::insn::{Fetched, OpCode}; +use crate::yamrb::helpers::mrb_call_inspect; use super::prelude::hash::mrb_hash_delete; use super::prelude::object::mrb_object_is_equal; @@ -897,7 +898,7 @@ pub(crate) fn do_op_send( b: u8, c: u8, ) -> Result<(), Error> { - let n: usize = (c & 0x0f) as usize; + let mut n: usize = (c & 0x0f) as usize; let k: usize = (c >> 4) as usize; let method_id = vm.current_irep.syms[b as usize].clone(); @@ -948,9 +949,15 @@ pub(crate) fn do_op_send( } else { recv.singleton_or_this_class(vm) }; - let (owner_module, method) = resolve_method(&klass, &method_id.name).ok_or_else(|| { - Error::NoMethodError(format!("{} for {}", method_id.name, klass.full_name())) - })?; + let (owner_module, method) = resolve_method(&klass, &method_id.name) + .or_else(|| { + unshift_method_name(vm, &method_id, a as usize, n + k * 2 + 1); + n += 1; + resolve_method(&klass, "method_missing") + }) + .ok_or_else(|| { + Error::NoMethodError(format!("{} for {}", method_id.name, klass.full_name())) + })?; let upper = vm.current_breadcrumb.take(); let new_breadcrumb = Rc::new(Breadcrumb { @@ -1007,6 +1014,16 @@ pub(crate) fn do_op_send( Ok(()) } +fn unshift_method_name(vm: &mut VM, method_id: &RSym, a: usize, total_args: usize) { + let method_name = RObject::symbol(method_id.clone()).to_refcount_assigned(); + for i in (a + 1..=a + total_args).rev() { + let val = vm.current_regs().get(i).and_then(|r| r.as_ref().cloned()); + val.as_ref().cloned().map(|v| mrb_call_inspect(vm, v)); + vm.current_regs()[i + 1].replace(val.unwrap_or_else(|| Rc::new(RObject::nil()))); + } + vm.current_regs()[a + 1].replace(method_name); +} + fn kwarg_op_enter(vm: &mut VM, rest_pos: usize) { let kwrest_reg = Cell::new(rest_pos); let current_arg = if let Some(args) = vm.kargs.borrow_mut().take() { From b5ad93bb01da79320518b8335ac449bece10c455 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sat, 31 Jan 2026 22:44:45 +0900 Subject: [PATCH 155/314] Implement Object#method_missing --- mrubyedge/examples/method_missing.rb | 7 ++++++- mrubyedge/src/yamrb/optable.rs | 17 ++++++++++++++--- mrubyedge/src/yamrb/prelude/object.rs | 18 ++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/mrubyedge/examples/method_missing.rb b/mrubyedge/examples/method_missing.rb index cb6dedd..b950b47 100644 --- a/mrubyedge/examples/method_missing.rb +++ b/mrubyedge/examples/method_missing.rb @@ -5,4 +5,9 @@ def method_missing(name, *args, **kwargs) end foo = Foo.new -foo.bar(1, 2, 3, a: 4, b: 5) \ No newline at end of file +foo.bar(1, 2, 3, a: 4, b: 5) + +class Bar; end + +bar = Bar.new +bar.baz(10, 20, x: 30) \ No newline at end of file diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 5afa0cf..5e9b19f 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -951,12 +951,16 @@ pub(crate) fn do_op_send( }; let (owner_module, method) = resolve_method(&klass, &method_id.name) .or_else(|| { - unshift_method_name(vm, &method_id, a as usize, n + k * 2 + 1); + unshift_method_name(vm, &mut args, &method_id, a as usize, n + k * 2 + 1); n += 1; resolve_method(&klass, "method_missing") }) .ok_or_else(|| { - Error::NoMethodError(format!("{} for {}", method_id.name, klass.full_name())) + Error::Internal(format!( + "[BUG] method_missing not defined. {} for {}", + method_id.name, + klass.full_name() + )) })?; let upper = vm.current_breadcrumb.take(); @@ -1014,13 +1018,20 @@ pub(crate) fn do_op_send( Ok(()) } -fn unshift_method_name(vm: &mut VM, method_id: &RSym, a: usize, total_args: usize) { +fn unshift_method_name( + vm: &mut VM, + args: &mut Vec>, + method_id: &RSym, + a: usize, + total_args: usize, +) { let method_name = RObject::symbol(method_id.clone()).to_refcount_assigned(); for i in (a + 1..=a + total_args).rev() { let val = vm.current_regs().get(i).and_then(|r| r.as_ref().cloned()); val.as_ref().cloned().map(|v| mrb_call_inspect(vm, v)); vm.current_regs()[i + 1].replace(val.unwrap_or_else(|| Rc::new(RObject::nil()))); } + args.insert(0, method_name.clone()); vm.current_regs()[a + 1].replace(method_name); } diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index 63f9589..1846398 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -97,6 +97,12 @@ pub(crate) fn initialize_object(vm: &mut VM) { "class", Box::new(mrb_object_class), ); + mrb_define_cmethod( + vm, + object_class.clone(), + "method_missing", + Box::new(mrb_object_method_missing), + ); // define global consts: vm.consts.insert( @@ -263,6 +269,18 @@ fn mrb_object_class(vm: &mut VM, _args: &[Rc]) -> Result, E Ok(RObject::class_or_module(class.as_module(), vm)) } +fn mrb_object_method_missing(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let method_name_obj = &args + .first() + .ok_or_else(|| Error::Internal("[BUG] method_missing without any args".to_string()))?; + let method_name: String = method_name_obj.as_ref().try_into()?; + Err(Error::NoMethodError(format!( + "undefined method `{}` for {}", + method_name, + vm.getself()?.get_class(vm).full_name() + ))) +} + pub fn mrb_is_a(vm: &mut VM, obj: Rc, class: impl AsModule) -> bool { let obj_class = obj.get_class(vm); let target_module = class.as_module(); From f79f94ed8ce895986e114b5fbe3ec593e1a59072 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sat, 31 Jan 2026 23:20:21 +0900 Subject: [PATCH 156/314] Add tests --- mrubyedge/tests/alias.rs | 3 +- mrubyedge/tests/method_missing.rs | 175 ++++++++++++++++++++++++++++++ mrubyedge/tests/raise_rust.rs | 2 +- 3 files changed, 178 insertions(+), 2 deletions(-) create mode 100644 mrubyedge/tests/method_missing.rs diff --git a/mrubyedge/tests/alias.rs b/mrubyedge/tests/alias.rs index da3ffa7..2eb7752 100644 --- a/mrubyedge/tests/alias.rs +++ b/mrubyedge/tests/alias.rs @@ -75,5 +75,6 @@ fn undef_test() { .as_ref() .try_into() .unwrap(); - assert!(result.contains("Method not found: sample")); + assert!(result.contains("Method not found")); + assert!(result.contains("undefined method `sample`")); } diff --git a/mrubyedge/tests/method_missing.rs b/mrubyedge/tests/method_missing.rs new file mode 100644 index 0000000..2ca40ad --- /dev/null +++ b/mrubyedge/tests/method_missing.rs @@ -0,0 +1,175 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn basic_method_missing_test() { + let code = " +class Foo + def method_missing(name, *args) + name.to_s + ':' + args.length.to_s + end +end + +foo = Foo.new +foo.bar(1, 2, 3) + "; + let binary = mrbc_compile("basic_method_missing", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(&result_str, "bar:3"); +} + +#[test] +fn method_missing_with_no_args_test() { + let code = " +class Foo + def method_missing(name) + 'missing:' + name.to_s + end +end + +foo = Foo.new +foo.unknown_method + "; + let binary = mrbc_compile("method_missing_no_args", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(&result_str, "missing:unknown_method"); +} + +#[test] +fn method_missing_return_value_test() { + let code = " +class Calculator + def method_missing(name, *args) + if name == :add + args[0] + args[1] + elsif name == :multiply + args[0] * args[1] + else + 0 + end + end +end + +calc = Calculator.new +result1 = calc.add(10, 20) +result2 = calc.multiply(5, 6) +result1 + result2 + "; + let binary = mrbc_compile("method_missing_return", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 60); // (10 + 20) + (5 * 6) = 30 + 30 +} + +#[test] +fn method_missing_with_kwargs_test() { + let code = " +class Foo + def method_missing(name, *args, **kwargs) + name.to_s + ':' + kwargs.length.to_s + end +end + +foo = Foo.new +foo.test_method(1, 2, a: 3, b: 4, c: 5) + "; + let binary = mrbc_compile("method_missing_kwargs", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(&result_str, "test_method:3"); +} + +#[test] +fn method_missing_access_kwargs_test() { + let code = " +class Config + def method_missing(name, **options) + if options[:default] + options[:default] + else + 'no default' + end + end +end + +config = Config.new +result1 = config.setting1(default: 'value1') +result2 = config.setting2() +result1 + ',' + result2 + "; + let binary = mrbc_compile("method_missing_access_kwargs", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(&result_str, "value1,no default"); +} + +#[test] +fn method_missing_multiple_calls_test() { + let code = " +class Counter + def initialize + @count = 0 + end + + def method_missing(name, *args) + @count = @count + 1 + @count + end +end + +counter = Counter.new +r1 = counter.foo() +r2 = counter.bar() +r3 = counter.baz() +r1 + r2 + r3 + "; + let binary = mrbc_compile("method_missing_multiple", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 6); // 1 + 2 + 3 +} + +#[test] +fn method_missing_not_defined_error_test() { + let code = " +class Bar +end + +def call_nonexistent + bar = Bar.new + bar.nonexistent_method +end + "; + let binary = mrbc_compile("no_method_missing", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // method_missingが定義されていないので、NoMethodErrorが発生する + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "call_nonexistent", &args).err(); + let error = result.unwrap(); + let error_msg = error.message(); + assert!( + error_msg.contains("undefined method") + && error_msg.contains("Bar") + && error_msg.contains("nonexistent_method") + ); +} diff --git a/mrubyedge/tests/raise_rust.rs b/mrubyedge/tests/raise_rust.rs index fc6acc8..262e94d 100644 --- a/mrubyedge/tests/raise_rust.rs +++ b/mrubyedge/tests/raise_rust.rs @@ -106,7 +106,7 @@ fn rust_nomethod_rescue_test() { .as_ref() .try_into() .unwrap(); - assert!(result.contains("rescued: Method not found: dummy_nomethod")); + assert!(result.contains("rescued: Method not found")); } #[test] From 6fd14716c711c281804683c5f391f7f94eb18a85 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sat, 31 Jan 2026 23:23:42 +0900 Subject: [PATCH 157/314] Bump --- Cargo.lock | 24 ++++++++++++------------ mrubyedge-cli/Cargo.toml | 4 ++-- mrubyedge/Cargo.toml | 2 +- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1274159..e02d54f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -571,10 +571,14 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "557cc8dd1929612cab34a71fafc6aa68114a56147465484bf09eeb228cafa005" +version = "1.0.19" dependencies = [ + "criterion", + "fnv", + "getrandom 0.3.4", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "regex", "simple_endian", @@ -582,14 +586,10 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.19-rc1" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def7e2ed8da8f56cbdbb257d93cf22f400aeffbaff4be906d26050149a9cdf27" dependencies = [ - "criterion", - "fnv", - "getrandom 0.3.4", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "regex", "simple_endian", @@ -597,12 +597,12 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.0.18" +version = "1.0.19" dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.18", + "mrubyedge 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index d3f200d..e676daa 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.0.18" +version = "1.0.19" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } mruby-compiler2-sys = "0.2.2" -mrubyedge = { version = "1.0.18", features = ["default", "mruby-regexp"] } +mrubyedge = { version = "1.0.19", features = ["default", "mruby-regexp"] } rand = "0.8.5" nom = "7.1.3" askama = "0.12.1" diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 1125c7f..ec80717 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.0.19-rc1" +version = "1.0.19" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From e3404907cea272c0677a27b864afa6c100289b01 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sat, 31 Jan 2026 23:55:46 +0900 Subject: [PATCH 158/314] Use uninit array for vtable... --- mrubyedge/src/lib.rs | 1 + mrubyedge/src/yamrb/helpers.rs | 4 +-- mrubyedge/src/yamrb/vm.rs | 53 +++++++++++++++++++++++++++++++--- 3 files changed, 52 insertions(+), 6 deletions(-) diff --git a/mrubyedge/src/lib.rs b/mrubyedge/src/lib.rs index ed4d7f4..47d0690 100644 --- a/mrubyedge/src/lib.rs +++ b/mrubyedge/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(maybe_uninit_uninit_array_transpose)] //! mruby/edge is a pure-Rust reimplementation of the mruby VM that keeps its //! core execution engine `no_std`-friendly while striving for behavioral //! compatibility with upstream mruby. It primarily targets WebAssembly diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index ad2ab9c..4a1bc42 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -184,7 +184,7 @@ pub fn mrb_funcall( ) } else { let prev = vm.current_regs()[0].replace(recv.clone()); - let func = vm.fn_table[method.func.unwrap()].clone(); + let func = vm.fn_table.get(method.func.unwrap()).unwrap(); let res = func(vm, args); if let Some(prev) = prev { vm.current_regs()[0].replace(prev); @@ -221,7 +221,7 @@ pub fn mrb_call_inspect(vm: &mut VM, recv: Rc) -> Result, E ) } else { let old = vm.current_regs()[0].replace(recv.clone()); - let func = vm.fn_table[method.func.unwrap()].clone(); + let func = vm.fn_table.get(method.func.unwrap()).unwrap(); let res = func(vm, &[]); if let Some(old) = old { vm.current_regs()[0].replace(old); diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 6b97b8f..f4bc031 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -1,5 +1,6 @@ use std::cell::{Cell, RefCell}; use std::env; +use std::mem::MaybeUninit; use std::rc::Rc; use crate::Error; @@ -97,7 +98,51 @@ pub struct VM { pub cur_env: RHashMap>, pub has_env_ref: RHashMap, - pub fn_table: Vec>, + pub fn_table: RFnTable, +} + +pub struct RFnTable { + pub size: Cell, + pub table: [MaybeUninit>; 4096], +} + +impl RFnTable { + #[allow(clippy::new_without_default)] + pub fn new() -> RFnTable { + RFnTable { + size: Cell::new(0), + table: MaybeUninit::uninit().transpose(), + } + } + + pub fn set(&mut self, f: Rc) { + let i = self.size.get(); + if i >= self.table.len() { + panic!("RFnTable overflow"); + } + + self.table[i].write(f); + let size = self.size.get(); + if i >= size { + self.size.set(i + 1); + } + } + + pub fn get(&self, i: usize) -> Option> { + if i >= self.size.get() { + return None; + } + + unsafe { self.table[i].assume_init_ref() }.clone().into() + } + + pub fn len(&self) -> usize { + self.size.get() + } + + pub fn is_empty(&self) -> bool { + self.size.get() == 0 + } } impl VM { @@ -162,7 +207,7 @@ impl VM { let target_class = TargetContext::Class(object_class.clone()); let exception = None; let flag_preemption = Cell::new(false); - let fn_table = Vec::new(); + let fn_table = RFnTable::new(); let upper = None; let cur_env = RHashMap::default(); let has_env_ref = RHashMap::default(); @@ -403,12 +448,12 @@ impl VM { } pub(crate) fn register_fn(&mut self, f: RFn) -> usize { - self.fn_table.push(Rc::new(f)); + self.fn_table.set(Rc::new(f)); self.fn_table.len() - 1 } pub(crate) fn get_fn(&self, i: usize) -> Option> { - self.fn_table.get(i).cloned() + self.fn_table.get(i) } /// Looks up a previously defined builtin class by name. Panics if the From 2b78f5f7e314b0a612d3dfe9ad3379e0bd775b0e Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sat, 31 Jan 2026 23:57:41 +0900 Subject: [PATCH 159/314] Replace to use from_fn --- mrubyedge/src/lib.rs | 1 - mrubyedge/src/yamrb/vm.rs | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/mrubyedge/src/lib.rs b/mrubyedge/src/lib.rs index 47d0690..ed4d7f4 100644 --- a/mrubyedge/src/lib.rs +++ b/mrubyedge/src/lib.rs @@ -1,4 +1,3 @@ -#![feature(maybe_uninit_uninit_array_transpose)] //! mruby/edge is a pure-Rust reimplementation of the mruby VM that keeps its //! core execution engine `no_std`-friendly while striving for behavioral //! compatibility with upstream mruby. It primarily targets WebAssembly diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index f4bc031..2b67a07 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -1,7 +1,7 @@ use std::cell::{Cell, RefCell}; -use std::env; use std::mem::MaybeUninit; use std::rc::Rc; +use std::{array, env}; use crate::Error; use crate::rite::{Irep, Rite, insn}; @@ -111,7 +111,7 @@ impl RFnTable { pub fn new() -> RFnTable { RFnTable { size: Cell::new(0), - table: MaybeUninit::uninit().transpose(), + table: array::from_fn(|_| MaybeUninit::uninit()), } } From 85430984c446ccf959d6832f54dd73e61b7a05b7 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 1 Feb 2026 00:18:18 +0900 Subject: [PATCH 160/314] Fix method missing call on funcall --- mrubyedge/src/yamrb/helpers.rs | 20 ++++++++++++++++---- mrubyedge/tests/method_missing.rs | 23 ++++++++++++++++++++++- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index 4a1bc42..19c3914 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -152,13 +152,25 @@ pub fn mrb_funcall( name: &str, args: &[Rc], ) -> Result, Error> { - let recv: Rc = match top_self { - Some(obj) => obj, + let recv: Rc = match &top_self { + Some(obj) => obj.clone(), None => vm.getself()?, }; let binding = recv.singleton_or_this_class(vm); - let (owner_module, method) = resolve_method(&binding, name) - .ok_or_else(|| Error::NoMethodError(format!("{} for {}", name, binding.full_name())))?; + let (owner_module, method) = match resolve_method(&binding, name) { + Some((owner, method)) => (owner, method), + None => { + if name == "method_missing" { + return Err(Error::Internal( + "[BUG] method_missing not defined".to_string(), + )); + } + + let mut mm_args = vec![Rc::new(RObject::symbol(RSym::new(name.to_string())))]; + mm_args.extend_from_slice(args); + return mrb_funcall(vm, top_self, "method_missing", &mm_args); + } + }; let upper = vm.current_breadcrumb.take(); let new_breadcrumb = Rc::new(Breadcrumb { diff --git a/mrubyedge/tests/method_missing.rs b/mrubyedge/tests/method_missing.rs index 2ca40ad..c461dcf 100644 --- a/mrubyedge/tests/method_missing.rs +++ b/mrubyedge/tests/method_missing.rs @@ -162,7 +162,6 @@ end let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); vm.run().unwrap(); - // method_missingが定義されていないので、NoMethodErrorが発生する let args = vec![]; let result = mrb_funcall(&mut vm, None, "call_nonexistent", &args).err(); let error = result.unwrap(); @@ -173,3 +172,25 @@ end && error_msg.contains("nonexistent_method") ); } + +#[test] +fn method_missing_from_funcall_test() { + let code = " +class Bar + def method_missing(name, *args) + \"handled by method_missing: #{name}\" + end +end + +Bar.new + "; + let binary = mrbc_compile("funcall_method_missing", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let target = vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, Some(target), "call_nonexistent", &args).unwrap(); + let msg: String = result.as_ref().try_into().unwrap(); + assert_eq!(msg, "handled by method_missing: call_nonexistent"); +} From 8f2602ca9e4d873f53c7799ac4ee84723a14b31c Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 1 Feb 2026 00:22:53 +0900 Subject: [PATCH 161/314] RC1 --- Cargo.lock | 20 ++++++++++---------- mrubyedge/Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e02d54f..3580556 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -572,13 +572,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "def7e2ed8da8f56cbdbb257d93cf22f400aeffbaff4be906d26050149a9cdf27" dependencies = [ - "criterion", - "fnv", - "getrandom 0.3.4", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "regex", "simple_endian", @@ -586,10 +582,14 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def7e2ed8da8f56cbdbb257d93cf22f400aeffbaff4be906d26050149a9cdf27" +version = "1.0.20-rc1" dependencies = [ + "criterion", + "fnv", + "getrandom 0.3.4", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "regex", "simple_endian", @@ -602,7 +602,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.0.19", "nom", "rand", "syn", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index ec80717..ed2ccd3 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.0.19" +version = "1.0.20-rc1" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From c63beeeb51fd2d517f6f295ac8bc582197b81f69 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 1 Feb 2026 00:32:10 +0900 Subject: [PATCH 162/314] Support MRUBYEDGE_LOCAL_CRATE_PATH --- Cargo.lock | 2 +- mrubyedge-cli/src/subcommands/wasm.rs | 6 +++++- mrubyedge/Cargo.toml | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3580556..498a850 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -582,7 +582,7 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.20-rc1" +version = "1.0.20" dependencies = [ "criterion", "fnv", diff --git a/mrubyedge-cli/src/subcommands/wasm.rs b/mrubyedge-cli/src/subcommands/wasm.rs index 8a25943..9e4fbe6 100644 --- a/mrubyedge-cli/src/subcommands/wasm.rs +++ b/mrubyedge-cli/src/subcommands/wasm.rs @@ -3,6 +3,7 @@ extern crate rand; use clap::Args; use std::{ + env, fs::{File, rename}, io::Read, path::{Path, PathBuf}, @@ -124,8 +125,11 @@ pub fn execute(args: WasmArgs) -> Result<(), Box> { .join(", "); if args.debug_mruby_edge { + let mruby_edge_crate_path = env::var("MRUBYEDGE_LOCAL_CRATE_PATH").unwrap_or_else(|_| { + "/Users/udzura/ghq/github.com/mrubyedge/mrubyedge/mrubyedge".to_string() + }); let cargo_toml = template::cargo_toml::CargoTomlDebug { - mruby_edge_crate_path: "/Users/udzura/ghq/github.com/udzura/mrubyedge/mrubyedge", + mruby_edge_crate_path: &mruby_edge_crate_path, mrubyedge_feature: &mrubyedge_feature, }; std::fs::write("Cargo.toml", cargo_toml.render()?)?; diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index ed2ccd3..6f5be33 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.0.20-rc1" +version = "1.0.20" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From f05da5fb2f7c8e5be18b873818f54197622a3092 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 1 Feb 2026 00:33:07 +0900 Subject: [PATCH 163/314] Bump version to 1.0.20 --- Cargo.lock | 22 +++++++++++----------- mrubyedge-cli/Cargo.toml | 4 ++-- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 498a850..2368e2e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -571,10 +571,14 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def7e2ed8da8f56cbdbb257d93cf22f400aeffbaff4be906d26050149a9cdf27" +version = "1.0.20" dependencies = [ + "criterion", + "fnv", + "getrandom 0.3.4", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "regex", "simple_endian", @@ -583,13 +587,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f34d536e7b90424d866f961ce38f7c21c2e8a0673e63be3539e70ac0c9e729a" dependencies = [ - "criterion", - "fnv", - "getrandom 0.3.4", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "regex", "simple_endian", @@ -597,12 +597,12 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.0.19" +version = "1.0.20" dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.19", + "mrubyedge 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index e676daa..b4bb793 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.0.19" +version = "1.0.20" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } mruby-compiler2-sys = "0.2.2" -mrubyedge = { version = "1.0.19", features = ["default", "mruby-regexp"] } +mrubyedge = { version = "1.0.20", features = ["default", "mruby-regexp"] } rand = "0.8.5" nom = "7.1.3" askama = "0.12.1" From ec728b889bb659f5275f6e35d7550351c9fd59d9 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 1 Feb 2026 17:46:35 +0900 Subject: [PATCH 164/314] Support Integer methods --- mrubyedge/src/error.rs | 4 + mrubyedge/src/yamrb/prelude/exception.rs | 1 + mrubyedge/src/yamrb/prelude/integer.rs | 160 +++++++++++++++++++++++ mrubyedge/src/yamrb/value.rs | 20 +++ 4 files changed, 185 insertions(+) diff --git a/mrubyedge/src/error.rs b/mrubyedge/src/error.rs index 6a6b66a..d440790 100644 --- a/mrubyedge/src/error.rs +++ b/mrubyedge/src/error.rs @@ -13,6 +13,7 @@ pub enum Error { InvalidOpCode, RuntimeError(String), ArgumentError(String), + RangeError(String), TypeMismatch, NoMethodError(String), NameError(String), @@ -41,6 +42,7 @@ impl Error { Error::InvalidOpCode => "Invalid opcode".to_string(), Error::RuntimeError(msg) => msg.clone(), Error::ArgumentError(msg) => format!("Invalid argument: {}", msg), + Error::RangeError(msg) => format!("Out of range: {}", msg), Error::TypeMismatch => "Type mismatch".to_string(), Error::NoMethodError(msg) => format!("Method not found: {}", msg), Error::NameError(msg) => format!("Cannot found name: {}", msg), @@ -58,6 +60,7 @@ impl Error { | (Error::InvalidOpCode, "StandardError") | (Error::RuntimeError(_), "RuntimeError") | (Error::ArgumentError(_), "ArgumentError") + | (Error::RangeError(_), "RangeError") | (Error::TypeMismatch, "StandardError") | (Error::NoMethodError(_), "NoMethodError") | (Error::NameError(_), "NameError") @@ -103,6 +106,7 @@ impl From for StaticError { Error::InvalidOpCode => StaticError::General("Invalid opcode".to_string()), Error::RuntimeError(msg) => StaticError::General(msg), Error::ArgumentError(msg) => StaticError::General(format!("Invalid argument: {}", msg)), + Error::RangeError(msg) => StaticError::General(format!("Out of range: {}", msg)), Error::TypeMismatch => StaticError::General("Type mismatch".to_string()), Error::NoMethodError(msg) => StaticError::General(format!("Method not found: {}", msg)), Error::NameError(msg) => StaticError::General(format!("Cannot found name: {}", msg)), diff --git a/mrubyedge/src/yamrb/prelude/exception.rs b/mrubyedge/src/yamrb/prelude/exception.rs index 2c2b7a9..aa17a98 100644 --- a/mrubyedge/src/yamrb/prelude/exception.rs +++ b/mrubyedge/src/yamrb/prelude/exception.rs @@ -15,6 +15,7 @@ pub(crate) fn initialize_exception(vm: &mut VM) { let _ = vm.define_standard_class_with_superclass("RuntimeError", std_exp_class.clone()); let _ = vm.define_standard_class_with_superclass("TypeError", std_exp_class.clone()); let _ = vm.define_standard_class_with_superclass("ArgumentError", std_exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("RangeError", std_exp_class.clone()); let _ = vm.define_standard_class_with_superclass("NoMemoryError", exp_class.clone()); let _ = vm.define_standard_class_with_superclass("ScriptError", exp_class.clone()); let _ = vm.define_standard_class_with_superclass("LoadError", exp_class.clone()); diff --git a/mrubyedge/src/yamrb/prelude/integer.rs b/mrubyedge/src/yamrb/prelude/integer.rs index bdb53a2..ee6fe94 100644 --- a/mrubyedge/src/yamrb/prelude/integer.rs +++ b/mrubyedge/src/yamrb/prelude/integer.rs @@ -8,7 +8,50 @@ use crate::yamrb::{helpers::mrb_call_block, value::RObject, vm::VM}; pub(crate) fn initialize_integer(vm: &mut VM) { let integer_class = vm.define_standard_class("Integer"); + mrb_define_cmethod( + vm, + integer_class.clone(), + "[]", + Box::new(mrb_integer_bitref), + ); + mrb_define_cmethod( + vm, + integer_class.clone(), + "-@", + Box::new(mrb_integer_negative), + ); + mrb_define_cmethod(vm, integer_class.clone(), "**", Box::new(mrb_integer_power)); mrb_define_cmethod(vm, integer_class.clone(), "%", Box::new(mrb_integer_mod)); + mrb_define_cmethod(vm, integer_class.clone(), "&", Box::new(mrb_integer_and)); + mrb_define_cmethod(vm, integer_class.clone(), "|", Box::new(mrb_integer_or)); + mrb_define_cmethod(vm, integer_class.clone(), "^", Box::new(mrb_integer_xor)); + mrb_define_cmethod(vm, integer_class.clone(), "~", Box::new(mrb_integer_not)); + mrb_define_cmethod( + vm, + integer_class.clone(), + "<<", + Box::new(mrb_integer_lshift), + ); + mrb_define_cmethod( + vm, + integer_class.clone(), + ">>", + Box::new(mrb_integer_rshift), + ); + mrb_define_cmethod(vm, integer_class.clone(), "abs", Box::new(mrb_integer_abs)); + mrb_define_cmethod( + vm, + integer_class.clone(), + "to_i", + Box::new(mrb_integer_to_i), + ); + mrb_define_cmethod( + vm, + integer_class.clone(), + "to_f", + Box::new(mrb_integer_to_f), + ); + mrb_define_cmethod(vm, integer_class.clone(), "chr", Box::new(mrb_integer_chr)); mrb_define_cmethod( vm, integer_class.clone(), @@ -50,3 +93,120 @@ fn mrb_integer_mod(vm: &mut VM, args: &[Rc]) -> Result, Err Ok(Rc::new(RObject::integer(lhs % rhs))) } + +fn mrb_integer_bitref(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this: i64 = vm.getself()?.as_ref().try_into()?; + let index: i64 = args[0].as_ref().try_into()?; + + if index < 0 { + return Ok(Rc::new(RObject::integer(0))); + } + + let bit = (this >> index) & 1; + Ok(Rc::new(RObject::integer(bit))) +} + +fn mrb_integer_negative(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: i64 = vm.getself()?.as_ref().try_into()?; + Ok(Rc::new(RObject::integer(-this))) +} + +fn mrb_integer_power(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let base: i64 = vm.getself()?.as_ref().try_into()?; + + // Try to get exponent as integer first, then as float + let exponent_obj = &args[0]; + let exponent_int: Result = exponent_obj.as_ref().try_into(); + let exponent_float: Result = exponent_obj.as_ref().try_into(); + + match (exponent_int, exponent_float) { + (Ok(exp), _) if exp >= 0 => { + // Positive integer exponent + let result = base.pow(exp as u32); + Ok(Rc::new(RObject::integer(result))) + } + (Ok(exp), _) if exp < 0 => { + // Negative integer exponent - return float + let result = (base as f64).powf(exp as f64); + Ok(Rc::new(RObject::float(result))) + } + (_, Ok(exp)) => { + // Float exponent - return float + let result = (base as f64).powf(exp); + Ok(Rc::new(RObject::float(result))) + } + _ => Err(Error::ArgumentError("exponent must be numeric".to_string())), + } +} + +fn mrb_integer_and(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let lhs: i64 = vm.getself()?.as_ref().try_into()?; + let rhs: i64 = args[0].as_ref().try_into()?; + Ok(Rc::new(RObject::integer(lhs & rhs))) +} + +fn mrb_integer_or(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let lhs: i64 = vm.getself()?.as_ref().try_into()?; + let rhs: i64 = args[0].as_ref().try_into()?; + Ok(Rc::new(RObject::integer(lhs | rhs))) +} + +fn mrb_integer_xor(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let lhs: i64 = vm.getself()?.as_ref().try_into()?; + let rhs: i64 = args[0].as_ref().try_into()?; + Ok(Rc::new(RObject::integer(lhs ^ rhs))) +} + +fn mrb_integer_not(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: i64 = vm.getself()?.as_ref().try_into()?; + Ok(Rc::new(RObject::integer(!this))) +} + +fn mrb_integer_lshift(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let lhs: i64 = vm.getself()?.as_ref().try_into()?; + let rhs: i64 = args[0].as_ref().try_into()?; + + if rhs < 0 { + return Err(Error::ArgumentError("negative shift count".to_string())); + } + + Ok(Rc::new(RObject::integer(lhs << rhs))) +} + +fn mrb_integer_rshift(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let lhs: i64 = vm.getself()?.as_ref().try_into()?; + let rhs: i64 = args[0].as_ref().try_into()?; + + if rhs < 0 { + return Err(Error::ArgumentError("negative shift count".to_string())); + } + + Ok(Rc::new(RObject::integer(lhs >> rhs))) +} + +fn mrb_integer_abs(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: i64 = vm.getself()?.as_ref().try_into()?; + Ok(Rc::new(RObject::integer(this.abs()))) +} + +fn mrb_integer_to_i(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + vm.getself() +} + +fn mrb_integer_to_f(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: i64 = vm.getself()?.as_ref().try_into()?; + Ok(Rc::new(RObject::float(this as f64))) +} + +fn mrb_integer_chr(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: i64 = vm.getself()?.as_ref().try_into()?; + + if !(0..=0x10FFFF).contains(&this) { + return Err(Error::RangeError(format!("{} out of char range", this))); + } + + let ch = char::from_u32(this as u32) + .ok_or_else(|| Error::RangeError(format!("invalid codepoint: {}", this)))?; + + Ok(Rc::new(RObject::string(ch.to_string()))) +} diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 270e649..519d858 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -680,6 +680,25 @@ impl TryFrom<&RObject> for f32 { } } +impl TryFrom<&RObject> for f64 { + type Error = Error; + + fn try_from(value: &RObject) -> Result { + match value.value { + RValue::Integer(i) => Ok(i as f64), + RValue::Bool(b) => { + if b { + Ok(1.0) + } else { + Ok(0.0) + } + } + RValue::Float(f) => Ok(f), + _ => Err(Error::TypeMismatch), + } + } +} + impl TryFrom<&RObject> for bool { type Error = Error; @@ -1094,6 +1113,7 @@ impl RClass { Error::InvalidOpCode => vm.get_class_by_name("LoadError"), Error::RuntimeError(_) => vm.get_class_by_name("RuntimeError"), Error::ArgumentError(_) => vm.get_class_by_name("ArgumentError"), + Error::RangeError(_) => vm.get_class_by_name("RangeError"), Error::TypeMismatch => vm.get_class_by_name("LoadError"), Error::NoMethodError(_) => vm.get_class_by_name("NoMethodError"), Error::NameError(_) => vm.get_class_by_name("NameError"), From e2e8e37d92ff342c2c2bb9e233d2c4128fd1ce59 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 1 Feb 2026 18:17:11 +0900 Subject: [PATCH 165/314] Add basic integer tests, with some refinements --- mrubyedge/src/rite/mod.rs | 1 + mrubyedge/src/rite/rite.rs | 69 +++++++- mrubyedge/src/yamrb/optable.rs | 14 +- mrubyedge/src/yamrb/prelude/float.rs | 55 ++++++ mrubyedge/src/yamrb/prelude/integer.rs | 34 ++-- mrubyedge/src/yamrb/prelude/mod.rs | 3 +- mrubyedge/src/yamrb/value.rs | 2 + mrubyedge/src/yamrb/vm.rs | 23 ++- mrubyedge/tests/integer.rs | 230 +++++++++++++++++++++++++ 9 files changed, 399 insertions(+), 32 deletions(-) create mode 100644 mrubyedge/src/yamrb/prelude/float.rs create mode 100644 mrubyedge/tests/integer.rs diff --git a/mrubyedge/src/rite/mod.rs b/mrubyedge/src/rite/mod.rs index 1d041e5..8d433e2 100644 --- a/mrubyedge/src/rite/mod.rs +++ b/mrubyedge/src/rite/mod.rs @@ -21,6 +21,7 @@ pub enum Error { TypeMismatch, InvalidOperand, NoMethod, + UnknownPoolType(u8), } impl fmt::Display for Error { diff --git a/mrubyedge/src/rite/rite.rs b/mrubyedge/src/rite/rite.rs index e8b0f12..393d6a2 100644 --- a/mrubyedge/src/rite/rite.rs +++ b/mrubyedge/src/rite/rite.rs @@ -11,6 +11,16 @@ use super::marker::*; use simple_endian::{u16be, u32be}; +#[derive(Debug, Clone)] +pub enum PoolValue { + Str(CString), // IREP_TT_STR = 0 (need free) + Int32(i32), // IREP_TT_INT32 = 1 + SStr(CString), // IREP_TT_SSTR = 2 (static) + Int64(i64), // IREP_TT_INT64 = 3 + Float(f64), // IREP_TT_FLOAT = 5 + BigInt(Vec), // IREP_TT_BIGINT = 7 (not yet fully supported) +} + #[derive(Debug, Default)] pub struct Rite<'a> { pub binary_header: RiteBinaryHeader, @@ -24,7 +34,7 @@ pub struct Irep<'a> { pub header: IrepRecord, pub insn: &'a [u8], pub plen: usize, - pub strvals: Vec, + pub pool: Vec, pub slen: usize, pub syms: Vec, pub catch_handlers: Vec, @@ -131,7 +141,7 @@ pub fn section_irep_1(head: &[u8]) -> Result<(usize, SectionIrepHeader, Vec = Vec::new(); while cur < irep_size { - let mut strvals = Vec::::new(); + let mut pool = Vec::::new(); let mut syms = Vec::::new(); let start_cur = cur; @@ -175,19 +185,66 @@ pub fn section_irep_1(head: &[u8]) -> Result<(usize, SectionIrepHeader, Vec { - cur += 1; + // IREP_TT_STR: String (need free) let data = &head[cur..cur + 2]; let strlen = be16_to_u16([data[0], data[1]]) as usize + 1; cur += 2; let strval = CStr::from_bytes_with_nul(&head[cur..cur + strlen]) .or(Err(Error::InvalidFormat))?; - strvals.push(strval.to_owned()); + pool.push(PoolValue::Str(strval.to_owned())); cur += strlen; } + 1 => { + // IREP_TT_INT32: 32-bit integer + let data = &head[cur..cur + 4]; + let mut bytes = [0u8; 4]; + bytes.copy_from_slice(data); + let intval = i32::from_be_bytes(bytes); + pool.push(PoolValue::Int32(intval)); + cur += 4; + } + 2 => { + // IREP_TT_SSTR: Static string + let data = &head[cur..cur + 2]; + let strlen = be16_to_u16([data[0], data[1]]) as usize + 1; + cur += 2; + let strval = CStr::from_bytes_with_nul(&head[cur..cur + strlen]) + .or(Err(Error::InvalidFormat))?; + pool.push(PoolValue::SStr(strval.to_owned())); + cur += strlen; + } + 3 => { + // IREP_TT_INT64: 64-bit integer + let data = &head[cur..cur + 8]; + let mut bytes = [0u8; 8]; + bytes.copy_from_slice(data); + let intval = i64::from_le_bytes(bytes); + pool.push(PoolValue::Int64(intval)); + cur += 8; + } + 5 => { + // IREP_TT_FLOAT: Float (double/float) + let data = &head[cur..cur + 8]; + let mut bytes = [0u8; 8]; + bytes.copy_from_slice(data); + let floatval = f64::from_le_bytes(bytes); + pool.push(PoolValue::Float(floatval)); + cur += 8; + } + 7 => { + // IREP_TT_BIGINT: Big integer (not yet fully supported) + let data = &head[cur..cur + 2]; + let bigint_len = be16_to_u16([data[0], data[1]]) as usize; + cur += 2; + let bigint_data = head[cur..cur + bigint_len].to_vec(); + pool.push(PoolValue::BigInt(bigint_data)); + cur += bigint_len; + } v => { - unimplemented!("require more support pool type {}", v); + return Err(Error::UnknownPoolType(v)); } } } @@ -212,7 +269,7 @@ pub fn section_irep_1(head: &[u8]) -> Result<(usize, SectionIrepHeader, Vec Result<(), E pub(crate) fn op_loadl(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; - let val = vm.current_irep.pool[b as usize].clone(); - // TODO: support other rpool types? - let val = Rc::new(RObject::string(val.as_str().to_string())); + let pool_val = vm.current_irep.pool[b as usize].clone(); + let val = match pool_val { + RPool::Str(s) => Rc::new(RObject::string(s)), + RPool::Int(i) => Rc::new(RObject::integer(i)), + RPool::Float(f) => Rc::new(RObject::float(f)), + RPool::Data(_) => { + return Err(Error::Internal( + "Binary data in pool not supported yet".to_string(), + )); + } + }; vm.current_regs()[a as usize].replace(val); Ok(()) } diff --git a/mrubyedge/src/yamrb/prelude/float.rs b/mrubyedge/src/yamrb/prelude/float.rs new file mode 100644 index 0000000..6cfc26a --- /dev/null +++ b/mrubyedge/src/yamrb/prelude/float.rs @@ -0,0 +1,55 @@ +use std::rc::Rc; + +use crate::Error; +use crate::yamrb::helpers::mrb_define_cmethod; + +use crate::yamrb::{value::RObject, vm::VM}; + +pub(crate) fn initialize_float(vm: &mut VM) { + let float_class = vm.define_standard_class("Float"); + mrb_define_cmethod(vm, float_class.clone(), "to_i", Box::new(mrb_float_to_i)); + mrb_define_cmethod(vm, float_class.clone(), "to_f", Box::new(mrb_float_to_f)); + mrb_define_cmethod( + vm, + float_class.clone(), + "inspect", + Box::new(mrb_float_inspect), + ); + mrb_define_cmethod(vm, float_class.clone(), "to_s", Box::new(mrb_float_inspect)); +} + +pub fn mrb_float_to_i(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + match &this.value { + crate::yamrb::value::RValue::Float(f) => { + let int_value = *f as i64; + Ok(Rc::new(RObject::integer(int_value))) + } + _ => Err(Error::RuntimeError( + "Float#to_i must be called on a Float".to_string(), + )), + } +} + +pub fn mrb_float_to_f(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + match &this.value { + crate::yamrb::value::RValue::Float(f) => Ok(Rc::new(RObject::float(*f))), + _ => Err(Error::RuntimeError( + "Float#to_f must be called on a Float".to_string(), + )), + } +} + +pub fn mrb_float_inspect(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + match &this.value { + crate::yamrb::value::RValue::Float(f) => { + let s = format!("{}", f); + Ok(Rc::new(RObject::string(s))) + } + _ => Err(Error::RuntimeError( + "Float#inspect must be called on a Float".to_string(), + )), + } +} diff --git a/mrubyedge/src/yamrb/prelude/integer.rs b/mrubyedge/src/yamrb/prelude/integer.rs index ee6fe94..1c98323 100644 --- a/mrubyedge/src/yamrb/prelude/integer.rs +++ b/mrubyedge/src/yamrb/prelude/integer.rs @@ -3,6 +3,7 @@ use std::rc::Rc; use crate::Error; use crate::yamrb::helpers::mrb_define_cmethod; +use crate::yamrb::value::RValue; use crate::yamrb::{helpers::mrb_call_block, value::RObject, vm::VM}; pub(crate) fn initialize_integer(vm: &mut VM) { @@ -113,29 +114,26 @@ fn mrb_integer_negative(vm: &mut VM, _args: &[Rc]) -> Result]) -> Result, Error> { let base: i64 = vm.getself()?.as_ref().try_into()?; - - // Try to get exponent as integer first, then as float let exponent_obj = &args[0]; - let exponent_int: Result = exponent_obj.as_ref().try_into(); - let exponent_float: Result = exponent_obj.as_ref().try_into(); - - match (exponent_int, exponent_float) { - (Ok(exp), _) if exp >= 0 => { - // Positive integer exponent - let result = base.pow(exp as u32); - Ok(Rc::new(RObject::integer(result))) - } - (Ok(exp), _) if exp < 0 => { - // Negative integer exponent - return float - let result = (base as f64).powf(exp as f64); - Ok(Rc::new(RObject::float(result))) + + match &exponent_obj.as_ref().value { + RValue::Integer(exp) => { + if *exp >= 0 { + // Positive integer exponent + let result = base.pow(*exp as u32); + Ok(Rc::new(RObject::integer(result))) + } else { + // Negative integer exponent - return float + let result = (base as f64).powf(*exp as f64); + Ok(Rc::new(RObject::float(result))) + } } - (_, Ok(exp)) => { + RValue::Float(exp) => { // Float exponent - return float - let result = (base as f64).powf(exp); + let result = (base as f64).powf(*exp); Ok(Rc::new(RObject::float(result))) } - _ => Err(Error::ArgumentError("exponent must be numeric".to_string())), + _ => Err(Error::TypeMismatch), } } diff --git a/mrubyedge/src/yamrb/prelude/mod.rs b/mrubyedge/src/yamrb/prelude/mod.rs index fbfc7f9..5975363 100644 --- a/mrubyedge/src/yamrb/prelude/mod.rs +++ b/mrubyedge/src/yamrb/prelude/mod.rs @@ -8,6 +8,7 @@ pub mod array; pub mod class; pub mod exception; pub mod falseclass; +pub mod float; pub mod hash; pub mod integer; pub mod module; @@ -39,7 +40,7 @@ pub fn prelude(vm: &mut VM) { hash::initialize_hash(vm); range::initialize_range(vm); shared_memory::initialize_shared_memory(vm); - + float::initialize_float(vm); #[cfg(feature = "mruby-regexp")] regexp::initialize_regexp(vm); } diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 519d858..4256ff1 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -1085,6 +1085,8 @@ impl From<&'static str> for RSym { pub enum RPool { Str(String), Data(Vec), + Int(i64), + Float(f64), } impl RPool { diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 2b67a07..6070616 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -663,10 +663,25 @@ fn load_irep_1(reps: &mut [Irep], pos: usize) -> (IREP, usize) { .syms .push(RSym::new(sym.to_string_lossy().to_string())); } - for str in irep.strvals.iter() { - irep1 - .pool - .push(RPool::Str(str.to_string_lossy().to_string())); + for val in irep.pool.iter() { + match val { + crate::rite::PoolValue::Str(s) | crate::rite::PoolValue::SStr(s) => { + irep1.pool.push(RPool::Str(s.to_string_lossy().to_string())); + } + crate::rite::PoolValue::Int32(i) => { + irep1.pool.push(RPool::Int(*i as i64)); + } + crate::rite::PoolValue::Int64(i) => { + irep1.pool.push(RPool::Int(*i)); + } + crate::rite::PoolValue::Float(f) => { + irep1.pool.push(RPool::Float(*f)); + } + crate::rite::PoolValue::BigInt(_) => { + // BigInt not yet supported, store as 0 for now + irep1.pool.push(RPool::Int(0)); + } + } } let code = interpret_insn(irep.insn); for ch in irep.catch_handlers.iter() { diff --git a/mrubyedge/tests/integer.rs b/mrubyedge/tests/integer.rs new file mode 100644 index 0000000..c902fac --- /dev/null +++ b/mrubyedge/tests/integer.rs @@ -0,0 +1,230 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn integer_bitref_test() { + let code = " +n = 0b1010 +n[0] + n[1] + n[2] + n[3] + "; + let binary = mrbc_compile("bitref", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 2); // 0 + 1 + 0 + 1 = 2 +} + +#[test] +fn integer_negative_test() { + let code = " +a = 42 +-a + "; + let binary = mrbc_compile("negative", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, -42); +} + +#[test] +fn integer_power_test() { + let code = " +2 ** 10 + "; + let binary = mrbc_compile("power", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 1024); +} + +#[test] +fn integer_power_float_test() { + let code = " +4 ** 0.5 + "; + let binary = mrbc_compile("power_float", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_float: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(result_float, 2.0); +} + +#[test] +fn integer_mod_test() { + let code = " +17 % 5 + "; + let binary = mrbc_compile("mod", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 2); +} + +#[test] +fn integer_and_test() { + let code = " +0b1100 & 0b1010 + "; + let binary = mrbc_compile("and", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 0b1000); +} + +#[test] +fn integer_or_test() { + let code = " +0b1100 | 0b1010 + "; + let binary = mrbc_compile("or", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 0b1110); +} + +#[test] +fn integer_xor_test() { + let code = " +0b1100 ^ 0b1010 + "; + let binary = mrbc_compile("xor", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 0b0110); +} + +#[test] +fn integer_not_test() { + let code = " +~5 + "; + let binary = mrbc_compile("not", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, -6); +} + +#[test] +fn integer_lshift_test() { + let code = " +5 << 2 + "; + let binary = mrbc_compile("lshift", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 20); +} + +#[test] +fn integer_rshift_test() { + let code = " +20 >> 2 + "; + let binary = mrbc_compile("rshift", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 5); +} + +#[test] +fn integer_abs_test() { + let code = " +result1 = (-42).abs +result2 = 42.abs +result1 + result2 + "; + let binary = mrbc_compile("abs", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 84); +} + +#[test] +fn integer_to_i_test() { + let code = " +42.to_i + "; + let binary = mrbc_compile("to_i", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 42); +} + +#[test] +fn integer_to_f_test() { + let code = " +42.to_f + "; + let binary = mrbc_compile("to_f", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_float: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(result_float, 42.0); +} + +#[test] +fn integer_chr_test() { + let code = " +65.chr + "; + let binary = mrbc_compile("chr", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(&result_str, "A"); +} + +#[test] +fn integer_to_s_test() { + let code = " +123.to_s + "; + let binary = mrbc_compile("to_s", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(&result_str, "123"); +} + +#[test] +fn integer_inspect_test() { + let code = " +456.inspect + "; + let binary = mrbc_compile("inspect", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(&result_str, "456"); +} From 09ef91359a89bfdf96678eb6451a62097a653fef Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 1 Feb 2026 21:02:55 +0900 Subject: [PATCH 166/314] Add internal is_utf or binary flag --- mrubyedge/src/yamrb/optable.rs | 10 +++++----- mrubyedge/src/yamrb/prelude/object.rs | 4 ++-- mrubyedge/src/yamrb/value.rs | 19 ++++++++++--------- 3 files changed, 17 insertions(+), 16 deletions(-) diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index a1e8c60..8ba93a2 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1411,7 +1411,7 @@ pub(crate) fn op_add(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { (RValue::Float(n1), RValue::Float(n2)) => Rc::new(RObject::float(n1 + n2)), (RValue::Integer(n1), RValue::Float(n2)) => Rc::new(RObject::float(*n1 as f64 + n2)), (RValue::Float(n1), RValue::Integer(n2)) => Rc::new(RObject::float(n1 + *n2 as f64)), - (RValue::String(n1), RValue::String(n2)) => { + (RValue::String(n1, _), RValue::String(n2, _)) => { let mut n1 = n1.borrow_mut(); let n2 = n2.borrow(); for c in n2.iter() { @@ -1693,25 +1693,25 @@ pub(crate) fn op_strcat(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val1 = vm.get_current_regs_cloned(a)?; let val2 = vm.get_current_regs_cloned(b)?; match (&val1.value, &val2.value) { - (RValue::String(s1), RValue::String(s2)) => { + (RValue::String(s1, _), RValue::String(s2, _)) => { let mut s1 = s1.borrow_mut(); let s2 = s2.borrow(); for c in s2.iter() { s1.push(*c); } } - (RValue::String(s1), RValue::Integer(s2)) => { + (RValue::String(s1, _), RValue::Integer(s2)) => { let mut s1 = s1.borrow_mut(); let s2 = s2.to_string(); for c in s2.as_bytes() { s1.push(*c); } } - (RValue::String(s1), _) => { + (RValue::String(s1, _), _) => { let mut s1 = s1.borrow_mut(); let s2 = mrb_funcall(vm, Some(val2.clone()), "to_s", &[])?; let s2 = match &s2.value { - RValue::String(s) => s.borrow(), + RValue::String(s, _) => s.borrow(), _ => unreachable!("to_s must return string"), }; for c in s2.to_vec().iter() { diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index 1846398..a28bf90 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -132,7 +132,7 @@ pub fn mrb_self(vm: &mut VM, _args: &[Rc]) -> Result, Error pub fn mrb_kernel_puts(vm: &mut VM, args: &[Rc]) -> Result, Error> { let msg = args[0].clone(); match &msg.value { - RValue::String(s) => { + RValue::String(s, _) => { println!("{}", String::from_utf8_lossy(&s.borrow())); } RValue::Integer(i) => { @@ -186,7 +186,7 @@ pub fn mrb_object_triple_eq(vm: &mut VM, args: &[Rc]) -> Result Ok(Rc::new(RObject::boolean(*i1 == *i2))), (RValue::Float(f1), RValue::Float(f2)) => Ok(Rc::new(RObject::boolean(*f1 == *f2))), (RValue::Symbol(sym1), RValue::Symbol(sym2)) => Ok(Rc::new(RObject::boolean(sym1 == sym2))), - (RValue::String(s1), RValue::String(s2)) => Ok(Rc::new(RObject::boolean(s1 == s2))), + (RValue::String(s1, _), RValue::String(s2, _)) => Ok(Rc::new(RObject::boolean(s1 == s2))), (RValue::Class(c1), _) => match &lhs.value { RValue::Class(c2) => Ok(Rc::new(RObject::boolean(c1.sym_id == c2.sym_id))), _ => { diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 4256ff1..b9b9e67 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -56,7 +56,8 @@ pub enum RValue { Proc(RProc), Array(RefCell>>), Hash(RefCell), - String(RefCell>), + /// (bytes, is_utf8) + String(RefCell>, Cell), Range(Rc, Rc, bool), SharedMemory(Rc>), Data(Rc), @@ -184,7 +185,7 @@ impl RObject { pub fn string(s: String) -> Self { RObject { tt: RType::String, - value: RValue::String(RefCell::new(s.into_bytes())), + value: RValue::String(RefCell::new(s.into_bytes()), Cell::new(true)), object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), ivar: RefCell::new(RHashMap::default()), @@ -194,7 +195,7 @@ impl RObject { pub fn string_from_vec(v: Vec) -> Self { RObject { tt: RType::String, - value: RValue::String(RefCell::new(v)), + value: RValue::String(RefCell::new(v), Cell::new(false)), object_id: (UNSET_OBJECT_ID).into(), singleton_class: RefCell::new(None), ivar: RefCell::new(RHashMap::default()), @@ -356,7 +357,7 @@ impl RObject { RValue::Integer(i) => Ok(ValueHasher::Integer(*i)), RValue::Float(f) => Ok(ValueHasher::Float(f.to_be_bytes().to_vec())), RValue::Symbol(s) => Ok(ValueHasher::Symbol(s.name.clone())), - RValue::String(s) => Ok(ValueHasher::String(s.borrow().clone())), + RValue::String(s, _) => Ok(ValueHasher::String(s.borrow().clone())), RValue::Class(c) => Ok(ValueHasher::Class(c.sym_id.name.clone())), _ => Err(Error::TypeMismatch), } @@ -368,7 +369,7 @@ impl RObject { RValue::Integer(i) => ValueEquality::Integer(*i), RValue::Float(f) => ValueEquality::Float(*f), RValue::Symbol(s) => ValueEquality::Symbol(s.name.clone()), - RValue::String(s) => ValueEquality::String(s.borrow().clone()), + RValue::String(s, _) => ValueEquality::String(s.borrow().clone()), RValue::Class(c) => ValueEquality::Class(c.sym_id.name.clone()), RValue::Range(s, e, ex) => { ValueEquality::Range(Box::new(s.as_eq_value()), Box::new(e.as_eq_value()), *ex) @@ -410,7 +411,7 @@ impl RObject { RValue::Proc(_) => vm.get_class_by_name("Proc"), RValue::Array(_) => vm.get_class_by_name("Array"), RValue::Hash(_) => vm.get_class_by_name("Hash"), - RValue::String(_) => vm.get_class_by_name("String"), + RValue::String(_, _) => vm.get_class_by_name("String"), RValue::Range(_, _, _) => vm.get_class_by_name("Range"), RValue::SharedMemory(_) => vm.get_class_by_name("SharedMemory"), RValue::Data(d) => d.class.clone(), @@ -493,7 +494,7 @@ impl RObject { pub fn intern(&self) -> Result { match &self.value { - RValue::String(s) => Ok(RSym::new(String::from_utf8_lossy(&s.borrow()).to_string())), + RValue::String(s, _) => Ok(RSym::new(String::from_utf8_lossy(&s.borrow()).to_string())), RValue::Symbol(s) => Ok(s.clone()), _ => Err(Error::TypeMismatch), } @@ -717,7 +718,7 @@ impl TryFrom<&RObject> for String { fn try_from(value: &RObject) -> Result { match &value.value { - RValue::String(s) => Ok(String::from_utf8_lossy(&s.borrow()).to_string()), + RValue::String(s, _) => Ok(String::from_utf8_lossy(&s.borrow()).to_string()), RValue::Symbol(sym) => Ok(sym.name.clone()), v => Ok(format!("{:?}", v)), } @@ -729,7 +730,7 @@ impl TryFrom<&RObject> for Vec { fn try_from(value: &RObject) -> Result { match &value.value { - RValue::String(s) => Ok(s.borrow().clone()), + RValue::String(s, _) => Ok(s.borrow().clone()), _ => Err(Error::TypeMismatch), } } From a2ea819505e5d2cbf3953824f22306bf85ce244e Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 1 Feb 2026 21:05:10 +0900 Subject: [PATCH 167/314] Add note --- mrubyedge/src/yamrb/value.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index b9b9e67..1426d4a 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -57,6 +57,7 @@ pub enum RValue { Array(RefCell>>), Hash(RefCell), /// (bytes, is_utf8) + /// FIXME: currently, we compare strings by bytes only, so is_utf8 is unused. String(RefCell>, Cell), Range(Rc, Rc, bool), SharedMemory(Rc>), From ab34e5239eade1d9bb0d82b731ea764e7ce909e7 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 1 Feb 2026 21:13:10 +0900 Subject: [PATCH 168/314] Basic methods of String --- mrubyedge/src/yamrb/prelude/string.rs | 317 +++++++++++++++++++++++++- 1 file changed, 316 insertions(+), 1 deletion(-) diff --git a/mrubyedge/src/yamrb/prelude/string.rs b/mrubyedge/src/yamrb/prelude/string.rs index 303729b..0ec7e17 100644 --- a/mrubyedge/src/yamrb/prelude/string.rs +++ b/mrubyedge/src/yamrb/prelude/string.rs @@ -5,7 +5,7 @@ use crate::{ yamrb::{ helpers::{mrb_define_class_cmethod, mrb_define_cmethod}, prelude::object, - value::RObject, + value::{RObject, RSym}, vm::VM, }, }; @@ -18,6 +18,123 @@ pub(crate) fn initialize_string(vm: &mut VM) { mrb_define_class_cmethod(vm, string_class.clone(), "new", Box::new(mrb_string_new)); + mrb_define_cmethod(vm, string_class.clone(), "+", Box::new(mrb_string_add)); + mrb_define_cmethod(vm, string_class.clone(), "*", Box::new(mrb_string_mul)); + mrb_define_cmethod(vm, string_class.clone(), "<<", Box::new(mrb_string_append)); + mrb_define_cmethod(vm, string_class.clone(), "[]", Box::new(mrb_string_slice)); + mrb_define_cmethod(vm, string_class.clone(), "b", Box::new(object::mrb_self)); + mrb_define_cmethod( + vm, + string_class.clone(), + "clear", + Box::new(mrb_string_clear), + ); + mrb_define_cmethod( + vm, + string_class.clone(), + "chomp", + Box::new(mrb_string_chomp), + ); + mrb_define_cmethod(vm, string_class.clone(), "dup", Box::new(mrb_string_dup)); + mrb_define_cmethod( + vm, + string_class.clone(), + "empty?", + Box::new(mrb_string_empty), + ); + mrb_define_cmethod( + vm, + string_class.clone(), + "getbyte", + Box::new(mrb_string_getbyte), + ); + mrb_define_cmethod( + vm, + string_class.clone(), + "index", + Box::new(mrb_string_index), + ); + mrb_define_cmethod(vm, string_class.clone(), "ord", Box::new(mrb_string_ord)); + mrb_define_cmethod( + vm, + string_class.clone(), + "slice", + Box::new(mrb_string_slice), + ); + mrb_define_cmethod( + vm, + string_class.clone(), + "split", + Box::new(mrb_string_split), + ); + mrb_define_cmethod( + vm, + string_class.clone(), + "lstrip", + Box::new(mrb_string_lstrip), + ); + mrb_define_cmethod( + vm, + string_class.clone(), + "rstrip", + Box::new(mrb_string_rstrip), + ); + mrb_define_cmethod( + vm, + string_class.clone(), + "strip", + Box::new(mrb_string_strip), + ); + mrb_define_cmethod( + vm, + string_class.clone(), + "to_sym", + Box::new(mrb_string_to_sym), + ); + mrb_define_cmethod( + vm, + string_class.clone(), + "intern", + Box::new(mrb_string_to_sym), + ); + mrb_define_cmethod( + vm, + string_class.clone(), + "start_with?", + Box::new(mrb_string_start_with), + ); + mrb_define_cmethod( + vm, + string_class.clone(), + "end_with?", + Box::new(mrb_string_end_with), + ); + mrb_define_cmethod( + vm, + string_class.clone(), + "include?", + Box::new(mrb_string_include), + ); + mrb_define_cmethod( + vm, + string_class.clone(), + "bytes", + Box::new(mrb_string_bytes), + ); + mrb_define_cmethod( + vm, + string_class.clone(), + "upcase", + Box::new(mrb_string_upcase), + ); + mrb_define_cmethod( + vm, + string_class.clone(), + "downcase", + Box::new(mrb_string_downcase), + ); + mrb_define_cmethod(vm, string_class.clone(), "to_i", Box::new(mrb_string_to_i)); + mrb_define_cmethod(vm, string_class.clone(), "to_f", Box::new(mrb_string_to_f)); mrb_define_cmethod( vm, string_class.clone(), @@ -179,6 +296,204 @@ fn mrb_string_size(vm: &mut VM, _args: &[Rc]) -> Result, Er Ok(Rc::new(RObject::integer(value.len() as i64))) } +fn mrb_string_add(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + let other: String = args[0].as_ref().try_into()?; + Ok(Rc::new(RObject::string(this + &other))) +} + +fn mrb_string_mul(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + let times: i64 = args[0].as_ref().try_into()?; + if times < 0 { + return Err(Error::ArgumentError("negative argument".to_string())); + } + Ok(Rc::new(RObject::string(this.repeat(times as usize)))) +} + +// FIXME: destructive method +fn mrb_string_append(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let mut this: String = vm.getself()?.as_ref().try_into()?; + let other: String = args[0].as_ref().try_into()?; + this.push_str(&other); + Ok(Rc::new(RObject::string(this))) +} + +fn mrb_string_slice(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + let chars: Vec = this.chars().collect(); + + if args.is_empty() { + return Err(Error::ArgumentError( + "wrong number of arguments".to_string(), + )); + } + + let index: i64 = args[0].as_ref().try_into()?; + let len = chars.len() as i64; + let idx = if index < 0 { len + index } else { index }; + + if idx < 0 || idx >= len { + return Ok(Rc::new(RObject::nil())); + } + + if args.len() == 1 { + Ok(Rc::new(RObject::string(chars[idx as usize].to_string()))) + } else { + let length: i64 = args[1].as_ref().try_into()?; + if length < 0 { + return Ok(Rc::new(RObject::nil())); + } + let end = (idx + length).min(len); + let result: String = chars[idx as usize..end as usize].iter().collect(); + Ok(Rc::new(RObject::string(result))) + } +} + +fn mrb_string_clear(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { + Ok(Rc::new(RObject::string(String::new()))) +} + +fn mrb_string_chomp(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + let result = this + .strip_suffix("\r\n") + .or_else(|| this.strip_suffix('\n')) + .or_else(|| this.strip_suffix('\r')) + .unwrap_or(&this); + Ok(Rc::new(RObject::string(result.to_string()))) +} + +fn mrb_string_dup(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + Ok(Rc::new(RObject::string(this))) +} + +fn mrb_string_empty(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + Ok(Rc::new(RObject::boolean(this.is_empty()))) +} + +fn mrb_string_getbyte(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this: Vec = vm.getself()?.as_ref().try_into()?; + let index: i64 = args[0].as_ref().try_into()?; + let len = this.len() as i64; + let idx = if index < 0 { len + index } else { index }; + + if idx < 0 || idx >= len { + return Ok(Rc::new(RObject::nil())); + } + Ok(Rc::new(RObject::integer(this[idx as usize] as i64))) +} + +fn mrb_string_index(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + let search: String = args[0].as_ref().try_into()?; + + match this.find(&search) { + Some(pos) => Ok(Rc::new(RObject::integer(pos as i64))), + None => Ok(Rc::new(RObject::nil())), + } +} + +fn mrb_string_ord(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + if let Some(ch) = this.chars().next() { + Ok(Rc::new(RObject::integer(ch as i64))) + } else { + Err(Error::ArgumentError("empty string".to_string())) + } +} + +fn mrb_string_split(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + + let result = if args.is_empty() { + // Split by whitespace + this.split_whitespace() + .map(|s| Rc::new(RObject::string(s.to_string()))) + .collect() + } else { + let separator: String = args[0].as_ref().try_into()?; + this.split(&separator) + .map(|s| Rc::new(RObject::string(s.to_string()))) + .collect() + }; + + Ok(Rc::new(RObject::array(result))) +} + +fn mrb_string_lstrip(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + Ok(Rc::new(RObject::string(this.trim_start().to_string()))) +} + +fn mrb_string_rstrip(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + Ok(Rc::new(RObject::string(this.trim_end().to_string()))) +} + +fn mrb_string_strip(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + Ok(Rc::new(RObject::string(this.trim().to_string()))) +} + +fn mrb_string_to_sym(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + Ok(Rc::new(RObject::symbol(RSym::new(this)))) +} + +fn mrb_string_start_with(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + let prefix: String = args[0].as_ref().try_into()?; + Ok(Rc::new(RObject::boolean(this.starts_with(&prefix)))) +} + +fn mrb_string_end_with(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + let suffix: String = args[0].as_ref().try_into()?; + Ok(Rc::new(RObject::boolean(this.ends_with(&suffix)))) +} + +fn mrb_string_include(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + let search: String = args[0].as_ref().try_into()?; + Ok(Rc::new(RObject::boolean(this.contains(&search)))) +} + +fn mrb_string_bytes(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: Vec = vm.getself()?.as_ref().try_into()?; + let result: Vec> = this + .into_iter() + .map(|b| Rc::new(RObject::integer(b as i64))) + .collect(); + Ok(Rc::new(RObject::array(result))) +} + +fn mrb_string_upcase(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + Ok(Rc::new(RObject::string(this.to_uppercase()))) +} + +fn mrb_string_downcase(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + Ok(Rc::new(RObject::string(this.to_lowercase()))) +} + +fn mrb_string_to_i(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + let trimmed = this.trim(); + let result = trimmed.parse::().unwrap_or(0); + Ok(Rc::new(RObject::integer(result))) +} + +fn mrb_string_to_f(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: String = vm.getself()?.as_ref().try_into()?; + let trimmed = this.trim(); + let result = trimmed.parse::().unwrap_or(0.0); + Ok(Rc::new(RObject::float(result))) +} + #[test] fn test_mrb_string_size() { use crate::yamrb::*; From aa872cacf01aa1facd98442ab11f03fe90f06c52 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 1 Feb 2026 22:07:05 +0900 Subject: [PATCH 169/314] Add all the destructive methods to String --- mrubyedge/src/yamrb/prelude/string.rs | 233 +++++++++++++++++++++++++- mrubyedge/src/yamrb/value.rs | 7 + 2 files changed, 234 insertions(+), 6 deletions(-) diff --git a/mrubyedge/src/yamrb/prelude/string.rs b/mrubyedge/src/yamrb/prelude/string.rs index 0ec7e17..8bdf420 100644 --- a/mrubyedge/src/yamrb/prelude/string.rs +++ b/mrubyedge/src/yamrb/prelude/string.rs @@ -5,7 +5,7 @@ use crate::{ yamrb::{ helpers::{mrb_define_class_cmethod, mrb_define_cmethod}, prelude::object, - value::{RObject, RSym}, + value::{RObject, RSym, RValue}, vm::VM, }, }; @@ -22,7 +22,7 @@ pub(crate) fn initialize_string(vm: &mut VM) { mrb_define_cmethod(vm, string_class.clone(), "*", Box::new(mrb_string_mul)); mrb_define_cmethod(vm, string_class.clone(), "<<", Box::new(mrb_string_append)); mrb_define_cmethod(vm, string_class.clone(), "[]", Box::new(mrb_string_slice)); - mrb_define_cmethod(vm, string_class.clone(), "b", Box::new(object::mrb_self)); + mrb_define_cmethod(vm, string_class.clone(), "b", Box::new(mrb_string_b)); mrb_define_cmethod( vm, string_class.clone(), @@ -35,6 +35,12 @@ pub(crate) fn initialize_string(vm: &mut VM) { "chomp", Box::new(mrb_string_chomp), ); + mrb_define_cmethod( + vm, + string_class.clone(), + "chomp!", + Box::new(mrb_string_chomp_self), + ); mrb_define_cmethod(vm, string_class.clone(), "dup", Box::new(mrb_string_dup)); mrb_define_cmethod( vm, @@ -48,6 +54,12 @@ pub(crate) fn initialize_string(vm: &mut VM) { "getbyte", Box::new(mrb_string_getbyte), ); + mrb_define_cmethod( + vm, + string_class.clone(), + "setbyte", + Box::new(mrb_string_setbyte), + ); mrb_define_cmethod( vm, string_class.clone(), @@ -61,6 +73,12 @@ pub(crate) fn initialize_string(vm: &mut VM) { "slice", Box::new(mrb_string_slice), ); + mrb_define_cmethod( + vm, + string_class.clone(), + "slice!", + Box::new(mrb_string_slice_self), + ); mrb_define_cmethod( vm, string_class.clone(), @@ -73,18 +91,36 @@ pub(crate) fn initialize_string(vm: &mut VM) { "lstrip", Box::new(mrb_string_lstrip), ); + mrb_define_cmethod( + vm, + string_class.clone(), + "lstrip!", + Box::new(mrb_string_lstrip_self), + ); mrb_define_cmethod( vm, string_class.clone(), "rstrip", Box::new(mrb_string_rstrip), ); + mrb_define_cmethod( + vm, + string_class.clone(), + "rstrip!", + Box::new(mrb_string_rstrip_self), + ); mrb_define_cmethod( vm, string_class.clone(), "strip", Box::new(mrb_string_strip), ); + mrb_define_cmethod( + vm, + string_class.clone(), + "strip!", + Box::new(mrb_string_strip_self), + ); mrb_define_cmethod( vm, string_class.clone(), @@ -127,12 +163,24 @@ pub(crate) fn initialize_string(vm: &mut VM) { "upcase", Box::new(mrb_string_upcase), ); + mrb_define_cmethod( + vm, + string_class.clone(), + "upcase!", + Box::new(mrb_string_upcase_self), + ); mrb_define_cmethod( vm, string_class.clone(), "downcase", Box::new(mrb_string_downcase), ); + mrb_define_cmethod( + vm, + string_class.clone(), + "downcase!", + Box::new(mrb_string_downcase_self), + ); mrb_define_cmethod(vm, string_class.clone(), "to_i", Box::new(mrb_string_to_i)); mrb_define_cmethod(vm, string_class.clone(), "to_f", Box::new(mrb_string_to_f)); mrb_define_cmethod( @@ -142,6 +190,12 @@ pub(crate) fn initialize_string(vm: &mut VM) { Box::new(mrb_string_unpack), ); mrb_define_cmethod(vm, string_class.clone(), "size", Box::new(mrb_string_size)); + mrb_define_cmethod( + vm, + string_class.clone(), + "bytesize", + Box::new(mrb_string_size), + ); mrb_define_cmethod( vm, string_class.clone(), @@ -311,12 +365,12 @@ fn mrb_string_mul(vm: &mut VM, args: &[Rc]) -> Result, Erro Ok(Rc::new(RObject::string(this.repeat(times as usize)))) } -// FIXME: destructive method fn mrb_string_append(vm: &mut VM, args: &[Rc]) -> Result, Error> { - let mut this: String = vm.getself()?.as_ref().try_into()?; + let this = vm.getself()?; let other: String = args[0].as_ref().try_into()?; - this.push_str(&other); - Ok(Rc::new(RObject::string(this))) + this.string_borrow_mut()? + .extend_from_slice(other.as_bytes()); + Ok(this) } fn mrb_string_slice(vm: &mut VM, args: &[Rc]) -> Result, Error> { @@ -350,6 +404,65 @@ fn mrb_string_slice(vm: &mut VM, args: &[Rc]) -> Result, Er } } +fn mrb_string_slice_self(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let s: String = this.as_ref().try_into()?; + let chars: Vec = s.chars().collect(); + + if args.is_empty() { + return Err(Error::ArgumentError( + "wrong number of arguments".to_string(), + )); + } + + let args = if args[args.len() - 1].is_nil() { + &args[..args.len() - 1] + } else { + args + }; + + let index: i64 = args[0].as_ref().try_into()?; + let len = chars.len() as i64; + let idx = if index < 0 { len + index } else { index }; + + if idx < 0 || idx >= len { + return Ok(Rc::new(RObject::nil())); + } + + let (removed, remaining) = if args.len() == 1 { + let removed = chars[idx as usize].to_string(); + let mut remaining_chars = chars.clone(); + remaining_chars.remove(idx as usize); + let remaining: String = remaining_chars.iter().collect(); + (removed, remaining) + } else { + let length: i64 = args[1].as_ref().try_into()?; + if length < 0 { + return Ok(Rc::new(RObject::nil())); + } + let end = (idx + length).min(len); + let removed: String = chars[idx as usize..end as usize].iter().collect(); + let mut remaining_chars = chars.clone(); + remaining_chars.drain(idx as usize..end as usize); + let remaining: String = remaining_chars.iter().collect(); + (removed, remaining) + }; + + *this.string_borrow_mut()? = remaining.as_bytes().to_vec(); + Ok(Rc::new(RObject::string(removed))) +} + +// Returns self with UTF-8 flag set to false (binary encoding). +fn mrb_string_b(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + + if let RValue::String(value, _) = &this.value { + Ok(RObject::string_from_vec(value.borrow().to_owned()).to_refcount_assigned()) + } else { + Err(Error::TypeMismatch) + } +} + fn mrb_string_clear(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { Ok(Rc::new(RObject::string(String::new()))) } @@ -364,6 +477,23 @@ fn mrb_string_chomp(vm: &mut VM, _args: &[Rc]) -> Result, E Ok(Rc::new(RObject::string(result.to_string()))) } +fn mrb_string_chomp_self(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let s: String = this.as_ref().try_into()?; + let result = s + .strip_suffix("\r\n") + .or_else(|| s.strip_suffix('\n')) + .or_else(|| s.strip_suffix('\r')) + .unwrap_or(&s); + + if result.len() != s.len() { + *this.string_borrow_mut()? = result.as_bytes().to_vec(); + Ok(this) + } else { + Ok(Rc::new(RObject::nil())) + } +} + fn mrb_string_dup(vm: &mut VM, _args: &[Rc]) -> Result, Error> { let this: String = vm.getself()?.as_ref().try_into()?; Ok(Rc::new(RObject::string(this))) @@ -386,6 +516,32 @@ fn mrb_string_getbyte(vm: &mut VM, args: &[Rc]) -> Result, Ok(Rc::new(RObject::integer(this[idx as usize] as i64))) } +fn mrb_string_setbyte(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let mut bytes: Vec = this.as_ref().try_into()?; + let index: i64 = args[0].as_ref().try_into()?; + let value: i64 = args[1].as_ref().try_into()?; + + let len = bytes.len() as i64; + let idx = if index < 0 { len + index } else { index }; + + if idx < 0 || idx >= len { + return Err(Error::ArgumentError(format!( + "index {} out of string", + index + ))); + } + + if !(0..=255).contains(&value) { + return Err(Error::ArgumentError(format!("{} out of char range", value))); + } + + bytes[idx as usize] = value as u8; + *this.string_borrow_mut()? = bytes; + + Ok(Rc::new(RObject::integer(value))) +} + fn mrb_string_index(vm: &mut VM, args: &[Rc]) -> Result, Error> { let this: String = vm.getself()?.as_ref().try_into()?; let search: String = args[0].as_ref().try_into()?; @@ -428,16 +584,55 @@ fn mrb_string_lstrip(vm: &mut VM, _args: &[Rc]) -> Result, Ok(Rc::new(RObject::string(this.trim_start().to_string()))) } +fn mrb_string_lstrip_self(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let s: String = this.as_ref().try_into()?; + let result = s.trim_start(); + + if result.len() != s.len() { + *this.string_borrow_mut()? = result.as_bytes().to_vec(); + Ok(this) + } else { + Ok(Rc::new(RObject::nil())) + } +} + fn mrb_string_rstrip(vm: &mut VM, _args: &[Rc]) -> Result, Error> { let this: String = vm.getself()?.as_ref().try_into()?; Ok(Rc::new(RObject::string(this.trim_end().to_string()))) } +fn mrb_string_rstrip_self(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let s: String = this.as_ref().try_into()?; + let result = s.trim_end(); + + if result.len() != s.len() { + *this.string_borrow_mut()? = result.as_bytes().to_vec(); + Ok(this) + } else { + Ok(Rc::new(RObject::nil())) + } +} + fn mrb_string_strip(vm: &mut VM, _args: &[Rc]) -> Result, Error> { let this: String = vm.getself()?.as_ref().try_into()?; Ok(Rc::new(RObject::string(this.trim().to_string()))) } +fn mrb_string_strip_self(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let s: String = this.as_ref().try_into()?; + let result = s.trim(); + + if result.len() != s.len() { + *this.string_borrow_mut()? = result.as_bytes().to_vec(); + Ok(this) + } else { + Ok(Rc::new(RObject::nil())) + } +} + fn mrb_string_to_sym(vm: &mut VM, _args: &[Rc]) -> Result, Error> { let this: String = vm.getself()?.as_ref().try_into()?; Ok(Rc::new(RObject::symbol(RSym::new(this)))) @@ -475,11 +670,37 @@ fn mrb_string_upcase(vm: &mut VM, _args: &[Rc]) -> Result, Ok(Rc::new(RObject::string(this.to_uppercase()))) } +fn mrb_string_upcase_self(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let s: String = this.as_ref().try_into()?; + let result = s.to_uppercase(); + + if result != s { + *this.string_borrow_mut()? = result.as_bytes().to_vec(); + Ok(this) + } else { + Ok(Rc::new(RObject::nil())) + } +} + fn mrb_string_downcase(vm: &mut VM, _args: &[Rc]) -> Result, Error> { let this: String = vm.getself()?.as_ref().try_into()?; Ok(Rc::new(RObject::string(this.to_lowercase()))) } +fn mrb_string_downcase_self(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let s: String = this.as_ref().try_into()?; + let result = s.to_lowercase(); + + if result != s { + *this.string_borrow_mut()? = result.as_bytes().to_vec(); + Ok(this) + } else { + Ok(Rc::new(RObject::nil())) + } +} + fn mrb_string_to_i(vm: &mut VM, _args: &[Rc]) -> Result, Error> { let this: String = vm.getself()?.as_ref().try_into()?; let trimmed = this.trim(); diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 1426d4a..bced710 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -500,6 +500,13 @@ impl RObject { _ => Err(Error::TypeMismatch), } } + + pub(crate) fn string_borrow_mut(&self) -> Result>, Error> { + match &self.value { + RValue::String(s, _) => Ok(s.borrow_mut()), + _ => Err(Error::TypeMismatch), + } + } } impl TryFrom<&RObject> for i32 { From 28f4f01ac41ef0c3e0f63f9ff06a8d70b50044e2 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 1 Feb 2026 22:15:37 +0900 Subject: [PATCH 170/314] Add String#chars that is missing in mruby/c --- mrubyedge/src/yamrb/prelude/string.rs | 33 +++++++++++++++++++++++++++ mrubyedge/src/yamrb/value.rs | 7 ++++++ 2 files changed, 40 insertions(+) diff --git a/mrubyedge/src/yamrb/prelude/string.rs b/mrubyedge/src/yamrb/prelude/string.rs index 8bdf420..7295b6f 100644 --- a/mrubyedge/src/yamrb/prelude/string.rs +++ b/mrubyedge/src/yamrb/prelude/string.rs @@ -157,6 +157,12 @@ pub(crate) fn initialize_string(vm: &mut VM) { "bytes", Box::new(mrb_string_bytes), ); + mrb_define_cmethod( + vm, + string_class.clone(), + "chars", + Box::new(mrb_string_chars), + ); mrb_define_cmethod( vm, string_class.clone(), @@ -665,6 +671,33 @@ fn mrb_string_bytes(vm: &mut VM, _args: &[Rc]) -> Result, E Ok(Rc::new(RObject::array(result))) } +/// Returns an array of characters. +/// If UTF-8 flag is true, splits by runes (UTF-8 characters). +/// If UTF-8 flag is false, splits by bytes. +fn mrb_string_chars(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + + let is_utf8 = this.string_is_utf8()?; + + let bytes: Vec = this.as_ref().try_into()?; + + let result: Vec> = if is_utf8 { + // Split by UTF-8 characters (runes) + let s = String::from_utf8_lossy(&bytes); + s.chars() + .map(|c| Rc::new(RObject::string(c.to_string()))) + .collect() + } else { + // Split by bytes + bytes + .into_iter() + .map(|b| Rc::new(RObject::string_from_vec(vec![b]))) + .collect() + }; + + Ok(Rc::new(RObject::array(result))) +} + fn mrb_string_upcase(vm: &mut VM, _args: &[Rc]) -> Result, Error> { let this: String = vm.getself()?.as_ref().try_into()?; Ok(Rc::new(RObject::string(this.to_uppercase()))) diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index bced710..5129189 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -507,6 +507,13 @@ impl RObject { _ => Err(Error::TypeMismatch), } } + + pub(crate) fn string_is_utf8(&self) -> Result { + match &self.value { + RValue::String(_, is_utf8) => Ok(is_utf8.get()), + _ => Err(Error::TypeMismatch), + } + } } impl TryFrom<&RObject> for i32 { From 65706b2d7e6185b1ff570511d7906a51a1515089 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 1 Feb 2026 22:51:00 +0900 Subject: [PATCH 171/314] Add basic test with some fix --- mrubyedge/src/yamrb/optable.rs | 16 +- mrubyedge/src/yamrb/prelude/string.rs | 6 +- mrubyedge/src/yamrb/value.rs | 7 + mrubyedge/tests/string.rs | 661 ++++++++++++++++++++++++++ 4 files changed, 680 insertions(+), 10 deletions(-) diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 8ba93a2..37c54d7 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1477,12 +1477,12 @@ pub(crate) fn op_mul(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val1 = vm.take_current_regs(a)?; let val2 = vm.get_current_regs_cloned(b)?; let result = match (&val1.value, &val2.value) { - (RValue::Integer(n1), RValue::Integer(n2)) => RObject::integer(n1 * n2), - _ => { - unreachable!("mul supports only integer") + (RValue::Integer(n1), RValue::Integer(n2)) => { + RObject::integer(n1 * n2).to_refcount_assigned() } + _ => mrb_funcall(vm, Some(val1), "*", &[val2])?, }; - vm.current_regs()[a].replace(Rc::new(result)); + vm.current_regs()[a].replace(result); Ok(()) } @@ -1492,12 +1492,12 @@ pub(crate) fn op_div(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val1 = vm.take_current_regs(a)?; let val2 = vm.get_current_regs_cloned(b)?; let result = match (&val1.value, &val2.value) { - (RValue::Integer(n1), RValue::Integer(n2)) => RObject::integer(n1 / n2), - _ => { - unreachable!("div supports only integer") + (RValue::Integer(n1), RValue::Integer(n2)) => { + RObject::integer(n1 / n2).to_refcount_assigned() } + _ => mrb_funcall(vm, Some(val1), "/", &[val2])?, }; - vm.current_regs()[a].replace(Rc::new(result)); + vm.current_regs()[a].replace(result); Ok(()) } diff --git a/mrubyedge/src/yamrb/prelude/string.rs b/mrubyedge/src/yamrb/prelude/string.rs index 7295b6f..732b310 100644 --- a/mrubyedge/src/yamrb/prelude/string.rs +++ b/mrubyedge/src/yamrb/prelude/string.rs @@ -469,8 +469,10 @@ fn mrb_string_b(vm: &mut VM, _args: &[Rc]) -> Result, Error } } -fn mrb_string_clear(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { - Ok(Rc::new(RObject::string(String::new()))) +fn mrb_string_clear(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + this.string_borrow_mut()?.clear(); + Ok(this) } fn mrb_string_chomp(vm: &mut VM, _args: &[Rc]) -> Result, Error> { diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 5129189..320cd71 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -514,6 +514,13 @@ impl RObject { _ => Err(Error::TypeMismatch), } } + + pub fn as_vec_owned(&self) -> Result>, Error> { + match &self.value { + RValue::Array(arr) => Ok(arr.borrow().to_owned()), + _ => Err(Error::TypeMismatch), + } + } } impl TryFrom<&RObject> for i32 { diff --git a/mrubyedge/tests/string.rs b/mrubyedge/tests/string.rs index 609a3ad..4509a3f 100644 --- a/mrubyedge/tests/string.rs +++ b/mrubyedge/tests/string.rs @@ -26,3 +26,664 @@ fn string_new_test() { let result: i32 = result.as_ref().try_into().unwrap(); assert_eq!(result, 0); } + +#[test] +fn string_add_test() { + let code = r#" + def test_string_add + "hello" + " " + "world" + end + "#; + let binary = mrbc_compile("string_add", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_add", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "hello world"); +} + +#[test] +fn string_mul_test() { + let code = r#" + def test_string_mul + "ab" * 3 + end + "#; + let binary = mrbc_compile("string_mul", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_mul", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "ababab"); +} + +#[test] +fn string_append_test() { + let code = r#" + def test_string_append + s = "hello" + s << " world" + s + end + "#; + let binary = mrbc_compile("string_append", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_append", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "hello world"); +} + +#[test] +fn string_b_test() { + let code = r#" + def test_string_b + s = "hello" + s.b + end + "#; + let binary = mrbc_compile("string_b", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_b", &args).unwrap(); + let result: Vec = result.as_ref().try_into().unwrap(); + assert_eq!(result, b"hello"); +} + +#[test] +fn string_clear_test() { + let code = r#" + def test_string_clear + str = "hello" + str.clear + str + end + "#; + let binary = mrbc_compile("string_clear", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_clear", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, ""); +} + +#[test] +fn string_chomp_test() { + let code = r#" + def test_string_chomp + "hello\n".chomp + end + "#; + let binary = mrbc_compile("string_chomp", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_chomp", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "hello"); +} + +#[test] +fn string_chomp_self_test() { + let code = r#" + def test_string_chomp_self + s = "hello\n" + s.chomp! + s + end + "#; + let binary = mrbc_compile("string_chomp_self", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_chomp_self", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "hello"); +} + +#[test] +fn string_dup_test() { + let code = r#" + def test_string_dup + s = "hello" + s.dup == s + end + "#; + let binary = mrbc_compile("string_dup", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_dup", &args).unwrap(); + let result: bool = result.as_ref().try_into().unwrap(); + assert!(result); +} + +#[test] +fn string_empty_test() { + let code = r#" + def test_string_empty + "".empty? + end + "#; + let binary = mrbc_compile("string_empty", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_empty", &args).unwrap(); + let result: bool = result.as_ref().try_into().unwrap(); + assert!(result); +} + +#[test] +fn string_getbyte_test() { + let code = r#" + def test_string_getbyte + "hello".getbyte(1) + end + "#; + let binary = mrbc_compile("string_getbyte", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_getbyte", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 101); // 'e' +} + +#[test] +fn string_setbyte_test() { + let code = r#" + def test_string_setbyte + s = "hello" + s.setbyte(0, 72) + s + end + "#; + let binary = mrbc_compile("string_setbyte", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_setbyte", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "Hello"); +} + +#[test] +fn string_index_test() { + let code = r#" + def test_string_index + "hello".index("l") + end + "#; + let binary = mrbc_compile("string_index", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_index", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 2); +} + +#[test] +fn string_ord_test() { + let code = r#" + def test_string_ord + "A".ord + end + "#; + let binary = mrbc_compile("string_ord", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_ord", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 65); +} + +#[test] +fn string_slice_test() { + let code = r#" + def test_string_slice + s = "hello" + s.slice(1, 2) + end + "#; + let binary = mrbc_compile("string_slice", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_slice", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "el"); +} + +#[test] +fn string_slice_self_test() { + let code = r#" + def test_string_slice_self + s = "hello" + s.slice!(1, 2) + s + end + "#; + let binary = mrbc_compile("string_slice_self", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_slice_self", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "hlo"); +} + +#[test] +fn string_split_test() { + let code = r#" + def test_string_split + "a,b,c".split(",").size + end + "#; + let binary = mrbc_compile("string_split", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_split", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 3); +} + +#[test] +fn string_lstrip_test() { + let code = r#" + def test_string_lstrip + " hello ".lstrip + end + "#; + let binary = mrbc_compile("string_lstrip", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_lstrip", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "hello "); +} + +#[test] +fn string_lstrip_self_test() { + let code = r#" + def test_string_lstrip_self + s = " hello " + s.lstrip! + s + end + "#; + let binary = mrbc_compile("string_lstrip_self", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_lstrip_self", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "hello "); +} + +#[test] +fn string_rstrip_test() { + let code = r#" + def test_string_rstrip + " hello ".rstrip + end + "#; + let binary = mrbc_compile("string_rstrip", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_rstrip", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, " hello"); +} + +#[test] +fn string_rstrip_self_test() { + let code = r#" + def test_string_rstrip_self + s = " hello " + s.rstrip! + s + end + "#; + let binary = mrbc_compile("string_rstrip_self", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_rstrip_self", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, " hello"); +} + +#[test] +fn string_strip_test() { + let code = r#" + def test_string_strip + " hello ".strip + end + "#; + let binary = mrbc_compile("string_strip", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_strip", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "hello"); +} + +#[test] +fn string_strip_self_test() { + let code = r#" + def test_string_strip_self + s = " hello " + s.strip! + s + end + "#; + let binary = mrbc_compile("string_strip_self", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_strip_self", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "hello"); +} + +#[test] +fn string_to_sym_test() { + let code = r#" + def test_string_to_sym + "hello".to_sym.to_s + end + "#; + let binary = mrbc_compile("string_to_sym", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_to_sym", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "hello"); +} + +#[test] +fn string_start_with_test() { + let code = r#" + def test_string_start_with + "hello".start_with?("hel") + end + "#; + let binary = mrbc_compile("string_start_with", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_start_with", &args).unwrap(); + let result: bool = result.as_ref().try_into().unwrap(); + assert!(result); +} + +#[test] +fn string_end_with_test() { + let code = r#" + def test_string_end_with + "hello".end_with?("lo") + end + "#; + let binary = mrbc_compile("string_end_with", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_end_with", &args).unwrap(); + let result: bool = result.as_ref().try_into().unwrap(); + assert!(result); +} + +#[test] +fn string_include_test() { + let code = r#" + def test_string_include + "hello".include?("ll") + end + "#; + let binary = mrbc_compile("string_include", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_include", &args).unwrap(); + let result: bool = result.as_ref().try_into().unwrap(); + assert!(result); +} + +#[test] +fn string_bytes_test() { + let code = r#" + def test_string_bytes + "AB".bytes + end + "#; + let binary = mrbc_compile("string_bytes", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_bytes", &args).unwrap(); + let result = result.as_vec_owned().unwrap(); + assert_eq!(result.len(), 2); + let first: i64 = result[0].as_ref().try_into().unwrap(); + let second: i64 = result[1].as_ref().try_into().unwrap(); + assert_eq!(first, 65); + assert_eq!(second, 66); +} + +#[test] +fn string_chars_test() { + let code = r#" + def test_string_chars + "hello".chars + end + "#; + let binary = mrbc_compile("string_chars", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_chars", &args).unwrap(); + let result = result.as_vec_owned().unwrap(); + assert_eq!(result.len(), 5); + let first: String = result[0].as_ref().try_into().unwrap(); + assert_eq!(first, "h"); + let last: String = result[4].as_ref().try_into().unwrap(); + assert_eq!(last, "o"); +} + +#[test] +fn string_upcase_test() { + let code = r#" + def test_string_upcase + "hello".upcase + end + "#; + let binary = mrbc_compile("string_upcase", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_upcase", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "HELLO"); +} + +#[test] +fn string_upcase_self_test() { + let code = r#" + def test_string_upcase_self + s = "hello" + s.upcase! + s + end + "#; + let binary = mrbc_compile("string_upcase_self", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_upcase_self", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "HELLO"); +} + +#[test] +fn string_downcase_test() { + let code = r#" + def test_string_downcase + "HELLO".downcase + end + "#; + let binary = mrbc_compile("string_downcase", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_downcase", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "hello"); +} + +#[test] +fn string_downcase_self_test() { + let code = r#" + def test_string_downcase_self + s = "HELLO" + s.downcase! + s + end + "#; + let binary = mrbc_compile("string_downcase_self", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_downcase_self", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "hello"); +} + +#[test] +fn string_to_i_test() { + let code = r#" + def test_string_to_i + "123".to_i + end + "#; + let binary = mrbc_compile("string_to_i", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_to_i", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 123); +} + +#[test] +fn string_to_f_test() { + let code = r#" + def test_string_to_f + "54.71".to_f + end + "#; + let binary = mrbc_compile("string_to_f", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_to_f", &args).unwrap(); + let result: f64 = result.as_ref().try_into().unwrap(); + assert!((result - 54.71).abs() < f64::EPSILON); +} + +#[test] +fn string_size_test() { + let code = r#" + def test_string_size + "hello".size + end + "#; + let binary = mrbc_compile("string_size", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_string_size", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 5); +} From 27b23a9003bb614c70f10b531f5e204d5f1b9e12 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 1 Feb 2026 22:55:47 +0900 Subject: [PATCH 172/314] Fix regexp --- mrubyedge/src/yamrb/prelude/regexp.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mrubyedge/src/yamrb/prelude/regexp.rs b/mrubyedge/src/yamrb/prelude/regexp.rs index 497f5c5..1dd5956 100644 --- a/mrubyedge/src/yamrb/prelude/regexp.rs +++ b/mrubyedge/src/yamrb/prelude/regexp.rs @@ -110,7 +110,7 @@ fn get_regexp_from_object(obj: &Rc) -> Result { pub fn mrb_regexp_new(vm: &mut VM, args: &[Rc]) -> Result, Error> { let pattern_obj = args[0].clone(); match &pattern_obj.value { - RValue::String(pattern) => { + RValue::String(pattern, _) => { // For simplicity, we only support literal patterns without options. let pattern_str = pattern.clone().borrow().to_owned(); let regexp = RRegexp { @@ -145,7 +145,7 @@ fn mrb_regexp_match_tilda(vm: &mut VM, args: &[Rc]) -> Result s.clone().borrow().to_owned(), + RValue::String(s, _) => s.clone().borrow().to_owned(), _ => { return Err(Error::RuntimeError( "Regexp#=~ requires a string argument".to_string(), @@ -177,7 +177,7 @@ fn mrb_regexp_match(vm: &mut VM, args: &[Rc]) -> Result, Er let regexp = get_regexp_from_object(®exp_obj)?; let target_str = match &target_obj.value { - RValue::String(s) => s.clone().borrow().to_owned(), + RValue::String(s, _) => s.clone().borrow().to_owned(), _ => { return Err(Error::RuntimeError( "Regexp#match requires a string argument".to_string(), From 9c8c94e553b8e6f9393fa2209f1577eb408ebc4d Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 1 Feb 2026 22:59:39 +0900 Subject: [PATCH 173/314] Add borrow_mut methods to RObject --- mrubyedge/src/yamrb/value.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 320cd71..e17d50a 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -508,6 +508,22 @@ impl RObject { } } + pub(crate) fn array_borrow_mut( + &self, + ) -> Result>>, Error> { + match &self.value { + RValue::Array(arr) => Ok(arr.borrow_mut()), + _ => Err(Error::TypeMismatch), + } + } + + pub(crate) fn hash_borrow_mut(&self) -> Result, Error> { + match &self.value { + RValue::Hash(h) => Ok(h.borrow_mut()), + _ => Err(Error::TypeMismatch), + } + } + pub(crate) fn string_is_utf8(&self) -> Result { match &self.value { RValue::String(_, is_utf8) => Ok(is_utf8.get()), From 07d07a9f59c9a0dd420356fd1235f3dcedf8bd54 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 1 Feb 2026 23:43:32 +0900 Subject: [PATCH 174/314] Implement array, hash methods(basic) --- mrubyedge/src/yamrb/prelude/array.rs | 378 ++++++++++++++++++++++++++- mrubyedge/src/yamrb/prelude/hash.rs | 209 +++++++++++++++ 2 files changed, 576 insertions(+), 11 deletions(-) diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index 351ab3f..9b56d98 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -14,27 +14,78 @@ pub(crate) fn initialize_array(vm: &mut VM) { mrb_define_class_cmethod(vm, array_class.clone(), "new", Box::new(mrb_array_new)); + mrb_define_cmethod(vm, array_class.clone(), "+", Box::new(mrb_array_add)); mrb_define_cmethod( vm, array_class.clone(), "push", Box::new(mrb_array_push_self), ); + mrb_define_cmethod(vm, array_class.clone(), "<<", Box::new(mrb_array_push_self)); mrb_define_cmethod( vm, array_class.clone(), "[]", Box::new(mrb_array_get_index_self), ); + mrb_define_cmethod( + vm, + array_class.clone(), + "at", + Box::new(mrb_array_get_index_self), + ); mrb_define_cmethod( vm, array_class.clone(), "[]=", Box::new(mrb_array_set_index_self), ); + mrb_define_cmethod(vm, array_class.clone(), "clear", Box::new(mrb_array_clear)); + mrb_define_cmethod( + vm, + array_class.clone(), + "delete_at", + Box::new(mrb_array_delete_at), + ); mrb_define_cmethod(vm, array_class.clone(), "each", Box::new(mrb_array_each)); + mrb_define_cmethod(vm, array_class.clone(), "empty?", Box::new(mrb_array_empty)); mrb_define_cmethod(vm, array_class.clone(), "size", Box::new(mrb_array_size)); mrb_define_cmethod(vm, array_class.clone(), "length", Box::new(mrb_array_size)); + mrb_define_cmethod(vm, array_class.clone(), "count", Box::new(mrb_array_size)); + mrb_define_cmethod( + vm, + array_class.clone(), + "include?", + Box::new(mrb_array_include), + ); + mrb_define_cmethod(vm, array_class.clone(), "&", Box::new(mrb_array_and)); + mrb_define_cmethod(vm, array_class.clone(), "|", Box::new(mrb_array_or)); + mrb_define_cmethod(vm, array_class.clone(), "first", Box::new(mrb_array_first)); + mrb_define_cmethod(vm, array_class.clone(), "last", Box::new(mrb_array_last)); + mrb_define_cmethod(vm, array_class.clone(), "pop", Box::new(mrb_array_pop)); + mrb_define_cmethod(vm, array_class.clone(), "shift", Box::new(mrb_array_shift)); + mrb_define_cmethod( + vm, + array_class.clone(), + "unshift", + Box::new(mrb_array_unshift), + ); + mrb_define_cmethod(vm, array_class.clone(), "dup", Box::new(mrb_array_dup)); + mrb_define_cmethod(vm, array_class.clone(), "min", Box::new(mrb_array_min)); + mrb_define_cmethod(vm, array_class.clone(), "max", Box::new(mrb_array_max)); + mrb_define_cmethod( + vm, + array_class.clone(), + "minmax", + Box::new(mrb_array_minmax), + ); + mrb_define_cmethod(vm, array_class.clone(), "uniq", Box::new(mrb_array_uniq)); + mrb_define_cmethod( + vm, + array_class.clone(), + "uniq!", + Box::new(mrb_array_uniq_self), + ); mrb_define_cmethod(vm, array_class.clone(), "pack", Box::new(mrb_array_pack)); mrb_define_cmethod( vm, @@ -42,6 +93,8 @@ pub(crate) fn initialize_array(vm: &mut VM) { "inspect", Box::new(mrb_array_inspect), ); + mrb_define_cmethod(vm, array_class.clone(), "to_s", Box::new(mrb_array_inspect)); + mrb_define_cmethod(vm, array_class.clone(), "join", Box::new(mrb_array_join)); } pub fn mrb_array_inspect(vm: &mut VM, _args: &[Rc]) -> Result, Error> { @@ -84,18 +137,12 @@ fn mrb_array_push_self(vm: &mut VM, args: &[Rc]) -> Result, } pub fn mrb_array_push(this: Rc, args: &[Rc]) -> Result, Error> { - match &this.value { - RValue::Array(a) => { - let mut array = a.borrow_mut(); - for arg in args { - array.push(arg.clone()); - } - Ok(this.clone()) - } - _ => Err(Error::RuntimeError( - "Array#push must be called on an Array".to_string(), - )), + let mut array = this.array_borrow_mut()?; + for arg in args { + array.push(arg.clone()); } + drop(array); + Ok(this) } fn mrb_array_get_index_self(vm: &mut VM, args: &[Rc]) -> Result, Error> { @@ -314,6 +361,315 @@ fn mrb_array_size(vm: &mut VM, _args: &[Rc]) -> Result, Err Ok(Rc::new(RObject::integer(value.len() as i64))) } +// Array#+: Returns a new array containing elements from both arrays +fn mrb_array_add(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this: Vec> = vm.getself()?.as_ref().try_into()?; + let other: Vec> = args[0].as_ref().try_into()?; + let mut result = this; + result.extend(other); + Ok(Rc::new(RObject::array(result))) +} + +// Array#clear: Removes all elements from the array (destructive) +fn mrb_array_clear(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + this.array_borrow_mut()?.clear(); + Ok(this) +} + +// Array#delete_at: Deletes the element at the specified index (destructive) +fn mrb_array_delete_at(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let index: i64 = args[0].as_ref().try_into()?; + let mut arr = this.array_borrow_mut()?; + let len = arr.len() as i64; + let idx = if index < 0 { len + index } else { index }; + + if idx < 0 || idx >= len { + return Ok(Rc::new(RObject::nil())); + } + + let removed = arr.remove(idx as usize); + Ok(removed) +} + +// Array#empty?: Returns true if the array contains no elements +fn mrb_array_empty(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: Vec> = vm.getself()?.as_ref().try_into()?; + Ok(Rc::new(RObject::boolean(this.is_empty()))) +} + +// Array#include?: Returns true if the array contains the given object +fn mrb_array_include(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this: Vec> = vm.getself()?.as_ref().try_into()?; + let search = &args[0]; + + for elem in this.iter() { + if elem.as_eq_value() == search.as_eq_value() { + return Ok(Rc::new(RObject::boolean(true))); + } + } + Ok(Rc::new(RObject::boolean(false))) +} + +// Array#&: Set intersection - returns a new array containing elements common to both arrays +fn mrb_array_and(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this: Vec> = vm.getself()?.as_ref().try_into()?; + let other: Vec> = args[0].as_ref().try_into()?; + + let mut result = Vec::new(); + for elem in this.iter() { + let elem_eq = elem.as_eq_value(); + if other.iter().any(|e| e.as_eq_value() == elem_eq) + && !result + .iter() + .any(|e: &Rc| e.as_eq_value() == elem_eq) + { + result.push(elem.clone()); + } + } + Ok(Rc::new(RObject::array(result))) +} + +// Array#|: Set union - returns a new array by joining arrays, excluding duplicates +fn mrb_array_or(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this: Vec> = vm.getself()?.as_ref().try_into()?; + let other: Vec> = args[0].as_ref().try_into()?; + + let mut result = Vec::new(); + for elem in this.iter().chain(other.iter()) { + let elem_eq = elem.as_eq_value(); + if !result + .iter() + .any(|e: &Rc| e.as_eq_value() == elem_eq) + { + result.push(elem.clone()); + } + } + Ok(Rc::new(RObject::array(result))) +} + +// Array#first: Returns the first element, or the first n elements +fn mrb_array_first(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this: Vec> = vm.getself()?.as_ref().try_into()?; + + if args.is_empty() { + Ok(this + .first() + .cloned() + .unwrap_or_else(|| Rc::new(RObject::nil()))) + } else { + let n: i64 = args[0].as_ref().try_into()?; + if n < 0 { + return Err(Error::ArgumentError("negative array size".to_string())); + } + let n = (n as usize).min(this.len()); + Ok(Rc::new(RObject::array(this[..n].to_vec()))) + } +} + +// Array#last: Returns the last element, or the last n elements +fn mrb_array_last(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this: Vec> = vm.getself()?.as_ref().try_into()?; + + if args.is_empty() { + Ok(this + .last() + .cloned() + .unwrap_or_else(|| Rc::new(RObject::nil()))) + } else { + let n: i64 = args[0].as_ref().try_into()?; + if n < 0 { + return Err(Error::ArgumentError("negative array size".to_string())); + } + let n = (n as usize).min(this.len()); + let start = this.len().saturating_sub(n); + Ok(Rc::new(RObject::array(this[start..].to_vec()))) + } +} + +// Array#pop: Removes and returns the last element (destructive) +fn mrb_array_pop(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let removed = this.array_borrow_mut()?.pop(); + Ok(removed.unwrap_or_else(|| Rc::new(RObject::nil()))) +} + +// Array#shift: Removes and returns the first element (destructive) +fn mrb_array_shift(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let mut arr = this.array_borrow_mut()?; + if arr.is_empty() { + Ok(Rc::new(RObject::nil())) + } else { + Ok(arr.remove(0)) + } +} + +// Array#unshift: Prepends objects to the front of the array (destructive) +fn mrb_array_unshift(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let mut arr = this.array_borrow_mut()?; + for (i, arg) in args.iter().enumerate() { + arr.insert(i, arg.clone()); + } + drop(arr); + Ok(this) +} + +// Array#dup: Returns a shallow copy of the array +fn mrb_array_dup(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: Vec> = vm.getself()?.as_ref().try_into()?; + Ok(Rc::new(RObject::array(this))) +} + +// Array#min: Returns the minimum value +// FIXME: this will be moved to Enumerable module +fn mrb_array_min(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: Vec> = vm.getself()?.as_ref().try_into()?; + + if this.is_empty() { + return Ok(Rc::new(RObject::nil())); + } + + let mut min = this[0].clone(); + for elem in this.iter().skip(1) { + let args = vec![elem.clone()]; + let cmp: i64 = helpers::mrb_funcall(vm, Some(min.clone()), "<=>", &args)? + .as_ref() + .try_into()?; + if cmp > 0 { + min = elem.clone(); + } + } + Ok(min) +} + +// Array#max: Returns the maximum value +// FIXME: this will be moved to Enumerable module +fn mrb_array_max(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: Vec> = vm.getself()?.as_ref().try_into()?; + + if this.is_empty() { + return Ok(Rc::new(RObject::nil())); + } + + let mut max = this[0].clone(); + for elem in this.iter().skip(1) { + let args = vec![elem.clone()]; + let cmp: i64 = helpers::mrb_funcall(vm, Some(max.clone()), "<=>", &args)? + .as_ref() + .try_into()?; + if cmp < 0 { + max = elem.clone(); + } + } + Ok(max) +} + +// Array#minmax: Returns a two-element array containing the minimum and maximum values +// FIXME: this will be moved to Enumerable module +fn mrb_array_minmax(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: Vec> = vm.getself()?.as_ref().try_into()?; + + if this.is_empty() { + return Ok(Rc::new(RObject::array(vec![ + Rc::new(RObject::nil()), + Rc::new(RObject::nil()), + ]))); + } + + let mut min = this[0].clone(); + let mut max = this[0].clone(); + + for elem in this.iter().skip(1) { + let args = vec![elem.clone()]; + let cmp_min: i64 = helpers::mrb_funcall(vm, Some(min.clone()), "<=>", &args)? + .as_ref() + .try_into()?; + if cmp_min > 0 { + min = elem.clone(); + } + + let args = vec![elem.clone()]; + let cmp_max: i64 = helpers::mrb_funcall(vm, Some(max.clone()), "<=>", &args)? + .as_ref() + .try_into()?; + if cmp_max < 0 { + max = elem.clone(); + } + } + + Ok(Rc::new(RObject::array(vec![min, max]))) +} + +// Array#uniq: Returns a new array with duplicate values removed +// FIXME: this will be moved to Enumerable module +fn mrb_array_uniq(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: Vec> = vm.getself()?.as_ref().try_into()?; + + let mut result = Vec::new(); + for elem in this.iter() { + let elem_eq = elem.as_eq_value(); + if !result + .iter() + .any(|e: &Rc| e.as_eq_value() == elem_eq) + { + result.push(elem.clone()); + } + } + Ok(Rc::new(RObject::array(result))) +} + +// Array#uniq!: Removes duplicate elements from self (destructive) +// FIXME: this will be moved to Enumerable module +fn mrb_array_uniq_self(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let arr: Vec> = this.as_ref().try_into()?; + + let mut unique = Vec::new(); + for elem in arr.iter() { + let elem_eq = elem.as_eq_value(); + if !unique + .iter() + .any(|e: &Rc| e.as_eq_value() == elem_eq) + { + unique.push(elem.clone()); + } + } + + if unique.len() == arr.len() { + return Ok(Rc::new(RObject::nil())); + } + + *this.array_borrow_mut()? = unique; + Ok(this) +} + +// Array#join: Returns a string created by converting each element to a string, separated by the given separator +fn mrb_array_join(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this: Vec> = vm.getself()?.as_ref().try_into()?; + + let separator = if args.is_empty() { + "".to_string() + } else { + args[0].as_ref().try_into()? + }; + + let mut result = String::new(); + for (i, elem) in this.iter().enumerate() { + let elem_str: String = helpers::mrb_funcall(vm, Some(elem.clone()), "to_s", &[])? + .as_ref() + .try_into()?; + result.push_str(&elem_str); + if i + 1 < this.len() { + result.push_str(&separator); + } + } + + Ok(Rc::new(RObject::string(result))) +} + #[test] fn test_mrb_array_size() { use crate::yamrb::*; diff --git a/mrubyedge/src/yamrb/prelude/hash.rs b/mrubyedge/src/yamrb/prelude/hash.rs index 9f4dc11..95e8c9d 100644 --- a/mrubyedge/src/yamrb/prelude/hash.rs +++ b/mrubyedge/src/yamrb/prelude/hash.rs @@ -26,21 +26,49 @@ pub(crate) fn initialize_hash(vm: &mut VM) { "[]=", Box::new(mrb_hash_set_index_self), ); + mrb_define_cmethod(vm, hash_class.clone(), "clear", Box::new(mrb_hash_clear)); + mrb_define_cmethod(vm, hash_class.clone(), "dup", Box::new(mrb_hash_dup)); mrb_define_cmethod( vm, hash_class.clone(), "delete", Box::new(mrb_hash_delete_self), ); + mrb_define_cmethod(vm, hash_class.clone(), "empty?", Box::new(mrb_hash_empty)); + mrb_define_cmethod( + vm, + hash_class.clone(), + "has_key?", + Box::new(mrb_hash_has_key), + ); + mrb_define_cmethod( + vm, + hash_class.clone(), + "has_value?", + Box::new(mrb_hash_has_value), + ); + mrb_define_cmethod(vm, hash_class.clone(), "key", Box::new(mrb_hash_key)); + mrb_define_cmethod(vm, hash_class.clone(), "keys", Box::new(mrb_hash_keys)); mrb_define_cmethod(vm, hash_class.clone(), "each", Box::new(mrb_hash_each)); mrb_define_cmethod(vm, hash_class.clone(), "size", Box::new(mrb_hash_size)); mrb_define_cmethod(vm, hash_class.clone(), "length", Box::new(mrb_hash_size)); + mrb_define_cmethod(vm, hash_class.clone(), "count", Box::new(mrb_hash_size)); + mrb_define_cmethod(vm, hash_class.clone(), "merge", Box::new(mrb_hash_merge)); + mrb_define_cmethod( + vm, + hash_class.clone(), + "merge!", + Box::new(mrb_hash_merge_self), + ); + mrb_define_cmethod(vm, hash_class.clone(), "to_h", Box::new(mrb_hash_to_h)); + mrb_define_cmethod(vm, hash_class.clone(), "values", Box::new(mrb_hash_values)); mrb_define_cmethod( vm, hash_class.clone(), "inspect", Box::new(mrb_hash_inspect), ); + mrb_define_cmethod(vm, hash_class.clone(), "to_s", Box::new(mrb_hash_inspect)); } pub fn mrb_hash_new(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { @@ -232,6 +260,187 @@ fn mrb_hash_size(vm: &mut VM, _args: &[Rc]) -> Result, Erro Ok(Rc::new(RObject::integer(hash.len() as i64))) } +// Hash#clear: Removes all key-value pairs from the hash (destructive) +fn mrb_hash_clear(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + this.hash_borrow_mut()?.clear(); + Ok(this) +} + +// Hash#dup: Returns a shallow copy of the hash +fn mrb_hash_dup(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let hash = match &this.value { + RValue::Hash(h) => h.borrow().clone(), + _ => { + return Err(Error::RuntimeError( + "Hash#dup must be called on a hash".to_string(), + )); + } + }; + Ok(Rc::new(RObject::hash(hash))) +} + +// Hash#empty?: Returns true if the hash contains no key-value pairs +fn mrb_hash_empty(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let hash = match &this.value { + RValue::Hash(h) => h, + _ => { + return Err(Error::RuntimeError( + "Hash#empty? must be called on a hash".to_string(), + )); + } + }; + Ok(Rc::new(RObject::boolean(hash.borrow().is_empty()))) +} + +// Hash#has_key?: Returns true if the given key is present in the hash +fn mrb_hash_has_key(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let key = args[0].as_hash_key()?; + let hash = match &this.value { + RValue::Hash(h) => h, + _ => { + return Err(Error::RuntimeError( + "Hash#has_key? must be called on a hash".to_string(), + )); + } + }; + Ok(Rc::new(RObject::boolean(hash.borrow().contains_key(&key)))) +} + +// Hash#has_value?: Returns true if the given value is present for some key in the hash +fn mrb_hash_has_value(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let search_value = &args[0]; + let hash = match &this.value { + RValue::Hash(h) => h, + _ => { + return Err(Error::RuntimeError( + "Hash#has_value? must be called on a hash".to_string(), + )); + } + }; + + let search_eq = search_value.as_eq_value(); + for (_, (_, value)) in hash.borrow().iter() { + if value.as_eq_value() == search_eq { + return Ok(Rc::new(RObject::boolean(true))); + } + } + Ok(Rc::new(RObject::boolean(false))) +} + +// Hash#key: Returns the key of an occurrence of a given value +fn mrb_hash_key(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let search_value = &args[0]; + let hash = match &this.value { + RValue::Hash(h) => h, + _ => { + return Err(Error::RuntimeError( + "Hash#key must be called on a hash".to_string(), + )); + } + }; + + let search_eq = search_value.as_eq_value(); + for (_, (key, value)) in hash.borrow().iter() { + if value.as_eq_value() == search_eq { + return Ok(key.clone()); + } + } + Ok(Rc::new(RObject::nil())) +} + +// Hash#keys: Returns a new array populated with the keys from this hash +fn mrb_hash_keys(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let hash = match &this.value { + RValue::Hash(h) => h, + _ => { + return Err(Error::RuntimeError( + "Hash#keys must be called on a hash".to_string(), + )); + } + }; + + let keys: Vec> = hash.borrow().values().map(|(k, _)| k.clone()).collect(); + Ok(RObject::array(keys).to_refcount_assigned()) +} + +// Hash#values: Returns a new array populated with the values from this hash +fn mrb_hash_values(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let hash = match &this.value { + RValue::Hash(h) => h, + _ => { + return Err(Error::RuntimeError( + "Hash#values must be called on a hash".to_string(), + )); + } + }; + + let values: Vec> = hash.borrow().values().map(|(_, v)| v.clone()).collect(); + Ok(RObject::array(values).to_refcount_assigned()) +} + +// Hash#merge: Returns a new hash containing the contents of other_hash and the contents of self +fn mrb_hash_merge(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let other = &args[0]; + + let this_hash = match &this.value { + RValue::Hash(h) => h.borrow().clone(), + _ => { + return Err(Error::RuntimeError( + "Hash#merge must be called on a hash".to_string(), + )); + } + }; + + let other_hash = match &other.value { + RValue::Hash(h) => h, + _ => { + return Err(Error::ArgumentError("argument must be a hash".to_string())); + } + }; + + let mut result = this_hash; + for (key_hash, (key, value)) in other_hash.borrow().iter() { + result.insert(key_hash.clone(), (key.clone(), value.clone())); + } + + Ok(RObject::hash(result).to_refcount_assigned()) +} + +// Hash#merge!: Adds the contents of other_hash to self (destructive) +fn mrb_hash_merge_self(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let other = &args[0]; + + let other_hash = match &other.value { + RValue::Hash(h) => h, + _ => { + return Err(Error::ArgumentError("argument must be a hash".to_string())); + } + }; + + let mut this_hash = this.hash_borrow_mut()?; + for (key_hash, (key, value)) in other_hash.borrow().iter() { + this_hash.insert(key_hash.clone(), (key.clone(), value.clone())); + } + drop(this_hash); + + Ok(this) +} + +// Hash#to_h: Returns self +fn mrb_hash_to_h(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + vm.getself() +} + #[test] fn test_mrb_hash_size() { let mut vm = VM::empty(); From 695e4b92444e611a86a6a33e6755c9d7590a90cb Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 00:10:06 +0900 Subject: [PATCH 175/314] Add array tests --- mrubyedge/src/yamrb/prelude/array.rs | 20 ++ mrubyedge/src/yamrb/prelude/object.rs | 83 ++++++ mrubyedge/tests/array.rs | 401 ++++++++++++++++++++++++++ 3 files changed, 504 insertions(+) create mode 100644 mrubyedge/tests/array.rs diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index 9b56d98..5296bd9 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -133,6 +133,11 @@ pub fn mrb_array_new(_vm: &mut VM, args: &[Rc]) -> Result, fn mrb_array_push_self(vm: &mut VM, args: &[Rc]) -> Result, Error> { let this = vm.getself()?; + let args = if args[args.len() - 1].as_ref().is_nil() { + &args[..args.len() - 1] + } else { + args + }; mrb_array_push(this, args) } @@ -452,6 +457,11 @@ fn mrb_array_or(vm: &mut VM, args: &[Rc]) -> Result, Error> // Array#first: Returns the first element, or the first n elements fn mrb_array_first(vm: &mut VM, args: &[Rc]) -> Result, Error> { let this: Vec> = vm.getself()?.as_ref().try_into()?; + let args = if args[args.len() - 1].as_ref().is_nil() { + &args[..args.len() - 1] + } else { + args + }; if args.is_empty() { Ok(this @@ -471,6 +481,11 @@ fn mrb_array_first(vm: &mut VM, args: &[Rc]) -> Result, Err // Array#last: Returns the last element, or the last n elements fn mrb_array_last(vm: &mut VM, args: &[Rc]) -> Result, Error> { let this: Vec> = vm.getself()?.as_ref().try_into()?; + let args = if args[args.len() - 1].as_ref().is_nil() { + &args[..args.len() - 1] + } else { + args + }; if args.is_empty() { Ok(this @@ -510,6 +525,11 @@ fn mrb_array_shift(vm: &mut VM, _args: &[Rc]) -> Result, Er fn mrb_array_unshift(vm: &mut VM, args: &[Rc]) -> Result, Error> { let this = vm.getself()?; let mut arr = this.array_borrow_mut()?; + let args = if args[args.len() - 1].as_ref().is_nil() { + &args[..args.len() - 1] + } else { + args + }; for (i, arg) in args.iter().enumerate() { arr.insert(i, arg.clone()); } diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index a28bf90..95e6eff 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -97,6 +97,12 @@ pub(crate) fn initialize_object(vm: &mut VM) { "class", Box::new(mrb_object_class), ); + mrb_define_cmethod( + vm, + object_class.clone(), + "<=>", + Box::new(mrb_object_compare), + ); mrb_define_cmethod( vm, object_class.clone(), @@ -203,6 +209,83 @@ pub fn mrb_object_triple_eq(vm: &mut VM, args: &[Rc]) -> Result: Comparison operator (returns -1, 0, or 1) +pub fn mrb_object_compare(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let lhs = vm.getself()?; + let rhs = &args[0]; + + // Use as_eq_value for comparison + let lhs_val = lhs.as_eq_value(); + let rhs_val = rhs.as_eq_value(); + + use ValueEquality::*; + let result = match (&lhs_val, &rhs_val) { + (Integer(a), Integer(b)) => { + if a < b { + -1 + } else if a > b { + 1 + } else { + 0 + } + } + (Float(a), Float(b)) => { + if a < b { + -1 + } else if a > b { + 1 + } else { + 0 + } + } + (String(a), String(b)) => { + if a < b { + -1 + } else if a > b { + 1 + } else { + 0 + } + } + (Integer(a), Float(b)) => { + let a_float = *a as f64; + if a_float < *b { + -1 + } else if a_float > *b { + 1 + } else { + 0 + } + } + (Float(a), Integer(b)) => { + let b_float = *b as f64; + if a < &b_float { + -1 + } else if a > &b_float { + 1 + } else { + 0 + } + } + (Symbol(a), Symbol(b)) => { + if a < b { + -1 + } else if a > b { + 1 + } else { + 0 + } + } + _ => { + return Err(Error::ArgumentError( + "comparison of incompatible types".to_string(), + )); + } + }; + + Ok(Rc::new(RObject::integer(result))) +} + pub fn mrb_object_object_id(vm: &mut VM, _args: &[Rc]) -> Result, Error> { // Abstract method; do nothing let x = vm.getself()?.object_id.get(); diff --git a/mrubyedge/tests/array.rs b/mrubyedge/tests/array.rs new file mode 100644 index 0000000..bd87510 --- /dev/null +++ b/mrubyedge/tests/array.rs @@ -0,0 +1,401 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; + +use helpers::*; + +#[test] +fn array_add_test() { + let code = r#" + def test_array_add + [1, 2] + [3, 4] + end + "#; + let binary = mrbc_compile("array_add", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_add", &args).unwrap(); + let result = result.as_vec_owned().unwrap(); + assert_eq!(result.len(), 4); +} + +#[test] +fn array_push_test() { + let code = r#" + def test_array_push + a = [1, 2] + a << 3 + a + end + "#; + let binary = mrbc_compile("array_push", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_push", &args).unwrap(); + let result = result.as_vec_owned().unwrap(); + assert_eq!(result.len(), 3); +} + +#[test] +fn array_at_test() { + let code = r#" + def test_array_at + [1, 2, 3].at(1) + end + "#; + let binary = mrbc_compile("array_at", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_at", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 2); +} + +#[test] +fn array_clear_test() { + let code = r#" + def test_array_clear + a = [1, 2, 3] + a.clear + a.size + end + "#; + let binary = mrbc_compile("array_clear", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_clear", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 0); +} + +#[test] +fn array_delete_at_test() { + let code = r#" + def test_array_delete_at + a = [1, 2, 3] + a.delete_at(1) + end + "#; + let binary = mrbc_compile("array_delete_at", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_delete_at", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 2); +} + +#[test] +fn array_empty_test() { + let code = r#" + def test_array_empty + [].empty? + end + "#; + let binary = mrbc_compile("array_empty", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_empty", &args).unwrap(); + let result: bool = result.as_ref().try_into().unwrap(); + assert!(result); +} + +#[test] +fn array_include_test() { + let code = r#" + def test_array_include + [1, 2, 3].include?(2) + end + "#; + let binary = mrbc_compile("array_include", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_include", &args).unwrap(); + let result: bool = result.as_ref().try_into().unwrap(); + assert!(result); +} + +#[test] +fn array_and_test() { + let code = r#" + def test_array_and + ([1, 2, 3] & [2, 3, 4]).size + end + "#; + let binary = mrbc_compile("array_and", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_and", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 2); +} + +#[test] +fn array_or_test() { + let code = r#" + def test_array_or + ([1, 2] | [2, 3]).size + end + "#; + let binary = mrbc_compile("array_or", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_or", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 3); +} + +#[test] +fn array_first_test() { + let code = r#" + def test_array_first + [1, 2, 3].first + end + "#; + let binary = mrbc_compile("array_first", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_first", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 1); +} + +#[test] +fn array_last_test() { + let code = r#" + def test_array_last + [1, 2, 3].last + end + "#; + let binary = mrbc_compile("array_last", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_last", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 3); +} + +#[test] +fn array_pop_test() { + let code = r#" + def test_array_pop + a = [1, 2, 3] + a.pop + end + "#; + let binary = mrbc_compile("array_pop", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_pop", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 3); +} + +#[test] +fn array_shift_test() { + let code = r#" + def test_array_shift + a = [1, 2, 3] + a.shift + end + "#; + let binary = mrbc_compile("array_shift", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_shift", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 1); +} + +#[test] +fn array_unshift_test() { + let code = r#" + def test_array_unshift + a = [2, 3] + a.unshift(1) + a.first + end + "#; + let binary = mrbc_compile("array_unshift", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_unshift", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 1); +} + +#[test] +fn array_dup_test() { + let code = r#" + def test_array_dup + a = [1, 2, 3] + b = a.dup + b == a + end + "#; + let binary = mrbc_compile("array_dup", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_dup", &args).unwrap(); + let result: bool = result.as_ref().try_into().unwrap(); + assert!(result); +} + +#[test] +fn array_min_test() { + let code = r#" + def test_array_min + [3, 1, 2].min + end + "#; + let binary = mrbc_compile("array_min", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_min", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 1); +} + +#[test] +fn array_max_test() { + let code = r#" + def test_array_max + [3, 1, 2].max + end + "#; + let binary = mrbc_compile("array_max", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_max", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 3); +} + +#[test] +fn array_minmax_test() { + let code = r#" + def test_array_minmax + [3, 1, 2].minmax + end + "#; + let binary = mrbc_compile("array_minmax", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_minmax", &args).unwrap(); + let result = result.as_vec_owned().unwrap(); + assert_eq!(result.len(), 2); + let min: i64 = result[0].as_ref().try_into().unwrap(); + let max: i64 = result[1].as_ref().try_into().unwrap(); + assert_eq!(min, 1); + assert_eq!(max, 3); +} + +#[test] +fn array_uniq_test() { + let code = r#" + def test_array_uniq + [1, 2, 2, 3].uniq + end + "#; + let binary = mrbc_compile("array_uniq", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_uniq", &args).unwrap(); + let result = result.as_vec_owned().unwrap(); + assert_eq!(result.len(), 3); +} + +#[test] +fn array_uniq_self_test() { + let code = r#" + def test_array_uniq_self + a = [1, 2, 2, 3] + a.uniq! + a.size + end + "#; + let binary = mrbc_compile("array_uniq_self", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_uniq_self", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 3); +} + +#[test] +fn array_join_test() { + let code = r#" + def test_array_join + [1, 2, 3].join(",") + end + "#; + let binary = mrbc_compile("array_join", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_join", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "1,2,3"); +} From de200a05546e9c0456f44ae5112190712722762a Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 00:11:31 +0900 Subject: [PATCH 176/314] Add Hash tests --- mrubyedge/tests/hash.rs | 215 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 215 insertions(+) diff --git a/mrubyedge/tests/hash.rs b/mrubyedge/tests/hash.rs index eaa3f11..d05a064 100644 --- a/mrubyedge/tests/hash.rs +++ b/mrubyedge/tests/hash.rs @@ -148,3 +148,218 @@ fn hash_each_test_2() { assert!(value.contains("bar")); assert!(value.contains("baz")); } + +#[test] +fn hash_clear_test() { + let code = r#" + def test_hash_clear + h = {"a" => 1, "b" => 2} + h.clear + h.size + end + "#; + let binary = mrbc_compile("hash_clear", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_hash_clear", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 0); +} + +#[test] +fn hash_dup_test() { + let code = r#" + def test_hash_dup + h = {"a" => 1} + h2 = h.dup + h2["a"] + end + "#; + let binary = mrbc_compile("hash_dup", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_hash_dup", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 1); +} + +#[test] +fn hash_empty_test() { + let code = r#" + def test_hash_empty + {}.empty? + end + "#; + let binary = mrbc_compile("hash_empty", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_hash_empty", &args).unwrap(); + let result: bool = result.as_ref().try_into().unwrap(); + assert!(result); +} + +#[test] +fn hash_has_key_test() { + let code = r#" + def test_hash_has_key + h = {"a" => 1, "b" => 2} + h.has_key?("a") + end + "#; + let binary = mrbc_compile("hash_has_key", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_hash_has_key", &args).unwrap(); + let result: bool = result.as_ref().try_into().unwrap(); + assert!(result); +} + +#[test] +fn hash_has_value_test() { + let code = r#" + def test_hash_has_value + h = {"a" => 1, "b" => 2} + h.has_value?(2) + end + "#; + let binary = mrbc_compile("hash_has_value", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_hash_has_value", &args).unwrap(); + let result: bool = result.as_ref().try_into().unwrap(); + assert!(result); +} + +#[test] +fn hash_key_test() { + let code = r#" + def test_hash_key + h = {"a" => 1, "b" => 2} + h.key(2) + end + "#; + let binary = mrbc_compile("hash_key", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_hash_key", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "b"); +} + +#[test] +fn hash_keys_test() { + let code = r#" + def test_hash_keys + h = {"a" => 1, "b" => 2} + h.keys.size + end + "#; + let binary = mrbc_compile("hash_keys", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_hash_keys", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 2); +} + +#[test] +fn hash_values_test() { + let code = r#" + def test_hash_values + h = {"a" => 1, "b" => 2} + h.values.size + end + "#; + let binary = mrbc_compile("hash_values", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_hash_values", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 2); +} + +#[test] +fn hash_merge_test() { + let code = r#" + def test_hash_merge + h1 = {"a" => 1, "b" => 2} + h2 = {"b" => 3, "c" => 4} + h3 = h1.merge(h2) + h3.size + end + "#; + let binary = mrbc_compile("hash_merge", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_hash_merge", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 3); +} + +#[test] +fn hash_merge_self_test() { + let code = r#" + def test_hash_merge_self + h1 = {"a" => 1, "b" => 2} + h2 = {"b" => 3, "c" => 4} + h1.merge!(h2) + h1.size + end + "#; + let binary = mrbc_compile("hash_merge_self", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_hash_merge_self", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 3); +} + +#[test] +fn hash_to_h_test() { + let code = r#" + def test_hash_to_h + h = {"a" => 1} + h2 = h.to_h + h2.object_id == h.object_id + end + "#; + let binary = mrbc_compile("hash_to_h", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_hash_to_h", &args).unwrap(); + let result: bool = result.as_ref().try_into().unwrap(); + assert!(result); +} From f2ec8163906f21edbd18b98a443a38262f00a5ff Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 00:23:44 +0900 Subject: [PATCH 177/314] Support rust level proc calls in mrb_call_block --- mrubyedge/src/yamrb/helpers.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index 19c3914..a88bbd4 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -124,7 +124,12 @@ pub fn mrb_call_block( return_reg: None, }); vm.current_breadcrumb.replace(new_breadcrumb); - let res = call_block(vm, block, recv, args, None, return_register); + let res = if block.is_rb_func { + call_block(vm, block, recv, args, None, return_register) + } else { + let func = vm.fn_table.get(block.func.unwrap()).unwrap(); + func(vm, args) + }; let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); if let Some(upper) = &cur.as_ref().upper { vm.current_breadcrumb.replace(upper.clone()); From 9eb8144140e657bbd2dad8c44b306c71a6203c5c Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 12:08:51 +0900 Subject: [PATCH 178/314] PoC of rust closure call from mruby block --- mrubyedge/src/yamrb/helpers.rs | 17 +++++++++-- mrubyedge/src/yamrb/optable.rs | 3 ++ mrubyedge/src/yamrb/prelude/array.rs | 45 ++++++++++++++++++++++++++-- mrubyedge/src/yamrb/value.rs | 12 ++++++++ mrubyedge/src/yamrb/vm.rs | 12 ++++++++ mrubyedge/tests/enumerable.rs | 24 +++++++++++++++ 6 files changed, 109 insertions(+), 4 deletions(-) create mode 100644 mrubyedge/tests/enumerable.rs diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index a88bbd4..53ed76e 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -126,9 +126,18 @@ pub fn mrb_call_block( vm.current_breadcrumb.replace(new_breadcrumb); let res = if block.is_rb_func { call_block(vm, block, recv, args, None, return_register) + } else if block.is_fnmut { + let mut func = vm + .fnmut_buf + .take() + .ok_or_else(|| Error::Internal("[BUG] fnmut not registered".to_string()))?; + let res = func(vm, args); + vm.push_fnmut(func); + res } else { - let func = vm.fn_table.get(block.func.unwrap()).unwrap(); - func(vm, args) + Err(Error::RuntimeError( + "Cannot call non-block RProc".to_string(), + )) }; let cur = vm.current_breadcrumb.take().expect("not found breadcrumb"); if let Some(upper) = &cur.as_ref().upper { @@ -270,6 +279,7 @@ pub fn mrb_define_cmethod(vm: &mut VM, klass: Rc, name: &str, cmethod: R let index = vm.register_fn(cmethod); let method = RProc { is_rb_func: false, + is_fnmut: false, sym_id: Some(RSym::new(name.to_string())), next: None, irep: None, @@ -298,6 +308,7 @@ pub fn mrb_define_class_cmethod(vm: &mut VM, klass: Rc, name: &str, cmet let index = vm.register_fn(cmethod); let method = RProc { is_rb_func: false, + is_fnmut: false, sym_id: Some(RSym::new(name.to_string())), next: None, irep: None, @@ -324,6 +335,7 @@ pub fn mrb_define_singleton_cmethod(vm: &mut VM, dest: Rc, name: &str, let index = vm.register_fn(cmethod); let method = RProc { is_rb_func: false, + is_fnmut: false, sym_id: Some(RSym::new(name.to_string())), next: None, irep: None, @@ -364,6 +376,7 @@ pub fn mrb_define_module_cmethod(vm: &mut VM, module: Rc, name: &str, c let index = vm.register_fn(cmethod); let method = RProc { is_rb_func: false, + is_fnmut: false, sym_id: Some(RSym::new(name.to_string())), next: None, irep: None, diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 37c54d7..61d7793 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1761,6 +1761,7 @@ pub(crate) fn op_lambda(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { value: RValue::Proc(RProc { irep, is_rb_func: true, + is_fnmut: false, sym_id: Some("".into()), next: None, func: None, @@ -1794,6 +1795,7 @@ pub(crate) fn op_block(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { value: RValue::Proc(RProc { irep, is_rb_func: true, + is_fnmut: false, sym_id: Some("".into()), next: None, func: None, @@ -1816,6 +1818,7 @@ pub(crate) fn op_method(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { value: super::value::RValue::Proc(super::value::RProc { irep, is_rb_func: true, + is_fnmut: false, sym_id: None, next: None, func: None, diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index 5296bd9..c01ea65 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -3,8 +3,10 @@ use std::rc::Rc; use crate::{ Error, yamrb::{ - helpers::{self, mrb_call_block, mrb_define_class_cmethod, mrb_define_cmethod}, - value::{RObject, RValue}, + helpers::{ + self, mrb_call_block, mrb_define_class_cmethod, mrb_define_cmethod, mrb_funcall, + }, + value::{RFnMut, RObject, RProc, RValue}, vm::VM, }, }; @@ -48,6 +50,7 @@ pub(crate) fn initialize_array(vm: &mut VM) { Box::new(mrb_array_delete_at), ); mrb_define_cmethod(vm, array_class.clone(), "each", Box::new(mrb_array_each)); + mrb_define_cmethod(vm, array_class.clone(), "map", Box::new(mrb_array_map)); mrb_define_cmethod(vm, array_class.clone(), "empty?", Box::new(mrb_array_empty)); mrb_define_cmethod(vm, array_class.clone(), "size", Box::new(mrb_array_size)); mrb_define_cmethod(vm, array_class.clone(), "length", Box::new(mrb_array_size)); @@ -690,6 +693,44 @@ fn mrb_array_join(vm: &mut VM, args: &[Rc]) -> Result, Erro Ok(Rc::new(RObject::string(result))) } +fn mrb_array_map(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let original_block = args + .last() + .cloned() + .ok_or_else(|| Error::ArgumentError("block should be specified".to_string()))?; + let results: Rc = RObject::array(vec![]).to_refcount_assigned(); + let results_ref = results.clone(); + let wrapping_block: RFnMut = Box::new(move |vm: &mut VM, args: &[Rc]| { + let block = original_block.clone(); + let result = mrb_call_block(vm, block, None, args, 0)?; + mrb_funcall( + vm, + Some(results_ref.clone()), + "push", + std::slice::from_ref(&result), + )?; + Ok(result) + }); + + let block = RProc { + is_rb_func: false, + is_fnmut: true, + sym_id: None, + next: None, + irep: None, + func: Some(999), + environ: None, + block_self: vm.getself().ok(), + }; + let this = vm.getself()?; + let block = RObject::proc(block).to_refcount_assigned(); + vm.push_fnmut(wrapping_block); + mrb_funcall(vm, Some(this.clone()), "each", &[block])?; + vm.pop_fnmut(); + + Ok(results) +} + #[test] fn test_mrb_array_size() { use crate::yamrb::*; diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index e17d50a..f607086 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -296,6 +296,16 @@ impl RObject { } } + pub fn proc(p: RProc) -> Self { + RObject { + tt: RType::Proc, + value: RValue::Proc(p), + object_id: (UNSET_OBJECT_ID).into(), + singleton_class: RefCell::new(None), + ivar: RefCell::new(RHashMap::default()), + } + } + pub fn exception(e: Rc) -> Self { RObject { tt: RType::Exception, @@ -1087,6 +1097,7 @@ pub struct RData { #[derive(Debug, Clone)] pub struct RProc { pub is_rb_func: bool, + pub is_fnmut: bool, pub sym_id: Option, pub next: Option>, pub irep: Option>, @@ -1097,6 +1108,7 @@ pub struct RProc { /// Native Rust callable used to implement Ruby methods in the VM. pub type RFn = Box]) -> Result, Error>>; +pub type RFnMut = Box]) -> Result, Error>>; /// Interned symbol name used across the VM to identify methods and constants. #[derive(Debug, Clone, PartialEq, Eq, Hash)] diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 6070616..383a2c5 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -99,6 +99,7 @@ pub struct VM { pub has_env_ref: RHashMap, pub fn_table: RFnTable, + pub fnmut_buf: Option, } pub struct RFnTable { @@ -208,6 +209,7 @@ impl VM { let exception = None; let flag_preemption = Cell::new(false); let fn_table = RFnTable::new(); + let fnmut_buf = None; let upper = None; let cur_env = RHashMap::default(); let has_env_ref = RHashMap::default(); @@ -236,6 +238,7 @@ impl VM { cur_env, has_env_ref, fn_table, + fnmut_buf, }; prelude(&mut vm); @@ -452,6 +455,15 @@ impl VM { self.fn_table.len() - 1 } + pub(crate) fn push_fnmut(&mut self, f: RFnMut) { + self.fnmut_buf = Some(f); + } + + #[allow(dead_code)] + pub(crate) fn pop_fnmut(&mut self) { + self.fnmut_buf.take(); + } + pub(crate) fn get_fn(&self, i: usize) -> Option> { self.fn_table.get(i) } diff --git a/mrubyedge/tests/enumerable.rs b/mrubyedge/tests/enumerable.rs new file mode 100644 index 0000000..0463376 --- /dev/null +++ b/mrubyedge/tests/enumerable.rs @@ -0,0 +1,24 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; + +use helpers::*; + +#[test] +fn array_map_basic_test() { + let code = r#" + def test_array_map + [1, 2, 3].map { |x| x * 2 } + end + "#; + let binary = mrbc_compile("array_map_basic", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_map", &args).unwrap(); + let result: (i32, i32, i32) = result.as_ref().try_into().unwrap(); + assert_eq!(result, (2, 4, 6)); +} From 7c033b40b8c5ac9e6469909186b13e9f311c7737 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 12:48:14 +0900 Subject: [PATCH 179/314] Support nested case --- mrubyedge/src/yamrb/helpers.rs | 17 +++---- mrubyedge/src/yamrb/optable.rs | 6 +-- mrubyedge/src/yamrb/prelude/array.rs | 10 ++-- mrubyedge/src/yamrb/value.rs | 4 +- mrubyedge/src/yamrb/vm.rs | 70 ++++++++++++++++++++++++---- mrubyedge/tests/enumerable.rs | 26 +++++++++++ 6 files changed, 102 insertions(+), 31 deletions(-) diff --git a/mrubyedge/src/yamrb/helpers.rs b/mrubyedge/src/yamrb/helpers.rs index 53ed76e..01f918f 100644 --- a/mrubyedge/src/yamrb/helpers.rs +++ b/mrubyedge/src/yamrb/helpers.rs @@ -126,13 +126,10 @@ pub fn mrb_call_block( vm.current_breadcrumb.replace(new_breadcrumb); let res = if block.is_rb_func { call_block(vm, block, recv, args, None, return_register) - } else if block.is_fnmut { - let mut func = vm - .fnmut_buf - .take() - .ok_or_else(|| Error::Internal("[BUG] fnmut not registered".to_string()))?; + } else if block.is_fnblock { + let func = vm.pop_fnblock()?; let res = func(vm, args); - vm.push_fnmut(func); + vm.push_fnblock(func)?; res } else { Err(Error::RuntimeError( @@ -279,7 +276,7 @@ pub fn mrb_define_cmethod(vm: &mut VM, klass: Rc, name: &str, cmethod: R let index = vm.register_fn(cmethod); let method = RProc { is_rb_func: false, - is_fnmut: false, + is_fnblock: false, sym_id: Some(RSym::new(name.to_string())), next: None, irep: None, @@ -308,7 +305,7 @@ pub fn mrb_define_class_cmethod(vm: &mut VM, klass: Rc, name: &str, cmet let index = vm.register_fn(cmethod); let method = RProc { is_rb_func: false, - is_fnmut: false, + is_fnblock: false, sym_id: Some(RSym::new(name.to_string())), next: None, irep: None, @@ -335,7 +332,7 @@ pub fn mrb_define_singleton_cmethod(vm: &mut VM, dest: Rc, name: &str, let index = vm.register_fn(cmethod); let method = RProc { is_rb_func: false, - is_fnmut: false, + is_fnblock: false, sym_id: Some(RSym::new(name.to_string())), next: None, irep: None, @@ -376,7 +373,7 @@ pub fn mrb_define_module_cmethod(vm: &mut VM, module: Rc, name: &str, c let index = vm.register_fn(cmethod); let method = RProc { is_rb_func: false, - is_fnmut: false, + is_fnblock: false, sym_id: Some(RSym::new(name.to_string())), next: None, irep: None, diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 61d7793..0921957 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1761,7 +1761,7 @@ pub(crate) fn op_lambda(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { value: RValue::Proc(RProc { irep, is_rb_func: true, - is_fnmut: false, + is_fnblock: false, sym_id: Some("".into()), next: None, func: None, @@ -1795,7 +1795,7 @@ pub(crate) fn op_block(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { value: RValue::Proc(RProc { irep, is_rb_func: true, - is_fnmut: false, + is_fnblock: false, sym_id: Some("".into()), next: None, func: None, @@ -1818,7 +1818,7 @@ pub(crate) fn op_method(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { value: super::value::RValue::Proc(super::value::RProc { irep, is_rb_func: true, - is_fnmut: false, + is_fnblock: false, sym_id: None, next: None, func: None, diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index c01ea65..b467848 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -6,7 +6,7 @@ use crate::{ helpers::{ self, mrb_call_block, mrb_define_class_cmethod, mrb_define_cmethod, mrb_funcall, }, - value::{RFnMut, RObject, RProc, RValue}, + value::{RFn, RObject, RProc, RValue}, vm::VM, }, }; @@ -700,7 +700,7 @@ fn mrb_array_map(vm: &mut VM, args: &[Rc]) -> Result, Error .ok_or_else(|| Error::ArgumentError("block should be specified".to_string()))?; let results: Rc = RObject::array(vec![]).to_refcount_assigned(); let results_ref = results.clone(); - let wrapping_block: RFnMut = Box::new(move |vm: &mut VM, args: &[Rc]| { + let wrapping_block: RFn = Box::new(move |vm: &mut VM, args: &[Rc]| { let block = original_block.clone(); let result = mrb_call_block(vm, block, None, args, 0)?; mrb_funcall( @@ -714,7 +714,7 @@ fn mrb_array_map(vm: &mut VM, args: &[Rc]) -> Result, Error let block = RProc { is_rb_func: false, - is_fnmut: true, + is_fnblock: true, sym_id: None, next: None, irep: None, @@ -724,9 +724,9 @@ fn mrb_array_map(vm: &mut VM, args: &[Rc]) -> Result, Error }; let this = vm.getself()?; let block = RObject::proc(block).to_refcount_assigned(); - vm.push_fnmut(wrapping_block); + vm.push_fnblock(Rc::new(wrapping_block))?; mrb_funcall(vm, Some(this.clone()), "each", &[block])?; - vm.pop_fnmut(); + vm.pop_fnblock()?; Ok(results) } diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index f607086..888d0d8 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -1097,7 +1097,7 @@ pub struct RData { #[derive(Debug, Clone)] pub struct RProc { pub is_rb_func: bool, - pub is_fnmut: bool, + pub is_fnblock: bool, pub sym_id: Option, pub next: Option>, pub irep: Option>, @@ -1108,8 +1108,6 @@ pub struct RProc { /// Native Rust callable used to implement Ruby methods in the VM. pub type RFn = Box]) -> Result, Error>>; -pub type RFnMut = Box]) -> Result, Error>>; - /// Interned symbol name used across the VM to identify methods and constants. #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct RSym { diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 383a2c5..96b43ef 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -99,7 +99,7 @@ pub struct VM { pub has_env_ref: RHashMap, pub fn_table: RFnTable, - pub fnmut_buf: Option, + pub fn_block_stack: RFnStack, } pub struct RFnTable { @@ -109,8 +109,8 @@ pub struct RFnTable { impl RFnTable { #[allow(clippy::new_without_default)] - pub fn new() -> RFnTable { - RFnTable { + pub fn new() -> Self { + Self { size: Cell::new(0), table: array::from_fn(|_| MaybeUninit::uninit()), } @@ -146,6 +146,57 @@ impl RFnTable { } } +pub struct RFnStack { + pub size: Cell, + pub stack: [Option>; 64], +} + +impl RFnStack { + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self { + size: Cell::new(0), + stack: array::from_fn(|_| None), + } + } + + pub fn push(&mut self, f: Rc) -> Result<(), Error> { + let i = self.size.get(); + if i >= self.stack.len() { + return Err(Error::internal("RFnStack overflow")); + } + + self.stack[i] = Some(f); + let size = self.size.get(); + if i >= size { + self.size.set(i + 1); + } + Ok(()) + } + + pub fn pop(&self) -> Result, Error> { + let i = self.size.get(); + if i == 0 { + return Err(Error::internal("RFnStack underflow")); + } + + self.size.set(i - 1); + + self.stack[i - 1] + .as_ref() + .cloned() + .ok_or_else(|| Error::internal("RFnStack invalid state")) + } + + pub fn len(&self) -> usize { + self.size.get() + } + + pub fn is_empty(&self) -> bool { + self.size.get() == 0 + } +} + impl VM { /// Builds a VM from a parsed Rite chunk, consuming the bytecode and /// preparing the VM so it can be executed via [`VM::run`]. @@ -209,7 +260,7 @@ impl VM { let exception = None; let flag_preemption = Cell::new(false); let fn_table = RFnTable::new(); - let fnmut_buf = None; + let fn_block_stack = RFnStack::new(); let upper = None; let cur_env = RHashMap::default(); let has_env_ref = RHashMap::default(); @@ -238,7 +289,7 @@ impl VM { cur_env, has_env_ref, fn_table, - fnmut_buf, + fn_block_stack, }; prelude(&mut vm); @@ -455,13 +506,12 @@ impl VM { self.fn_table.len() - 1 } - pub(crate) fn push_fnmut(&mut self, f: RFnMut) { - self.fnmut_buf = Some(f); + pub(crate) fn push_fnblock(&mut self, f: Rc) -> Result<(), Error> { + self.fn_block_stack.push(f) } - #[allow(dead_code)] - pub(crate) fn pop_fnmut(&mut self) { - self.fnmut_buf.take(); + pub(crate) fn pop_fnblock(&mut self) -> Result, Error> { + self.fn_block_stack.pop() } pub(crate) fn get_fn(&self, i: usize) -> Option> { diff --git a/mrubyedge/tests/enumerable.rs b/mrubyedge/tests/enumerable.rs index 0463376..57ac49a 100644 --- a/mrubyedge/tests/enumerable.rs +++ b/mrubyedge/tests/enumerable.rs @@ -3,7 +3,10 @@ extern crate mrubyedge; mod helpers; +use std::rc::Rc; + use helpers::*; +use mrubyedge::yamrb::value::RObject; #[test] fn array_map_basic_test() { @@ -22,3 +25,26 @@ fn array_map_basic_test() { let result: (i32, i32, i32) = result.as_ref().try_into().unwrap(); assert_eq!(result, (2, 4, 6)); } + +#[test] +fn array_map_nested_test() { + let code = r#" + def array_map_nested + [[1,1,1], [2,2,2], [3,3,3]].map { |arr| arr.map { |x| x * 2 } } + end + "#; + let binary = mrbc_compile("array_map_nested", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "array_map_nested", &args).unwrap(); + let result_array: Vec> = result.as_ref().try_into().unwrap(); + let r0: (i32, i32, i32) = result_array[0].as_ref().try_into().unwrap(); + let r1: (i32, i32, i32) = result_array[1].as_ref().try_into().unwrap(); + let r2: (i32, i32, i32) = result_array[2].as_ref().try_into().unwrap(); + assert_eq!(r0, (2, 2, 2)); + assert_eq!(r1, (4, 4, 4)); + assert_eq!(r2, (6, 6, 6)); +} From e4500303d046c6afbe273f40e37294e0eb3f8ea3 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 13:53:13 +0900 Subject: [PATCH 180/314] Add Enumerable#find --- mrubyedge/src/yamrb/prelude/array.rs | 68 ++++++++++++++++++++++------ mrubyedge/tests/enumerable.rs | 35 ++++++++++++++ 2 files changed, 90 insertions(+), 13 deletions(-) diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index b467848..c15f165 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -1,4 +1,4 @@ -use std::rc::Rc; +use std::{cell::Cell, rc::Rc}; use crate::{ Error, @@ -50,7 +50,10 @@ pub(crate) fn initialize_array(vm: &mut VM) { Box::new(mrb_array_delete_at), ); mrb_define_cmethod(vm, array_class.clone(), "each", Box::new(mrb_array_each)); + // TODO: move to enumerable mrb_define_cmethod(vm, array_class.clone(), "map", Box::new(mrb_array_map)); + mrb_define_cmethod(vm, array_class.clone(), "find", Box::new(mrb_array_find)); + // TODO end mrb_define_cmethod(vm, array_class.clone(), "empty?", Box::new(mrb_array_empty)); mrb_define_cmethod(vm, array_class.clone(), "size", Box::new(mrb_array_size)); mrb_define_cmethod(vm, array_class.clone(), "length", Box::new(mrb_array_size)); @@ -693,6 +696,21 @@ fn mrb_array_join(vm: &mut VM, args: &[Rc]) -> Result, Erro Ok(Rc::new(RObject::string(result))) } +fn rproc_from_rust_block(vm: &mut VM, rfn: RFn) -> Result, Error> { + vm.push_fnblock(Rc::new(rfn))?; + let block = RProc { + is_rb_func: false, + is_fnblock: true, + sym_id: None, + next: None, + irep: None, + func: None, + environ: None, + block_self: vm.getself().ok(), + }; + Ok(RObject::proc(block).to_refcount_assigned()) +} + fn mrb_array_map(vm: &mut VM, args: &[Rc]) -> Result, Error> { let original_block = args .last() @@ -712,25 +730,49 @@ fn mrb_array_map(vm: &mut VM, args: &[Rc]) -> Result, Error Ok(result) }); - let block = RProc { - is_rb_func: false, - is_fnblock: true, - sym_id: None, - next: None, - irep: None, - func: Some(999), - environ: None, - block_self: vm.getself().ok(), - }; let this = vm.getself()?; - let block = RObject::proc(block).to_refcount_assigned(); - vm.push_fnblock(Rc::new(wrapping_block))?; + let block = rproc_from_rust_block(vm, wrapping_block)?; mrb_funcall(vm, Some(this.clone()), "each", &[block])?; vm.pop_fnblock()?; Ok(results) } +fn mrb_array_find(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let original_block = args + .last() + .cloned() + .ok_or_else(|| Error::ArgumentError("block should be specified".to_string()))?; + let found = Cell::new(false); + let result_box: Rc = RObject::array(vec![]).to_refcount_assigned(); + let result_box_ref = result_box.clone(); + let wrapping_block: RFn = Box::new(move |vm: &mut VM, args: &[Rc]| { + if found.get() { + return Ok(Rc::new(RObject::nil())); + } + + let block = original_block.clone(); + let result = mrb_call_block(vm, block, None, args, 0)?; + if result.is_truthy() { + mrb_funcall( + vm, + Some(result_box_ref.clone()), + "push", + std::slice::from_ref(&args[0]), + )?; + found.set(true); + } + Ok(result) + }); + let this = vm.getself()?; + let block = rproc_from_rust_block(vm, wrapping_block)?; + mrb_funcall(vm, Some(this.clone()), "each", &[block])?; + vm.pop_fnblock()?; + + let found = mrb_funcall(vm, result_box.into(), "pop", &[])?; + Ok(found) +} + #[test] fn test_mrb_array_size() { use crate::yamrb::*; diff --git a/mrubyedge/tests/enumerable.rs b/mrubyedge/tests/enumerable.rs index 57ac49a..f8ac10e 100644 --- a/mrubyedge/tests/enumerable.rs +++ b/mrubyedge/tests/enumerable.rs @@ -48,3 +48,38 @@ fn array_map_nested_test() { assert_eq!(r1, (4, 4, 4)); assert_eq!(r2, (6, 6, 6)); } + +#[test] +fn array_find_found_test() { + let code = r#" + def test_array_find_found + [1, 2, 3, 4, 5].find { |x| x > 3 } + end + "#; + let binary = mrbc_compile("array_find_found", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_find_found", &args).unwrap(); + let result_value: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_value, 4); +} + +#[test] +fn array_find_not_found_test() { + let code = r#" + def test_array_find_not_found + [1, 2, 3, 4, 5].find { |x| x > 10 } + end + "#; + let binary = mrbc_compile("array_find_not_found", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_find_not_found", &args).unwrap(); + assert!(result.is_nil()); +} From a5d16d30975d30e433d0237b89d5b0d4408507f8 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 14:15:51 +0900 Subject: [PATCH 181/314] Introduced Enumerable module and included it in Array and Hash --- mrubyedge/src/yamrb/prelude/array.rs | 93 ++----------------- mrubyedge/src/yamrb/prelude/enumerable.rs | 106 ++++++++++++++++++++++ mrubyedge/src/yamrb/prelude/hash.rs | 4 + mrubyedge/src/yamrb/prelude/mod.rs | 2 + mrubyedge/src/yamrb/prelude/module.rs | 24 ++--- mrubyedge/src/yamrb/vm.rs | 10 ++ 6 files changed, 142 insertions(+), 97 deletions(-) create mode 100644 mrubyedge/src/yamrb/prelude/enumerable.rs diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index c15f165..c9325fa 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -1,12 +1,11 @@ -use std::{cell::Cell, rc::Rc}; +use std::rc::Rc; use crate::{ Error, yamrb::{ - helpers::{ - self, mrb_call_block, mrb_define_class_cmethod, mrb_define_cmethod, mrb_funcall, - }, - value::{RFn, RObject, RProc, RValue}, + helpers::{self, mrb_call_block, mrb_define_class_cmethod, mrb_define_cmethod}, + prelude::module::mrb_include_module, + value::{RObject, RValue}, vm::VM, }, }; @@ -50,10 +49,6 @@ pub(crate) fn initialize_array(vm: &mut VM) { Box::new(mrb_array_delete_at), ); mrb_define_cmethod(vm, array_class.clone(), "each", Box::new(mrb_array_each)); - // TODO: move to enumerable - mrb_define_cmethod(vm, array_class.clone(), "map", Box::new(mrb_array_map)); - mrb_define_cmethod(vm, array_class.clone(), "find", Box::new(mrb_array_find)); - // TODO end mrb_define_cmethod(vm, array_class.clone(), "empty?", Box::new(mrb_array_empty)); mrb_define_cmethod(vm, array_class.clone(), "size", Box::new(mrb_array_size)); mrb_define_cmethod(vm, array_class.clone(), "length", Box::new(mrb_array_size)); @@ -101,6 +96,9 @@ pub(crate) fn initialize_array(vm: &mut VM) { ); mrb_define_cmethod(vm, array_class.clone(), "to_s", Box::new(mrb_array_inspect)); mrb_define_cmethod(vm, array_class.clone(), "join", Box::new(mrb_array_join)); + + let enumerable_module = vm.get_module_by_name("Enumerable"); + mrb_include_module(&array_class, enumerable_module).expect("failed to include Enumerable"); } pub fn mrb_array_inspect(vm: &mut VM, _args: &[Rc]) -> Result, Error> { @@ -696,83 +694,6 @@ fn mrb_array_join(vm: &mut VM, args: &[Rc]) -> Result, Erro Ok(Rc::new(RObject::string(result))) } -fn rproc_from_rust_block(vm: &mut VM, rfn: RFn) -> Result, Error> { - vm.push_fnblock(Rc::new(rfn))?; - let block = RProc { - is_rb_func: false, - is_fnblock: true, - sym_id: None, - next: None, - irep: None, - func: None, - environ: None, - block_self: vm.getself().ok(), - }; - Ok(RObject::proc(block).to_refcount_assigned()) -} - -fn mrb_array_map(vm: &mut VM, args: &[Rc]) -> Result, Error> { - let original_block = args - .last() - .cloned() - .ok_or_else(|| Error::ArgumentError("block should be specified".to_string()))?; - let results: Rc = RObject::array(vec![]).to_refcount_assigned(); - let results_ref = results.clone(); - let wrapping_block: RFn = Box::new(move |vm: &mut VM, args: &[Rc]| { - let block = original_block.clone(); - let result = mrb_call_block(vm, block, None, args, 0)?; - mrb_funcall( - vm, - Some(results_ref.clone()), - "push", - std::slice::from_ref(&result), - )?; - Ok(result) - }); - - let this = vm.getself()?; - let block = rproc_from_rust_block(vm, wrapping_block)?; - mrb_funcall(vm, Some(this.clone()), "each", &[block])?; - vm.pop_fnblock()?; - - Ok(results) -} - -fn mrb_array_find(vm: &mut VM, args: &[Rc]) -> Result, Error> { - let original_block = args - .last() - .cloned() - .ok_or_else(|| Error::ArgumentError("block should be specified".to_string()))?; - let found = Cell::new(false); - let result_box: Rc = RObject::array(vec![]).to_refcount_assigned(); - let result_box_ref = result_box.clone(); - let wrapping_block: RFn = Box::new(move |vm: &mut VM, args: &[Rc]| { - if found.get() { - return Ok(Rc::new(RObject::nil())); - } - - let block = original_block.clone(); - let result = mrb_call_block(vm, block, None, args, 0)?; - if result.is_truthy() { - mrb_funcall( - vm, - Some(result_box_ref.clone()), - "push", - std::slice::from_ref(&args[0]), - )?; - found.set(true); - } - Ok(result) - }); - let this = vm.getself()?; - let block = rproc_from_rust_block(vm, wrapping_block)?; - mrb_funcall(vm, Some(this.clone()), "each", &[block])?; - vm.pop_fnblock()?; - - let found = mrb_funcall(vm, result_box.into(), "pop", &[])?; - Ok(found) -} - #[test] fn test_mrb_array_size() { use crate::yamrb::*; diff --git a/mrubyedge/src/yamrb/prelude/enumerable.rs b/mrubyedge/src/yamrb/prelude/enumerable.rs new file mode 100644 index 0000000..d4f040a --- /dev/null +++ b/mrubyedge/src/yamrb/prelude/enumerable.rs @@ -0,0 +1,106 @@ +use std::{cell::Cell, rc::Rc}; + +use crate::{ + Error, + yamrb::{ + helpers::{mrb_call_block, mrb_define_module_cmethod, mrb_funcall}, + value::{RFn, RObject, RProc}, + vm::VM, + }, +}; + +pub(crate) fn initialize_enumerable(vm: &mut VM) { + let enumerable_module = vm.define_module("Enumerable", None); + + mrb_define_module_cmethod( + vm, + enumerable_module.clone(), + "map", + Box::new(mrb_enumerable_map), + ); + mrb_define_module_cmethod( + vm, + enumerable_module.clone(), + "find", + Box::new(mrb_enumerable_find), + ); +} + +fn rproc_from_rust_block(vm: &mut VM, rfn: RFn) -> Result, Error> { + vm.push_fnblock(Rc::new(rfn))?; + let block = RProc { + is_rb_func: false, + is_fnblock: true, + sym_id: None, + next: None, + irep: None, + func: None, + environ: None, + block_self: vm.getself().ok(), + }; + Ok(RObject::proc(block).to_refcount_assigned()) +} + +// Enumerable#map: Returns a new array with the results of running block once for every element +fn mrb_enumerable_map(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let original_block = args + .last() + .cloned() + .ok_or_else(|| Error::ArgumentError("block should be specified".to_string()))?; + let results: Rc = RObject::array(vec![]).to_refcount_assigned(); + let results_ref = results.clone(); + let wrapping_block: RFn = Box::new(move |vm: &mut VM, args: &[Rc]| { + let block = original_block.clone(); + let result = mrb_call_block(vm, block, None, args, 0)?; + mrb_funcall( + vm, + Some(results_ref.clone()), + "push", + std::slice::from_ref(&result), + )?; + Ok(result) + }); + + let this = vm.getself()?; + let block = rproc_from_rust_block(vm, wrapping_block)?; + mrb_funcall(vm, Some(this.clone()), "each", &[block])?; + vm.pop_fnblock()?; + + Ok(results) +} + +// Enumerable#find: Returns the first element for which the block returns true +fn mrb_enumerable_find(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let original_block = args + .last() + .cloned() + .ok_or_else(|| Error::ArgumentError("block should be specified".to_string()))?; + let found = Cell::new(false); + let result_box: Rc = RObject::array(vec![]).to_refcount_assigned(); + let result_box_ref = result_box.clone(); + let wrapping_block: RFn = Box::new(move |vm: &mut VM, args: &[Rc]| { + if found.get() { + return Ok(Rc::new(RObject::nil())); + } + + let block = original_block.clone(); + let result = mrb_call_block(vm, block, None, args, 0)?; + if result.is_truthy() { + mrb_funcall( + vm, + Some(result_box_ref.clone()), + "push", + std::slice::from_ref(&args[0]), + )?; + found.set(true); + } + Ok(result) + }); + let this = vm.getself()?; + let block = rproc_from_rust_block(vm, wrapping_block)?; + mrb_funcall(vm, Some(this.clone()), "each", &[block])?; + vm.pop_fnblock()?; + + let found = mrb_funcall(vm, result_box.into(), "pop", &[])?; + Ok(found) +} diff --git a/mrubyedge/src/yamrb/prelude/hash.rs b/mrubyedge/src/yamrb/prelude/hash.rs index 95e8c9d..b503225 100644 --- a/mrubyedge/src/yamrb/prelude/hash.rs +++ b/mrubyedge/src/yamrb/prelude/hash.rs @@ -4,6 +4,7 @@ use crate::{ Error, yamrb::{ helpers::{mrb_call_block, mrb_call_inspect, mrb_define_class_cmethod, mrb_define_cmethod}, + prelude::module::mrb_include_module, value::{RHashMap, RObject, RValue}, vm::VM, }, @@ -69,6 +70,9 @@ pub(crate) fn initialize_hash(vm: &mut VM) { Box::new(mrb_hash_inspect), ); mrb_define_cmethod(vm, hash_class.clone(), "to_s", Box::new(mrb_hash_inspect)); + + let enumerable_module = vm.get_module_by_name("Enumerable"); + mrb_include_module(&hash_class, enumerable_module).expect("failed to include Enumerable"); } pub fn mrb_hash_new(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { diff --git a/mrubyedge/src/yamrb/prelude/mod.rs b/mrubyedge/src/yamrb/prelude/mod.rs index 5975363..70a3d4f 100644 --- a/mrubyedge/src/yamrb/prelude/mod.rs +++ b/mrubyedge/src/yamrb/prelude/mod.rs @@ -6,6 +6,7 @@ use super::vm::VM; pub mod array; pub mod class; +pub mod enumerable; pub mod exception; pub mod falseclass; pub mod float; @@ -36,6 +37,7 @@ pub fn prelude(vm: &mut VM) { symbol::initialize_symbol(vm); proc::initialize_proc(vm); string::initialize_string(vm); + enumerable::initialize_enumerable(vm); array::initialize_array(vm); hash::initialize_hash(vm); range::initialize_range(vm); diff --git a/mrubyedge/src/yamrb/prelude/module.rs b/mrubyedge/src/yamrb/prelude/module.rs index a30fe0b..0ed01ce 100644 --- a/mrubyedge/src/yamrb/prelude/module.rs +++ b/mrubyedge/src/yamrb/prelude/module.rs @@ -28,33 +28,35 @@ fn mrb_module_include(vm: &mut VM, args: &[Rc]) -> Result, )); } - let self_obj = vm.getself()?; - let target_module = match &self_obj.value { - RValue::Class(klass) => klass.module.clone(), + let arg0 = &args[0]; + let mixin = match &arg0.value { RValue::Module(module) => module.clone(), _ => { return Err(Error::RuntimeError( - "Module#include must be called on class or module".to_string(), + "Module#include expects module arguments".to_string(), )); } }; - let arg0 = &args[0]; - let mixin = match &arg0.value { - RValue::Module(module) => module.clone(), + let self_obj = vm.getself()?; + match &self_obj.value { + RValue::Class(klass) => mrb_include_module(klass, mixin)?, + RValue::Module(module) => mrb_include_module(module, mixin)?, _ => { return Err(Error::RuntimeError( - "Module#include expects module arguments".to_string(), + "Module#include must be called on class or module".to_string(), )); } }; - include_module(&target_module, mixin)?; Ok(self_obj) } -fn include_module(target: &Rc, mixin: Rc) -> Result<(), Error> { - if Rc::ptr_eq(target, &mixin) { +/// Public helper. +/// Includes `mixin` module into `target`. +pub fn mrb_include_module(target: &impl AsModule, mixin: Rc) -> Result<(), Error> { + let target = target.as_module(); + if Rc::ptr_eq(&target, &mixin) { return Err(Error::RuntimeError("cannot include itself".to_string())); } diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 96b43ef..49c9087 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -527,6 +527,16 @@ impl VM { .unwrap_or_else(|| panic!("Class {} not found", name)) } + pub fn get_module_by_name(&self, name: &str) -> Rc { + match self.consts.get(name).cloned() { + Some(obj) => match &obj.value { + RValue::Module(m) => m.clone(), + _ => panic!("Module {} not found", name), + }, + None => panic!("Module {} not found", name), + } + } + pub fn get_const_by_name(&self, name: &str) -> Option> { self.consts.get(name).cloned() } From aded12718956253ebf8b1ac02b6fb8bd2c045c72 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 18:47:50 +0900 Subject: [PATCH 182/314] Move enumerable methods --- mrubyedge/src/yamrb/prelude/array.rs | 195 ++++----- mrubyedge/src/yamrb/prelude/enumerable.rs | 488 ++++++++++++++++++++++ 2 files changed, 577 insertions(+), 106 deletions(-) diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index c9325fa..a33dce1 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -3,7 +3,9 @@ use std::rc::Rc; use crate::{ Error, yamrb::{ - helpers::{self, mrb_call_block, mrb_define_class_cmethod, mrb_define_cmethod}, + helpers::{ + self, mrb_call_block, mrb_define_class_cmethod, mrb_define_cmethod, mrb_funcall, + }, prelude::module::mrb_include_module, value::{RObject, RValue}, vm::VM, @@ -52,7 +54,6 @@ pub(crate) fn initialize_array(vm: &mut VM) { mrb_define_cmethod(vm, array_class.clone(), "empty?", Box::new(mrb_array_empty)); mrb_define_cmethod(vm, array_class.clone(), "size", Box::new(mrb_array_size)); mrb_define_cmethod(vm, array_class.clone(), "length", Box::new(mrb_array_size)); - mrb_define_cmethod(vm, array_class.clone(), "count", Box::new(mrb_array_size)); mrb_define_cmethod( vm, array_class.clone(), @@ -72,20 +73,41 @@ pub(crate) fn initialize_array(vm: &mut VM) { Box::new(mrb_array_unshift), ); mrb_define_cmethod(vm, array_class.clone(), "dup", Box::new(mrb_array_dup)); - mrb_define_cmethod(vm, array_class.clone(), "min", Box::new(mrb_array_min)); - mrb_define_cmethod(vm, array_class.clone(), "max", Box::new(mrb_array_max)); mrb_define_cmethod( vm, array_class.clone(), - "minmax", - Box::new(mrb_array_minmax), + "uniq!", + Box::new(mrb_array_uniq_self), ); - mrb_define_cmethod(vm, array_class.clone(), "uniq", Box::new(mrb_array_uniq)); mrb_define_cmethod( vm, array_class.clone(), - "uniq!", - Box::new(mrb_array_uniq_self), + "map!", + Box::new(mrb_array_map_self), + ); + mrb_define_cmethod( + vm, + array_class.clone(), + "select!", + Box::new(mrb_array_select_self), + ); + mrb_define_cmethod( + vm, + array_class.clone(), + "reject!", + Box::new(mrb_array_reject_self), + ); + mrb_define_cmethod( + vm, + array_class.clone(), + "sort!", + Box::new(mrb_array_sort_self), + ); + mrb_define_cmethod( + vm, + array_class.clone(), + "sort_by!", + Box::new(mrb_array_sort_by_self), ); mrb_define_cmethod(vm, array_class.clone(), "pack", Box::new(mrb_array_pack)); mrb_define_cmethod( @@ -547,126 +569,87 @@ fn mrb_array_dup(vm: &mut VM, _args: &[Rc]) -> Result, Erro Ok(Rc::new(RObject::array(this))) } -// Array#min: Returns the minimum value -// FIXME: this will be moved to Enumerable module -fn mrb_array_min(vm: &mut VM, _args: &[Rc]) -> Result, Error> { - let this: Vec> = vm.getself()?.as_ref().try_into()?; - - if this.is_empty() { - return Ok(Rc::new(RObject::nil())); - } - - let mut min = this[0].clone(); - for elem in this.iter().skip(1) { - let args = vec![elem.clone()]; - let cmp: i64 = helpers::mrb_funcall(vm, Some(min.clone()), "<=>", &args)? - .as_ref() - .try_into()?; - if cmp > 0 { - min = elem.clone(); - } - } - Ok(min) -} +// Array#uniq!: Removes duplicate elements from self (destructive) +fn mrb_array_uniq_self(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let arr: Vec> = this.as_ref().try_into()?; -// Array#max: Returns the maximum value -// FIXME: this will be moved to Enumerable module -fn mrb_array_max(vm: &mut VM, _args: &[Rc]) -> Result, Error> { - let this: Vec> = vm.getself()?.as_ref().try_into()?; + let unique: Vec> = mrb_funcall(vm, Some(this.clone()), "uniq", &[])? + .as_ref() + .try_into()?; - if this.is_empty() { + if unique.len() == arr.len() { return Ok(Rc::new(RObject::nil())); } - let mut max = this[0].clone(); - for elem in this.iter().skip(1) { - let args = vec![elem.clone()]; - let cmp: i64 = helpers::mrb_funcall(vm, Some(max.clone()), "<=>", &args)? - .as_ref() - .try_into()?; - if cmp < 0 { - max = elem.clone(); - } - } - Ok(max) + *this.array_borrow_mut()? = unique; + Ok(this) } -// Array#minmax: Returns a two-element array containing the minimum and maximum values -// FIXME: this will be moved to Enumerable module -fn mrb_array_minmax(vm: &mut VM, _args: &[Rc]) -> Result, Error> { - let this: Vec> = vm.getself()?.as_ref().try_into()?; +// Array#map!: Invokes the given block once for each element, replacing the element with the value returned by the block (destructive) +fn mrb_array_map_self(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let mapped: Vec> = mrb_funcall(vm, Some(this.clone()), "map", args)? + .as_ref() + .try_into()?; - if this.is_empty() { - return Ok(Rc::new(RObject::array(vec![ - Rc::new(RObject::nil()), - Rc::new(RObject::nil()), - ]))); - } + *this.array_borrow_mut()? = mapped; + Ok(this) +} - let mut min = this[0].clone(); - let mut max = this[0].clone(); +// Array#select!: Invokes the given block for each element, keeping only elements for which the block returns true (destructive) +fn mrb_array_select_self(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let arr: Vec> = this.as_ref().try_into()?; - for elem in this.iter().skip(1) { - let args = vec![elem.clone()]; - let cmp_min: i64 = helpers::mrb_funcall(vm, Some(min.clone()), "<=>", &args)? - .as_ref() - .try_into()?; - if cmp_min > 0 { - min = elem.clone(); - } + let selected: Vec> = mrb_funcall(vm, Some(this.clone()), "select", args)? + .as_ref() + .try_into()?; - let args = vec![elem.clone()]; - let cmp_max: i64 = helpers::mrb_funcall(vm, Some(max.clone()), "<=>", &args)? - .as_ref() - .try_into()?; - if cmp_max < 0 { - max = elem.clone(); - } + if selected.len() == arr.len() { + return Ok(Rc::new(RObject::nil())); } - Ok(Rc::new(RObject::array(vec![min, max]))) + *this.array_borrow_mut()? = selected; + Ok(this) } -// Array#uniq: Returns a new array with duplicate values removed -// FIXME: this will be moved to Enumerable module -fn mrb_array_uniq(vm: &mut VM, _args: &[Rc]) -> Result, Error> { - let this: Vec> = vm.getself()?.as_ref().try_into()?; +// Array#reject!: Invokes the given block for each element, removing elements for which the block returns true (destructive) +fn mrb_array_reject_self(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let arr: Vec> = this.as_ref().try_into()?; - let mut result = Vec::new(); - for elem in this.iter() { - let elem_eq = elem.as_eq_value(); - if !result - .iter() - .any(|e: &Rc| e.as_eq_value() == elem_eq) - { - result.push(elem.clone()); - } + let rejected: Vec> = mrb_funcall(vm, Some(this.clone()), "delete_if", args)? + .as_ref() + .try_into()?; + + if rejected.len() == arr.len() { + return Ok(Rc::new(RObject::nil())); } - Ok(Rc::new(RObject::array(result))) + + *this.array_borrow_mut()? = rejected; + Ok(this) } -// Array#uniq!: Removes duplicate elements from self (destructive) -// FIXME: this will be moved to Enumerable module -fn mrb_array_uniq_self(vm: &mut VM, _args: &[Rc]) -> Result, Error> { +// Array#sort!: Sorts the array in place (destructive) +fn mrb_array_sort_self(vm: &mut VM, _args: &[Rc]) -> Result, Error> { let this = vm.getself()?; - let arr: Vec> = this.as_ref().try_into()?; + let sorted: Vec> = mrb_funcall(vm, Some(this.clone()), "sort", &[])? + .as_ref() + .try_into()?; - let mut unique = Vec::new(); - for elem in arr.iter() { - let elem_eq = elem.as_eq_value(); - if !unique - .iter() - .any(|e: &Rc| e.as_eq_value() == elem_eq) - { - unique.push(elem.clone()); - } - } + *this.array_borrow_mut()? = sorted; + Ok(this) +} - if unique.len() == arr.len() { - return Ok(Rc::new(RObject::nil())); - } +// Array#sort_by!: Sorts the array in place by the result of the block (destructive) +fn mrb_array_sort_by_self(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let sorted: Vec> = mrb_funcall(vm, Some(this.clone()), "sort_by", args)? + .as_ref() + .try_into()?; - *this.array_borrow_mut()? = unique; + *this.array_borrow_mut()? = sorted; Ok(this) } diff --git a/mrubyedge/src/yamrb/prelude/enumerable.rs b/mrubyedge/src/yamrb/prelude/enumerable.rs index d4f040a..90296d0 100644 --- a/mrubyedge/src/yamrb/prelude/enumerable.rs +++ b/mrubyedge/src/yamrb/prelude/enumerable.rs @@ -24,6 +24,90 @@ pub(crate) fn initialize_enumerable(vm: &mut VM) { "find", Box::new(mrb_enumerable_find), ); + mrb_define_module_cmethod( + vm, + enumerable_module.clone(), + "select", + Box::new(mrb_enumerable_select), + ); + mrb_define_module_cmethod( + vm, + enumerable_module.clone(), + "all?", + Box::new(mrb_enumerable_all), + ); + mrb_define_module_cmethod( + vm, + enumerable_module.clone(), + "any?", + Box::new(mrb_enumerable_any), + ); + mrb_define_module_cmethod( + vm, + enumerable_module.clone(), + "delete_if", + Box::new(mrb_enumerable_delete_if), + ); + mrb_define_module_cmethod( + vm, + enumerable_module.clone(), + "each_with_index", + Box::new(mrb_enumerable_each_with_index), + ); + mrb_define_module_cmethod( + vm, + enumerable_module.clone(), + "sort", + Box::new(mrb_enumerable_sort), + ); + mrb_define_module_cmethod( + vm, + enumerable_module.clone(), + "sort_by", + Box::new(mrb_enumerable_sort_by), + ); + mrb_define_module_cmethod( + vm, + enumerable_module.clone(), + "max", + Box::new(mrb_enumerable_max), + ); + mrb_define_module_cmethod( + vm, + enumerable_module.clone(), + "min", + Box::new(mrb_enumerable_min), + ); + mrb_define_module_cmethod( + vm, + enumerable_module.clone(), + "minmax", + Box::new(mrb_enumerable_minmax), + ); + mrb_define_module_cmethod( + vm, + enumerable_module.clone(), + "compact", + Box::new(mrb_enumerable_compact), + ); + mrb_define_module_cmethod( + vm, + enumerable_module.clone(), + "count", + Box::new(mrb_enumerable_count), + ); + mrb_define_module_cmethod( + vm, + enumerable_module.clone(), + "to_a", + Box::new(mrb_enumerable_to_a), + ); + mrb_define_module_cmethod( + vm, + enumerable_module.clone(), + "uniq", + Box::new(mrb_enumerable_uniq), + ); } fn rproc_from_rust_block(vm: &mut VM, rfn: RFn) -> Result, Error> { @@ -41,6 +125,28 @@ fn rproc_from_rust_block(vm: &mut VM, rfn: RFn) -> Result, Error> { Ok(RObject::proc(block).to_refcount_assigned()) } +// Enumerable#to_a: Returns an array containing all elements +fn mrb_enumerable_to_a(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let results: Rc = RObject::array(vec![]).to_refcount_assigned(); + let results_ref = results.clone(); + let wrapping_block: RFn = Box::new(move |vm: &mut VM, args: &[Rc]| { + mrb_funcall( + vm, + Some(results_ref.clone()), + "push", + std::slice::from_ref(&args[0]), + )?; + Ok(Rc::new(RObject::nil())) + }); + + let this = vm.getself()?; + let block = rproc_from_rust_block(vm, wrapping_block)?; + mrb_funcall(vm, Some(this.clone()), "each", &[block])?; + vm.pop_fnblock()?; + + Ok(results) +} + // Enumerable#map: Returns a new array with the results of running block once for every element fn mrb_enumerable_map(vm: &mut VM, args: &[Rc]) -> Result, Error> { let original_block = args @@ -104,3 +210,385 @@ fn mrb_enumerable_find(vm: &mut VM, args: &[Rc]) -> Result, let found = mrb_funcall(vm, result_box.into(), "pop", &[])?; Ok(found) } + +// Enumerable#select: Returns a new array containing all elements for which the block returns true +fn mrb_enumerable_select(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let original_block = args + .last() + .cloned() + .ok_or_else(|| Error::ArgumentError("block should be specified".to_string()))?; + let results: Rc = RObject::array(vec![]).to_refcount_assigned(); + let results_ref = results.clone(); + let wrapping_block: RFn = Box::new(move |vm: &mut VM, args: &[Rc]| { + let block = original_block.clone(); + let result = mrb_call_block(vm, block, None, args, 0)?; + if result.is_truthy() { + mrb_funcall( + vm, + Some(results_ref.clone()), + "push", + std::slice::from_ref(&args[0]), + )?; + } + Ok(result) + }); + + let this = vm.getself()?; + let block = rproc_from_rust_block(vm, wrapping_block)?; + mrb_funcall(vm, Some(this.clone()), "each", &[block])?; + vm.pop_fnblock()?; + + Ok(results) +} + +// Enumerable#all?: Returns true if all elements match the condition +fn mrb_enumerable_all(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let original_block = args + .last() + .cloned() + .ok_or_else(|| Error::ArgumentError("block should be specified".to_string()))?; + let all_true = Rc::new(Cell::new(true)); + let all_true_ref = all_true.clone(); + let wrapping_block: RFn = Box::new(move |vm: &mut VM, args: &[Rc]| { + if !all_true_ref.get() { + return Ok(Rc::new(RObject::nil())); + } + + let block = original_block.clone(); + let result = mrb_call_block(vm, block, None, args, 0)?; + if !result.is_truthy() { + all_true_ref.set(false); + } + Ok(result) + }); + + let this = vm.getself()?; + let block = rproc_from_rust_block(vm, wrapping_block)?; + mrb_funcall(vm, Some(this.clone()), "each", &[block])?; + vm.pop_fnblock()?; + + Ok(Rc::new(RObject::boolean(all_true.get()))) +} + +// Enumerable#any?: Returns true if any element matches the condition +fn mrb_enumerable_any(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let original_block = args + .last() + .cloned() + .ok_or_else(|| Error::ArgumentError("block should be specified".to_string()))?; + let found_true = Rc::new(Cell::new(false)); + let found_true_ref = found_true.clone(); + let wrapping_block: RFn = Box::new(move |vm: &mut VM, args: &[Rc]| { + if found_true_ref.get() { + return Ok(Rc::new(RObject::nil())); + } + + let block = original_block.clone(); + let result = mrb_call_block(vm, block, None, args, 0)?; + if result.is_truthy() { + found_true_ref.set(true); + } + Ok(result) + }); + + let this = vm.getself()?; + let block = rproc_from_rust_block(vm, wrapping_block)?; + mrb_funcall(vm, Some(this.clone()), "each", &[block])?; + vm.pop_fnblock()?; + + Ok(Rc::new(RObject::boolean(found_true.get()))) +} + +// Enumerable#delete_if: Deletes every element for which block evaluates to true +fn mrb_enumerable_delete_if(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let original_block = args + .last() + .cloned() + .ok_or_else(|| Error::ArgumentError("block should be specified".to_string()))?; + let results: Rc = RObject::array(vec![]).to_refcount_assigned(); + let results_ref = results.clone(); + let wrapping_block: RFn = Box::new(move |vm: &mut VM, args: &[Rc]| { + let block = original_block.clone(); + let result = mrb_call_block(vm, block, None, args, 0)?; + if !result.is_truthy() { + mrb_funcall( + vm, + Some(results_ref.clone()), + "push", + std::slice::from_ref(&args[0]), + )?; + } + Ok(result) + }); + + let this = vm.getself()?; + let block = rproc_from_rust_block(vm, wrapping_block)?; + mrb_funcall(vm, Some(this.clone()), "each", &[block])?; + vm.pop_fnblock()?; + + Ok(results) +} + +// Enumerable#each_with_index: Calls block with two arguments, the item and its index +fn mrb_enumerable_each_with_index(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let original_block = args + .last() + .cloned() + .ok_or_else(|| Error::ArgumentError("block should be specified".to_string()))?; + let index = Rc::new(Cell::new(0i64)); + let index_ref = index.clone(); + let wrapping_block: RFn = Box::new(move |vm: &mut VM, args: &[Rc]| { + let block = original_block.clone(); + let idx = index_ref.get(); + let index_obj = Rc::new(RObject::integer(idx)); + let block_args = vec![args[0].clone(), index_obj]; + let result = mrb_call_block(vm, block, None, &block_args, 0)?; + index_ref.set(idx + 1); + Ok(result) + }); + + let this = vm.getself()?; + let block = rproc_from_rust_block(vm, wrapping_block)?; + mrb_funcall(vm, Some(this.clone()), "each", &[block])?; + vm.pop_fnblock()?; + + Ok(this) +} + +// Enumerable#sort: Returns an array with sorted elements +fn mrb_enumerable_sort(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let array = mrb_funcall(vm, Some(this), "to_a", &[])?; + let mut collected: Vec> = array.as_ref().try_into()?; + + collected.sort_by(|a, b| { + let args = vec![b.clone()]; + let cmp_result = mrb_funcall(vm, Some(a.clone()), "<=>", &args); + match cmp_result { + Ok(cmp_obj) => { + let cmp_val: Result = cmp_obj.as_ref().try_into(); + match cmp_val { + Ok(v) => v.cmp(&0), + Err(_) => std::cmp::Ordering::Equal, + } + } + Err(_) => std::cmp::Ordering::Equal, + } + }); + + Ok(RObject::array(collected).to_refcount_assigned()) +} + +// Enumerable#sort_by: Returns an array with elements sorted by the block's return value +fn mrb_enumerable_sort_by(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let original_block = args + .last() + .cloned() + .ok_or_else(|| Error::ArgumentError("block should be specified".to_string()))?; + + // Collect elements first using to_a + let this = vm.getself()?; + let array = mrb_funcall(vm, Some(this), "to_a", &[])?; + let elements: Vec> = array.as_ref().try_into()?; + + // Collect keys by calling the block on each element + let mut sort_keys: Vec> = Vec::new(); + for elem in &elements { + let key = mrb_call_block( + vm, + original_block.clone(), + None, + std::slice::from_ref(elem), + 0, + )?; + sort_keys.push(key); + } + + let mut elements = elements; + let mut sort_keys = sort_keys; + + let mut pairs: Vec<(Rc, Rc)> = + elements.drain(..).zip(sort_keys.drain(..)).collect(); + + pairs.sort_by(|a, b| { + let args = vec![b.1.clone()]; + let cmp_result = mrb_funcall(vm, Some(a.1.clone()), "<=>", &args); + match cmp_result { + Ok(cmp_obj) => { + let cmp_val: Result = cmp_obj.as_ref().try_into(); + match cmp_val { + Ok(v) => v.cmp(&0), + Err(_) => std::cmp::Ordering::Equal, + } + } + Err(_) => std::cmp::Ordering::Equal, + } + }); + + let sorted: Vec> = pairs.into_iter().map(|(elem, _)| elem).collect(); + Ok(RObject::array(sorted).to_refcount_assigned()) +} + +// Enumerable#max: Returns the maximum element +fn mrb_enumerable_max(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let array = mrb_funcall(vm, Some(this), "to_a", &[])?; + let collected: Vec> = array.as_ref().try_into()?; + + if collected.is_empty() { + return Ok(Rc::new(RObject::nil())); + } + + let mut max = collected[0].clone(); + for elem in collected.iter().skip(1) { + let args = vec![elem.clone()]; + let cmp: i64 = mrb_funcall(vm, Some(max.clone()), "<=>", &args)? + .as_ref() + .try_into()?; + if cmp < 0 { + max = elem.clone(); + } + } + Ok(max) +} + +// Enumerable#min: Returns the minimum element +fn mrb_enumerable_min(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let array = mrb_funcall(vm, Some(this), "to_a", &[])?; + let collected: Vec> = array.as_ref().try_into()?; + + if collected.is_empty() { + return Ok(Rc::new(RObject::nil())); + } + + let mut min = collected[0].clone(); + for elem in collected.iter().skip(1) { + let args = vec![elem.clone()]; + let cmp: i64 = mrb_funcall(vm, Some(min.clone()), "<=>", &args)? + .as_ref() + .try_into()?; + if cmp > 0 { + min = elem.clone(); + } + } + Ok(min) +} + +// Enumerable#minmax: Returns a two-element array containing the minimum and maximum +fn mrb_enumerable_minmax(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let array = mrb_funcall(vm, Some(this), "to_a", &[])?; + let collected: Vec> = array.as_ref().try_into()?; + + if collected.is_empty() { + return Ok( + RObject::array(vec![Rc::new(RObject::nil()), Rc::new(RObject::nil())]) + .to_refcount_assigned(), + ); + } + + let mut min = collected[0].clone(); + let mut max = collected[0].clone(); + + for elem in collected.iter().skip(1) { + let args = vec![elem.clone()]; + let cmp_min: i64 = mrb_funcall(vm, Some(min.clone()), "<=>", &args)? + .as_ref() + .try_into()?; + if cmp_min > 0 { + min = elem.clone(); + } + + let args = vec![elem.clone()]; + let cmp_max: i64 = mrb_funcall(vm, Some(max.clone()), "<=>", &args)? + .as_ref() + .try_into()?; + if cmp_max < 0 { + max = elem.clone(); + } + } + + Ok(RObject::array(vec![min, max]).to_refcount_assigned()) +} + +// Enumerable#compact: Returns a new array with nil values removed +fn mrb_enumerable_compact(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let results: Rc = RObject::array(vec![]).to_refcount_assigned(); + let results_ref = results.clone(); + let wrapping_block: RFn = Box::new(move |vm: &mut VM, args: &[Rc]| { + if !args[0].is_nil() { + mrb_funcall( + vm, + Some(results_ref.clone()), + "push", + std::slice::from_ref(&args[0]), + )?; + } + Ok(Rc::new(RObject::nil())) + }); + + let this = vm.getself()?; + let block = rproc_from_rust_block(vm, wrapping_block)?; + mrb_funcall(vm, Some(this.clone()), "each", &[block])?; + vm.pop_fnblock()?; + + Ok(results) +} + +// Enumerable#count: Returns the number of elements (with optional condition) +fn mrb_enumerable_count(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let count = Rc::new(Cell::new(0i64)); + + if args.is_empty() { + // Count all elements + let count_ref = count.clone(); + let wrapping_block: RFn = Box::new(move |_vm: &mut VM, _args: &[Rc]| { + count_ref.set(count_ref.get() + 1); + Ok(Rc::new(RObject::nil())) + }); + + let this = vm.getself()?; + let block = rproc_from_rust_block(vm, wrapping_block)?; + mrb_funcall(vm, Some(this.clone()), "each", &[block])?; + vm.pop_fnblock()?; + } else { + // Count elements matching the block condition + let count_ref = count.clone(); + let original_block = args.last().cloned().unwrap(); + let wrapping_block: RFn = Box::new(move |vm: &mut VM, args: &[Rc]| { + let block = original_block.clone(); + let result = mrb_call_block(vm, block, None, args, 0)?; + if result.is_truthy() { + count_ref.set(count_ref.get() + 1); + } + Ok(result) + }); + + let this = vm.getself()?; + let block = rproc_from_rust_block(vm, wrapping_block)?; + mrb_funcall(vm, Some(this.clone()), "each", &[block])?; + vm.pop_fnblock()?; + } + + Ok(Rc::new(RObject::integer(count.get()))) +} + +// Enumerable#uniq: Returns a new array with duplicate values removed +fn mrb_enumerable_uniq(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let array = mrb_funcall(vm, Some(this), "to_a", &[])?; + let collected: Vec> = array.as_ref().try_into()?; + + let mut result = Vec::new(); + for elem in collected.iter() { + let elem_eq = elem.as_eq_value(); + if !result + .iter() + .any(|e: &Rc| e.as_eq_value() == elem_eq) + { + result.push(elem.clone()); + } + } + Ok(Rc::new(RObject::array(result))) +} From 7067fec81ee1d31954ff07bcca4759bb30e8a90c Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 18:51:49 +0900 Subject: [PATCH 183/314] Tidy up Enumerable tests --- mrubyedge/tests/array.rs | 76 ------------------------------- mrubyedge/tests/enumerable.rs | 84 +++++++++++++++++++++++++++++++++-- 2 files changed, 80 insertions(+), 80 deletions(-) diff --git a/mrubyedge/tests/array.rs b/mrubyedge/tests/array.rs index bd87510..61d2f22 100644 --- a/mrubyedge/tests/array.rs +++ b/mrubyedge/tests/array.rs @@ -286,82 +286,6 @@ fn array_dup_test() { assert!(result); } -#[test] -fn array_min_test() { - let code = r#" - def test_array_min - [3, 1, 2].min - end - "#; - let binary = mrbc_compile("array_min", code); - let mut rite = mrubyedge::rite::load(&binary).unwrap(); - let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - vm.run().unwrap(); - - let args = vec![]; - let result = mrb_funcall(&mut vm, None, "test_array_min", &args).unwrap(); - let result: i64 = result.as_ref().try_into().unwrap(); - assert_eq!(result, 1); -} - -#[test] -fn array_max_test() { - let code = r#" - def test_array_max - [3, 1, 2].max - end - "#; - let binary = mrbc_compile("array_max", code); - let mut rite = mrubyedge::rite::load(&binary).unwrap(); - let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - vm.run().unwrap(); - - let args = vec![]; - let result = mrb_funcall(&mut vm, None, "test_array_max", &args).unwrap(); - let result: i64 = result.as_ref().try_into().unwrap(); - assert_eq!(result, 3); -} - -#[test] -fn array_minmax_test() { - let code = r#" - def test_array_minmax - [3, 1, 2].minmax - end - "#; - let binary = mrbc_compile("array_minmax", code); - let mut rite = mrubyedge::rite::load(&binary).unwrap(); - let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - vm.run().unwrap(); - - let args = vec![]; - let result = mrb_funcall(&mut vm, None, "test_array_minmax", &args).unwrap(); - let result = result.as_vec_owned().unwrap(); - assert_eq!(result.len(), 2); - let min: i64 = result[0].as_ref().try_into().unwrap(); - let max: i64 = result[1].as_ref().try_into().unwrap(); - assert_eq!(min, 1); - assert_eq!(max, 3); -} - -#[test] -fn array_uniq_test() { - let code = r#" - def test_array_uniq - [1, 2, 2, 3].uniq - end - "#; - let binary = mrbc_compile("array_uniq", code); - let mut rite = mrubyedge::rite::load(&binary).unwrap(); - let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - vm.run().unwrap(); - - let args = vec![]; - let result = mrb_funcall(&mut vm, None, "test_array_uniq", &args).unwrap(); - let result = result.as_vec_owned().unwrap(); - assert_eq!(result.len(), 3); -} - #[test] fn array_uniq_self_test() { let code = r#" diff --git a/mrubyedge/tests/enumerable.rs b/mrubyedge/tests/enumerable.rs index f8ac10e..7756a52 100644 --- a/mrubyedge/tests/enumerable.rs +++ b/mrubyedge/tests/enumerable.rs @@ -9,7 +9,7 @@ use helpers::*; use mrubyedge::yamrb::value::RObject; #[test] -fn array_map_basic_test() { +fn enumerable_map_basic_test() { let code = r#" def test_array_map [1, 2, 3].map { |x| x * 2 } @@ -27,7 +27,7 @@ fn array_map_basic_test() { } #[test] -fn array_map_nested_test() { +fn enumerable_map_nested_test() { let code = r#" def array_map_nested [[1,1,1], [2,2,2], [3,3,3]].map { |arr| arr.map { |x| x * 2 } } @@ -50,7 +50,7 @@ fn array_map_nested_test() { } #[test] -fn array_find_found_test() { +fn enumerable_find_found_test() { let code = r#" def test_array_find_found [1, 2, 3, 4, 5].find { |x| x > 3 } @@ -68,7 +68,7 @@ fn array_find_found_test() { } #[test] -fn array_find_not_found_test() { +fn enumerable_find_not_found_test() { let code = r#" def test_array_find_not_found [1, 2, 3, 4, 5].find { |x| x > 10 } @@ -83,3 +83,79 @@ fn array_find_not_found_test() { let result = mrb_funcall(&mut vm, None, "test_array_find_not_found", &args).unwrap(); assert!(result.is_nil()); } + +#[test] +fn enumerable_min_test() { + let code = r#" + def test_array_min + [3, 1, 2].min + end + "#; + let binary = mrbc_compile("array_min", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_min", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 1); +} + +#[test] +fn enumerable_max_test() { + let code = r#" + def test_array_max + [3, 1, 2].max + end + "#; + let binary = mrbc_compile("array_max", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_max", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 3); +} + +#[test] +fn enumerable_minmax_test() { + let code = r#" + def test_array_minmax + [3, 1, 2].minmax + end + "#; + let binary = mrbc_compile("array_minmax", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_minmax", &args).unwrap(); + let result_array: Vec> = result.as_ref().try_into().unwrap(); + assert_eq!(result_array.len(), 2); + let min: i64 = result_array[0].as_ref().try_into().unwrap(); + let max: i64 = result_array[1].as_ref().try_into().unwrap(); + assert_eq!(min, 1); + assert_eq!(max, 3); +} + +#[test] +fn enumerable_uniq_test() { + let code = r#" + def test_array_uniq + [1, 2, 2, 3].uniq + end + "#; + let binary = mrbc_compile("array_uniq", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_uniq", &args).unwrap(); + let result_array: Vec> = result.as_ref().try_into().unwrap(); + assert_eq!(result_array.len(), 3); +} From 5085cc83464ec6b763930bac22947474eaf3feeb Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 19:00:57 +0900 Subject: [PATCH 184/314] Add more tests --- mrubyedge/tests/enumerable.rs | 190 ++++++++++++++++++++++++++++++++++ 1 file changed, 190 insertions(+) diff --git a/mrubyedge/tests/enumerable.rs b/mrubyedge/tests/enumerable.rs index 7756a52..36cb589 100644 --- a/mrubyedge/tests/enumerable.rs +++ b/mrubyedge/tests/enumerable.rs @@ -159,3 +159,193 @@ fn enumerable_uniq_test() { let result_array: Vec> = result.as_ref().try_into().unwrap(); assert_eq!(result_array.len(), 3); } + +#[test] +fn enumerable_select_test() { + let code = r#" + def test_select + [1, 2, 3, 4, 5].select { |x| x > 3 } + end + "#; + let binary = mrbc_compile("enumerable_select", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_select", &args).unwrap(); + let result_array: Vec> = result.as_ref().try_into().unwrap(); + assert_eq!(result_array.len(), 2); +} + +#[test] +fn enumerable_all_test() { + let code = r#" + def test_all + [2, 4, 6].all? { |x| x % 2 == 0 } + end + "#; + let binary = mrbc_compile("enumerable_all", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_all", &args).unwrap(); + let result: bool = result.as_ref().try_into().unwrap(); + assert!(result); +} + +#[test] +fn enumerable_any_test() { + let code = r#" + def test_any + [1, 2, 3].any? { |x| x > 2 } + end + "#; + let binary = mrbc_compile("enumerable_any", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_any", &args).unwrap(); + let result: bool = result.as_ref().try_into().unwrap(); + assert!(result); +} + +#[test] +fn enumerable_delete_if_test() { + let code = r#" + def test_delete_if + [1, 2, 3, 4, 5].delete_if { |x| x % 2 == 0 } + end + "#; + let binary = mrbc_compile("enumerable_delete_if", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_delete_if", &args).unwrap(); + let result_array: Vec> = result.as_ref().try_into().unwrap(); + assert_eq!(result_array.len(), 3); +} + +#[test] +fn enumerable_each_with_index_test() { + let code = r#" + def test_each_with_index + result = [] + [10, 20, 30].each_with_index { |x, i| result.push(x + i) } + result + end + "#; + let binary = mrbc_compile("enumerable_each_with_index", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_each_with_index", &args).unwrap(); + let result_array: Vec> = result.as_ref().try_into().unwrap(); + assert_eq!(result_array.len(), 3); + let r0: i64 = result_array[0].as_ref().try_into().unwrap(); + let r1: i64 = result_array[1].as_ref().try_into().unwrap(); + let r2: i64 = result_array[2].as_ref().try_into().unwrap(); + assert_eq!(r0, 10); + assert_eq!(r1, 21); + assert_eq!(r2, 32); +} + +#[test] +fn enumerable_sort_test() { + let code = r#" + def test_sort + [3, 1, 4, 1, 5].sort + end + "#; + let binary = mrbc_compile("enumerable_sort", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_sort", &args).unwrap(); + let result_array: Vec> = result.as_ref().try_into().unwrap(); + let r0: i64 = result_array[0].as_ref().try_into().unwrap(); + assert_eq!(r0, 1); +} + +#[test] +fn enumerable_sort_by_test() { + let code = r#" + def test_sort_by + [3, 1, 4, 1, 5].sort_by { |x| -x } + end + "#; + let binary = mrbc_compile("enumerable_sort_by", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_sort_by", &args).unwrap(); + let result_array: Vec> = result.as_ref().try_into().unwrap(); + let r0: i64 = result_array[0].as_ref().try_into().unwrap(); + assert_eq!(r0, 5); +} + +#[test] +fn enumerable_compact_test() { + let code = r#" + def test_compact + [1, nil, 2, nil, 3].compact + end + "#; + let binary = mrbc_compile("enumerable_compact", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_compact", &args).unwrap(); + let result_array: Vec> = result.as_ref().try_into().unwrap(); + assert_eq!(result_array.len(), 3); +} + +#[test] +fn enumerable_count_test() { + let code = r#" + def test_count + [1, 2, 3, 4, 5].count { |x| x % 2 == 0 } + end + "#; + let binary = mrbc_compile("enumerable_count", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_count", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 2); +} + +#[test] +fn enumerable_to_a_test() { + let code = r#" + def test_to_a + [1, 2, 3].to_a + end + "#; + let binary = mrbc_compile("enumerable_to_a", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_to_a", &args).unwrap(); + let result_array: Vec> = result.as_ref().try_into().unwrap(); + assert_eq!(result_array.len(), 3); +} From c2d9f19501a043a6cd79fd566a8a780c5775d363 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 19:04:31 +0900 Subject: [PATCH 185/314] Add custom class... TODO: support yield-ing --- mrubyedge/tests/enumerable.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/mrubyedge/tests/enumerable.rs b/mrubyedge/tests/enumerable.rs index 36cb589..5018b6f 100644 --- a/mrubyedge/tests/enumerable.rs +++ b/mrubyedge/tests/enumerable.rs @@ -349,3 +349,32 @@ fn enumerable_to_a_test() { let result_array: Vec> = result.as_ref().try_into().unwrap(); assert_eq!(result_array.len(), 3); } + +#[test] +fn enumerable_map_custom_class_test() { + // FIXME: support `yield` + let code = r#" + class MyCollection + def each(&block) + block.call(1) + block.call(2) + block.call(3) + end + + include Enumerable + end + + def test_my_collection_map + MyCollection.new.map { |x| x * 2 } + end + "#; + let binary = mrbc_compile("my_collection_map", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_my_collection_map", &args).unwrap(); + let result: (i32, i32, i32) = result.as_ref().try_into().unwrap(); + assert_eq!(result, (2, 4, 6)); +} From ae102bac1f7c0ca49b0d3b7de2d7d6e16d4af064 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 19:15:02 +0900 Subject: [PATCH 186/314] Support reduce --- mrubyedge/src/yamrb/prelude/array.rs | 4 +- mrubyedge/src/yamrb/prelude/enumerable.rs | 68 +++++++++++++++++++++++ mrubyedge/tests/enumerable.rs | 18 ++++++ 3 files changed, 88 insertions(+), 2 deletions(-) diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index a33dce1..34a3b67 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -483,7 +483,7 @@ fn mrb_array_or(vm: &mut VM, args: &[Rc]) -> Result, Error> // Array#first: Returns the first element, or the first n elements fn mrb_array_first(vm: &mut VM, args: &[Rc]) -> Result, Error> { let this: Vec> = vm.getself()?.as_ref().try_into()?; - let args = if args[args.len() - 1].as_ref().is_nil() { + let args = if !args.is_empty() && args[args.len() - 1].as_ref().is_nil() { &args[..args.len() - 1] } else { args @@ -507,7 +507,7 @@ fn mrb_array_first(vm: &mut VM, args: &[Rc]) -> Result, Err // Array#last: Returns the last element, or the last n elements fn mrb_array_last(vm: &mut VM, args: &[Rc]) -> Result, Error> { let this: Vec> = vm.getself()?.as_ref().try_into()?; - let args = if args[args.len() - 1].as_ref().is_nil() { + let args = if !args.is_empty() && args[args.len() - 1].as_ref().is_nil() { &args[..args.len() - 1] } else { args diff --git a/mrubyedge/src/yamrb/prelude/enumerable.rs b/mrubyedge/src/yamrb/prelude/enumerable.rs index 90296d0..d34d802 100644 --- a/mrubyedge/src/yamrb/prelude/enumerable.rs +++ b/mrubyedge/src/yamrb/prelude/enumerable.rs @@ -108,6 +108,12 @@ pub(crate) fn initialize_enumerable(vm: &mut VM) { "uniq", Box::new(mrb_enumerable_uniq), ); + mrb_define_module_cmethod( + vm, + enumerable_module.clone(), + "reduce", + Box::new(mrb_enumerable_reduce), + ); } fn rproc_from_rust_block(vm: &mut VM, rfn: RFn) -> Result, Error> { @@ -592,3 +598,65 @@ fn mrb_enumerable_uniq(vm: &mut VM, _args: &[Rc]) -> Result } Ok(Rc::new(RObject::array(result))) } + +// Enumerable#reduce: Combines all elements by applying a binary operation +fn mrb_enumerable_reduce(vm: &mut VM, args: &[Rc]) -> Result, Error> { + // Check if we have an initial value or just a block + let (initial_value, original_block) = if args.len() == 2 { + // Initial value provided: reduce(initial) { |acc, elem| ... } + (Some(args[0].clone()), args[1].clone()) + } else if args.len() == 1 { + // No initial value: reduce { |acc, elem| ... } + (None, args[0].clone()) + } else { + return Err(Error::ArgumentError( + "wrong number of arguments".to_string(), + )); + }; + + let accumulator: Rc = if let Some(init) = initial_value { + RObject::array(vec![init]).to_refcount_assigned() + } else { + RObject::array(vec![]).to_refcount_assigned() + }; + + let acc_ref = accumulator.clone(); + let wrapping_block: RFn = Box::new(move |vm: &mut VM, args: &[Rc]| { + let current_elem = args[0].clone(); + let acc_array: Vec> = acc_ref.as_ref().try_into()?; + + if acc_array.is_empty() { + // First element becomes the initial accumulator + mrb_funcall( + vm, + Some(acc_ref.clone()), + "push", + std::slice::from_ref(¤t_elem), + )?; + } else { + // Call block with (accumulator, element) + let current_acc = acc_array[0].clone(); + let block = original_block.clone(); + let result = mrb_call_block(vm, block, None, &[current_acc, current_elem], 0)?; + + // Update accumulator + mrb_funcall(vm, Some(acc_ref.clone()), "pop", &[])?; + mrb_funcall( + vm, + Some(acc_ref.clone()), + "push", + std::slice::from_ref(&result), + )?; + } + Ok(Rc::new(RObject::nil())) + }); + + let this = vm.getself()?; + let block = rproc_from_rust_block(vm, wrapping_block)?; + mrb_funcall(vm, Some(this.clone()), "each", &[block])?; + vm.pop_fnblock()?; + + // Return the final accumulator value + let result = mrb_funcall(vm, Some(accumulator), "first", &[])?; + Ok(result) +} diff --git a/mrubyedge/tests/enumerable.rs b/mrubyedge/tests/enumerable.rs index 5018b6f..f082461 100644 --- a/mrubyedge/tests/enumerable.rs +++ b/mrubyedge/tests/enumerable.rs @@ -350,6 +350,24 @@ fn enumerable_to_a_test() { assert_eq!(result_array.len(), 3); } +#[test] +fn enumerable_reduce_test() { + let code = r#" + def test_reduce + [1, 2, 3, 4].reduce(0) { |sum, x| sum + x } + end + "#; + let binary = mrbc_compile("enumerable_reduce", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_reduce", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 10); +} + #[test] fn enumerable_map_custom_class_test() { // FIXME: support `yield` From 71133d1c0e444d39252c09c663462ca98862cf41 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 19:46:30 +0900 Subject: [PATCH 187/314] Implement clamp --- mrubyedge/src/yamrb/prelude/float.rs | 49 ++++++++++++++++++++++++++ mrubyedge/src/yamrb/prelude/integer.rs | 35 ++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/mrubyedge/src/yamrb/prelude/float.rs b/mrubyedge/src/yamrb/prelude/float.rs index 6cfc26a..b8731e5 100644 --- a/mrubyedge/src/yamrb/prelude/float.rs +++ b/mrubyedge/src/yamrb/prelude/float.rs @@ -16,6 +16,7 @@ pub(crate) fn initialize_float(vm: &mut VM) { Box::new(mrb_float_inspect), ); mrb_define_cmethod(vm, float_class.clone(), "to_s", Box::new(mrb_float_inspect)); + mrb_define_cmethod(vm, float_class.clone(), "clamp", Box::new(mrb_float_clamp)); } pub fn mrb_float_to_i(vm: &mut VM, _args: &[Rc]) -> Result, Error> { @@ -53,3 +54,51 @@ pub fn mrb_float_inspect(vm: &mut VM, _args: &[Rc]) -> Result]) -> Result, Error> { + if args.len() < 2 { + return Err(Error::ArgumentError(format!( + "wrong number of arguments (given {}, expected 2)", + args.len() + ))); + } + + let this = vm.getself()?; + let this_float = match &this.value { + crate::yamrb::value::RValue::Float(f) => *f, + _ => { + return Err(Error::RuntimeError( + "Float#clamp must be called on a Float".to_string(), + )); + } + }; + + // Convert min and max to f64 + let min = match &args[0].value { + crate::yamrb::value::RValue::Float(f) => *f, + crate::yamrb::value::RValue::Integer(i) => *i as f64, + _ => return Err(Error::TypeMismatch), + }; + + let max = match &args[1].value { + crate::yamrb::value::RValue::Float(f) => *f, + crate::yamrb::value::RValue::Integer(i) => *i as f64, + _ => return Err(Error::TypeMismatch), + }; + + if min > max { + return Err(Error::ArgumentError( + "min argument must be smaller than max argument".to_string(), + )); + } + + let result = if this_float < min { + min + } else if this_float > max { + max + } else { + this_float + }; + + Ok(Rc::new(RObject::float(result))) +} diff --git a/mrubyedge/src/yamrb/prelude/integer.rs b/mrubyedge/src/yamrb/prelude/integer.rs index 1c98323..3923642 100644 --- a/mrubyedge/src/yamrb/prelude/integer.rs +++ b/mrubyedge/src/yamrb/prelude/integer.rs @@ -71,6 +71,12 @@ pub(crate) fn initialize_integer(vm: &mut VM) { "to_s", Box::new(mrb_integer_inspect), ); + mrb_define_cmethod( + vm, + integer_class.clone(), + "clamp", + Box::new(mrb_integer_clamp), + ); } fn mrb_integer_inspect(vm: &mut VM, _args: &[Rc]) -> Result, Error> { @@ -208,3 +214,32 @@ fn mrb_integer_chr(vm: &mut VM, _args: &[Rc]) -> Result, Er Ok(Rc::new(RObject::string(ch.to_string()))) } + +fn mrb_integer_clamp(vm: &mut VM, args: &[Rc]) -> Result, Error> { + if args.len() < 2 { + return Err(Error::ArgumentError(format!( + "wrong number of arguments (given {}, expected 2)", + args.len() + ))); + } + + let this: i64 = vm.getself()?.as_ref().try_into()?; + let min: i64 = args[0].as_ref().try_into()?; + let max: i64 = args[1].as_ref().try_into()?; + + if min > max { + return Err(Error::ArgumentError( + "min argument must be smaller than max argument".to_string(), + )); + } + + let result = if this < min { + min + } else if this > max { + max + } else { + this + }; + + Ok(Rc::new(RObject::integer(result))) +} From 9b8f71143d7413a783a44d297909dc8679a8885a Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 19:48:51 +0900 Subject: [PATCH 188/314] Add tests --- mrubyedge/tests/float.rs | 37 +++++++++++++++++++++++++++++++++++++ mrubyedge/tests/integer.rs | 13 +++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 mrubyedge/tests/float.rs diff --git a/mrubyedge/tests/float.rs b/mrubyedge/tests/float.rs new file mode 100644 index 0000000..659a816 --- /dev/null +++ b/mrubyedge/tests/float.rs @@ -0,0 +1,37 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn float_clamp_test() { + let code = " +result1 = 100.5.clamp(50.0, 150.0) +result2 = 25.5.clamp(50.0, 150.0) +result3 = 200.5.clamp(50.0, 150.0) +result1 + result2 + result3 + "; + let binary = mrbc_compile("float_clamp", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_float: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(result_float, 300.5); // 100.5 + 50.0 + 150.0 +} + +#[test] +fn float_clamp_with_integer_bounds_test() { + let code = " +result1 = 100.5.clamp(50, 150) +result2 = 25.5.clamp(50, 150) +result3 = 200.5.clamp(50, 150) +result1 + result2 + result3 + "; + let binary = mrbc_compile("float_clamp_int", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_float: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(result_float, 300.5); // 100.5 + 50.0 + 150.0 +} diff --git a/mrubyedge/tests/integer.rs b/mrubyedge/tests/integer.rs index c902fac..23de1c6 100644 --- a/mrubyedge/tests/integer.rs +++ b/mrubyedge/tests/integer.rs @@ -228,3 +228,16 @@ fn integer_inspect_test() { let result_str: String = result.as_ref().try_into().unwrap(); assert_eq!(&result_str, "456"); } + +#[test] +fn integer_clamp_test() { + let code = " +100.clamp(50, 150) + 25.clamp(50, 150) + 200.clamp(50, 150) + "; + let binary = mrbc_compile("integer_clamp", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 300); // 100 + 50 + 150 +} From ab3edda4e30677f2f57c1f3666c065a61b88f96e Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 19:57:48 +0900 Subject: [PATCH 189/314] 1.1.0 RC1 --- Cargo.lock | 20 ++++++++++---------- mrubyedge/Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2368e2e..7d05179 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -572,13 +572,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f34d536e7b90424d866f961ce38f7c21c2e8a0673e63be3539e70ac0c9e729a" dependencies = [ - "criterion", - "fnv", - "getrandom 0.3.4", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "regex", "simple_endian", @@ -586,10 +582,14 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f34d536e7b90424d866f961ce38f7c21c2e8a0673e63be3539e70ac0c9e729a" +version = "1.1.0-rc1" dependencies = [ + "criterion", + "fnv", + "getrandom 0.3.4", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "regex", "simple_endian", @@ -602,7 +602,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.0.20", "nom", "rand", "syn", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 6f5be33..dfba627 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.0.20" +version = "1.1.0-rc1" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From 3bebdafe85c73f6659980be1db9cebe1b6a3c0bb Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 20:02:46 +0900 Subject: [PATCH 190/314] Add rand --- Cargo.lock | 38 +++++++++++++++++++++++++++++++++++--- mrubyedge/Cargo.toml | 9 +++++++-- 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7d05179..5e866aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -591,6 +591,8 @@ dependencies = [ "mruby-compiler2-sys", "once_cell", "plain", + "rand 0.9.2", + "rand_xorshift", "regex", "simple_endian", ] @@ -604,7 +606,7 @@ dependencies = [ "mruby-compiler2-sys", "mrubyedge 1.0.20", "nom", - "rand", + "rand 0.8.5", "syn", ] @@ -736,7 +738,16 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" +dependencies = [ + "rand_core 0.9.5", ] [[package]] @@ -746,7 +757,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", ] [[package]] @@ -758,6 +769,27 @@ dependencies = [ "getrandom 0.2.16", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" + +[[package]] +name = "rand_core" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c8d0fd677905edcbeedbf2edb6494d676f0e98d54d5cf9bda0b061cb8fb8aba" + +[[package]] +name = "rand_xorshift" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60aa6af80be32871323012e02e6e65f8a7cc7890931ae421d217ad8fe0df2ccf" +dependencies = [ + "rand_core 0.10.0", +] + [[package]] name = "rayon" version = "1.11.0" diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index dfba627..7e8d2da 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -12,6 +12,10 @@ license = "BSD-3-Clause" fnv = { version = "1.0.7", optional = true } getrandom = { version = "0.3.4", optional = true } plain = "0.2.3" +rand = { version = "0.9.2", default-features = false, optional = true, features = [ + "small_rng", +] } +rand_xorshift = { version = "0.5.0", optional = true } regex = { version = "1.12.2", default-features = false, features = [ "std", ], optional = true } @@ -33,10 +37,11 @@ name = "hashmap_comparison" harness = false [features] -default = ["wasi", "mrubyedge-debug"] +default = ["wasi", "mrubyedge-debug", "mruby-random"] wasi = [] mruby-hash-fnv = ["dep:fnv"] mruby-regexp = ["dep:regex"] mrubyedge-debug = ["wasi"] -mruby-random = ["wasi", "dep:getrandom"] +mruby-random = ["dep:rand", "dep:rand_xorshift"] +# mruby-securerandom = ["wasi", "dep:getrandom"] no-wasi = [] From a2ddbe5dcbd5055dd110bffc17f6c5d01cb893cb Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 22:03:46 +0900 Subject: [PATCH 191/314] Update rand on cli --- mrubyedge-cli/Cargo.toml | 2 +- mrubyedge-cli/src/subcommands/wasm.rs | 11 ++++++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index b4bb793..1792f53 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -15,7 +15,7 @@ path = "src/main.rs" clap = { version = "4.5", features = ["derive"] } mruby-compiler2-sys = "0.2.2" mrubyedge = { version = "1.0.20", features = ["default", "mruby-regexp"] } -rand = "0.8.5" +rand = "0.9.2" nom = "7.1.3" askama = "0.12.1" diff --git a/mrubyedge-cli/src/subcommands/wasm.rs b/mrubyedge-cli/src/subcommands/wasm.rs index 9e4fbe6..9337441 100644 --- a/mrubyedge-cli/src/subcommands/wasm.rs +++ b/mrubyedge-cli/src/subcommands/wasm.rs @@ -11,7 +11,7 @@ use std::{ }; use askama::Template; -use rand::distributions::{Alphanumeric, DistString}; +use rand::Rng; use crate::rbs_parser; use crate::template; @@ -79,8 +79,13 @@ fn debug_println(debug: bool, msg: &str) { } pub fn execute(args: WasmArgs) -> Result<(), Box> { - let mut rng = rand::thread_rng(); - let suffix = Alphanumeric.sample_string(&mut rng, 32); + let mut rng = rand::rng(); + let suffix: String = (0..32) + .map(|_| { + let chars = b"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; + chars[rng.random_range(0..chars.len())] as char + }) + .collect(); let fnname = args.fnname; let path = args.path; From cb75151109701b4918894595a312769250263b07 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 22:04:02 +0900 Subject: [PATCH 192/314] Implement basic Random class with fixes --- Cargo.lock | 167 +++++++--------- mrubyedge/Cargo.toml | 10 +- mrubyedge/src/yamrb/optable.rs | 20 +- mrubyedge/src/yamrb/prelude/mod.rs | 2 + mrubyedge/src/yamrb/prelude/object.rs | 7 + mrubyedge/src/yamrb/prelude/rand.rs | 265 ++++++++++++++++++++++++++ mrubyedge/tests/random.rs | 183 ++++++++++++++++++ 7 files changed, 544 insertions(+), 110 deletions(-) create mode 100644 mrubyedge/src/yamrb/prelude/rand.rs create mode 100644 mrubyedge/tests/random.rs diff --git a/Cargo.lock b/Cargo.lock index 5e866aa..203146a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -174,9 +174,9 @@ checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" [[package]] name = "bumpalo" -version = "3.19.0" +version = "3.19.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46c5e41b57b8bba42a04676d81cb89e9ee8e859a1a66f80a5a72e1cb76b34d43" +checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" [[package]] name = "cast" @@ -186,9 +186,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.49" +version = "1.2.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90583009037521a116abf44494efecd645ba48b6622457080f080b85544e2215" +checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" dependencies = [ "find-msvc-tools", "shlex", @@ -249,9 +249,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.53" +version = "4.5.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9e340e012a1bf4935f5282ed1436d1489548e8f72308207ea5df0e23d2d03f8" +checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" dependencies = [ "clap_builder", "clap_derive", @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.53" +version = "4.5.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d76b5d13eaa18c901fd2f7fca939fefe3a0727a953561fefdf3b2922b8569d00" +checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" dependencies = [ "anstream", "anstyle", @@ -271,9 +271,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.49" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a0b5487afeab2deb2ff4e03a807ad1a03ac532ff5a2cee5d86884440c7f7671" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck", "proc-macro2", @@ -283,9 +283,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.6" +version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1d728cc89cf3aee9ff92b05e62b19ee65a02b5702cff7d5a377e32c6ae29d8d" +checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" [[package]] name = "colorchoice" @@ -368,9 +368,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "find-msvc-tools" -version = "0.1.5" +version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" [[package]] name = "fnv" @@ -378,17 +378,6 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" -[[package]] -name = "getrandom" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - [[package]] name = "getrandom" version = "0.3.4" @@ -476,15 +465,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.15" +version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "464a3709c7f55f1f721e5389aa6ea4e3bc6aba669353300af094b29ffbdde1d8" +checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" dependencies = [ "once_cell", "wasm-bindgen", @@ -492,9 +481,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.178" +version = "0.2.180" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" +checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" [[package]] name = "libloading" @@ -508,9 +497,9 @@ dependencies = [ [[package]] name = "libm" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "log" @@ -586,12 +575,12 @@ version = "1.1.0-rc1" dependencies = [ "criterion", "fnv", - "getrandom 0.3.4", + "getrandom", "mec-mrbc-sys", "mruby-compiler2-sys", "once_cell", "plain", - "rand 0.9.2", + "rand_core 0.10.0", "rand_xorshift", "regex", "simple_endian", @@ -606,7 +595,7 @@ dependencies = [ "mruby-compiler2-sys", "mrubyedge 1.0.20", "nom", - "rand 0.8.5", + "rand", "syn", ] @@ -708,18 +697,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.42" +version = "1.0.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a338cc41d27e6cc6dce6cefc13a0729dfbb81c262b1f519331575dd80ef3067f" +checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" dependencies = [ "proc-macro2", ] @@ -730,43 +719,24 @@ version = "5.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core 0.6.4", -] - [[package]] name = "rand" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ + "rand_chacha", "rand_core 0.9.5", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom 0.2.16", + "rand_core 0.9.5", ] [[package]] @@ -774,6 +744,9 @@ name = "rand_core" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom", +] [[package]] name = "rand_core" @@ -851,12 +824,6 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" -[[package]] -name = "ryu" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" - [[package]] name = "same-file" version = "1.0.6" @@ -898,15 +865,15 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.145" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "402a6f66d8c709116cf22f558eab210f5a50187f702eb4d7e5ef38d9a7f1c79c" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" dependencies = [ "itoa", "memchr", - "ryu", "serde", "serde_core", + "zmij", ] [[package]] @@ -929,9 +896,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.111" +version = "2.0.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "390cc9a294ab71bdb1aa2e99d13be9c753cd2d7bd6560c77118597410c4d2e87" +checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" dependencies = [ "proc-macro2", "quote", @@ -950,9 +917,9 @@ dependencies = [ [[package]] name = "unicase" -version = "2.8.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" +checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" @@ -976,26 +943,20 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "wasi" -version = "0.11.1+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" - [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] [[package]] name = "wasm-bindgen" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d759f433fa64a2d763d1340820e46e111a7a5ab75f993d1852d70b03dbb80fd" +checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" dependencies = [ "cfg-if", "once_cell", @@ -1006,9 +967,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48cb0d2638f8baedbc542ed444afc0644a29166f1595371af4fecf8ce1e7eeb3" +checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1016,9 +977,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cefb59d5cd5f92d9dcf80e4683949f15ca4b511f4ac0a6e14d4e1ac60c6ecd40" +checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" dependencies = [ "bumpalo", "proc-macro2", @@ -1029,18 +990,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.106" +version = "0.2.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbc538057e648b67f72a982e708d485b2efa771e1ac05fec311f9f63e5800db4" +checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.83" +version = "0.3.85" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b32828d774c412041098d182a8b38b16ea816958e07cf40eec2bc080ae137ac" +checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" dependencies = [ "js-sys", "wasm-bindgen", @@ -1072,26 +1033,32 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "zerocopy" -version = "0.8.31" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd74ec98b9250adb3ca554bdde269adf631549f51d8a8f8f0a10b50f1cb298c3" +checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.31" +version = "0.8.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8a8d209fdf45cf5138cbb5a506f6b52522a25afccc534d1475dad8e31105c6a" +checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" dependencies = [ "proc-macro2", "quote", "syn", ] + +[[package]] +name = "zmij" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 7e8d2da..349a2c4 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -12,10 +12,8 @@ license = "BSD-3-Clause" fnv = { version = "1.0.7", optional = true } getrandom = { version = "0.3.4", optional = true } plain = "0.2.3" -rand = { version = "0.9.2", default-features = false, optional = true, features = [ - "small_rng", -] } -rand_xorshift = { version = "0.5.0", optional = true } +rand_core = { version = "0.10.0" } +rand_xorshift = { version = "0.5.0" } regex = { version = "1.12.2", default-features = false, features = [ "std", ], optional = true } @@ -37,11 +35,11 @@ name = "hashmap_comparison" harness = false [features] -default = ["wasi", "mrubyedge-debug", "mruby-random"] +default = ["wasi", "mrubyedge-debug"] wasi = [] mruby-hash-fnv = ["dep:fnv"] mruby-regexp = ["dep:regex"] mrubyedge-debug = ["wasi"] -mruby-random = ["dep:rand", "dep:rand_xorshift"] +# mruby-random = ["dep:rand", "dep:rand_xorshift"] # mruby-securerandom = ["wasi", "dep:getrandom"] no-wasi = [] diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 0921957..f2bc047 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1508,8 +1508,11 @@ pub(crate) fn op_lt(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val2 = vm.get_current_regs_cloned(b)?; let result = match (&val1.value, &val2.value) { (RValue::Integer(n1), RValue::Integer(n2)) => RObject::boolean(n1 < n2), + (RValue::Float(n1), RValue::Float(n2)) => RObject::boolean(n1 < n2), + (RValue::Integer(n1), RValue::Float(n2)) => RObject::boolean((*n1 as f64) < *n2), + (RValue::Float(n1), RValue::Integer(n2)) => RObject::boolean(*n1 < (*n2 as f64)), _ => { - unreachable!("lt supports only integer") + unreachable!("lt supports only numeric") } }; vm.current_regs()[a].replace(Rc::new(result)); @@ -1523,8 +1526,11 @@ pub(crate) fn op_le(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val2 = vm.get_current_regs_cloned(b)?; let result = match (&val1.value, &val2.value) { (RValue::Integer(n1), RValue::Integer(n2)) => RObject::boolean(n1 <= n2), + (RValue::Float(n1), RValue::Float(n2)) => RObject::boolean(n1 <= n2), + (RValue::Integer(n1), RValue::Float(n2)) => RObject::boolean((*n1 as f64) <= *n2), + (RValue::Float(n1), RValue::Integer(n2)) => RObject::boolean(*n1 <= (*n2 as f64)), _ => { - unreachable!("le supports only integer") + unreachable!("le supports only numeric") } }; vm.current_regs()[a].replace(Rc::new(result)); @@ -1548,8 +1554,11 @@ pub(crate) fn op_gt(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val2 = vm.get_current_regs_cloned(b)?; let result = match (&val1.value, &val2.value) { (RValue::Integer(n1), RValue::Integer(n2)) => RObject::boolean(n1 > n2), + (RValue::Float(n1), RValue::Float(n2)) => RObject::boolean(n1 > n2), + (RValue::Integer(n1), RValue::Float(n2)) => RObject::boolean((*n1 as f64) > *n2), + (RValue::Float(n1), RValue::Integer(n2)) => RObject::boolean(*n1 > (*n2 as f64)), _ => { - unreachable!("gt supports only integer") + unreachable!("gt supports only numeric") } }; vm.current_regs()[a].replace(Rc::new(result)); @@ -1563,8 +1572,11 @@ pub(crate) fn op_ge(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val2 = vm.get_current_regs_cloned(b)?; let result = match (&val1.value, &val2.value) { (RValue::Integer(n1), RValue::Integer(n2)) => RObject::boolean(n1 >= n2), + (RValue::Float(n1), RValue::Float(n2)) => RObject::boolean(n1 >= n2), + (RValue::Integer(n1), RValue::Float(n2)) => RObject::boolean((*n1 as f64) >= *n2), + (RValue::Float(n1), RValue::Integer(n2)) => RObject::boolean(*n1 >= (*n2 as f64)), _ => { - unreachable!("ge supports only integer") + unreachable!("ge supports only numeric") } }; vm.current_regs()[a].replace(Rc::new(result)); diff --git a/mrubyedge/src/yamrb/prelude/mod.rs b/mrubyedge/src/yamrb/prelude/mod.rs index 70a3d4f..d0aa3ff 100644 --- a/mrubyedge/src/yamrb/prelude/mod.rs +++ b/mrubyedge/src/yamrb/prelude/mod.rs @@ -16,6 +16,7 @@ pub mod module; pub mod nilclass; pub mod object; pub mod proc; +pub mod rand; pub mod range; pub mod shared_memory; pub mod string; @@ -43,6 +44,7 @@ pub fn prelude(vm: &mut VM) { range::initialize_range(vm); shared_memory::initialize_shared_memory(vm); float::initialize_float(vm); + rand::initialize_rand(vm); #[cfg(feature = "mruby-regexp")] regexp::initialize_regexp(vm); } diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index 95e6eff..106cfa0 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -40,6 +40,7 @@ pub(crate) fn initialize_object(vm: &mut VM) { "==", Box::new(mrb_object_double_eq), ); + mrb_define_cmethod(vm, object_class.clone(), "!=", Box::new(mrb_object_not_eq)); mrb_define_cmethod( vm, object_class.clone(), @@ -184,6 +185,12 @@ pub fn mrb_object_double_eq(vm: &mut VM, args: &[Rc]) -> Result]) -> Result, Error> { + let lhs = vm.getself()?; + let rhs = args[0].clone(); + Ok(mrb_object_is_not_equal(vm, lhs, rhs)) +} + pub fn mrb_object_triple_eq(vm: &mut VM, args: &[Rc]) -> Result, Error> { let lhs = vm.getself()?; let rhs = args[0].clone(); diff --git a/mrubyedge/src/yamrb/prelude/rand.rs b/mrubyedge/src/yamrb/prelude/rand.rs new file mode 100644 index 0000000..a566da1 --- /dev/null +++ b/mrubyedge/src/yamrb/prelude/rand.rs @@ -0,0 +1,265 @@ +use std::cell::{Cell, RefCell}; +use std::rc::Rc; + +use crate::yamrb::helpers::mrb_funcall; +use crate::yamrb::value::{RClass, RHashMap}; +use crate::{ + Error, + yamrb::{ + helpers::{mrb_define_class_cmethod, mrb_define_cmethod}, + value::{RData, RObject, RType, RValue}, + vm::VM, + }, +}; + +use rand_core::SeedableRng; +use rand_xorshift; + +const DEFAULT_RNG_KEY: &str = "@_default_rng"; + +pub struct Random { + pub rng_state: rand_xorshift::XorShiftRng, + pub seed: u64, +} + +fn new_seed() -> u64 { + #[cfg(target_arch = "wasm32")] + { + // ref: https://github.com/rust-lang/rust/blob/b2a322beb29110e22a1782e2ce5ed2a0719b81ed/library/std/src/sys/random/unsupported.rs + let heap = Box::new(0u8); + std::ptr::from_ref(&*heap).addr() as u64 + } + #[cfg(not(target_arch = "wasm32"))] + { + use std::time::{SystemTime, UNIX_EPOCH}; + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_nanos() as u64 + } +} + +fn create_seeded_rng(seed: u64) -> rand_xorshift::XorShiftRng { + // Create a seed array for XorShiftRng (16 bytes) + let seed_bytes = [ + (seed & 0xFF) as u8, + ((seed >> 8) & 0xFF) as u8, + ((seed >> 16) & 0xFF) as u8, + ((seed >> 24) & 0xFF) as u8, + ((seed >> 32) & 0xFF) as u8, + ((seed >> 40) & 0xFF) as u8, + ((seed >> 48) & 0xFF) as u8, + ((seed >> 56) & 0xFF) as u8, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, // Additional bytes to fill 16 bytes + ]; + + rand_xorshift::XorShiftRng::from_seed(seed_bytes) +} + +pub(crate) fn initialize_rand(vm: &mut VM) { + let random_class = vm.define_class("Random", None, None); + + mrb_define_class_cmethod(vm, random_class.clone(), "new", Box::new(mrb_random_new)); + mrb_define_cmethod(vm, random_class.clone(), "rand", Box::new(mrb_random_rand)); + mrb_define_cmethod(vm, random_class.clone(), "seed", Box::new(mrb_random_seed)); + mrb_define_class_cmethod( + vm, + random_class.clone(), + "rand", + Box::new(mrb_random_class_rand), + ); + mrb_define_class_cmethod( + vm, + random_class.clone(), + "srand", + Box::new(mrb_random_class_srand), + ); + + let object_class = vm.object_class.clone(); + mrb_define_cmethod(vm, object_class, "rand", Box::new(mrb_kernel_rand)); + + let default_rng = mrb_random_new(vm, &[]).expect("failed to generate default rng"); + let random_singleton_instance = RObject::class(random_class, vm); + random_singleton_instance.set_ivar(DEFAULT_RNG_KEY, default_rng); +} + +fn get_rng_singleton(vm: &mut VM) -> Rc { + vm.get_const_by_name("Random") + .expect("Random class not found when accessing singleton instance") +} + +fn get_rng_class(vm: &mut VM) -> Rc { + match &get_rng_singleton(vm).value { + RValue::Class(class) => class.clone(), + _ => panic!("Random is not a class"), + } +} + +fn get_default_rng(vm: &mut VM) -> Rc { + get_rng_singleton(vm).get_ivar(DEFAULT_RNG_KEY) +} + +pub(crate) fn mrb_random_new(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let class = get_rng_class(vm); + let args = if !args.is_empty() && args[args.len() - 1].is_nil() { + &args[0..args.len() - 1] + } else { + args + }; + let seed = if args.is_empty() { + new_seed() + } else { + let seed_obj = &args[0]; + seed_obj.as_ref().try_into()? + }; + let rand = Random { + rng_state: create_seeded_rng(seed), + seed, + }; + // For simplicity, we return a new Random instance without state + let random_data = RData { + class, + data: RefCell::new(Some(Rc::new(Box::new(rand)))), + ref_count: 1, + }; + let random_instance = Rc::new(RObject { + tt: RType::Data, + value: RValue::Data(Rc::new(random_data)), + object_id: Cell::new(u64::MAX), + singleton_class: RefCell::new(None), + ivar: RefCell::new(RHashMap::default()), + }); + + Ok(random_instance) +} + +// Random.srand +fn mrb_random_class_srand(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = if !args.is_empty() && args[args.len() - 1].is_nil() { + &args[0..args.len() - 1] + } else { + args + }; + let seed = if args.is_empty() { + new_seed() + } else { + let seed_obj = &args[0]; + seed_obj.as_ref().try_into()? + }; + + let old_seed = { + let default_rng = get_default_rng(vm); + mrb_funcall(vm, Some(default_rng), "seed", &[])? + }; + let new_rng = mrb_random_new(vm, &[Rc::new(RObject::integer(seed as i64))])?; + let random_singleton = get_rng_singleton(vm); + random_singleton.set_ivar(DEFAULT_RNG_KEY, new_rng); + + Ok(old_seed) +} + +// Random#seed +fn mrb_random_seed(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let self_obj = vm.getself()?; + + let seed = match &self_obj.value { + RValue::Data(data) => { + let borrow = data.data.borrow(); + let any_ref = borrow + .as_ref() + .ok_or_else(|| Error::RuntimeError("Invalid Random data".to_string()))?; + let random = any_ref + .downcast_ref::() + .ok_or_else(|| Error::RuntimeError("Invalid Random data".to_string()))?; + random.seed + } + _ => { + return Err(Error::RuntimeError( + "Random#seed must be called on a Random object".to_string(), + )); + } + }; + + Ok(Rc::new(RObject::integer(seed as i64))) +} + +// Random#rand +fn mrb_random_rand(vm: &mut VM, args: &[Rc]) -> Result, Error> { + use rand_core::Rng; + + let args = if !args.is_empty() && args[args.len() - 1].is_nil() { + &args[0..args.len() - 1] + } else { + args + }; + + let self_obj = vm.getself()?; + + let result = match &self_obj.value { + RValue::Data(data) => { + let mut borrow = data.data.borrow_mut(); + let any_ref = borrow + .as_mut() + .ok_or_else(|| Error::RuntimeError("Invalid Random data".to_string()))?; + let random = Rc::get_mut(any_ref) + .and_then(|boxed| boxed.downcast_mut::()) + .ok_or_else(|| Error::RuntimeError("Invalid Random data".to_string()))?; + let rng: &mut dyn Rng = &mut random.rng_state; + + if args.is_empty() { + // Return a float between 0.0 and 1.0 + let value = (rng.next_u32() as f64) / (u32::MAX as f64); + Rc::new(RObject::float(value)) + } else { + let max_obj = &args[0]; + match max_obj.value { + RValue::Integer(max) => { + if max <= 0 { + return Err(Error::ArgumentError("max must be positive".to_string())); + } + let value = (rng.next_u64() % (max as u64)) as i64; + Rc::new(RObject::integer(value)) + } + RValue::Float(max) => { + if max <= 0.0 { + return Err(Error::ArgumentError("max must be positive".to_string())); + } + let value = (rng.next_u32() as f64) / (u32::MAX as f64) * max; + Rc::new(RObject::float(value)) + } + _ => { + return Err(Error::ArgumentError( + "argument must be a number".to_string(), + )); + } + } + } + } + _ => { + return Err(Error::RuntimeError( + "Random#rand must be called on a Random object".to_string(), + )); + } + }; + + Ok(result) +} + +// Random.rand (class method) +fn mrb_random_class_rand(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let default_rng = get_default_rng(vm); + mrb_funcall(vm, Some(default_rng), "rand", args) +} + +// Kernel#rand +fn mrb_kernel_rand(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let default_rng = get_default_rng(vm); + mrb_funcall(vm, Some(default_rng), "rand", args) +} diff --git a/mrubyedge/tests/random.rs b/mrubyedge/tests/random.rs new file mode 100644 index 0000000..53a5193 --- /dev/null +++ b/mrubyedge/tests/random.rs @@ -0,0 +1,183 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn random_new_test() { + let code = " +r = Random.new +r.class.inspect + "; + let binary = mrbc_compile("random_new", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(result_str, "Random"); +} + +#[test] +fn random_new_with_seed_test() { + let code = " +r = Random.new(12345) +r.seed + "; + let binary = mrbc_compile("random_new_seed", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 12345); +} + +#[test] +fn random_rand_no_args_test() { + let code = " +r = Random.new(42) +val = r.rand +puts val.to_s +val >= 0.0 && val < 1.0 + "; + let binary = mrbc_compile("random_rand_no_args", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + assert!(result.as_ref().is_truthy()); +} + +#[test] +fn random_rand_with_int_test() { + let code = " +r = Random.new(100) +val = r.rand(10) +puts val.to_s +val >= 0 && val < 10 + "; + let binary = mrbc_compile("random_rand_int", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + assert!(result.as_ref().is_truthy()); +} + +#[test] +fn random_rand_with_float_test() { + let code = " +r = Random.new(200) +val = r.rand(5.0) +puts val.to_s +val >= 0.0 && val < 5.0 + "; + let binary = mrbc_compile("random_rand_float", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + assert!(result.as_ref().is_truthy()); +} + +#[test] +fn random_class_rand_test() { + let code = " +val = Random.rand +puts val.to_s +val >= 0.0 && val < 1.0 + "; + let binary = mrbc_compile("random_class_rand", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + assert!(result.as_ref().is_truthy()); +} + +#[test] +fn random_class_rand_with_arg_test() { + let code = " +val = Random.rand(100) +puts val.to_s +val >= 0 && val < 100 + "; + let binary = mrbc_compile("random_class_rand_arg", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + assert!(result.as_ref().is_truthy()); +} + +#[test] +fn kernel_rand_test() { + let code = " +val = rand +puts val.to_s +val >= 0.0 && val < 1.0 + "; + let binary = mrbc_compile("kernel_rand", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + assert!(result.as_ref().is_truthy()); +} + +#[test] +fn kernel_rand_with_arg_test() { + let code = " +val = rand(50) +puts val.to_s +val >= 0 && val < 50 + "; + let binary = mrbc_compile("kernel_rand_arg", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + assert!(result.as_ref().is_truthy()); +} + +#[test] +fn random_srand_test() { + let code = " +Random.srand(777) +old = Random.srand(888) +old + "; + let binary = mrbc_compile("random_srand", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 777); +} + +#[test] +fn random_deterministic_test() { + let code = " +r1 = Random.new(12345) +r2 = Random.new(12345) +a = r1.rand(100) +b = r2.rand(100) +puts \"a = #{a}, b = #{b}\" +a == b + "; + let binary = mrbc_compile("random_deterministic", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + assert!(result.as_ref().is_truthy()); +} + +#[test] +fn random_deterministic_test_2() { + let code = " +r1 = Random.new(12345) +r2 = Random.new(123456) +a = r1.rand(100) +b = r2.rand(100) +puts \"a = #{a}, b = #{b}\" +a != b + "; + let binary = mrbc_compile("random_deterministic_2", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + assert!(result.as_ref().is_truthy()); +} From 2702157ff3100f5fce8670e0c4acad9046f9d027 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 22:07:42 +0900 Subject: [PATCH 193/314] cfg(feature = "mruby-regexp") --- Cargo.lock | 1 - mrubyedge/Cargo.toml | 7 +++---- mrubyedge/src/yamrb/prelude/mod.rs | 5 ++++- mrubyedge/tests/random.rs | 1 + 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 203146a..d15d3f2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -575,7 +575,6 @@ version = "1.1.0-rc1" dependencies = [ "criterion", "fnv", - "getrandom", "mec-mrbc-sys", "mruby-compiler2-sys", "once_cell", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 349a2c4..053f66e 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -10,10 +10,9 @@ license = "BSD-3-Clause" [dependencies] fnv = { version = "1.0.7", optional = true } -getrandom = { version = "0.3.4", optional = true } plain = "0.2.3" -rand_core = { version = "0.10.0" } -rand_xorshift = { version = "0.5.0" } +rand_core = { version = "0.10.0", optional = true } +rand_xorshift = { version = "0.5.0", optional = true } regex = { version = "1.12.2", default-features = false, features = [ "std", ], optional = true } @@ -40,6 +39,6 @@ wasi = [] mruby-hash-fnv = ["dep:fnv"] mruby-regexp = ["dep:regex"] mrubyedge-debug = ["wasi"] -# mruby-random = ["dep:rand", "dep:rand_xorshift"] +mruby-random = ["dep:rand_core", "dep:rand_xorshift"] # mruby-securerandom = ["wasi", "dep:getrandom"] no-wasi = [] diff --git a/mrubyedge/src/yamrb/prelude/mod.rs b/mrubyedge/src/yamrb/prelude/mod.rs index d0aa3ff..6c218dd 100644 --- a/mrubyedge/src/yamrb/prelude/mod.rs +++ b/mrubyedge/src/yamrb/prelude/mod.rs @@ -16,13 +16,15 @@ pub mod module; pub mod nilclass; pub mod object; pub mod proc; -pub mod rand; pub mod range; pub mod shared_memory; pub mod string; pub mod symbol; pub mod trueclass; +#[cfg(feature = "mruby-random")] +pub mod rand; + #[cfg(feature = "mruby-regexp")] pub mod regexp; @@ -44,6 +46,7 @@ pub fn prelude(vm: &mut VM) { range::initialize_range(vm); shared_memory::initialize_shared_memory(vm); float::initialize_float(vm); + #[cfg(feature = "mruby-random")] rand::initialize_rand(vm); #[cfg(feature = "mruby-regexp")] regexp::initialize_regexp(vm); diff --git a/mrubyedge/tests/random.rs b/mrubyedge/tests/random.rs index 53a5193..4c5f49d 100644 --- a/mrubyedge/tests/random.rs +++ b/mrubyedge/tests/random.rs @@ -1,3 +1,4 @@ +#![cfg(feature = "mruby-random")] extern crate mec_mrbc_sys; extern crate mrubyedge; From 4b98a8098c05aec009a31bfb717f67f7425408a4 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 22:10:46 +0900 Subject: [PATCH 194/314] Add to CI --- .github/workflows/mrubyedge.yml | 6 +++--- Cargo.lock | 1 + mrubyedge-cli/Cargo.toml | 6 +++++- mrubyedge/src/yamrb/prelude/regexp.rs | 1 - 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/workflows/mrubyedge.yml b/.github/workflows/mrubyedge.yml index 5d0e529..1f648e1 100644 --- a/.github/workflows/mrubyedge.yml +++ b/.github/workflows/mrubyedge.yml @@ -43,7 +43,7 @@ jobs: set +e cargo test --workspace \ --exclude mec \ - --features "mrubyedge-debug,mruby-regexp${{ matrix.ENABLE_FNV_HASH }}" \ + --features "mrubyedge-debug,mruby-random,mruby-regexp${{ matrix.ENABLE_FNV_HASH }}" \ --profile ${{ matrix.BUILD_TARGET }} TEST_RESULT=$? if [ $TEST_RESULT != 0 ]; then @@ -55,7 +55,7 @@ jobs: run: | cargo build --workspace \ --exclude mec \ - --features "mrubyedge-debug,mruby-regexp${{ matrix.ENABLE_FNV_HASH }}" \ + --features "mrubyedge-debug,mruby-random,mruby-regexp${{ matrix.ENABLE_FNV_HASH }}" \ --profile ${{ matrix.BUILD_TARGET }} lint: @@ -73,4 +73,4 @@ jobs: - name: Install C compiler run: sudo apt-get update && sudo apt-get install -y build-essential - name: Run linter - run: cargo clippy -p mrubyedge \ No newline at end of file + run: cargo clippy -p mrubyedge --features "mruby-random,mruby-regexp" \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index d15d3f2..80b19d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -564,6 +564,7 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f34d536e7b90424d866f961ce38f7c21c2e8a0673e63be3539e70ac0c9e729a" dependencies = [ + "getrandom", "plain", "regex", "simple_endian", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 1792f53..3fa9689 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -14,7 +14,11 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } mruby-compiler2-sys = "0.2.2" -mrubyedge = { version = "1.0.20", features = ["default", "mruby-regexp"] } +mrubyedge = { version = "1.0.20", features = [ + "default", + "mruby-random", + "mruby-regexp", +] } rand = "0.9.2" nom = "7.1.3" askama = "0.12.1" diff --git a/mrubyedge/src/yamrb/prelude/regexp.rs b/mrubyedge/src/yamrb/prelude/regexp.rs index 1dd5956..cc430d3 100644 --- a/mrubyedge/src/yamrb/prelude/regexp.rs +++ b/mrubyedge/src/yamrb/prelude/regexp.rs @@ -1,4 +1,3 @@ -#![cfg(feature = "mruby-regexp")] use std::cell::{Cell, RefCell}; use std::rc::Rc; From 13dbed7def542aba88e599c481d73faf5641eb6a Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Feb 2026 22:14:57 +0900 Subject: [PATCH 195/314] Bump version to 1.1.0 --- Cargo.lock | 25 +++++++++++++------------ mrubyedge-cli/Cargo.toml | 4 ++-- mrubyedge/Cargo.toml | 2 +- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 80b19d9..702a149 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -560,25 +560,26 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.0.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f34d536e7b90424d866f961ce38f7c21c2e8a0673e63be3539e70ac0c9e729a" +version = "1.1.0" dependencies = [ - "getrandom", + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", + "rand_core 0.10.0", + "rand_xorshift", "regex", "simple_endian", ] [[package]] name = "mrubyedge" -version = "1.1.0-rc1" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb58245ddb9f38ad7d3426a244b823567fb6a00e5a82b49eaa42e40ab66b652c" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -588,12 +589,12 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.0.20" +version = "1.1.0" dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.0.20", + "mrubyedge 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 3fa9689..d2e3859 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.0.20" +version = "1.1.0" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } mruby-compiler2-sys = "0.2.2" -mrubyedge = { version = "1.0.20", features = [ +mrubyedge = { version = "1.1.0", features = [ "default", "mruby-random", "mruby-regexp", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 053f66e..2b33ac9 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.1.0-rc1" +version = "1.1.0" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From 21a87619e218e2acc780d8a55450caab52b2276b Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 4 Feb 2026 00:47:19 +0900 Subject: [PATCH 196/314] Implemented JSON serialization for mruby objects using serde_json --- Cargo.lock | 10 ++ Cargo.toml | 2 +- mruby-serde-json/Cargo.toml | 14 ++ mruby-serde-json/src/json_value/mod.rs | 93 ++++++++++++ mruby-serde-json/src/lib.rs | 28 ++++ mruby-serde-json/tests/dump.rs | 194 +++++++++++++++++++++++++ mruby-serde-json/tests/helpers/mod.rs | 35 +++++ 7 files changed, 375 insertions(+), 1 deletion(-) create mode 100644 mruby-serde-json/Cargo.toml create mode 100644 mruby-serde-json/src/json_value/mod.rs create mode 100644 mruby-serde-json/src/lib.rs create mode 100644 mruby-serde-json/tests/dump.rs create mode 100644 mruby-serde-json/tests/helpers/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 702a149..116c6a6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -558,6 +558,16 @@ dependencies = [ "libc", ] +[[package]] +name = "mruby-serde-json" +version = "0.1.0" +dependencies = [ + "mec-mrbc-sys", + "mrubyedge 1.1.0", + "serde", + "serde_json", +] + [[package]] name = "mrubyedge" version = "1.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3b62b1e..19ee2f6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [workspace] resolver = "2" -members = [ +members = ["mruby-serde-json", "mrubyedge", "mrubyedge-cli", ] diff --git a/mruby-serde-json/Cargo.toml b/mruby-serde-json/Cargo.toml new file mode 100644 index 0000000..030a4b4 --- /dev/null +++ b/mruby-serde-json/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "mruby-serde-json" +version = "0.1.0" +edition = "2024" + +[dependencies] +# mrubyedge = ">= 1.1.0" +mrubyedge = { version = "1.1.0", path = "../mrubyedge", default-features = false } +serde = ">= 1.0.228" +serde_json = ">= 1.0.149" + +[dev-dependencies] +mrubyedge = { version = "1.1.0", path = "../mrubyedge", features = ["default"] } +mec-mrbc-sys = "3.3.1" diff --git a/mruby-serde-json/src/json_value/mod.rs b/mruby-serde-json/src/json_value/mod.rs new file mode 100644 index 0000000..bbecd86 --- /dev/null +++ b/mruby-serde-json/src/json_value/mod.rs @@ -0,0 +1,93 @@ +use std::cell::RefCell; +use std::rc::Rc; + +use mrubyedge::Error; +use mrubyedge::yamrb::helpers::mrb_funcall; +use mrubyedge::yamrb::value::RObject; +use mrubyedge::yamrb::value::RValue; +use mrubyedge::yamrb::vm::VM; +use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; + +pub struct Json<'a> { + mrb: Rc>, + inner: Rc, +} + +impl<'a> Json<'a> { + pub fn get_inner(&self) -> Rc { + self.inner.clone() + } + + pub fn from_robject(mrb: Rc>, inner: Rc) -> Self { + Self { mrb, inner } + } +} + +impl<'a> From> for Rc { + fn from(value: Json<'a>) -> Self { + value.get_inner() + } +} + +impl<'a> Serialize for Json<'a> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self.get_inner().value { + RValue::Nil => serializer.serialize_none(), + RValue::Bool(b) => serializer.serialize_bool(b), + RValue::Integer(i) => serializer.serialize_i64(i), + RValue::Float(f) => serializer.serialize_f64(f), + RValue::String(ref s, _) => { + serializer.serialize_str(&String::from_utf8_lossy(&s.borrow())) + } + RValue::Symbol(ref s) => serializer.serialize_str(&s.name), + RValue::Array(ref arr) => { + let arr = arr.borrow(); + let mut seq = serializer.serialize_seq(Some(arr.len()))?; + for item in arr.iter() { + let json_item = Json::from_robject(self.mrb.clone(), item.clone()); + seq.serialize_element(&json_item)?; + } + seq.end() + } + RValue::Hash(ref hash) => { + let hash = hash.borrow(); + let mut map = serializer.serialize_map(Some(hash.len()))?; + for (_, (key, value)) in hash.iter() { + let key_str = match key.value { + RValue::String(ref s, _) => { + String::from_utf8_lossy(&s.borrow()).to_string() + } + RValue::Symbol(ref s) => s.name.to_string(), + _ => { + return Err(serde::ser::Error::custom("Non-string key in JSON object")); + } + }; + let json_value = Json::from_robject(self.mrb.clone(), value.clone()); + map.serialize_entry(&key_str, &json_value)?; + } + map.end() + } + _ => { + let obj = self.get_inner(); + let vm = &mut self.mrb.borrow_mut(); + let serializable = mrb_funcall(vm, Some(obj), "to_json", &[]); + let json_obj = Json::from_robject( + self.mrb.clone(), + serializable.expect("to_json not defined for instance"), + ); + json_obj.serialize(serializer) + } + } + } +} + +pub(crate) fn mrb_json_dump(vm: &mut VM, obj: Rc) -> Result, Error> { + let vm = Rc::new(RefCell::new(vm)); + let json_value = Json::from_robject(vm, obj); + let serialized = + serde_json::to_string(&json_value).expect("Failed to serialize JSON value to string"); + Ok(RObject::string(serialized).to_refcount_assigned()) +} diff --git a/mruby-serde-json/src/lib.rs b/mruby-serde-json/src/lib.rs new file mode 100644 index 0000000..e3de96e --- /dev/null +++ b/mruby-serde-json/src/lib.rs @@ -0,0 +1,28 @@ +pub mod json_value; + +use std::rc::Rc; + +use mrubyedge::{ + Error, + yamrb::{helpers::mrb_define_class_cmethod, value::RObject, vm::VM}, +}; + +pub fn init_json(vm: &mut VM) { + let json_class = vm.define_class("JSON", None, None); + mrb_define_class_cmethod(vm, json_class, "dump", Box::new(mrb_json_class_dump)); +} + +pub fn mrb_json_class_dump(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = if args[args.len() - 1].is_nil() { + &args[0..args.len() - 1] + } else { + args + }; + if args.len() != 1 { + return Err(Error::ArgumentError( + "wrong number of arguments".to_string(), + )); + } + let result = json_value::mrb_json_dump(vm, args[0].clone())?; + Ok(result) +} diff --git a/mruby-serde-json/tests/dump.rs b/mruby-serde-json/tests/dump.rs new file mode 100644 index 0000000..bba219e --- /dev/null +++ b/mruby-serde-json/tests/dump.rs @@ -0,0 +1,194 @@ +extern crate mruby_serde_json; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn test_json_dump_integer() { + let code = " + JSON.dump(42) + "; + let binary = mrbc_compile("json_dump_integer", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + let json_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(json_str, "42"); +} + +#[test] +fn test_json_dump_string() { + let code = r#" + JSON.dump("hello") + "#; + let binary = mrbc_compile("json_dump_string", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + let json_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(json_str, r#""hello""#); +} + +#[test] +fn test_json_dump_array() { + let code = " + JSON.dump([1, 2, 3]) + "; + let binary = mrbc_compile("json_dump_array", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + let json_str: String = result.as_ref().try_into().unwrap(); + println!("json_str: {}", json_str); + assert_eq!(json_str, "[1,2,3]"); +} + +#[test] +fn test_json_dump_hash() { + let code = r#" + JSON.dump({ + "name" => "Alice", + "age" => 30 + }) + "#; + let binary = mrbc_compile("json_dump_hash", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + let json_str: String = result.as_ref().try_into().unwrap(); + println!("json_str: {}", json_str); + assert!(json_str.contains(r#""name":"Alice""#)); + assert!(json_str.contains(r#""age":30"#)); +} + +#[test] +fn test_json_dump_nested_structure() { + let code = r#" + JSON.dump({ + "users" => [{ + "name" => "Bob", + "age" => 25 + }, { + "name" => "Carol", + "age" => 28 + }] + }) + "#; + let binary = mrbc_compile("json_dump_nested", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + let json_str: String = result.as_ref().try_into().unwrap(); + println!("json_str: {}", json_str); + assert!(json_str.contains(r#""users""#)); + assert!(json_str.contains(r#""name":"Bob""#)); + assert!(json_str.contains(r#""age":25"#)); + assert!(json_str.contains(r#""name":"Carol""#)); + assert!(json_str.contains(r#""age":28"#)); +} + +#[test] +fn test_json_dump_boolean() { + let code = " + JSON.dump(true) + "; + let binary = mrbc_compile("json_dump_bool", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + let json_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(json_str, "true"); +} + +#[test] +fn test_json_dump_nil() { + let code = " + JSON.dump(nil) + "; + let binary = mrbc_compile("json_dump_nil", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + let json_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(json_str, "null"); +} + +#[test] +fn test_json_dump_float() { + let code = " + JSON.dump(3.14) + "; + let binary = mrbc_compile("json_dump_float", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + let json_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(json_str, "3.14"); +} + +#[test] +fn test_json_dump_symbol_key() { + let code = " + JSON.dump({ + status: :ok + }) + "; + let binary = mrbc_compile("json_dump_symbol", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + let json_str: String = result.as_ref().try_into().unwrap(); + println!("json_str: {}", json_str); + assert!(json_str.contains(r#""status":"ok""#)); +} + +#[test] +fn test_json_dump_to_json() { + let code = r#" + class User + def initialize(name, age) + @name = name + @age = age + end + + def to_json(*_args) + { + name: @name, + age: @age + } + end + end + JSON.dump(User.new("Dave", 40)) + "#; + let binary = mrbc_compile("json_dump_to_json", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + let json_str: String = result.as_ref().try_into().unwrap(); + println!("json_str: {}", json_str); + assert!(json_str.contains(r#""name":"Dave""#)); + assert!(json_str.contains(r#""age":40"#)); +} + +// FIXME: panic!s when to_json is not defined in user-defined class diff --git a/mruby-serde-json/tests/helpers/mod.rs b/mruby-serde-json/tests/helpers/mod.rs new file mode 100644 index 0000000..5e41bc7 --- /dev/null +++ b/mruby-serde-json/tests/helpers/mod.rs @@ -0,0 +1,35 @@ +#![allow(dead_code)] + +use std::{ffi::CStr, fs::File, io::Write}; + +pub fn mrbc_compile(fname: &'static str, code: &'static str) -> Vec { + let mut src = std::env::temp_dir(); + src.push(format!("{}.{}.rb", fname, std::process::id())); + let mut f = File::create(&src).expect("cannot open src file"); + f.write_all(code.as_bytes()) + .expect("cannot create src file"); + f.flush().unwrap(); + + let mut src0 = src.as_os_str().to_string_lossy().into_owned(); + src0.push('\0'); + + let mut dest = std::env::temp_dir(); + dest.push(format!("{}.{}.mrb", fname, std::process::id())); + let mut dest0 = dest.as_os_str().to_string_lossy().into_owned(); + dest0.push('\0'); + + let args = [ + CStr::from_bytes_with_nul(b"mrbc\0").unwrap().as_ptr(), + CStr::from_bytes_with_nul(b"-o\0").unwrap().as_ptr(), + CStr::from_bytes_with_nul(dest0.as_bytes()) + .unwrap() + .as_ptr(), + CStr::from_bytes_with_nul(src0.as_bytes()).unwrap().as_ptr(), + ]; + unsafe { + mec_mrbc_sys::mrbc_main(args.len() as i32, args.as_ptr() as *mut *mut i8); + } + + let binary = std::fs::read(dest).unwrap(); + binary +} From 595092aaf7f9673160e1b4d629a790169b211ea7 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 4 Feb 2026 00:48:28 +0900 Subject: [PATCH 197/314] Fix format --- Cargo.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 19ee2f6..7239b09 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,4 @@ [workspace] resolver = "2" -members = ["mruby-serde-json", - "mrubyedge", "mrubyedge-cli", -] +members = ["mruby-serde-json", "mrubyedge", "mrubyedge-cli"] From e8787ecab2d93a38f81452daa19661840ff78bda Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 4 Feb 2026 00:52:43 +0900 Subject: [PATCH 198/314] New CI target --- .github/workflows/mruby-serde-json.yml | 43 ++++++++++++++++++++++++++ .github/workflows/mrubyedge.yml | 7 ++--- 2 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/mruby-serde-json.yml diff --git a/.github/workflows/mruby-serde-json.yml b/.github/workflows/mruby-serde-json.yml new file mode 100644 index 0000000..38a4322 --- /dev/null +++ b/.github/workflows/mruby-serde-json.yml @@ -0,0 +1,43 @@ +name: mruby-serde-json CI + +on: + push: + branches: + - master + paths: + - 'mruby-serde-json/**' + pull_request: + branches: + - master + paths: + - 'mruby-serde-json/**' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + BUILD_TARGET: [release] + steps: + - uses: actions/checkout@v5 + - name: Cache + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-serde-json-${{ hashFiles('**/Cargo.lock') }} + # - name: Install C compiler + # run: sudo apt-get update && sudo apt-get install -y build-essential + - name: Run formatter + run: | + cargo fmt -p mruby-serde-json --check + - name: Run tests for "${{ matrix.BUILD_TARGET }}" profile + run: | + cargo test -p mruby-serde-json \ + --profile ${{ matrix.BUILD_TARGET }} + - name: Build binaries for "${{ matrix.BUILD_TARGET }}" profile + run: | + cargo build -p mruby-serde-json \ + --profile ${{ matrix.BUILD_TARGET }} diff --git a/.github/workflows/mrubyedge.yml b/.github/workflows/mrubyedge.yml index 1f648e1..04bd624 100644 --- a/.github/workflows/mrubyedge.yml +++ b/.github/workflows/mrubyedge.yml @@ -41,8 +41,7 @@ jobs: - name: Run tests for "${{ matrix.BUILD_TARGET }}${{ matrix.ENABLE_FNV_HASH }}" profile run: | set +e - cargo test --workspace \ - --exclude mec \ + cargo test -p mrubyedge \ --features "mrubyedge-debug,mruby-random,mruby-regexp${{ matrix.ENABLE_FNV_HASH }}" \ --profile ${{ matrix.BUILD_TARGET }} TEST_RESULT=$? @@ -53,10 +52,10 @@ jobs: fi - name: Build binaries for "${{ matrix.BUILD_TARGET }}${{ matrix.ENABLE_FNV_HASH }}" profile run: | - cargo build --workspace \ - --exclude mec \ + cargo build -p mrubyedge \ --features "mrubyedge-debug,mruby-random,mruby-regexp${{ matrix.ENABLE_FNV_HASH }}" \ --profile ${{ matrix.BUILD_TARGET }} + cargo build -p mrubyedge-cli lint: runs-on: ubuntu-latest From 568e3b5dba93ce850a7238b83ddb402caaddf577 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 5 Feb 2026 23:44:59 +0900 Subject: [PATCH 199/314] Implement JSON.load function --- mruby-serde-json/src/json_value/mod.rs | 68 ++++++++ mruby-serde-json/src/lib.rs | 42 ++++- mruby-serde-json/tests/helpers/mod.rs | 3 +- mruby-serde-json/tests/load.rs | 224 +++++++++++++++++++++++++ 4 files changed, 334 insertions(+), 3 deletions(-) create mode 100644 mruby-serde-json/tests/load.rs diff --git a/mruby-serde-json/src/json_value/mod.rs b/mruby-serde-json/src/json_value/mod.rs index bbecd86..fa35e5b 100644 --- a/mruby-serde-json/src/json_value/mod.rs +++ b/mruby-serde-json/src/json_value/mod.rs @@ -7,6 +7,7 @@ use mrubyedge::yamrb::value::RObject; use mrubyedge::yamrb::value::RValue; use mrubyedge::yamrb::vm::VM; use serde::ser::{Serialize, SerializeMap, SerializeSeq, Serializer}; +use serde_json::Value; pub struct Json<'a> { mrb: Rc>, @@ -91,3 +92,70 @@ pub(crate) fn mrb_json_dump(vm: &mut VM, obj: Rc) -> Result serde_json::to_string(&json_value).expect("Failed to serialize JSON value to string"); Ok(RObject::string(serialized).to_refcount_assigned()) } + +pub struct JsonValue { + inner: Rc, +} + +impl JsonValue { + pub fn new(inner: Rc) -> Self { + Self { inner } + } + + pub fn get_inner(&self) -> Rc { + self.inner.clone() + } +} + +impl From for Rc { + fn from(value: JsonValue) -> Self { + value.get_inner() + } +} + +impl From for JsonValue { + fn from(value: Value) -> Self { + let obj = match value { + Value::Null => RObject::nil().to_refcount_assigned(), + Value::Bool(b) => RObject::boolean(b).to_refcount_assigned(), + Value::Number(n) => { + if let Some(i) = n.as_i64() { + RObject::integer(i).to_refcount_assigned() + } else if let Some(u) = n.as_u64() { + RObject::integer(u as i64).to_refcount_assigned() + } else if let Some(f) = n.as_f64() { + RObject::float(f).to_refcount_assigned() + } else { + panic!("Invalid range of numeric value"); + } + } + Value::String(s) => RObject::string(s).to_refcount_assigned(), + Value::Array(arr) => { + let vec = arr.into_iter().map(JsonValue::from).collect::>(); + let arr = RObject::array(vec.into_iter().map(|j| j.get_inner()).collect()); + arr.to_refcount_assigned() + } + Value::Object(obj) => { + let map = obj + .into_iter() + .map(|(k, v)| { + let key = RObject::string(k).to_refcount_assigned(); + ( + key.as_hash_key().expect("object cannot use for hashed key"), + (key.clone(), JsonValue::from(v).get_inner()), + ) + }) + .collect(); + let hash = RObject::hash(map); + hash.to_refcount_assigned() + } + }; + JsonValue::new(obj) + } +} + +pub(crate) fn mrb_json_load(_vm: &mut VM, json_str: impl Into) -> Result { + let value = serde_json::from_str::(&json_str.into()) + .map_err(|e| Error::RuntimeError(format!("Failed to parse JSON string: {}", e)))?; + Ok(value.into()) +} diff --git a/mruby-serde-json/src/lib.rs b/mruby-serde-json/src/lib.rs index e3de96e..ea8333c 100644 --- a/mruby-serde-json/src/lib.rs +++ b/mruby-serde-json/src/lib.rs @@ -9,7 +9,31 @@ use mrubyedge::{ pub fn init_json(vm: &mut VM) { let json_class = vm.define_class("JSON", None, None); - mrb_define_class_cmethod(vm, json_class, "dump", Box::new(mrb_json_class_dump)); + + mrb_define_class_cmethod( + vm, + json_class.clone(), + "dump", + Box::new(mrb_json_class_dump), + ); + mrb_define_class_cmethod( + vm, + json_class.clone(), + "generate", + Box::new(mrb_json_class_dump), + ); + mrb_define_class_cmethod( + vm, + json_class.clone(), + "load", + Box::new(mrb_json_class_load), + ); + mrb_define_class_cmethod( + vm, + json_class.clone(), + "parse", + Box::new(mrb_json_class_load), + ); } pub fn mrb_json_class_dump(vm: &mut VM, args: &[Rc]) -> Result, Error> { @@ -26,3 +50,19 @@ pub fn mrb_json_class_dump(vm: &mut VM, args: &[Rc]) -> Result]) -> Result, Error> { + let args = if args[args.len() - 1].is_nil() { + &args[0..args.len() - 1] + } else { + args + }; + if args.len() != 1 { + return Err(Error::ArgumentError( + "wrong number of arguments".to_string(), + )); + } + let json_str: String = args[0].as_ref().try_into()?; + let result = json_value::mrb_json_load(vm, json_str)?; + Ok(result.get_inner()) +} diff --git a/mruby-serde-json/tests/helpers/mod.rs b/mruby-serde-json/tests/helpers/mod.rs index 5e41bc7..41f22e4 100644 --- a/mruby-serde-json/tests/helpers/mod.rs +++ b/mruby-serde-json/tests/helpers/mod.rs @@ -30,6 +30,5 @@ pub fn mrbc_compile(fname: &'static str, code: &'static str) -> Vec { mec_mrbc_sys::mrbc_main(args.len() as i32, args.as_ptr() as *mut *mut i8); } - let binary = std::fs::read(dest).unwrap(); - binary + std::fs::read(dest).unwrap() } diff --git a/mruby-serde-json/tests/load.rs b/mruby-serde-json/tests/load.rs new file mode 100644 index 0000000..b0237ab --- /dev/null +++ b/mruby-serde-json/tests/load.rs @@ -0,0 +1,224 @@ +extern crate mruby_serde_json; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn test_json_load_integer() { + let code = r#" + JSON.load("42") + "#; + let binary = mrbc_compile("json_load_integer", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + let value: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(value, 42); +} + +#[test] +fn test_json_load_string() { + let code = r#" + JSON.load('"hello"') + "#; + let binary = mrbc_compile("json_load_string", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + let value: String = result.as_ref().try_into().unwrap(); + assert_eq!(value, "hello"); +} + +#[test] +fn test_json_load_array() { + let code = r#" + result = JSON.load('[ + 1, + 2, + 3 + ]') + result + "#; + let binary = mrbc_compile("json_load_array", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + if let mrubyedge::yamrb::value::RValue::Array(arr) = &result.value { + assert_eq!(arr.borrow().len(), 3); + let v0: i64 = arr.borrow()[0].as_ref().try_into().unwrap(); + let v1: i64 = arr.borrow()[1].as_ref().try_into().unwrap(); + let v2: i64 = arr.borrow()[2].as_ref().try_into().unwrap(); + assert_eq!(v0, 1); + assert_eq!(v1, 2); + assert_eq!(v2, 3); + } else { + panic!("Expected array result"); + } +} + +#[test] +fn test_json_load_hash() { + let code = r#" + result = JSON.load('{ + "name": "Alice", + "age": 30 + }') + result["name"] + "#; + let binary = mrbc_compile("json_load_hash_name", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + let result = vm.run().unwrap(); + let name: String = result.as_ref().try_into().unwrap(); + assert_eq!(name, "Alice"); + + let code2 = r#" + result = JSON.load('{ + "name": "Alice", + "age": 30 + }') + result["age"] + "#; + let binary2 = mrbc_compile("json_load_hash_age", code2); + let mut rite2 = mrubyedge::rite::load(&binary2).unwrap(); + let mut vm2 = mrubyedge::yamrb::vm::VM::open(&mut rite2); + mruby_serde_json::init_json(&mut vm2); + let result2 = vm2.run().unwrap(); + let age: i64 = result2.as_ref().try_into().unwrap(); + assert_eq!(age, 30); +} + +#[test] +fn test_json_load_nested_structure() { + let code = r#" + result = JSON.load('{ + "users": [ + { + "name": "Bob", + "age": 25 + }, + { + "name": "Carol", + "age": 28 + } + ] + }') + result["users"][0]["name"] + "#; + let binary = mrbc_compile("json_load_nested", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + let name: String = result.as_ref().try_into().unwrap(); + assert_eq!(name, "Bob"); + + let code2 = r#" + result = JSON.load('{ + "users": [ + { + "name": "Bob", + "age": 25 + }, + { + "name": "Carol", + "age": 28 + } + ] + }') + result["users"][1]["name"] + "#; + let binary2 = mrbc_compile("json_load_nested2", code2); + let mut rite2 = mrubyedge::rite::load(&binary2).unwrap(); + let mut vm2 = mrubyedge::yamrb::vm::VM::open(&mut rite2); + mruby_serde_json::init_json(&mut vm2); + let result2 = vm2.run().unwrap(); + let name2: String = result2.as_ref().try_into().unwrap(); + assert_eq!(name2, "Carol"); +} + +#[test] +fn test_json_load_boolean() { + let code = r#" + JSON.load("true") + "#; + let binary = mrbc_compile("json_load_bool", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + let value: bool = result.as_ref().try_into().unwrap(); + assert_eq!(value, true); +} + +#[test] +fn test_json_load_boolean_2() { + let code = r#" + JSON.load("false") + "#; + let binary = mrbc_compile("json_load_bool_2", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + let value: bool = result.as_ref().try_into().unwrap(); + assert_eq!(value, false); +} + +#[test] +fn test_json_load_nil() { + let code = r#" + JSON.load("null") + "#; + let binary = mrbc_compile("json_load_nil", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + assert!(result.is_nil()); +} + +#[test] +fn test_json_load_float() { + let code = r#" + JSON.load("3.14") + "#; + let binary = mrbc_compile("json_load_float", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(value, 3.14); +} + +#[test] +fn test_json_load_key() { + let code = r#" + result = JSON.load('{ + "status": "ok" + }') + result["status"] + "#; + let binary = mrbc_compile("json_load_key", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_serde_json::init_json(&mut vm); + + let result = vm.run().unwrap(); + let value: String = result.as_ref().try_into().unwrap(); + assert_eq!(value, "ok"); +} From 4318eef7b7bdb1cbdbdcbebf65f997b087cf336f Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 5 Feb 2026 23:48:50 +0900 Subject: [PATCH 200/314] Fix dependencies --- Cargo.lock | 2 +- mruby-serde-json/Cargo.toml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 116c6a6..024e5d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -563,7 +563,7 @@ name = "mruby-serde-json" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.0", + "mrubyedge 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "serde_json", ] diff --git a/mruby-serde-json/Cargo.toml b/mruby-serde-json/Cargo.toml index 030a4b4..6c44aff 100644 --- a/mruby-serde-json/Cargo.toml +++ b/mruby-serde-json/Cargo.toml @@ -4,11 +4,11 @@ version = "0.1.0" edition = "2024" [dependencies] -# mrubyedge = ">= 1.1.0" -mrubyedge = { version = "1.1.0", path = "../mrubyedge", default-features = false } +mrubyedge = ">= 1.1.0" +# mrubyedge = { version = "1.1.0", path = "../mrubyedge", default-features = false } serde = ">= 1.0.228" serde_json = ">= 1.0.149" [dev-dependencies] -mrubyedge = { version = "1.1.0", path = "../mrubyedge", features = ["default"] } +mrubyedge = { version = "1.1.0", features = ["default"] } mec-mrbc-sys = "3.3.1" From 98e3da219a6bad7967e397e375381d5cfd237b50 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 5 Feb 2026 23:50:43 +0900 Subject: [PATCH 201/314] Add description and author to Cargo.toml --- mruby-serde-json/Cargo.toml | 3 +++ mruby-serde-json/tests/load.rs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/mruby-serde-json/Cargo.toml b/mruby-serde-json/Cargo.toml index 6c44aff..47cb138 100644 --- a/mruby-serde-json/Cargo.toml +++ b/mruby-serde-json/Cargo.toml @@ -2,6 +2,9 @@ name = "mruby-serde-json" version = "0.1.0" edition = "2024" +authors = ["Uchio Kondo "] +description = "mruby-serde-json provides JSON serialization/deserialization for mruby/edge using serde_json" +license = "BSD-3-Clause" [dependencies] mrubyedge = ">= 1.1.0" diff --git a/mruby-serde-json/tests/load.rs b/mruby-serde-json/tests/load.rs index b0237ab..16fbaca 100644 --- a/mruby-serde-json/tests/load.rs +++ b/mruby-serde-json/tests/load.rs @@ -193,7 +193,7 @@ fn test_json_load_nil() { #[test] fn test_json_load_float() { let code = r#" - JSON.load("3.14") + JSON.load("3.1415") "#; let binary = mrbc_compile("json_load_float", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); @@ -202,7 +202,7 @@ fn test_json_load_float() { let result = vm.run().unwrap(); let value: f64 = result.as_ref().try_into().unwrap(); - assert_eq!(value, 3.14); + assert_eq!(value, 3.1415); } #[test] From 316b4b69d7bd6894c2b0198af20f2ac5425b8f39 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 5 Feb 2026 23:51:21 +0900 Subject: [PATCH 202/314] Add to readme --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 1437ad6..339bc38 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,9 @@ mruby/edge is yet another mruby-compatible VM implementation, specialized for We ### [mrubyedge-cli](./mrubyedge-cli) [![crates.io](https://img.shields.io/crates/v/mrubyedge-cli.svg)](https://crates.io/crates/mrubyedge-cli) [![docs.rs](https://docs.rs/mrubyedge-cli/badge.svg)](https://docs.rs/mrubyedge-cli) * CLI endpoint for mruby/edge - run, compile to wasm, etc. +### [mruby-serde-json](./mruby-serde-json) [![crates.io](https://img.shields.io/crates/v/mruby-serde-json.svg)](https://crates.io/crates/mruby-serde-json) [![docs.rs](https://docs.rs/mruby-serde-json/badge.svg)](https://docs.rs/mruby-serde-json) +* JSON serialization/deserialization for mruby/edge using serde_json + ### mec [deprecated] * Old versions of compiler From b3c64f9a01123c99a156998118b95c65f8988011 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 5 Feb 2026 23:55:34 +0900 Subject: [PATCH 203/314] Add --- mruby-serde-json/README.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 mruby-serde-json/README.md diff --git a/mruby-serde-json/README.md b/mruby-serde-json/README.md new file mode 100644 index 0000000..d52ec32 --- /dev/null +++ b/mruby-serde-json/README.md @@ -0,0 +1,9 @@ +# mruby-serde-json + +mruby-serde-json provides JSON serialization/deserialization for mruby/edge using serde_json. + + ## Features + +This gem implements `JSON.load` and `JSON.dump` methods for mruby/edge. + +Natural serialization/deserialization is supported for basic classes (Integer, Float, String, Array, Hash, TrueClass, FalseClass, NilClass). For classes with `to_json` implementation, the gem uses that method for serialization (Otherwise panics). From 74d3fb0c7c06e6b6697f428eb86bb9aa118c2999 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sat, 7 Feb 2026 10:28:59 +0900 Subject: [PATCH 204/314] Skip some pi check --- mruby-serde-json/tests/load.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/mruby-serde-json/tests/load.rs b/mruby-serde-json/tests/load.rs index 16fbaca..ae3f47d 100644 --- a/mruby-serde-json/tests/load.rs +++ b/mruby-serde-json/tests/load.rs @@ -1,3 +1,4 @@ +#![allow(clippy::approx_constant)] extern crate mruby_serde_json; extern crate mrubyedge; From ebe6bfaab94680254100d3a105e70750cdc2d939 Mon Sep 17 00:00:00 2001 From: Kondo Uchio Date: Tue, 10 Feb 2026 00:40:00 +0900 Subject: [PATCH 205/314] Add Playground link to README Added a link to the Playground for feature demonstration. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 339bc38..31dab4e 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ mruby/edge is yet another mruby-compatible VM implementation, specialized for WebAssembly. +You can try what features mruby/edge have implemented in [Playground](https://mrubyedge.github.io/playground/). + ## badges - ![crates.io](https://img.shields.io/crates/v/mrubyedge.svg) ![crates.io](https://img.shields.io/crates/v/mrubyedge-cli.svg) From 23ec058e87c3bf54bc1f2113b430775deb9888b0 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 10 Feb 2026 11:09:16 +0900 Subject: [PATCH 206/314] Add hanoi example --- mrubyedge/examples/hanoi.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 mrubyedge/examples/hanoi.rb diff --git a/mrubyedge/examples/hanoi.rb b/mrubyedge/examples/hanoi.rb new file mode 100644 index 0000000..0311501 --- /dev/null +++ b/mrubyedge/examples/hanoi.rb @@ -0,0 +1,21 @@ +# Tower of Hanoi +# Move disks from source to destination using auxiliary peg + +def hanoi(n, source, destination, auxiliary, step = [0]) + if n == 1 + step[0] += 1 + puts "Step #{step[0]}: Move disk 1 from #{source} to #{destination}" + return + end + + hanoi(n - 1, source, auxiliary, destination, step) + step[0] += 1 + puts "Step #{step[0]}: Move disk #{n} from #{source} to #{destination}" + hanoi(n - 1, auxiliary, destination, source, step) +end + +puts "Tower of Hanoi with 3 disks:" +puts "----------------------------" +hanoi(3, 'A', 'C', 'B') +puts "----------------------------" +puts "Complete!" \ No newline at end of file From 8bd0528f6c53cc7067cde4a1eca2b9425a3110e3 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 10 Feb 2026 11:41:44 +0900 Subject: [PATCH 207/314] Fix array element index assignment --- mrubyedge/examples/default-arg.rb | 10 ++++++++++ mrubyedge/examples/hanoi.rb | 4 ++-- mrubyedge/src/yamrb/optable.rs | 6 +++--- mrubyedge/src/yamrb/prelude/array.rs | 6 +++++- 4 files changed, 20 insertions(+), 6 deletions(-) create mode 100644 mrubyedge/examples/default-arg.rb diff --git a/mrubyedge/examples/default-arg.rb b/mrubyedge/examples/default-arg.rb new file mode 100644 index 0000000..85a28d6 --- /dev/null +++ b/mrubyedge/examples/default-arg.rb @@ -0,0 +1,10 @@ +def incr(times, state) + return if times == 0 + p "state: #{state.inspect}" + state[0] += 1 + p "state: #{state.inspect}" + incr(times - 1, state) + state +end + +p incr(1, [0]) \ No newline at end of file diff --git a/mrubyedge/examples/hanoi.rb b/mrubyedge/examples/hanoi.rb index 0311501..cbfad41 100644 --- a/mrubyedge/examples/hanoi.rb +++ b/mrubyedge/examples/hanoi.rb @@ -1,7 +1,7 @@ # Tower of Hanoi # Move disks from source to destination using auxiliary peg -def hanoi(n, source, destination, auxiliary, step = [0]) +def hanoi(n, source, destination, auxiliary, step) if n == 1 step[0] += 1 puts "Step #{step[0]}: Move disk 1 from #{source} to #{destination}" @@ -16,6 +16,6 @@ def hanoi(n, source, destination, auxiliary, step = [0]) puts "Tower of Hanoi with 3 disks:" puts "----------------------------" -hanoi(3, 'A', 'C', 'B') +hanoi(3, 'A', 'C', 'B', [0]) puts "----------------------------" puts "Complete!" \ No newline at end of file diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index f2bc047..8e95fd9 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1438,7 +1438,7 @@ pub(crate) fn op_addi(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { unreachable!("addi supports only integer") } }; - vm.current_regs()[a as usize].replace(Rc::new(result)); + vm.current_regs()[a as usize].replace(result.to_refcount_assigned()); Ok(()) } @@ -1453,7 +1453,7 @@ pub(crate) fn op_sub(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { unreachable!("sub supports only integer") } }; - vm.current_regs()[a].replace(Rc::new(result)); + vm.current_regs()[a].replace(result.to_refcount_assigned()); Ok(()) } @@ -1467,7 +1467,7 @@ pub(crate) fn op_subi(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { unreachable!("subi supports only integer") } }; - vm.current_regs()[a as usize].replace(Rc::new(result)); + vm.current_regs()[a as usize].replace(result.to_refcount_assigned()); Ok(()) } diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index 34a3b67..d667fcd 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -206,7 +206,11 @@ pub fn mrb_array_set_index(this: Rc, args: &[Rc]) -> Result { let mut a = a.borrow_mut(); - a.insert(index, value.clone()); + if a.len() <= index { + a.insert(index, value.clone()); + } else { + a[index] = value.clone(); + } } _ => { return Err(Error::RuntimeError( From b27f2e10ab1c8d79681715c08653452f62effcb8 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 10 Feb 2026 13:07:44 +0900 Subject: [PATCH 208/314] tests --- mrubyedge/tests/array.rs | 68 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/mrubyedge/tests/array.rs b/mrubyedge/tests/array.rs index 61d2f22..33ed9ad 100644 --- a/mrubyedge/tests/array.rs +++ b/mrubyedge/tests/array.rs @@ -323,3 +323,71 @@ fn array_join_test() { let result: String = result.as_ref().try_into().unwrap(); assert_eq!(result, "1,2,3"); } + +#[test] +fn array_reference_mutation_test() { + let code = r#" + def incr(times, state) + return state if times == 0 + state[0] += 1 + incr(times - 1, state) + end + + def test_array_reference + arr = [0] + incr(3, arr) + arr[0] + end + "#; + let binary = mrbc_compile("array_reference_mutation", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_reference", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 3); +} + +#[test] +fn array_reference_mutation_recursive_test() { + let code = r#" + def incr_recursive(times, state, results) + return results if times == 0 + state[0] += 1 + results << state[0] + incr_recursive(times - 1, state, results) + end + + def test_recursive_mutation + arr = [0] + results = [] + incr_recursive(5, arr, results) + [arr[0], results] + end + "#; + let binary = mrbc_compile("array_reference_recursive", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_recursive_mutation", &args).unwrap(); + let outer: Vec> = + result.as_ref().try_into().unwrap(); + + // arr[0] should be 5 + let final_count: i64 = outer[0].as_ref().try_into().unwrap(); + assert_eq!(final_count, 5); + + // results should be [1, 2, 3, 4, 5] + let results: Vec> = + outer[1].as_ref().try_into().unwrap(); + assert_eq!(results.len(), 5); + + for (i, item) in results.iter().enumerate() { + let val: i64 = item.as_ref().try_into().unwrap(); + assert_eq!(val, (i + 1) as i64); + } +} From ba513a25d3a1bbf785ce20215112768bb44fb327 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 10 Feb 2026 22:05:26 +0900 Subject: [PATCH 209/314] Handle optional arg flag in op_enter --- mrubyedge/examples/default-arg.rb | 7 +++---- mrubyedge/src/yamrb/optable.rs | 26 +++++++++++++++++++++----- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/mrubyedge/examples/default-arg.rb b/mrubyedge/examples/default-arg.rb index 85a28d6..9e64927 100644 --- a/mrubyedge/examples/default-arg.rb +++ b/mrubyedge/examples/default-arg.rb @@ -1,10 +1,9 @@ -def incr(times, state) +def incr(times, state=[0]) return if times == 0 - p "state: #{state.inspect}" + p "state: #{state.inspect} #{state.object_id}" state[0] += 1 - p "state: #{state.inspect}" incr(times - 1, state) state end -p incr(1, [0]) \ No newline at end of file +p incr(3) \ No newline at end of file diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 8e95fd9..1a13f5e 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1201,6 +1201,7 @@ impl From for EnterArgInfo { pub(crate) fn op_enter(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_w()?; + let argc = vm.current_callinfo.as_ref().map_or(0, |ci| ci.n_args); let arg_info = EnterArgInfo::from(a); let m1_argc = arg_info.m1 as usize; for i in 0..m1_argc { @@ -1214,9 +1215,24 @@ pub(crate) fn op_enter(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { } } } + let optional_arg = arg_info.o as usize; + if optional_arg > 0 { + let m2_argc = arg_info.m2 as usize; + let total_preset_args = argc.saturating_sub(m1_argc + m2_argc); + for peek_pc in 0..total_preset_args { + match vm.current_irep.code[vm.pc.get() + peek_pc].code { + OpCode::JMP => {} + _ => { + unreachable!("unexpected opcode while processing optional args") + } + } + } + vm.pc.set(vm.pc.get() + total_preset_args); + } + let splat_arg = arg_info.r as usize; if splat_arg == 1 { - let total_args = vm.current_callinfo.as_ref().map_or(0, |ci| ci.n_args); + let total_args = argc; let passed_args = total_args.saturating_sub(m1_argc); let mut array = Vec::new(); for i in 0..passed_args { @@ -1603,7 +1619,7 @@ fn do_op_array(vm: &mut VM, this: usize, start: usize, n: usize) -> Result<(), E } } let val = RObject::array(ary); - vm.current_regs()[this].replace(Rc::new(val)); + vm.current_regs()[this].replace(val.to_refcount_assigned()); Ok(()) } @@ -1627,7 +1643,7 @@ pub(crate) fn op_arycat(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { ary1.push(item.clone()); } let val = RObject::array(ary1); - vm.current_regs()[a].replace(Rc::new(val)); + vm.current_regs()[a].replace(val.to_refcount_assigned()); } _ => { unreachable!("arycat supports only array") @@ -1687,7 +1703,7 @@ pub(crate) fn op_symbol(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let symstr = vm.current_irep.pool[b as usize].as_str().to_string(); let sym = RSym::new(symstr); let val = RObject::symbol(sym); - vm.current_regs()[a as usize].replace(Rc::new(val)); + vm.current_regs()[a as usize].replace(val.to_refcount_assigned()); Ok(()) } @@ -1695,7 +1711,7 @@ pub(crate) fn op_string(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let (a, b) = operand.as_bb()?; let str = vm.current_irep.pool[b as usize].as_str().to_string(); let val = RObject::string(str); - vm.current_regs()[a as usize].replace(Rc::new(val)); + vm.current_regs()[a as usize].replace(val.to_refcount_assigned()); Ok(()) } From c4d784c8770dc01c89a3d08d41d550ad5809ae86 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 10 Feb 2026 22:14:07 +0900 Subject: [PATCH 210/314] Add tests for optional arguments in mrubyedge --- mrubyedge/tests/optional_args.rs | 132 +++++++++++++++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 mrubyedge/tests/optional_args.rs diff --git a/mrubyedge/tests/optional_args.rs b/mrubyedge/tests/optional_args.rs new file mode 100644 index 0000000..63cf78d --- /dev/null +++ b/mrubyedge/tests/optional_args.rs @@ -0,0 +1,132 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn default_arg_array_reused() { + let code = r#" +def incr(times, state=[0]) + return state if times == 0 + state[0] += 1 + incr(times - 1, state) +end + +result = incr(3) +result[0] + "#; + let binary = mrbc_compile("default_arg_array_reused", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 3); +} + +#[test] +fn default_arg_simple() { + let code = r##" +def greet(name, greeting="Hello") + "#{greeting}, #{name}!" +end + +greet("Alice") + "##; + let binary = mrbc_compile("default_arg_simple", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(result_str, "Hello, Alice!"); +} + +#[test] +fn default_arg_multiple() { + let code = r#" +def create_point(x=0, y=3, z=6) + [x, y, z] +end + +result = create_point() +result[0] + result[1] + result[2] + "#; + let binary = mrbc_compile("default_arg_multiple", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 9); +} + +#[test] +fn default_arg_override() { + let code = r#" +def add(a, b=10) + a + b +end + +add(5, 20) + "#; + let binary = mrbc_compile("default_arg_override", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 25); +} + +#[test] +fn default_arg_partial_override() { + let code = r#" +def calc(a, b=2, c=3) + a * b + c +end + +calc(5, 4) + "#; + let binary = mrbc_compile("default_arg_partial_override", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 23); +} + +#[test] +fn default_arg_hash_reused() { + let code = r#" +def update_counter(times, state={count: 0, other: 2}) + return state if times == 0 + state[:count] += 1 + update_counter(times - 1, state) +end + +result = update_counter(5) +result[:count] + result[:other] + "#; + let binary = mrbc_compile("default_arg_hash_reused", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 5 + 2); +} + +#[test] +fn default_arg_mixed_types() { + let code = r##" +def format_message(msg, prefix="Info", level=1, enabled=true) + return "disabled" unless enabled + "[#{prefix}:#{level}] #{msg}" +end + +format_message("Test") + "##; + let binary = mrbc_compile("default_arg_mixed_types", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let result_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(result_str, "[Info:1] Test"); +} From d191f268aa71bee6792687aa6f7450d339a0f099 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 10 Feb 2026 22:19:03 +0900 Subject: [PATCH 211/314] Bump version to 1.1.1 --- Cargo.lock | 20 ++++++++++---------- mrubyedge/Cargo.toml | 2 +- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 024e5d4..82879e7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -563,7 +563,7 @@ name = "mruby-serde-json" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.0", "serde", "serde_json", ] @@ -571,12 +571,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb58245ddb9f38ad7d3426a244b823567fb6a00e5a82b49eaa42e40ab66b652c" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -586,10 +583,13 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb58245ddb9f38ad7d3426a244b823567fb6a00e5a82b49eaa42e40ab66b652c" +version = "1.1.1" dependencies = [ + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -604,7 +604,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.0", "nom", "rand", "syn", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 2b33ac9..cc49ffe 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.1.0" +version = "1.1.1" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From 03dd16624d053d4d32e6f9f11435b908399cbab9 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 10 Feb 2026 22:21:08 +0900 Subject: [PATCH 212/314] Update cli --- Cargo.lock | 66 ++++++++++++++++++++-------------------- mrubyedge-cli/Cargo.toml | 4 +-- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 82879e7..fb25701 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -249,9 +249,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.56" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75ca66430e33a14957acc24c5077b503e7d374151b2b4b3a10c83b4ceb4be0e" +checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" dependencies = [ "clap_builder", "clap_derive", @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.56" +version = "4.5.57" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793207c7fa6300a0608d1080b858e5fdbe713cdc1c8db9fb17777d8a13e63df0" +checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" dependencies = [ "anstream", "anstyle", @@ -481,9 +481,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.180" +version = "0.2.181" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc" +checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" [[package]] name = "libloading" @@ -520,9 +520,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.6" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "mime" @@ -563,17 +563,20 @@ name = "mruby-serde-json" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.0", + "mrubyedge 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "serde_json", ] [[package]] name = "mrubyedge" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb58245ddb9f38ad7d3426a244b823567fb6a00e5a82b49eaa42e40ab66b652c" +version = "1.1.1" dependencies = [ + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -584,12 +587,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9806ae81ff6ebcb666faba199ab68a1b21873aa7f1a8a08d432d79198ff5658" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -599,12 +599,12 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.1.0" +version = "1.1.1" dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.1.0", + "mrubyedge 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", @@ -796,9 +796,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.12.2" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", @@ -808,9 +808,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -819,9 +819,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.8" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58" +checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" [[package]] name = "rustc-hash" @@ -934,9 +934,9 @@ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" +checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" [[package]] name = "utf8parse" @@ -1050,18 +1050,18 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "zerocopy" -version = "0.8.37" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7456cf00f0685ad319c5b1693f291a650eaf345e941d082fc4e03df8a03996ac" +checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.37" +version = "0.8.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1328722bbf2115db7e19d69ebcc15e795719e2d66b60827c6a69a117365e37a0" +checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" dependencies = [ "proc-macro2", "quote", @@ -1070,6 +1070,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.19" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff05f8caa9038894637571ae6b9e29466c1f4f829d26c9b28f869a29cbe3445" +checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index d2e3859..6b03ada 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.1.0" +version = "1.1.1" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } mruby-compiler2-sys = "0.2.2" -mrubyedge = { version = "1.1.0", features = [ +mrubyedge = { version = "1.1.1", features = [ "default", "mruby-random", "mruby-regexp", From 2c7a75cddbcf7b74295e6fa4993909a97a327a13 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 10 Feb 2026 22:31:43 +0900 Subject: [PATCH 213/314] Sine example w/ math --- mrubyedge/examples/sin_curve.rb | 43 +++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 mrubyedge/examples/sin_curve.rb diff --git a/mrubyedge/examples/sin_curve.rb b/mrubyedge/examples/sin_curve.rb new file mode 100644 index 0000000..70f609a --- /dev/null +++ b/mrubyedge/examples/sin_curve.rb @@ -0,0 +1,43 @@ +# Sin curve ASCII art +# Draw sine wave from 0 to 4π + +PI = Math::PI + +# Settings +width = 60 # Width of the graph +height = 15 # Height of the graph (centered) +x_range = 4 * PI # 0 to 4π +steps = 80 # Number of points to plot + +puts "Sin(x) curve from 0 to 4π" +puts "=" * width +puts "" + +# Draw the curve +(0..steps).each do |i| + x = (x_range * i) / steps + y = Math.sin(x) + + # Map y (-1 to 1) to column position (0 to width-1) + col = ((y + 1) * (width - 1) / 2).to_i + + # Print spaces then the marker + line = " " * col + "*" + + # Add x-axis marker at y=0 + if y.abs < 0.1 + line = line.sub("*", "|") + end + + # Show x value at specific points + if i % 20 == 0 + x_label = (x / PI * 10).to_i / 10.0 + puts line + " (x=#{x_label}π)" + else + puts line + end +end + +puts "" +puts "=" * width +puts "Legend: * = sin(x), | = x-axis (y≈0)" From 5630f7307467a5c4c8ed2ad291f859b8b36cd7b3 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 10 Feb 2026 22:32:41 +0900 Subject: [PATCH 214/314] Initial commit mruby-math --- Cargo.lock | 4 ++++ Cargo.toml | 2 +- mruby-math/Cargo.toml | 6 ++++++ mruby-math/src/lib.rs | 14 ++++++++++++++ 4 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 mruby-math/Cargo.toml create mode 100644 mruby-math/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index fb25701..e07de1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -558,6 +558,10 @@ dependencies = [ "libc", ] +[[package]] +name = "mruby-math" +version = "0.1.0" + [[package]] name = "mruby-serde-json" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 7239b09..87c41c8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,4 @@ [workspace] resolver = "2" -members = ["mruby-serde-json", "mrubyedge", "mrubyedge-cli"] +members = ["mruby-math","mruby-serde-json", "mrubyedge", "mrubyedge-cli"] diff --git a/mruby-math/Cargo.toml b/mruby-math/Cargo.toml new file mode 100644 index 0000000..80cce58 --- /dev/null +++ b/mruby-math/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "mruby-math" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/mruby-math/src/lib.rs b/mruby-math/src/lib.rs new file mode 100644 index 0000000..b93cf3f --- /dev/null +++ b/mruby-math/src/lib.rs @@ -0,0 +1,14 @@ +pub fn add(left: u64, right: u64) -> u64 { + left + right +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn it_works() { + let result = add(2, 2); + assert_eq!(result, 4); + } +} From 5e4b8943b8a12b6b65294d120d352ae7ecaefdd0 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 00:01:27 +0900 Subject: [PATCH 215/314] WIP mruby-math; fixes in mrubyedge core Float --- Cargo.lock | 4 + Cargo.toml | 2 +- mruby-math/Cargo.toml | 8 + mruby-math/README.md | 89 +++++++ mruby-math/src/lib.rs | 318 ++++++++++++++++++++++- mruby-math/tests/helpers/mod.rs | 34 +++ mruby-math/tests/math_functions.rs | 155 +++++++++++ mruby-math/tests/simple_math.rs | 50 ++++ mrubyedge/src/error.rs | 4 + mrubyedge/src/yamrb/optable.rs | 16 +- mrubyedge/src/yamrb/prelude/exception.rs | 1 + mrubyedge/src/yamrb/prelude/float.rs | 126 ++++++++- mrubyedge/src/yamrb/value.rs | 1 + mrubyedge/tests/float_ops.rs | 145 +++++++++++ 14 files changed, 940 insertions(+), 13 deletions(-) create mode 100644 mruby-math/README.md create mode 100644 mruby-math/tests/helpers/mod.rs create mode 100644 mruby-math/tests/math_functions.rs create mode 100644 mruby-math/tests/simple_math.rs create mode 100644 mrubyedge/tests/float_ops.rs diff --git a/Cargo.lock b/Cargo.lock index e07de1b..5abe987 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -561,6 +561,10 @@ dependencies = [ [[package]] name = "mruby-math" version = "0.1.0" +dependencies = [ + "mec-mrbc-sys", + "mrubyedge 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] [[package]] name = "mruby-serde-json" diff --git a/Cargo.toml b/Cargo.toml index 87c41c8..f4e4c99 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,4 @@ [workspace] resolver = "2" -members = ["mruby-math","mruby-serde-json", "mrubyedge", "mrubyedge-cli"] +members = ["mruby-math", "mruby-serde-json", "mrubyedge", "mrubyedge-cli"] diff --git a/mruby-math/Cargo.toml b/mruby-math/Cargo.toml index 80cce58..44c0e3c 100644 --- a/mruby-math/Cargo.toml +++ b/mruby-math/Cargo.toml @@ -2,5 +2,13 @@ name = "mruby-math" version = "0.1.0" edition = "2024" +authors = ["Uchio Kondo "] +description = "mruby-math provides Math module for mruby/edge" +license = "BSD-3-Clause" [dependencies] +mrubyedge = "1.1.1" + +[dev-dependencies] +mrubyedge = { version = "1.1.1", features = ["default"] } +mec-mrbc-sys = "3.3.1" diff --git a/mruby-math/README.md b/mruby-math/README.md new file mode 100644 index 0000000..fd02183 --- /dev/null +++ b/mruby-math/README.md @@ -0,0 +1,89 @@ +# mruby-math + +A Rust implementation of the Math module for mruby/edge. + +## Features + +This crate provides mathematical functions and constants for mruby/edge: + +### Constants + +- `Math::PI` - π (pi) +- `Math::E` - e (Euler's number) + +### Trigonometric Functions + +- `Math.sin(x)` - Sine +- `Math.cos(x)` - Cosine +- `Math.tan(x)` - Tangent +- `Math.asin(x)` - Arcsine +- `Math.acos(x)` - Arccosine +- `Math.atan(x)` - Arctangent +- `Math.atan2(y, x)` - Arctangent of y/x + +### Hyperbolic Functions + +- `Math.sinh(x)` - Hyperbolic sine +- `Math.cosh(x)` - Hyperbolic cosine +- `Math.tanh(x)` - Hyperbolic tangent +- `Math.asinh(x)` - Inverse hyperbolic sine +- `Math.acosh(x)` - Inverse hyperbolic cosine +- `Math.atanh(x)` - Inverse hyperbolic tangent + +### Exponential and Logarithmic Functions + +- `Math.exp(x)` - e^x +- `Math.log(x)` - Natural logarithm (ln) +- `Math.log(x, base)` - Logarithm with custom base +- `Math.log10(x)` - Base-10 logarithm +- `Math.log2(x)` - Base-2 logarithm + +### Root Functions + +- `Math.sqrt(x)` - Square root +- `Math.cbrt(x)` - Cube root + +### Other Functions + +- `Math.hypot(x, y)` - Hypotenuse (√(x² + y²)) +- `Math.ldexp(fraction, exponent)` - fraction × 2^exponent +- `Math.erf(x)` - Error function +- `Math.erfc(x)` - Complementary error function + +## Usage + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +mruby-math = "0.1.0" +mrubyedge = "1.1.1" +``` + +Initialize the Math module in your VM: + +```rust +use mruby_math::init_math; +use mrubyedge::yamrb::vm::VM; + +let mut vm = VM::open(&mut rite); +init_math(&mut vm); +``` + +## Example + +```ruby +# Calculate sine wave +x = Math::PI / 4 +y = Math.sin(x) + +# Calculate distance +distance = Math.hypot(3, 4) # => 5.0 + +# Exponential growth +result = Math.exp(2) # => e^2 +``` + +## License + +BSD-3-Clause diff --git a/mruby-math/src/lib.rs b/mruby-math/src/lib.rs index b93cf3f..182293f 100644 --- a/mruby-math/src/lib.rs +++ b/mruby-math/src/lib.rs @@ -1,5 +1,313 @@ -pub fn add(left: u64, right: u64) -> u64 { - left + right +use std::rc::Rc; + +use mrubyedge::{ + Error, + yamrb::{ + helpers::mrb_define_singleton_cmethod, + value::{RObject, RValue}, + vm::VM, + }, +}; + +pub fn init_math(vm: &mut VM) { + let math_module = vm.define_module("Math", None); + + // Define constants + math_module.consts.borrow_mut().insert( + "PI".to_string(), + RObject::float(std::f64::consts::PI).to_refcount_assigned(), + ); + math_module.consts.borrow_mut().insert( + "E".to_string(), + RObject::float(std::f64::consts::E).to_refcount_assigned(), + ); + + // Get the module object to define singleton methods (module methods) + let math_module_obj = vm.get_const_by_name("Math").expect("Math module not found"); + + // Trigonometric functions + mrb_define_singleton_cmethod(vm, math_module_obj.clone(), "sin", Box::new(mrb_math_sin)); + mrb_define_singleton_cmethod(vm, math_module_obj.clone(), "cos", Box::new(mrb_math_cos)); + mrb_define_singleton_cmethod(vm, math_module_obj.clone(), "tan", Box::new(mrb_math_tan)); + + // Inverse trigonometric functions + mrb_define_singleton_cmethod(vm, math_module_obj.clone(), "asin", Box::new(mrb_math_asin)); + mrb_define_singleton_cmethod(vm, math_module_obj.clone(), "acos", Box::new(mrb_math_acos)); + mrb_define_singleton_cmethod(vm, math_module_obj.clone(), "atan", Box::new(mrb_math_atan)); + mrb_define_singleton_cmethod( + vm, + math_module_obj.clone(), + "atan2", + Box::new(mrb_math_atan2), + ); + + // Hyperbolic functions + mrb_define_singleton_cmethod(vm, math_module_obj.clone(), "sinh", Box::new(mrb_math_sinh)); + mrb_define_singleton_cmethod(vm, math_module_obj.clone(), "cosh", Box::new(mrb_math_cosh)); + mrb_define_singleton_cmethod(vm, math_module_obj.clone(), "tanh", Box::new(mrb_math_tanh)); + + // Inverse hyperbolic functions + mrb_define_singleton_cmethod( + vm, + math_module_obj.clone(), + "asinh", + Box::new(mrb_math_asinh), + ); + mrb_define_singleton_cmethod( + vm, + math_module_obj.clone(), + "acosh", + Box::new(mrb_math_acosh), + ); + mrb_define_singleton_cmethod( + vm, + math_module_obj.clone(), + "atanh", + Box::new(mrb_math_atanh), + ); + + // Exponential and logarithmic functions + mrb_define_singleton_cmethod(vm, math_module_obj.clone(), "exp", Box::new(mrb_math_exp)); + mrb_define_singleton_cmethod(vm, math_module_obj.clone(), "log", Box::new(mrb_math_log)); + mrb_define_singleton_cmethod( + vm, + math_module_obj.clone(), + "log10", + Box::new(mrb_math_log10), + ); + mrb_define_singleton_cmethod(vm, math_module_obj.clone(), "log2", Box::new(mrb_math_log2)); + + // Root functions + mrb_define_singleton_cmethod(vm, math_module_obj.clone(), "sqrt", Box::new(mrb_math_sqrt)); + mrb_define_singleton_cmethod(vm, math_module_obj.clone(), "cbrt", Box::new(mrb_math_cbrt)); + + // Other mathematical functions + mrb_define_singleton_cmethod( + vm, + math_module_obj.clone(), + "hypot", + Box::new(mrb_math_hypot), + ); + mrb_define_singleton_cmethod( + vm, + math_module_obj.clone(), + "ldexp", + Box::new(mrb_math_ldexp), + ); + mrb_define_singleton_cmethod(vm, math_module_obj.clone(), "erf", Box::new(mrb_math_erf)); + mrb_define_singleton_cmethod(vm, math_module_obj.clone(), "erfc", Box::new(mrb_math_erfc)); +} + +// Helper function to get a float from RObject +fn get_float_arg(obj: &RObject) -> Result { + match &obj.value { + RValue::Integer(i) => Ok(*i as f64), + RValue::Float(f) => Ok(*f), + _ => Err(Error::internal("expected Numeric for Math function")), + } +} + +// Helper function to check argument count (excluding trailing nil) +fn check_args_count(args: &[Rc], expected: usize) -> Result>, Error> { + let args = if args.len() > 0 && args[args.len() - 1].is_nil() { + &args[0..args.len() - 1] + } else { + args + }; + + if args.len() != expected { + return Err(Error::ArgumentError(format!( + "wrong number of arguments (given {}, expected {})", + args.len(), + expected + ))); + } + + Ok(args.to_vec()) +} + +// Trigonometric functions +pub fn mrb_math_sin(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + Ok(RObject::float(x.sin()).to_refcount_assigned()) +} + +pub fn mrb_math_cos(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + Ok(RObject::float(x.cos()).to_refcount_assigned()) +} + +pub fn mrb_math_tan(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + Ok(RObject::float(x.tan()).to_refcount_assigned()) +} + +// Inverse trigonometric functions +pub fn mrb_math_asin(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + Ok(RObject::float(x.asin()).to_refcount_assigned()) +} + +pub fn mrb_math_acos(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + Ok(RObject::float(x.acos()).to_refcount_assigned()) +} + +pub fn mrb_math_atan(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + Ok(RObject::float(x.atan()).to_refcount_assigned()) +} + +pub fn mrb_math_atan2(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 2)?; + let y = get_float_arg(&args[0])?; + let x = get_float_arg(&args[1])?; + Ok(RObject::float(y.atan2(x)).to_refcount_assigned()) +} + +// Hyperbolic functions +pub fn mrb_math_sinh(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + Ok(RObject::float(x.sinh()).to_refcount_assigned()) +} + +pub fn mrb_math_cosh(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + Ok(RObject::float(x.cosh()).to_refcount_assigned()) +} + +pub fn mrb_math_tanh(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + Ok(RObject::float(x.tanh()).to_refcount_assigned()) +} + +// Inverse hyperbolic functions +pub fn mrb_math_asinh(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + Ok(RObject::float(x.asinh()).to_refcount_assigned()) +} + +pub fn mrb_math_acosh(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + Ok(RObject::float(x.acosh()).to_refcount_assigned()) +} + +pub fn mrb_math_atanh(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + Ok(RObject::float(x.atanh()).to_refcount_assigned()) +} + +// Exponential and logarithmic functions +pub fn mrb_math_exp(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + Ok(RObject::float(x.exp()).to_refcount_assigned()) +} + +pub fn mrb_math_log(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args_vec = if args.len() > 0 && args[args.len() - 1].is_nil() { + args[0..args.len() - 1].to_vec() + } else { + args.to_vec() + }; + + if args_vec.len() == 1 { + let x = get_float_arg(&args_vec[0])?; + Ok(RObject::float(x.ln()).to_refcount_assigned()) + } else if args_vec.len() == 2 { + let x = get_float_arg(&args_vec[0])?; + let base = get_float_arg(&args_vec[1])?; + Ok(RObject::float(x.log(base)).to_refcount_assigned()) + } else { + Err(Error::ArgumentError(format!( + "wrong number of arguments (given {}, expected 1..2)", + args_vec.len() + ))) + } +} + +pub fn mrb_math_log10(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + Ok(RObject::float(x.log10()).to_refcount_assigned()) +} + +pub fn mrb_math_log2(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + Ok(RObject::float(x.log2()).to_refcount_assigned()) +} + +// Root functions +pub fn mrb_math_sqrt(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + Ok(RObject::float(x.sqrt()).to_refcount_assigned()) +} + +pub fn mrb_math_cbrt(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + Ok(RObject::float(x.cbrt()).to_refcount_assigned()) +} + +// Other mathematical functions +pub fn mrb_math_hypot(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 2)?; + let x = get_float_arg(&args[0])?; + let y = get_float_arg(&args[1])?; + Ok(RObject::float(x.hypot(y)).to_refcount_assigned()) +} + +pub fn mrb_math_ldexp(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 2)?; + let fraction = get_float_arg(&args[0])?; + let exponent: i32 = args[1].as_ref().try_into()?; + Ok(RObject::float(fraction * 2f64.powi(exponent)).to_refcount_assigned()) +} + +pub fn mrb_math_erf(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + let result = erf_approximation(x); + Ok(RObject::float(result).to_refcount_assigned()) +} + +pub fn mrb_math_erfc(_vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = check_args_count(args, 1)?; + let x = get_float_arg(&args[0])?; + let result = 1.0 - erf_approximation(x); + Ok(RObject::float(result).to_refcount_assigned()) +} + +// Abramowitz and Stegun approximation of error function +fn erf_approximation(x: f64) -> f64 { + let a1 = 0.254829592; + let a2 = -0.284496736; + let a3 = 1.421413741; + let a4 = -1.453152027; + let a5 = 1.061405429; + let p = 0.3275911; + + let sign = if x < 0.0 { -1.0 } else { 1.0 }; + let x = x.abs(); + + let t = 1.0 / (1.0 + p * x); + let y = 1.0 - (((((a5 * t + a4) * t) + a3) * t + a2) * t + a1) * t * (-x * x).exp(); + + sign * y } #[cfg(test)] @@ -7,8 +315,8 @@ mod tests { use super::*; #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + fn test_smoke_sin() { + let x = std::f64::consts::PI / 2.0; + assert!((x.sin() - 1.0).abs() < 1e-10); } } diff --git a/mruby-math/tests/helpers/mod.rs b/mruby-math/tests/helpers/mod.rs new file mode 100644 index 0000000..41f22e4 --- /dev/null +++ b/mruby-math/tests/helpers/mod.rs @@ -0,0 +1,34 @@ +#![allow(dead_code)] + +use std::{ffi::CStr, fs::File, io::Write}; + +pub fn mrbc_compile(fname: &'static str, code: &'static str) -> Vec { + let mut src = std::env::temp_dir(); + src.push(format!("{}.{}.rb", fname, std::process::id())); + let mut f = File::create(&src).expect("cannot open src file"); + f.write_all(code.as_bytes()) + .expect("cannot create src file"); + f.flush().unwrap(); + + let mut src0 = src.as_os_str().to_string_lossy().into_owned(); + src0.push('\0'); + + let mut dest = std::env::temp_dir(); + dest.push(format!("{}.{}.mrb", fname, std::process::id())); + let mut dest0 = dest.as_os_str().to_string_lossy().into_owned(); + dest0.push('\0'); + + let args = [ + CStr::from_bytes_with_nul(b"mrbc\0").unwrap().as_ptr(), + CStr::from_bytes_with_nul(b"-o\0").unwrap().as_ptr(), + CStr::from_bytes_with_nul(dest0.as_bytes()) + .unwrap() + .as_ptr(), + CStr::from_bytes_with_nul(src0.as_bytes()).unwrap().as_ptr(), + ]; + unsafe { + mec_mrbc_sys::mrbc_main(args.len() as i32, args.as_ptr() as *mut *mut i8); + } + + std::fs::read(dest).unwrap() +} diff --git a/mruby-math/tests/math_functions.rs b/mruby-math/tests/math_functions.rs new file mode 100644 index 0000000..99ed22a --- /dev/null +++ b/mruby-math/tests/math_functions.rs @@ -0,0 +1,155 @@ +extern crate mruby_math; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn test_math_sin() { + let code = " + Math.sin(Math::PI / 2) + "; + let binary = mrbc_compile("math_sin", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_math::init_math(&mut vm); + + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert!((value - 1.0).abs() < 1e-10); +} + +#[test] +fn test_math_cos() { + let code = " + Math.cos(Math::PI) + "; + let binary = mrbc_compile("math_cos", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_math::init_math(&mut vm); + + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert!((value + 1.0).abs() < 1e-10); +} + +#[test] +fn test_math_sqrt() { + let code = " + Math.sqrt(16) + "; + let binary = mrbc_compile("math_sqrt", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_math::init_math(&mut vm); + + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(value, 4.0); +} + +#[test] +fn test_math_log() { + let code = " + Math.log(Math::E) + "; + let binary = mrbc_compile("math_log", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_math::init_math(&mut vm); + + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert!((value - 1.0).abs() < 1e-10); +} + +#[test] +fn test_math_log_with_base() { + let code = " + Math.log(8, 2) + "; + let binary = mrbc_compile("math_log_base", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_math::init_math(&mut vm); + + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert!((value - 3.0).abs() < 1e-10); +} + +#[test] +fn test_math_pi() { + let code = " + Math::PI + "; + let binary = mrbc_compile("math_pi", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_math::init_math(&mut vm); + + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert!((value - std::f64::consts::PI).abs() < 1e-10); +} + +#[test] +fn test_math_e() { + let code = " + Math::E + "; + let binary = mrbc_compile("math_e", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_math::init_math(&mut vm); + + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert!((value - std::f64::consts::E).abs() < 1e-10); +} + +#[test] +fn test_math_atan2() { + let code = " + Math.atan2(1, 1) + "; + let binary = mrbc_compile("math_atan2", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_math::init_math(&mut vm); + + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert!((value - std::f64::consts::PI / 4.0).abs() < 1e-10); +} + +#[test] +fn test_math_hypot() { + let code = " + Math.hypot(3, 4) + "; + let binary = mrbc_compile("math_hypot", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_math::init_math(&mut vm); + + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert!((value - 5.0).abs() < 1e-10); +} + +#[test] +fn test_math_exp() { + let code = " + Math.exp(1) + "; + let binary = mrbc_compile("math_exp", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_math::init_math(&mut vm); + + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert!((value - std::f64::consts::E).abs() < 1e-10); +} diff --git a/mruby-math/tests/simple_math.rs b/mruby-math/tests/simple_math.rs new file mode 100644 index 0000000..d47ba29 --- /dev/null +++ b/mruby-math/tests/simple_math.rs @@ -0,0 +1,50 @@ +extern crate mruby_math; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn test_math_pi_constant() { + let code = " + Math::PI + "; + let binary = mrbc_compile("math_pi_const", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_math::init_math(&mut vm); + + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert!((value - std::f64::consts::PI).abs() < 1e-10); +} + +#[test] +fn test_math_sqrt_simple() { + let code = " + Math.sqrt(4.0) + "; + let binary = mrbc_compile("math_sqrt_simple", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_math::init_math(&mut vm); + + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(value, 2.0); +} + +#[test] +fn test_math_sin_simple() { + let code = " + Math.sin(Math::PI / 2) + "; + let binary = mrbc_compile("math_sin_simple", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_math::init_math(&mut vm); + + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert!((value - 1.0).abs() < 1e-10); +} diff --git a/mrubyedge/src/error.rs b/mrubyedge/src/error.rs index d440790..73f74d0 100644 --- a/mrubyedge/src/error.rs +++ b/mrubyedge/src/error.rs @@ -17,6 +17,7 @@ pub enum Error { TypeMismatch, NoMethodError(String), NameError(String), + ZeroDivisionError, Break(Rc), BlockReturn(usize, Rc), @@ -46,6 +47,7 @@ impl Error { Error::TypeMismatch => "Type mismatch".to_string(), Error::NoMethodError(msg) => format!("Method not found: {}", msg), Error::NameError(msg) => format!("Cannot found name: {}", msg), + Error::ZeroDivisionError => "divided by 0".to_string(), Error::Break(_) => "[Break]".to_string(), Error::BlockReturn(_, _) => "[BlockReturn]".to_string(), @@ -64,6 +66,7 @@ impl Error { | (Error::TypeMismatch, "StandardError") | (Error::NoMethodError(_), "NoMethodError") | (Error::NameError(_), "NameError") + | (Error::ZeroDivisionError, "ZeroDivisionError") ) } @@ -110,6 +113,7 @@ impl From for StaticError { Error::TypeMismatch => StaticError::General("Type mismatch".to_string()), Error::NoMethodError(msg) => StaticError::General(format!("Method not found: {}", msg)), Error::NameError(msg) => StaticError::General(format!("Cannot found name: {}", msg)), + Error::ZeroDivisionError => StaticError::General("divided by 0".to_string()), Error::Break(_) => StaticError::General("[Break]".to_string()), Error::BlockReturn(_, _) => StaticError::General("[BlockReturn]".to_string()), diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 1a13f5e..4726206 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1464,12 +1464,22 @@ pub(crate) fn op_sub(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val1 = vm.take_current_regs(a)?; let val2 = vm.get_current_regs_cloned(b)?; let result = match (&val1.value, &val2.value) { - (RValue::Integer(n1), RValue::Integer(n2)) => RObject::integer(n1 - n2), + (RValue::Integer(n1), RValue::Integer(n2)) => { + RObject::integer(n1 - n2).to_refcount_assigned() + } + (RValue::Float(n1), RValue::Float(n2)) => RObject::float(n1 - n2).to_refcount_assigned(), + (RValue::Integer(n1), RValue::Float(n2)) => { + RObject::float(*n1 as f64 - n2).to_refcount_assigned() + } + (RValue::Float(n1), RValue::Integer(n2)) => { + RObject::float(n1 - *n2 as f64).to_refcount_assigned() + } _ => { - unreachable!("sub supports only integer") + let args = vec![val2.clone()]; + mrb_funcall(vm, Some(val1.clone()), "-", &args)? } }; - vm.current_regs()[a].replace(result.to_refcount_assigned()); + vm.current_regs()[a].replace(result); Ok(()) } diff --git a/mrubyedge/src/yamrb/prelude/exception.rs b/mrubyedge/src/yamrb/prelude/exception.rs index aa17a98..a838d63 100644 --- a/mrubyedge/src/yamrb/prelude/exception.rs +++ b/mrubyedge/src/yamrb/prelude/exception.rs @@ -16,6 +16,7 @@ pub(crate) fn initialize_exception(vm: &mut VM) { let _ = vm.define_standard_class_with_superclass("TypeError", std_exp_class.clone()); let _ = vm.define_standard_class_with_superclass("ArgumentError", std_exp_class.clone()); let _ = vm.define_standard_class_with_superclass("RangeError", std_exp_class.clone()); + let _ = vm.define_standard_class_with_superclass("ZeroDivisionError", std_exp_class.clone()); let _ = vm.define_standard_class_with_superclass("NoMemoryError", exp_class.clone()); let _ = vm.define_standard_class_with_superclass("ScriptError", exp_class.clone()); let _ = vm.define_standard_class_with_superclass("LoadError", exp_class.clone()); diff --git a/mrubyedge/src/yamrb/prelude/float.rs b/mrubyedge/src/yamrb/prelude/float.rs index b8731e5..0b94892 100644 --- a/mrubyedge/src/yamrb/prelude/float.rs +++ b/mrubyedge/src/yamrb/prelude/float.rs @@ -9,6 +9,12 @@ pub(crate) fn initialize_float(vm: &mut VM) { let float_class = vm.define_standard_class("Float"); mrb_define_cmethod(vm, float_class.clone(), "to_i", Box::new(mrb_float_to_i)); mrb_define_cmethod(vm, float_class.clone(), "to_f", Box::new(mrb_float_to_f)); + mrb_define_cmethod(vm, float_class.clone(), "*", Box::new(mrb_float_mul)); + mrb_define_cmethod(vm, float_class.clone(), "/", Box::new(mrb_float_div)); + mrb_define_cmethod(vm, float_class.clone(), "+@", Box::new(mrb_float_positive)); + mrb_define_cmethod(vm, float_class.clone(), "-@", Box::new(mrb_float_negative)); + mrb_define_cmethod(vm, float_class.clone(), "**", Box::new(mrb_float_power)); + mrb_define_cmethod(vm, float_class.clone(), "abs", Box::new(mrb_float_abs)); mrb_define_cmethod( vm, float_class.clone(), @@ -24,7 +30,7 @@ pub fn mrb_float_to_i(vm: &mut VM, _args: &[Rc]) -> Result, match &this.value { crate::yamrb::value::RValue::Float(f) => { let int_value = *f as i64; - Ok(Rc::new(RObject::integer(int_value))) + Ok(RObject::integer(int_value).to_refcount_assigned()) } _ => Err(Error::RuntimeError( "Float#to_i must be called on a Float".to_string(), @@ -35,7 +41,7 @@ pub fn mrb_float_to_i(vm: &mut VM, _args: &[Rc]) -> Result, pub fn mrb_float_to_f(vm: &mut VM, _args: &[Rc]) -> Result, Error> { let this = vm.getself()?; match &this.value { - crate::yamrb::value::RValue::Float(f) => Ok(Rc::new(RObject::float(*f))), + crate::yamrb::value::RValue::Float(f) => Ok(RObject::float(*f).to_refcount_assigned()), _ => Err(Error::RuntimeError( "Float#to_f must be called on a Float".to_string(), )), @@ -47,7 +53,7 @@ pub fn mrb_float_inspect(vm: &mut VM, _args: &[Rc]) -> Result { let s = format!("{}", f); - Ok(Rc::new(RObject::string(s))) + Ok(RObject::string(s).to_refcount_assigned()) } _ => Err(Error::RuntimeError( "Float#inspect must be called on a Float".to_string(), @@ -100,5 +106,117 @@ pub fn mrb_float_clamp(vm: &mut VM, args: &[Rc]) -> Result, this_float }; - Ok(Rc::new(RObject::float(result))) + Ok(RObject::float(result).to_refcount_assigned()) +} + +pub fn mrb_float_mul(vm: &mut VM, args: &[Rc]) -> Result, Error> { + if args.is_empty() { + return Err(Error::ArgumentError( + "wrong number of arguments (given 0, expected 1)".to_string(), + )); + } + + let this = vm.getself()?; + let this_float = match &this.value { + crate::yamrb::value::RValue::Float(f) => *f, + _ => { + return Err(Error::RuntimeError( + "Float#* must be called on a Float".to_string(), + )); + } + }; + + let other = match &args[0].value { + crate::yamrb::value::RValue::Float(f) => *f, + crate::yamrb::value::RValue::Integer(i) => *i as f64, + _ => return Err(Error::TypeMismatch), + }; + + Ok(RObject::float(this_float * other).to_refcount_assigned()) +} + +pub fn mrb_float_div(vm: &mut VM, args: &[Rc]) -> Result, Error> { + if args.is_empty() { + return Err(Error::ArgumentError( + "wrong number of arguments (given 0, expected 1)".to_string(), + )); + } + + let this = vm.getself()?; + let this_float = match &this.value { + crate::yamrb::value::RValue::Float(f) => *f, + _ => { + return Err(Error::RuntimeError( + "Float#/ must be called on a Float".to_string(), + )); + } + }; + + let other = match &args[0].value { + crate::yamrb::value::RValue::Float(f) => *f, + crate::yamrb::value::RValue::Integer(i) => *i as f64, + _ => return Err(Error::TypeMismatch), + }; + + if other == 0.0 { + return Err(Error::ZeroDivisionError); + } + + Ok(RObject::float(this_float / other).to_refcount_assigned()) +} + +pub fn mrb_float_positive(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + match &this.value { + crate::yamrb::value::RValue::Float(f) => Ok(RObject::float(*f).to_refcount_assigned()), + _ => Err(Error::RuntimeError( + "Float#+@ must be called on a Float".to_string(), + )), + } +} + +pub fn mrb_float_negative(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + match &this.value { + crate::yamrb::value::RValue::Float(f) => Ok(RObject::float(-*f).to_refcount_assigned()), + _ => Err(Error::RuntimeError( + "Float#-@ must be called on a Float".to_string(), + )), + } +} + +pub fn mrb_float_power(vm: &mut VM, args: &[Rc]) -> Result, Error> { + if args.is_empty() { + return Err(Error::ArgumentError( + "wrong number of arguments (given 0, expected 1)".to_string(), + )); + } + + let this = vm.getself()?; + let this_float = match &this.value { + crate::yamrb::value::RValue::Float(f) => *f, + _ => { + return Err(Error::RuntimeError( + "Float#** must be called on a Float".to_string(), + )); + } + }; + + let other = match &args[0].value { + crate::yamrb::value::RValue::Float(f) => *f, + crate::yamrb::value::RValue::Integer(i) => *i as f64, + _ => return Err(Error::TypeMismatch), + }; + + Ok(RObject::float(this_float.powf(other)).to_refcount_assigned()) +} + +pub fn mrb_float_abs(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + match &this.value { + crate::yamrb::value::RValue::Float(f) => Ok(RObject::float(f.abs()).to_refcount_assigned()), + _ => Err(Error::RuntimeError( + "Float#abs must be called on a Float".to_string(), + )), + } } diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 888d0d8..6333f03 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -1168,6 +1168,7 @@ impl RClass { Error::TypeMismatch => vm.get_class_by_name("LoadError"), Error::NoMethodError(_) => vm.get_class_by_name("NoMethodError"), Error::NameError(_) => vm.get_class_by_name("NameError"), + Error::ZeroDivisionError => vm.get_class_by_name("ZeroDivisionError"), Error::Break(_) => vm.get_class_by_name("_Break"), Error::BlockReturn(_, _) => vm.get_class_by_name("_BlockReturn"), diff --git a/mrubyedge/tests/float_ops.rs b/mrubyedge/tests/float_ops.rs new file mode 100644 index 0000000..74574b9 --- /dev/null +++ b/mrubyedge/tests/float_ops.rs @@ -0,0 +1,145 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn test_float_mul() { + let code = " + result = 3.5 * 2.0 + result + "; + let binary = mrbc_compile("float_mul", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(value, 7.0); +} + +#[test] +fn test_float_div() { + let code = " + result = 10.0 / 2.0 + result + "; + let binary = mrbc_compile("float_div", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(value, 5.0); +} + +#[test] +fn test_float_positive() { + let code = " + result = +3.5 + result + "; + let binary = mrbc_compile("float_positive", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(value, 3.5); +} + +#[test] +fn test_float_negative() { + let code = " + result = -3.5 + result + "; + let binary = mrbc_compile("float_negative", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(value, -3.5); +} + +#[test] +fn test_float_power() { + let code = " + result = 2.0 ** 3.0 + result + "; + let binary = mrbc_compile("float_power", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(value, 8.0); +} + +#[test] +fn test_float_abs() { + let code = " + result = -3.5.abs + result + "; + let binary = mrbc_compile("float_abs", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(value, 3.5); +} + +#[test] +fn test_float_mul_with_integer() { + let code = " + result = 3.5 * 2 + result + "; + let binary = mrbc_compile("float_mul_int", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(value, 7.0); +} + +#[test] +fn test_float_div_with_integer() { + let code = " + result = 10.0 / 2 + result + "; + let binary = mrbc_compile("float_div_int", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(value, 5.0); +} + +#[test] +fn test_float_add() { + let code = " + result = 3.5 + 2.5 + result + "; + let binary = mrbc_compile("float_add", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(value, 6.0); +} + +#[test] +fn test_float_sub() { + let code = " + result = 10.0 - 3.5 + result + "; + let binary = mrbc_compile("float_sub", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(value, 6.5); +} From f18b8888ac754ae8a67b3edcbd878fbcc1b29d63 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 00:10:17 +0900 Subject: [PATCH 216/314] Implement mruby-math basic with minimal tests --- Cargo.lock | 2 +- mruby-math/Cargo.toml | 6 ++-- mruby-math/src/lib.rs | 15 ++------ .../tests/{math_functions.rs => functions.rs} | 34 ++----------------- mruby-math/tests/helpers/mod.rs | 4 +-- mruby-math/tests/{simple_math.rs => smoke.rs} | 23 ++++++++++--- 6 files changed, 30 insertions(+), 54 deletions(-) rename mruby-math/tests/{math_functions.rs => functions.rs} (78%) rename mruby-math/tests/{simple_math.rs => smoke.rs} (66%) diff --git a/Cargo.lock b/Cargo.lock index 5abe987..5ee7989 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -563,7 +563,7 @@ name = "mruby-math" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.1", ] [[package]] diff --git a/mruby-math/Cargo.toml b/mruby-math/Cargo.toml index 44c0e3c..84b4699 100644 --- a/mruby-math/Cargo.toml +++ b/mruby-math/Cargo.toml @@ -7,8 +7,10 @@ description = "mruby-math provides Math module for mruby/edge" license = "BSD-3-Clause" [dependencies] -mrubyedge = "1.1.1" +mrubyedge = { path = "../mrubyedge", version = ">= 1.1.0" } [dev-dependencies] -mrubyedge = { version = "1.1.1", features = ["default"] } +mrubyedge = { path = "../mrubyedge", version = ">= 1.1.0", features = [ + "default", +] } mec-mrbc-sys = "3.3.1" diff --git a/mruby-math/src/lib.rs b/mruby-math/src/lib.rs index 182293f..945ed54 100644 --- a/mruby-math/src/lib.rs +++ b/mruby-math/src/lib.rs @@ -109,7 +109,7 @@ fn get_float_arg(obj: &RObject) -> Result { // Helper function to check argument count (excluding trailing nil) fn check_args_count(args: &[Rc], expected: usize) -> Result>, Error> { - let args = if args.len() > 0 && args[args.len() - 1].is_nil() { + let args = if !args.is_empty() && args[args.len() - 1].is_nil() { &args[0..args.len() - 1] } else { args @@ -217,7 +217,7 @@ pub fn mrb_math_exp(_vm: &mut VM, args: &[Rc]) -> Result, E } pub fn mrb_math_log(_vm: &mut VM, args: &[Rc]) -> Result, Error> { - let args_vec = if args.len() > 0 && args[args.len() - 1].is_nil() { + let args_vec = if !args.is_empty() && args[args.len() - 1].is_nil() { args[0..args.len() - 1].to_vec() } else { args.to_vec() @@ -309,14 +309,3 @@ fn erf_approximation(x: f64) -> f64 { sign * y } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_smoke_sin() { - let x = std::f64::consts::PI / 2.0; - assert!((x.sin() - 1.0).abs() < 1e-10); - } -} diff --git a/mruby-math/tests/math_functions.rs b/mruby-math/tests/functions.rs similarity index 78% rename from mruby-math/tests/math_functions.rs rename to mruby-math/tests/functions.rs index 99ed22a..e6d74fb 100644 --- a/mruby-math/tests/math_functions.rs +++ b/mruby-math/tests/functions.rs @@ -7,7 +7,7 @@ use helpers::*; #[test] fn test_math_sin() { let code = " - Math.sin(Math::PI / 2) + Math.sin(Math::PI / 6.0) "; let binary = mrbc_compile("math_sin", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); @@ -16,7 +16,7 @@ fn test_math_sin() { let result = vm.run().unwrap(); let value: f64 = result.as_ref().try_into().unwrap(); - assert!((value - 1.0).abs() < 1e-10); + assert!((value - 0.5).abs() < 1e-10); } #[test] @@ -79,36 +79,6 @@ fn test_math_log_with_base() { assert!((value - 3.0).abs() < 1e-10); } -#[test] -fn test_math_pi() { - let code = " - Math::PI - "; - let binary = mrbc_compile("math_pi", code); - let mut rite = mrubyedge::rite::load(&binary).unwrap(); - let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_math::init_math(&mut vm); - - let result = vm.run().unwrap(); - let value: f64 = result.as_ref().try_into().unwrap(); - assert!((value - std::f64::consts::PI).abs() < 1e-10); -} - -#[test] -fn test_math_e() { - let code = " - Math::E - "; - let binary = mrbc_compile("math_e", code); - let mut rite = mrubyedge::rite::load(&binary).unwrap(); - let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_math::init_math(&mut vm); - - let result = vm.run().unwrap(); - let value: f64 = result.as_ref().try_into().unwrap(); - assert!((value - std::f64::consts::E).abs() < 1e-10); -} - #[test] fn test_math_atan2() { let code = " diff --git a/mruby-math/tests/helpers/mod.rs b/mruby-math/tests/helpers/mod.rs index 41f22e4..9156fa6 100644 --- a/mruby-math/tests/helpers/mod.rs +++ b/mruby-math/tests/helpers/mod.rs @@ -19,8 +19,8 @@ pub fn mrbc_compile(fname: &'static str, code: &'static str) -> Vec { dest0.push('\0'); let args = [ - CStr::from_bytes_with_nul(b"mrbc\0").unwrap().as_ptr(), - CStr::from_bytes_with_nul(b"-o\0").unwrap().as_ptr(), + c"mrbc".as_ptr(), + c"-o".as_ptr(), CStr::from_bytes_with_nul(dest0.as_bytes()) .unwrap() .as_ptr(), diff --git a/mruby-math/tests/simple_math.rs b/mruby-math/tests/smoke.rs similarity index 66% rename from mruby-math/tests/simple_math.rs rename to mruby-math/tests/smoke.rs index d47ba29..0d9f945 100644 --- a/mruby-math/tests/simple_math.rs +++ b/mruby-math/tests/smoke.rs @@ -20,11 +20,26 @@ fn test_math_pi_constant() { } #[test] -fn test_math_sqrt_simple() { +fn test_math_e_constant() { + let code = " + Math::E + "; + let binary = mrbc_compile("math_e_const", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_math::init_math(&mut vm); + + let result = vm.run().unwrap(); + let value: f64 = result.as_ref().try_into().unwrap(); + assert!((value - std::f64::consts::E).abs() < 1e-10); +} + +#[test] +fn test_math_sqrt_smoke() { let code = " Math.sqrt(4.0) "; - let binary = mrbc_compile("math_sqrt_simple", code); + let binary = mrbc_compile("math_sqrt_smoke", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); mruby_math::init_math(&mut vm); @@ -35,11 +50,11 @@ fn test_math_sqrt_simple() { } #[test] -fn test_math_sin_simple() { +fn test_math_sin_smoke() { let code = " Math.sin(Math::PI / 2) "; - let binary = mrbc_compile("math_sin_simple", code); + let binary = mrbc_compile("math_sin_smoke", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); mruby_math::init_math(&mut vm); From 47c1e8c1eca0edb27369de6a0ec3566aaf5c7674 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 00:11:06 +0900 Subject: [PATCH 217/314] Bump mruby/edge --- Cargo.lock | 22 +++++++++++----------- mrubyedge/Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5ee7989..95a52a5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -563,7 +563,7 @@ name = "mruby-math" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.1", + "mrubyedge 1.1.2", ] [[package]] @@ -571,7 +571,7 @@ name = "mruby-serde-json" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.1", "serde", "serde_json", ] @@ -579,12 +579,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9806ae81ff6ebcb666faba199ab68a1b21873aa7f1a8a08d432d79198ff5658" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -594,10 +591,13 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9806ae81ff6ebcb666faba199ab68a1b21873aa7f1a8a08d432d79198ff5658" +version = "1.1.2" dependencies = [ + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -612,7 +612,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.1", "nom", "rand", "syn", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index cc49ffe..ba6efb8 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.1.1" +version = "1.1.2" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From 81cc2d6398e4f5bdab8bc4f8b63b2bd5d6b91ed7 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 00:12:44 +0900 Subject: [PATCH 218/314] Update deps --- Cargo.lock | 22 +++++++++++----------- mruby-math/Cargo.toml | 6 ++---- 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 95a52a5..fe7fe88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -563,7 +563,7 @@ name = "mruby-math" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.2", + "mrubyedge 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -571,17 +571,20 @@ name = "mruby-serde-json" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.1", + "mrubyedge 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "serde_json", ] [[package]] name = "mrubyedge" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9806ae81ff6ebcb666faba199ab68a1b21873aa7f1a8a08d432d79198ff5658" +version = "1.1.2" dependencies = [ + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -592,12 +595,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61d88627868b0d2130554392e3f520a74626c9a380b4b4e8015cef4b009b5b78" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -612,7 +612,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.1.1", + "mrubyedge 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", diff --git a/mruby-math/Cargo.toml b/mruby-math/Cargo.toml index 84b4699..34f690c 100644 --- a/mruby-math/Cargo.toml +++ b/mruby-math/Cargo.toml @@ -7,10 +7,8 @@ description = "mruby-math provides Math module for mruby/edge" license = "BSD-3-Clause" [dependencies] -mrubyedge = { path = "../mrubyedge", version = ">= 1.1.0" } +mrubyedge = { version = ">= 1.1.2" } [dev-dependencies] -mrubyedge = { path = "../mrubyedge", version = ">= 1.1.0", features = [ - "default", -] } +mrubyedge = { version = ">= 1.1.2", features = ["default"] } mec-mrbc-sys = "3.3.1" From ee330d5c2166f1f1dd180d04d939822ba6ba5806 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 00:14:24 +0900 Subject: [PATCH 219/314] Add CI file --- .github/workflows/mruby-math.yml | 43 ++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/mruby-math.yml diff --git a/.github/workflows/mruby-math.yml b/.github/workflows/mruby-math.yml new file mode 100644 index 0000000..c3eaab9 --- /dev/null +++ b/.github/workflows/mruby-math.yml @@ -0,0 +1,43 @@ +name: mruby-math CI + +on: + push: + branches: + - master + paths: + - 'mruby-math/**' + pull_request: + branches: + - master + paths: + - 'mruby-math/**' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + BUILD_TARGET: [release] + steps: + - uses: actions/checkout@v5 + - name: Cache + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-math-${{ hashFiles('**/Cargo.lock') }} + # - name: Install C compiler + # run: sudo apt-get update && sudo apt-get install -y build-essential + - name: Run formatter + run: | + cargo fmt -p mruby-math --check + - name: Run tests for "${{ matrix.BUILD_TARGET }}" profile + run: | + cargo test -p mruby-math \ + --profile ${{ matrix.BUILD_TARGET }} + - name: Build binaries for "${{ matrix.BUILD_TARGET }}" profile + run: | + cargo build -p mruby-math \ + --profile ${{ matrix.BUILD_TARGET }} From 0d6e7cdae2924d346a647a576d0a50897376bc34 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 00:25:12 +0900 Subject: [PATCH 220/314] Bump again... --- mruby-math/Cargo.toml | 4 +-- mruby-math/tests/sine.rs | 60 +++++++++++++++++++++++++++++++++ mruby-serde-json/Cargo.toml | 6 ++-- mrubyedge-cli/Cargo.toml | 2 +- mrubyedge/Cargo.toml | 2 +- mrubyedge/examples/sin_curve.rb | 1 - mrubyedge/src/yamrb/optable.rs | 17 +++++++++- 7 files changed, 83 insertions(+), 9 deletions(-) create mode 100644 mruby-math/tests/sine.rs diff --git a/mruby-math/Cargo.toml b/mruby-math/Cargo.toml index 34f690c..07c19e2 100644 --- a/mruby-math/Cargo.toml +++ b/mruby-math/Cargo.toml @@ -7,8 +7,8 @@ description = "mruby-math provides Math module for mruby/edge" license = "BSD-3-Clause" [dependencies] -mrubyedge = { version = ">= 1.1.2" } +mrubyedge = { version = ">= 1.1.3" } [dev-dependencies] -mrubyedge = { version = ">= 1.1.2", features = ["default"] } +mrubyedge = { version = ">= 1.1.3", features = ["default"] } mec-mrbc-sys = "3.3.1" diff --git a/mruby-math/tests/sine.rs b/mruby-math/tests/sine.rs new file mode 100644 index 0000000..92cc3a7 --- /dev/null +++ b/mruby-math/tests/sine.rs @@ -0,0 +1,60 @@ +extern crate mruby_math; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn test_sine_curve() { + let code = r##" +# Sin curve ASCII art +# Draw sine wave from 0 to 4π + +PI = Math::PI + +# Settings +width = 60 # Width of the graph +height = 15 # Height of the graph (centered) +x_range = 4.to_f * PI # 0 to 4π +steps = 80 # Number of points to plot + +puts "Sin(x) curve from 0 to 4π" +puts "=" * width +puts "" + +# Draw the curve +(0..steps).each do |i| + x = (x_range * i) / steps + y = Math.sin(x) + + # Map y (-1 to 1) to column position (0 to width-1) + col = ((y + 1) * (width - 1) / 2).to_i + + # Print spaces then the marker + line = " " * col + "*" + + # Add x-axis marker at y=0 + if y.abs < 0.1 + line = line.sub("*", "|") + end + + # Show x value at specific points + if i % 20 == 0 + x_label = (x / PI * 10).to_i / 10.0 + puts line + " (x=#{x_label}π)" + else + puts line + end +end + +puts "" +puts "=" * width +puts "Legend: * = sin(x), | = x-axis (y≈0)" + "##; + let binary = mrbc_compile("sine_curve", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_math::init_math(&mut vm); + + assert!(vm.run().unwrap().is_nil()); +} diff --git a/mruby-serde-json/Cargo.toml b/mruby-serde-json/Cargo.toml index 47cb138..cc4214c 100644 --- a/mruby-serde-json/Cargo.toml +++ b/mruby-serde-json/Cargo.toml @@ -7,11 +7,11 @@ description = "mruby-serde-json provides JSON serialization/deserialization for license = "BSD-3-Clause" [dependencies] -mrubyedge = ">= 1.1.0" -# mrubyedge = { version = "1.1.0", path = "../mrubyedge", default-features = false } +mrubyedge = ">= 1.1.3" +# mrubyedge = { version = "1.1.3", path = "../mrubyedge", default-features = false } serde = ">= 1.0.228" serde_json = ">= 1.0.149" [dev-dependencies] -mrubyedge = { version = "1.1.0", features = ["default"] } +mrubyedge = { version = ">= 1.1.3", features = ["default"] } mec-mrbc-sys = "3.3.1" diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 6b03ada..3d37e66 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } mruby-compiler2-sys = "0.2.2" -mrubyedge = { version = "1.1.1", features = [ +mrubyedge = { version = "1.1.3", features = [ "default", "mruby-random", "mruby-regexp", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index ba6efb8..c9393ff 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.1.2" +version = "1.1.3" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" diff --git a/mrubyedge/examples/sin_curve.rb b/mrubyedge/examples/sin_curve.rb index 70f609a..76ee4a8 100644 --- a/mrubyedge/examples/sin_curve.rb +++ b/mrubyedge/examples/sin_curve.rb @@ -5,7 +5,6 @@ # Settings width = 60 # Width of the graph -height = 15 # Height of the graph (centered) x_range = 4 * PI # 0 to 4π steps = 80 # Number of points to plot diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 4726206..cdb3287 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1450,8 +1450,9 @@ pub(crate) fn op_addi(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let val2 = b as i64; let result = match &val1.value { RValue::Integer(n1) => RObject::integer(*n1 + val2), + RValue::Float(n1) => RObject::float(n1 + val2 as f64), _ => { - unreachable!("addi supports only integer") + unreachable!("addi supports only integer and float") } }; vm.current_regs()[a as usize].replace(result.to_refcount_assigned()); @@ -1506,6 +1507,13 @@ pub(crate) fn op_mul(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { (RValue::Integer(n1), RValue::Integer(n2)) => { RObject::integer(n1 * n2).to_refcount_assigned() } + (RValue::Float(n1), RValue::Float(n2)) => RObject::float(n1 * n2).to_refcount_assigned(), + (RValue::Integer(n1), RValue::Float(n2)) => { + RObject::float(*n1 as f64 * n2).to_refcount_assigned() + } + (RValue::Float(n1), RValue::Integer(n2)) => { + RObject::float(n1 * *n2 as f64).to_refcount_assigned() + } _ => mrb_funcall(vm, Some(val1), "*", &[val2])?, }; vm.current_regs()[a].replace(result); @@ -1521,6 +1529,13 @@ pub(crate) fn op_div(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { (RValue::Integer(n1), RValue::Integer(n2)) => { RObject::integer(n1 / n2).to_refcount_assigned() } + (RValue::Float(n1), RValue::Float(n2)) => RObject::float(n1 / n2).to_refcount_assigned(), + (RValue::Integer(n1), RValue::Float(n2)) => { + RObject::float(*n1 as f64 / n2).to_refcount_assigned() + } + (RValue::Float(n1), RValue::Integer(n2)) => { + RObject::float(n1 / *n2 as f64).to_refcount_assigned() + } _ => mrb_funcall(vm, Some(val1), "/", &[val2])?, }; vm.current_regs()[a].replace(result); From 3e35bfc67dcc9bf43fd9a50d7127ed3df0453359 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 00:28:54 +0900 Subject: [PATCH 221/314] Fix lockfile --- Cargo.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fe7fe88..d7dcc5c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -563,7 +563,7 @@ name = "mruby-math" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -571,14 +571,14 @@ name = "mruby-serde-json" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "serde_json", ] [[package]] name = "mrubyedge" -version = "1.1.2" +version = "1.1.3" dependencies = [ "criterion", "fnv", @@ -594,9 +594,9 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.1.2" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61d88627868b0d2130554392e3f520a74626c9a380b4b4e8015cef4b009b5b78" +checksum = "6846a5b11991886da18451b11b3bebf8540afde422254df856c169ebe0633b89" dependencies = [ "plain", "rand_core 0.10.0", @@ -612,7 +612,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", From 2234d492ff8dbbd08ec4d4fdc5300404cd86e8f0 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 00:31:40 +0900 Subject: [PATCH 222/314] Add sine curve sample --- mruby-math/tests/sine.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/mruby-math/tests/sine.rs b/mruby-math/tests/sine.rs index 92cc3a7..f4b837f 100644 --- a/mruby-math/tests/sine.rs +++ b/mruby-math/tests/sine.rs @@ -30,12 +30,11 @@ puts "" # Map y (-1 to 1) to column position (0 to width-1) col = ((y + 1) * (width - 1) / 2).to_i - # Print spaces then the marker - line = " " * col + "*" - # Add x-axis marker at y=0 - if y.abs < 0.1 - line = line.sub("*", "|") + line = if y.abs < 0.1 + " " * col + "|" + else + " " * col + "*" end # Show x value at specific points From 1b19dfa2a09accf7e529463222eda422a5883b1d Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 00:37:03 +0900 Subject: [PATCH 223/314] Release v1.1.3 --- Cargo.lock | 2 +- mrubyedge-cli/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d7dcc5c..ac3702a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -607,7 +607,7 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.1.1" +version = "1.1.3" dependencies = [ "askama", "clap", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 3d37e66..d0e40fe 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.1.1" +version = "1.1.3" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." From b1237f69c7871083c041635f24b425efa9bd525c Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 21:07:15 +0900 Subject: [PATCH 224/314] First implementation of Object#extend method --- mrubyedge/src/yamrb/prelude/object.rs | 43 ++++++++++++++ mrubyedge/src/yamrb/value.rs | 36 +++++++++-- mrubyedge/tests/object.rs | 86 +++++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 5 deletions(-) diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index 106cfa0..eee917f 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -110,6 +110,12 @@ pub(crate) fn initialize_object(vm: &mut VM) { "method_missing", Box::new(mrb_object_method_missing), ); + mrb_define_cmethod( + vm, + object_class.clone(), + "extend", + Box::new(mrb_object_extend), + ); // define global consts: vm.consts.insert( @@ -772,3 +778,40 @@ fn test_mrb_object_is_equal_instance() { .expect("must return bool"); assert!(!ret); } + +fn mrb_object_extend(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + + if args.is_empty() { + return Err(Error::ArgumentError( + "wrong number of arguments (given 0, expected 1+)".to_string(), + )); + } + + // Initialize or get singleton class + let singleton_class = this.initialize_or_get_singleton_class(vm); + + // Extend with each module argument + for arg in args { + let module = match &arg.value { + RValue::Module(m) => m.clone(), + RValue::Class(c) => c.module.clone(), + RValue::Nil => { + continue; + } + _ => { + return Err(Error::ArgumentError( + "wrong argument type (expected Module)".to_string(), + )); + } + }; + + // Add module to extended_modules + singleton_class + .extended_modules + .borrow_mut() + .insert(0, module); + } + + Ok(this) +} diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 6333f03..2c7190c 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -948,6 +948,7 @@ pub struct RClass { pub super_class: Option>, pub singleton_class_ref: RefCell>>, pub is_singleton: bool, + pub extended_modules: RefCell>>, } impl RClass { @@ -966,6 +967,7 @@ impl RClass { super_class, singleton_class_ref, is_singleton: false, + extended_modules: RefCell::new(Vec::new()), } } @@ -984,6 +986,7 @@ impl RClass { super_class, singleton_class_ref, is_singleton: true, + extended_modules: RefCell::new(Vec::new()), } } @@ -993,12 +996,26 @@ impl RClass { // find_method will search method from self to superclass pub fn find_method(&self, name: &str) -> Option { + // First check this class's module match self.module.find_method(name) { - Some(p) => Some(p), - None => match &self.super_class { - Some(sc) => sc.find_method(name), - None => None, - }, + Some(p) => return Some(p), + None => {} + } + + // For singleton classes, check extended modules + if self.is_singleton { + let extended = self.extended_modules.borrow(); + for module in extended.iter() { + if let Some(p) = module.find_method(name) { + return Some(p); + } + } + } + + // Finally check superclass + match &self.super_class { + Some(sc) => sc.find_method(name), + None => None, } } @@ -1020,6 +1037,15 @@ fn collect_class_chain( visited: &mut RHashSet, ) { collect_module_chain(&class.module, chain, visited); + + // For singleton classes, include extended modules + if class.is_singleton { + let extended = class.extended_modules.borrow(); + for module in extended.iter() { + collect_module_chain(module, chain, visited); + } + } + if let Some(super_class) = &class.super_class { collect_class_chain(super_class, chain, visited); } diff --git a/mrubyedge/tests/object.rs b/mrubyedge/tests/object.rs index 3deb9fa..9fd3001 100644 --- a/mrubyedge/tests/object.rs +++ b/mrubyedge/tests/object.rs @@ -2,7 +2,10 @@ extern crate mec_mrbc_sys; extern crate mrubyedge; mod helpers; +use std::rc::Rc; + use helpers::*; +use mrubyedge::yamrb::value::RObject; #[test] fn object_test() { @@ -32,3 +35,86 @@ fn object_test() { .unwrap(); assert_eq!(result, 1); } + +#[test] +fn object_extend_test() { + let code = r#" + module Greeter + def greet + "Hello from module" + end + end + + module Farewell + def bye + "Goodbye from module" + end + end + + def test_extend + obj = Object.new + obj.extend(Greeter) + result1 = obj.greet + + obj.extend(Farewell) + result2 = obj.bye + + [result1, result2] + end + "#; + let binary = mrbc_compile("extend", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_extend", &args).unwrap(); + let arr: Vec> = result.as_ref().try_into().unwrap(); + assert_eq!( + TryInto::::try_into(arr[0].as_ref()).unwrap(), + "Hello from module" + ); + assert_eq!( + TryInto::::try_into(arr[1].as_ref()).unwrap(), + "Goodbye from module" + ); +} + +#[test] +fn object_extend_multiple_modules_test() { + let code = r#" + module M1 + def m1_method + "from M1" + end + end + + module M2 + def m2_method + "from M2" + end + end + + def test_extend_multiple + obj = Object.new + obj.extend(M1, M2) + [obj.m1_method, obj.m2_method] + end + "#; + let binary = mrbc_compile("extend_multiple", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_extend_multiple", &args).unwrap(); + let arr: Vec> = result.as_ref().try_into().unwrap(); + assert_eq!( + TryInto::::try_into(arr[0].as_ref()).unwrap(), + "from M1" + ); + assert_eq!( + TryInto::::try_into(arr[1].as_ref()).unwrap(), + "from M2" + ); +} From e1eb3bd1ab0a47b12c38864395fd176ad9ad933d Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 21:19:10 +0900 Subject: [PATCH 225/314] Add tests --- mrubyedge/src/yamrb/prelude/object.rs | 2 +- mrubyedge/tests/object.rs | 173 ++++++++++++++++++++++++++ 2 files changed, 174 insertions(+), 1 deletion(-) diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index eee917f..9517a2c 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -792,7 +792,7 @@ fn mrb_object_extend(vm: &mut VM, args: &[Rc]) -> Result, E let singleton_class = this.initialize_or_get_singleton_class(vm); // Extend with each module argument - for arg in args { + for arg in args.iter().rev() { let module = match &arg.value { RValue::Module(m) => m.clone(), RValue::Class(c) => c.module.clone(), diff --git a/mrubyedge/tests/object.rs b/mrubyedge/tests/object.rs index 9fd3001..8569720 100644 --- a/mrubyedge/tests/object.rs +++ b/mrubyedge/tests/object.rs @@ -118,3 +118,176 @@ fn object_extend_multiple_modules_test() { "from M2" ); } + +#[test] +fn object_extend_overrides_class_method_test() { + let code = r#" + class MyClass + def greet + "from class" + end + end + + module MyModule + def greet + "from module" + end + end + + def test_extend_override + obj = MyClass.new + obj.extend(MyModule) + obj.greet + end + "#; + let binary = mrbc_compile("extend_override_class", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_extend_override", &args).unwrap(); + let result_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(result_str, "from module"); +} + +#[test] +fn object_extend_singleton_method_priority_test() { + let code = r#" + module MyModule + def greet + "from module" + end + end + + def test_singleton_priority + obj = Object.new + obj.extend(MyModule) + + def obj.greet + "from singleton" + end + + obj.greet + end + "#; + let binary = mrbc_compile("extend_singleton_priority", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_singleton_priority", &args).unwrap(); + let result_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(result_str, "from singleton"); +} + +#[test] +fn object_extend_multiple_priority_test() { + let code = r#" + module M1 + def greet + "from M1" + end + end + + module M2 + def greet + "from M2" + end + end + + module M3 + def greet + "from M3" + end + end + + def test_multiple_priority + obj = Object.new + obj.extend(M1) + result1 = obj.greet + + obj.extend(M2) + result2 = obj.greet + + obj.extend(M3) + result3 = obj.greet + + [result1, result2, result3] + end + "#; + let binary = mrbc_compile("extend_multiple_priority", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_multiple_priority", &args).unwrap(); + let arr: Vec> = result.as_ref().try_into().unwrap(); + assert_eq!( + TryInto::::try_into(arr[0].as_ref()).unwrap(), + "from M1" + ); + assert_eq!( + TryInto::::try_into(arr[1].as_ref()).unwrap(), + "from M2" + ); + assert_eq!( + TryInto::::try_into(arr[2].as_ref()).unwrap(), + "from M3" + ); +} + +#[test] +fn object_extend_multiple_arguments_priority_test() { + let code = r#" + module M1 + def greet + "from M1" + end + + def m1_only + "M1 only" + end + end + + module M2 + def greet + "from M2" + end + + def m2_only + "M2 only" + end + end + + def test_args_priority + obj = Object.new + # extend(M1, M2) extends in order: M2, then M1, so M1 has priority + obj.extend(M1, M2) + [obj.greet, obj.m1_only, obj.m2_only] + end + "#; + let binary = mrbc_compile("extend_args_priority", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_args_priority", &args).unwrap(); + let arr: Vec> = result.as_ref().try_into().unwrap(); + // M1 is extended last, so greet calls M1's method + assert_eq!( + TryInto::::try_into(arr[0].as_ref()).unwrap(), + "from M1" + ); + assert_eq!( + TryInto::::try_into(arr[1].as_ref()).unwrap(), + "M1 only" + ); + assert_eq!( + TryInto::::try_into(arr[2].as_ref()).unwrap(), + "M2 only" + ); +} From a41309d7b9a547bfef939e0d16d74651c682a33f Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 21:31:14 +0900 Subject: [PATCH 226/314] re-exports --- mrubyedge/src/lib.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mrubyedge/src/lib.rs b/mrubyedge/src/lib.rs index ed4d7f4..4d82896 100644 --- a/mrubyedge/src/lib.rs +++ b/mrubyedge/src/lib.rs @@ -69,7 +69,11 @@ pub mod eval; pub mod rite; pub mod yamrb; +// re-exports for easier access pub use error::Error; +pub use rite::{Rite, load}; +pub use yamrb::value::RObject; +pub use yamrb::vm::VM; /// The version of the mrubyedge crate #[macro_export] From 208e7c7e3b3aca598c7f51a277845370597b2c56 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 21:43:46 +0900 Subject: [PATCH 227/314] Force to set default irep on run() call --- mrubyedge/src/yamrb/vm.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 49c9087..22695aa 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -301,6 +301,8 @@ impl VM { /// register 0 or propagating any raised exception as an error. The /// top-level `self` is initialized automatically before evaluation. pub fn run(&mut self) -> Result, Box> { + self.current_irep = self.irep.clone(); + let upper = self.current_breadcrumb.take(); let new_breadcrumb = Rc::new(Breadcrumb { upper, From ec508e5ac0618632f50d86e5c70c1fe160673fad Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 22:10:08 +0900 Subject: [PATCH 228/314] Add eval_rite to load Rite file dynamically multiple times --- mrubyedge/examples/eval.rs | 91 ++++++++++++++++++++++++++++++++++++++ mrubyedge/src/yamrb/vm.rs | 20 +++++++++ 2 files changed, 111 insertions(+) create mode 100644 mrubyedge/examples/eval.rs diff --git a/mrubyedge/examples/eval.rs b/mrubyedge/examples/eval.rs new file mode 100644 index 0000000..b24ae7e --- /dev/null +++ b/mrubyedge/examples/eval.rs @@ -0,0 +1,91 @@ +use std::env; +use std::fs::remove_file; +use std::process::Command; +use std::rc::Rc; + +use mrubyedge::RObject; +use mrubyedge::yamrb::helpers::mrb_call_p; + +extern crate mrubyedge; + +fn compile( + source_code: &str, + output_path: &str, + is_verbose: bool, +) -> Result, std::io::Error> { + let source_path = output_path.replace(".mrb", ".rb"); + std::fs::write(&source_path, source_code)?; + let mut mrbc = Command::new("mrbc"); + if is_verbose { + mrbc.arg("-v"); + } + let result = mrbc + .arg("-o") + .arg(output_path) + .arg(&source_path) + .output() + .expect("failed to compile mruby script"); + if is_verbose { + eprintln!("stdout: {}", String::from_utf8_lossy(&result.stdout)); + eprintln!("stderr: {}", String::from_utf8_lossy(&result.stderr)); + } + std::fs::read(output_path) +} + +fn result_p(vm: &mut mrubyedge::VM, result: Rc) { + eprint!("return value: "); + mrb_call_p(vm, result); +} + +fn main() -> Result<(), std::io::Error> { + let is_verbose = env::var("MRUBYEDGE_DEBUG").is_ok(); + + let code1 = r#" + class Foo + def bar + puts "Hello from Foo#bar" + 42 + end + end + puts "Hi" + "#; + let output_path_1 = "/tmp/__tmp__.mrb"; + let mrb1 = compile(code1, output_path_1, is_verbose)?; + let mut rite = mrubyedge::rite::load(&mrb1).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + let _ = vm.run().unwrap(); + remove_file(output_path_1)?; + + let code = r#" + class Bar + def baz + puts "Hello from Bar#baz" + 100 + end + end + "#; + let output_path = "/tmp/__tmp_a__.mrb"; + let mrb = compile(code, output_path, is_verbose)?; + let mut rite = mrubyedge::rite::load(&mrb).unwrap(); + let res = vm.eval_rite(&mut rite).unwrap(); + result_p(&mut vm, res); + remove_file(output_path)?; + + let code = r#" + puts "Hola" + foo = Foo.new + v1 = foo.bar + bar = Bar.new + v2 = bar.baz + v1 + v2 + "#; + let output_path = "/tmp/__tmp_z__.mrb"; + let mrb = compile(code, output_path, is_verbose)?; + let mut rite = mrubyedge::rite::load(&mrb).unwrap(); + let res = vm.eval_rite(&mut rite).unwrap(); + result_p(&mut vm, res); + remove_file(output_path)?; + + // dbg!(&vm); + Ok(()) +} diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 22695aa..a6096b5 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -302,6 +302,7 @@ impl VM { /// top-level `self` is initialized automatically before evaluation. pub fn run(&mut self) -> Result, Box> { self.current_irep = self.irep.clone(); + self.pc.set(0); let upper = self.current_breadcrumb.take(); let new_breadcrumb = Rc::new(Breadcrumb { @@ -327,6 +328,25 @@ impl VM { self.__run() } + pub fn eval_rite( + &mut self, + rite: &mut Rite, + ) -> Result, Box> { + let irep = rite_to_irep(rite); + self.pc.set(0); + self.current_irep = Rc::new(irep); + + let upper = self.current_breadcrumb.take(); + let new_breadcrumb = Rc::new(Breadcrumb { + upper, + event: "eval", + caller: None, + return_reg: None, + }); + self.current_breadcrumb.replace(new_breadcrumb); + self.__run() + } + fn __run(&mut self) -> Result, Box> { let class = self.object_class.clone(); // Insert top_self From 4a71ad7decae9f79d5402c9175059cfffbd76ee2 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 22:18:08 +0900 Subject: [PATCH 229/314] Add eval tests --- mrubyedge/tests/vm.rs | 5 + mrubyedge/tests/vm/eval_rite.rs | 196 ++++++++++++++++++++++++++++++++ 2 files changed, 201 insertions(+) create mode 100644 mrubyedge/tests/vm.rs create mode 100644 mrubyedge/tests/vm/eval_rite.rs diff --git a/mrubyedge/tests/vm.rs b/mrubyedge/tests/vm.rs new file mode 100644 index 0000000..3ae5ac6 --- /dev/null +++ b/mrubyedge/tests/vm.rs @@ -0,0 +1,5 @@ +#[path = "helpers/mod.rs"] +mod helpers; + +#[path = "vm/eval_rite.rs"] +mod eval_rite; diff --git a/mrubyedge/tests/vm/eval_rite.rs b/mrubyedge/tests/vm/eval_rite.rs new file mode 100644 index 0000000..09e339e --- /dev/null +++ b/mrubyedge/tests/vm/eval_rite.rs @@ -0,0 +1,196 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +use std::rc::Rc; + +use super::helpers::*; +use mrubyedge::yamrb::value::RObject; + +#[test] +fn test_eval_multiple_rites_with_classes() { + // First code: define Foo class + let code1 = r#" + class Foo + def bar + "Hello from Foo" + end + end + "#; + let binary1 = mrbc_compile("code1", code1); + let mut rite1 = mrubyedge::rite::load(&binary1).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite1); + vm.run().unwrap(); + + // Second code: define Bar class + let code2 = r#" + class Bar + def baz + "Hello from Bar" + end + end + "#; + let binary2 = mrbc_compile("code2", code2); + let mut rite2 = mrubyedge::rite::load(&binary2).unwrap(); + vm.eval_rite(&mut rite2).unwrap(); + + // Third code: use both Foo and Bar classes + let code3 = r#" + def test_both + foo = Foo.new + bar = Bar.new + [foo.bar, bar.baz] + end + "#; + let binary3 = mrbc_compile("code3", code3); + let mut rite3 = mrubyedge::rite::load(&binary3).unwrap(); + vm.eval_rite(&mut rite3).unwrap(); + + // Call the method that uses both classes + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_both", &args).unwrap(); + let arr: Vec> = result.as_ref().try_into().unwrap(); + assert_eq!( + TryInto::::try_into(arr[0].as_ref()).unwrap(), + "Hello from Foo" + ); + assert_eq!( + TryInto::::try_into(arr[1].as_ref()).unwrap(), + "Hello from Bar" + ); +} + +#[test] +fn test_eval_multiple_rites_accumulate_methods() { + // First code: define initial method + let code1 = r#" + def greet(name) + "Hello, #{name}!" + end + "#; + let binary1 = mrbc_compile("greet", code1); + let mut rite1 = mrubyedge::rite::load(&binary1).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite1); + vm.run().unwrap(); + + // Second code: define another method + let code2 = r#" + def farewell(name) + "Goodbye, #{name}!" + end + "#; + let binary2 = mrbc_compile("farewell", code2); + let mut rite2 = mrubyedge::rite::load(&binary2).unwrap(); + vm.eval_rite(&mut rite2).unwrap(); + + // Third code: use both methods + let code3 = r#" + def test_methods + [greet("Alice"), farewell("Bob")] + end + "#; + let binary3 = mrbc_compile("test_methods", code3); + let mut rite3 = mrubyedge::rite::load(&binary3).unwrap(); + vm.eval_rite(&mut rite3).unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_methods", &args).unwrap(); + let arr: Vec> = result.as_ref().try_into().unwrap(); + assert_eq!( + TryInto::::try_into(arr[0].as_ref()).unwrap(), + "Hello, Alice!" + ); + assert_eq!( + TryInto::::try_into(arr[1].as_ref()).unwrap(), + "Goodbye, Bob!" + ); +} + +#[test] +fn test_eval_multiple_rites_with_inheritance() { + // First code: define base class + let code1 = r#" + class Animal + def speak + "Some sound" + end + end + "#; + let binary1 = mrbc_compile("animal", code1); + let mut rite1 = mrubyedge::rite::load(&binary1).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite1); + vm.run().unwrap(); + + // Second code: define subclass that inherits from Animal + let code2 = r#" + class Dog < Animal + def speak + "Woof!" + end + end + "#; + let binary2 = mrbc_compile("dog", code2); + let mut rite2 = mrubyedge::rite::load(&binary2).unwrap(); + vm.eval_rite(&mut rite2).unwrap(); + + // Third code: use the subclass + let code3 = r#" + def test_inheritance + dog = Dog.new + dog.speak + end + "#; + let binary3 = mrbc_compile("test_inheritance", code3); + let mut rite3 = mrubyedge::rite::load(&binary3).unwrap(); + vm.eval_rite(&mut rite3).unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_inheritance", &args).unwrap(); + let result_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(result_str, "Woof!"); +} + +#[test] +fn test_eval_multiple_rites_with_modules() { + // First code: define module + let code1 = r#" + module Greeter + def greet + "Hello from #{@name}" + end + end + "#; + let binary1 = mrbc_compile("module", code1); + let mut rite1 = mrubyedge::rite::load(&binary1).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite1); + vm.run().unwrap(); + + // Second code: define class that includes the module + let code2 = r#" + class Person + include Greeter + + def initialize(name) + @name = name + end + end + "#; + let binary2 = mrbc_compile("person", code2); + let mut rite2 = mrubyedge::rite::load(&binary2).unwrap(); + vm.eval_rite(&mut rite2).unwrap(); + + // Third code: use the class with included module + let code3 = r#" + def test_module_include + person = Person.new("Alice") + person.greet + end + "#; + let binary3 = mrbc_compile("test_include", code3); + let mut rite3 = mrubyedge::rite::load(&binary3).unwrap(); + vm.eval_rite(&mut rite3).unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_module_include", &args).unwrap(); + let result_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(result_str, "Hello from Alice"); +} From 5406b170dbd099b0ab7e64633d0948bedd0caf55 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 22:39:19 +0900 Subject: [PATCH 230/314] Support basic repl... for debug --- Cargo.lock | 5 +- mrubyedge-cli/Cargo.toml | 2 +- mrubyedge-cli/src/main.rs | 5 ++ mrubyedge-cli/src/subcommands/mod.rs | 1 + mrubyedge-cli/src/subcommands/repl.rs | 122 ++++++++++++++++++++++++++ 5 files changed, 130 insertions(+), 5 deletions(-) create mode 100644 mrubyedge-cli/src/subcommands/repl.rs diff --git a/Cargo.lock b/Cargo.lock index ac3702a..3759910 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -599,9 +599,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6846a5b11991886da18451b11b3bebf8540afde422254df856c169ebe0633b89" dependencies = [ "plain", - "rand_core 0.10.0", - "rand_xorshift", - "regex", "simple_endian", ] @@ -612,7 +609,7 @@ dependencies = [ "askama", "clap", "mruby-compiler2-sys", - "mrubyedge 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.3", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index d0e40fe..79cc922 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -18,7 +18,7 @@ mrubyedge = { version = "1.1.3", features = [ "default", "mruby-random", "mruby-regexp", -] } +], path = "../mrubyedge" } rand = "0.9.2" nom = "7.1.3" askama = "0.12.1" diff --git a/mrubyedge-cli/src/main.rs b/mrubyedge-cli/src/main.rs index a94d412..f5ae520 100644 --- a/mrubyedge-cli/src/main.rs +++ b/mrubyedge-cli/src/main.rs @@ -27,6 +27,8 @@ enum Commands { /// Run Ruby code or binary. /// Run is invoked when rb/mrb file is directly passed to the command Run(subcommands::run::RunArgs), + /// Start an interactive REPL (Read-Eval-Print Loop) + Repl(subcommands::repl::ReplArgs), /// Generate WebAssembly binary from Ruby code Wasm(subcommands::wasm::WasmArgs), /// Compile Ruby script to mrb @@ -51,6 +53,9 @@ fn main() -> Result<(), Box> { Some(Commands::Run(args)) => { subcommands::run::execute(args)?; } + Some(Commands::Repl(args)) => { + subcommands::repl::execute(args)?; + } Some(Commands::Wasm(args)) => { subcommands::wasm::execute(args)?; } diff --git a/mrubyedge-cli/src/subcommands/mod.rs b/mrubyedge-cli/src/subcommands/mod.rs index 3abd289..e0e43bf 100644 --- a/mrubyedge-cli/src/subcommands/mod.rs +++ b/mrubyedge-cli/src/subcommands/mod.rs @@ -1,4 +1,5 @@ pub mod compile_mrb; +pub mod repl; pub mod run; pub mod scaffold; pub mod wasm; diff --git a/mrubyedge-cli/src/subcommands/repl.rs b/mrubyedge-cli/src/subcommands/repl.rs new file mode 100644 index 0000000..537c279 --- /dev/null +++ b/mrubyedge-cli/src/subcommands/repl.rs @@ -0,0 +1,122 @@ +use clap::Args; +use std::io::{self, Write}; + +use mruby_compiler2_sys as mrbc; +use mrubyedge::yamrb::helpers::{mrb_call_inspect, mrb_funcall}; + +#[derive(Args)] +pub struct ReplArgs { + /// Show verbose output + #[arg(short = 'v', long)] + pub verbose: bool, +} + +pub fn execute(args: ReplArgs) -> Result<(), Box> { + eprintln!("mruby/edge REPL ({})", mrubyedge::version!()); + eprintln!("Type 'exit' or press Ctrl+D to quit"); + eprintln!("Enter empty line to execute buffered code"); + eprintln!(); + + // Initialize VM with empty rite + let empty_code = ""; + let mrb_bin = unsafe { + let mut ctx = mrbc::MRubyCompiler2Context::new(); + ctx.compile(empty_code)? + }; + let mut rite = mrubyedge::rite::load(&mrb_bin)?; + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + + let stdin = io::stdin(); + let mut line_number = 1; + let mut buffer = String::new(); + + loop { + // Print prompt based on whether we're in multi-line mode + if buffer.is_empty() { + print!("mrb:{}> ", line_number); + } else { + print!("mrb:{}* ", line_number); + } + io::stdout().flush()?; + + // Read line + let mut input = String::new(); + match stdin.read_line(&mut input) { + Ok(0) => { + // EOF (Ctrl+D) + eprintln!(); + break; + } + Ok(_) => { + let trimmed = input.trim(); + + // Check for exit command + if trimmed == "exit" || trimmed == "quit" { + if buffer.is_empty() { + break; + } else { + eprintln!("Warning: discarding buffered code"); + buffer.clear(); + break; + } + } + + // Empty line executes the buffer + if trimmed.is_empty() { + if buffer.is_empty() { + // Empty line with empty buffer, just continue + continue; + } + + // Execute buffered code + unsafe { + let mut ctx = mrbc::MRubyCompiler2Context::new(); + match ctx.compile(&buffer) { + Ok(mrb_bin) => match mrubyedge::rite::load(&mrb_bin) { + Ok(mut new_rite) => match vm.eval_rite(&mut new_rite) { + Ok(result) => match mrb_call_inspect(&mut vm, result) { + Ok(inspect_result) => { + match TryInto::::try_into( + inspect_result.as_ref(), + ) { + Ok(s) => println!(" => {}", s), + Err(_) => println!(" => "), + } + } + Err(_) => println!(" => "), + }, + Err(e) => { + eprintln!("{:?}", e); + vm.exception.take(); + } + }, + Err(e) => { + eprintln!("Failed to load bytecode: {:?}", e); + } + }, + Err(e) => { + eprintln!("Compilation error: {}", e); + } + } + } + + buffer.clear(); + line_number += 1; + } else { + // Add line to buffer + buffer.push_str(&input); + } + } + Err(e) => { + eprintln!("Error reading input: {}", e); + break; + } + } + } + + if args.verbose { + eprintln!("REPL session ended"); + } + + Ok(()) +} From 1a694ee2d2e6f8cf855ab59bf1b8dcf2c7692549 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 23:14:09 +0900 Subject: [PATCH 231/314] Fix format --- mrubyedge-cli/src/subcommands/repl.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mrubyedge-cli/src/subcommands/repl.rs b/mrubyedge-cli/src/subcommands/repl.rs index 537c279..aa0fa6c 100644 --- a/mrubyedge-cli/src/subcommands/repl.rs +++ b/mrubyedge-cli/src/subcommands/repl.rs @@ -33,9 +33,9 @@ pub fn execute(args: ReplArgs) -> Result<(), Box> { loop { // Print prompt based on whether we're in multi-line mode if buffer.is_empty() { - print!("mrb:{}> ", line_number); + print!("repl:{:03}> ", line_number); } else { - print!("mrb:{}* ", line_number); + print!("repl:{:03}* ", line_number); } io::stdout().flush()?; From c83be8faa6af6c5a55dc4212ebad34f86454978d Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 23:15:22 +0900 Subject: [PATCH 232/314] Warning --- mrubyedge-cli/src/subcommands/repl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrubyedge-cli/src/subcommands/repl.rs b/mrubyedge-cli/src/subcommands/repl.rs index aa0fa6c..416bba0 100644 --- a/mrubyedge-cli/src/subcommands/repl.rs +++ b/mrubyedge-cli/src/subcommands/repl.rs @@ -2,7 +2,7 @@ use clap::Args; use std::io::{self, Write}; use mruby_compiler2_sys as mrbc; -use mrubyedge::yamrb::helpers::{mrb_call_inspect, mrb_funcall}; +use mrubyedge::yamrb::helpers::mrb_call_inspect; #[derive(Args)] pub struct ReplArgs { From 8c3cbba5047e8a27e31e45af632d46c94402717d Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 23:42:19 +0900 Subject: [PATCH 233/314] Smooth multi line in repl --- Cargo.lock | 260 +++++++++++++++++++++++++- mrubyedge-cli/Cargo.toml | 1 + mrubyedge-cli/src/subcommands/repl.rs | 218 ++++++++++++++------- 3 files changed, 408 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3759910..4a31e41 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -53,7 +53,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -64,7 +64,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -354,6 +354,31 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crossterm" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" +dependencies = [ + "bitflags", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crunchy" version = "0.2.4" @@ -366,6 +391,16 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -436,7 +471,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys", + "windows-sys 0.61.2", ] [[package]] @@ -501,6 +536,21 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + [[package]] name = "log" version = "0.4.29" @@ -546,6 +596,18 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" +[[package]] +name = "mio" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +dependencies = [ + "libc", + "log", + "wasi", + "windows-sys 0.61.2", +] + [[package]] name = "mruby-compiler2-sys" version = "0.2.2" @@ -608,6 +670,7 @@ version = "1.1.3" dependencies = [ "askama", "clap", + "crossterm", "mruby-compiler2-sys", "mrubyedge 1.1.3", "nom", @@ -652,6 +715,29 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + [[package]] name = "percent-encoding" version = "2.3.2" @@ -799,6 +885,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.12.3" @@ -834,6 +929,19 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.59.0", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -849,6 +957,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "serde" version = "1.0.228" @@ -898,12 +1012,49 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "simple_endian" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d70930bff3e7d43bdedbd86ad3c5ed7b247dcaa761749355915956c50e0b10b" +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + [[package]] name = "strsim" version = "0.11.1" @@ -959,6 +1110,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasip2" version = "1.0.2+wasi-0.2.9" @@ -1023,21 +1180,52 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys", + "windows-sys 0.61.2", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-link" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-sys" version = "0.61.2" @@ -1047,6 +1235,70 @@ dependencies = [ "windows-link", ] +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "wit-bindgen" version = "0.51.0" diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 79cc922..579f06e 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -13,6 +13,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } +crossterm = "0.28" mruby-compiler2-sys = "0.2.2" mrubyedge = { version = "1.1.3", features = [ "default", diff --git a/mrubyedge-cli/src/subcommands/repl.rs b/mrubyedge-cli/src/subcommands/repl.rs index 416bba0..fa24d58 100644 --- a/mrubyedge-cli/src/subcommands/repl.rs +++ b/mrubyedge-cli/src/subcommands/repl.rs @@ -1,4 +1,10 @@ use clap::Args; +use crossterm::{ + cursor, + event::{self, Event, KeyCode, KeyEvent, KeyModifiers}, + execute, + terminal::{self, ClearType}, +}; use std::io::{self, Write}; use mruby_compiler2_sys as mrbc; @@ -13,8 +19,10 @@ pub struct ReplArgs { pub fn execute(args: ReplArgs) -> Result<(), Box> { eprintln!("mruby/edge REPL ({})", mrubyedge::version!()); - eprintln!("Type 'exit' or press Ctrl+D to quit"); - eprintln!("Enter empty line to execute buffered code"); + eprintln!("Type 'exit[↩]' or press Ctrl+D to quit"); + eprintln!( + "Press Enter to execute, Option+Enter(Shift+Enter also supported in iTerm2) for line continuation" + ); eprintln!(); // Initialize VM with empty rite @@ -26,97 +34,173 @@ pub fn execute(args: ReplArgs) -> Result<(), Box> { let mut rite = mrubyedge::rite::load(&mrb_bin)?; let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - let stdin = io::stdin(); + // Enable raw mode + terminal::enable_raw_mode()?; + let mut stdout = io::stdout(); + let mut line_number = 1; let mut buffer = String::new(); + let mut current_line = String::new(); - loop { - // Print prompt based on whether we're in multi-line mode - if buffer.is_empty() { - print!("repl:{:03}> ", line_number); - } else { - print!("repl:{:03}* ", line_number); - } - io::stdout().flush()?; - - // Read line - let mut input = String::new(); - match stdin.read_line(&mut input) { - Ok(0) => { - // EOF (Ctrl+D) - eprintln!(); - break; - } - Ok(_) => { - let trimmed = input.trim(); + // Print initial prompt + print!("repl:{:03}> ", line_number); + stdout.flush()?; - // Check for exit command - if trimmed == "exit" || trimmed == "quit" { - if buffer.is_empty() { + let result = (|| -> Result<(), Box> { + loop { + // Read key event + if let Event::Key(key_event) = event::read()? { + match key_event { + // Ctrl+D - Exit + KeyEvent { + code: KeyCode::Char('d'), + modifiers: KeyModifiers::CONTROL, + .. + } => { + terminal::disable_raw_mode()?; + println!(); break; - } else { - eprintln!("Warning: discarding buffered code"); + } + // Ctrl+C - Clear current line + KeyEvent { + code: KeyCode::Char('c'), + modifiers: KeyModifiers::CONTROL, + .. + } => { + terminal::disable_raw_mode()?; + println!(); + current_line.clear(); buffer.clear(); - break; + line_number += 1; + print!("repl:{:03}> ", line_number); + stdout.flush()?; + terminal::enable_raw_mode()?; } - } - - // Empty line executes the buffer - if trimmed.is_empty() { - if buffer.is_empty() { - // Empty line with empty buffer, just continue - continue; + // Alt+Enter (Option+Enter) - Add line to buffer and continue + KeyEvent { + code: KeyCode::Enter, + modifiers: KeyModifiers::ALT | KeyModifiers::SHIFT, + .. + } => { + terminal::disable_raw_mode()?; + println!(); + buffer.push_str(¤t_line); + buffer.push('\n'); + current_line.clear(); + print!("repl:{:03}* ", line_number); + stdout.flush()?; + terminal::enable_raw_mode()?; } + // Regular Enter - Execute buffer + KeyEvent { + code: KeyCode::Enter, + modifiers: KeyModifiers::NONE, + .. + } => { + terminal::disable_raw_mode()?; + println!(); + + // Add current line to buffer + if !current_line.is_empty() { + buffer.push_str(¤t_line); + buffer.push('\n'); + } + + if buffer.trim().is_empty() { + current_line.clear(); + print!("repl:{:03}> ", line_number); + stdout.flush()?; + terminal::enable_raw_mode()?; + continue; + } + + // Check for exit command + let trimmed = buffer.trim(); + if trimmed == "exit" || trimmed == "quit" { + break; + } - // Execute buffered code - unsafe { - let mut ctx = mrbc::MRubyCompiler2Context::new(); - match ctx.compile(&buffer) { - Ok(mrb_bin) => match mrubyedge::rite::load(&mrb_bin) { - Ok(mut new_rite) => match vm.eval_rite(&mut new_rite) { - Ok(result) => match mrb_call_inspect(&mut vm, result) { - Ok(inspect_result) => { - match TryInto::::try_into( - inspect_result.as_ref(), - ) { - Ok(s) => println!(" => {}", s), - Err(_) => println!(" => "), + // Execute buffered code + unsafe { + let mut ctx = mrbc::MRubyCompiler2Context::new(); + match ctx.compile(&buffer) { + Ok(mrb_bin) => match mrubyedge::rite::load(&mrb_bin) { + Ok(mut new_rite) => match vm.eval_rite(&mut new_rite) { + Ok(result) => match mrb_call_inspect(&mut vm, result) { + Ok(inspect_result) => { + match TryInto::::try_into( + inspect_result.as_ref(), + ) { + Ok(s) => println!(" => {}", s), + Err(_) => println!(" => "), + } } + Err(_) => println!(" => "), + }, + Err(e) => { + eprintln!("{:?}", e); + vm.exception.take(); } - Err(_) => println!(" => "), }, Err(e) => { - eprintln!("{:?}", e); - vm.exception.take(); + eprintln!("Failed to load bytecode: {:?}", e); } }, Err(e) => { - eprintln!("Failed to load bytecode: {:?}", e); + eprintln!("Compilation error: {}", e); } - }, - Err(e) => { - eprintln!("Compilation error: {}", e); } } - } - buffer.clear(); - line_number += 1; - } else { - // Add line to buffer - buffer.push_str(&input); + buffer.clear(); + current_line.clear(); + line_number += 1; + print!("repl:{:03}> ", line_number); + stdout.flush()?; + + terminal::enable_raw_mode()?; + } + // Backspace + KeyEvent { + code: KeyCode::Backspace, + .. + } => { + if !current_line.is_empty() { + current_line.pop(); + execute!( + stdout, + cursor::MoveLeft(1), + terminal::Clear(ClearType::UntilNewLine) + )?; + stdout.flush()?; + } + } + // Regular character input + KeyEvent { + code: KeyCode::Char(c), + modifiers: KeyModifiers::NONE | KeyModifiers::SHIFT, + .. + } => { + current_line.push(c); + print!("{}", c); + stdout.flush()?; + } + _ => { + // Ignore unhandled key events + } } } - Err(e) => { - eprintln!("Error reading input: {}", e); - break; - } } - } + + Ok(()) + })(); + + // Disable raw mode + terminal::disable_raw_mode()?; if args.verbose { eprintln!("REPL session ended"); } - Ok(()) + result } From e3a047f39d321f55d2d3e9acfe164b0ffa4a5b1e Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 23:47:26 +0900 Subject: [PATCH 234/314] Bump version to 1.1.4 --- Cargo.lock | 26 +++++++++++++------------- mrubyedge/Cargo.toml | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4a31e41..1794eed 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -625,7 +625,7 @@ name = "mruby-math" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.3", ] [[package]] @@ -633,7 +633,7 @@ name = "mruby-serde-json" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.3 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.3", "serde", "serde_json", ] @@ -641,6 +641,16 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6846a5b11991886da18451b11b3bebf8540afde422254df856c169ebe0633b89" +dependencies = [ + "plain", + "simple_endian", +] + +[[package]] +name = "mrubyedge" +version = "1.1.4" dependencies = [ "criterion", "fnv", @@ -654,16 +664,6 @@ dependencies = [ "simple_endian", ] -[[package]] -name = "mrubyedge" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6846a5b11991886da18451b11b3bebf8540afde422254df856c169ebe0633b89" -dependencies = [ - "plain", - "simple_endian", -] - [[package]] name = "mrubyedge-cli" version = "1.1.3" @@ -672,7 +672,7 @@ dependencies = [ "clap", "crossterm", "mruby-compiler2-sys", - "mrubyedge 1.1.3", + "mrubyedge 1.1.4", "nom", "rand", "syn", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index c9393ff..bc938fe 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.1.3" +version = "1.1.4" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From 21f8e8fe815307937536dabbd3e1e8cfbe5f6f2f Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Wed, 11 Feb 2026 23:48:42 +0900 Subject: [PATCH 235/314] CLI version 1.1.4 --- Cargo.lock | 39 +++++++++++++++++++++------------------ mrubyedge-cli/Cargo.toml | 6 +++--- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1794eed..2003963 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -249,9 +249,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.57" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6899ea499e3fb9305a65d5ebf6e3d2248c5fab291f300ad0a704fbe142eae31a" +checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" dependencies = [ "clap_builder", "clap_derive", @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.57" +version = "4.5.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b12c8b680195a62a8364d16b8447b01b6c2c8f9aaf68bee653be34d4245e238" +checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" dependencies = [ "anstream", "anstyle", @@ -283,9 +283,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e64b0cc0439b12df2fa678eae89a1c56a529fd067a9115f7827f1fffd22b32" +checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" [[package]] name = "colorchoice" @@ -625,7 +625,7 @@ name = "mruby-math" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.3", + "mrubyedge 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -633,30 +633,33 @@ name = "mruby-serde-json" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.3", + "mrubyedge 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "serde_json", ] [[package]] name = "mrubyedge" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6846a5b11991886da18451b11b3bebf8540afde422254df856c169ebe0633b89" +version = "1.1.4" dependencies = [ + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", + "rand_core 0.10.0", + "rand_xorshift", + "regex", "simple_endian", ] [[package]] name = "mrubyedge" version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "654a26dffb9457e3e26efdaeef3ebe7f259241d6d2f80b98377d9d4efa96046b" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -666,13 +669,13 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.1.3" +version = "1.1.4" dependencies = [ "askama", "clap", "crossterm", "mruby-compiler2-sys", - "mrubyedge 1.1.4", + "mrubyedge 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 579f06e..2b7d3ed 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.1.3" +version = "1.1.4" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -15,11 +15,11 @@ path = "src/main.rs" clap = { version = "4.5", features = ["derive"] } crossterm = "0.28" mruby-compiler2-sys = "0.2.2" -mrubyedge = { version = "1.1.3", features = [ +mrubyedge = { version = "1.1.4", features = [ "default", "mruby-random", "mruby-regexp", -], path = "../mrubyedge" } +] } rand = "0.9.2" nom = "7.1.3" askama = "0.12.1" From 9b9fb0bb6ffefe1103175bc17a457ac8831652d6 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 12 Feb 2026 23:30:46 +0900 Subject: [PATCH 236/314] Basic lv parser --- mrubyedge/examples/yield.rb | 12 +++++ mrubyedge/src/rite/rite.rs | 87 ++++++++++++++++++++++++++++++-- mrubyedge/tests/rite.rs | 5 ++ mrubyedge/tests/rite/rite.rs | 96 ++++++++++++++++++++++++++++++++++++ 4 files changed, 196 insertions(+), 4 deletions(-) create mode 100644 mrubyedge/examples/yield.rb create mode 100644 mrubyedge/tests/rite.rs create mode 100644 mrubyedge/tests/rite/rite.rs diff --git a/mrubyedge/examples/yield.rb b/mrubyedge/examples/yield.rb new file mode 100644 index 0000000..98a4091 --- /dev/null +++ b/mrubyedge/examples/yield.rb @@ -0,0 +1,12 @@ +def got_block + yield 1 + #b.call + yield 2 + #b.call 2 +end + +got_block + +# got_block do |x| +# puts "Got block with #{x}" +# end \ No newline at end of file diff --git a/mrubyedge/src/rite/rite.rs b/mrubyedge/src/rite/rite.rs index 393d6a2..7453781 100644 --- a/mrubyedge/src/rite/rite.rs +++ b/mrubyedge/src/rite/rite.rs @@ -38,6 +38,7 @@ pub struct Irep<'a> { pub slen: usize, pub syms: Vec, pub catch_handlers: Vec, + pub lv: Vec>, // Local variable names (indices into LVar::syms) } impl Irep<'_> { @@ -69,8 +70,11 @@ pub struct CatchHandler { #[derive(Debug)] pub struct LVar { pub header: SectionMiscHeader, + pub syms: Vec, } +const RITE_LV_NULL_MARK: u16 = 0xFFFF; + pub fn load<'a>(src: &'a [u8]) -> Result, Error> { let mut rite = Rite::default(); @@ -103,7 +107,7 @@ pub fn load<'a>(src: &'a [u8]) -> Result, Error> { head = &head[cur..]; } LVAR => { - let (cur, lvar) = section_lvar(head)?; + let (cur, lvar) = section_lvar(head, &mut rite.irep)?; rite.lvar = Some(lvar); head = &head[cur..]; } @@ -273,6 +277,7 @@ pub fn section_irep_1(head: &[u8]) -> Result<(usize, SectionIrepHeader, Vec Result { Ok(be32_to_u32(header.size) as usize) } -pub fn section_lvar(head: &[u8]) -> Result<(usize, LVar), Error> { - let header = SectionMiscHeader::from_bytes(head)?; - let lvar = LVar { header }; +pub fn section_lvar(head: &[u8], ireps: &mut [Irep]) -> Result<(usize, LVar), Error> { + let mut cur = 0; + let header_size = mem::size_of::(); + let header = SectionMiscHeader::from_bytes(&head[cur..cur + header_size])?; + cur += header_size; + + // Read syms_len (4 bytes) + let syms_len = be32_to_u32([head[cur], head[cur + 1], head[cur + 2], head[cur + 3]]) as usize; + cur += 4; + + // Read symbols + let mut syms = Vec::new(); + for _ in 0..syms_len { + let str_len = be16_to_u16([head[cur], head[cur + 1]]) as usize; + cur += 2; + + // Read string bytes (NOT null-terminated in the binary) + let str_bytes = &head[cur..cur + str_len]; + let c_str = CString::new(str_bytes).map_err(|_| Error::InvalidFormat)?; + syms.push(c_str); + cur += str_len; + } + + // Read lv records recursively + let _ = read_lv_records(&head[cur..], ireps, 0, &syms, syms_len)?; + + let lvar = LVar { header, syms }; Ok((be32_to_u32(lvar.header.size) as usize, lvar)) } +fn read_lv_records( + head: &[u8], + ireps: &mut [Irep], + irep_idx: usize, + syms: &[CString], + syms_len: usize, +) -> Result<(usize, usize), Error> { + if irep_idx >= ireps.len() { + return Ok((0, irep_idx)); + } + + let mut cur = 0; + let nlocals = ireps[irep_idx].nlocals(); + let rlen = ireps[irep_idx].rlen(); + + if nlocals == 0 { + return Err(Error::InvalidFormat); + } + + // Read local variable names for this irep (nlocals - 1 entries) + let mut lv = Vec::new(); + for _ in 0..(nlocals - 1) { + let sym_idx = be16_to_u16([head[cur], head[cur + 1]]); + cur += 2; + + if sym_idx == RITE_LV_NULL_MARK { + lv.push(None); + } else { + let sym_idx = sym_idx as usize; + if sym_idx >= syms_len { + return Err(Error::InvalidFormat); + } + lv.push(Some(sym_idx)); + } + } + + ireps[irep_idx].lv = lv; + + // Recursively read child ireps + let mut child_irep_idx = irep_idx + 1; + for _ in 0..rlen { + let (bytes_read, next_irep_idx) = + read_lv_records(&head[cur..], ireps, child_irep_idx, syms, syms_len)?; + cur += bytes_read; + child_irep_idx = next_irep_idx; + } + + Ok((cur, child_irep_idx)) +} + pub fn section_skip(head: &[u8]) -> Result { let header = SectionMiscHeader::from_bytes(head)?; // eprintln!("skipped section {:?}", header.ident.as_ascii()); diff --git a/mrubyedge/tests/rite.rs b/mrubyedge/tests/rite.rs new file mode 100644 index 0000000..7f9af30 --- /dev/null +++ b/mrubyedge/tests/rite.rs @@ -0,0 +1,5 @@ +#[path = "helpers/mod.rs"] +mod helpers; + +#[path = "rite/rite.rs"] +mod rite; diff --git a/mrubyedge/tests/rite/rite.rs b/mrubyedge/tests/rite/rite.rs new file mode 100644 index 0000000..d49b18d --- /dev/null +++ b/mrubyedge/tests/rite/rite.rs @@ -0,0 +1,96 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +use super::helpers::*; + +#[test] +fn test_rite_parse_hello_world() { + let code = r#" + puts "Hello, World!" + "#; + let binary = mrbc_compile("hello", code); + let rite = mrubyedge::rite::load(&binary).unwrap(); + + // Check binary header + assert_eq!(&rite.binary_header.ident, b"RITE"); + assert_eq!(&rite.binary_header.major_version, b"03"); + assert_eq!(&rite.binary_header.minor_version, b"00"); + + // Check IREP section + assert_eq!(rite.irep.len(), 1); + let irep = &rite.irep[0]; + + // Should have at least some instructions + assert!(!irep.insn.is_empty()); + + // Should have some pool values (for the string) + assert!(irep.plen > 0); +} + +#[test] +fn test_rite_parse_with_local_variables() { + let code = r#" + def greet(name) + message = "Hello, #{name}!" + puts message + message + end + + greet("Alice") + "#; + let binary = mrbc_compile("greet", code); + let rite = mrubyedge::rite::load(&binary).unwrap(); + + // Check binary header + assert_eq!(&rite.binary_header.ident, b"RITE"); + + // Should have multiple ireps (main + function definition) + assert!(rite.irep.len() == 2); + assert!(rite.irep[0].lv.is_empty()); + assert!(rite.irep[1].lv.len() == 3); // name, (&nil), message + + // Check LVAR section exists + let lvar = rite.lvar.unwrap(); + assert!(lvar.syms.len() == 2); + + let name = rite.irep[1].lv[0].unwrap(); + assert_eq!(lvar.syms[name].to_str().unwrap(), "name"); + + let message = rite.irep[1].lv[2].unwrap(); + assert_eq!(lvar.syms[message].to_str().unwrap(), "message"); +} + +#[test] +fn test_rite_parse_pool_values() { + let code = r#" + a = "string" + b = 42 + c = 3.14 + d = 9999999999 + [a, b, c, d] + "#; + let binary = mrbc_compile("pool", code); + let rite = mrubyedge::rite::load(&binary).unwrap(); + + assert_eq!(&rite.binary_header.ident, b"RITE"); + + let irep = &rite.irep[0]; + assert!(rite.lvar.unwrap().syms.len() == 4); + + // Should have multiple pool values + assert!(irep.pool.len() == 3); + assert!(irep.lv.len() == 4); + + // Check pool value types + use mrubyedge::rite::PoolValue; + let has_string = irep + .pool + .iter() + .any(|p| matches!(p, PoolValue::Str(_) | PoolValue::SStr(_))); + let has_float = irep.pool.iter().any(|p| matches!(p, PoolValue::Float(_))); + let has_int64 = irep.pool.iter().any(|p| matches!(p, PoolValue::Int64(_))); + + assert!(has_string, "Should have string in pool"); + assert!(has_float, "Should have float in pool"); + assert!(has_int64, "Should have int64 in pool"); +} From f89d71feab9c6c0b10eb22663a4e3e1523df5cee Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 12 Feb 2026 23:31:12 +0900 Subject: [PATCH 237/314] clipppy --- mrubyedge/src/yamrb/value.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 2c7190c..982c769 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -997,9 +997,8 @@ impl RClass { // find_method will search method from self to superclass pub fn find_method(&self, name: &str) -> Option { // First check this class's module - match self.module.find_method(name) { - Some(p) => return Some(p), - None => {} + if let Some(p) = self.module.find_method(name) { + return Some(p); } // For singleton classes, check extended modules From dfabe1b5db6096c3171c7dd2a58f2301132e8035 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 12 Feb 2026 23:38:40 +0900 Subject: [PATCH 238/314] Set string directly --- mrubyedge/src/rite/rite.rs | 4 ++-- mrubyedge/tests/rite/rite.rs | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mrubyedge/src/rite/rite.rs b/mrubyedge/src/rite/rite.rs index 7453781..b6e6150 100644 --- a/mrubyedge/src/rite/rite.rs +++ b/mrubyedge/src/rite/rite.rs @@ -38,7 +38,7 @@ pub struct Irep<'a> { pub slen: usize, pub syms: Vec, pub catch_handlers: Vec, - pub lv: Vec>, // Local variable names (indices into LVar::syms) + pub lv: Vec>, // Local variable names (indices into LVar::syms) } impl Irep<'_> { @@ -353,7 +353,7 @@ fn read_lv_records( if sym_idx >= syms_len { return Err(Error::InvalidFormat); } - lv.push(Some(sym_idx)); + lv.push(Some(syms[sym_idx].clone())); } } diff --git a/mrubyedge/tests/rite/rite.rs b/mrubyedge/tests/rite/rite.rs index d49b18d..6b8e200 100644 --- a/mrubyedge/tests/rite/rite.rs +++ b/mrubyedge/tests/rite/rite.rs @@ -53,11 +53,11 @@ fn test_rite_parse_with_local_variables() { let lvar = rite.lvar.unwrap(); assert!(lvar.syms.len() == 2); - let name = rite.irep[1].lv[0].unwrap(); - assert_eq!(lvar.syms[name].to_str().unwrap(), "name"); + let name = rite.irep[1].lv[0].as_ref().cloned().unwrap(); + assert_eq!(&name.to_string_lossy(), "name"); - let message = rite.irep[1].lv[2].unwrap(); - assert_eq!(lvar.syms[message].to_str().unwrap(), "message"); + let message = rite.irep[1].lv[2].as_ref().cloned().unwrap(); + assert_eq!(&message.to_string_lossy(), "message"); } #[test] From 86bb1d0b4c26b929b56d313aaab6347b6a7296d1 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 12 Feb 2026 23:48:29 +0900 Subject: [PATCH 239/314] Set lv to working IREP --- mrubyedge/examples/newvm-2.rs | 2 ++ mrubyedge/examples/newvm-3.rs | 2 ++ mrubyedge/examples/newvm.rs | 1 + mrubyedge/src/lib.rs | 1 + mrubyedge/src/yamrb/vm.rs | 12 ++++++++++++ 5 files changed, 18 insertions(+) diff --git a/mrubyedge/examples/newvm-2.rs b/mrubyedge/examples/newvm-2.rs index 2b3ca38..c20aef5 100644 --- a/mrubyedge/examples/newvm-2.rs +++ b/mrubyedge/examples/newvm-2.rs @@ -60,6 +60,7 @@ fn main() { syms: Vec::new(), pool: Vec::new(), reps: Vec::new(), + lv: None, catch_target_pos: Vec::new(), }; @@ -149,6 +150,7 @@ fn main() { syms: vec![value::RSym::new("do_add".to_string())], pool: Vec::new(), reps: vec![Rc::new(irep1)], + lv: None, catch_target_pos: Vec::new(), }; let mut vm = vm::VM::new_by_raw_irep(irep0); diff --git a/mrubyedge/examples/newvm-3.rs b/mrubyedge/examples/newvm-3.rs index 21b0bfe..42b62ab 100644 --- a/mrubyedge/examples/newvm-3.rs +++ b/mrubyedge/examples/newvm-3.rs @@ -193,6 +193,7 @@ fn main() { syms: vec![value::RSym::new("fib".to_string())], pool: Vec::new(), reps: Vec::new(), + lv: None, catch_target_pos: Vec::new(), }; @@ -259,6 +260,7 @@ fn main() { syms: vec![value::RSym::new("fib".to_string())], pool: Vec::new(), reps: vec![Rc::new(irep1)], + lv: None, catch_target_pos: Vec::new(), }; let mut vm = vm::VM::new_by_raw_irep(irep0); diff --git a/mrubyedge/examples/newvm.rs b/mrubyedge/examples/newvm.rs index fe15305..4f6f34e 100644 --- a/mrubyedge/examples/newvm.rs +++ b/mrubyedge/examples/newvm.rs @@ -76,6 +76,7 @@ fn main() { syms: vec![RSym::new("puts".to_string())], pool: Vec::new(), reps: Vec::new(), + lv: None, catch_target_pos: Vec::new(), }; let mut vm = vm::VM::new_by_raw_irep(irep); diff --git a/mrubyedge/src/lib.rs b/mrubyedge/src/lib.rs index 4d82896..b8e8343 100644 --- a/mrubyedge/src/lib.rs +++ b/mrubyedge/src/lib.rs @@ -36,6 +36,7 @@ //! syms: vec![RSym::new("puts".to_string())], //! pool: Vec::new(), //! reps: Vec::new(), +//! lv: None, //! catch_target_pos: Vec::new(), //! }; //! diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index a6096b5..d354900 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -223,6 +223,7 @@ impl VM { syms: Vec::new(), pool: Vec::new(), reps: Vec::new(), + lv: None, catch_target_pos: Vec::new(), }; Self::new_by_raw_irep(irep) @@ -750,6 +751,7 @@ fn load_irep_1(reps: &mut [Irep], pos: usize) -> (IREP, usize) { syms: Vec::new(), pool: Vec::new(), reps: Vec::new(), + lv: None, catch_target_pos: Vec::new(), }; for sym in irep.syms.iter() { @@ -787,6 +789,15 @@ fn load_irep_1(reps: &mut [Irep], pos: usize) -> (IREP, usize) { .expect("catch handler mismatch"); irep1.catch_target_pos.push(i); } + let mut map = RHashMap::default(); + for (reg, name) in irep.lv.iter().enumerate() { + if let Some(name) = name { + map.insert(reg, name.to_string_lossy().to_string()); + } + } + if !map.is_empty() { + irep1.lv = Some(map); + } irep1.catch_target_pos.sort(); irep1.code = code; @@ -821,6 +832,7 @@ pub struct IREP { pub syms: Vec, pub pool: Vec, pub reps: Vec>, + pub lv: Option>, pub catch_target_pos: Vec, } From 9897014c41d428d849fc162f29bab0c81ecaa1da Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 12 Feb 2026 23:50:17 +0900 Subject: [PATCH 240/314] Start with 1 --- mrubyedge/src/yamrb/vm.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index d354900..156100b 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -792,7 +792,8 @@ fn load_irep_1(reps: &mut [Irep], pos: usize) -> (IREP, usize) { let mut map = RHashMap::default(); for (reg, name) in irep.lv.iter().enumerate() { if let Some(name) = name { - map.insert(reg, name.to_string_lossy().to_string()); + // lv register index in mruby is 1-based + map.insert(reg + 1, name.to_string_lossy().to_string()); } } if !map.is_empty() { From 133b02160584275bc8a9f468a2bcfd5bb2978d35 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Fri, 13 Feb 2026 00:27:36 +0900 Subject: [PATCH 241/314] WIP code --- Cargo.lock | 5 +- mrubyedge-cli/Cargo.toml | 2 +- mrubyedge-cli/src/subcommands/repl.rs | 75 +++++++++++++++++++++------ 3 files changed, 61 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2003963..f239ae8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -661,9 +661,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "654a26dffb9457e3e26efdaeef3ebe7f259241d6d2f80b98377d9d4efa96046b" dependencies = [ "plain", - "rand_core 0.10.0", - "rand_xorshift", - "regex", "simple_endian", ] @@ -675,7 +672,7 @@ dependencies = [ "clap", "crossterm", "mruby-compiler2-sys", - "mrubyedge 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.4", "nom", "rand", "syn", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 2b7d3ed..3c1fe89 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -19,7 +19,7 @@ mrubyedge = { version = "1.1.4", features = [ "default", "mruby-random", "mruby-regexp", -] } +], path = "../mrubyedge" } rand = "0.9.2" nom = "7.1.3" askama = "0.12.1" diff --git a/mrubyedge-cli/src/subcommands/repl.rs b/mrubyedge-cli/src/subcommands/repl.rs index fa24d58..3d33b5f 100644 --- a/mrubyedge-cli/src/subcommands/repl.rs +++ b/mrubyedge-cli/src/subcommands/repl.rs @@ -5,10 +5,16 @@ use crossterm::{ execute, terminal::{self, ClearType}, }; -use std::io::{self, Write}; +use std::{ + io::{self, Write}, + rc::Rc, +}; use mruby_compiler2_sys as mrbc; -use mrubyedge::yamrb::helpers::mrb_call_inspect; +use mrubyedge::{ + RObject, + yamrb::{helpers::mrb_call_inspect, value::RHashMap}, +}; #[derive(Args)] pub struct ReplArgs { @@ -45,6 +51,7 @@ pub fn execute(args: ReplArgs) -> Result<(), Box> { // Print initial prompt print!("repl:{:03}> ", line_number); stdout.flush()?; + let mut top_level_lvars: RHashMap> = RHashMap::default(); let result = (|| -> Result<(), Box> { loop { @@ -123,25 +130,61 @@ pub fn execute(args: ReplArgs) -> Result<(), Box> { // Execute buffered code unsafe { let mut ctx = mrbc::MRubyCompiler2Context::new(); + if args.verbose { + ctx.dump_bytecode(&buffer).unwrap(); + } match ctx.compile(&buffer) { Ok(mrb_bin) => match mrubyedge::rite::load(&mrb_bin) { - Ok(mut new_rite) => match vm.eval_rite(&mut new_rite) { - Ok(result) => match mrb_call_inspect(&mut vm, result) { - Ok(inspect_result) => { - match TryInto::::try_into( - inspect_result.as_ref(), - ) { - Ok(s) => println!(" => {}", s), - Err(_) => println!(" => "), + Ok(mut new_rite) => { + // FIXME: sub ireps's lv not handled yet + let top_rep = &new_rite.irep[0]; + for (reg, name) in top_rep.lv.iter().enumerate() { + if let Some(name) = name + && let Some(value) = top_level_lvars + .get(&name.to_string_lossy().to_string()) + { + vm.regs[reg + 1] = value.clone().into(); + } + } + match vm.eval_rite(&mut new_rite) { + Ok(result) => match mrb_call_inspect(&mut vm, result) { + Ok(inspect_result) => { + match TryInto::::try_into( + inspect_result.as_ref(), + ) { + Ok(s) => println!(" => {}", s), + Err(_) => println!(" => "), + } } + Err(_) => println!(" => "), + }, + Err(e) => { + eprintln!("{:?}", e); + vm.exception.take(); } - Err(_) => println!(" => "), - }, - Err(e) => { - eprintln!("{:?}", e); - vm.exception.take(); } - }, + if let Some(lv) = &vm.current_irep.lv { + for (reg, name) in lv.iter() { + let value = + vm.regs[*reg].as_ref().cloned().unwrap_or( + RObject::nil().to_refcount_assigned(), + ); + top_level_lvars + .insert(name.to_string(), value.clone()); + } + for (k, v) in top_level_lvars.iter() { + let inspect: String = + mrb_call_inspect(&mut vm, v.clone()) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + if args.verbose { + eprintln!(" [lv] {} => {}", k, inspect); + } + } + } + } Err(e) => { eprintln!("Failed to load bytecode: {:?}", e); } From 3618bf893d9c3900a76186466ee33ec701ff5e17 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Fri, 13 Feb 2026 22:09:54 +0900 Subject: [PATCH 242/314] Do not remove returning register in op_return --- mrubyedge/src/yamrb/optable.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index cdb3287..b22d9ad 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1344,7 +1344,7 @@ pub(crate) fn op_return(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { } let regs0 = vm.current_regs(); - if let Some(regs_a) = regs0[a].take() { + if let Some(regs_a) = regs0[a].clone() { regs0[0].replace(regs_a); } // TODO: inspect if this is needed From 53ef2238db33a213f737e95f8a1b7d27b68a6aac Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Fri, 13 Feb 2026 22:34:55 +0900 Subject: [PATCH 243/314] Fix --- Cargo.lock | 20 ++++++++++++++++---- mrubyedge-cli/Cargo.toml | 2 +- mrubyedge-cli/src/subcommands/repl.rs | 8 +++++--- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f239ae8..8b0b078 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -516,9 +516,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.181" +version = "0.2.182" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "459427e2af2b9c839b132acb702a1c654d95e10f8c326bfc2ad11310e458b1c5" +checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" [[package]] name = "libloading" @@ -620,6 +620,18 @@ dependencies = [ "libc", ] +[[package]] +name = "mruby-compiler2-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e57b6bea7a439d4a48c5e2d680104b99855404caa0e24a30febe5bbee176f25" +dependencies = [ + "bindgen 0.72.1", + "cc", + "glob", + "libc", +] + [[package]] name = "mruby-math" version = "0.1.0" @@ -645,7 +657,7 @@ dependencies = [ "criterion", "fnv", "mec-mrbc-sys", - "mruby-compiler2-sys", + "mruby-compiler2-sys 0.2.2", "once_cell", "plain", "rand_core 0.10.0", @@ -671,7 +683,7 @@ dependencies = [ "askama", "clap", "crossterm", - "mruby-compiler2-sys", + "mruby-compiler2-sys 0.3.0", "mrubyedge 1.1.4", "nom", "rand", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 3c1fe89..39fd03b 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -14,7 +14,7 @@ path = "src/main.rs" [dependencies] clap = { version = "4.5", features = ["derive"] } crossterm = "0.28" -mruby-compiler2-sys = "0.2.2" +mruby-compiler2-sys = "0.3.0" mrubyedge = { version = "1.1.4", features = [ "default", "mruby-random", diff --git a/mrubyedge-cli/src/subcommands/repl.rs b/mrubyedge-cli/src/subcommands/repl.rs index 3d33b5f..0270679 100644 --- a/mrubyedge-cli/src/subcommands/repl.rs +++ b/mrubyedge-cli/src/subcommands/repl.rs @@ -2,7 +2,7 @@ use clap::Args; use crossterm::{ cursor, event::{self, Event, KeyCode, KeyEvent, KeyModifiers}, - execute, + execute as cs_execute, terminal::{self, ClearType}, }; use std::{ @@ -53,6 +53,8 @@ pub fn execute(args: ReplArgs) -> Result<(), Box> { stdout.flush()?; let mut top_level_lvars: RHashMap> = RHashMap::default(); + let mut ctx = unsafe { mrbc::MRubyCompiler2Context::new() }; + let result = (|| -> Result<(), Box> { loop { // Read key event @@ -129,7 +131,6 @@ pub fn execute(args: ReplArgs) -> Result<(), Box> { // Execute buffered code unsafe { - let mut ctx = mrbc::MRubyCompiler2Context::new(); if args.verbose { ctx.dump_bytecode(&buffer).unwrap(); } @@ -163,6 +164,7 @@ pub fn execute(args: ReplArgs) -> Result<(), Box> { vm.exception.take(); } } + // Display top-level local variables if let Some(lv) = &vm.current_irep.lv { for (reg, name) in lv.iter() { let value = @@ -210,7 +212,7 @@ pub fn execute(args: ReplArgs) -> Result<(), Box> { } => { if !current_line.is_empty() { current_line.pop(); - execute!( + cs_execute!( stdout, cursor::MoveLeft(1), terminal::Clear(ClearType::UntilNewLine) From fa0174eacea112fc7be3f0e0243fae3a1c098954 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Fri, 13 Feb 2026 22:36:34 +0900 Subject: [PATCH 244/314] Bump core --- Cargo.lock | 42 +++++++++++++++--------------------------- mrubyedge/Cargo.toml | 4 ++-- 2 files changed, 17 insertions(+), 29 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8b0b078..5d5a36c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -608,18 +608,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "mruby-compiler2-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "feba2d9bebead1e86da7c36f229bd92f7a81e193dab29d982815a2a776811f19" -dependencies = [ - "bindgen 0.72.1", - "cc", - "glob", - "libc", -] - [[package]] name = "mruby-compiler2-sys" version = "0.3.0" @@ -637,7 +625,7 @@ name = "mruby-math" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.4", ] [[package]] @@ -645,7 +633,7 @@ name = "mruby-serde-json" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.4", "serde", "serde_json", ] @@ -653,26 +641,26 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "654a26dffb9457e3e26efdaeef3ebe7f259241d6d2f80b98377d9d4efa96046b" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys 0.2.2", - "once_cell", "plain", - "rand_core 0.10.0", - "rand_xorshift", - "regex", "simple_endian", ] [[package]] name = "mrubyedge" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "654a26dffb9457e3e26efdaeef3ebe7f259241d6d2f80b98377d9d4efa96046b" +version = "1.1.5" dependencies = [ + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", + "rand_core 0.10.0", + "rand_xorshift", + "regex", "simple_endian", ] @@ -683,8 +671,8 @@ dependencies = [ "askama", "clap", "crossterm", - "mruby-compiler2-sys 0.3.0", - "mrubyedge 1.1.4", + "mruby-compiler2-sys", + "mrubyedge 1.1.5", "nom", "rand", "syn", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index bc938fe..d67a834 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.1.4" +version = "1.1.5" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" @@ -23,7 +23,7 @@ criterion = "0.5.1" mec-mrbc-sys = "3.3.1" fnv = "1.0.7" once_cell = "1.20.2" -mruby-compiler2-sys = "0.2.2" +mruby-compiler2-sys = "0.3.0" [[bench]] name = "benchmark" From 314dda8d8f9fa7c4638d8eacc293713c818da663 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Fri, 13 Feb 2026 22:39:27 +0900 Subject: [PATCH 245/314] Bupm 2 --- Cargo.lock | 37 ++++++++++++++++++++----------------- mrubyedge-cli/Cargo.toml | 4 ++-- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5d5a36c..2e8a212 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -186,9 +186,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.55" +version = "1.2.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "47b26a0954ae34af09b50f0de26458fa95369a0d478d8236d3f93082b219bd29" +checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" dependencies = [ "find-msvc-tools", "shlex", @@ -625,7 +625,7 @@ name = "mruby-math" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.4", + "mrubyedge 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -633,30 +633,33 @@ name = "mruby-serde-json" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.4", + "mrubyedge 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "serde_json", ] [[package]] name = "mrubyedge" -version = "1.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "654a26dffb9457e3e26efdaeef3ebe7f259241d6d2f80b98377d9d4efa96046b" +version = "1.1.5" dependencies = [ + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", + "rand_core 0.10.0", + "rand_xorshift", + "regex", "simple_endian", ] [[package]] name = "mrubyedge" version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12abf12304eecad6d7ae2806ff47b22ae915a6ce8d490555b44b86d78596382" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -672,7 +675,7 @@ dependencies = [ "clap", "crossterm", "mruby-compiler2-sys", - "mrubyedge 1.1.5", + "mrubyedge 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", @@ -1063,9 +1066,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.114" +version = "2.0.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4d107df263a3013ef9b1879b0df87d706ff80f65a86ea879bd9c31f9b307c2a" +checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" dependencies = [ "proc-macro2", "quote", @@ -1327,6 +1330,6 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4de98dfa5d5b7fef4ee834d0073d560c9ca7b6c46a71d058c48db7960f8cfaf7" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 39fd03b..9dd9b5c 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -15,11 +15,11 @@ path = "src/main.rs" clap = { version = "4.5", features = ["derive"] } crossterm = "0.28" mruby-compiler2-sys = "0.3.0" -mrubyedge = { version = "1.1.4", features = [ +mrubyedge = { version = "1.1.5", features = [ "default", "mruby-random", "mruby-regexp", -], path = "../mrubyedge" } +] } rand = "0.9.2" nom = "7.1.3" askama = "0.12.1" From d6a43ae0e747f99d772df8713a306dc8d6419ef3 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Fri, 13 Feb 2026 22:44:54 +0900 Subject: [PATCH 246/314] Bump self version... --- Cargo.lock | 2 +- mrubyedge-cli/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2e8a212..17d9ef3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -669,7 +669,7 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.1.4" +version = "1.1.5" dependencies = [ "askama", "clap", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 9dd9b5c..027e44d 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.1.4" +version = "1.1.5" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." From 333a3598edfafc6c19563c180a6631cc84aca59c Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 16 Feb 2026 20:44:52 +0900 Subject: [PATCH 247/314] Assert range is enumerable --- mrubyedge/src/yamrb/prelude/range.rs | 4 ++ mrubyedge/tests/range.rs | 75 ++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 mrubyedge/tests/range.rs diff --git a/mrubyedge/src/yamrb/prelude/range.rs b/mrubyedge/src/yamrb/prelude/range.rs index 12eb5b7..3c7fdbc 100644 --- a/mrubyedge/src/yamrb/prelude/range.rs +++ b/mrubyedge/src/yamrb/prelude/range.rs @@ -4,6 +4,7 @@ use crate::{ Error, yamrb::{ helpers::{mrb_call_block, mrb_define_cmethod}, + prelude::module::mrb_include_module, value::{RObject, RValue}, vm::VM, }, @@ -19,6 +20,9 @@ pub(crate) fn initialize_range(vm: &mut VM) { Box::new(mrb_range_is_include), ); mrb_define_cmethod(vm, range_class.clone(), "each", Box::new(mrb_range_each)); + + let enumerable_module = vm.get_module_by_name("Enumerable"); + mrb_include_module(&range_class, enumerable_module).expect("failed to include Enumerable"); } pub fn mrb_range_is_include(vm: &mut VM, args: &[Rc]) -> Result, Error> { diff --git a/mrubyedge/tests/range.rs b/mrubyedge/tests/range.rs new file mode 100644 index 0000000..5560340 --- /dev/null +++ b/mrubyedge/tests/range.rs @@ -0,0 +1,75 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn range_inclusive_each_test() { + let code = r#" + def test_range_each + sum = 0 + (0..10).each do |i| + sum += i + end + sum + end + "#; + let binary = mrbc_compile("range_inclusive_each", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_range_each", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 55); +} + +#[test] +fn range_exclusive_each_test() { + let code = r#" + def test_range_each + sum = 0 + (0...10).each do |i| + sum += i + end + sum + end + "#; + let binary = mrbc_compile("range_exclusive_each", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_range_each", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 45); +} + +#[test] +fn range_map_test() { + let code = r#" + def test_range_map + (1..3).map do |i| + i * 2 + end + end + "#; + let binary = mrbc_compile("range_map", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_range_map", &args).unwrap(); + let result: (i32, i32, i32) = result.as_ref().try_into().unwrap(); + assert_eq!(result, (2, 4, 6)); +} From 4937921231945237d0ef8dfa3e9e195c9a764621 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 16 Feb 2026 20:58:26 +0900 Subject: [PATCH 248/314] Fix basic numeric methods --- mrubyedge/src/yamrb/prelude/enumerable.rs | 49 +++++++++++++++++ mrubyedge/src/yamrb/prelude/float.rs | 54 +++++++++++++++++++ mrubyedge/src/yamrb/prelude/integer.rs | 24 +++++++++ mrubyedge/tests/enumerable.rs | 36 +++++++++++++ mrubyedge/tests/float.rs | 60 +++++++++++++++++++++ mrubyedge/tests/integer.rs | 60 +++++++++++++++++++++ mrubyedge/tests/range.rs | 66 +++++++++++++++++++++++ 7 files changed, 349 insertions(+) diff --git a/mrubyedge/src/yamrb/prelude/enumerable.rs b/mrubyedge/src/yamrb/prelude/enumerable.rs index d34d802..7fb5c03 100644 --- a/mrubyedge/src/yamrb/prelude/enumerable.rs +++ b/mrubyedge/src/yamrb/prelude/enumerable.rs @@ -114,6 +114,12 @@ pub(crate) fn initialize_enumerable(vm: &mut VM) { "reduce", Box::new(mrb_enumerable_reduce), ); + mrb_define_module_cmethod( + vm, + enumerable_module.clone(), + "sum", + Box::new(mrb_enumerable_sum), + ); } fn rproc_from_rust_block(vm: &mut VM, rfn: RFn) -> Result, Error> { @@ -660,3 +666,46 @@ fn mrb_enumerable_reduce(vm: &mut VM, args: &[Rc]) -> Result]) -> Result, Error> { + // Check if we have an initial value + let initial_value = if args.is_empty() || args[0].is_nil() { + // Default initial value is 0 + Rc::new(RObject::integer(0)) + } else { + // Initial value provided: sum(init) + args[0].clone() + }; + + let accumulator: Rc = RObject::array(vec![initial_value]).to_refcount_assigned(); + let acc_ref = accumulator.clone(); + + let wrapping_block: RFn = Box::new(move |vm: &mut VM, args: &[Rc]| { + let current_elem = args[0].clone(); + let acc_array: Vec> = acc_ref.as_ref().try_into()?; + let current_acc = acc_array[0].clone(); + + // Call + operator on accumulator with current element + let result = mrb_funcall(vm, Some(current_acc), "+", &[current_elem])?; + + // Update accumulator + mrb_funcall(vm, Some(acc_ref.clone()), "pop", &[])?; + mrb_funcall( + vm, + Some(acc_ref.clone()), + "push", + std::slice::from_ref(&result), + )?; + Ok(Rc::new(RObject::nil())) + }); + + let this = vm.getself()?; + let block = rproc_from_rust_block(vm, wrapping_block)?; + mrb_funcall(vm, Some(this.clone()), "each", &[block])?; + vm.pop_fnblock()?; + + // Return the final accumulator value + let result = mrb_funcall(vm, Some(accumulator), "first", &[])?; + Ok(result) +} diff --git a/mrubyedge/src/yamrb/prelude/float.rs b/mrubyedge/src/yamrb/prelude/float.rs index 0b94892..616cb34 100644 --- a/mrubyedge/src/yamrb/prelude/float.rs +++ b/mrubyedge/src/yamrb/prelude/float.rs @@ -9,6 +9,8 @@ pub(crate) fn initialize_float(vm: &mut VM) { let float_class = vm.define_standard_class("Float"); mrb_define_cmethod(vm, float_class.clone(), "to_i", Box::new(mrb_float_to_i)); mrb_define_cmethod(vm, float_class.clone(), "to_f", Box::new(mrb_float_to_f)); + mrb_define_cmethod(vm, float_class.clone(), "+", Box::new(mrb_float_add)); + mrb_define_cmethod(vm, float_class.clone(), "-", Box::new(mrb_float_sub)); mrb_define_cmethod(vm, float_class.clone(), "*", Box::new(mrb_float_mul)); mrb_define_cmethod(vm, float_class.clone(), "/", Box::new(mrb_float_div)); mrb_define_cmethod(vm, float_class.clone(), "+@", Box::new(mrb_float_positive)); @@ -109,6 +111,58 @@ pub fn mrb_float_clamp(vm: &mut VM, args: &[Rc]) -> Result, Ok(RObject::float(result).to_refcount_assigned()) } +pub fn mrb_float_add(vm: &mut VM, args: &[Rc]) -> Result, Error> { + if args.is_empty() { + return Err(Error::ArgumentError( + "wrong number of arguments (given 0, expected 1)".to_string(), + )); + } + + let this = vm.getself()?; + let this_float = match &this.value { + crate::yamrb::value::RValue::Float(f) => *f, + _ => { + return Err(Error::RuntimeError( + "Float#+ must be called on a Float".to_string(), + )); + } + }; + + let other = match &args[0].value { + crate::yamrb::value::RValue::Float(f) => *f, + crate::yamrb::value::RValue::Integer(i) => *i as f64, + _ => return Err(Error::TypeMismatch), + }; + + Ok(RObject::float(this_float + other).to_refcount_assigned()) +} + +pub fn mrb_float_sub(vm: &mut VM, args: &[Rc]) -> Result, Error> { + if args.is_empty() { + return Err(Error::ArgumentError( + "wrong number of arguments (given 0, expected 1)".to_string(), + )); + } + + let this = vm.getself()?; + let this_float = match &this.value { + crate::yamrb::value::RValue::Float(f) => *f, + _ => { + return Err(Error::RuntimeError( + "Float#- must be called on a Float".to_string(), + )); + } + }; + + let other = match &args[0].value { + crate::yamrb::value::RValue::Float(f) => *f, + crate::yamrb::value::RValue::Integer(i) => *i as f64, + _ => return Err(Error::TypeMismatch), + }; + + Ok(RObject::float(this_float - other).to_refcount_assigned()) +} + pub fn mrb_float_mul(vm: &mut VM, args: &[Rc]) -> Result, Error> { if args.is_empty() { return Err(Error::ArgumentError( diff --git a/mrubyedge/src/yamrb/prelude/integer.rs b/mrubyedge/src/yamrb/prelude/integer.rs index 3923642..f1293a1 100644 --- a/mrubyedge/src/yamrb/prelude/integer.rs +++ b/mrubyedge/src/yamrb/prelude/integer.rs @@ -21,6 +21,8 @@ pub(crate) fn initialize_integer(vm: &mut VM) { "-@", Box::new(mrb_integer_negative), ); + mrb_define_cmethod(vm, integer_class.clone(), "+", Box::new(mrb_integer_add)); + mrb_define_cmethod(vm, integer_class.clone(), "-", Box::new(mrb_integer_sub)); mrb_define_cmethod(vm, integer_class.clone(), "**", Box::new(mrb_integer_power)); mrb_define_cmethod(vm, integer_class.clone(), "%", Box::new(mrb_integer_mod)); mrb_define_cmethod(vm, integer_class.clone(), "&", Box::new(mrb_integer_and)); @@ -118,6 +120,28 @@ fn mrb_integer_negative(vm: &mut VM, _args: &[Rc]) -> Result]) -> Result, Error> { + let lhs: i64 = vm.getself()?.as_ref().try_into()?; + let rhs_obj = &args[0]; + + match &rhs_obj.as_ref().value { + RValue::Integer(rhs) => Ok(Rc::new(RObject::integer(lhs + rhs))), + RValue::Float(rhs) => Ok(Rc::new(RObject::float(lhs as f64 + rhs))), + _ => Err(Error::TypeMismatch), + } +} + +fn mrb_integer_sub(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let lhs: i64 = vm.getself()?.as_ref().try_into()?; + let rhs_obj = &args[0]; + + match &rhs_obj.as_ref().value { + RValue::Integer(rhs) => Ok(Rc::new(RObject::integer(lhs - rhs))), + RValue::Float(rhs) => Ok(Rc::new(RObject::float(lhs as f64 - rhs))), + _ => Err(Error::TypeMismatch), + } +} + fn mrb_integer_power(vm: &mut VM, args: &[Rc]) -> Result, Error> { let base: i64 = vm.getself()?.as_ref().try_into()?; let exponent_obj = &args[0]; diff --git a/mrubyedge/tests/enumerable.rs b/mrubyedge/tests/enumerable.rs index f082461..ace4cf2 100644 --- a/mrubyedge/tests/enumerable.rs +++ b/mrubyedge/tests/enumerable.rs @@ -396,3 +396,39 @@ fn enumerable_map_custom_class_test() { let result: (i32, i32, i32) = result.as_ref().try_into().unwrap(); assert_eq!(result, (2, 4, 6)); } + +#[test] +fn enumerable_sum_default_test() { + let code = r#" + def test_sum + [1, 2, 3, 4].sum + end + "#; + let binary = mrbc_compile("enumerable_sum_default", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_sum", &args).unwrap(); + let result: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 10); +} + +#[test] +fn enumerable_sum_empty_with_init_test() { + let code = r#" + def test_sum_with_init + ["a", "b", "c", "d"].sum("") + end + "#; + let binary = mrbc_compile("enumerable_sum_with_init", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_sum_with_init", &args).unwrap(); + let result: String = result.as_ref().try_into().unwrap(); + assert_eq!(result, "abcd"); +} diff --git a/mrubyedge/tests/float.rs b/mrubyedge/tests/float.rs index 659a816..ee28bfa 100644 --- a/mrubyedge/tests/float.rs +++ b/mrubyedge/tests/float.rs @@ -35,3 +35,63 @@ result1 + result2 + result3 let result_float: f64 = result.as_ref().try_into().unwrap(); assert_eq!(result_float, 300.5); // 100.5 + 50.0 + 150.0 } + +#[test] +fn float_add_method_test() { + let code = r#" + def test_add + a = 5.5 + b = 3.2 + a.+(b) + end + "#; + let binary = mrbc_compile("float_add_method", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_add", &args).unwrap(); + let result_float: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(result_float, 8.7); +} + +#[test] +fn float_sub_method_test() { + let code = r#" + def test_sub + a = 10.5 + b = 3.2 + a.-(b) + end + "#; + let binary = mrbc_compile("float_sub_method", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_sub", &args).unwrap(); + let result_float: f64 = result.as_ref().try_into().unwrap(); + assert!((result_float - 7.3).abs() < 0.0001); // Floating point comparison +} + +#[test] +fn float_add_integer_test() { + let code = r#" + def test_add_int + a = 5.5 + i = 3 + a.+(i) + end + "#; + let binary = mrbc_compile("float_add_int", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_add_int", &args).unwrap(); + let result_float: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(result_float, 8.5); +} diff --git a/mrubyedge/tests/integer.rs b/mrubyedge/tests/integer.rs index 23de1c6..c4ff352 100644 --- a/mrubyedge/tests/integer.rs +++ b/mrubyedge/tests/integer.rs @@ -241,3 +241,63 @@ fn integer_clamp_test() { let result_int: i32 = result.as_ref().try_into().unwrap(); assert_eq!(result_int, 300); // 100 + 50 + 150 } + +#[test] +fn integer_add_method_test() { + let code = r#" + def test_add + a = 5 + b = 3 + a.+(b) + end + "#; + let binary = mrbc_compile("integer_add_method", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_add", &args).unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 8); +} + +#[test] +fn integer_sub_method_test() { + let code = r#" + def test_sub + a = 10 + b = 3 + a.-(b) + end + "#; + let binary = mrbc_compile("integer_sub_method", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_sub", &args).unwrap(); + let result_int: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result_int, 7); +} + +#[test] +fn integer_add_float_test() { + let code = r#" + def test_add_float + a = 5 + f = 2.5 + a.+(f) + end + "#; + let binary = mrbc_compile("integer_add_float", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_add_float", &args).unwrap(); + let result_float: f64 = result.as_ref().try_into().unwrap(); + assert_eq!(result_float, 7.5); +} diff --git a/mrubyedge/tests/range.rs b/mrubyedge/tests/range.rs index 5560340..8b0d056 100644 --- a/mrubyedge/tests/range.rs +++ b/mrubyedge/tests/range.rs @@ -73,3 +73,69 @@ fn range_map_test() { let result: (i32, i32, i32) = result.as_ref().try_into().unwrap(); assert_eq!(result, (2, 4, 6)); } + +#[test] +fn range_sum_inclusive_test() { + let code = r#" + def test_range_sum + (0..10).sum + end + "#; + let binary = mrbc_compile("range_sum_inclusive", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_range_sum", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + // 0 + 1 + 2 + ... + 10 = 55 + assert_eq!(result, 55); +} + +#[test] +fn range_sum_exclusive_test() { + let code = r#" + def test_range_sum + (0...10).sum + end + "#; + let binary = mrbc_compile("range_sum_exclusive", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_range_sum", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + // 0 + 1 + 2 + ... + 9 = 45 + assert_eq!(result, 45); +} + +#[test] +fn range_sum_with_init_test() { + let code = r#" + def test_range_sum_init + (1..5).sum(10) + end + "#; + let binary = mrbc_compile("range_sum_with_init", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_range_sum_init", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + // 10 + 1 + 2 + 3 + 4 + 5 = 25 + assert_eq!(result, 25); +} From c0d789701d0e8314a1e9c9304835cefd3a7cf18e Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 16 Feb 2026 21:23:06 +0900 Subject: [PATCH 249/314] Fix backward jump and unconditional jump in optable.rs --- mrubyedge/examples/while.rb | 5 +++++ mrubyedge/src/yamrb/optable.rs | 28 ++++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 4 deletions(-) create mode 100644 mrubyedge/examples/while.rb diff --git a/mrubyedge/examples/while.rb b/mrubyedge/examples/while.rb new file mode 100644 index 0000000..23ba941 --- /dev/null +++ b/mrubyedge/examples/while.rb @@ -0,0 +1,5 @@ +a = 0 +while true + a += 1 + break if a > 10 +end \ No newline at end of file diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index b22d9ad..e61121e 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -793,7 +793,12 @@ pub(crate) fn op_setidx(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_jmp(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Result<(), Error> { let a = operand.as_s()?; - let next_pc = calcurate_pc(&vm.current_irep, vm.pc.get(), end_pos + a as usize); + let offset = a as i16; // u16をi16として再解釈(負のジャンプをサポート) + let next_pc = calcurate_pc( + &vm.current_irep, + 0, + (end_pos as isize + offset as isize) as usize, + ); vm.pc.set(next_pc); Ok(()) } @@ -802,7 +807,12 @@ pub(crate) fn op_jmpif(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Result let (a, b) = operand.as_bs()?; let val = vm.get_current_regs_cloned(a as usize)?; if val.is_truthy() { - let next_pc = calcurate_pc(&vm.current_irep, vm.pc.get(), end_pos + b as usize); + let offset = b as i16; // u16をi16として再解釈 + let next_pc = calcurate_pc( + &vm.current_irep, + 0, + (end_pos as isize + offset as isize) as usize, + ); vm.pc.set(next_pc); } Ok(()) @@ -812,7 +822,12 @@ pub(crate) fn op_jmpnot(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Resul let (a, b) = operand.as_bs()?; let val = vm.get_current_regs_cloned(a as usize)?; if val.is_falsy() { - let next_pc = calcurate_pc(&vm.current_irep, vm.pc.get(), end_pos + b as usize); + let offset = b as i16; // u16をi16として再解釈 + let next_pc = calcurate_pc( + &vm.current_irep, + 0, + (end_pos as isize + offset as isize) as usize, + ); vm.pc.set(next_pc); } Ok(()) @@ -822,7 +837,12 @@ pub(crate) fn op_jmpnil(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Resul let (a, b) = operand.as_bs()?; let val = vm.get_current_regs_cloned(a as usize)?; if val.is_nil() { - let next_pc = calcurate_pc(&vm.current_irep, vm.pc.get(), end_pos + b as usize); + let offset = b as i16; // u16をi16として再解釈 + let next_pc = calcurate_pc( + &vm.current_irep, + 0, + (end_pos as isize + offset as isize) as usize, + ); vm.pc.set(next_pc); } Ok(()) From 3cb3e92aad73f2e4e4a8ca8130580e069822c0c4 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 16 Feb 2026 21:23:45 +0900 Subject: [PATCH 250/314] Remove comments --- mrubyedge/src/yamrb/optable.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index e61121e..08decb3 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -793,7 +793,7 @@ pub(crate) fn op_setidx(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { pub(crate) fn op_jmp(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Result<(), Error> { let a = operand.as_s()?; - let offset = a as i16; // u16をi16として再解釈(負のジャンプをサポート) + let offset = a as i16; let next_pc = calcurate_pc( &vm.current_irep, 0, @@ -807,7 +807,7 @@ pub(crate) fn op_jmpif(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Result let (a, b) = operand.as_bs()?; let val = vm.get_current_regs_cloned(a as usize)?; if val.is_truthy() { - let offset = b as i16; // u16をi16として再解釈 + let offset = b as i16; let next_pc = calcurate_pc( &vm.current_irep, 0, @@ -822,7 +822,7 @@ pub(crate) fn op_jmpnot(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Resul let (a, b) = operand.as_bs()?; let val = vm.get_current_regs_cloned(a as usize)?; if val.is_falsy() { - let offset = b as i16; // u16をi16として再解釈 + let offset = b as i16; let next_pc = calcurate_pc( &vm.current_irep, 0, @@ -837,7 +837,7 @@ pub(crate) fn op_jmpnil(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Resul let (a, b) = operand.as_bs()?; let val = vm.get_current_regs_cloned(a as usize)?; if val.is_nil() { - let offset = b as i16; // u16をi16として再解釈 + let offset = b as i16; let next_pc = calcurate_pc( &vm.current_irep, 0, From 8312b75a4c0d24a040fc35306db0f66d35d7e146 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 16 Feb 2026 21:36:45 +0900 Subject: [PATCH 251/314] Managed to work jmpuw --- mrubyedge/examples/while.rb | 11 ++++++++--- mrubyedge/src/yamrb/optable.rs | 17 +++++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/mrubyedge/examples/while.rb b/mrubyedge/examples/while.rb index 23ba941..4ed7623 100644 --- a/mrubyedge/examples/while.rb +++ b/mrubyedge/examples/while.rb @@ -1,5 +1,10 @@ a = 0 while true - a += 1 - break if a > 10 -end \ No newline at end of file + begin + a += 1 + break if a > 10 + ensure + puts "ensure: a=#{a}" + end +end +p a \ No newline at end of file diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 08decb3..fa46e06 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -273,9 +273,9 @@ pub(crate) fn consume_expr( JMPNIL => { op_jmpnil(vm, operand, pos + len)?; } - // JMPUW => { - // // op_jmpuw(vm, &operand)?; - // } + JMPUW => { + op_jmpuw(vm, &operand, pos + len)?; + } EXCEPT => { op_except(vm, operand)?; } @@ -848,14 +848,19 @@ pub(crate) fn op_jmpnil(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Resul Ok(()) } +pub(crate) fn op_jmpuw(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Result<(), Error> { + // TODO: support ensure/rescue behavior... + op_jmp(vm, operand, end_pos) +} + pub(crate) fn op_except(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { let a = operand.as_b()?; let val = vm .exception .take() - .ok_or_else(|| Error::internal("exception not found"))?; - let exc = Rc::new(RObject::exception(val)); - vm.current_regs()[a as usize].replace(exc); + .map(|e| RObject::exception(e).to_refcount_assigned()) + .unwrap_or_else(|| RObject::nil().to_refcount_assigned()); + vm.current_regs()[a as usize].replace(val); Ok(()) } From 0bb8b0e5edf3140751ef4e0d5069bd66033ad616 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 16 Feb 2026 22:02:53 +0900 Subject: [PATCH 252/314] Support to execute ensure block when jumping with unwind --- mrubyedge/src/yamrb/optable.rs | 60 +++++++++- mrubyedge/tests/while.rs | 201 +++++++++++++++++++++++++++++++++ 2 files changed, 258 insertions(+), 3 deletions(-) create mode 100644 mrubyedge/tests/while.rs diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index fa46e06..a86be59 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1,6 +1,8 @@ use std::cell::Cell; use std::cell::RefCell; +#[cfg(feature = "mrubyedge-debug")] +use std::env; use std::rc::Rc; use crate::Error; @@ -274,7 +276,7 @@ pub(crate) fn consume_expr( op_jmpnil(vm, operand, pos + len)?; } JMPUW => { - op_jmpuw(vm, &operand, pos + len)?; + op_jmpuw(vm, operand, pos + len)?; } EXCEPT => { op_except(vm, operand)?; @@ -849,8 +851,60 @@ pub(crate) fn op_jmpnil(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Resul } pub(crate) fn op_jmpuw(vm: &mut VM, operand: &Fetched, end_pos: usize) -> Result<(), Error> { - // TODO: support ensure/rescue behavior... - op_jmp(vm, operand, end_pos) + if vm.current_irep.catch_target_pos.is_empty() { + op_jmp(vm, operand, end_pos) + } else { + // TODO multiple catch targets... :( + let target_pos = vm.current_irep.catch_target_pos[0]; + vm.pc.set(target_pos); + + consume_ensure_block(vm)?; + op_jmp(vm, operand, end_pos) + } +} + +fn consume_ensure_block(vm: &mut VM) -> Result<(), Error> { + loop { + let pc = vm.pc.get(); + if vm.current_irep.code.len() <= pc { + // reached end of the IREP + return Err(Error::internal( + "end of opcode reached while consuming ensure block", + )); + } + let op = *vm + .current_irep + .code + .get(pc) + .ok_or_else(|| Error::internal("end of opcode reached"))?; + let operand = op.operand; + vm.pc.set(pc + 1); + + if matches!(op.code, OpCode::RAISEIF) { + return Ok(()); + } + + #[cfg(feature = "mrubyedge-debug")] + if let Ok(v) = env::var("MRUBYEDGE_DEBUG") { + let level: i32 = v.parse().unwrap_or(1); + if level >= 2 { + vm.debug_dump_to_stdout(32); + } + eprintln!( + "{:?}: {:?} (pos={} len={})", + op.code, &operand, op.pos, op.len + ); + } + + match consume_expr(vm, op.code, &operand, op.pos, op.len) { + Ok(_) => {} + Err(e) => { + let exception = RException::from_error(vm, &e); + vm.exception = Some(Rc::new(exception)); + continue; + } + } + } } pub(crate) fn op_except(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { diff --git a/mrubyedge/tests/while.rs b/mrubyedge/tests/while.rs new file mode 100644 index 0000000..c6708f5 --- /dev/null +++ b/mrubyedge/tests/while.rs @@ -0,0 +1,201 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn while_basic_test() { + let code = r#" + def test_while + a = 0 + while a < 5 + a += 1 + end + a + end + "#; + let binary = mrbc_compile("while_basic", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_while", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 5); +} + +#[test] +fn while_with_break_test() { + let code = r#" + def test_while_break + a = 0 + while true + a += 1 + break if a > 10 + end + a + end + "#; + let binary = mrbc_compile("while_break", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_while_break", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 11); +} + +#[test] +fn while_with_ensure_test() { + let code = r#" + def test_while_ensure + a = 0 + $ensure_count = 0 + while true + begin + a += 1 + break if a > 10 + ensure + $ensure_count += 1 + end + end + a + end + "#; + let binary = mrbc_compile("while_ensure", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_while_ensure", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 11); + + // Verify that ensure is executed every iteration + let ensure_count: i32 = vm + .globals + .get("$ensure_count") + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(ensure_count, 11); +} + +#[test] +fn while_nested_test() { + let code = r#" + def test_while_nested + sum = 0 + i = 0 + while i < 3 + j = 0 + while j < 3 + sum += 1 + j += 1 + end + i += 1 + end + sum + end + "#; + let binary = mrbc_compile("while_nested", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_while_nested", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 9); // 3 * 3 = 9 +} + +#[test] +fn while_accumulate_test() { + let code = r#" + def test_while_accumulate + a = 0 + sum = 0 + while a < 10 + a += 1 + sum += a + end + sum + end + "#; + let binary = mrbc_compile("while_accumulate", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_while_accumulate", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + // 1 + 2 + 3 + ... + 10 = 55 + assert_eq!(result, 55); +} + +#[test] +fn while_with_ensure_and_exception_test() { + let code = r#" + def test_while_ensure_exception + a = 0 + $ensure_executed = false + begin + while a < 5 + begin + a += 1 + raise "error" if a == 3 + ensure + $ensure_executed = true + end + end + rescue + # catch the exception + end + a + end + "#; + let binary = mrbc_compile("while_ensure_exception", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_while_ensure_exception", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 3); + + // Verify that ensure was executed + let ensure_executed: bool = vm + .globals + .get("$ensure_executed") + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert!(ensure_executed); +} From c39dd3acd129613e422e15e4f45d5b07357224e5 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 16 Feb 2026 22:10:07 +0900 Subject: [PATCH 253/314] Add basic loop test --- mrubyedge/src/yamrb/prelude/object.rs | 17 ++++++++++++++++- mrubyedge/tests/object.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index 9517a2c..b018123 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -3,7 +3,7 @@ use std::rc::Rc; use crate::{ Error, yamrb::{ - helpers::{mrb_define_cmethod, mrb_funcall}, + helpers::{mrb_call_block, mrb_define_cmethod, mrb_funcall}, value::*, vm::VM, }, @@ -116,6 +116,7 @@ pub(crate) fn initialize_object(vm: &mut VM) { "extend", Box::new(mrb_object_extend), ); + mrb_define_cmethod(vm, object_class.clone(), "loop", Box::new(mrb_object_loop)); // define global consts: vm.consts.insert( @@ -365,6 +366,20 @@ fn mrb_object_class(vm: &mut VM, _args: &[Rc]) -> Result, E Ok(RObject::class_or_module(class.as_module(), vm)) } +fn mrb_object_loop(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let block = args[0].clone(); + if !matches!(block.value, RValue::Proc(_)) { + return Err(Error::ArgumentError( + "Object#loop expects a block".to_string(), + )); + } + + let this = vm.getself()?; + loop { + mrb_call_block(vm, block.clone(), Some(this.clone()), &[], 0)?; + } +} + fn mrb_object_method_missing(vm: &mut VM, args: &[Rc]) -> Result, Error> { let method_name_obj = &args .first() diff --git a/mrubyedge/tests/object.rs b/mrubyedge/tests/object.rs index 8569720..12ab4de 100644 --- a/mrubyedge/tests/object.rs +++ b/mrubyedge/tests/object.rs @@ -291,3 +291,29 @@ fn object_extend_multiple_arguments_priority_test() { "M2 only" ); } + +#[test] +fn object_loop_basic_test() { + let code = r#" + def test_loop + i = 0 + loop do + i += 1 + break if i >= 5 + end + i + end + "#; + let binary = mrbc_compile_debug("loop_basic", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_loop", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 5); +} From b275b9648f578fb1c1ee35e9c2f43acbce0879ac Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 16 Feb 2026 22:26:13 +0900 Subject: [PATCH 254/314] Support instruction count limit in VM and add test cases for it --- .github/workflows/mrubyedge.yml | 4 + mrubyedge/Cargo.toml | 1 + mrubyedge/src/yamrb/vm.rs | 41 +++++++++ mrubyedge/tests/insn_limit.rs | 150 ++++++++++++++++++++++++++++++++ 4 files changed, 196 insertions(+) create mode 100644 mrubyedge/tests/insn_limit.rs diff --git a/.github/workflows/mrubyedge.yml b/.github/workflows/mrubyedge.yml index 04bd624..425921e 100644 --- a/.github/workflows/mrubyedge.yml +++ b/.github/workflows/mrubyedge.yml @@ -50,6 +50,10 @@ jobs: git diff exit $TEST_RESULT fi + - name: Run extra test cases for "${{ matrix.BUILD_TARGET }}" profile + run: | + export MRUBYEDGE_INSN_LIMIT=10000 + cargo test --features insn-limit --test insn_limit --profile ${{ matrix.BUILD_TARGET }} - name: Build binaries for "${{ matrix.BUILD_TARGET }}${{ matrix.ENABLE_FNV_HASH }}" profile run: | cargo build -p mrubyedge \ diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index d67a834..e341470 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -40,5 +40,6 @@ mruby-hash-fnv = ["dep:fnv"] mruby-regexp = ["dep:regex"] mrubyedge-debug = ["wasi"] mruby-random = ["dep:rand_core", "dep:rand_xorshift"] +insn-limit = [] # mruby-securerandom = ["wasi", "dep:getrandom"] no-wasi = [] diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 156100b..5056dcd 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -85,6 +85,11 @@ pub struct VM { pub flag_preemption: Cell, + #[cfg(feature = "insn-limit")] + pub insn_count: Cell, + #[cfg(feature = "insn-limit")] + pub insn_limit: usize, + // common class pub object_class: Rc, pub builtin_class_table: RHashMap<&'static str, Rc>, @@ -266,6 +271,14 @@ impl VM { let cur_env = RHashMap::default(); let has_env_ref = RHashMap::default(); + #[cfg(feature = "insn-limit")] + let insn_count = Cell::new(0); + #[cfg(feature = "insn-limit")] + let insn_limit = { + let limit_str = env!("MRUBYEDGE_INSN_LIMIT", "MRUBYEDGE_INSN_LIMIT must be set when insn-limit feature is enabled"); + limit_str.parse::().expect("MRUBYEDGE_INSN_LIMIT must be a valid number") + }; + let mut vm = VM { id, bytecode, @@ -281,6 +294,10 @@ impl VM { target_class, exception, flag_preemption, + #[cfg(feature = "insn-limit")] + insn_count, + #[cfg(feature = "insn-limit")] + insn_limit, object_class, builtin_class_table, class_object_table, @@ -298,6 +315,18 @@ impl VM { vm } + /// Resets the instruction counter. Only available when the `insn-limit` feature is enabled. + #[cfg(feature = "insn-limit")] + pub fn reset_insn_count(&mut self) { + self.insn_count.set(0); + } + + /// Returns the current instruction count. Only available when the `insn-limit` feature is enabled. + #[cfg(feature = "insn-limit")] + pub fn get_insn_count(&self) -> usize { + self.insn_count.get() + } + /// Executes the current IREP until completion, returning the value in /// register 0 or propagating any raised exception as an error. The /// top-level `self` is initialized automatically before evaluation. @@ -431,6 +460,18 @@ impl VM { let operand = op.operand; self.pc.set(pc + 1); + #[cfg(feature = "insn-limit")] + { + let count = self.insn_count.get(); + if count >= self.insn_limit { + return Err(Box::new(Error::internal(format!( + "instruction limit exceeded: {} instructions", + self.insn_limit + )))); + } + self.insn_count.set(count + 1); + } + #[cfg(feature = "mrubyedge-debug")] if let Ok(v) = env::var("MRUBYEDGE_DEBUG") { let level: i32 = v.parse().unwrap_or(1); diff --git a/mrubyedge/tests/insn_limit.rs b/mrubyedge/tests/insn_limit.rs new file mode 100644 index 0000000..5f0fd46 --- /dev/null +++ b/mrubyedge/tests/insn_limit.rs @@ -0,0 +1,150 @@ +#![cfg(feature = "insn-limit")] +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +#[test] +fn insn_limit_basic_test() { + let code = r#" + def test_simple + a = 1 + b = 2 + a + b + end + "#; + let binary = mrbc_compile("insn_limit_basic", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + + // Simple function should complete within limit + let result = vm.run(); + assert!(result.is_ok()); + + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_simple", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 3); +} + +#[test] +fn insn_limit_exceeded_test() { + let code = r#" + def test_infinite_loop + i = 0 + loop do + i += 1 + end + end + "#; + let binary = mrbc_compile("insn_limit_exceeded", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_infinite_loop", &args); + + // Should fail due to instruction limit + assert!(result.is_err()); + let err_msg = format!("{:?}", result.unwrap_err()); + assert!(err_msg.contains("instruction limit exceeded")); +} + +#[test] +fn insn_limit_reset_test() { + let code = r#" + def test_count + sum = 0 + 10.times do |i| + sum += i + end + sum + end + "#; + let binary = mrbc_compile("insn_limit_reset", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + // First call + let args = vec![]; + let result: i32 = mrb_funcall(&mut vm, None, "test_count", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 45); + + let count_before_reset = vm.get_insn_count(); + assert!(count_before_reset > 0); + + vm.reset_insn_count(); + assert_eq!(vm.get_insn_count(), 0); + + // Second call should work after reset + let result: i32 = mrb_funcall(&mut vm, None, "test_count", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, 45); +} + +#[test] +fn insn_limit_while_loop_test() { + let code = r#" + def test_while + i = 0 + sum = 0 + while i < 100000 + i += 1 + sum += i + end + sum + end + "#; + let binary = mrbc_compile("insn_limit_while", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_while", &args); + + assert!(result.is_err()); + assert!(format!("{:?}", result.unwrap_err()).contains("instruction limit exceeded")); +} + +#[test] +fn insn_limit_counter_increments_test() { + let code = r#" + def test_increment + a = 1 + b = 2 + c = 3 + a + b + c + end + "#; + let binary = mrbc_compile("insn_limit_increment", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + + let initial_count = vm.get_insn_count(); + assert_eq!(initial_count, 0); + + vm.run().unwrap(); + + let count_after_run = vm.get_insn_count(); + assert!(count_after_run > 0); + + let args = vec![]; + mrb_funcall(&mut vm, None, "test_increment", &args).unwrap(); + + let count_after_call = vm.get_insn_count(); + assert!(count_after_call > count_after_run); +} From 09fc71472b88b1b662c7b0a69e80a6f2062e2c70 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 16 Feb 2026 22:27:56 +0900 Subject: [PATCH 255/314] Fix format --- mrubyedge/src/yamrb/vm.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 5056dcd..8fe0c8a 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -275,8 +275,13 @@ impl VM { let insn_count = Cell::new(0); #[cfg(feature = "insn-limit")] let insn_limit = { - let limit_str = env!("MRUBYEDGE_INSN_LIMIT", "MRUBYEDGE_INSN_LIMIT must be set when insn-limit feature is enabled"); - limit_str.parse::().expect("MRUBYEDGE_INSN_LIMIT must be a valid number") + let limit_str = env!( + "MRUBYEDGE_INSN_LIMIT", + "MRUBYEDGE_INSN_LIMIT must be set when insn-limit feature is enabled" + ); + limit_str + .parse::() + .expect("MRUBYEDGE_INSN_LIMIT must be a valid number") }; let mut vm = VM { @@ -464,10 +469,11 @@ impl VM { { let count = self.insn_count.get(); if count >= self.insn_limit { - return Err(Box::new(Error::internal(format!( + return Err(Error::internal(format!( "instruction limit exceeded: {} instructions", self.insn_limit - )))); + )) + .into()); } self.insn_count.set(count + 1); } From e285d36d2acce81c01cd4a5b3789a28c13121e9d Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 16 Feb 2026 22:34:31 +0900 Subject: [PATCH 256/314] Bump version to 1.1.6 --- Cargo.lock | 22 +++++++++++----------- mrubyedge/Cargo.toml | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17d9ef3..dcb9251 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -625,7 +625,7 @@ name = "mruby-math" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.5", ] [[package]] @@ -633,7 +633,7 @@ name = "mruby-serde-json" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.5", "serde", "serde_json", ] @@ -641,12 +641,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12abf12304eecad6d7ae2806ff47b22ae915a6ce8d490555b44b86d78596382" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -656,10 +653,13 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12abf12304eecad6d7ae2806ff47b22ae915a6ce8d490555b44b86d78596382" +version = "1.1.6" dependencies = [ + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -675,7 +675,7 @@ dependencies = [ "clap", "crossterm", "mruby-compiler2-sys", - "mrubyedge 1.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.5", "nom", "rand", "syn", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index e341470..bf443ae 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.1.5" +version = "1.1.6" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From 570857920bfdd0427865e5bdd0da7569f60c20db Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 16 Feb 2026 22:35:30 +0900 Subject: [PATCH 257/314] Bump version to 1.1.6 and update Cargo.lock --- Cargo.lock | 36 ++++++++++++++++++------------------ mrubyedge-cli/Cargo.toml | 4 ++-- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dcb9251..32ce3f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -168,9 +168,9 @@ dependencies = [ [[package]] name = "bitflags" -version = "2.10.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bumpalo" @@ -625,7 +625,7 @@ name = "mruby-math" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.5", + "mrubyedge 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -633,17 +633,20 @@ name = "mruby-serde-json" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.5", + "mrubyedge 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "serde_json", ] [[package]] name = "mrubyedge" -version = "1.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12abf12304eecad6d7ae2806ff47b22ae915a6ce8d490555b44b86d78596382" +version = "1.1.6" dependencies = [ + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -654,12 +657,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adbb814b82d92d05c2803ea2bb5f7dea364e9b1af3b8f06d7dfd352374f41c3b" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -669,13 +669,13 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.1.5" +version = "1.1.6" dependencies = [ "askama", "clap", "crossterm", "mruby-compiler2-sys", - "mrubyedge 1.1.5", + "mrubyedge 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", @@ -1066,9 +1066,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.115" +version = "2.0.116" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e614ed320ac28113fa64972c4262d5dbc89deacdfd00c34a3e4cea073243c12" +checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" dependencies = [ "proc-macro2", "quote", @@ -1093,9 +1093,9 @@ checksum = "dbc4bc3a9f746d862c45cb89d705aa10f187bb96c76001afab07a0d35ce60142" [[package]] name = "unicode-ident" -version = "1.0.23" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537dd038a89878be9b64dd4bd1b260315c1bb94f4d784956b81e27a088d9a09e" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "utf8parse" diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 027e44d..0514d00 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.1.5" +version = "1.1.6" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -15,7 +15,7 @@ path = "src/main.rs" clap = { version = "4.5", features = ["derive"] } crossterm = "0.28" mruby-compiler2-sys = "0.3.0" -mrubyedge = { version = "1.1.5", features = [ +mrubyedge = { version = "1.1.6", features = [ "default", "mruby-random", "mruby-regexp", From 3130fd9d3f045daf76ba0bb4cbc8098bd2501136 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 22 Feb 2026 00:09:09 +0900 Subject: [PATCH 258/314] Initial commit for mruby-time --- Cargo.lock | 8 + Cargo.toml | 2 +- mruby-time/Cargo.toml | 19 ++ mruby-time/src/lib.rs | 501 ++++++++++++++++++++++++++++++++ mruby-time/tests/helpers/mod.rs | 66 +++++ mruby-time/tests/smoke.rs | 396 +++++++++++++++++++++++++ mrubyedge/src/rite/insn.rs | 2 +- mrubyedge/src/yamrb/value.rs | 21 ++ 8 files changed, 1013 insertions(+), 2 deletions(-) create mode 100644 mruby-time/Cargo.toml create mode 100644 mruby-time/src/lib.rs create mode 100644 mruby-time/tests/helpers/mod.rs create mode 100644 mruby-time/tests/smoke.rs diff --git a/Cargo.lock b/Cargo.lock index 32ce3f7..455455e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -638,6 +638,14 @@ dependencies = [ "serde_json", ] +[[package]] +name = "mruby-time" +version = "0.1.0" +dependencies = [ + "mec-mrbc-sys", + "mrubyedge 1.1.6", +] + [[package]] name = "mrubyedge" version = "1.1.6" diff --git a/Cargo.toml b/Cargo.toml index f4e4c99..450223b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,4 @@ [workspace] resolver = "2" -members = ["mruby-math", "mruby-serde-json", "mrubyedge", "mrubyedge-cli"] +members = ["mruby-math", "mruby-serde-json", "mruby-time", "mrubyedge", "mrubyedge-cli"] diff --git a/mruby-time/Cargo.toml b/mruby-time/Cargo.toml new file mode 100644 index 0000000..0665380 --- /dev/null +++ b/mruby-time/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "mruby-time" +version = "0.1.0" +edition = "2024" +authors = ["Uchio Kondo "] +description = "mruby-time provides Time class for mruby/edge" +license = "BSD-3-Clause" + +[dependencies] +mrubyedge = { path = "../mrubyedge" } + +[dev-dependencies] +mrubyedge = { path = "../mrubyedge", features = ["default"] } +mec-mrbc-sys = "3.3.1" + +[features] +default = [] +# Enable std::time based Time.__source (disabled on wasm by default) +std-time = [] diff --git a/mruby-time/src/lib.rs b/mruby-time/src/lib.rs new file mode 100644 index 0000000..443b72d --- /dev/null +++ b/mruby-time/src/lib.rs @@ -0,0 +1,501 @@ +use std::any::Any; +use std::cell::{Cell, RefCell}; +use std::rc::Rc; + +use mrubyedge::{ + Error, + yamrb::{ + helpers::{mrb_define_class_cmethod, mrb_define_cmethod, mrb_funcall}, + value::{RData, RHashMap, RObject, RType, RValue}, + vm::VM, + }, +}; + +/// Rust-side representation of a Ruby Time object. +/// Stores seconds and nanoseconds since UNIX epoch, and UTC offset in seconds. +#[derive(Debug, Clone)] +pub struct RTimeData { + /// Seconds since UNIX epoch (can be negative for times before 1970) + pub sec: i64, + /// Nanoseconds within the current second (0..999_999_999) + pub nsec: u32, + /// UTC offset in seconds (e.g. +9h = 32400, -5h = -18000) + pub utc_offset: i32, + /// Cached result of to_datetime_parts() — computed lazily, interior-mutable. + cached_parts: Cell>, +} + +impl RTimeData { + pub fn new(sec: i64, nsec: u32, utc_offset: i32) -> Self { + RTimeData { + sec, + nsec, + utc_offset, + cached_parts: Cell::new(None), + } + } + + /// Calculate the "local" seconds (sec + utc_offset) for date/time decomposition. + fn local_sec(&self) -> i64 { + self.sec + self.utc_offset as i64 + } + + /// Decompose into (year, month, day, wday, hour, min, sec_in_day). + /// Uses the proleptic Gregorian calendar algorithm. + /// Result is cached on first call via interior mutability. + pub fn to_datetime_parts(&self) -> (i32, u32, u32, u32, u32, u32, u32) { + if let Some(parts) = self.cached_parts.get() { + return parts; + } + let local = self.local_sec(); + + // Time of day + let sec_in_day = local.rem_euclid(86400) as u32; + let hour = sec_in_day / 3600; + let min = (sec_in_day % 3600) / 60; + let sec = sec_in_day % 60; + + // Day number from epoch (days since 1970-01-01, can be negative) + let days_from_epoch = local.div_euclid(86400); + + // Convert to Julian Day Number; 1970-01-01 = JDN 2440588 + let jdn = days_from_epoch + 2440588; + + // Gregorian calendar conversion from JDN + // Algorithm from: https://en.wikipedia.org/wiki/Julian_day#Julian_day_number_calculation + let l = jdn + 68569; + let n = (4 * l) / 146097; + let l = l - (146097 * n + 3) / 4; + let i = (4000 * (l + 1)) / 1461001; + let l = l - (1461 * i) / 4 + 31; + let j = (80 * l) / 2447; + let day = l - (2447 * j) / 80; + let l = j / 11; + let month = j + 2 - 12 * l; + let year = 100 * (n - 49) + i + l; + + // Weekday: JDN mod 7; JDN=0 is Monday in proleptic... actually + // 2440588 % 7 = 4, and 1970-01-01 was Thursday (wday=4 in Ruby) + let wday = (jdn + 1).rem_euclid(7) as u32; // 0=Sunday, 1=Monday, ... + + let parts = (year as i32, month as u32, day as u32, wday, hour, min, sec); + self.cached_parts.set(Some(parts)); + parts + } + + /// Format as "%Y-%m-%d %H:%M:%S %z" + pub fn to_s(&self) -> String { + let (year, month, day, _wday, hour, min, sec) = self.to_datetime_parts(); + let offset_sign = if self.utc_offset >= 0 { '+' } else { '-' }; + let abs_offset = self.utc_offset.unsigned_abs(); + let offset_h = abs_offset / 3600; + let offset_m = (abs_offset % 3600) / 60; + format!( + "{:04}-{:02}-{:02} {:02}:{:02}:{:02} {}{:02}{:02}", + year, month, day, hour, min, sec, offset_sign, offset_h, offset_m + ) + } +} + +/// Extract RTimeData from an RObject (must be a Data object holding RTimeData). +fn get_time_data(obj: &Rc) -> Result { + match &obj.value { + RValue::Data(data) => { + let borrow = data.data.borrow(); + let any_ref = borrow + .as_ref() + .ok_or_else(|| Error::RuntimeError("Invalid Time data".to_string()))?; + let time = any_ref + .downcast_ref::() + .ok_or_else(|| Error::RuntimeError("Invalid Time data".to_string()))?; + Ok(time.clone()) + } + _ => Err(Error::RuntimeError( + "Expected a Time object".to_string(), + )), + } +} + +/// Create an Rc wrapping an RTimeData. +fn make_time_object(vm: &mut VM, time_data: RTimeData) -> Rc { + let time_class_obj = vm + .get_const_by_name("Time") + .expect("Time class not found; did you call init_time?"); + let class = match &time_class_obj.value { + RValue::Class(c) => c.clone(), + _ => panic!("Time is not a class"), + }; + let rdata = Rc::new(RData { + class, + data: RefCell::new(Some(Rc::new( + Box::new(time_data) as Box + ))), + ref_count: 1, + }); + Rc::new(RObject { + tt: RType::Data, + value: RValue::Data(rdata), + object_id: Cell::new(u64::MAX), + singleton_class: RefCell::new(None), + ivar: RefCell::new(RHashMap::default()), + }) +} + +// --------------------------------------------------------------------------- +// Class methods +// --------------------------------------------------------------------------- + +/// Time.now +/// Calls Time.__source to get [sec, nsec], then creates a Time object. +/// utc_offset defaults to 0 (UTC). +fn mrb_time_now(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let time_class_obj = vm.get_const_by_name("Time") + .ok_or_else(|| Error::RuntimeError("Time class not found".to_string()))?; + + // Call Time.__source -> [sec, nsec] + let source = mrb_funcall(vm, Some(time_class_obj), "__source", &[])?; + let (sec, nsec) = source.as_ref().try_into()?; + + Ok(make_time_object(vm, RTimeData::new(sec, nsec, 0))) +} + +/// Time.at(sec) or Time.at(sec, nsec) +fn mrb_time_at(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = strip_trailing_nil(args); + if args.is_empty() { + return Err(Error::ArgumentError( + "wrong number of arguments (given 0, expected 1+)".to_string(), + )); + } + + let sec = get_integer_or_float_as_i64(&args[0])?; + let nsec = if args.len() >= 2 { + get_integer_or_float_as_u32(&args[1])? + } else { + 0 + }; + + Ok(make_time_object(vm, RTimeData::new(sec, nsec, 0))) +} + +// --------------------------------------------------------------------------- +// Instance methods +// --------------------------------------------------------------------------- + +/// Time#year +fn mrb_time_year(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let self_obj = vm.getself()?; + let t = get_time_data(&self_obj)?; + let (year, _, _, _, _, _, _) = t.to_datetime_parts(); + Ok(RObject::integer(year as i64).to_refcount_assigned()) +} + +/// Time#month (alias: mon) +fn mrb_time_month(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let self_obj = vm.getself()?; + let t = get_time_data(&self_obj)?; + let (_, month, _, _, _, _, _) = t.to_datetime_parts(); + Ok(RObject::integer(month as i64).to_refcount_assigned()) +} + +/// Time#day (alias: mday) +fn mrb_time_day(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let self_obj = vm.getself()?; + let t = get_time_data(&self_obj)?; + let (_, _, day, _, _, _, _) = t.to_datetime_parts(); + Ok(RObject::integer(day as i64).to_refcount_assigned()) +} + +/// Time#wday (0=Sunday, 1=Monday, ..., 6=Saturday) +fn mrb_time_wday(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let self_obj = vm.getself()?; + let t = get_time_data(&self_obj)?; + let (_, _, _, wday, _, _, _) = t.to_datetime_parts(); + Ok(RObject::integer(wday as i64).to_refcount_assigned()) +} + +/// Time#hour +fn mrb_time_hour(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let self_obj = vm.getself()?; + let t = get_time_data(&self_obj)?; + let (_, _, _, _, hour, _, _) = t.to_datetime_parts(); + Ok(RObject::integer(hour as i64).to_refcount_assigned()) +} + +/// Time#min +fn mrb_time_min(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let self_obj = vm.getself()?; + let t = get_time_data(&self_obj)?; + let (_, _, _, _, _, min, _) = t.to_datetime_parts(); + Ok(RObject::integer(min as i64).to_refcount_assigned()) +} + +/// Time#sec +fn mrb_time_sec(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let self_obj = vm.getself()?; + let t = get_time_data(&self_obj)?; + let (_, _, _, _, _, _, sec) = t.to_datetime_parts(); + Ok(RObject::integer(sec as i64).to_refcount_assigned()) +} + +/// Time#nsec +fn mrb_time_nsec(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let self_obj = vm.getself()?; + let t = get_time_data(&self_obj)?; + Ok(RObject::integer(t.nsec as i64).to_refcount_assigned()) +} + +/// Time#to_s -> "%Y-%m-%d %H:%M:%S %z" +fn mrb_time_to_s(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let self_obj = vm.getself()?; + let t = get_time_data(&self_obj)?; + Ok(RObject::string(t.to_s()).to_refcount_assigned()) +} + +/// Time#+ (sec as integer or float) +fn mrb_time_add(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = strip_trailing_nil(args); + if args.is_empty() { + return Err(Error::ArgumentError( + "wrong number of arguments (given 0, expected 1)".to_string(), + )); + } + let self_obj = vm.getself()?; + let t = get_time_data(&self_obj)?; + + let (delta_sec, delta_nsec) = float_to_sec_nsec(&args[0])?; + let new_nsec = t.nsec as i64 + delta_nsec as i64; + let carry = new_nsec.div_euclid(1_000_000_000); + let new_nsec = new_nsec.rem_euclid(1_000_000_000) as u32; + let new_sec = t.sec + delta_sec + carry; + + Ok(make_time_object(vm, RTimeData::new(new_sec, new_nsec, t.utc_offset))) +} + +/// Time#- (sec as integer or float), also supports Time - Time -> Float (seconds) +fn mrb_time_sub(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = strip_trailing_nil(args); + if args.is_empty() { + return Err(Error::ArgumentError( + "wrong number of arguments (given 0, expected 1)".to_string(), + )); + } + let self_obj = vm.getself()?; + let t = get_time_data(&self_obj)?; + + // Check if rhs is a Time object + if let RValue::Data(_) = &args[0].value { + if let Ok(rhs) = get_time_data(&args[0]) { + // Time - Time -> Float (difference in seconds) + let sec_diff = (t.sec - rhs.sec) as f64 + + (t.nsec as f64 - rhs.nsec as f64) / 1_000_000_000.0; + return Ok(RObject::float(sec_diff).to_refcount_assigned()); + } + } + + let (delta_sec, delta_nsec) = float_to_sec_nsec(&args[0])?; + let new_nsec = t.nsec as i64 - delta_nsec as i64; + let carry = new_nsec.div_euclid(1_000_000_000); + let new_nsec = new_nsec.rem_euclid(1_000_000_000) as u32; + let new_sec = t.sec - delta_sec + carry; + + Ok(make_time_object(vm, RTimeData::new(new_sec, new_nsec, t.utc_offset))) +} + +/// Time#<=> (compare with another Time object) +fn mrb_time_cmp(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = strip_trailing_nil(args); + if args.is_empty() { + return Err(Error::ArgumentError( + "wrong number of arguments (given 0, expected 1)".to_string(), + )); + } + let self_obj = vm.getself()?; + let t = get_time_data(&self_obj)?; + + let rhs = match get_time_data(&args[0]) { + Ok(r) => r, + Err(_) => return Ok(RObject::nil().to_refcount_assigned()), + }; + + let result = match t.sec.cmp(&rhs.sec) { + std::cmp::Ordering::Equal => t.nsec.cmp(&rhs.nsec), + other => other, + }; + + let int_val = match result { + std::cmp::Ordering::Less => -1i64, + std::cmp::Ordering::Equal => 0, + std::cmp::Ordering::Greater => 1, + }; + Ok(RObject::integer(int_val).to_refcount_assigned()) +} + +/// Time#utc_offset -> Integer (seconds) +fn mrb_time_utc_offset(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let self_obj = vm.getself()?; + let t = get_time_data(&self_obj)?; + Ok(RObject::integer(t.utc_offset as i64).to_refcount_assigned()) +} + +/// Time#localtime(offset) - returns a new Time with the given UTC offset (in seconds) +fn mrb_time_localtime(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let args = strip_trailing_nil(args); + let self_obj = vm.getself()?; + let t = get_time_data(&self_obj)?; + + let new_offset = if args.is_empty() { + 0i32 // default to UTC if no arg + } else { + get_integer_or_float_as_i64(&args[0])? as i32 + }; + + Ok(make_time_object(vm, RTimeData::new(t.sec, t.nsec, new_offset))) +} + +/// Time#to_i -> sec +fn mrb_time_to_i(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let self_obj = vm.getself()?; + let t = get_time_data(&self_obj)?; + Ok(RObject::integer(t.sec).to_refcount_assigned()) +} + +/// Time#to_f -> sec.nsec as float +fn mrb_time_to_f(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let self_obj = vm.getself()?; + let t = get_time_data(&self_obj)?; + let f = t.sec as f64 + t.nsec as f64 / 1_000_000_000.0; + Ok(RObject::float(f).to_refcount_assigned()) +} + +// --------------------------------------------------------------------------- +// Default Time.__source (std::time based, non-wasm) +// --------------------------------------------------------------------------- + +/// Default implementation of Time.__source using std::time. +/// Returns [sec, nsec] as a Ruby array. +/// Only compiled on non-wasm targets (wasi feature disabled implies std::time available). +#[cfg(not(target_arch = "wasm32"))] +fn mrb_time_source_default(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { + use std::time::{SystemTime, UNIX_EPOCH}; + let now = SystemTime::now(); + let unixtime = now + .duration_since(UNIX_EPOCH) + .map_err(|_| Error::RuntimeError("system time before UNIX EPOCH".to_string()))?; + let sec = unixtime.as_secs() as i64; + let nsec = unixtime.subsec_nanos() as i64; + let arr = vec![ + RObject::integer(sec).to_refcount_assigned(), + RObject::integer(nsec).to_refcount_assigned(), + ]; + Ok(RObject::array(arr).to_refcount_assigned()) +} + +// --------------------------------------------------------------------------- +// Helper utilities +// --------------------------------------------------------------------------- + +fn strip_trailing_nil(args: &[Rc]) -> &[Rc] { + if !args.is_empty() && args[args.len() - 1].is_nil() { + &args[0..args.len() - 1] + } else { + args + } +} + +fn get_integer_or_float_as_i64(obj: &RObject) -> Result { + match &obj.value { + RValue::Integer(i) => Ok(*i), + RValue::Float(f) => Ok(*f as i64), + _ => Err(Error::ArgumentError("expected Integer or Float".to_string())), + } +} + +fn get_integer_or_float_as_u32(obj: &RObject) -> Result { + match &obj.value { + RValue::Integer(i) => { + if *i < 0 { + return Err(Error::ArgumentError("nsec must be non-negative".to_string())); + } + Ok(*i as u32) + } + RValue::Float(f) => { + if *f < 0.0 { + return Err(Error::ArgumentError("nsec must be non-negative".to_string())); + } + Ok(*f as u32) + } + _ => Err(Error::ArgumentError("expected Integer or Float".to_string())), + } +} + +/// Convert a numeric seconds value (possibly fractional) to (whole_sec, nsec). +fn float_to_sec_nsec(obj: &RObject) -> Result<(i64, u32), Error> { + match &obj.value { + RValue::Integer(i) => Ok((*i, 0)), + RValue::Float(f) => { + let sec = f.trunc() as i64; + let nsec = (f.fract().abs() * 1_000_000_000.0).round() as u32; + Ok((sec, nsec)) + } + _ => Err(Error::ArgumentError("expected Integer or Float".to_string())), + } +} + +// --------------------------------------------------------------------------- +// Public initializer +// --------------------------------------------------------------------------- + +/// Initialize the Time class in the VM. +/// Call this after `VM::open` to make `Time` available in Ruby code. +pub fn init_time(vm: &mut VM) { + let time_class = vm.define_class("Time", None, None); + + // Class methods + mrb_define_class_cmethod(vm, time_class.clone(), "now", Box::new(mrb_time_now)); + mrb_define_class_cmethod(vm, time_class.clone(), "at", Box::new(mrb_time_at)); + + // Instance methods + mrb_define_cmethod(vm, time_class.clone(), "year", Box::new(mrb_time_year)); + mrb_define_cmethod(vm, time_class.clone(), "month", Box::new(mrb_time_month)); + mrb_define_cmethod(vm, time_class.clone(), "mon", Box::new(mrb_time_month)); + mrb_define_cmethod(vm, time_class.clone(), "day", Box::new(mrb_time_day)); + mrb_define_cmethod(vm, time_class.clone(), "mday", Box::new(mrb_time_day)); + mrb_define_cmethod(vm, time_class.clone(), "wday", Box::new(mrb_time_wday)); + mrb_define_cmethod(vm, time_class.clone(), "hour", Box::new(mrb_time_hour)); + mrb_define_cmethod(vm, time_class.clone(), "min", Box::new(mrb_time_min)); + mrb_define_cmethod(vm, time_class.clone(), "sec", Box::new(mrb_time_sec)); + mrb_define_cmethod(vm, time_class.clone(), "nsec", Box::new(mrb_time_nsec)); + mrb_define_cmethod(vm, time_class.clone(), "to_s", Box::new(mrb_time_to_s)); + mrb_define_cmethod(vm, time_class.clone(), "inspect", Box::new(mrb_time_to_s)); + mrb_define_cmethod(vm, time_class.clone(), "+", Box::new(mrb_time_add)); + mrb_define_cmethod(vm, time_class.clone(), "-", Box::new(mrb_time_sub)); + mrb_define_cmethod(vm, time_class.clone(), "<=>", Box::new(mrb_time_cmp)); + mrb_define_cmethod(vm, time_class.clone(), "utc_offset", Box::new(mrb_time_utc_offset)); + mrb_define_cmethod(vm, time_class.clone(), "gmt_offset", Box::new(mrb_time_utc_offset)); + mrb_define_cmethod(vm, time_class.clone(), "localtime", Box::new(mrb_time_localtime)); + mrb_define_cmethod(vm, time_class.clone(), "to_i", Box::new(mrb_time_to_i)); + mrb_define_cmethod(vm, time_class.clone(), "to_f", Box::new(mrb_time_to_f)); + + // Register default Time.__source on non-wasm targets + #[cfg(not(target_arch = "wasm32"))] + { + let _time_class_obj = RObject::class(time_class, vm); + let time_class_obj_for_source = vm + .get_const_by_name("Time") + .expect("Time class not found after definition"); + mrb_define_class_cmethod_on_obj(vm, time_class_obj_for_source, "__source", Box::new(mrb_time_source_default)); + } +} + +/// Helper: define a singleton (class-side) cmethod on a class RObject. +#[cfg(not(target_arch = "wasm32"))] +fn mrb_define_class_cmethod_on_obj( + vm: &mut VM, + class_obj: Rc, + name: &str, + cmethod: mrubyedge::yamrb::value::RFn, +) { + use mrubyedge::yamrb::helpers::mrb_define_singleton_cmethod; + mrb_define_singleton_cmethod(vm, class_obj, name, cmethod); +} diff --git a/mruby-time/tests/helpers/mod.rs b/mruby-time/tests/helpers/mod.rs new file mode 100644 index 0000000..f84ec2f --- /dev/null +++ b/mruby-time/tests/helpers/mod.rs @@ -0,0 +1,66 @@ +#![allow(dead_code)] + +use std::{ffi::CStr, fs::File, io::Write}; + +pub fn mrbc_compile(fname: &'static str, code: &str) -> Vec { + let mut src = std::env::temp_dir(); + src.push(format!("{}.{}.rb", fname, std::process::id())); + let mut f = File::create(&src).expect("cannot open src file"); + f.write_all(code.as_bytes()) + .expect("cannot create src file"); + f.flush().unwrap(); + + let mut src0 = src.as_os_str().to_string_lossy().into_owned(); + src0.push('\0'); + + let mut dest = std::env::temp_dir(); + dest.push(format!("{}.{}.mrb", fname, std::process::id())); + let mut dest0 = dest.as_os_str().to_string_lossy().into_owned(); + dest0.push('\0'); + + let args = [ + c"mrbc".as_ptr(), + c"-o".as_ptr(), + CStr::from_bytes_with_nul(dest0.as_bytes()) + .unwrap() + .as_ptr(), + CStr::from_bytes_with_nul(src0.as_bytes()).unwrap().as_ptr(), + ]; + unsafe { + mec_mrbc_sys::mrbc_main(args.len() as i32, args.as_ptr() as *mut *mut i8); + } + + std::fs::read(dest).unwrap() +} + +pub fn mrbc_compile_debug(fname: &'static str, code: &str) -> Vec { + let mut src = std::env::temp_dir(); + src.push(format!("{}.{}.rb", fname, std::process::id())); + let mut f = File::create(&src).expect("cannot open src file"); + f.write_all(code.as_bytes()) + .expect("cannot create src file"); + f.flush().unwrap(); + + let mut src0 = src.as_os_str().to_string_lossy().into_owned(); + src0.push('\0'); + + let mut dest = std::env::temp_dir(); + dest.push(format!("{}.{}.mrb", fname, std::process::id())); + let mut dest0 = dest.as_os_str().to_string_lossy().into_owned(); + dest0.push('\0'); + + let args = [ + c"mrbc".as_ptr(), + c"-v".as_ptr(), + c"-o".as_ptr(), + CStr::from_bytes_with_nul(dest0.as_bytes()) + .unwrap() + .as_ptr(), + CStr::from_bytes_with_nul(src0.as_bytes()).unwrap().as_ptr(), + ]; + unsafe { + mec_mrbc_sys::mrbc_main(args.len() as i32, args.as_ptr() as *mut *mut i8); + } + + std::fs::read(dest).unwrap() +} diff --git a/mruby-time/tests/smoke.rs b/mruby-time/tests/smoke.rs new file mode 100644 index 0000000..29c00c0 --- /dev/null +++ b/mruby-time/tests/smoke.rs @@ -0,0 +1,396 @@ +extern crate mruby_time; +extern crate mrubyedge; + +mod helpers; +use helpers::*; + +/// Helper: build a VM with Time initialized and a fixed Time.__source returning [sec, nsec]. +fn make_vm_with_time_source(sec: i64, nsec: u32) -> mrubyedge::yamrb::vm::VM { + use mrubyedge::yamrb::value::RObject; + use mrubyedge::yamrb::helpers::mrb_define_singleton_cmethod; + + // We need a dummy bytecode to open a VM. Use a trivial "nil" script. + let dummy = mrbc_compile("dummy_source", "nil"); + let mut rite = mrubyedge::rite::load(&dummy).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + // Override Time.__source to return [sec, nsec] + let sec_val = sec; + let nsec_val = nsec; + let time_class_obj = vm.get_const_by_name("Time").expect("Time not found"); + mrb_define_singleton_cmethod( + &mut vm, + time_class_obj, + "__source", + Box::new(move |_vm, _args| { + let arr = vec![ + RObject::integer(sec_val).to_refcount_assigned(), + RObject::integer(nsec_val as i64).to_refcount_assigned(), + ]; + Ok(RObject::array(arr).to_refcount_assigned()) + }), + ); + + vm +} + +// 1970-01-01 00:00:00 UTC (UNIX epoch) +const EPOCH_SEC: i64 = 0; +// 2024-03-14 15:09:26 UTC (Pi Day!) +// date -d "2024-03-14 15:09:26 UTC" +%s => 1710428966 +const PI_DAY_SEC: i64 = 1710428966; +// 2009-01-03 18:15:05 UTC (Bitcoin genesis block) +const GENESIS_SEC: i64 = 1231006505; + +#[test] +fn test_time_at_epoch_year() { + let code = "Time.at(0).year"; + let binary = mrbc_compile("time_epoch_year", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let year: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(year, 1970); +} + +#[test] +fn test_time_at_epoch_month() { + let code = "Time.at(0).month"; + let binary = mrbc_compile("time_epoch_month", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let v: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(v, 1); +} + +#[test] +fn test_time_at_epoch_day() { + let code = "Time.at(0).day"; + let binary = mrbc_compile("time_epoch_day", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let v: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(v, 1); +} + +#[test] +fn test_time_at_epoch_wday() { + // 1970-01-01 was Thursday (wday=4) + let code = "Time.at(0).wday"; + let binary = mrbc_compile("time_epoch_wday", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let v: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(v, 4); // Thursday +} + +#[test] +fn test_time_at_epoch_hour_min_sec() { + let code = "[Time.at(0).hour, Time.at(0).min, Time.at(0).sec]"; + let binary = mrbc_compile("time_epoch_hms", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let arr = match &result.value { + mrubyedge::yamrb::value::RValue::Array(a) => a.borrow().clone(), + _ => panic!("expected array"), + }; + assert_eq!(arr.len(), 3); + let h: i64 = arr[0].as_ref().try_into().unwrap(); + let m: i64 = arr[1].as_ref().try_into().unwrap(); + let s: i64 = arr[2].as_ref().try_into().unwrap(); + assert_eq!(h, 0); + assert_eq!(m, 0); + assert_eq!(s, 0); +} + +#[test] +fn test_time_pi_day() { + // 2024-03-14 15:09:26 UTC + let code = format!( + "[Time.at({}).year, Time.at({}).month, Time.at({}).day, Time.at({}).hour, Time.at({}).min, Time.at({}).sec]", + PI_DAY_SEC, PI_DAY_SEC, PI_DAY_SEC, PI_DAY_SEC, PI_DAY_SEC, PI_DAY_SEC + ); + let binary = mrbc_compile("time_pi_day", &code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let arr = match &result.value { + mrubyedge::yamrb::value::RValue::Array(a) => a.borrow().clone(), + _ => panic!("expected array"), + }; + assert_eq!(arr.len(), 6); + let year: i64 = arr[0].as_ref().try_into().unwrap(); + let month: i64 = arr[1].as_ref().try_into().unwrap(); + let day: i64 = arr[2].as_ref().try_into().unwrap(); + let hour: i64 = arr[3].as_ref().try_into().unwrap(); + let min: i64 = arr[4].as_ref().try_into().unwrap(); + let sec: i64 = arr[5].as_ref().try_into().unwrap(); + assert_eq!(year, 2024); + assert_eq!(month, 3); + assert_eq!(day, 14); + assert_eq!(hour, 15); + assert_eq!(min, 9); + assert_eq!(sec, 26); +} + +#[test] +fn test_time_nsec() { + let code = "Time.at(0, 123456789).nsec"; + let binary = mrbc_compile("time_nsec", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let v: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(v, 123456789); +} + +#[test] +fn test_time_to_s() { + // 1970-01-01 00:00:00 UTC + let code = "Time.at(0).to_s"; + let binary = mrbc_compile("time_to_s", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let s: String = result.as_ref().try_into().unwrap(); + assert_eq!(s, "1970-01-01 00:00:00 +0000"); +} + +#[test] +fn test_time_to_s_with_offset() { + // 1970-01-01 00:00:00 UTC, localtime with +32400 (JST +09:00) + let code = "Time.at(0).localtime(32400).to_s"; + let binary = mrbc_compile("time_to_s_offset", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let s: String = result.as_ref().try_into().unwrap(); + assert_eq!(s, "1970-01-01 09:00:00 +0900"); +} + +#[test] +fn test_time_add() { + // epoch + 3661 = 1970-01-01 01:01:01 UTC + let code = "(Time.at(0) + 3661).to_s"; + let binary = mrbc_compile("time_add", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let s: String = result.as_ref().try_into().unwrap(); + assert_eq!(s, "1970-01-01 01:01:01 +0000"); +} + +#[test] +fn test_time_sub_seconds() { + // epoch + 3600 - 1800 = 1970-01-01 00:30:00 UTC + let code = "(Time.at(3600) - 1800).to_s"; + let binary = mrbc_compile("time_sub", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let s: String = result.as_ref().try_into().unwrap(); + assert_eq!(s, "1970-01-01 00:30:00 +0000"); +} + +#[test] +fn test_time_sub_times() { + // Time.at(3600) - Time.at(0) = 3600.0 + let code = "Time.at(3600) - Time.at(0)"; + let binary = mrbc_compile("time_sub_times", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let f: f64 = result.as_ref().try_into().unwrap(); + assert!((f - 3600.0).abs() < 1e-6); +} + +#[test] +fn test_time_cmp() { + let code = "Time.at(100) <=> Time.at(50)"; + let binary = mrbc_compile("time_cmp_gt", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let v: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(v, 1); +} + +#[test] +fn test_time_cmp_eq() { + let code = "Time.at(100) <=> Time.at(100)"; + let binary = mrbc_compile("time_cmp_eq", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let v: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(v, 0); +} + +#[test] +fn test_time_cmp_lt() { + let code = "Time.at(50) <=> Time.at(100)"; + let binary = mrbc_compile("time_cmp_lt", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let v: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(v, -1); +} + +#[test] +fn test_time_now() { + // Time.now uses Time.__source; default impl is tested on non-wasm + // Just verify we get a Time object back (year >= 2020) + let code = "Time.now.year"; + let binary = mrbc_compile("time_now_year", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let year: i64 = result.as_ref().try_into().unwrap(); + assert!(year >= 2020, "year should be >= 2020, got {}", year); +} + +#[test] +fn test_time_now_overridden_source() { + // Override Time.__source to return a fixed time (Pi Day 2024) + let code = "Time.now.year"; + let binary = mrbc_compile("time_now_override", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = make_vm_with_fixed_source(&mut rite, PI_DAY_SEC, 0); + + let result = vm.run().unwrap(); + let year: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(year, 2024); +} + +fn make_vm_with_fixed_source( + rite: &mut mrubyedge::rite::Rite, + sec: i64, + nsec: u32, +) -> mrubyedge::yamrb::vm::VM { + use mrubyedge::yamrb::helpers::mrb_define_singleton_cmethod; + use mrubyedge::yamrb::value::RObject; + + let mut vm = mrubyedge::yamrb::vm::VM::open(rite); + mruby_time::init_time(&mut vm); + + let time_class_obj = vm.get_const_by_name("Time").expect("Time not found"); + mrb_define_singleton_cmethod( + &mut vm, + time_class_obj, + "__source", + Box::new(move |_vm, _args| { + let arr = vec![ + RObject::integer(sec).to_refcount_assigned(), + RObject::integer(nsec as i64).to_refcount_assigned(), + ]; + Ok(RObject::array(arr).to_refcount_assigned()) + }), + ); + + vm +} + +#[test] +fn test_time_utc_offset() { + let code = "Time.at(0).utc_offset"; + let binary = mrbc_compile("time_utc_offset", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let v: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(v, 0); +} + +#[test] +fn test_time_localtime_offset() { + // JST = UTC+9 = +32400 seconds + let code = "Time.at(0).localtime(32400).utc_offset"; + let binary = mrbc_compile("time_localtime", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let v: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(v, 32400); +} + +#[test] +fn test_time_to_i() { + let code = "Time.at(12345).to_i"; + let binary = mrbc_compile("time_to_i", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let v: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(v, 12345); +} + +#[test] +fn test_time_to_f() { + let code = "Time.at(12345, 500000000).to_f"; + let binary = mrbc_compile("time_to_f", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let v: f64 = result.as_ref().try_into().unwrap(); + assert!((v - 12345.5).abs() < 1e-6); +} + +#[test] +fn test_pi_day_wday() { + // 2024-03-14 was a Thursday (wday=4) + let code = format!("Time.at({}).wday", PI_DAY_SEC); + let binary = mrbc_compile("time_pi_day_wday", &code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mruby_time::init_time(&mut vm); + + let result = vm.run().unwrap(); + let v: i64 = result.as_ref().try_into().unwrap(); + assert_eq!(v, 4); // Thursday +} diff --git a/mrubyedge/src/rite/insn.rs b/mrubyedge/src/rite/insn.rs index fb3380c..a7eb8d8 100644 --- a/mrubyedge/src/rite/insn.rs +++ b/mrubyedge/src/rite/insn.rs @@ -507,7 +507,7 @@ fn fetch_bss(bin: &mut &[u8]) -> Result { } let a = bin[1]; let s1 = ((bin[2] as u16) << 8) | bin[3] as u16; - let s2 = ((bin[2] as u16) << 8) | bin[5] as u16; + let s2 = ((bin[4] as u16) << 8) | bin[5] as u16; let operand = Fetched::BSS(a, s1, s2); *bin = &bin[6..]; diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 982c769..7b69f27 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -634,6 +634,27 @@ impl TryFrom<&RObject> for (i32, i32, i32, i32) { } } +impl TryFrom<&RObject> for (i64, u32) { + type Error = Error; + + fn try_from(value: &RObject) -> Result { + match &value.value { + RValue::Array(ar) => { + let vec = ar.borrow(); + if vec.len() < 2 { + return Err(Error::ArgumentError( + "expected array of at least length 2".to_string(), + )); + } + let first: i64 = vec[0].as_ref().try_into()?; + let second: u32 = vec[1].as_ref().try_into()?; + Ok((first, second)) + } + _ => Err(Error::TypeMismatch), + } + } +} + impl TryFrom<&RObject> for u32 { type Error = Error; From 3bc1abc5eb4d6f4b7985575b7cb376f08a299cf9 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 22 Feb 2026 00:20:49 +0900 Subject: [PATCH 259/314] Small fixes --- mruby-time/src/lib.rs | 95 ++++++++++++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 28 deletions(-) diff --git a/mruby-time/src/lib.rs b/mruby-time/src/lib.rs index 443b72d..0a405d0 100644 --- a/mruby-time/src/lib.rs +++ b/mruby-time/src/lib.rs @@ -11,6 +11,9 @@ use mrubyedge::{ }, }; +/// Type alias for datetime parts: (year, month, day, wday, hour, min, sec) +type DateTimeParts = (i32, u32, u32, u32, u32, u32, u32); + /// Rust-side representation of a Ruby Time object. /// Stores seconds and nanoseconds since UNIX epoch, and UTC offset in seconds. #[derive(Debug, Clone)] @@ -22,7 +25,7 @@ pub struct RTimeData { /// UTC offset in seconds (e.g. +9h = 32400, -5h = -18000) pub utc_offset: i32, /// Cached result of to_datetime_parts() — computed lazily, interior-mutable. - cached_parts: Cell>, + cached_parts: Cell>, } impl RTimeData { @@ -43,7 +46,7 @@ impl RTimeData { /// Decompose into (year, month, day, wday, hour, min, sec_in_day). /// Uses the proleptic Gregorian calendar algorithm. /// Result is cached on first call via interior mutability. - pub fn to_datetime_parts(&self) -> (i32, u32, u32, u32, u32, u32, u32) { + pub fn to_datetime_parts(&self) -> DateTimeParts { if let Some(parts) = self.cached_parts.get() { return parts; } @@ -110,9 +113,7 @@ fn get_time_data(obj: &Rc) -> Result { .ok_or_else(|| Error::RuntimeError("Invalid Time data".to_string()))?; Ok(time.clone()) } - _ => Err(Error::RuntimeError( - "Expected a Time object".to_string(), - )), + _ => Err(Error::RuntimeError("Expected a Time object".to_string())), } } @@ -127,9 +128,7 @@ fn make_time_object(vm: &mut VM, time_data: RTimeData) -> Rc { }; let rdata = Rc::new(RData { class, - data: RefCell::new(Some(Rc::new( - Box::new(time_data) as Box - ))), + data: RefCell::new(Some(Rc::new(Box::new(time_data) as Box))), ref_count: 1, }); Rc::new(RObject { @@ -149,7 +148,8 @@ fn make_time_object(vm: &mut VM, time_data: RTimeData) -> Rc { /// Calls Time.__source to get [sec, nsec], then creates a Time object. /// utc_offset defaults to 0 (UTC). fn mrb_time_now(vm: &mut VM, _args: &[Rc]) -> Result, Error> { - let time_class_obj = vm.get_const_by_name("Time") + let time_class_obj = vm + .get_const_by_name("Time") .ok_or_else(|| Error::RuntimeError("Time class not found".to_string()))?; // Call Time.__source -> [sec, nsec] @@ -269,7 +269,10 @@ fn mrb_time_add(vm: &mut VM, args: &[Rc]) -> Result, Error> let new_nsec = new_nsec.rem_euclid(1_000_000_000) as u32; let new_sec = t.sec + delta_sec + carry; - Ok(make_time_object(vm, RTimeData::new(new_sec, new_nsec, t.utc_offset))) + Ok(make_time_object( + vm, + RTimeData::new(new_sec, new_nsec, t.utc_offset), + )) } /// Time#- (sec as integer or float), also supports Time - Time -> Float (seconds) @@ -284,13 +287,13 @@ fn mrb_time_sub(vm: &mut VM, args: &[Rc]) -> Result, Error> let t = get_time_data(&self_obj)?; // Check if rhs is a Time object - if let RValue::Data(_) = &args[0].value { - if let Ok(rhs) = get_time_data(&args[0]) { - // Time - Time -> Float (difference in seconds) - let sec_diff = (t.sec - rhs.sec) as f64 - + (t.nsec as f64 - rhs.nsec as f64) / 1_000_000_000.0; - return Ok(RObject::float(sec_diff).to_refcount_assigned()); - } + if let RValue::Data(_) = &args[0].value + && let Ok(rhs) = get_time_data(&args[0]) + { + // Time - Time -> Float (difference in seconds) + let sec_diff = + (t.sec - rhs.sec) as f64 + (t.nsec as f64 - rhs.nsec as f64) / 1_000_000_000.0; + return Ok(RObject::float(sec_diff).to_refcount_assigned()); } let (delta_sec, delta_nsec) = float_to_sec_nsec(&args[0])?; @@ -299,7 +302,10 @@ fn mrb_time_sub(vm: &mut VM, args: &[Rc]) -> Result, Error> let new_nsec = new_nsec.rem_euclid(1_000_000_000) as u32; let new_sec = t.sec - delta_sec + carry; - Ok(make_time_object(vm, RTimeData::new(new_sec, new_nsec, t.utc_offset))) + Ok(make_time_object( + vm, + RTimeData::new(new_sec, new_nsec, t.utc_offset), + )) } /// Time#<=> (compare with another Time object) @@ -350,7 +356,10 @@ fn mrb_time_localtime(vm: &mut VM, args: &[Rc]) -> Result, get_integer_or_float_as_i64(&args[0])? as i32 }; - Ok(make_time_object(vm, RTimeData::new(t.sec, t.nsec, new_offset))) + Ok(make_time_object( + vm, + RTimeData::new(t.sec, t.nsec, new_offset), + )) } /// Time#to_i -> sec @@ -407,7 +416,9 @@ fn get_integer_or_float_as_i64(obj: &RObject) -> Result { match &obj.value { RValue::Integer(i) => Ok(*i), RValue::Float(f) => Ok(*f as i64), - _ => Err(Error::ArgumentError("expected Integer or Float".to_string())), + _ => Err(Error::ArgumentError( + "expected Integer or Float".to_string(), + )), } } @@ -415,17 +426,23 @@ fn get_integer_or_float_as_u32(obj: &RObject) -> Result { match &obj.value { RValue::Integer(i) => { if *i < 0 { - return Err(Error::ArgumentError("nsec must be non-negative".to_string())); + return Err(Error::ArgumentError( + "nsec must be non-negative".to_string(), + )); } Ok(*i as u32) } RValue::Float(f) => { if *f < 0.0 { - return Err(Error::ArgumentError("nsec must be non-negative".to_string())); + return Err(Error::ArgumentError( + "nsec must be non-negative".to_string(), + )); } Ok(*f as u32) } - _ => Err(Error::ArgumentError("expected Integer or Float".to_string())), + _ => Err(Error::ArgumentError( + "expected Integer or Float".to_string(), + )), } } @@ -438,7 +455,9 @@ fn float_to_sec_nsec(obj: &RObject) -> Result<(i64, u32), Error> { let nsec = (f.fract().abs() * 1_000_000_000.0).round() as u32; Ok((sec, nsec)) } - _ => Err(Error::ArgumentError("expected Integer or Float".to_string())), + _ => Err(Error::ArgumentError( + "expected Integer or Float".to_string(), + )), } } @@ -471,9 +490,24 @@ pub fn init_time(vm: &mut VM) { mrb_define_cmethod(vm, time_class.clone(), "+", Box::new(mrb_time_add)); mrb_define_cmethod(vm, time_class.clone(), "-", Box::new(mrb_time_sub)); mrb_define_cmethod(vm, time_class.clone(), "<=>", Box::new(mrb_time_cmp)); - mrb_define_cmethod(vm, time_class.clone(), "utc_offset", Box::new(mrb_time_utc_offset)); - mrb_define_cmethod(vm, time_class.clone(), "gmt_offset", Box::new(mrb_time_utc_offset)); - mrb_define_cmethod(vm, time_class.clone(), "localtime", Box::new(mrb_time_localtime)); + mrb_define_cmethod( + vm, + time_class.clone(), + "utc_offset", + Box::new(mrb_time_utc_offset), + ); + mrb_define_cmethod( + vm, + time_class.clone(), + "gmt_offset", + Box::new(mrb_time_utc_offset), + ); + mrb_define_cmethod( + vm, + time_class.clone(), + "localtime", + Box::new(mrb_time_localtime), + ); mrb_define_cmethod(vm, time_class.clone(), "to_i", Box::new(mrb_time_to_i)); mrb_define_cmethod(vm, time_class.clone(), "to_f", Box::new(mrb_time_to_f)); @@ -484,7 +518,12 @@ pub fn init_time(vm: &mut VM) { let time_class_obj_for_source = vm .get_const_by_name("Time") .expect("Time class not found after definition"); - mrb_define_class_cmethod_on_obj(vm, time_class_obj_for_source, "__source", Box::new(mrb_time_source_default)); + mrb_define_class_cmethod_on_obj( + vm, + time_class_obj_for_source, + "__source", + Box::new(mrb_time_source_default), + ); } } From 9b66681d045291009d69d803888dc7ef796eb817 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 22 Feb 2026 00:26:41 +0900 Subject: [PATCH 260/314] Fix in conditions --- Cargo.toml | 8 +++++++- mruby-time/src/lib.rs | 18 ++++++++++-------- mruby-time/tests/smoke.rs | 4 +++- mrubyedge/tests/kargs.rs | 2 +- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 450223b..ee1daf9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,4 +1,10 @@ [workspace] resolver = "2" -members = ["mruby-math", "mruby-serde-json", "mruby-time", "mrubyedge", "mrubyedge-cli"] +members = [ + "mruby-math", + "mruby-serde-json", + "mruby-time", + "mrubyedge", + "mrubyedge-cli", +] diff --git a/mruby-time/src/lib.rs b/mruby-time/src/lib.rs index 0a405d0..468f24e 100644 --- a/mruby-time/src/lib.rs +++ b/mruby-time/src/lib.rs @@ -383,14 +383,16 @@ fn mrb_time_to_f(vm: &mut VM, _args: &[Rc]) -> Result, Erro /// Default implementation of Time.__source using std::time. /// Returns [sec, nsec] as a Ruby array. -/// Only compiled on non-wasm targets (wasi feature disabled implies std::time available). -#[cfg(not(target_arch = "wasm32"))] +/// Compiled on non-wasm targets, and also on wasm32-wasi where std::time is available. +#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] fn mrb_time_source_default(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { use std::time::{SystemTime, UNIX_EPOCH}; let now = SystemTime::now(); - let unixtime = now - .duration_since(UNIX_EPOCH) - .map_err(|_| Error::RuntimeError("system time before UNIX EPOCH".to_string()))?; + let unixtime = now.duration_since(UNIX_EPOCH).map_err(|_| { + Error::RuntimeError( + "system time before UNIX EPOCH -- are you running this from the past?".to_string(), + ) + })?; let sec = unixtime.as_secs() as i64; let nsec = unixtime.subsec_nanos() as i64; let arr = vec![ @@ -511,8 +513,8 @@ pub fn init_time(vm: &mut VM) { mrb_define_cmethod(vm, time_class.clone(), "to_i", Box::new(mrb_time_to_i)); mrb_define_cmethod(vm, time_class.clone(), "to_f", Box::new(mrb_time_to_f)); - // Register default Time.__source on non-wasm targets - #[cfg(not(target_arch = "wasm32"))] + // Register default Time.__source on non-wasm targets, and also on wasm32-wasi + #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] { let _time_class_obj = RObject::class(time_class, vm); let time_class_obj_for_source = vm @@ -528,7 +530,7 @@ pub fn init_time(vm: &mut VM) { } /// Helper: define a singleton (class-side) cmethod on a class RObject. -#[cfg(not(target_arch = "wasm32"))] +#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] fn mrb_define_class_cmethod_on_obj( vm: &mut VM, class_obj: Rc, diff --git a/mruby-time/tests/smoke.rs b/mruby-time/tests/smoke.rs index 29c00c0..484ca03 100644 --- a/mruby-time/tests/smoke.rs +++ b/mruby-time/tests/smoke.rs @@ -6,8 +6,8 @@ use helpers::*; /// Helper: build a VM with Time initialized and a fixed Time.__source returning [sec, nsec]. fn make_vm_with_time_source(sec: i64, nsec: u32) -> mrubyedge::yamrb::vm::VM { - use mrubyedge::yamrb::value::RObject; use mrubyedge::yamrb::helpers::mrb_define_singleton_cmethod; + use mrubyedge::yamrb::value::RObject; // We need a dummy bytecode to open a VM. Use a trivial "nil" script. let dummy = mrbc_compile("dummy_source", "nil"); @@ -36,11 +36,13 @@ fn make_vm_with_time_source(sec: i64, nsec: u32) -> mrubyedge::yamrb::vm::VM { } // 1970-01-01 00:00:00 UTC (UNIX epoch) +#[allow(unused)] const EPOCH_SEC: i64 = 0; // 2024-03-14 15:09:26 UTC (Pi Day!) // date -d "2024-03-14 15:09:26 UTC" +%s => 1710428966 const PI_DAY_SEC: i64 = 1710428966; // 2009-01-03 18:15:05 UTC (Bitcoin genesis block) +#[allow(unused)] const GENESIS_SEC: i64 = 1231006505; #[test] diff --git a/mrubyedge/tests/kargs.rs b/mrubyedge/tests/kargs.rs index e13a645..cfa3ba6 100644 --- a/mrubyedge/tests/kargs.rs +++ b/mrubyedge/tests/kargs.rs @@ -92,7 +92,7 @@ fn keyword_args_nested_call_test() { let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); let result = vm.run().unwrap(); - let (got1, got2) = result.as_ref().try_into().unwrap(); + let (got1, got2): (i32, i32) = result.as_ref().try_into().unwrap(); assert_eq!(got1, 28); // 5 * 2 + 6 * 3 assert_eq!(got2, 34); // 5 * 2 + 6 * 4 } From 242690f9c41925bcd10c3cb9ac548854095887c9 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 22 Feb 2026 00:28:49 +0900 Subject: [PATCH 261/314] CI --- .github/workflows/mruby-time.yml | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 .github/workflows/mruby-time.yml diff --git a/.github/workflows/mruby-time.yml b/.github/workflows/mruby-time.yml new file mode 100644 index 0000000..dcde48a --- /dev/null +++ b/.github/workflows/mruby-time.yml @@ -0,0 +1,41 @@ +name: mruby-time CI + +on: + push: + branches: + - master + paths: + - 'mruby-time/**' + pull_request: + branches: + - master + paths: + - 'mruby-time/**' + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + BUILD_TARGET: [release] + steps: + - uses: actions/checkout@v5 + - name: Cache + uses: actions/cache@v4 + with: + path: | + ~/.cargo/registry + ~/.cargo/git + target + key: ${{ runner.os }}-cargo-time-${{ hashFiles('**/Cargo.lock') }} + - name: Run formatter + run: | + cargo fmt -p mruby-time --check + - name: Run tests for "${{ matrix.BUILD_TARGET }}" profile + run: | + cargo test -p mruby-time \ + --profile ${{ matrix.BUILD_TARGET }} + - name: Build binaries for "${{ matrix.BUILD_TARGET }}" profile + run: | + cargo build -p mruby-time \ + --profile ${{ matrix.BUILD_TARGET }} From d820285ed294ba78866c48e95271b7d65ab195ff Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 22 Feb 2026 00:33:40 +0900 Subject: [PATCH 262/314] Bump version to 1.1.7 --- Cargo.lock | 24 ++++++++++++------------ mrubyedge/Cargo.toml | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 455455e..cade40d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -625,7 +625,7 @@ name = "mruby-math" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.6", ] [[package]] @@ -633,7 +633,7 @@ name = "mruby-serde-json" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.6", "serde", "serde_json", ] @@ -643,18 +643,15 @@ name = "mruby-time" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.6", + "mrubyedge 1.1.7", ] [[package]] name = "mrubyedge" version = "1.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adbb814b82d92d05c2803ea2bb5f7dea364e9b1af3b8f06d7dfd352374f41c3b" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -664,10 +661,13 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adbb814b82d92d05c2803ea2bb5f7dea364e9b1af3b8f06d7dfd352374f41c3b" +version = "1.1.7" dependencies = [ + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -683,7 +683,7 @@ dependencies = [ "clap", "crossterm", "mruby-compiler2-sys", - "mrubyedge 1.1.6 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.6", "nom", "rand", "syn", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index bf443ae..af4bd52 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.1.6" +version = "1.1.7" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From 2c6f5f3a74d2adf20d49e064d218a732f3ab37eb Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 22 Feb 2026 00:36:44 +0900 Subject: [PATCH 263/314] Rename crates... --- Cargo.lock | 112 ++++++++++++++++++------------------ mruby-math/Cargo.toml | 2 +- mruby-serde-json/Cargo.toml | 2 +- mruby-time/Cargo.toml | 6 +- mrubyedge-cli/Cargo.toml | 4 +- 5 files changed, 63 insertions(+), 63 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cade40d..81712ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -174,9 +174,9 @@ checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bumpalo" -version = "3.19.1" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dd9dc738b7a8311c7ade152424974d8115f2cdad61e8dab8dac9f2362298510" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "cast" @@ -249,9 +249,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.58" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63be97961acde393029492ce0be7a1af7e323e6bae9511ebfac33751be5e6806" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", "clap_derive", @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.58" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f13174bda5dfd69d7e947827e5af4b0f2f94a4a3ee92912fba07a66150f21e2" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -506,9 +506,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.85" +version = "0.3.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c942ebf8e95485ca0d52d97da7c5a2c387d0e7f0ba4c35e93bfcaee045955b3" +checksum = "93f0862381daaec758576dcc22eb7bbf4d7efd67328553f3b45a412a51a3fb21" dependencies = [ "once_cell", "wasm-bindgen", @@ -620,38 +620,15 @@ dependencies = [ "libc", ] -[[package]] -name = "mruby-math" -version = "0.1.0" -dependencies = [ - "mec-mrbc-sys", - "mrubyedge 1.1.6", -] - -[[package]] -name = "mruby-serde-json" -version = "0.1.0" -dependencies = [ - "mec-mrbc-sys", - "mrubyedge 1.1.6", - "serde", - "serde_json", -] - -[[package]] -name = "mruby-time" -version = "0.1.0" -dependencies = [ - "mec-mrbc-sys", - "mrubyedge 1.1.7", -] - [[package]] name = "mrubyedge" -version = "1.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adbb814b82d92d05c2803ea2bb5f7dea364e9b1af3b8f06d7dfd352374f41c3b" +version = "1.1.7" dependencies = [ + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -662,12 +639,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f7f736089b8158f3a9b8af4706fa097840e3cb76654b8736cda557514eb3f9" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -677,18 +651,44 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.1.6" +version = "1.1.7" dependencies = [ "askama", "clap", "crossterm", "mruby-compiler2-sys", - "mrubyedge 1.1.6", + "mrubyedge 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", ] +[[package]] +name = "mrubyedge-math" +version = "0.1.0" +dependencies = [ + "mec-mrbc-sys", + "mrubyedge 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "mrubyedge-serde-json" +version = "0.1.0" +dependencies = [ + "mec-mrbc-sys", + "mrubyedge 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "serde", + "serde_json", +] + +[[package]] +name = "mrubyedge-time" +version = "0.1.0" +dependencies = [ + "mec-mrbc-sys", + "mrubyedge 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nom" version = "7.1.3" @@ -1074,9 +1074,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "syn" -version = "2.0.116" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3df424c70518695237746f84cede799c9c58fcb37450d7b23716568cc8bc69cb" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -1138,9 +1138,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.108" +version = "0.2.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64024a30ec1e37399cf85a7ffefebdb72205ca1c972291c51512360d90bd8566" +checksum = "1de241cdc66a9d91bd84f097039eb140cdc6eec47e0cdbaf9d932a1dd6c35866" dependencies = [ "cfg-if", "once_cell", @@ -1151,9 +1151,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.108" +version = "0.2.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "008b239d9c740232e71bd39e8ef6429d27097518b6b30bdf9086833bd5b6d608" +checksum = "e12fdf6649048f2e3de6d7d5ff3ced779cdedee0e0baffd7dff5cdfa3abc8a52" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1161,9 +1161,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.108" +version = "0.2.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5256bae2d58f54820e6490f9839c49780dff84c65aeab9e772f15d5f0e913a55" +checksum = "0e63d1795c565ac3462334c1e396fd46dbf481c40f51f5072c310717bc4fb309" dependencies = [ "bumpalo", "proc-macro2", @@ -1174,18 +1174,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.108" +version = "0.2.110" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f01b580c9ac74c8d8f0c0e4afb04eeef2acf145458e52c03845ee9cd23e3d12" +checksum = "e9f9cdac23a5ce71f6bf9f8824898a501e511892791ea2a0c6b8568c68b9cb53" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.85" +version = "0.3.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "312e32e551d92129218ea9a2452120f4aabc03529ef03e4d0d82fb2780608598" +checksum = "f2c7c5718134e770ee62af3b6b4a84518ec10101aad610c024b64d6ff29bb1ff" dependencies = [ "js-sys", "wasm-bindgen", diff --git a/mruby-math/Cargo.toml b/mruby-math/Cargo.toml index 07c19e2..e2dfc3e 100644 --- a/mruby-math/Cargo.toml +++ b/mruby-math/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mruby-math" +name = "mrubyedge-math" version = "0.1.0" edition = "2024" authors = ["Uchio Kondo "] diff --git a/mruby-serde-json/Cargo.toml b/mruby-serde-json/Cargo.toml index cc4214c..487660a 100644 --- a/mruby-serde-json/Cargo.toml +++ b/mruby-serde-json/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mruby-serde-json" +name = "mrubyedge-serde-json" version = "0.1.0" edition = "2024" authors = ["Uchio Kondo "] diff --git a/mruby-time/Cargo.toml b/mruby-time/Cargo.toml index 0665380..3df8bb0 100644 --- a/mruby-time/Cargo.toml +++ b/mruby-time/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "mruby-time" +name = "mrubyedge-time" version = "0.1.0" edition = "2024" authors = ["Uchio Kondo "] @@ -7,10 +7,10 @@ description = "mruby-time provides Time class for mruby/edge" license = "BSD-3-Clause" [dependencies] -mrubyedge = { path = "../mrubyedge" } +mrubyedge = "1.1.7" [dev-dependencies] -mrubyedge = { path = "../mrubyedge", features = ["default"] } +mrubyedge = { version = "1.1.7", features = ["default"] } mec-mrbc-sys = "3.3.1" [features] diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 0514d00..84099ac 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.1.6" +version = "1.1.7" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -15,7 +15,7 @@ path = "src/main.rs" clap = { version = "4.5", features = ["derive"] } crossterm = "0.28" mruby-compiler2-sys = "0.3.0" -mrubyedge = { version = "1.1.6", features = [ +mrubyedge = { version = "1.1.7", features = [ "default", "mruby-random", "mruby-regexp", From 8946cdd5c5f9e7365e12fa752138d92b422a6c78 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 22 Feb 2026 00:43:13 +0900 Subject: [PATCH 264/314] Fix tests --- Cargo.lock | 20 ++++++++++++++++++++ mruby-math/tests/functions.rs | 2 +- mruby-math/tests/sine.rs | 2 +- mruby-math/tests/smoke.rs | 2 +- mruby-serde-json/tests/dump.rs | 22 +++++++++++----------- mruby-serde-json/tests/load.rs | 26 +++++++++++++------------- mruby-time/tests/smoke.rs | 2 +- mrubyedge-cli/Cargo.toml | 2 ++ 8 files changed, 50 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 81712ba..064658f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -658,6 +658,8 @@ dependencies = [ "crossterm", "mruby-compiler2-sys", "mrubyedge 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge-math 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge-time 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", @@ -671,6 +673,15 @@ dependencies = [ "mrubyedge 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "mrubyedge-math" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faac1fa8bf95e7f02ca71824aebf139ed1dd20696e42e8e1c4cb8374dde2dbe" +dependencies = [ + "mrubyedge 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "mrubyedge-serde-json" version = "0.1.0" @@ -689,6 +700,15 @@ dependencies = [ "mrubyedge 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "mrubyedge-time" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50f7bd2889e92c1b6e98915d091e487a340fd138cf9548b49718c9099d28a79" +dependencies = [ + "mrubyedge 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "nom" version = "7.1.3" diff --git a/mruby-math/tests/functions.rs b/mruby-math/tests/functions.rs index e6d74fb..6c860ab 100644 --- a/mruby-math/tests/functions.rs +++ b/mruby-math/tests/functions.rs @@ -1,5 +1,5 @@ -extern crate mruby_math; extern crate mrubyedge; +extern crate mrubyedge_math as mruby_math; mod helpers; use helpers::*; diff --git a/mruby-math/tests/sine.rs b/mruby-math/tests/sine.rs index f4b837f..a7104ee 100644 --- a/mruby-math/tests/sine.rs +++ b/mruby-math/tests/sine.rs @@ -1,5 +1,5 @@ -extern crate mruby_math; extern crate mrubyedge; +extern crate mrubyedge_math as mruby_math; mod helpers; use helpers::*; diff --git a/mruby-math/tests/smoke.rs b/mruby-math/tests/smoke.rs index 0d9f945..ae0c107 100644 --- a/mruby-math/tests/smoke.rs +++ b/mruby-math/tests/smoke.rs @@ -1,5 +1,5 @@ -extern crate mruby_math; extern crate mrubyedge; +extern crate mrubyedge_math as mruby_math; mod helpers; use helpers::*; diff --git a/mruby-serde-json/tests/dump.rs b/mruby-serde-json/tests/dump.rs index bba219e..dd0e27f 100644 --- a/mruby-serde-json/tests/dump.rs +++ b/mruby-serde-json/tests/dump.rs @@ -1,5 +1,5 @@ -extern crate mruby_serde_json; extern crate mrubyedge; +extern crate mrubyedge_serde_json; mod helpers; use helpers::*; @@ -12,7 +12,7 @@ fn test_json_dump_integer() { let binary = mrbc_compile("json_dump_integer", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); let json_str: String = result.as_ref().try_into().unwrap(); @@ -27,7 +27,7 @@ fn test_json_dump_string() { let binary = mrbc_compile("json_dump_string", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); let json_str: String = result.as_ref().try_into().unwrap(); @@ -42,7 +42,7 @@ fn test_json_dump_array() { let binary = mrbc_compile("json_dump_array", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); let json_str: String = result.as_ref().try_into().unwrap(); @@ -61,7 +61,7 @@ fn test_json_dump_hash() { let binary = mrbc_compile("json_dump_hash", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); let json_str: String = result.as_ref().try_into().unwrap(); @@ -86,7 +86,7 @@ fn test_json_dump_nested_structure() { let binary = mrbc_compile("json_dump_nested", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); let json_str: String = result.as_ref().try_into().unwrap(); @@ -106,7 +106,7 @@ fn test_json_dump_boolean() { let binary = mrbc_compile("json_dump_bool", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); let json_str: String = result.as_ref().try_into().unwrap(); @@ -121,7 +121,7 @@ fn test_json_dump_nil() { let binary = mrbc_compile("json_dump_nil", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); let json_str: String = result.as_ref().try_into().unwrap(); @@ -136,7 +136,7 @@ fn test_json_dump_float() { let binary = mrbc_compile("json_dump_float", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); let json_str: String = result.as_ref().try_into().unwrap(); @@ -153,7 +153,7 @@ fn test_json_dump_symbol_key() { let binary = mrbc_compile("json_dump_symbol", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); let json_str: String = result.as_ref().try_into().unwrap(); @@ -182,7 +182,7 @@ fn test_json_dump_to_json() { let binary = mrbc_compile("json_dump_to_json", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); let json_str: String = result.as_ref().try_into().unwrap(); diff --git a/mruby-serde-json/tests/load.rs b/mruby-serde-json/tests/load.rs index ae3f47d..5811949 100644 --- a/mruby-serde-json/tests/load.rs +++ b/mruby-serde-json/tests/load.rs @@ -1,6 +1,6 @@ #![allow(clippy::approx_constant)] -extern crate mruby_serde_json; extern crate mrubyedge; +extern crate mrubyedge_serde_json; mod helpers; use helpers::*; @@ -13,7 +13,7 @@ fn test_json_load_integer() { let binary = mrbc_compile("json_load_integer", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); let value: i64 = result.as_ref().try_into().unwrap(); @@ -28,7 +28,7 @@ fn test_json_load_string() { let binary = mrbc_compile("json_load_string", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); let value: String = result.as_ref().try_into().unwrap(); @@ -48,7 +48,7 @@ fn test_json_load_array() { let binary = mrbc_compile("json_load_array", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); if let mrubyedge::yamrb::value::RValue::Array(arr) = &result.value { @@ -76,7 +76,7 @@ fn test_json_load_hash() { let binary = mrbc_compile("json_load_hash_name", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); let name: String = result.as_ref().try_into().unwrap(); assert_eq!(name, "Alice"); @@ -91,7 +91,7 @@ fn test_json_load_hash() { let binary2 = mrbc_compile("json_load_hash_age", code2); let mut rite2 = mrubyedge::rite::load(&binary2).unwrap(); let mut vm2 = mrubyedge::yamrb::vm::VM::open(&mut rite2); - mruby_serde_json::init_json(&mut vm2); + mrubyedge_serde_json::init_json(&mut vm2); let result2 = vm2.run().unwrap(); let age: i64 = result2.as_ref().try_into().unwrap(); assert_eq!(age, 30); @@ -117,7 +117,7 @@ fn test_json_load_nested_structure() { let binary = mrbc_compile("json_load_nested", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); let name: String = result.as_ref().try_into().unwrap(); @@ -141,7 +141,7 @@ fn test_json_load_nested_structure() { let binary2 = mrbc_compile("json_load_nested2", code2); let mut rite2 = mrubyedge::rite::load(&binary2).unwrap(); let mut vm2 = mrubyedge::yamrb::vm::VM::open(&mut rite2); - mruby_serde_json::init_json(&mut vm2); + mrubyedge_serde_json::init_json(&mut vm2); let result2 = vm2.run().unwrap(); let name2: String = result2.as_ref().try_into().unwrap(); assert_eq!(name2, "Carol"); @@ -155,7 +155,7 @@ fn test_json_load_boolean() { let binary = mrbc_compile("json_load_bool", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); let value: bool = result.as_ref().try_into().unwrap(); @@ -170,7 +170,7 @@ fn test_json_load_boolean_2() { let binary = mrbc_compile("json_load_bool_2", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); let value: bool = result.as_ref().try_into().unwrap(); @@ -185,7 +185,7 @@ fn test_json_load_nil() { let binary = mrbc_compile("json_load_nil", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); assert!(result.is_nil()); @@ -199,7 +199,7 @@ fn test_json_load_float() { let binary = mrbc_compile("json_load_float", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); let value: f64 = result.as_ref().try_into().unwrap(); @@ -217,7 +217,7 @@ fn test_json_load_key() { let binary = mrbc_compile("json_load_key", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); - mruby_serde_json::init_json(&mut vm); + mrubyedge_serde_json::init_json(&mut vm); let result = vm.run().unwrap(); let value: String = result.as_ref().try_into().unwrap(); diff --git a/mruby-time/tests/smoke.rs b/mruby-time/tests/smoke.rs index 484ca03..700819d 100644 --- a/mruby-time/tests/smoke.rs +++ b/mruby-time/tests/smoke.rs @@ -1,5 +1,5 @@ -extern crate mruby_time; extern crate mrubyedge; +extern crate mrubyedge_time as mruby_time; mod helpers; use helpers::*; diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 84099ac..4fcd2cc 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -20,6 +20,8 @@ mrubyedge = { version = "1.1.7", features = [ "mruby-random", "mruby-regexp", ] } +mrubyedge-math = "0.1.0" +mrubyedge-time = "0.1.0" rand = "0.9.2" nom = "7.1.3" askama = "0.12.1" From c75851e10e82e1323b6dd287ae55983744248e2f Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 22 Feb 2026 00:46:04 +0900 Subject: [PATCH 265/314] Update REPL to initialize math and time gems --- mrubyedge-cli/src/subcommands/repl.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mrubyedge-cli/src/subcommands/repl.rs b/mrubyedge-cli/src/subcommands/repl.rs index 0270679..742f5ba 100644 --- a/mrubyedge-cli/src/subcommands/repl.rs +++ b/mrubyedge-cli/src/subcommands/repl.rs @@ -39,6 +39,8 @@ pub fn execute(args: ReplArgs) -> Result<(), Box> { }; let mut rite = mrubyedge::rite::load(&mrb_bin)?; let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + mrubyedge_math::init_math(&mut vm); + mrubyedge_time::init_time(&mut vm); // Enable raw mode terminal::enable_raw_mode()?; From 60f81f9b02a2b840a6531440d880f09fc2406c68 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 22 Feb 2026 00:58:21 +0900 Subject: [PATCH 266/314] Get localtime from source --- Cargo.lock | 3 ++- mruby-time/Cargo.toml | 5 +++-- mruby-time/src/lib.rs | 30 ++++++++++++++++++++++++------ mruby-time/tests/smoke.rs | 4 +++- mrubyedge/src/yamrb/value.rs | 22 ++++++++++++++++++++++ 5 files changed, 54 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 064658f..20f75ca 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -696,8 +696,9 @@ dependencies = [ name = "mrubyedge-time" version = "0.1.0" dependencies = [ + "libc", "mec-mrbc-sys", - "mrubyedge 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.7", ] [[package]] diff --git a/mruby-time/Cargo.toml b/mruby-time/Cargo.toml index 3df8bb0..e3efba1 100644 --- a/mruby-time/Cargo.toml +++ b/mruby-time/Cargo.toml @@ -7,10 +7,11 @@ description = "mruby-time provides Time class for mruby/edge" license = "BSD-3-Clause" [dependencies] -mrubyedge = "1.1.7" +mrubyedge = { path = "../mrubyedge" } +libc = "0.2" [dev-dependencies] -mrubyedge = { version = "1.1.7", features = ["default"] } +mrubyedge = { path = "../mrubyedge", features = ["default"] } mec-mrbc-sys = "3.3.1" [features] diff --git a/mruby-time/src/lib.rs b/mruby-time/src/lib.rs index 468f24e..a2f6d6a 100644 --- a/mruby-time/src/lib.rs +++ b/mruby-time/src/lib.rs @@ -145,18 +145,17 @@ fn make_time_object(vm: &mut VM, time_data: RTimeData) -> Rc { // --------------------------------------------------------------------------- /// Time.now -/// Calls Time.__source to get [sec, nsec], then creates a Time object. -/// utc_offset defaults to 0 (UTC). +/// Calls Time.__source to get [sec, nsec, utc_offset], then creates a Time object. fn mrb_time_now(vm: &mut VM, _args: &[Rc]) -> Result, Error> { let time_class_obj = vm .get_const_by_name("Time") .ok_or_else(|| Error::RuntimeError("Time class not found".to_string()))?; - // Call Time.__source -> [sec, nsec] + // Call Time.__source -> [sec, nsec, utc_offset] let source = mrb_funcall(vm, Some(time_class_obj), "__source", &[])?; - let (sec, nsec) = source.as_ref().try_into()?; + let (sec, nsec, utc_offset) = source.as_ref().try_into()?; - Ok(make_time_object(vm, RTimeData::new(sec, nsec, 0))) + Ok(make_time_object(vm, RTimeData::new(sec, nsec, utc_offset))) } /// Time.at(sec) or Time.at(sec, nsec) @@ -382,7 +381,8 @@ fn mrb_time_to_f(vm: &mut VM, _args: &[Rc]) -> Result, Erro // --------------------------------------------------------------------------- /// Default implementation of Time.__source using std::time. -/// Returns [sec, nsec] as a Ruby array. +/// Returns [sec, nsec, utc_offset] as a Ruby array. +/// utc_offset is the local timezone offset in seconds (e.g. JST = +32400). /// Compiled on non-wasm targets, and also on wasm32-wasi where std::time is available. #[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))] fn mrb_time_source_default(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { @@ -395,13 +395,31 @@ fn mrb_time_source_default(_vm: &mut VM, _args: &[Rc]) -> Result i32 { + use std::time::{SystemTime, UNIX_EPOCH}; + let t = SystemTime::now() + .duration_since(UNIX_EPOCH) + .map(|d| d.as_secs() as libc::time_t) + .unwrap_or(0); + unsafe { + let mut tm: libc::tm = std::mem::zeroed(); + libc::localtime_r(&t, &mut tm); + tm.tm_gmtoff as i32 + } +} + // --------------------------------------------------------------------------- // Helper utilities // --------------------------------------------------------------------------- diff --git a/mruby-time/tests/smoke.rs b/mruby-time/tests/smoke.rs index 700819d..b259a39 100644 --- a/mruby-time/tests/smoke.rs +++ b/mruby-time/tests/smoke.rs @@ -15,7 +15,7 @@ fn make_vm_with_time_source(sec: i64, nsec: u32) -> mrubyedge::yamrb::vm::VM { let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); mruby_time::init_time(&mut vm); - // Override Time.__source to return [sec, nsec] + // Override Time.__source to return [sec, nsec, utc_offset] let sec_val = sec; let nsec_val = nsec; let time_class_obj = vm.get_const_by_name("Time").expect("Time not found"); @@ -27,6 +27,7 @@ fn make_vm_with_time_source(sec: i64, nsec: u32) -> mrubyedge::yamrb::vm::VM { let arr = vec![ RObject::integer(sec_val).to_refcount_assigned(), RObject::integer(nsec_val as i64).to_refcount_assigned(), + RObject::integer(0).to_refcount_assigned(), // utc_offset = 0 ]; Ok(RObject::array(arr).to_refcount_assigned()) }), @@ -322,6 +323,7 @@ fn make_vm_with_fixed_source( let arr = vec![ RObject::integer(sec).to_refcount_assigned(), RObject::integer(nsec as i64).to_refcount_assigned(), + RObject::integer(0).to_refcount_assigned(), // utc_offset = 0 ]; Ok(RObject::array(arr).to_refcount_assigned()) }), diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 7b69f27..ff9687d 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -655,6 +655,28 @@ impl TryFrom<&RObject> for (i64, u32) { } } +impl TryFrom<&RObject> for (i64, u32, i32) { + type Error = Error; + + fn try_from(value: &RObject) -> Result { + match &value.value { + RValue::Array(ar) => { + let vec = ar.borrow(); + if vec.len() < 3 { + return Err(Error::ArgumentError( + "expected array of at least length 3".to_string(), + )); + } + let first: i64 = vec[0].as_ref().try_into()?; + let second: u32 = vec[1].as_ref().try_into()?; + let third: i64 = vec[2].as_ref().try_into()?; + Ok((first, second, third as i32)) + } + _ => Err(Error::TypeMismatch), + } + } +} + impl TryFrom<&RObject> for u32 { type Error = Error; From 38726ccf95ef26bb402f6b310ecb4a7de9fddaba Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 22 Feb 2026 01:01:06 +0900 Subject: [PATCH 267/314] Bump --- Cargo.lock | 28 ++++++++++++++-------------- mrubyedge/Cargo.toml | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 20f75ca..1418527 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -623,12 +623,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f7f736089b8158f3a9b8af4706fa097840e3cb76654b8736cda557514eb3f9" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -638,10 +635,13 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f7f736089b8158f3a9b8af4706fa097840e3cb76654b8736cda557514eb3f9" +version = "1.1.8" dependencies = [ + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -657,7 +657,7 @@ dependencies = [ "clap", "crossterm", "mruby-compiler2-sys", - "mrubyedge 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.7", "mrubyedge-math 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "mrubyedge-time 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "nom", @@ -670,7 +670,7 @@ name = "mrubyedge-math" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.7", ] [[package]] @@ -679,7 +679,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7faac1fa8bf95e7f02ca71824aebf139ed1dd20696e42e8e1c4cb8374dde2dbe" dependencies = [ - "mrubyedge 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.7", ] [[package]] @@ -687,7 +687,7 @@ name = "mrubyedge-serde-json" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.7", "serde", "serde_json", ] @@ -698,7 +698,7 @@ version = "0.1.0" dependencies = [ "libc", "mec-mrbc-sys", - "mrubyedge 1.1.7", + "mrubyedge 1.1.8", ] [[package]] @@ -707,7 +707,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50f7bd2889e92c1b6e98915d091e487a340fd138cf9548b49718c9099d28a79" dependencies = [ - "mrubyedge 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.7", ] [[package]] diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index af4bd52..51c1251 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.1.7" +version = "1.1.8" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From cd4cdc9878ac0d7b8ae54c3740361c18d1eb18e4 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 22 Feb 2026 01:02:28 +0900 Subject: [PATCH 268/314] Bump all version --- Cargo.lock | 40 ++++++++++++++++++++-------------------- mruby-time/Cargo.toml | 6 +++--- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1418527..098cf87 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -622,10 +622,13 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8f7f736089b8158f3a9b8af4706fa097840e3cb76654b8736cda557514eb3f9" +version = "1.1.8" dependencies = [ + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -636,12 +639,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6637cd859a862100009f116830b1b1f6e932633d574c24480ba57983e208567" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -657,9 +657,9 @@ dependencies = [ "clap", "crossterm", "mruby-compiler2-sys", - "mrubyedge 1.1.7", + "mrubyedge 1.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "mrubyedge-math 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "mrubyedge-time 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge-time 0.1.0", "nom", "rand", "syn", @@ -670,7 +670,7 @@ name = "mrubyedge-math" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.7", + "mrubyedge 1.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -679,7 +679,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7faac1fa8bf95e7f02ca71824aebf139ed1dd20696e42e8e1c4cb8374dde2dbe" dependencies = [ - "mrubyedge 1.1.7", + "mrubyedge 1.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -687,7 +687,7 @@ name = "mrubyedge-serde-json" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.7", + "mrubyedge 1.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "serde_json", ] @@ -695,19 +695,19 @@ dependencies = [ [[package]] name = "mrubyedge-time" version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50f7bd2889e92c1b6e98915d091e487a340fd138cf9548b49718c9099d28a79" dependencies = [ - "libc", - "mec-mrbc-sys", - "mrubyedge 1.1.8", + "mrubyedge 1.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "mrubyedge-time" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50f7bd2889e92c1b6e98915d091e487a340fd138cf9548b49718c9099d28a79" +version = "0.1.1" dependencies = [ - "mrubyedge 1.1.7", + "libc", + "mec-mrbc-sys", + "mrubyedge 1.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/mruby-time/Cargo.toml b/mruby-time/Cargo.toml index e3efba1..52c6f7a 100644 --- a/mruby-time/Cargo.toml +++ b/mruby-time/Cargo.toml @@ -1,17 +1,17 @@ [package] name = "mrubyedge-time" -version = "0.1.0" +version = "0.1.1" edition = "2024" authors = ["Uchio Kondo "] description = "mruby-time provides Time class for mruby/edge" license = "BSD-3-Clause" [dependencies] -mrubyedge = { path = "../mrubyedge" } +mrubyedge = ">= 1.1.8" libc = "0.2" [dev-dependencies] -mrubyedge = { path = "../mrubyedge", features = ["default"] } +mrubyedge = { version = ">= 1.1.8", features = ["default"] } mec-mrbc-sys = "3.3.1" [features] From a69bd592db4a61fc567b80353c6c9e27c034310b Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 22 Feb 2026 01:06:45 +0900 Subject: [PATCH 269/314] CI --- .github/workflows/mruby-math.yml | 6 +++--- .github/workflows/mruby-serde-json.yml | 6 +++--- .github/workflows/mruby-time.yml | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/mruby-math.yml b/.github/workflows/mruby-math.yml index c3eaab9..39942aa 100644 --- a/.github/workflows/mruby-math.yml +++ b/.github/workflows/mruby-math.yml @@ -32,12 +32,12 @@ jobs: # run: sudo apt-get update && sudo apt-get install -y build-essential - name: Run formatter run: | - cargo fmt -p mruby-math --check + cargo fmt -p mrubyedge-math --check - name: Run tests for "${{ matrix.BUILD_TARGET }}" profile run: | - cargo test -p mruby-math \ + cargo test -p mrubyedge-math \ --profile ${{ matrix.BUILD_TARGET }} - name: Build binaries for "${{ matrix.BUILD_TARGET }}" profile run: | - cargo build -p mruby-math \ + cargo build -p mrubyedge-math \ --profile ${{ matrix.BUILD_TARGET }} diff --git a/.github/workflows/mruby-serde-json.yml b/.github/workflows/mruby-serde-json.yml index 38a4322..109909b 100644 --- a/.github/workflows/mruby-serde-json.yml +++ b/.github/workflows/mruby-serde-json.yml @@ -32,12 +32,12 @@ jobs: # run: sudo apt-get update && sudo apt-get install -y build-essential - name: Run formatter run: | - cargo fmt -p mruby-serde-json --check + cargo fmt -p mrubyedge-serde-json --check - name: Run tests for "${{ matrix.BUILD_TARGET }}" profile run: | - cargo test -p mruby-serde-json \ + cargo test -p mrubyedge-serde-json \ --profile ${{ matrix.BUILD_TARGET }} - name: Build binaries for "${{ matrix.BUILD_TARGET }}" profile run: | - cargo build -p mruby-serde-json \ + cargo build -p mrubyedge-serde-json \ --profile ${{ matrix.BUILD_TARGET }} diff --git a/.github/workflows/mruby-time.yml b/.github/workflows/mruby-time.yml index dcde48a..31882e7 100644 --- a/.github/workflows/mruby-time.yml +++ b/.github/workflows/mruby-time.yml @@ -30,12 +30,12 @@ jobs: key: ${{ runner.os }}-cargo-time-${{ hashFiles('**/Cargo.lock') }} - name: Run formatter run: | - cargo fmt -p mruby-time --check + cargo fmt -p mrubyedge-time --check - name: Run tests for "${{ matrix.BUILD_TARGET }}" profile run: | - cargo test -p mruby-time \ + cargo test -p mrubyedge-time \ --profile ${{ matrix.BUILD_TARGET }} - name: Build binaries for "${{ matrix.BUILD_TARGET }}" profile run: | - cargo build -p mruby-time \ + cargo build -p mrubyedge-time \ --profile ${{ matrix.BUILD_TARGET }} From 82a3b7be87c426081eb79d76cece86df3f09e956 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 22 Feb 2026 01:13:51 +0900 Subject: [PATCH 270/314] Bump all --- Cargo.lock | 13 +++++++------ mrubyedge-cli/Cargo.toml | 4 ++-- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 098cf87..03d7ed1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -651,7 +651,7 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.1.7" +version = "1.1.8" dependencies = [ "askama", "clap", @@ -659,7 +659,7 @@ dependencies = [ "mruby-compiler2-sys", "mrubyedge 1.1.8 (registry+https://github.com/rust-lang/crates.io-index)", "mrubyedge-math 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "mrubyedge-time 0.1.0", + "mrubyedge-time 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", @@ -694,19 +694,20 @@ dependencies = [ [[package]] name = "mrubyedge-time" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b50f7bd2889e92c1b6e98915d091e487a340fd138cf9548b49718c9099d28a79" +version = "0.1.1" dependencies = [ + "libc", + "mec-mrbc-sys", "mrubyedge 1.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "mrubyedge-time" version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583821a12194d23b55bf1fb9051d5559c9ca3481b8bf285693a20debf11fb093" dependencies = [ "libc", - "mec-mrbc-sys", "mrubyedge 1.1.8 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 4fcd2cc..308d20a 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.1.7" +version = "1.1.8" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -21,7 +21,7 @@ mrubyedge = { version = "1.1.7", features = [ "mruby-regexp", ] } mrubyedge-math = "0.1.0" -mrubyedge-time = "0.1.0" +mrubyedge-time = "0.1.1" rand = "0.9.2" nom = "7.1.3" askama = "0.12.1" From 8d51c583332f32d52d7e5720fb24b0771a7d2549 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 24 Feb 2026 00:21:22 +0900 Subject: [PATCH 271/314] Add document deployer --- .github/workflows/docs.yml | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 .github/workflows/docs.yml diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..6f9f99b --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,38 @@ +name: Deploy mrubyedge docs + +on: + push: + branches: [ "master" ] + paths: [ "mrubyedge/**" ] + pull_request: + branches: [ "master" ] + paths: [ "mrubyedge/**" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + doc: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Run doc on mrubyedge + run: cargo doc --no-deps -p mrubyedge + - name: Deploy + uses: actions/upload-pages-artifact@v1 + with: + path: target/doc/mrubyedge + + deploy: + needs: doc + permissions: + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v1 From 48bb6c7301176cb6ff4b2b856ef3e68edb32e695 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 24 Feb 2026 00:36:02 +0900 Subject: [PATCH 272/314] Add Ruby coverage link --- README.md | 1 + mrubyedge/COVERAGE.md | 447 ++++++++++++++++++++++++++++++++++++++++++ mrubyedge/README.md | 1 + mrubyedge/src/lib.rs | 15 ++ 4 files changed, 464 insertions(+) create mode 100644 mrubyedge/COVERAGE.md diff --git a/README.md b/README.md index 31dab4e..4fa680e 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ You can try what features mruby/edge have implemented in [Playground](https://mr * mruby/edge core VM implementation +* [Ruby Compatibility Coverage](./mrubyedge/COVERAGE.md) ### [mrubyedge-cli](./mrubyedge-cli) [![crates.io](https://img.shields.io/crates/v/mrubyedge-cli.svg)](https://crates.io/crates/mrubyedge-cli) [![docs.rs](https://docs.rs/mrubyedge-cli/badge.svg)](https://docs.rs/mrubyedge-cli) * CLI endpoint for mruby/edge - run, compile to wasm, etc. diff --git a/mrubyedge/COVERAGE.md b/mrubyedge/COVERAGE.md new file mode 100644 index 0000000..2eb6291 --- /dev/null +++ b/mrubyedge/COVERAGE.md @@ -0,0 +1,447 @@ +# mruby/edge — Built-in Class & Method Coverage + +A list of currently supported classes and methods, based on the implementations in `mrubyedge/src/yamrb/prelude/`. + +> **Legend** +> - `.method` — class method (`self.method`) +> - `#method` — instance method +> - `(alias: x)` — also available under this name +> - `[feature: xxx]` — requires the corresponding Cargo feature flag + +--- + +## Object (base of all classes) + +`prelude/object.rs` + +### Instance methods + +| Method | Notes | +|---|---| +| `#initialize` | | +| `#==` | | +| `#!=` | | +| `#===` | | +| `#object_id` | alias: `__id__` | +| `#to_s` | | +| `#inspect` | | +| `#raise` | | +| `#nil?` | | +| `#lambda` | alias: `proc` | +| `#is_a?` | alias: `kind_of?` | +| `#class` | | +| `#<=>` | | +| `#method_missing` | | +| `#extend` | | +| `#loop` | | +| `#wasm?` | mruby/edge specific | +| `#puts` | `[feature: wasi]` only | +| `#p` | `[feature: wasi]` only | +| `#debug` | `[feature: wasi]` only | + +### Predefined constants + +| Constant | Value | +|---|---| +| `RUBY_VERSION` | VM VERSION string | +| `MRUBY_VERSION` | same as above | +| `MRUBY_EDGE_VERSION` | same as above | +| `RUBY_ENGINE` | VM ENGINE string | + +--- + +## Exception hierarchy + +`prelude/exception.rs` + +Defined classes (with inheritance): + +``` +Exception +├── InternalError +├── NoMemoryError +├── ScriptError +│ └── LoadError +├── SyntaxError +├── SignalException +│ └── Interrupt +├── SystemExit +├── SystemStackError +└── StandardError + ├── RuntimeError + ├── TypeError + ├── ArgumentError + ├── RangeError + ├── ZeroDivisionError + ├── NotImplementedError + ├── SecurityError + ├── SystemCallError + ├── NoMethodError + └── NameError +``` + +### Instance methods (Exception) + +| Method | Notes | +|---|---| +| `#message` | | + +--- + +## Module + +`prelude/module.rs` + +| Method | Notes | +|---|---| +| `#include` | | +| `#ancestors` | | + +--- + +## Class (subclass of Module) + +`prelude/class.rs` + +| Method | Notes | +|---|---| +| `#new` | creates a new instance | +| `#attr_reader` | | +| `#attr_writer` | | +| `#attr_accessor` | alias: `attr` | +| `#ancestors` | | +| `#inspect` | defined on the Module side | + +--- + +## Integer + +`prelude/integer.rs` + +| Method | Notes | +|---|---| +| `#[]` | bit reference | +| `#-@` | unary minus | +| `#+` | mixed arithmetic with Float | +| `#-` | mixed arithmetic with Float | +| `#**` | mixed arithmetic with Float | +| `#%` | | +| `#&` | bitwise AND | +| `#\|` | bitwise OR | +| `#^` | bitwise XOR | +| `#~` | bitwise NOT | +| `#<<` | left shift | +| `#>>` | right shift | +| `#abs` | | +| `#to_i` | | +| `#to_f` | | +| `#chr` | | +| `#times` | takes a block | +| `#inspect` | alias: `to_s` | +| `#clamp` | | + +--- + +## Float + +`prelude/float.rs` + +| Method | Notes | +|---|---| +| `#to_i` | | +| `#to_f` | | +| `#+` | mixed arithmetic with Integer | +| `#-` | mixed arithmetic with Integer | +| `#*` | mixed arithmetic with Integer | +| `#/` | mixed arithmetic with Integer | +| `#+@` | unary plus | +| `#-@` | unary minus | +| `#**` | mixed arithmetic with Integer | +| `#abs` | | +| `#inspect` | alias: `to_s` | +| `#clamp` | | + +--- + +## NilClass + +`prelude/nilclass.rs` + +| Method | Notes | +|---|---| +| `#to_s` | returns `""` | +| `#inspect` | returns `"nil"` | +| `#nil?` | returns `true` | + +--- + +## TrueClass + +`prelude/trueclass.rs` + +| Method | Notes | +|---|---| +| `#to_s` | alias: `inspect` | +| `#&` | | +| `#\|` | | +| `#^` | | + +--- + +## FalseClass + +`prelude/falseclass.rs` + +| Method | Notes | +|---|---| +| `#to_s` | alias: `inspect` | +| `#&` | | +| `#\|` | | +| `#^` | | + +--- + +## Symbol + +`prelude/symbol.rs` + +| Method | Notes | +|---|---| +| `#to_s` | | +| `#inspect` | `:sym` format | + +--- + +## Proc + +`prelude/proc.rs` + +| Method | Notes | +|---|---| +| `.new` | class method | +| `#call` | | + +--- + +## String + +`prelude/string.rs` + +| Method | Notes | +|---|---| +| `.new` | class method | +| `#+` | string concatenation | +| `#*` | repetition | +| `#<<` | destructive append | +| `#[]` | alias: `slice` | +| `#[]=` | cf. `slice!` | +| `#b` | returns a binary (byte) string | +| `#clear` | | +| `#chomp` | | +| `#chomp!` | | +| `#dup` | | +| `#empty?` | | +| `#getbyte` | | +| `#setbyte` | | +| `#index` | | +| `#ord` | | +| `#slice` | | +| `#slice!` | | +| `#split` | | +| `#lstrip` | | +| `#lstrip!` | | +| `#rstrip` | | +| `#rstrip!` | | +| `#strip` | | +| `#strip!` | | +| `#to_sym` | alias: `intern` | +| `#start_with?` | | +| `#end_with?` | | +| `#include?` | | +| `#bytes` | | +| `#chars` | | +| `#upcase` | | +| `#upcase!` | | +| `#downcase` | | +| `#downcase!` | | +| `#to_i` | | +| `#to_f` | | +| `#unpack` | pack format: `Q q L l I i S s C c` | +| `#size` | alias: `bytesize`, `length` | +| `#inspect` | | +| `#to_s` | | +| `#=~` | added by `[feature: mruby-regexp]` | +| `#!~` | added by `[feature: mruby-regexp]` | + +--- + +## Enumerable (module) + +`prelude/enumerable.rs` +Included in Array, Hash, and Range. + +| Method | Notes | +|---|---| +| `#map` | | +| `#find` | | +| `#select` | | +| `#all?` | | +| `#any?` | | +| `#delete_if` | | +| `#each_with_index` | | +| `#sort` | | +| `#sort_by` | | +| `#max` | | +| `#min` | | +| `#minmax` | | +| `#compact` | | +| `#count` | | +| `#to_a` | | +| `#uniq` | | +| `#reduce` | | +| `#sum` | | + +--- + +## Array + +`prelude/array.rs` +Includes Enumerable. + +| Method | Notes | +|---|---| +| `.new` | class method | +| `#+` | returns a new array containing elements from both arrays | +| `#push` | alias: `<<` | +| `#[]` | alias: `at` | +| `#[]=` | | +| `#clear` | | +| `#delete_at` | | +| `#each` | | +| `#empty?` | | +| `#size` | alias: `length` | +| `#include?` | | +| `#&` | set intersection | +| `#\|` | set union | +| `#first` | | +| `#last` | | +| `#pop` | | +| `#shift` | | +| `#unshift` | | +| `#dup` | | +| `#uniq!` | | +| `#map!` | | +| `#select!` | | +| `#reject!` | | +| `#sort!` | | +| `#sort_by!` | | +| `#pack` | format: `Q q L l I i S s C c` | +| `#inspect` | alias: `to_s` | +| `#join` | | + +--- + +## Hash + +`prelude/hash.rs` +Includes Enumerable. + +| Method | Notes | +|---|---| +| `.new` | class method | +| `#[]` | | +| `#[]=` | | +| `#clear` | | +| `#dup` | | +| `#delete` | | +| `#empty?` | | +| `#has_key?` | | +| `#has_value?` | | +| `#key` | reverse lookup: value → key | +| `#keys` | | +| `#each` | block receives key and value | +| `#size` | alias: `length`, `count` | +| `#merge` | | +| `#merge!` | | +| `#to_h` | | +| `#values` | | +| `#inspect` | alias: `to_s` | + +--- + +## Range + +`prelude/range.rs` +Includes Enumerable. Integer ranges only. + +| Method | Notes | +|---|---| +| `#include?` | supports Integer and Float arguments | +| `#each` | Integer ranges only | + +--- + +## SharedMemory (mruby/edge specific) + +`prelude/shared_memory.rs` +A class for zero-copy sharing with WASM linear memory. + +| Method | Notes | +|---|---| +| `.new` | takes a size in bytes | +| `#to_s` | | +| `#offset_in_memory` | alias: `to_i` — returns the memory offset (address) | +| `#[]` | range / index access | +| `#[]=` | | +| `#replace` | | +| `#read_by_size` | | + +--- + +## Random `[feature: mruby-random]` + +`prelude/rand.rs` +Uses the XorShift PRNG. + +| Method | Notes | +|---|---| +| `.new` | seed is optional | +| `.rand` | class method | +| `.srand` | class method | +| `#rand` | instance method | +| `#seed` | returns the current seed | + +Added to Kernel (Object): + +| Method | Notes | +|---|---| +| `#rand` | uses the global default RNG | + +--- + +## Regexp `[feature: mruby-regexp]` + +`prelude/regexp.rs` +Uses the Rust `regex` crate. + +| Method | Notes | +|---|---| +| `.new` | alias: `.compile` | +| `#=~` | returns match position or `nil` | +| `#!~` | | +| `#match` | returns a MatchData object | +| `#inspect` | | + +### MatchData `[feature: mruby-regexp]` + +| Method | Notes | +|---|---| +| `#[]` | capture group reference | + +--- + +## Notes + +- Some arithmetic operators (`*`, `/`) for Integer are not defined as instance methods in this prelude; they are handled directly by the VM bytecode interpreter (`eval.rs`). +- Comparison operators (`<`, `<=`, `>`, `>=`) are similarly handled on the VM side. +- `String#=~` and `#!~` are only added when `[feature: mruby-regexp]` is enabled. diff --git a/mrubyedge/README.md b/mrubyedge/README.md index e38f0fa..e6fe55d 100644 --- a/mrubyedge/README.md +++ b/mrubyedge/README.md @@ -94,6 +94,7 @@ For a command-line interface to compile and run Ruby scripts, see [mrubyedge-cli - [API Documentation](https://docs.rs/mrubyedge) - [GitHub Repository](https://github.com/mrubyedge/mrubyedge) +- [Ruby Compatibility Coverage](./COVERAGE.md) ## License diff --git a/mrubyedge/src/lib.rs b/mrubyedge/src/lib.rs index b8e8343..fefc5bf 100644 --- a/mrubyedge/src/lib.rs +++ b/mrubyedge/src/lib.rs @@ -47,6 +47,21 @@ //! } //! ``` //! +//! # Ruby Compatibility +//! +//! mruby/edge implements a subset of the Ruby standard library. +//! The built-in classes and methods that are currently supported are listed in +//! [`COVERAGE.md`](https://github.com/mrubyedge/mrubyedge/blob/master/COVERAGE.md). +//! +//! In brief, the following classes are available out of the box: +//! `Object`, `Integer`, `Float`, `String`, `Array`, `Hash`, `Range`, +//! `Symbol`, `Proc`, `NilClass`, `TrueClass`, `FalseClass`, `Module`, +//! `Class`, `Exception` (and standard subclasses), and the `Enumerable` +//! module. Additional classes such as `Random` and `Regexp` are available +//! behind Cargo feature flags (`mruby-random` and `mruby-regexp`). +//! A `SharedMemory` class unique to mruby/edge provides zero-copy access to +//! WASM linear memory. +//! //! Loading a precompiled `*.mrb` produced by `mrbc` is also straightforward //! using `include_bytes!`: //! From 6868d0d6f9484f00c37263efa94d4500c6c2c763 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 24 Feb 2026 00:38:49 +0900 Subject: [PATCH 273/314] Fix versions --- .github/workflows/docs.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 6f9f99b..ae9e7ac 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -15,11 +15,11 @@ jobs: doc: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Run doc on mrubyedge run: cargo doc --no-deps -p mrubyedge - - name: Deploy - uses: actions/upload-pages-artifact@v1 + - name: Upload + uses: actions/upload-pages-artifact@v3 with: path: target/doc/mrubyedge @@ -35,4 +35,4 @@ jobs: steps: - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v1 + uses: actions/deploy-pages@v4 From 4b2ddb4af5b051a7ad09f8b517f6a1b8e745139f Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 24 Feb 2026 00:48:01 +0900 Subject: [PATCH 274/314] Fix project root on GitHub Pages --- .github/workflows/docs.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ae9e7ac..51770a4 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -18,10 +18,25 @@ jobs: - uses: actions/checkout@v4 - name: Run doc on mrubyedge run: cargo doc --no-deps -p mrubyedge + - name: Add redirect index.html + run: | + cat > target/doc/index.html <<'EOF' + + + + + + + + +

Redirecting to mrubyedge documentation...

+ + + EOF - name: Upload uses: actions/upload-pages-artifact@v3 with: - path: target/doc/mrubyedge + path: target/doc deploy: needs: doc From 00a9fca167590a2c2d040de4c826a756757bdced Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 24 Feb 2026 00:49:07 +0900 Subject: [PATCH 275/314] Skip on CI --- .github/workflows/docs.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 51770a4..0d54850 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -40,6 +40,7 @@ jobs: deploy: needs: doc + if: github.event_name == 'push' permissions: pages: write id-token: write From bb7f09ae9f681a150d9cb266e52f9e3c44310138 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 24 Feb 2026 00:50:28 +0900 Subject: [PATCH 276/314] Fix deployment condition --- .github/workflows/docs.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 0d54850..05905ee 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,7 +3,7 @@ name: Deploy mrubyedge docs on: push: branches: [ "master" ] - paths: [ "mrubyedge/**" ] + paths: [ "mrubyedge/**", ".github/workflows/docs.yml" ] pull_request: branches: [ "master" ] paths: [ "mrubyedge/**" ] From e298630af156ef84b6cd2edf29721ea9c0fb3e3c Mon Sep 17 00:00:00 2001 From: Kondo Uchio Date: Tue, 24 Feb 2026 00:52:30 +0900 Subject: [PATCH 277/314] Fix link to COVERAGE.md in documentation --- mrubyedge/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrubyedge/src/lib.rs b/mrubyedge/src/lib.rs index fefc5bf..66c7719 100644 --- a/mrubyedge/src/lib.rs +++ b/mrubyedge/src/lib.rs @@ -51,7 +51,7 @@ //! //! mruby/edge implements a subset of the Ruby standard library. //! The built-in classes and methods that are currently supported are listed in -//! [`COVERAGE.md`](https://github.com/mrubyedge/mrubyedge/blob/master/COVERAGE.md). +//! [`COVERAGE.md`](https://github.com/mrubyedge/mrubyedge/blob/master/mrubyedge/COVERAGE.md). //! //! In brief, the following classes are available out of the box: //! `Object`, `Integer`, `Float`, `String`, `Array`, `Hash`, `Range`, From d6b4a749072a700505790b485e7f1b1ae770793d Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Mar 2026 13:31:50 +0900 Subject: [PATCH 278/314] First implementation of `to_proc` for `Symbol` --- .gitignore | 4 +- mrubyedge/src/yamrb/optable.rs | 8 +++- mrubyedge/src/yamrb/prelude/symbol.rs | 37 +++++++++++++- mrubyedge/tests/to_proc.rs | 69 +++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 mrubyedge/tests/to_proc.rs diff --git a/.gitignore b/.gitignore index 7ac4044..fc26a52 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ /target *.wasm -*.mrb \ No newline at end of file +*.mrb + +.claude/ \ No newline at end of file diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index a86be59..6414f4b 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1023,7 +1023,13 @@ pub(crate) fn do_op_send( vm.kargs.borrow_mut().replace(map); if let Some(blk_index) = blk_index { - args.push(vm.get_current_regs_cloned(blk_index)?); + let blk_val = vm.get_current_regs_cloned(blk_index)?; + if matches!(blk_val.tt, RType::Symbol) { + let proc_val = mrb_funcall(vm, Some(blk_val), "to_proc", &[])?; + args.push(proc_val); + } else { + args.push(blk_val); + } } else { // When no block is provided, set nil in the block register vm.current_regs()[block_index].replace(Rc::new(RObject::nil())); diff --git a/mrubyedge/src/yamrb/prelude/symbol.rs b/mrubyedge/src/yamrb/prelude/symbol.rs index 4aa4229..6a0363a 100644 --- a/mrubyedge/src/yamrb/prelude/symbol.rs +++ b/mrubyedge/src/yamrb/prelude/symbol.rs @@ -1,9 +1,12 @@ use std::rc::Rc; use crate::Error; -use crate::yamrb::helpers::mrb_define_cmethod; +use crate::yamrb::helpers::{mrb_define_cmethod, mrb_funcall}; -use crate::yamrb::{value::RObject, vm::VM}; +use crate::yamrb::{ + value::{RFn, RObject, RProc}, + vm::VM, +}; pub(crate) fn initialize_symbol(vm: &mut VM) { let symbol_class = vm.define_standard_class("Symbol"); @@ -14,6 +17,12 @@ pub(crate) fn initialize_symbol(vm: &mut VM) { "inspect", Box::new(mrb_symbol_inspect), ); + mrb_define_cmethod( + vm, + symbol_class.clone(), + "to_proc", + Box::new(mrb_symbol_to_proc), + ); } fn mrb_symbol_inspect(vm: &mut VM, _args: &[Rc]) -> Result, Error> { @@ -25,3 +34,27 @@ fn mrb_symbol_to_s(vm: &mut VM, _args: &[Rc]) -> Result, Er let symbol: String = vm.getself()?.as_ref().try_into()?; Ok(Rc::new(RObject::string(symbol))) } + +fn mrb_symbol_to_proc(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let method_name: String = vm.getself()?.as_ref().try_into()?; + let rfn: RFn = Box::new(move |vm: &mut VM, args: &[Rc]| { + let recv = args + .first() + .cloned() + .ok_or_else(|| Error::ArgumentError("no receiver given".to_string()))?; + let method_args = if args.len() > 1 { &args[1..] } else { &[] }; + mrb_funcall(vm, Some(recv), &method_name, method_args) + }); + vm.push_fnblock(Rc::new(rfn))?; + let block = RProc { + is_rb_func: false, + is_fnblock: true, + sym_id: None, + next: None, + irep: None, + func: None, + environ: None, + block_self: vm.getself().ok(), + }; + Ok(RObject::proc(block).to_refcount_assigned()) +} diff --git a/mrubyedge/tests/to_proc.rs b/mrubyedge/tests/to_proc.rs new file mode 100644 index 0000000..94e1599 --- /dev/null +++ b/mrubyedge/tests/to_proc.rs @@ -0,0 +1,69 @@ +extern crate mec_mrbc_sys; +extern crate mrubyedge; + +mod helpers; + +use std::rc::Rc; + +use helpers::*; +use mrubyedge::yamrb::value::RObject; + +#[test] +fn symbol_to_proc_direct() { + let code = r#" + def test_to_proc_direct + sym = :upcase + proc = sym.to_proc + proc.call("hello") + end + "#; + let binary = mrbc_compile("to_proc_direct", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let result = mrb_funcall(&mut vm, None, "test_to_proc_direct", &[]).unwrap(); + let result_str: String = result.as_ref().try_into().unwrap(); + assert_eq!(result_str, "HELLO"); +} + +#[test] +fn symbol_to_proc_map_to_s() { + let code = r#" + def test_to_proc_map_to_s + [1, 2, 3].map(&:to_s) + end + "#; + let binary = mrbc_compile_debug("to_proc_map_to_s", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let result = mrb_funcall(&mut vm, None, "test_to_proc_map_to_s", &[]).unwrap(); + let result_array: Vec> = result.as_ref().try_into().unwrap(); + let r0: String = result_array[0].as_ref().try_into().unwrap(); + let r1: String = result_array[1].as_ref().try_into().unwrap(); + let r2: String = result_array[2].as_ref().try_into().unwrap(); + assert_eq!(r0, "1"); + assert_eq!(r1, "2"); + assert_eq!(r2, "3"); +} + +#[test] +fn symbol_to_proc_select() { + let code = r#" + def test_to_proc_select + [nil, 1, nil, 2, 3].select(&:nil?) + end + "#; + let binary = mrbc_compile("to_proc_select", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let result = mrb_funcall(&mut vm, None, "test_to_proc_select", &[]).unwrap(); + let result_array: Vec> = result.as_ref().try_into().unwrap(); + assert_eq!(result_array.len(), 2); + assert!(result_array[0].is_nil()); + assert!(result_array[1].is_nil()); +} From 03ad096ae230d6cd1dce38fe98dbfc85831542dd Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Mar 2026 13:56:53 +0900 Subject: [PATCH 279/314] Fix bug in pushing nil to array --- Cargo.lock | 44 ++++------------------------ mruby-math/Cargo.toml | 4 +-- mruby-serde-json/Cargo.toml | 5 ++-- mruby-time/Cargo.toml | 4 +-- mrubyedge-cli/Cargo.toml | 6 ++-- mrubyedge/src/yamrb/optable.rs | 3 +- mrubyedge/src/yamrb/prelude/array.rs | 5 ---- 7 files changed, 16 insertions(+), 55 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 03d7ed1..95a13dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -636,19 +636,6 @@ dependencies = [ "simple_endian", ] -[[package]] -name = "mrubyedge" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6637cd859a862100009f116830b1b1f6e932633d574c24480ba57983e208567" -dependencies = [ - "plain", - "rand_core 0.10.0", - "rand_xorshift", - "regex", - "simple_endian", -] - [[package]] name = "mrubyedge-cli" version = "1.1.8" @@ -657,9 +644,9 @@ dependencies = [ "clap", "crossterm", "mruby-compiler2-sys", - "mrubyedge 1.1.8 (registry+https://github.com/rust-lang/crates.io-index)", - "mrubyedge-math 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", - "mrubyedge-time 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge", + "mrubyedge-math", + "mrubyedge-time", "nom", "rand", "syn", @@ -670,16 +657,7 @@ name = "mrubyedge-math" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mrubyedge-math" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faac1fa8bf95e7f02ca71824aebf139ed1dd20696e42e8e1c4cb8374dde2dbe" -dependencies = [ - "mrubyedge 1.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge", ] [[package]] @@ -687,7 +665,7 @@ name = "mrubyedge-serde-json" version = "0.1.0" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge", "serde", "serde_json", ] @@ -698,17 +676,7 @@ version = "0.1.1" dependencies = [ "libc", "mec-mrbc-sys", - "mrubyedge 1.1.8 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "mrubyedge-time" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583821a12194d23b55bf1fb9051d5559c9ca3481b8bf285693a20debf11fb093" -dependencies = [ - "libc", - "mrubyedge 1.1.8 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge", ] [[package]] diff --git a/mruby-math/Cargo.toml b/mruby-math/Cargo.toml index e2dfc3e..dfb8936 100644 --- a/mruby-math/Cargo.toml +++ b/mruby-math/Cargo.toml @@ -7,8 +7,8 @@ description = "mruby-math provides Math module for mruby/edge" license = "BSD-3-Clause" [dependencies] -mrubyedge = { version = ">= 1.1.3" } +mrubyedge = { path = "../mrubyedge" } [dev-dependencies] -mrubyedge = { version = ">= 1.1.3", features = ["default"] } +mrubyedge = { path = "../mrubyedge", features = ["default"] } mec-mrbc-sys = "3.3.1" diff --git a/mruby-serde-json/Cargo.toml b/mruby-serde-json/Cargo.toml index 487660a..8d96ce5 100644 --- a/mruby-serde-json/Cargo.toml +++ b/mruby-serde-json/Cargo.toml @@ -7,11 +7,10 @@ description = "mruby-serde-json provides JSON serialization/deserialization for license = "BSD-3-Clause" [dependencies] -mrubyedge = ">= 1.1.3" -# mrubyedge = { version = "1.1.3", path = "../mrubyedge", default-features = false } +mrubyedge = { path = "../mrubyedge" } serde = ">= 1.0.228" serde_json = ">= 1.0.149" [dev-dependencies] -mrubyedge = { version = ">= 1.1.3", features = ["default"] } +mrubyedge = { path = "../mrubyedge", features = ["default"] } mec-mrbc-sys = "3.3.1" diff --git a/mruby-time/Cargo.toml b/mruby-time/Cargo.toml index 52c6f7a..6d355fa 100644 --- a/mruby-time/Cargo.toml +++ b/mruby-time/Cargo.toml @@ -7,11 +7,11 @@ description = "mruby-time provides Time class for mruby/edge" license = "BSD-3-Clause" [dependencies] -mrubyedge = ">= 1.1.8" +mrubyedge = { path = "../mrubyedge" } libc = "0.2" [dev-dependencies] -mrubyedge = { version = ">= 1.1.8", features = ["default"] } +mrubyedge = { path = "../mrubyedge", features = ["default"] } mec-mrbc-sys = "3.3.1" [features] diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 308d20a..05b1c81 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -15,13 +15,13 @@ path = "src/main.rs" clap = { version = "4.5", features = ["derive"] } crossterm = "0.28" mruby-compiler2-sys = "0.3.0" -mrubyedge = { version = "1.1.7", features = [ +mrubyedge = { path = "../mrubyedge", features = [ "default", "mruby-random", "mruby-regexp", ] } -mrubyedge-math = "0.1.0" -mrubyedge-time = "0.1.1" +mrubyedge-math = { path = "../mruby-math" } +mrubyedge-time = { path = "../mruby-time" } rand = "0.9.2" nom = "7.1.3" askama = "0.12.1" diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 6414f4b..3d6e33d 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -1031,9 +1031,8 @@ pub(crate) fn do_op_send( args.push(blk_val); } } else { - // When no block is provided, set nil in the block register + // When no block is provided, do not push a nil placeholder vm.current_regs()[block_index].replace(Rc::new(RObject::nil())); - args.push(Rc::new(RObject::nil())); } let klass = recv.get_class(vm); diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index d667fcd..cb918ad 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -159,11 +159,6 @@ pub fn mrb_array_new(_vm: &mut VM, args: &[Rc]) -> Result, fn mrb_array_push_self(vm: &mut VM, args: &[Rc]) -> Result, Error> { let this = vm.getself()?; - let args = if args[args.len() - 1].as_ref().is_nil() { - &args[..args.len() - 1] - } else { - args - }; mrb_array_push(this, args) } From 83f055f627b98ff9781d1003904b7b00729dcf26 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Mar 2026 13:58:06 +0900 Subject: [PATCH 280/314] No debug --- mrubyedge/tests/to_proc.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mrubyedge/tests/to_proc.rs b/mrubyedge/tests/to_proc.rs index 94e1599..83b1187 100644 --- a/mrubyedge/tests/to_proc.rs +++ b/mrubyedge/tests/to_proc.rs @@ -34,7 +34,7 @@ fn symbol_to_proc_map_to_s() { [1, 2, 3].map(&:to_s) end "#; - let binary = mrbc_compile_debug("to_proc_map_to_s", code); + let binary = mrbc_compile("to_proc_map_to_s", code); let mut rite = mrubyedge::rite::load(&binary).unwrap(); let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); vm.run().unwrap(); From 097228a771f21f9e07c44c4f5b0d316aa39c8184 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Mar 2026 14:05:28 +0900 Subject: [PATCH 281/314] Remove nil hack --- mruby-math/src/lib.rs | 26 +++++++------------------- mruby-serde-json/src/lib.rs | 10 ---------- mruby-time/src/lib.rs | 18 +++++------------- mruby-time/tests/smoke.rs | 1 + mrubyedge/src/yamrb/prelude/array.rs | 15 --------------- mrubyedge/src/yamrb/prelude/rand.rs | 16 ---------------- mrubyedge/src/yamrb/prelude/string.rs | 6 ------ 7 files changed, 13 insertions(+), 79 deletions(-) diff --git a/mruby-math/src/lib.rs b/mruby-math/src/lib.rs index 945ed54..4a7d790 100644 --- a/mruby-math/src/lib.rs +++ b/mruby-math/src/lib.rs @@ -107,14 +107,8 @@ fn get_float_arg(obj: &RObject) -> Result { } } -// Helper function to check argument count (excluding trailing nil) +// Helper function to check argument count fn check_args_count(args: &[Rc], expected: usize) -> Result>, Error> { - let args = if !args.is_empty() && args[args.len() - 1].is_nil() { - &args[0..args.len() - 1] - } else { - args - }; - if args.len() != expected { return Err(Error::ArgumentError(format!( "wrong number of arguments (given {}, expected {})", @@ -217,23 +211,17 @@ pub fn mrb_math_exp(_vm: &mut VM, args: &[Rc]) -> Result, E } pub fn mrb_math_log(_vm: &mut VM, args: &[Rc]) -> Result, Error> { - let args_vec = if !args.is_empty() && args[args.len() - 1].is_nil() { - args[0..args.len() - 1].to_vec() - } else { - args.to_vec() - }; - - if args_vec.len() == 1 { - let x = get_float_arg(&args_vec[0])?; + if args.len() == 1 { + let x = get_float_arg(&args[0])?; Ok(RObject::float(x.ln()).to_refcount_assigned()) - } else if args_vec.len() == 2 { - let x = get_float_arg(&args_vec[0])?; - let base = get_float_arg(&args_vec[1])?; + } else if args.len() == 2 { + let x = get_float_arg(&args[0])?; + let base = get_float_arg(&args[1])?; Ok(RObject::float(x.log(base)).to_refcount_assigned()) } else { Err(Error::ArgumentError(format!( "wrong number of arguments (given {}, expected 1..2)", - args_vec.len() + args.len() ))) } } diff --git a/mruby-serde-json/src/lib.rs b/mruby-serde-json/src/lib.rs index ea8333c..c8cb96d 100644 --- a/mruby-serde-json/src/lib.rs +++ b/mruby-serde-json/src/lib.rs @@ -37,11 +37,6 @@ pub fn init_json(vm: &mut VM) { } pub fn mrb_json_class_dump(vm: &mut VM, args: &[Rc]) -> Result, Error> { - let args = if args[args.len() - 1].is_nil() { - &args[0..args.len() - 1] - } else { - args - }; if args.len() != 1 { return Err(Error::ArgumentError( "wrong number of arguments".to_string(), @@ -52,11 +47,6 @@ pub fn mrb_json_class_dump(vm: &mut VM, args: &[Rc]) -> Result]) -> Result, Error> { - let args = if args[args.len() - 1].is_nil() { - &args[0..args.len() - 1] - } else { - args - }; if args.len() != 1 { return Err(Error::ArgumentError( "wrong number of arguments".to_string(), diff --git a/mruby-time/src/lib.rs b/mruby-time/src/lib.rs index a2f6d6a..0216fb8 100644 --- a/mruby-time/src/lib.rs +++ b/mruby-time/src/lib.rs @@ -160,7 +160,7 @@ fn mrb_time_now(vm: &mut VM, _args: &[Rc]) -> Result, Error /// Time.at(sec) or Time.at(sec, nsec) fn mrb_time_at(vm: &mut VM, args: &[Rc]) -> Result, Error> { - let args = strip_trailing_nil(args); + if args.is_empty() { return Err(Error::ArgumentError( "wrong number of arguments (given 0, expected 1+)".to_string(), @@ -253,7 +253,7 @@ fn mrb_time_to_s(vm: &mut VM, _args: &[Rc]) -> Result, Erro /// Time#+ (sec as integer or float) fn mrb_time_add(vm: &mut VM, args: &[Rc]) -> Result, Error> { - let args = strip_trailing_nil(args); + if args.is_empty() { return Err(Error::ArgumentError( "wrong number of arguments (given 0, expected 1)".to_string(), @@ -276,7 +276,7 @@ fn mrb_time_add(vm: &mut VM, args: &[Rc]) -> Result, Error> /// Time#- (sec as integer or float), also supports Time - Time -> Float (seconds) fn mrb_time_sub(vm: &mut VM, args: &[Rc]) -> Result, Error> { - let args = strip_trailing_nil(args); + if args.is_empty() { return Err(Error::ArgumentError( "wrong number of arguments (given 0, expected 1)".to_string(), @@ -309,7 +309,7 @@ fn mrb_time_sub(vm: &mut VM, args: &[Rc]) -> Result, Error> /// Time#<=> (compare with another Time object) fn mrb_time_cmp(vm: &mut VM, args: &[Rc]) -> Result, Error> { - let args = strip_trailing_nil(args); + if args.is_empty() { return Err(Error::ArgumentError( "wrong number of arguments (given 0, expected 1)".to_string(), @@ -345,7 +345,7 @@ fn mrb_time_utc_offset(vm: &mut VM, _args: &[Rc]) -> Result /// Time#localtime(offset) - returns a new Time with the given UTC offset (in seconds) fn mrb_time_localtime(vm: &mut VM, args: &[Rc]) -> Result, Error> { - let args = strip_trailing_nil(args); + let self_obj = vm.getself()?; let t = get_time_data(&self_obj)?; @@ -424,14 +424,6 @@ fn local_utc_offset_secs() -> i32 { // Helper utilities // --------------------------------------------------------------------------- -fn strip_trailing_nil(args: &[Rc]) -> &[Rc] { - if !args.is_empty() && args[args.len() - 1].is_nil() { - &args[0..args.len() - 1] - } else { - args - } -} - fn get_integer_or_float_as_i64(obj: &RObject) -> Result { match &obj.value { RValue::Integer(i) => Ok(*i), diff --git a/mruby-time/tests/smoke.rs b/mruby-time/tests/smoke.rs index b259a39..078e959 100644 --- a/mruby-time/tests/smoke.rs +++ b/mruby-time/tests/smoke.rs @@ -5,6 +5,7 @@ mod helpers; use helpers::*; /// Helper: build a VM with Time initialized and a fixed Time.__source returning [sec, nsec]. +#[allow(unused)] fn make_vm_with_time_source(sec: i64, nsec: u32) -> mrubyedge::yamrb::vm::VM { use mrubyedge::yamrb::helpers::mrb_define_singleton_cmethod; use mrubyedge::yamrb::value::RObject; diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index cb918ad..c351cd5 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -482,11 +482,6 @@ fn mrb_array_or(vm: &mut VM, args: &[Rc]) -> Result, Error> // Array#first: Returns the first element, or the first n elements fn mrb_array_first(vm: &mut VM, args: &[Rc]) -> Result, Error> { let this: Vec> = vm.getself()?.as_ref().try_into()?; - let args = if !args.is_empty() && args[args.len() - 1].as_ref().is_nil() { - &args[..args.len() - 1] - } else { - args - }; if args.is_empty() { Ok(this @@ -506,11 +501,6 @@ fn mrb_array_first(vm: &mut VM, args: &[Rc]) -> Result, Err // Array#last: Returns the last element, or the last n elements fn mrb_array_last(vm: &mut VM, args: &[Rc]) -> Result, Error> { let this: Vec> = vm.getself()?.as_ref().try_into()?; - let args = if !args.is_empty() && args[args.len() - 1].as_ref().is_nil() { - &args[..args.len() - 1] - } else { - args - }; if args.is_empty() { Ok(this @@ -550,11 +540,6 @@ fn mrb_array_shift(vm: &mut VM, _args: &[Rc]) -> Result, Er fn mrb_array_unshift(vm: &mut VM, args: &[Rc]) -> Result, Error> { let this = vm.getself()?; let mut arr = this.array_borrow_mut()?; - let args = if args[args.len() - 1].as_ref().is_nil() { - &args[..args.len() - 1] - } else { - args - }; for (i, arg) in args.iter().enumerate() { arr.insert(i, arg.clone()); } diff --git a/mrubyedge/src/yamrb/prelude/rand.rs b/mrubyedge/src/yamrb/prelude/rand.rs index a566da1..7d7ec83 100644 --- a/mrubyedge/src/yamrb/prelude/rand.rs +++ b/mrubyedge/src/yamrb/prelude/rand.rs @@ -108,11 +108,6 @@ fn get_default_rng(vm: &mut VM) -> Rc { pub(crate) fn mrb_random_new(vm: &mut VM, args: &[Rc]) -> Result, Error> { let class = get_rng_class(vm); - let args = if !args.is_empty() && args[args.len() - 1].is_nil() { - &args[0..args.len() - 1] - } else { - args - }; let seed = if args.is_empty() { new_seed() } else { @@ -142,11 +137,6 @@ pub(crate) fn mrb_random_new(vm: &mut VM, args: &[Rc]) -> Result]) -> Result, Error> { - let args = if !args.is_empty() && args[args.len() - 1].is_nil() { - &args[0..args.len() - 1] - } else { - args - }; let seed = if args.is_empty() { new_seed() } else { @@ -194,12 +184,6 @@ fn mrb_random_seed(vm: &mut VM, _args: &[Rc]) -> Result, Er fn mrb_random_rand(vm: &mut VM, args: &[Rc]) -> Result, Error> { use rand_core::Rng; - let args = if !args.is_empty() && args[args.len() - 1].is_nil() { - &args[0..args.len() - 1] - } else { - args - }; - let self_obj = vm.getself()?; let result = match &self_obj.value { diff --git a/mrubyedge/src/yamrb/prelude/string.rs b/mrubyedge/src/yamrb/prelude/string.rs index 732b310..66aef55 100644 --- a/mrubyedge/src/yamrb/prelude/string.rs +++ b/mrubyedge/src/yamrb/prelude/string.rs @@ -421,12 +421,6 @@ fn mrb_string_slice_self(vm: &mut VM, args: &[Rc]) -> Result Date: Mon, 2 Mar 2026 14:07:19 +0900 Subject: [PATCH 282/314] Bump versions --- Cargo.lock | 6 +++--- mruby-math/Cargo.toml | 2 +- mruby-serde-json/Cargo.toml | 2 +- mruby-time/Cargo.toml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 95a13dd..e7fa715 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -654,7 +654,7 @@ dependencies = [ [[package]] name = "mrubyedge-math" -version = "0.1.0" +version = "0.1.1" dependencies = [ "mec-mrbc-sys", "mrubyedge", @@ -662,7 +662,7 @@ dependencies = [ [[package]] name = "mrubyedge-serde-json" -version = "0.1.0" +version = "0.1.1" dependencies = [ "mec-mrbc-sys", "mrubyedge", @@ -672,7 +672,7 @@ dependencies = [ [[package]] name = "mrubyedge-time" -version = "0.1.1" +version = "0.1.2" dependencies = [ "libc", "mec-mrbc-sys", diff --git a/mruby-math/Cargo.toml b/mruby-math/Cargo.toml index dfb8936..320c025 100644 --- a/mruby-math/Cargo.toml +++ b/mruby-math/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-math" -version = "0.1.0" +version = "0.1.1" edition = "2024" authors = ["Uchio Kondo "] description = "mruby-math provides Math module for mruby/edge" diff --git a/mruby-serde-json/Cargo.toml b/mruby-serde-json/Cargo.toml index 8d96ce5..6b7eb54 100644 --- a/mruby-serde-json/Cargo.toml +++ b/mruby-serde-json/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-serde-json" -version = "0.1.0" +version = "0.1.1" edition = "2024" authors = ["Uchio Kondo "] description = "mruby-serde-json provides JSON serialization/deserialization for mruby/edge using serde_json" diff --git a/mruby-time/Cargo.toml b/mruby-time/Cargo.toml index 6d355fa..59f3c83 100644 --- a/mruby-time/Cargo.toml +++ b/mruby-time/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-time" -version = "0.1.1" +version = "0.1.2" edition = "2024" authors = ["Uchio Kondo "] description = "mruby-time provides Time class for mruby/edge" From 56947d295628d8c8c7d83ef268ce94dda6bcb6e3 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Mar 2026 15:38:36 +0900 Subject: [PATCH 283/314] Save block_givn info --- mrubyedge/src/yamrb/optable.rs | 24 ++++--- mrubyedge/src/yamrb/prelude/object.rs | 17 +++++ mrubyedge/src/yamrb/vm.rs | 2 + mrubyedge/tests/object.rs | 100 ++++++++++++++++++++++++++ 4 files changed, 134 insertions(+), 9 deletions(-) diff --git a/mrubyedge/src/yamrb/optable.rs b/mrubyedge/src/yamrb/optable.rs index 3d6e33d..f6e7364 100644 --- a/mrubyedge/src/yamrb/optable.rs +++ b/mrubyedge/src/yamrb/optable.rs @@ -494,6 +494,7 @@ pub(crate) fn push_callinfo( return_reg, target_class: vm.target_class.clone(), method_owner, + has_block: Cell::new(false), }; vm.current_callinfo = Some(Rc::new(callinfo)); } @@ -1104,6 +1105,11 @@ pub(crate) fn do_op_send( push_callinfo(vm, method_id, n, Some(owner_module), a as usize); + // Set has_block flag based on whether a block was provided + if let Some(ci) = vm.current_callinfo.as_ref() { + ci.has_block.set(blk_index.is_some()); + } + vm.pc.set(0); vm.current_irep = method.irep.ok_or_else(|| Error::internal("empry irep"))?; vm.current_regs_offset += a as usize; @@ -1258,15 +1264,15 @@ pub(crate) fn op_super(vm: &mut VM, operand: &Fetched) -> Result<(), Error> { } #[allow(dead_code)] -#[derive(Debug)] -struct EnterArgInfo { - m1: u32, - o: u32, - r: u32, - m2: u32, - k: u32, - d: u32, - b: u32, +#[derive(Debug, Copy, Clone)] +pub(crate) struct EnterArgInfo { + pub m1: u32, + pub o: u32, + pub r: u32, + pub m2: u32, + pub k: u32, + pub d: u32, + pub b: u32, } impl From for EnterArgInfo { diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index b018123..37ec453 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -73,6 +73,12 @@ pub(crate) fn initialize_object(vm: &mut VM) { Box::new(mrb_object_raise), ); mrb_define_cmethod(vm, object_class.clone(), "nil?", Box::new(mrb_object_nil_p)); + mrb_define_cmethod( + vm, + object_class.clone(), + "block_given?", + Box::new(mrb_object_block_given), + ); mrb_define_cmethod( vm, object_class.clone(), @@ -329,6 +335,17 @@ fn mrb_object_nil_p(_vm: &mut VM, _args: &[Rc]) -> Result, Ok(Rc::new(RObject::boolean(false))) } +fn mrb_object_block_given(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + // CALLINFO の has_block フラグをチェック + let has_block = if let Some(ci) = vm.current_callinfo.as_ref() { + ci.has_block.get() + } else { + false + }; + + Ok(Rc::new(RObject::boolean(has_block))) +} + pub fn mrb_object_initialize(_vm: &mut VM, _args: &[Rc]) -> Result, Error> { // Abstract method; do nothing Ok(Rc::new(RObject::nil())) diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 8fe0c8a..131baef 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -11,6 +11,7 @@ use super::prelude::prelude; use super::value::RHashMap; use super::value::*; use super::{op, optable::*}; +use super::optable::EnterArgInfo; pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const ENGINE: &str = "mruby/edge"; @@ -895,6 +896,7 @@ pub struct CALLINFO { pub n_args: usize, pub return_reg: usize, pub method_owner: Option>, + pub has_block: Cell, } #[derive(Debug, Clone)] diff --git a/mrubyedge/tests/object.rs b/mrubyedge/tests/object.rs index 12ab4de..3f5157e 100644 --- a/mrubyedge/tests/object.rs +++ b/mrubyedge/tests/object.rs @@ -317,3 +317,103 @@ fn object_loop_basic_test() { .unwrap(); assert_eq!(result, 5); } + +#[test] +fn object_block_given_with_block_test() { + let code = r#" + def method_with_block + block_given? + end + + def test_block_given_with_block + method_with_block { } + end + "#; + let binary = mrbc_compile("block_given_with_block", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: bool = mrb_funcall(&mut vm, None, "test_block_given_with_block", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, true); +} + +#[test] +fn object_block_given_without_block_test() { + let code = r#" + def method_with_block + block_given? + end + + def test_block_given_without_block + method_with_block + end + "#; + let binary = mrbc_compile("block_given_without_block", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: bool = mrb_funcall(&mut vm, None, "test_block_given_without_block", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, false); +} + +#[test] +fn object_block_given_with_args_and_block_test() { + let code = r#" + def method_with_args(a, b, c) + block_given? + end + + def test_block_given_with_args_and_block + method_with_args(1, 2, 3) { } + end + "#; + let binary = mrbc_compile("block_given_with_args_and_block", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: bool = mrb_funcall(&mut vm, None, "test_block_given_with_args_and_block", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, true); +} + +#[test] +fn object_block_given_with_args_without_block_test() { + let code = r#" + def method_with_args(a, b, c) + block_given? + end + + def test_block_given_with_args_without_block + method_with_args(1, 2, 3) + end + "#; + let binary = mrbc_compile("block_given_with_args_without_block", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: bool = mrb_funcall(&mut vm, None, "test_block_given_with_args_without_block", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, false); +} From 2c401ed46ad5672c17901e1fbaec0fa86387eca7 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Mar 2026 15:44:38 +0900 Subject: [PATCH 284/314] Add more case --- mrubyedge/src/yamrb/vm.rs | 1 - mrubyedge/tests/to_proc.rs | 21 +++++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 131baef..0fc7e6f 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -11,7 +11,6 @@ use super::prelude::prelude; use super::value::RHashMap; use super::value::*; use super::{op, optable::*}; -use super::optable::EnterArgInfo; pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const ENGINE: &str = "mruby/edge"; diff --git a/mrubyedge/tests/to_proc.rs b/mrubyedge/tests/to_proc.rs index 83b1187..966824f 100644 --- a/mrubyedge/tests/to_proc.rs +++ b/mrubyedge/tests/to_proc.rs @@ -49,6 +49,27 @@ fn symbol_to_proc_map_to_s() { assert_eq!(r2, "3"); } +#[test] +fn symbol_to_proc_keep() { + let code = r#" + def test_to_proc_keep + blk = :to_i.to_proc + a = ["1", "2", "3"].map(&blk).sum + b = ["4", "5", "6"].map(&blk).sum + c = ["7", "8", "9"].map(&blk).sum + a + b + c + end + "#; + let binary = mrbc_compile("to_proc_keep", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let result = mrb_funcall(&mut vm, None, "test_to_proc_keep", &[]).unwrap(); + let result: i32 = result.as_ref().try_into().unwrap(); + assert_eq!(result, 45); +} + #[test] fn symbol_to_proc_select() { let code = r#" From 268865ca559f4136423b6be584f615b5e85c193c Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Mar 2026 16:01:25 +0900 Subject: [PATCH 285/314] Support metaprog features --- mrubyedge/src/yamrb/prelude/object.rs | 35 +++++++++ mrubyedge/tests/object.rs | 100 ++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) diff --git a/mrubyedge/src/yamrb/prelude/object.rs b/mrubyedge/src/yamrb/prelude/object.rs index 37ec453..c92dc9a 100644 --- a/mrubyedge/src/yamrb/prelude/object.rs +++ b/mrubyedge/src/yamrb/prelude/object.rs @@ -123,6 +123,18 @@ pub(crate) fn initialize_object(vm: &mut VM) { Box::new(mrb_object_extend), ); mrb_define_cmethod(vm, object_class.clone(), "loop", Box::new(mrb_object_loop)); + mrb_define_cmethod( + vm, + object_class.clone(), + "respond_to?", + Box::new(mrb_object_respond_to), + ); + mrb_define_cmethod( + vm, + object_class.clone(), + "public_send", + Box::new(mrb_object_public_send), + ); // define global consts: vm.consts.insert( @@ -397,6 +409,29 @@ fn mrb_object_loop(vm: &mut VM, args: &[Rc]) -> Result, Err } } +fn mrb_object_respond_to(vm: &mut VM, args: &[Rc]) -> Result, Error> { + let method_name: String = args[0].as_ref().try_into()?; + let obj = vm.getself()?; + let klass = obj.singleton_or_this_class(vm); + let has_method = resolve_method(&klass, &method_name).is_some(); + Ok(Rc::new(RObject::boolean(has_method))) +} + +fn mrb_object_public_send(vm: &mut VM, args: &[Rc]) -> Result, Error> { + if args.is_empty() { + return Err(Error::ArgumentError( + "wrong number of arguments (given 0, expected 1+)".to_string(), + )); + } + + let method_name: String = args[0].as_ref().try_into()?; + let obj = vm.getself()?; + let method_args = &args[1..]; + + // For now, public_send behaves the same as send since we don't have visibility modifiers + mrb_funcall(vm, Some(obj), &method_name, method_args) +} + fn mrb_object_method_missing(vm: &mut VM, args: &[Rc]) -> Result, Error> { let method_name_obj = &args .first() diff --git a/mrubyedge/tests/object.rs b/mrubyedge/tests/object.rs index 3f5157e..3f36255 100644 --- a/mrubyedge/tests/object.rs +++ b/mrubyedge/tests/object.rs @@ -417,3 +417,103 @@ fn object_block_given_with_args_without_block_test() { .unwrap(); assert_eq!(result, false); } + +#[test] +fn object_respond_to_existing_method_test() { + let code = r#" + def test_respond_to_existing + obj = Object.new + obj.respond_to?("to_s") + end + "#; + let binary = mrbc_compile("respond_to_existing", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: bool = mrb_funcall(&mut vm, None, "test_respond_to_existing", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, true); +} + +#[test] +fn object_respond_to_non_existing_method_test() { + let code = r#" + def test_respond_to_non_existing + obj = Object.new + obj.respond_to?("non_existing_method") + end + "#; + let binary = mrbc_compile("respond_to_non_existing", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: bool = mrb_funcall(&mut vm, None, "test_respond_to_non_existing", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, false); +} + +#[test] +fn object_public_send_test() { + let code = r#" + class TestClass + def hello(name) + "Hello, #{name}!" + end + end + + def test_public_send + obj = TestClass.new + obj.public_send("hello", "World") + end + "#; + let binary = mrbc_compile("public_send", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: String = mrb_funcall(&mut vm, None, "test_public_send", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, "Hello, World!"); +} + +#[test] +fn object_public_send_no_args_test() { + let code = r#" + class TestClass + def greet + "Hi!" + end + end + + def test_public_send_no_args + obj = TestClass.new + obj.public_send("greet") + end + "#; + let binary = mrbc_compile("public_send_no_args", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result: String = mrb_funcall(&mut vm, None, "test_public_send_no_args", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(result, "Hi!"); +} From a13392b8662ce9e660491445ad7b29b65438def9 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Mar 2026 16:32:23 +0900 Subject: [PATCH 286/314] fmt --- mruby-time/src/lib.rs | 5 ----- mrubyedge/tests/object.rs | 15 ++++++++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/mruby-time/src/lib.rs b/mruby-time/src/lib.rs index 0216fb8..1977a26 100644 --- a/mruby-time/src/lib.rs +++ b/mruby-time/src/lib.rs @@ -160,7 +160,6 @@ fn mrb_time_now(vm: &mut VM, _args: &[Rc]) -> Result, Error /// Time.at(sec) or Time.at(sec, nsec) fn mrb_time_at(vm: &mut VM, args: &[Rc]) -> Result, Error> { - if args.is_empty() { return Err(Error::ArgumentError( "wrong number of arguments (given 0, expected 1+)".to_string(), @@ -253,7 +252,6 @@ fn mrb_time_to_s(vm: &mut VM, _args: &[Rc]) -> Result, Erro /// Time#+ (sec as integer or float) fn mrb_time_add(vm: &mut VM, args: &[Rc]) -> Result, Error> { - if args.is_empty() { return Err(Error::ArgumentError( "wrong number of arguments (given 0, expected 1)".to_string(), @@ -276,7 +274,6 @@ fn mrb_time_add(vm: &mut VM, args: &[Rc]) -> Result, Error> /// Time#- (sec as integer or float), also supports Time - Time -> Float (seconds) fn mrb_time_sub(vm: &mut VM, args: &[Rc]) -> Result, Error> { - if args.is_empty() { return Err(Error::ArgumentError( "wrong number of arguments (given 0, expected 1)".to_string(), @@ -309,7 +306,6 @@ fn mrb_time_sub(vm: &mut VM, args: &[Rc]) -> Result, Error> /// Time#<=> (compare with another Time object) fn mrb_time_cmp(vm: &mut VM, args: &[Rc]) -> Result, Error> { - if args.is_empty() { return Err(Error::ArgumentError( "wrong number of arguments (given 0, expected 1)".to_string(), @@ -345,7 +341,6 @@ fn mrb_time_utc_offset(vm: &mut VM, _args: &[Rc]) -> Result /// Time#localtime(offset) - returns a new Time with the given UTC offset (in seconds) fn mrb_time_localtime(vm: &mut VM, args: &[Rc]) -> Result, Error> { - let self_obj = vm.getself()?; let t = get_time_data(&self_obj)?; diff --git a/mrubyedge/tests/object.rs b/mrubyedge/tests/object.rs index 3f36255..e85438a 100644 --- a/mrubyedge/tests/object.rs +++ b/mrubyedge/tests/object.rs @@ -410,11 +410,16 @@ fn object_block_given_with_args_without_block_test() { vm.run().unwrap(); let args = vec![]; - let result: bool = mrb_funcall(&mut vm, None, "test_block_given_with_args_without_block", &args) - .unwrap() - .as_ref() - .try_into() - .unwrap(); + let result: bool = mrb_funcall( + &mut vm, + None, + "test_block_given_with_args_without_block", + &args, + ) + .unwrap() + .as_ref() + .try_into() + .unwrap(); assert_eq!(result, false); } From dc67df1545475e50c42fa28aa28107c8c71d2821 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Mar 2026 16:56:53 +0900 Subject: [PATCH 287/314] Update doc --- mrubyedge/COVERAGE.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mrubyedge/COVERAGE.md b/mrubyedge/COVERAGE.md index 2eb6291..a4f80c7 100644 --- a/mrubyedge/COVERAGE.md +++ b/mrubyedge/COVERAGE.md @@ -34,6 +34,9 @@ A list of currently supported classes and methods, based on the implementations | `#method_missing` | | | `#extend` | | | `#loop` | | +| `#block_given?` | | +| `#respond_to?` | | +| `#public_send` | | | `#wasm?` | mruby/edge specific | | `#puts` | `[feature: wasi]` only | | `#p` | `[feature: wasi]` only | @@ -209,6 +212,7 @@ Exception |---|---| | `#to_s` | | | `#inspect` | `:sym` format | +| `#to_proc` | converts symbol to a proc that calls the method | --- From fd305cd1aa6f1c4c013e675bf1051ffe28554db4 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Mar 2026 17:39:48 +0900 Subject: [PATCH 288/314] Added basic case --- mrubyedge/COVERAGE.md | 1 + mrubyedge/src/yamrb/prelude/array.rs | 57 +++++++++ mrubyedge/tests/array.rs | 183 +++++++++++++++++++++++++++ 3 files changed, 241 insertions(+) diff --git a/mrubyedge/COVERAGE.md b/mrubyedge/COVERAGE.md index a4f80c7..9e58f71 100644 --- a/mrubyedge/COVERAGE.md +++ b/mrubyedge/COVERAGE.md @@ -342,6 +342,7 @@ Includes Enumerable. | `#pack` | format: `Q q L l I i S s C c` | | `#inspect` | alias: `to_s` | | `#join` | | +| `#flatten` | returns a new flattened array (recursive) | --- diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index c351cd5..720e72c 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -118,6 +118,13 @@ pub(crate) fn initialize_array(vm: &mut VM) { ); mrb_define_cmethod(vm, array_class.clone(), "to_s", Box::new(mrb_array_inspect)); mrb_define_cmethod(vm, array_class.clone(), "join", Box::new(mrb_array_join)); + mrb_define_cmethod(vm, array_class.clone(), "flatten", Box::new(mrb_array_flatten)); + mrb_define_cmethod( + vm, + array_class.clone(), + "flatten!", + Box::new(mrb_array_flatten_self), + ); let enumerable_module = vm.get_module_by_name("Enumerable"); mrb_include_module(&array_class, enumerable_module).expect("failed to include Enumerable"); @@ -661,6 +668,56 @@ fn mrb_array_join(vm: &mut VM, args: &[Rc]) -> Result, Erro Ok(Rc::new(RObject::string(result))) } +/// Array#flatten: Returns a new array that is a one-dimensional flattening of self (recursively) +fn mrb_array_flatten(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this: Vec> = vm.getself()?.as_ref().try_into()?; + let mut result = Vec::new(); + do_array_flatten_recursive(&this, &mut result); + Ok(Rc::new(RObject::array(result))) +} + +/// Helper function to recursively flatten an array +fn do_array_flatten_recursive(array: &[Rc], result: &mut Vec>) { + for elem in array { + if let RValue::Array(inner_array) = &elem.value { + // Recursively flatten if element is an array + let inner: Vec> = inner_array.borrow().clone(); + do_array_flatten_recursive(&inner, result); + } else { + // Otherwise, add the element as-is + result.push(elem.clone()); + } + } +} + +/// Array#flatten!: Flattens self in place (recursively) +fn mrb_array_flatten_self(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let arr: Vec> = this.as_ref().try_into()?; + + let flattened: Vec> = mrb_funcall(vm, Some(this.clone()), "flatten", &[])? + .as_ref() + .try_into()?; + + // Return nil if no changes were made + if flattened.len() == arr.len() { + // Check if the structure actually changed + let mut changed = false; + for (i, elem) in arr.iter().enumerate() { + if elem.as_eq_value() != flattened[i].as_eq_value() { + changed = true; + break; + } + } + if !changed { + return Ok(Rc::new(RObject::nil())); + } + } + + *this.array_borrow_mut()? = flattened; + Ok(this) +} + #[test] fn test_mrb_array_size() { use crate::yamrb::*; diff --git a/mrubyedge/tests/array.rs b/mrubyedge/tests/array.rs index 33ed9ad..a2468b5 100644 --- a/mrubyedge/tests/array.rs +++ b/mrubyedge/tests/array.rs @@ -391,3 +391,186 @@ fn array_reference_mutation_recursive_test() { assert_eq!(val, (i + 1) as i64); } } + +#[test] +fn array_flatten_basic_test() { + let code = r#" + def test_flatten_basic + [1, 2, [3, 4]].flatten + end + "#; + let binary = mrbc_compile("flatten_basic", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_flatten_basic", &args).unwrap(); + let arr: Vec> = + result.as_ref().try_into().unwrap(); + + assert_eq!(arr.len(), 4); + let vals: Vec = arr + .iter() + .map(|r| r.as_ref().try_into().unwrap()) + .collect(); + assert_eq!(vals, vec![1, 2, 3, 4]); +} + +#[test] +fn array_flatten_nested_test() { + let code = r#" + def test_flatten_nested + [1, [2, [3, 4]], 5].flatten + end + "#; + let binary = mrbc_compile("flatten_nested", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_flatten_nested", &args).unwrap(); + let arr: Vec> = + result.as_ref().try_into().unwrap(); + + assert_eq!(arr.len(), 5); + let vals: Vec = arr + .iter() + .map(|r| r.as_ref().try_into().unwrap()) + .collect(); + assert_eq!(vals, vec![1, 2, 3, 4, 5]); +} + +#[test] +fn array_flatten_empty_test() { + let code = r#" + def test_flatten_empty + [].flatten + end + "#; + let binary = mrbc_compile("flatten_empty", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_flatten_empty", &args).unwrap(); + let arr: Vec> = + result.as_ref().try_into().unwrap(); + + assert_eq!(arr.len(), 0); +} + +#[test] +fn array_flatten_no_nested_test() { + let code = r#" + def test_flatten_no_nested + [1, 2, 3].flatten + end + "#; + let binary = mrbc_compile("flatten_no_nested", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_flatten_no_nested", &args).unwrap(); + let arr: Vec> = + result.as_ref().try_into().unwrap(); + + assert_eq!(arr.len(), 3); + let vals: Vec = arr + .iter() + .map(|r| r.as_ref().try_into().unwrap()) + .collect(); + assert_eq!(vals, vec![1, 2, 3]); +} + +#[test] +fn array_flatten_self_basic_test() { + let code = r#" + def test_flatten_self_basic + a = [1, 2, [3, 4]] + a.flatten! + a + end + "#; + let binary = mrbc_compile("flatten_self_basic", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_flatten_self_basic", &args).unwrap(); + let arr: Vec> = + result.as_ref().try_into().unwrap(); + + assert_eq!(arr.len(), 4); + let vals: Vec = arr + .iter() + .map(|r| r.as_ref().try_into().unwrap()) + .collect(); + assert_eq!(vals, vec![1, 2, 3, 4]); +} + +#[test] +fn array_flatten_self_returns_nil_if_no_change_test() { + let code = r#" + def test_flatten_self_no_change + a = [1, 2, 3] + a.flatten! + end + "#; + let binary = mrbc_compile("flatten_self_no_change", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_flatten_self_no_change", &args).unwrap(); + + // Should return nil if no changes were made + assert!(result.as_ref().is_nil()); +} + +#[test] +fn array_flatten_self_returns_self_if_changed_test() { + let code = r#" + def test_flatten_self_changed + a = [1, [2], 3] + result = a.flatten! + [result, a] + end + "#; + let binary = mrbc_compile("flatten_self_changed", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_flatten_self_changed", &args).unwrap(); + let outer: Vec> = + result.as_ref().try_into().unwrap(); + + // result (outer[0]) should be the same as a (outer[1]) + let result_arr: Vec> = + outer[0].as_ref().try_into().unwrap(); + let a_arr: Vec> = + outer[1].as_ref().try_into().unwrap(); + + assert_eq!(result_arr.len(), 3); + assert_eq!(a_arr.len(), 3); + + let result_vals: Vec = result_arr + .iter() + .map(|r| r.as_ref().try_into().unwrap()) + .collect(); + let a_vals: Vec = a_arr + .iter() + .map(|r| r.as_ref().try_into().unwrap()) + .collect(); + + assert_eq!(result_vals, vec![1, 2, 3]); + assert_eq!(a_vals, vec![1, 2, 3]); +} From 54d066dcc21cc869b377e3f3d982d7ec04416aad Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Mar 2026 17:41:17 +0900 Subject: [PATCH 289/314] Cover --- mrubyedge/COVERAGE.md | 1 + 1 file changed, 1 insertion(+) diff --git a/mrubyedge/COVERAGE.md b/mrubyedge/COVERAGE.md index 9e58f71..7e5458c 100644 --- a/mrubyedge/COVERAGE.md +++ b/mrubyedge/COVERAGE.md @@ -343,6 +343,7 @@ Includes Enumerable. | `#inspect` | alias: `to_s` | | `#join` | | | `#flatten` | returns a new flattened array (recursive) | +| `#flatten!` | flattens self in place (recursive), returns self or nil | --- From 0eb185e4ac9d94957b63af2dc5af86259cf0edb2 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Mar 2026 19:16:54 +0900 Subject: [PATCH 290/314] Update COVERAGE.md and hash.rs --- mrubyedge/COVERAGE.md | 1 + mrubyedge/src/yamrb/prelude/hash.rs | 23 ++++++++++++++++ mrubyedge/tests/hash.rs | 42 +++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+) diff --git a/mrubyedge/COVERAGE.md b/mrubyedge/COVERAGE.md index 7e5458c..5635ceb 100644 --- a/mrubyedge/COVERAGE.md +++ b/mrubyedge/COVERAGE.md @@ -372,6 +372,7 @@ Includes Enumerable. | `#to_h` | | | `#values` | | | `#inspect` | alias: `to_s` | +| `#flatten` | returns an array of [key1, value1, key2, value2, ...] | --- diff --git a/mrubyedge/src/yamrb/prelude/hash.rs b/mrubyedge/src/yamrb/prelude/hash.rs index b503225..6f8d592 100644 --- a/mrubyedge/src/yamrb/prelude/hash.rs +++ b/mrubyedge/src/yamrb/prelude/hash.rs @@ -70,6 +70,7 @@ pub(crate) fn initialize_hash(vm: &mut VM) { Box::new(mrb_hash_inspect), ); mrb_define_cmethod(vm, hash_class.clone(), "to_s", Box::new(mrb_hash_inspect)); + mrb_define_cmethod(vm, hash_class.clone(), "flatten", Box::new(mrb_hash_flatten)); let enumerable_module = vm.get_module_by_name("Enumerable"); mrb_include_module(&hash_class, enumerable_module).expect("failed to include Enumerable"); @@ -445,6 +446,28 @@ fn mrb_hash_to_h(vm: &mut VM, _args: &[Rc]) -> Result, Erro vm.getself() } +// Hash#flatten: Returns a new array that is a one-dimensional flattening of this hash +// Converts the hash to an array of [key1, value1, key2, value2, ...] +fn mrb_hash_flatten(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + let hash = match &this.value { + RValue::Hash(h) => h, + _ => { + return Err(Error::RuntimeError( + "Hash#flatten must be called on a hash".to_string(), + )); + } + }; + + let mut result = Vec::new(); + for (_, (key, value)) in hash.borrow().iter() { + result.push(key.clone()); + result.push(value.clone()); + } + + Ok(RObject::array(result).to_refcount_assigned()) +} + #[test] fn test_mrb_hash_size() { let mut vm = VM::empty(); diff --git a/mrubyedge/tests/hash.rs b/mrubyedge/tests/hash.rs index d05a064..83f12d7 100644 --- a/mrubyedge/tests/hash.rs +++ b/mrubyedge/tests/hash.rs @@ -363,3 +363,45 @@ fn hash_to_h_test() { let result: bool = result.as_ref().try_into().unwrap(); assert!(result); } + +#[test] +fn hash_flatten_test() { + let code = r#" + def test_hash_flatten + h = {"a" => 1, "b" => 2} + h.flatten + end + "#; + let binary = mrbc_compile("hash_flatten", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_hash_flatten", &args).unwrap(); + let arr: Vec> = result.as_ref().try_into().unwrap(); + + // Hash#flatten returns an array of [key1, value1, key2, value2, ...] + assert_eq!(arr.len(), 4); + + // Verify the array contains the keys and values + let strings: Vec = arr + .iter() + .filter_map(|obj| { + let s: Result = obj.as_ref().try_into(); + s.ok() + }) + .collect(); + let ints: Vec = arr + .iter() + .filter_map(|obj| { + let i: Result = obj.as_ref().try_into(); + i.ok() + }) + .collect(); + + assert!(strings.contains(&"a".to_string())); + assert!(strings.contains(&"b".to_string())); + assert!(ints.contains(&1)); + assert!(ints.contains(&2)); +} From 8975069914bd8cc68434443b4de0aa8d294a0945 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Mar 2026 19:24:38 +0900 Subject: [PATCH 291/314] Format --- mrubyedge/src/yamrb/prelude/array.rs | 7 ++++++- mrubyedge/src/yamrb/prelude/hash.rs | 7 ++++++- mrubyedge/tests/array.rs | 20 ++++---------------- 3 files changed, 16 insertions(+), 18 deletions(-) diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index 720e72c..db4daff 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -118,7 +118,12 @@ pub(crate) fn initialize_array(vm: &mut VM) { ); mrb_define_cmethod(vm, array_class.clone(), "to_s", Box::new(mrb_array_inspect)); mrb_define_cmethod(vm, array_class.clone(), "join", Box::new(mrb_array_join)); - mrb_define_cmethod(vm, array_class.clone(), "flatten", Box::new(mrb_array_flatten)); + mrb_define_cmethod( + vm, + array_class.clone(), + "flatten", + Box::new(mrb_array_flatten), + ); mrb_define_cmethod( vm, array_class.clone(), diff --git a/mrubyedge/src/yamrb/prelude/hash.rs b/mrubyedge/src/yamrb/prelude/hash.rs index 6f8d592..6049be7 100644 --- a/mrubyedge/src/yamrb/prelude/hash.rs +++ b/mrubyedge/src/yamrb/prelude/hash.rs @@ -70,7 +70,12 @@ pub(crate) fn initialize_hash(vm: &mut VM) { Box::new(mrb_hash_inspect), ); mrb_define_cmethod(vm, hash_class.clone(), "to_s", Box::new(mrb_hash_inspect)); - mrb_define_cmethod(vm, hash_class.clone(), "flatten", Box::new(mrb_hash_flatten)); + mrb_define_cmethod( + vm, + hash_class.clone(), + "flatten", + Box::new(mrb_hash_flatten), + ); let enumerable_module = vm.get_module_by_name("Enumerable"); mrb_include_module(&hash_class, enumerable_module).expect("failed to include Enumerable"); diff --git a/mrubyedge/tests/array.rs b/mrubyedge/tests/array.rs index a2468b5..022da2f 100644 --- a/mrubyedge/tests/array.rs +++ b/mrubyedge/tests/array.rs @@ -410,10 +410,7 @@ fn array_flatten_basic_test() { result.as_ref().try_into().unwrap(); assert_eq!(arr.len(), 4); - let vals: Vec = arr - .iter() - .map(|r| r.as_ref().try_into().unwrap()) - .collect(); + let vals: Vec = arr.iter().map(|r| r.as_ref().try_into().unwrap()).collect(); assert_eq!(vals, vec![1, 2, 3, 4]); } @@ -435,10 +432,7 @@ fn array_flatten_nested_test() { result.as_ref().try_into().unwrap(); assert_eq!(arr.len(), 5); - let vals: Vec = arr - .iter() - .map(|r| r.as_ref().try_into().unwrap()) - .collect(); + let vals: Vec = arr.iter().map(|r| r.as_ref().try_into().unwrap()).collect(); assert_eq!(vals, vec![1, 2, 3, 4, 5]); } @@ -480,10 +474,7 @@ fn array_flatten_no_nested_test() { result.as_ref().try_into().unwrap(); assert_eq!(arr.len(), 3); - let vals: Vec = arr - .iter() - .map(|r| r.as_ref().try_into().unwrap()) - .collect(); + let vals: Vec = arr.iter().map(|r| r.as_ref().try_into().unwrap()).collect(); assert_eq!(vals, vec![1, 2, 3]); } @@ -507,10 +498,7 @@ fn array_flatten_self_basic_test() { result.as_ref().try_into().unwrap(); assert_eq!(arr.len(), 4); - let vals: Vec = arr - .iter() - .map(|r| r.as_ref().try_into().unwrap()) - .collect(); + let vals: Vec = arr.iter().map(|r| r.as_ref().try_into().unwrap()).collect(); assert_eq!(vals, vec![1, 2, 3, 4]); } From 009717bc428d355375de1f2eab0813508bf5319c Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Mar 2026 20:41:26 +0900 Subject: [PATCH 292/314] Bump version to 1.1.9 --- Cargo.lock | 2 +- mrubyedge/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e7fa715..679db2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -622,7 +622,7 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.1.8" +version = "1.1.9" dependencies = [ "criterion", "fnv", diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 51c1251..70b45a6 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.1.8" +version = "1.1.9" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From c1e09cadc1b197dfe4ca6f3b3c3e90496ba659ee Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Mar 2026 20:46:43 +0900 Subject: [PATCH 293/314] Tmp change versions --- Cargo.lock | 80 ++++++++++++++++++++++++++----------- mruby-math/Cargo.toml | 4 +- mruby-serde-json/Cargo.toml | 4 +- mruby-time/Cargo.toml | 4 +- mrubyedge-cli/Cargo.toml | 6 +-- wasmsample/Cargo.toml | 5 +-- 6 files changed, 67 insertions(+), 36 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 679db2a..9b339f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -506,9 +506,9 @@ checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" [[package]] name = "js-sys" -version = "0.3.87" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93f0862381daaec758576dcc22eb7bbf4d7efd67328553f3b45a412a51a3fb21" +checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" dependencies = [ "once_cell", "wasm-bindgen", @@ -636,6 +636,19 @@ dependencies = [ "simple_endian", ] +[[package]] +name = "mrubyedge" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8196b89b47357c9d189abfdcb6024b0842817d089742cb127f5a223c11592a2b" +dependencies = [ + "plain", + "rand_core 0.10.0", + "rand_xorshift", + "regex", + "simple_endian", +] + [[package]] name = "mrubyedge-cli" version = "1.1.8" @@ -644,20 +657,29 @@ dependencies = [ "clap", "crossterm", "mruby-compiler2-sys", - "mrubyedge", - "mrubyedge-math", - "mrubyedge-time", + "mrubyedge 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge-math 0.1.0", + "mrubyedge-time 0.1.1", "nom", "rand", "syn", ] +[[package]] +name = "mrubyedge-math" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7faac1fa8bf95e7f02ca71824aebf139ed1dd20696e42e8e1c4cb8374dde2dbe" +dependencies = [ + "mrubyedge 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "mrubyedge-math" version = "0.1.1" dependencies = [ "mec-mrbc-sys", - "mrubyedge", + "mrubyedge 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -665,18 +687,28 @@ name = "mrubyedge-serde-json" version = "0.1.1" dependencies = [ "mec-mrbc-sys", - "mrubyedge", + "mrubyedge 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "serde_json", ] +[[package]] +name = "mrubyedge-time" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "583821a12194d23b55bf1fb9051d5559c9ca3481b8bf285693a20debf11fb093" +dependencies = [ + "libc", + "mrubyedge 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "mrubyedge-time" version = "0.1.2" dependencies = [ "libc", "mec-mrbc-sys", - "mrubyedge", + "mrubyedge 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -920,9 +952,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.9" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a96887878f22d7bad8a3b6dc5b7440e0ada9a245242924394987b21cf2210a4c" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rustc-hash" @@ -1128,9 +1160,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.110" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1de241cdc66a9d91bd84f097039eb140cdc6eec47e0cdbaf9d932a1dd6c35866" +checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" dependencies = [ "cfg-if", "once_cell", @@ -1141,9 +1173,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.110" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12fdf6649048f2e3de6d7d5ff3ced779cdedee0e0baffd7dff5cdfa3abc8a52" +checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1151,9 +1183,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.110" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e63d1795c565ac3462334c1e396fd46dbf481c40f51f5072c310717bc4fb309" +checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" dependencies = [ "bumpalo", "proc-macro2", @@ -1164,18 +1196,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.110" +version = "0.2.114" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e9f9cdac23a5ce71f6bf9f8824898a501e511892791ea2a0c6b8568c68b9cb53" +checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.87" +version = "0.3.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2c7c5718134e770ee62af3b6b4a84518ec10101aad610c024b64d6ff29bb1ff" +checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" dependencies = [ "js-sys", "wasm-bindgen", @@ -1308,18 +1340,18 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "zerocopy" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db6d35d663eadb6c932438e763b262fe1a70987f9ae936e60158176d710cae4a" +checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.39" +version = "0.8.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4122cd3169e94605190e77839c9a40d40ed048d305bfdc146e7df40ab0f3e517" +checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" dependencies = [ "proc-macro2", "quote", diff --git a/mruby-math/Cargo.toml b/mruby-math/Cargo.toml index 320c025..f00378d 100644 --- a/mruby-math/Cargo.toml +++ b/mruby-math/Cargo.toml @@ -7,8 +7,8 @@ description = "mruby-math provides Math module for mruby/edge" license = "BSD-3-Clause" [dependencies] -mrubyedge = { path = "../mrubyedge" } +mrubyedge = { version = "1.1.9" } [dev-dependencies] -mrubyedge = { path = "../mrubyedge", features = ["default"] } +mrubyedge = { version = "1.1.9", features = ["default"] } mec-mrbc-sys = "3.3.1" diff --git a/mruby-serde-json/Cargo.toml b/mruby-serde-json/Cargo.toml index 6b7eb54..4c87cef 100644 --- a/mruby-serde-json/Cargo.toml +++ b/mruby-serde-json/Cargo.toml @@ -7,10 +7,10 @@ description = "mruby-serde-json provides JSON serialization/deserialization for license = "BSD-3-Clause" [dependencies] -mrubyedge = { path = "../mrubyedge" } +mrubyedge = { version = "1.1.9" } serde = ">= 1.0.228" serde_json = ">= 1.0.149" [dev-dependencies] -mrubyedge = { path = "../mrubyedge", features = ["default"] } +mrubyedge = { version = "1.1.9", features = ["default"] } mec-mrbc-sys = "3.3.1" diff --git a/mruby-time/Cargo.toml b/mruby-time/Cargo.toml index 59f3c83..4e6956e 100644 --- a/mruby-time/Cargo.toml +++ b/mruby-time/Cargo.toml @@ -7,11 +7,11 @@ description = "mruby-time provides Time class for mruby/edge" license = "BSD-3-Clause" [dependencies] -mrubyedge = { path = "../mrubyedge" } +mrubyedge = { version = "1.1.9" } libc = "0.2" [dev-dependencies] -mrubyedge = { path = "../mrubyedge", features = ["default"] } +mrubyedge = { version = "1.1.9", features = ["default"] } mec-mrbc-sys = "3.3.1" [features] diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 05b1c81..6b2ce74 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -15,13 +15,13 @@ path = "src/main.rs" clap = { version = "4.5", features = ["derive"] } crossterm = "0.28" mruby-compiler2-sys = "0.3.0" -mrubyedge = { path = "../mrubyedge", features = [ +mrubyedge = { version = "1.1.9", features = [ "default", "mruby-random", "mruby-regexp", ] } -mrubyedge-math = { path = "../mruby-math" } -mrubyedge-time = { path = "../mruby-time" } +mrubyedge-math = { version = ">= 0.1.0" } +mrubyedge-time = { version = ">= 0.1.0" } rand = "0.9.2" nom = "7.1.3" askama = "0.12.1" diff --git a/wasmsample/Cargo.toml b/wasmsample/Cargo.toml index b92514a..b2fb625 100644 --- a/wasmsample/Cargo.toml +++ b/wasmsample/Cargo.toml @@ -7,9 +7,8 @@ edition = "2021" crate-type = ["cdylib", "rlib"] [dependencies.mrubyedge] -# path = "../mrubyedge" -version = "0.1.0" +version = "1.1.9" # [profile.release] # # Tell `rustc` to optimize for small code size. -# opt-level = "s" \ No newline at end of file +# opt-level = "s" From 57271213ba5b746cb2062ce5bde1f62d9389e418 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 2 Mar 2026 20:49:11 +0900 Subject: [PATCH 294/314] CLI released version 1.1.9. --- Cargo.lock | 22 +++++++++++----------- mrubyedge-cli/Cargo.toml | 6 +++--- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9b339f6..93ae539 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -651,15 +651,15 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.1.8" +version = "1.1.9" dependencies = [ "askama", "clap", "crossterm", "mruby-compiler2-sys", "mrubyedge 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)", - "mrubyedge-math 0.1.0", - "mrubyedge-time 0.1.1", + "mrubyedge-math 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge-time 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", @@ -667,18 +667,18 @@ dependencies = [ [[package]] name = "mrubyedge-math" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7faac1fa8bf95e7f02ca71824aebf139ed1dd20696e42e8e1c4cb8374dde2dbe" +version = "0.1.1" dependencies = [ + "mec-mrbc-sys", "mrubyedge 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "mrubyedge-math" version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94ab341d1a13e87a66cde9725f0a2f15a2e88c806b7995d9eb69968f4162fa45" dependencies = [ - "mec-mrbc-sys", "mrubyedge 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -694,20 +694,20 @@ dependencies = [ [[package]] name = "mrubyedge-time" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583821a12194d23b55bf1fb9051d5559c9ca3481b8bf285693a20debf11fb093" +version = "0.1.2" dependencies = [ "libc", + "mec-mrbc-sys", "mrubyedge 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "mrubyedge-time" version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143c9c3b434a5c16e206d2cb18917f8b6a8e035259b01029a2c7f9196a9642ec" dependencies = [ "libc", - "mec-mrbc-sys", "mrubyedge 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 6b2ce74..01a475d 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.1.8" +version = "1.1.9" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -20,8 +20,8 @@ mrubyedge = { version = "1.1.9", features = [ "mruby-random", "mruby-regexp", ] } -mrubyedge-math = { version = ">= 0.1.0" } -mrubyedge-time = { version = ">= 0.1.0" } +mrubyedge-math = { version = ">= 0.1.1" } +mrubyedge-time = { version = ">= 0.1.2" } rand = "0.9.2" nom = "7.1.3" askama = "0.12.1" From 139236dc82c1940438dce5ef9be5eaafa42d5428 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 8 Mar 2026 23:57:20 +0900 Subject: [PATCH 295/314] Implement tagged error --- mrubyedge/src/error.rs | 6 ++++++ mrubyedge/src/yamrb/value.rs | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/mrubyedge/src/error.rs b/mrubyedge/src/error.rs index 73f74d0..0e4fe61 100644 --- a/mrubyedge/src/error.rs +++ b/mrubyedge/src/error.rs @@ -19,6 +19,8 @@ pub enum Error { NameError(String), ZeroDivisionError, + TaggedError(&'static str, String), + Break(Rc), BlockReturn(usize, Rc), } @@ -49,6 +51,8 @@ impl Error { Error::NameError(msg) => format!("Cannot found name: {}", msg), Error::ZeroDivisionError => "divided by 0".to_string(), + Error::TaggedError(tag, msg) => format!("[{}] {}", tag, msg), + Error::Break(_) => "[Break]".to_string(), Error::BlockReturn(_, _) => "[BlockReturn]".to_string(), } @@ -115,6 +119,8 @@ impl From for StaticError { Error::NameError(msg) => StaticError::General(format!("Cannot found name: {}", msg)), Error::ZeroDivisionError => StaticError::General("divided by 0".to_string()), + Error::TaggedError(tag, msg) => StaticError::General(format!("[{}] {}", tag, msg)), + Error::Break(_) => StaticError::General("[Break]".to_string()), Error::BlockReturn(_, _) => StaticError::General("[BlockReturn]".to_string()), } diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index ff9687d..5906e94 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -1238,6 +1238,12 @@ impl RClass { Error::NameError(_) => vm.get_class_by_name("NameError"), Error::ZeroDivisionError => vm.get_class_by_name("ZeroDivisionError"), + Error::TaggedError(tag, _) => vm + .get_const_by_name(tag) + .and_then(|exc| exc.get_class(vm).into()) + .or_else(|| vm.get_class_by_name("Exception").into()) + .expect("Invalid error tag"), + Error::Break(_) => vm.get_class_by_name("_Break"), Error::BlockReturn(_, _) => vm.get_class_by_name("_BlockReturn"), } From 3f6fe28f6f2a89518fb1de14de836fca2ce77ce4 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 9 Mar 2026 00:09:06 +0900 Subject: [PATCH 296/314] Add tests, fix some --- mrubyedge/src/yamrb/value.rs | 11 ++++-- mrubyedge/tests/raise_rust.rs | 63 +++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 3 deletions(-) diff --git a/mrubyedge/src/yamrb/value.rs b/mrubyedge/src/yamrb/value.rs index 5906e94..39e4716 100644 --- a/mrubyedge/src/yamrb/value.rs +++ b/mrubyedge/src/yamrb/value.rs @@ -1240,9 +1240,14 @@ impl RClass { Error::TaggedError(tag, _) => vm .get_const_by_name(tag) - .and_then(|exc| exc.get_class(vm).into()) - .or_else(|| vm.get_class_by_name("Exception").into()) - .expect("Invalid error tag"), + .and_then(|obj| { + if let RValue::Class(c) = &obj.value { + Some(c.clone()) + } else { + None + } + }) + .unwrap_or_else(|| vm.get_class_by_name("Exception")), Error::Break(_) => vm.get_class_by_name("_Break"), Error::BlockReturn(_, _) => vm.get_class_by_name("_BlockReturn"), diff --git a/mrubyedge/tests/raise_rust.rs b/mrubyedge/tests/raise_rust.rs index 262e94d..d28ba04 100644 --- a/mrubyedge/tests/raise_rust.rs +++ b/mrubyedge/tests/raise_rust.rs @@ -18,6 +18,22 @@ fn mrb_test_dummy_raise(_vm: &mut VM, _args: &[Rc]) -> Result]) -> Result, Error> { + Err(Error::TaggedError( + "CustomError", + "Intentional Custom Error".to_string(), + )) +} + #[test] fn rust_raise_test() { let code = " @@ -132,3 +148,50 @@ fn rust_noname_rescue_test() { .unwrap(); assert_eq!(&result, "rescued: Cannot found name: NoName"); } + +#[test] +fn custom_error_raise_test() { + let code = " + def test_raise + custom_raise + end + "; + let binary = mrbc_compile("custom_error_raise", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + prelude_custom_error_func(&mut vm); + vm.run().unwrap(); + + // Assert: ユーザー定義例外 CustomError が関数内で raise されてもエラーが伝播する + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_raise", &args).err(); + assert_eq!( + &result.unwrap().message(), + "[CustomError] Intentional Custom Error" + ); +} + +#[test] +fn custom_error_rescue_test() { + let code = " + def test_raise + custom_raise + rescue => e + \"rescued: #{e.message}\" + end + "; + let binary = mrbc_compile("custom_error_rescue", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + prelude_custom_error_func(&mut vm); + vm.run().unwrap(); + + // Assert: ユーザー定義例外 CustomError を rescue で catch できる + let args = vec![]; + let result: String = mrb_funcall(&mut vm, None, "test_raise", &args) + .unwrap() + .as_ref() + .try_into() + .unwrap(); + assert_eq!(&result, "rescued: [CustomError] Intentional Custom Error"); +} From b50363e09ce4d6c01dbab3423864e92fa71fdb51 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 9 Mar 2026 00:13:12 +0900 Subject: [PATCH 297/314] Fix comments --- mrubyedge/tests/raise_rust.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mrubyedge/tests/raise_rust.rs b/mrubyedge/tests/raise_rust.rs index d28ba04..a03df7a 100644 --- a/mrubyedge/tests/raise_rust.rs +++ b/mrubyedge/tests/raise_rust.rs @@ -19,7 +19,7 @@ fn mrb_test_dummy_raise(_vm: &mut VM, _args: &[Rc]) -> Result Date: Mon, 9 Mar 2026 00:17:52 +0900 Subject: [PATCH 298/314] Bump --- Cargo.lock | 28 ++++++++++++++-------------- mrubyedge/Cargo.toml | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 93ae539..8131be5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -623,12 +623,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8196b89b47357c9d189abfdcb6024b0842817d089742cb127f5a223c11592a2b" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -638,10 +635,13 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8196b89b47357c9d189abfdcb6024b0842817d089742cb127f5a223c11592a2b" +version = "1.1.10" dependencies = [ + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -657,7 +657,7 @@ dependencies = [ "clap", "crossterm", "mruby-compiler2-sys", - "mrubyedge 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.9", "mrubyedge-math 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "mrubyedge-time 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "nom", @@ -670,7 +670,7 @@ name = "mrubyedge-math" version = "0.1.1" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.9", ] [[package]] @@ -679,7 +679,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94ab341d1a13e87a66cde9725f0a2f15a2e88c806b7995d9eb69968f4162fa45" dependencies = [ - "mrubyedge 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.9", ] [[package]] @@ -687,7 +687,7 @@ name = "mrubyedge-serde-json" version = "0.1.1" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.9", "serde", "serde_json", ] @@ -698,7 +698,7 @@ version = "0.1.2" dependencies = [ "libc", "mec-mrbc-sys", - "mrubyedge 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.9", ] [[package]] @@ -708,7 +708,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "143c9c3b434a5c16e206d2cb18917f8b6a8e035259b01029a2c7f9196a9642ec" dependencies = [ "libc", - "mrubyedge 1.1.9 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.9", ] [[package]] diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 70b45a6..01e9417 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.1.9" +version = "1.1.10" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From 8a7496f2883afec4c7232b55f7b772d4fa06fbb4 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Mon, 9 Mar 2026 00:18:55 +0900 Subject: [PATCH 299/314] Bump cli --- Cargo.lock | 38 +++++++++++++++++++------------------- mrubyedge-cli/Cargo.toml | 4 ++-- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8131be5..2fcadaf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -516,9 +516,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.182" +version = "0.2.183" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6800badb6cb2082ffd7b6a67e6125bb39f18782f793520caee8cb8846be06112" +checksum = "b5b646652bf6661599e1da8901b3b9522896f01e736bad5f723fe7a3a27f899d" [[package]] name = "libloading" @@ -622,10 +622,13 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8196b89b47357c9d189abfdcb6024b0842817d089742cb127f5a223c11592a2b" +version = "1.1.10" dependencies = [ + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -636,12 +639,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5fb7c3a4987ec0c40b6363c463393f7550859e1963c6a4d4716d6169320160" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -651,13 +651,13 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.1.9" +version = "1.1.10" dependencies = [ "askama", "clap", "crossterm", "mruby-compiler2-sys", - "mrubyedge 1.1.9", + "mrubyedge 1.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "mrubyedge-math 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "mrubyedge-time 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "nom", @@ -670,7 +670,7 @@ name = "mrubyedge-math" version = "0.1.1" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.9", + "mrubyedge 1.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -679,7 +679,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94ab341d1a13e87a66cde9725f0a2f15a2e88c806b7995d9eb69968f4162fa45" dependencies = [ - "mrubyedge 1.1.9", + "mrubyedge 1.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -687,7 +687,7 @@ name = "mrubyedge-serde-json" version = "0.1.1" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.9", + "mrubyedge 1.1.10 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "serde_json", ] @@ -698,7 +698,7 @@ version = "0.1.2" dependencies = [ "libc", "mec-mrbc-sys", - "mrubyedge 1.1.9", + "mrubyedge 1.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -708,7 +708,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "143c9c3b434a5c16e206d2cb18917f8b6a8e035259b01029a2c7f9196a9642ec" dependencies = [ "libc", - "mrubyedge 1.1.9", + "mrubyedge 1.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -841,9 +841,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.44" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b2ebcf727b7760c461f091f9f0f539b77b8e87f2fd88131e7f1b433b3cece4" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 01a475d..7d50540 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.1.9" +version = "1.1.10" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -15,7 +15,7 @@ path = "src/main.rs" clap = { version = "4.5", features = ["derive"] } crossterm = "0.28" mruby-compiler2-sys = "0.3.0" -mrubyedge = { version = "1.1.9", features = [ +mrubyedge = { version = "1.1.10", features = [ "default", "mruby-random", "mruby-regexp", From 9c87b2d595c4b89f3102b5f3ac97bf889e09207a Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 26 Mar 2026 19:34:49 +0900 Subject: [PATCH 300/314] Make gems idempotent loadable --- mruby-math/src/lib.rs | 4 ++++ mruby-serde-json/src/lib.rs | 4 ++++ mruby-time/src/lib.rs | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/mruby-math/src/lib.rs b/mruby-math/src/lib.rs index 4a7d790..431a5da 100644 --- a/mruby-math/src/lib.rs +++ b/mruby-math/src/lib.rs @@ -10,6 +10,10 @@ use mrubyedge::{ }; pub fn init_math(vm: &mut VM) { + if vm.get_const_by_name("Math").is_some() { + return; + } + let math_module = vm.define_module("Math", None); // Define constants diff --git a/mruby-serde-json/src/lib.rs b/mruby-serde-json/src/lib.rs index c8cb96d..448c713 100644 --- a/mruby-serde-json/src/lib.rs +++ b/mruby-serde-json/src/lib.rs @@ -8,6 +8,10 @@ use mrubyedge::{ }; pub fn init_json(vm: &mut VM) { + if vm.get_const_by_name("JSON").is_some() { + return; + } + let json_class = vm.define_class("JSON", None, None); mrb_define_class_cmethod( diff --git a/mruby-time/src/lib.rs b/mruby-time/src/lib.rs index 1977a26..0e0cf2d 100644 --- a/mruby-time/src/lib.rs +++ b/mruby-time/src/lib.rs @@ -475,6 +475,10 @@ fn float_to_sec_nsec(obj: &RObject) -> Result<(i64, u32), Error> { /// Initialize the Time class in the VM. /// Call this after `VM::open` to make `Time` available in Ruby code. pub fn init_time(vm: &mut VM) { + if vm.get_const_by_name("Time").is_some() { + return; + } + let time_class = vm.define_class("Time", None, None); // Class methods From e2b5ee32dcc4983a5f403120713ba09527f5172f Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 26 Mar 2026 19:36:46 +0900 Subject: [PATCH 301/314] Update --- Cargo.lock | 22 +++++++++++----------- mruby-math/Cargo.toml | 2 +- mruby-serde-json/Cargo.toml | 2 +- mruby-time/Cargo.toml | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2fcadaf..31f6f17 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -658,8 +658,8 @@ dependencies = [ "crossterm", "mruby-compiler2-sys", "mrubyedge 1.1.10 (registry+https://github.com/rust-lang/crates.io-index)", - "mrubyedge-math 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", - "mrubyedge-time 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge-math 0.1.1", + "mrubyedge-time 0.1.2", "nom", "rand", "syn", @@ -668,23 +668,23 @@ dependencies = [ [[package]] name = "mrubyedge-math" version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94ab341d1a13e87a66cde9725f0a2f15a2e88c806b7995d9eb69968f4162fa45" dependencies = [ - "mec-mrbc-sys", "mrubyedge 1.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "mrubyedge-math" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ab341d1a13e87a66cde9725f0a2f15a2e88c806b7995d9eb69968f4162fa45" +version = "0.1.2" dependencies = [ + "mec-mrbc-sys", "mrubyedge 1.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "mrubyedge-serde-json" -version = "0.1.1" +version = "0.1.2" dependencies = [ "mec-mrbc-sys", "mrubyedge 1.1.10 (registry+https://github.com/rust-lang/crates.io-index)", @@ -695,19 +695,19 @@ dependencies = [ [[package]] name = "mrubyedge-time" version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "143c9c3b434a5c16e206d2cb18917f8b6a8e035259b01029a2c7f9196a9642ec" dependencies = [ "libc", - "mec-mrbc-sys", "mrubyedge 1.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "mrubyedge-time" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143c9c3b434a5c16e206d2cb18917f8b6a8e035259b01029a2c7f9196a9642ec" +version = "0.1.3" dependencies = [ "libc", + "mec-mrbc-sys", "mrubyedge 1.1.10 (registry+https://github.com/rust-lang/crates.io-index)", ] diff --git a/mruby-math/Cargo.toml b/mruby-math/Cargo.toml index f00378d..606e924 100644 --- a/mruby-math/Cargo.toml +++ b/mruby-math/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-math" -version = "0.1.1" +version = "0.1.2" edition = "2024" authors = ["Uchio Kondo "] description = "mruby-math provides Math module for mruby/edge" diff --git a/mruby-serde-json/Cargo.toml b/mruby-serde-json/Cargo.toml index 4c87cef..3fee489 100644 --- a/mruby-serde-json/Cargo.toml +++ b/mruby-serde-json/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-serde-json" -version = "0.1.1" +version = "0.1.2" edition = "2024" authors = ["Uchio Kondo "] description = "mruby-serde-json provides JSON serialization/deserialization for mruby/edge using serde_json" diff --git a/mruby-time/Cargo.toml b/mruby-time/Cargo.toml index 4e6956e..e054210 100644 --- a/mruby-time/Cargo.toml +++ b/mruby-time/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-time" -version = "0.1.2" +version = "0.1.3" edition = "2024" authors = ["Uchio Kondo "] description = "mruby-time provides Time class for mruby/edge" From ddef8101ff699c3ff860db1c288b154b5cf79459 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 29 Mar 2026 18:04:39 +0900 Subject: [PATCH 302/314] Add testcase --- mrubyedge/tests/array.rs | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/mrubyedge/tests/array.rs b/mrubyedge/tests/array.rs index 022da2f..e12a765 100644 --- a/mrubyedge/tests/array.rs +++ b/mrubyedge/tests/array.rs @@ -61,6 +61,25 @@ fn array_at_test() { assert_eq!(result, 2); } +#[test] +fn array_negative_index_test() { + let code = r#" + def test_array_negative_index + arr = [1, 2, 3] + [arr[-1], arr[-2], arr[-3]] + end + "#; + let binary = mrbc_compile("array_negative_index", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_negative_index", &args).unwrap(); + let result: (i32, i32, i32) = result.as_ref().try_into().unwrap(); + assert_eq!(result, (3, 2, 1)); +} + #[test] fn array_clear_test() { let code = r#" From c8339cd8e3b0a18addbe4efbe4bae404a4ccbf9c Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 29 Mar 2026 18:09:05 +0900 Subject: [PATCH 303/314] Fix negative value handling in []/[]= --- mrubyedge/src/yamrb/prelude/array.rs | 15 ++++++++++++--- mrubyedge/tests/array.rs | 22 ++++++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/mrubyedge/src/yamrb/prelude/array.rs b/mrubyedge/src/yamrb/prelude/array.rs index db4daff..62d1011 100644 --- a/mrubyedge/src/yamrb/prelude/array.rs +++ b/mrubyedge/src/yamrb/prelude/array.rs @@ -197,8 +197,12 @@ pub fn mrb_array_get_index(this: Rc, args: &[Rc]) -> Result]) -> Result, args: &[Rc]) -> Result, Error> { - let index: usize = args[0].as_ref().try_into()?; + let index: i32 = args[0].as_ref().try_into()?; let value = &args[1]; match &this.value { RValue::Array(a) => { let mut a = a.borrow_mut(); + let index = if index < 0 { + (a.len() as i32 + index) as usize + } else { + index as usize + }; if a.len() <= index { a.insert(index, value.clone()); } else { diff --git a/mrubyedge/tests/array.rs b/mrubyedge/tests/array.rs index e12a765..90997d1 100644 --- a/mrubyedge/tests/array.rs +++ b/mrubyedge/tests/array.rs @@ -80,6 +80,28 @@ fn array_negative_index_test() { assert_eq!(result, (3, 2, 1)); } +#[test] +fn array_set_negative_index_test() { + let code = r#" + def test_array_set_negative_index + arr = [1, 2, 3] + arr[-1] = 4 + arr[-2] = 5 + arr[-3] = 6 + arr + end + "#; + let binary = mrbc_compile("array_set_negative_index", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_array_set_negative_index", &args).unwrap(); + let result: (i32, i32, i32) = result.as_ref().try_into().unwrap(); + assert_eq!(result, (6, 5, 4)); +} + #[test] fn array_clear_test() { let code = r#" From 4f159667b54e3947179f32163ef7dc05093a7629 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 29 Mar 2026 18:17:35 +0900 Subject: [PATCH 304/314] Bump --- Cargo.lock | 28 ++++++++++++++-------------- mrubyedge/Cargo.toml | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 31f6f17..806f1bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -623,12 +623,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5fb7c3a4987ec0c40b6363c463393f7550859e1963c6a4d4716d6169320160" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -638,10 +635,13 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5fb7c3a4987ec0c40b6363c463393f7550859e1963c6a4d4716d6169320160" +version = "1.1.11" dependencies = [ + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -657,7 +657,7 @@ dependencies = [ "clap", "crossterm", "mruby-compiler2-sys", - "mrubyedge 1.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.10", "mrubyedge-math 0.1.1", "mrubyedge-time 0.1.2", "nom", @@ -671,7 +671,7 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94ab341d1a13e87a66cde9725f0a2f15a2e88c806b7995d9eb69968f4162fa45" dependencies = [ - "mrubyedge 1.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.10", ] [[package]] @@ -679,7 +679,7 @@ name = "mrubyedge-math" version = "0.1.2" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.10", ] [[package]] @@ -687,7 +687,7 @@ name = "mrubyedge-serde-json" version = "0.1.2" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.10", "serde", "serde_json", ] @@ -699,7 +699,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "143c9c3b434a5c16e206d2cb18917f8b6a8e035259b01029a2c7f9196a9642ec" dependencies = [ "libc", - "mrubyedge 1.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.10", ] [[package]] @@ -708,7 +708,7 @@ version = "0.1.3" dependencies = [ "libc", "mec-mrbc-sys", - "mrubyedge 1.1.10 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.10", ] [[package]] diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 01e9417..0b913f2 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.1.10" +version = "1.1.11" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From 7ee48597816aa37bae4327f26ab1ae8c451fa3b6 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 29 Mar 2026 18:18:46 +0900 Subject: [PATCH 305/314] Bump 2 --- Cargo.lock | 134 +++++++++++++++++++-------------------- mrubyedge-cli/Cargo.toml | 4 +- 2 files changed, 69 insertions(+), 69 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 806f1bd..3a62f4e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,9 +19,9 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.21" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" dependencies = [ "anstyle", "anstyle-parse", @@ -34,15 +34,15 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.13" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5192cca8006f1fd4f7237516f40fa183bb07f8fbdfedaa0036de5ea9b0b45e78" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" -version = "0.2.7" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" dependencies = [ "utf8parse", ] @@ -186,9 +186,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.2.56" +version = "1.2.58" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aebf35691d1bfb0ac386a69bac2fde4dd276fb618cf8bf4f5318fe285e821bb2" +checksum = "e1e928d4b69e3077709075a938a05ffbedfa53a84c8f766efbf8220bb1ff60e1" dependencies = [ "find-msvc-tools", "shlex", @@ -249,9 +249,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", "clap_derive", @@ -259,9 +259,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.60" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ "anstream", "anstyle", @@ -271,9 +271,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.55" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" dependencies = [ "heck", "proc-macro2", @@ -283,15 +283,15 @@ dependencies = [ [[package]] name = "clap_lex" -version = "1.0.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a822ea5bc7590f9d40f1ba12c0dc3c2760f3482c6984db1573ad11031420831" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "colorchoice" -version = "1.0.4" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "criterion" @@ -500,15 +500,15 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92ecc6618181def0457392ccd0ee51198e065e016d1d527a7ac1b6dc7c1f09d2" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "js-sys" -version = "0.3.91" +version = "0.3.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b49715b7073f385ba4bc528e5747d02e66cb39c6146efb66b781f131f0fb399c" +checksum = "cc4c90f45aa2e6eacbe8645f77fdea542ac97a494bcd117a67df9ff4d611f995" dependencies = [ "once_cell", "wasm-bindgen", @@ -598,9 +598,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "mio" -version = "1.1.1" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", "log", @@ -622,10 +622,13 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d5fb7c3a4987ec0c40b6363c463393f7550859e1963c6a4d4716d6169320160" +version = "1.1.11" dependencies = [ + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -636,12 +639,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c723558424519e868a2e6fe83b9e741820e9c73852531de86a8717b7a14b3906" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -651,15 +651,15 @@ dependencies = [ [[package]] name = "mrubyedge-cli" -version = "1.1.10" +version = "1.1.11" dependencies = [ "askama", "clap", "crossterm", "mruby-compiler2-sys", - "mrubyedge 1.1.10", - "mrubyedge-math 0.1.1", - "mrubyedge-time 0.1.2", + "mrubyedge 1.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge-math 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge-time 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "nom", "rand", "syn", @@ -667,19 +667,19 @@ dependencies = [ [[package]] name = "mrubyedge-math" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94ab341d1a13e87a66cde9725f0a2f15a2e88c806b7995d9eb69968f4162fa45" +version = "0.1.2" dependencies = [ - "mrubyedge 1.1.10", + "mec-mrbc-sys", + "mrubyedge 1.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "mrubyedge-math" version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f80848bfd13ee2cce1e16592197c594b8a0d8c029b4d5c94308b1c3d51c5e3" dependencies = [ - "mec-mrbc-sys", - "mrubyedge 1.1.10", + "mrubyedge 1.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -687,28 +687,28 @@ name = "mrubyedge-serde-json" version = "0.1.2" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.10", + "mrubyedge 1.1.11 (registry+https://github.com/rust-lang/crates.io-index)", "serde", "serde_json", ] [[package]] name = "mrubyedge-time" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "143c9c3b434a5c16e206d2cb18917f8b6a8e035259b01029a2c7f9196a9642ec" +version = "0.1.3" dependencies = [ "libc", - "mrubyedge 1.1.10", + "mec-mrbc-sys", + "mrubyedge 1.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "mrubyedge-time" version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0104bd1eb5872c98245b0c59fc9488c4b3755453f2e5d1ddbc9f2479e3f21e7a" dependencies = [ "libc", - "mec-mrbc-sys", - "mrubyedge 1.1.10", + "mrubyedge 1.1.11 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -732,9 +732,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.3" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "once_cell_polyfill" @@ -958,9 +958,9 @@ checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] name = "rustc-hash" -version = "2.1.1" +version = "2.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "357703d41365b4b27c590e3ed91eabb1b663f07c4c084095e60cbed4362dff0d" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" [[package]] name = "rustix" @@ -1160,9 +1160,9 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.114" +version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6532f9a5c1ece3798cb1c2cfdba640b9b3ba884f5db45973a6f442510a87d38e" +checksum = "6523d69017b7633e396a89c5efab138161ed5aafcbc8d3e5c5a42ae38f50495a" dependencies = [ "cfg-if", "once_cell", @@ -1173,9 +1173,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.114" +version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18a2d50fcf105fb33bb15f00e7a77b772945a2ee45dcf454961fd843e74c18e6" +checksum = "4e3a6c758eb2f701ed3d052ff5737f5bfe6614326ea7f3bbac7156192dc32e67" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1183,9 +1183,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.114" +version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03ce4caeaac547cdf713d280eda22a730824dd11e6b8c3ca9e42247b25c631e3" +checksum = "921de2737904886b52bcbb237301552d05969a6f9c40d261eb0533c8b055fedf" dependencies = [ "bumpalo", "proc-macro2", @@ -1196,18 +1196,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.114" +version = "0.2.115" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75a326b8c223ee17883a4251907455a2431acc2791c98c26279376490c378c16" +checksum = "a93e946af942b58934c604527337bad9ae33ba1d5c6900bbb41c2c07c2364a93" dependencies = [ "unicode-ident", ] [[package]] name = "web-sys" -version = "0.3.91" +version = "0.3.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "854ba17bb104abfb26ba36da9729addc7ce7f06f5c0f90f3c391f8461cca21f9" +checksum = "84cde8507f4d7cfcb1185b8cb5890c494ffea65edbe1ba82cfd63661c805ed94" dependencies = [ "js-sys", "wasm-bindgen", @@ -1340,18 +1340,18 @@ checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "zerocopy" -version = "0.8.40" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a789c6e490b576db9f7e6b6d661bcc9799f7c0ac8352f56ea20193b2681532e5" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.8.40" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f65c489a7071a749c849713807783f70672b28094011623e200cb86dcb835953" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", diff --git a/mrubyedge-cli/Cargo.toml b/mrubyedge-cli/Cargo.toml index 7d50540..c7f1444 100644 --- a/mrubyedge-cli/Cargo.toml +++ b/mrubyedge-cli/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge-cli" -version = "1.1.10" +version = "1.1.11" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge cli endpoint - run, compile to wasm, etc." @@ -15,7 +15,7 @@ path = "src/main.rs" clap = { version = "4.5", features = ["derive"] } crossterm = "0.28" mruby-compiler2-sys = "0.3.0" -mrubyedge = { version = "1.1.10", features = [ +mrubyedge = { version = "1.1.11", features = [ "default", "mruby-random", "mruby-regexp", From e851dadfc7e04254fe99ca1144a70c499b7c2818 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 29 Mar 2026 20:50:37 +0900 Subject: [PATCH 306/314] Support finite?, infinite?, and nan? for Float --- mrubyedge/src/yamrb/prelude/float.rs | 49 ++++++++++++++++++++++++ mrubyedge/tests/float.rs | 57 ++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) diff --git a/mrubyedge/src/yamrb/prelude/float.rs b/mrubyedge/src/yamrb/prelude/float.rs index 616cb34..3115f6b 100644 --- a/mrubyedge/src/yamrb/prelude/float.rs +++ b/mrubyedge/src/yamrb/prelude/float.rs @@ -17,6 +17,19 @@ pub(crate) fn initialize_float(vm: &mut VM) { mrb_define_cmethod(vm, float_class.clone(), "-@", Box::new(mrb_float_negative)); mrb_define_cmethod(vm, float_class.clone(), "**", Box::new(mrb_float_power)); mrb_define_cmethod(vm, float_class.clone(), "abs", Box::new(mrb_float_abs)); + mrb_define_cmethod( + vm, + float_class.clone(), + "finite?", + Box::new(mrb_float_finite), + ); + mrb_define_cmethod( + vm, + float_class.clone(), + "infinite?", + Box::new(mrb_float_infinite), + ); + mrb_define_cmethod(vm, float_class.clone(), "nan?", Box::new(mrb_float_nan)); mrb_define_cmethod( vm, float_class.clone(), @@ -50,6 +63,42 @@ pub fn mrb_float_to_f(vm: &mut VM, _args: &[Rc]) -> Result, } } +pub fn mrb_float_finite(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + match &this.value { + crate::yamrb::value::RValue::Float(f) => { + Ok(RObject::boolean(f.is_finite()).to_refcount_assigned()) + } + _ => Err(Error::RuntimeError( + "Float#finite? must be called on a Float".to_string(), + )), + } +} + +pub fn mrb_float_infinite(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + match &this.value { + crate::yamrb::value::RValue::Float(f) => { + Ok(RObject::boolean(f.is_infinite()).to_refcount_assigned()) + } + _ => Err(Error::RuntimeError( + "Float#infinite? must be called on a Float".to_string(), + )), + } +} + +pub fn mrb_float_nan(vm: &mut VM, _args: &[Rc]) -> Result, Error> { + let this = vm.getself()?; + match &this.value { + crate::yamrb::value::RValue::Float(f) => { + Ok(RObject::boolean(f.is_nan()).to_refcount_assigned()) + } + _ => Err(Error::RuntimeError( + "Float#nan? must be called on a Float".to_string(), + )), + } +} + pub fn mrb_float_inspect(vm: &mut VM, _args: &[Rc]) -> Result, Error> { let this = vm.getself()?; match &this.value { diff --git a/mrubyedge/tests/float.rs b/mrubyedge/tests/float.rs index ee28bfa..7a634cd 100644 --- a/mrubyedge/tests/float.rs +++ b/mrubyedge/tests/float.rs @@ -95,3 +95,60 @@ fn float_add_integer_test() { let result_float: f64 = result.as_ref().try_into().unwrap(); assert_eq!(result_float, 8.5); } + +#[test] +fn float_infinite_test() { + let code = r#" + def test_infinite + value = 1.0 / 0.0 + value.infinite? + end + "#; + let binary = mrbc_compile("float_infinite", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_infinite", &args).unwrap(); + let infinite: bool = result.as_ref().try_into().unwrap(); + assert!(infinite); +} + +#[test] +fn float_finite_test() { + let code = r#" + def test_finite + value = 1.0 / 1.0 + value.finite? + end + "#; + let binary = mrbc_compile("float_finite", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_finite", &args).unwrap(); + let finite: bool = result.as_ref().try_into().unwrap(); + assert!(finite); +} + +#[test] +fn float_nan_test() { + let code = r#" + def test_nan + value = 0.0 / 0.0 + value.nan? + end + "#; + let binary = mrbc_compile("float_nan", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_nan", &args).unwrap(); + let nan: bool = result.as_ref().try_into().unwrap(); + assert!(nan); +} From 55280d7c896f2f59ea3074d31ad45141b0be144d Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 29 Mar 2026 20:54:18 +0900 Subject: [PATCH 307/314] Support constants --- mrubyedge/src/yamrb/prelude/float.rs | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/mrubyedge/src/yamrb/prelude/float.rs b/mrubyedge/src/yamrb/prelude/float.rs index 3115f6b..0853321 100644 --- a/mrubyedge/src/yamrb/prelude/float.rs +++ b/mrubyedge/src/yamrb/prelude/float.rs @@ -38,6 +38,20 @@ pub(crate) fn initialize_float(vm: &mut VM) { ); mrb_define_cmethod(vm, float_class.clone(), "to_s", Box::new(mrb_float_inspect)); mrb_define_cmethod(vm, float_class.clone(), "clamp", Box::new(mrb_float_clamp)); + + let mut const_table = float_class.consts.borrow_mut(); + const_table.insert( + "INFINITY".to_string(), + RObject::float(f64::INFINITY).to_refcount_assigned(), + ); + const_table.insert( + "NAN".to_string(), + RObject::float(f64::NAN).to_refcount_assigned(), + ); + const_table.insert( + "EPSILON".to_string(), + RObject::float(f64::EPSILON).to_refcount_assigned(), + ); } pub fn mrb_float_to_i(vm: &mut VM, _args: &[Rc]) -> Result, Error> { From 9af49295fc53ca33cb3ec2d8a982cad1eb7288ac Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sun, 29 Mar 2026 20:59:44 +0900 Subject: [PATCH 308/314] Tests --- mrubyedge/tests/float.rs | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/mrubyedge/tests/float.rs b/mrubyedge/tests/float.rs index 7a634cd..b00bce9 100644 --- a/mrubyedge/tests/float.rs +++ b/mrubyedge/tests/float.rs @@ -2,7 +2,10 @@ extern crate mec_mrbc_sys; extern crate mrubyedge; mod helpers; +use std::rc::Rc; + use helpers::*; +use mrubyedge::RObject; #[test] fn float_clamp_test() { @@ -152,3 +155,31 @@ fn float_nan_test() { let nan: bool = result.as_ref().try_into().unwrap(); assert!(nan); } + +#[test] +fn float_constants_test() { + let code = r#" + def test_constants + [ + Float::INFINITY, + Float::NAN, + Float::EPSILON + ] + end + "#; + let binary = mrbc_compile("float_constants", code); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let mut vm = mrubyedge::yamrb::vm::VM::open(&mut rite); + vm.run().unwrap(); + + let args = vec![]; + let result = mrb_funcall(&mut vm, None, "test_constants", &args).unwrap(); + let constants: Vec> = result.as_ref().try_into().unwrap(); + let floats: Vec = constants + .iter() + .map(|c| c.as_ref().try_into().unwrap()) + .collect::>(); + assert_eq!(floats[0], f64::INFINITY); + assert!(floats[1].is_nan()); + assert_eq!(floats[2], f64::EPSILON); +} From d2111228e5ebc0a005c231fc6567f5b1e3048670 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 2 Apr 2026 22:17:04 +0900 Subject: [PATCH 309/314] Fix overriding existing module --- mrubyedge/src/yamrb/vm.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index 0fc7e6f..b3ab650 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -645,7 +645,18 @@ impl VM { /// Defines a new module, optionally nested under another module, and stores /// it in the VM's constant table so it becomes accessible to Ruby code. + /// If a module with the same name already exists, it returns the existing one. pub fn define_module(&mut self, name: &str, parent_module: Option>) -> Rc { + let existing = if let Some(ref parent) = parent_module { + parent.consts.borrow().get(name).cloned() + } else { + self.consts.get(name).cloned() + }; + if let Some(existing) = existing + && let RValue::Module(ref m) = existing.value + { + return m.clone(); + } let module = Rc::new(RModule::new(name)); if let Some(parent) = parent_module { module.parent.replace(Some(parent)); From 8e803ca8b5781fef42f89bfe7dd6f5e705240f91 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Thu, 2 Apr 2026 22:21:04 +0900 Subject: [PATCH 310/314] Bump --- Cargo.lock | 28 ++++++++++++++-------------- mrubyedge/Cargo.toml | 2 +- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a62f4e..72ef834 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -623,12 +623,9 @@ dependencies = [ [[package]] name = "mrubyedge" version = "1.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c723558424519e868a2e6fe83b9e741820e9c73852531de86a8717b7a14b3906" dependencies = [ - "criterion", - "fnv", - "mec-mrbc-sys", - "mruby-compiler2-sys", - "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -638,10 +635,13 @@ dependencies = [ [[package]] name = "mrubyedge" -version = "1.1.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c723558424519e868a2e6fe83b9e741820e9c73852531de86a8717b7a14b3906" +version = "1.1.12" dependencies = [ + "criterion", + "fnv", + "mec-mrbc-sys", + "mruby-compiler2-sys", + "once_cell", "plain", "rand_core 0.10.0", "rand_xorshift", @@ -657,7 +657,7 @@ dependencies = [ "clap", "crossterm", "mruby-compiler2-sys", - "mrubyedge 1.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.11", "mrubyedge-math 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "mrubyedge-time 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", "nom", @@ -670,7 +670,7 @@ name = "mrubyedge-math" version = "0.1.2" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.11", ] [[package]] @@ -679,7 +679,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "31f80848bfd13ee2cce1e16592197c594b8a0d8c029b4d5c94308b1c3d51c5e3" dependencies = [ - "mrubyedge 1.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.11", ] [[package]] @@ -687,7 +687,7 @@ name = "mrubyedge-serde-json" version = "0.1.2" dependencies = [ "mec-mrbc-sys", - "mrubyedge 1.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.11", "serde", "serde_json", ] @@ -698,7 +698,7 @@ version = "0.1.3" dependencies = [ "libc", "mec-mrbc-sys", - "mrubyedge 1.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.11", ] [[package]] @@ -708,7 +708,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0104bd1eb5872c98245b0c59fc9488c4b3755453f2e5d1ddbc9f2479e3f21e7a" dependencies = [ "libc", - "mrubyedge 1.1.11 (registry+https://github.com/rust-lang/crates.io-index)", + "mrubyedge 1.1.11", ] [[package]] diff --git a/mrubyedge/Cargo.toml b/mrubyedge/Cargo.toml index 0b913f2..44e57c4 100644 --- a/mrubyedge/Cargo.toml +++ b/mrubyedge/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mrubyedge" -version = "1.1.11" +version = "1.1.12" edition = "2024" authors = ["Uchio Kondo "] description = "mruby/edge is yet another mruby that is specialized for running on WASM" From 258460b5c474d6a4d5b68489ede8e5a7f9710fe8 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sat, 4 Apr 2026 00:21:07 +0900 Subject: [PATCH 311/314] Fix nested module and testcases --- mrubyedge/src/yamrb/vm.rs | 16 ++++--- mrubyedge/tests/module.rs | 97 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 6 deletions(-) diff --git a/mrubyedge/src/yamrb/vm.rs b/mrubyedge/src/yamrb/vm.rs index b3ab650..88a0fe6 100644 --- a/mrubyedge/src/yamrb/vm.rs +++ b/mrubyedge/src/yamrb/vm.rs @@ -658,15 +658,19 @@ impl VM { return m.clone(); } let module = Rc::new(RModule::new(name)); - if let Some(parent) = parent_module { - module.parent.replace(Some(parent)); + if let Some(ref parent) = parent_module { + module.parent.replace(Some(parent.clone())); } let object = RObject::module(module.clone()).to_refcount_assigned(); self.consts.insert(name.to_string(), object.clone()); - self.object_class - .consts - .borrow_mut() - .insert(name.to_string(), object); + if let Some(parent) = parent_module { + parent.consts.borrow_mut().insert(name.to_string(), object); + } else { + self.object_class + .consts + .borrow_mut() + .insert(name.to_string(), object); + } module } diff --git a/mrubyedge/tests/module.rs b/mrubyedge/tests/module.rs index a8057d5..66b24c3 100644 --- a/mrubyedge/tests/module.rs +++ b/mrubyedge/tests/module.rs @@ -150,3 +150,100 @@ Wrapper.new.core_value .expect("core_value should return integer"); assert_eq!(value, 124); } + +#[test] +fn nested_define_module_preserves_existing_methods() { + use mrubyedge::yamrb::helpers::mrb_define_module_cmethod; + use mrubyedge::yamrb::value::RObject; + use std::rc::Rc; + + let mut vm = mrubyedge::yamrb::vm::VM::empty(); + + // First: define Outer module + let outer = vm.define_module("Outer", None); + + // Define a cmethod on Outer + mrb_define_module_cmethod( + &mut vm, + outer.clone(), + "foo", + Box::new(|_vm, _args| Ok(RObject::integer(42).to_refcount_assigned())), + ); + + // Define Inner nested under Outer + let inner = vm.define_module("Inner", Some(outer.clone())); + + mrb_define_module_cmethod( + &mut vm, + inner.clone(), + "bar", + Box::new(|_vm, _args| Ok(RObject::integer(99).to_refcount_assigned())), + ); + + // Re-open Outer via define_module — should return the same module + let outer2 = vm.define_module("Outer", None); + assert!( + Rc::ptr_eq(&outer, &outer2), + "define_module should return the existing module" + ); + + // foo should still be defined on re-opened Outer + assert!( + outer2.procs.borrow().contains_key("foo"), + "existing cmethod 'foo' should be preserved after re-opening" + ); + + // Re-open Inner nested under Outer — should return the same module + let inner2 = vm.define_module("Inner", Some(outer.clone())); + assert!( + Rc::ptr_eq(&inner, &inner2), + "nested define_module should return the existing module" + ); + + // bar should still be defined on re-opened Inner + assert!( + inner2.procs.borrow().contains_key("bar"), + "existing cmethod 'bar' should be preserved after re-opening nested module" + ); +} + +#[test] +fn include_nested_module_and_call_method() { + use mrubyedge::yamrb::helpers::mrb_define_module_cmethod; + use mrubyedge::yamrb::value::RObject; + + let mut vm = mrubyedge::yamrb::vm::VM::empty(); + let outer = vm.define_module("Outer", None); + mrb_define_module_cmethod( + &mut vm, + outer.clone(), + "foo", + Box::new(|_vm, _args| Ok(RObject::integer(42).to_refcount_assigned())), + ); + let inner = vm.define_module("Inner", Some(outer.clone())); + mrb_define_module_cmethod( + &mut vm, + inner.clone(), + "greet", + Box::new(|_vm, _args| { + Ok(RObject::string("hello from Inner".to_string()).to_refcount_assigned()) + }), + ); + + let script = r#" +class MyClass + include Outer::Inner +end + +MyClass.new.greet +"#; + let binary = mrbc_compile("include_nested_module", script); + let mut rite = mrubyedge::rite::load(&binary).unwrap(); + let result = vm.eval_rite(&mut rite).unwrap(); + + let value: String = result + .as_ref() + .try_into() + .expect("greet should return string"); + assert_eq!(value, "hello from Inner"); +} From 45750dd393b041b2bd191de4a0ea53d25c33fcab Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sat, 4 Apr 2026 00:31:55 +0900 Subject: [PATCH 312/314] Add opcode table,,, --- docs/table.html | 294 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100644 docs/table.html diff --git a/docs/table.html b/docs/table.html new file mode 100644 index 0000000..2c5bc80 --- /dev/null +++ b/docs/table.html @@ -0,0 +1,294 @@ + + + + + + + mruby Bytecode Table (with Types) + + + + + +

mruby Bytecode Table

+
+
+ + + +
+ +
+ +
+ + + + + + \ No newline at end of file From dab14dd23bbc90265d0e23df91234fd90c06189a Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Sat, 4 Apr 2026 00:33:30 +0900 Subject: [PATCH 313/314] Enable doc copier --- .github/workflows/docs.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 05905ee..68a3cf7 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -3,7 +3,7 @@ name: Deploy mrubyedge docs on: push: branches: [ "master" ] - paths: [ "mrubyedge/**", ".github/workflows/docs.yml" ] + paths: [ "mrubyedge/**", ".github/workflows/docs.yml", "docs/**" ] pull_request: branches: [ "master" ] paths: [ "mrubyedge/**" ] @@ -18,6 +18,8 @@ jobs: - uses: actions/checkout@v4 - name: Run doc on mrubyedge run: cargo doc --no-deps -p mrubyedge + - name: Copy root/docs + run: cp -r docs/* target/doc/ - name: Add redirect index.html run: | cat > target/doc/index.html <<'EOF' From c7dd9ae42be86620b90fff23277dd9251ed27fc6 Mon Sep 17 00:00:00 2001 From: Uchio Kondo Date: Tue, 14 Apr 2026 21:12:52 +0900 Subject: [PATCH 314/314] Update table --- docs/table.html | 275 +++++++++++++++++++++++++++++------------------- 1 file changed, 165 insertions(+), 110 deletions(-) diff --git a/docs/table.html b/docs/table.html index 2c5bc80..13e21bd 100644 --- a/docs/table.html +++ b/docs/table.html @@ -47,6 +47,10 @@ transition: background-color 0.2s; } + td.opcode.implemented { + background-color: #c8e6c9; + } + td.opcode:hover { background-color: #e3f2fd; color: #1565c0; @@ -57,6 +61,39 @@ background-color: #fafafa; } + .legend { + margin-top: 10px; + margin-bottom: 10px; + display: flex; + gap: 20px; + font-size: 0.9rem; + } + + .legend-item { + display: flex; + align-items: center; + gap: 6px; + } + + .legend-color { + width: 18px; + height: 18px; + border: 1px solid #ddd; + } + + .legend-color.impl { + background-color: #c8e6c9; + } + + .legend-color.not-impl { + background-color: #fff; + } + + #stats { + font-weight: bold; + color: #0d47a1; + } + /* ポップアップ(ツールチップ)のスタイル */ #tooltip { position: absolute; @@ -109,6 +146,17 @@

mruby Bytecode Table

+
+
+
+ Implemented in mruby/edge +
+
+
+ Not yet implemented +
+
+
@@ -123,112 +171,112 @@

mruby Bytecode Table