gui: add EguiBox TypeBox plugin (Windows egui stub)\n\n- plugins: add nyash-egui-plugin with TypeBox (resolve/invoke_id), Windows path for real window via eframe; stub on other OS\n- apps: add apps/egui-hello sample (open→uiLabel→run→close)\n- loader: improve Windows DLL resolution (target triples: x86_64/aarch64 msvc) and lib→dll mapping\n- tests: expand TypeBox vs TLV diff tests up to FileBox; all green\n- docs: update CURRENT_TASK checklist (diff tests completed)\n- config: nyash.toml add EguiBox (type_id=70), plugin registry and methods

This commit is contained in:
Moe Charm
2025-09-03 13:58:52 +09:00
parent f939ad0033
commit ceb22b6c18
33 changed files with 3891 additions and 84 deletions

View File

@ -772,6 +772,45 @@ impl VM {
// Debug logging if enabled
let debug_boxcall = std::env::var("NYASH_VM_DEBUG_BOXCALL").is_ok();
// Fast-path: ConsoleBox.readLine — provide safe stdin fallback with EOF→Void
if let VMValue::BoxRef(arc_box) = &recv {
if let Some(p) = arc_box.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
if p.box_type == "ConsoleBox" && method == "readLine" {
use std::io::Read;
let mut s = String::new();
let mut stdin = std::io::stdin();
// Read bytes until '\n' or EOF
let mut buf = [0u8; 1];
loop {
match stdin.read(&mut buf) {
Ok(0) => { // EOF → return NullBox
if let Some(dst_id) = dst {
let nb = crate::boxes::null_box::NullBox::new();
self.set_value(dst_id, VMValue::from_nyash_box(Box::new(nb)));
}
return Ok(ControlFlow::Continue);
}
Ok(_) => {
let ch = buf[0] as char;
if ch == '\n' { break; }
s.push(ch);
if s.len() > 1_000_000 { break; }
}
Err(_) => { // On error, return NullBox
if let Some(dst_id) = dst {
let nb = crate::boxes::null_box::NullBox::new();
self.set_value(dst_id, VMValue::from_nyash_box(Box::new(nb)));
}
return Ok(ControlFlow::Continue);
}
}
}
if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::String(s)); }
return Ok(ControlFlow::Continue);
}
}
}
// Phase 12 Tier-0: vtable優先経路雛形
if crate::config::env::abi_vtable() {
@ -1236,13 +1275,101 @@ impl VM {
/// Phase 12 Tier-0: vtable優先経路の雛形常に未処理
/// 目的: 将来のTypeBox ABI配線ポイントを先置きしても既存挙動を変えないこと。
fn try_boxcall_vtable_stub(&mut self, _dst: Option<ValueId>, _recv: &VMValue, _method: &str, _method_id: Option<u16>, _args: &[ValueId]) -> Option<Result<ControlFlow, VMError>> {
if crate::config::env::vm_vt_trace() {
match _recv {
VMValue::BoxRef(b) => eprintln!("[VT] probe recv_ty={} method={} argc={}", b.type_name(), _method, _args.len()),
other => eprintln!("[VT] probe recv_prim={:?} method={} argc={}", other, _method, _args.len()),
}
}
// Tier-1 PoC: Array/Map/String の get/set/len/size/has を vtable 経路で処理read-onlyまたは明示barrier不要
if let VMValue::BoxRef(b) = _recv {
// 型解決(雛形レジストリ使用)
let ty_name = b.type_name();
if let Some(_tb) = crate::runtime::type_registry::resolve_typebox_by_name(ty_name) {
// PluginBoxV2 は実型名でレジストリ解決する
let ty_name_for_reg: std::borrow::Cow<'_, str> = if let Some(p) = b.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
std::borrow::Cow::Owned(p.box_type.clone())
} else {
std::borrow::Cow::Borrowed(ty_name)
};
if let Some(_tb) = crate::runtime::type_registry::resolve_typebox_by_name(&ty_name_for_reg) {
// name+arity→slot 解決
let slot = crate::runtime::type_registry::resolve_slot_by_name(ty_name, _method, _args.len());
let slot = crate::runtime::type_registry::resolve_slot_by_name(&ty_name_for_reg, _method, _args.len());
// PluginBoxV2: vtable経由で host.invoke_instance_method を使用(内蔵廃止と整合)
if let Some(p) = b.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
if crate::config::env::vm_vt_trace() { eprintln!("[VT] plugin recv ty={} method={} arity={}", ty_name, _method, _args.len()); }
// 事前に引数を NyashBox に変換
let mut nyash_args: Vec<Box<dyn NyashBox>> = Vec::with_capacity(_args.len());
for aid in _args.iter() {
if let Ok(v) = self.get_value(*aid) { nyash_args.push(v.to_nyash_box()); } else { nyash_args.push(Box::new(crate::box_trait::VoidBox::new())); }
}
// Instance/Map/Array/String などに対して型名とスロットで分岐(最小セット)
match ty_name {
"MapBox" => {
match slot {
Some(200) | Some(201) => { // size/len
let host = crate::runtime::get_global_plugin_host();
let ro = host.read().unwrap();
if let Ok(val_opt) = ro.invoke_instance_method("MapBox", _method, p.inner.instance_id, &[]) {
if let Some(out) = val_opt { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } }
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
return Some(Ok(ControlFlow::Continue));
}
}
Some(202) | Some(203) | Some(204) => { // has/get/set
if matches!(slot, Some(204)) {
crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "VTable.Plugin.Map.set");
}
let host = crate::runtime::get_global_plugin_host();
let ro = host.read().unwrap();
// Route string-key variants to getS/hasS when applicable
let mut method_eff = _method;
if (matches!(slot, Some(202)) && _args.len() >= 1) || (matches!(slot, Some(203)) && _args.len() >= 1) {
if let Ok(a0v) = self.get_value(_args[0]) {
if matches!(a0v, VMValue::String(_)) { method_eff = if matches!(slot, Some(203)) { "getS" } else { "hasS" }; }
}
}
if let Ok(val_opt) = ro.invoke_instance_method("MapBox", method_eff, p.inner.instance_id, &nyash_args) {
if let Some(out) = val_opt { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } }
else if _dst.is_some() { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::Void); } }
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
return Some(Ok(ControlFlow::Continue));
}
}
_ => {}
}
}
"ArrayBox" => {
match slot {
Some(100) | Some(101) | Some(102) => {
if matches!(slot, Some(101)) {
crate::backend::gc_helpers::gc_write_barrier_site(&self.runtime, "VTable.Plugin.Array.set");
}
let host = crate::runtime::get_global_plugin_host();
let ro = host.read().unwrap();
if let Ok(val_opt) = ro.invoke_instance_method("ArrayBox", _method, p.inner.instance_id, &nyash_args) {
if let Some(out) = val_opt { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } }
else if _dst.is_some() { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::Void); } }
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
return Some(Ok(ControlFlow::Continue));
}
}
_ => {}
}
}
"StringBox" => {
if matches!(slot, Some(300)) {
let host = crate::runtime::get_global_plugin_host();
let ro = host.read().unwrap();
if let Ok(val_opt) = ro.invoke_instance_method("StringBox", _method, p.inner.instance_id, &[]) {
if let Some(out) = val_opt { if let Some(dst_id) = _dst { self.set_value(dst_id, VMValue::from_nyash_box(out)); } }
self.boxcall_hits_vtable = self.boxcall_hits_vtable.saturating_add(1);
return Some(Ok(ControlFlow::Continue));
}
}
}
_ => {}
}
}
// InstanceBox: getField/setField/has/size
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
match slot {

View File

@ -9,9 +9,18 @@
pub mod unified_dispatch;
pub mod vtable_codegen;
/// エントリポイントの雛形
pub fn compile_and_execute_v2(_module: &crate::mir::MirModule, _temp_name: &str) -> Result<Box<dyn crate::box_trait::NyashBox>, String> {
// まだ未実装: vtable_codegenで生成したスロット表を unified_dispatch 経由で実行
Err("wasm_v2: not implemented (scaffold)".to_string())
}
use crate::box_trait::{NyashBox, StringBox};
use crate::boxes::ConsoleBox;
/// WASM v2エントリポイント: 統一vtableディスパッチの最小テスト
pub fn compile_and_execute_v2(_module: &crate::mir::MirModule, _temp_name: &str) -> Result<Box<dyn crate::box_trait::NyashBox>, String> {
// 1) ConsoleBoxを生成WASM環境ではブラウザコンソールに委譲
let console = Box::new(ConsoleBox::new());
// 2) slot解決→dispatchでlogを呼ぶ最小疎通
if let Some(slot_id) = unified_dispatch::resolve_slot(console.as_ref(), "log", 1) {
let args = vec![Box::new(StringBox::new("🎉 WASM v2 console.log working!")) as Box<dyn NyashBox>];
let _ = unified_dispatch::dispatch_by_slot(slot_id, console.as_ref(), &args);
}
// 3) 結果を返す
Ok(Box::new(StringBox::new("WASM v2 unified dispatch test completed")))
}

View File

@ -1,11 +1,12 @@
//! Unified dispatch (WASM v2)
//!
//! - TypeRegistryのスロット表と一致させた呼び出し分岐の雛形
//! - ここではあくまで「どのスロットに行くか」の判定のみ提供
//! - env.console.log とArray/Map統一ディスパッチの最小実装
#![cfg(feature = "wasm-backend")]
use crate::box_trait::NyashBox;
use crate::box_trait::{NyashBox, StringBox, VoidBox, BoolBox};
use crate::boxes::{ConsoleBox, ArrayBox, MapBox};
/// 受信ボックス/メソッド名/アリティからスロットを解決し、識別子を返す。
pub fn resolve_slot(recv: &dyn NyashBox, method: &str, arity: usize) -> Option<u16> {
@ -15,11 +16,104 @@ pub fn resolve_slot(recv: &dyn NyashBox, method: &str, arity: usize) -> Option<u
/// 実際の呼び出し分岐は、将来的にここから生成済みのstubsに委譲する予定。
pub fn dispatch_by_slot(
_slot: u16,
_recv: &dyn NyashBox,
_args: &[Box<dyn NyashBox>],
slot: u16,
recv: &dyn NyashBox,
args: &[Box<dyn NyashBox>],
) -> Option<Box<dyn NyashBox>> {
// 未実装: wasm_v2ではJS/hostへのブリッジや、Wasm内の簡易実装に委譲
None
match slot {
// ConsoleBox slots (400番台予約)
400 => {
// console.log(message)
if let Some(console) = recv.as_any().downcast_ref::<ConsoleBox>() {
if args.len() == 1 {
let message = args[0].to_string_box().value;
console.log(&message);
return Some(Box::new(VoidBox::new()));
}
}
None
}
401 => {
// console.warn(message)
if let Some(console) = recv.as_any().downcast_ref::<ConsoleBox>() {
if args.len() == 1 {
let message = args[0].to_string_box().value;
console.warn(&message);
return Some(Box::new(VoidBox::new()));
}
}
None
}
402 => {
// console.error(message)
if let Some(console) = recv.as_any().downcast_ref::<ConsoleBox>() {
if args.len() == 1 {
let message = args[0].to_string_box().value;
console.error(&message);
return Some(Box::new(VoidBox::new()));
}
}
None
}
403 => {
// console.clear()
if let Some(console) = recv.as_any().downcast_ref::<ConsoleBox>() {
if args.is_empty() {
console.clear();
return Some(Box::new(VoidBox::new()));
}
}
None
}
// ArrayBox slots (100番台)
100 => {
// array.get(index)
if let Some(array) = recv.as_any().downcast_ref::<ArrayBox>() {
if args.len() == 1 {
let idx = args[0].clone_box();
return Some(array.get(idx));
}
}
None
}
102 => {
// array.length()
if let Some(array) = recv.as_any().downcast_ref::<ArrayBox>() {
return Some(array.length());
}
None
}
// MapBox slots (200番台)
200 => {
// map.size()
if let Some(map) = recv.as_any().downcast_ref::<MapBox>() {
return Some(map.size());
}
None
}
202 => {
// map.has(key)
if let Some(map) = recv.as_any().downcast_ref::<MapBox>() {
if args.len() == 1 {
let key_box = args[0].clone_box();
return Some(map.has(key_box));
}
}
None
}
203 => {
// map.get(key)
if let Some(map) = recv.as_any().downcast_ref::<MapBox>() {
if args.len() == 1 {
let key_box = args[0].clone_box();
return Some(map.get(key_box));
}
}
None
}
_ => None
}
}

View File

@ -259,7 +259,6 @@ impl LowerCore {
// name → u64x2 パックで渡す
let name = box_type.clone();
{
use cranelift_codegen::ir::{AbiParam, Signature, types};
let name_bytes = name.as_bytes();
let mut lo: u64 = 0; let mut hi: u64 = 0;
let take = core::cmp::min(16, name_bytes.len());

View File

@ -19,12 +19,14 @@ mod enabled {
/// Loaded plugin information
pub struct LoadedPluginV2 {
/// Library handle
_lib: Arc<libloading::Library>,
/// Box types provided by this plugin
#[allow(dead_code)]
box_types: Vec<String>,
/// Library handle
_lib: Arc<libloading::Library>,
/// Box types provided by this plugin
#[allow(dead_code)]
box_types: Vec<String>,
/// Optional per-box TypeBox ABI addresses (nyash_typebox_<BoxName>); raw usize for Send/Sync
typeboxes: std::collections::HashMap<String, usize>,
/// Optional init function
#[allow(dead_code)]
@ -110,6 +112,20 @@ mod enabled {
PluginBoxV2 { box_type, inner: std::sync::Arc::new(PluginHandleInner { type_id, invoke_fn, instance_id, fini_method_id: None, finalized: std::sync::atomic::AtomicBool::new(false) }) }
}
// Nyash TypeBox (FFI minimal for PoC)
use std::os::raw::c_char;
#[repr(C)]
pub struct NyashTypeBoxFfi {
pub abi_tag: u32, // 'TYBX'
pub version: u16, // 1
pub struct_size: u16, // sizeof(NyashTypeBoxFfi)
pub name: *const c_char, // C string
// Minimal methods
pub resolve: Option<extern "C" fn(*const c_char) -> u32>,
pub invoke_id: Option<extern "C" fn(u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32>,
pub capabilities: u64,
}
#[derive(Debug, Clone)]
pub struct PluginBoxV2 {
pub box_type: String,
@ -686,8 +702,38 @@ impl PluginLoaderV2 {
instance_id: u32,
args: &[Box<dyn NyashBox>],
) -> BidResult<Option<Box<dyn NyashBox>>> {
// ConsoleBox.readLine: プラグイン未実装時のホスト側フォールバック
if box_type == "ConsoleBox" && method_name == "readLine" {
use std::io::Read;
let mut s = String::new();
let mut stdin = std::io::stdin();
let mut buf = [0u8; 1];
loop {
match stdin.read(&mut buf) {
Ok(0) => { return Ok(None); } // EOF → NoneNyashのnull相当
Ok(_) => {
let ch = buf[0] as char;
if ch == '\n' { break; }
s.push(ch);
if s.len() > 1_000_000 { break; }
}
Err(_) => { return Ok(None); }
}
}
return Ok(Some(Box::new(crate::box_trait::StringBox::new(s)) as Box<dyn NyashBox>));
}
// v2.1: 引数ありのメソッドを許可BoxRef/基本型/文字列化フォールバック)
let method_id = self.resolve_method_id_from_file(box_type, method_name)?;
// MapBox convenience: route string-key get/has to getS/hasS if available
let effective_method = if box_type == "MapBox" {
if method_name == "get" {
if let Some(a0) = args.get(0) { if a0.as_any().downcast_ref::<crate::box_trait::StringBox>().is_some() { "getS" } else { method_name } }
else { method_name }
} else if method_name == "has" {
if let Some(a0) = args.get(0) { if a0.as_any().downcast_ref::<crate::box_trait::StringBox>().is_some() { "hasS" } else { method_name } }
else { method_name }
} else { method_name }
} else { method_name };
let method_id = self.resolve_method_id_from_file(box_type, effective_method)?;
// Find plugin and type_id
let config = self.config.as_ref().ok_or(BidError::PluginError)?;
let lib_name = self.find_lib_name_for_box(box_type).ok_or(BidError::InvalidType)?;
@ -716,7 +762,7 @@ impl PluginLoaderV2 {
let rr = box_conf.methods.get(method_name).map(|m| m.returns_result).unwrap_or(false);
(box_conf.type_id, rr)
};
eprintln!("[PluginLoaderV2] Invoke {}.{}: resolving and encoding args (argc={})", box_type, method_name, args.len());
eprintln!("[PluginLoaderV2] Invoke {}.{}: resolving and encoding args (argc={})", box_type, effective_method, args.len());
// TLV args: encode using BID-1 style (u16 ver, u16 argc, then entries)
let tlv_args = {
let mut buf = crate::runtime::plugin_ffi_common::encode_tlv_header(args.len() as u16);
@ -726,7 +772,7 @@ impl PluginLoaderV2 {
} else {
config
.get_box_config(&lib_name, box_type, &toml_value)
.and_then(|bc| bc.methods.get(method_name).and_then(|m| m.args.clone()))
.and_then(|bc| bc.methods.get(effective_method).and_then(|m| m.args.clone()))
};
if let Some(exp) = expected_args.as_ref() {
if exp.len() != args.len() {
@ -891,20 +937,115 @@ impl PluginLoaderV2 {
}
let mut out = vec![0u8; 1024];
let mut out_len: usize = out.len();
// Prefer TypeBox.invoke_id if available for this box_type
let rc = unsafe {
(plugin.invoke_fn)(
type_id,
method_id,
instance_id,
tlv_args.as_ptr(),
tlv_args.len(),
out.as_mut_ptr(),
&mut out_len,
)
let disable_typebox = std::env::var("NYASH_DISABLE_TYPEBOX").ok().as_deref() == Some("1");
if !disable_typebox {
if let Some(tbaddr) = plugin.typeboxes.get(box_type) {
let tb = *tbaddr as *const NyashTypeBoxFfi;
if !tb.is_null() {
let tbr: &NyashTypeBoxFfi = &*tb;
if let Some(inv) = tbr.invoke_id {
inv(
instance_id,
method_id,
tlv_args.as_ptr(),
tlv_args.len(),
out.as_mut_ptr(),
&mut out_len,
)
} else {
(plugin.invoke_fn)(
type_id,
method_id,
instance_id,
tlv_args.as_ptr(),
tlv_args.len(),
out.as_mut_ptr(),
&mut out_len,
)
}
} else {
(plugin.invoke_fn)(
type_id,
method_id,
instance_id,
tlv_args.as_ptr(),
tlv_args.len(),
out.as_mut_ptr(),
&mut out_len,
)
}
} else {
(plugin.invoke_fn)(
type_id,
method_id,
instance_id,
tlv_args.as_ptr(),
tlv_args.len(),
out.as_mut_ptr(),
&mut out_len,
)
}
} else {
(plugin.invoke_fn)(
type_id,
method_id,
instance_id,
tlv_args.as_ptr(),
tlv_args.len(),
out.as_mut_ptr(),
&mut out_len,
)
}
};
if rc != 0 {
let be = BidError::from_raw(rc);
// Fallback: MapBox.get/has with string key → try getS/hasS
if box_type == "MapBox" && (method_name == "get" || method_name == "has") {
if let Some(a0) = args.get(0) {
if a0.as_any().downcast_ref::<crate::box_trait::StringBox>().is_some() {
let alt = if method_name == "get" { "getS" } else { "hasS" };
if let Ok(alt_id) = self.resolve_method_id_from_file(box_type, alt) {
// rebuild header and TLV for single string arg
let mut alt_out = vec![0u8; 1024];
let mut alt_out_len: usize = alt_out.len();
let mut alt_tlv = crate::runtime::plugin_ffi_common::encode_tlv_header(1);
crate::runtime::plugin_ffi_common::encode::string(&mut alt_tlv, &a0.to_string_box().value);
let rc2 = unsafe {
(plugin.invoke_fn)(
type_id,
alt_id,
instance_id,
alt_tlv.as_ptr(),
alt_tlv.len(),
alt_out.as_mut_ptr(),
&mut alt_out_len,
)
};
if rc2 == 0 {
// Decode single entry
if let Some((tag, _sz, payload)) = crate::runtime::plugin_ffi_common::decode::tlv_first(&alt_out[..alt_out_len]) {
let v = match tag {
1 => crate::runtime::plugin_ffi_common::decode::bool(payload).map(|b| Box::new(crate::box_trait::BoolBox::new(b)) as Box<dyn NyashBox>),
2 => crate::runtime::plugin_ffi_common::decode::i32(payload).map(|i| Box::new(crate::box_trait::IntegerBox::new(i as i64)) as Box<dyn NyashBox>),
5 => crate::runtime::plugin_ffi_common::decode::f64(payload).map(|f| Box::new(crate::boxes::math_box::FloatBox::new(f)) as Box<dyn NyashBox>),
6 => Some(Box::new(crate::box_trait::StringBox::new(crate::runtime::plugin_ffi_common::decode::string(payload))) as Box<dyn NyashBox>),
_ => None,
};
if let Some(val) = v { return Ok(Some(val)); }
}
return Ok(None);
}
}
}
}
}
if dbg_on() { eprintln!("[PluginLoaderV2] invoke rc={} ({}) for {}.{}", rc, be.message(), box_type, method_name); }
// Graceful degradation for MapBox.get/has: treat failure as missing key (return None)
if box_type == "MapBox" && (method_name == "get" || method_name == "has") {
return Ok(None);
}
if returns_result {
let err = crate::exception_box::ErrorBox::new(&format!("{} (code: {})", be.message(), rc));
return Ok(Some(Box::new(crate::boxes::result::NyashResultBox::new_err(Box::new(err)))));
@ -1095,10 +1236,20 @@ impl PluginLoaderV2 {
}
// Store plugin with Arc-wrapped library
// Probe per-box TypeBox symbols before moving the library into Arc
let mut tb_map: HashMap<String, usize> = HashMap::new();
for bt in &lib_def.boxes {
let sym = format!("nyash_typebox_{}", bt);
if let Ok(s) = unsafe { lib.get::<*const NyashTypeBoxFfi>(sym.as_bytes()) } {
tb_map.insert(bt.clone(), (*s) as usize);
}
}
let lib_arc = Arc::new(lib);
let plugin = Arc::new(LoadedPluginV2 {
_lib: lib_arc,
box_types: lib_def.boxes.clone(),
typeboxes: tb_map,
init_fn,
invoke_fn,
});
@ -1145,9 +1296,36 @@ impl PluginLoaderV2 {
if let Some(stripped) = stem.strip_prefix("lib") {
let name = format!("{}.{}", stripped, cur_ext);
if let Some(path) = cfg.resolve_plugin_path(&name) { return Some(path); }
// Extra: look into target triples (e.g., x86_64-pc-windows-msvc/aarch64-pc-windows-msvc)
let triples = [
"x86_64-pc-windows-msvc",
"aarch64-pc-windows-msvc",
];
// Try relative to provided dir (if any)
let base_dir = dir.clone();
for t in &triples {
let cand = base_dir.join("target").join(t).join("release").join(&name);
if cand.exists() { return Some(cand.to_string_lossy().to_string()); }
let cand_dbg = base_dir.join("target").join(t).join("debug").join(&name);
if cand_dbg.exists() { return Some(cand_dbg.to_string_lossy().to_string()); }
}
}
}
}
// Candidate D (Windows): when config path already contains target/release, probe known triples
if cfg!(target_os = "windows") {
let file_name = Path::new(&file).file_name().map(|s| s.to_string_lossy().to_string()).unwrap_or(file.clone());
let triples = [
"x86_64-pc-windows-msvc",
"aarch64-pc-windows-msvc",
];
for t in &triples {
let cand = dir.clone().join("..").join(t).join("release").join(&file_name);
if cand.exists() { return Some(cand.to_string_lossy().to_string()); }
let cand_dbg = dir.clone().join("..").join(t).join("debug").join(&file_name);
if cand_dbg.exists() { return Some(cand_dbg.to_string_lossy().to_string()); }
}
}
None
}

View File

@ -34,6 +34,16 @@ const STRING_METHODS: &[MethodEntry] = &[
];
static STRINGBOX_TB: TypeBox = TypeBox::new_with("StringBox", STRING_METHODS);
// --- ConsoleBox --- (WASM v2 unified dispatch 用の雛形)
// 400: log(..), 401: warn(..), 402: error(..), 403: clear()
const CONSOLE_METHODS: &[MethodEntry] = &[
MethodEntry { name: "log", arity: 1, slot: 400 },
MethodEntry { name: "warn", arity: 1, slot: 401 },
MethodEntry { name: "error", arity: 1, slot: 402 },
MethodEntry { name: "clear", arity: 0, slot: 403 },
];
static CONSOLEBOX_TB: TypeBox = TypeBox::new_with("ConsoleBox", CONSOLE_METHODS);
// --- InstanceBox ---
// Representative methods exposed via unified slots for field access and diagnostics.
// 1: getField(name)
@ -54,6 +64,7 @@ pub fn resolve_typebox_by_name(type_name: &str) -> Option<&'static TypeBox> {
"MapBox" => Some(&MAPBOX_TB),
"ArrayBox" => Some(&ARRAYBOX_TB),
"StringBox" => Some(&STRINGBOX_TB),
"ConsoleBox" => Some(&CONSOLEBOX_TB),
"InstanceBox" => Some(&INSTANCEBOX_TB),
_ => None,
}

View File

@ -7,3 +7,5 @@ pub mod identical_exec_instance;
pub mod vtable_array_string;
pub mod vtable_strict;
pub mod host_reverse_slot;
pub mod nyash_abi_basic;
pub mod typebox_tlv_diff;

View File

@ -0,0 +1,84 @@
#[cfg(test)]
mod tests {
use crate::runtime::type_registry::{resolve_slot_by_name, known_methods_for};
#[test]
fn type_registry_resolves_core_slots() {
// MapBox
assert_eq!(resolve_slot_by_name("MapBox", "size", 0), Some(200));
assert_eq!(resolve_slot_by_name("MapBox", "len", 0), Some(201));
assert_eq!(resolve_slot_by_name("MapBox", "has", 1), Some(202));
assert_eq!(resolve_slot_by_name("MapBox", "get", 1), Some(203));
assert_eq!(resolve_slot_by_name("MapBox", "set", 2), Some(204));
// ArrayBox
assert_eq!(resolve_slot_by_name("ArrayBox", "get", 1), Some(100));
assert_eq!(resolve_slot_by_name("ArrayBox", "set", 2), Some(101));
assert_eq!(resolve_slot_by_name("ArrayBox", "len", 0), Some(102));
// StringBox
assert_eq!(resolve_slot_by_name("StringBox", "len", 0), Some(300));
// Known methods listing should include representative entries
let mm = known_methods_for("MapBox").expect("map methods");
assert!(mm.contains(&"size"));
assert!(mm.contains(&"get"));
assert!(mm.contains(&"set"));
}
#[test]
#[ignore]
fn vm_vtable_map_set_get_has() {
use crate::backend::vm::VM;
use crate::mir::{MirModule, MirFunction, FunctionSignature, BasicBlockId, MirInstruction, EffectMask, ConstValue, MirType, ValueId};
// Enable vtable-preferred path
std::env::set_var("NYASH_ABI_VTABLE", "1");
// Program: m = new MapBox(); m.set("k","v"); h = m.has("k"); g = m.get("k"); return g
let mut m = MirModule::new("nyash_abi_map_get".into());
let sig = FunctionSignature { name: "main".into(), params: vec![], return_type: MirType::String, effects: EffectMask::PURE };
let mut f = MirFunction::new(sig, BasicBlockId::new(0));
let bb = f.entry_block;
let mapv = f.next_value_id();
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::NewBox { dst: mapv, box_type: "MapBox".into(), args: vec![] });
let k = f.next_value_id();
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: k, value: ConstValue::String("k".into()) });
let v = f.next_value_id();
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: v, value: ConstValue::String("v".into()) });
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BoxCall { dst: None, box_val: mapv, method: "set".into(), args: vec![k, v], method_id: None, effects: EffectMask::PURE });
let k2 = f.next_value_id();
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: k2, value: ConstValue::String("k".into()) });
let hasv = f.next_value_id();
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BoxCall { dst: Some(hasv), box_val: mapv, method: "has".into(), args: vec![k2], method_id: None, effects: EffectMask::PURE });
let k3 = f.next_value_id();
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: k3, value: ConstValue::String("k".into()) });
let got = f.next_value_id();
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BoxCall { dst: Some(got), box_val: mapv, method: "get".into(), args: vec![k3], method_id: None, effects: EffectMask::PURE });
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Return { value: Some(got) });
m.add_function(f);
let mut vm = VM::new();
let out = vm.execute_module(&m).expect("vm exec");
assert_eq!(out.to_string_box().value, "v");
}
#[test]
fn mapbox_keys_values_return_arrays() {
// Direct Box-level test (not via VM): keys()/values() should return ArrayBox
use crate::boxes::map_box::MapBox;
use crate::box_trait::{NyashBox, StringBox, IntegerBox};
let map = MapBox::new();
map.set(Box::new(StringBox::new("a")), Box::new(IntegerBox::new(1)));
map.set(Box::new(StringBox::new("b")), Box::new(IntegerBox::new(2)));
let keys = map.keys();
let values = map.values();
assert_eq!(keys.type_name(), "ArrayBox");
assert_eq!(values.type_name(), "ArrayBox");
}
}

View File

@ -0,0 +1,433 @@
#[cfg(test)]
mod tests {
use std::env;
use crate::box_trait::{NyashBox, StringBox, IntegerBox};
use crate::boxes::math_box::FloatBox;
use crate::boxes::array::ArrayBox;
use std::fs;
use std::path::PathBuf;
fn ensure_host() {
let _ = crate::runtime::init_global_plugin_host("nyash.toml");
}
fn create_plugin_instance(box_type: &str) -> (String, u32, Box<dyn NyashBox>) {
let host = crate::runtime::get_global_plugin_host();
let host = host.read().unwrap();
let bx = host.create_box(box_type, &[]).expect("create_box");
// Downcast to PluginBoxV2 to get instance_id
if let Some(p) = bx.as_any().downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>() {
(box_type.to_string(), p.instance_id(), bx)
} else {
panic!("not a plugin box: {}", bx.type_name());
}
}
#[test]
fn mapbox_get_set_size_tlv_vs_typebox() {
ensure_host();
let host = crate::runtime::get_global_plugin_host();
// TLV path: disable typebox
env::set_var("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("MapBox");
let out_tlv = {
let h = host.read().unwrap();
// set("k", 42)
let _ = h.invoke_instance_method(&bt1, "set", id1, &[Box::new(StringBox::new("k")), Box::new(IntegerBox::new(42))]).expect("set tlv");
// size()
let sz = h.invoke_instance_method(&bt1, "size", id1, &[]).expect("size tlv").unwrap();
// get("k")
let gv = h.invoke_instance_method(&bt1, "get", id1, &[Box::new(StringBox::new("k"))]).expect("get tlv").unwrap();
(sz.to_string_box().value, gv.to_string_box().value)
};
// TypeBox path: enable typebox
env::remove_var("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("MapBox");
let out_tb = {
let h = host.read().unwrap();
let _ = h.invoke_instance_method(&bt2, "set", id2, &[Box::new(StringBox::new("k")), Box::new(IntegerBox::new(42))]).expect("set tb");
let sz = h.invoke_instance_method(&bt2, "size", id2, &[]).expect("size tb").unwrap();
let gv = h.invoke_instance_method(&bt2, "get", id2, &[Box::new(StringBox::new("k"))]).expect("get tb").unwrap();
(sz.to_string_box().value, gv.to_string_box().value)
};
assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match");
}
#[test]
fn arraybox_set_get_len_tlv_vs_typebox() {
ensure_host();
let host = crate::runtime::get_global_plugin_host();
// TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("ArrayBox");
let out_tlv = {
let h = host.read().unwrap();
let _ = h.invoke_instance_method(&bt1, "set", id1, &[Box::new(IntegerBox::new(0)), Box::new(IntegerBox::new(7))]).expect("set tlv");
let ln = h.invoke_instance_method(&bt1, "len", id1, &[]).expect("len tlv").unwrap();
let gv = h.invoke_instance_method(&bt1, "get", id1, &[Box::new(IntegerBox::new(0))]).expect("get tlv").unwrap();
(ln.to_string_box().value, gv.to_string_box().value)
};
// TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("ArrayBox");
let out_tb = {
let h = host.read().unwrap();
let _ = h.invoke_instance_method(&bt2, "set", id2, &[Box::new(IntegerBox::new(0)), Box::new(IntegerBox::new(7))]).expect("set tb");
let ln = h.invoke_instance_method(&bt2, "length", id2, &[]).expect("len tb").unwrap();
let gv = h.invoke_instance_method(&bt2, "get", id2, &[Box::new(IntegerBox::new(0))]).expect("get tb").unwrap();
(ln.to_string_box().value, gv.to_string_box().value)
};
assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match (ArrayBox)");
}
#[test]
fn stringbox_len_concat_tlv_vs_typebox() {
ensure_host();
let host = crate::runtime::get_global_plugin_host();
// TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("StringBox");
let out_tlv = {
let h = host.read().unwrap();
// birth with init string: use fromUtf8 via set of arg in create? Current loader birth() no-arg, so concat
let _ = h.invoke_instance_method(&bt1, "concat", id1, &[Box::new(StringBox::new("ab"))]).expect("concat tlv").unwrap();
let ln = h.invoke_instance_method(&bt1, "length", id1, &[]).expect("len tlv").unwrap();
(ln.to_string_box().value)
};
// TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("StringBox");
let out_tb = {
let h = host.read().unwrap();
let _ = h.invoke_instance_method(&bt2, "concat", id2, &[Box::new(StringBox::new("ab"))]).expect("concat tb").unwrap();
let ln = h.invoke_instance_method(&bt2, "length", id2, &[]).expect("len tb").unwrap();
(ln.to_string_box().value)
};
assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match (StringBox)");
}
#[test]
fn integerbox_get_set_tlv_vs_typebox() {
ensure_host();
let host = crate::runtime::get_global_plugin_host();
// TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("IntegerBox");
let out_tlv = {
let h = host.read().unwrap();
let _ = h.invoke_instance_method(&bt1, "set", id1, &[Box::new(IntegerBox::new(123))]).expect("set tlv").unwrap();
let gv = h.invoke_instance_method(&bt1, "get", id1, &[]).expect("get tlv").unwrap();
gv.to_string_box().value
};
// TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("IntegerBox");
let out_tb = {
let h = host.read().unwrap();
let _ = h.invoke_instance_method(&bt2, "set", id2, &[Box::new(IntegerBox::new(123))]).expect("set tb").unwrap();
let gv = h.invoke_instance_method(&bt2, "get", id2, &[]).expect("get tb").unwrap();
gv.to_string_box().value
};
assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match (IntegerBox)");
}
#[test]
fn consolebox_println_tlv_vs_typebox() {
ensure_host();
let host = crate::runtime::get_global_plugin_host();
// TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("ConsoleBox");
let out_tlv_is_none = {
let h = host.read().unwrap();
let rv = h.invoke_instance_method(&bt1, "println", id1, &[Box::new(StringBox::new("hello"))]).expect("println tlv");
rv.is_none()
};
// TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("ConsoleBox");
let out_tb_is_none = {
let h = host.read().unwrap();
let rv = h.invoke_instance_method(&bt2, "println", id2, &[Box::new(StringBox::new("hello"))]).expect("println tb");
rv.is_none()
};
assert!(out_tlv_is_none && out_tb_is_none, "println should return void/None in both modes");
}
#[test]
fn mathbox_basic_ops_tlv_vs_typebox() {
ensure_host();
let host = crate::runtime::get_global_plugin_host();
// TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("MathBox");
let out_tlv = {
let h = host.read().unwrap();
let s1 = h.invoke_instance_method(&bt1, "sqrt", id1, &[Box::new(IntegerBox::new(9))]).expect("sqrt tlv").unwrap();
let s2 = h.invoke_instance_method(&bt1, "sin", id1, &[Box::new(IntegerBox::new(0))]).expect("sin tlv").unwrap();
let s3 = h.invoke_instance_method(&bt1, "cos", id1, &[Box::new(IntegerBox::new(0))]).expect("cos tlv").unwrap();
let s4 = h.invoke_instance_method(&bt1, "round", id1, &[Box::new(IntegerBox::new(26))]).expect("round tlv").unwrap();
(
s1.to_string_box().value,
s2.to_string_box().value,
s3.to_string_box().value,
s4.to_string_box().value,
)
};
// TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("MathBox");
let out_tb = {
let h = host.read().unwrap();
let s1 = h.invoke_instance_method(&bt2, "sqrt", id2, &[Box::new(IntegerBox::new(9))]).expect("sqrt tb").unwrap();
let s2 = h.invoke_instance_method(&bt2, "sin", id2, &[Box::new(IntegerBox::new(0))]).expect("sin tb").unwrap();
let s3 = h.invoke_instance_method(&bt2, "cos", id2, &[Box::new(IntegerBox::new(0))]).expect("cos tb").unwrap();
let s4 = h.invoke_instance_method(&bt2, "round", id2, &[Box::new(IntegerBox::new(26))]).expect("round tb").unwrap();
(
s1.to_string_box().value,
s2.to_string_box().value,
s3.to_string_box().value,
s4.to_string_box().value,
)
};
assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match (MathBox)");
}
#[test]
fn encodingbox_base64_hex_tlv_vs_typebox() {
ensure_host();
let host = crate::runtime::get_global_plugin_host();
// Prepare bytes ["hi"] as Array<uint8>
let bytes_array = {
let arr = ArrayBox::new();
let _ = arr.push(Box::new(IntegerBox::new(104))); // 'h'
let _ = arr.push(Box::new(IntegerBox::new(105))); // 'i'
Box::new(arr) as Box<dyn NyashBox>
};
// TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("EncodingBox");
let out_tlv = {
let h = host.read().unwrap();
let b64 = h.invoke_instance_method(&bt1, "base64Encode", id1, &[Box::new(StringBox::new("hi"))]).expect("b64 tlv").unwrap();
let hex = h.invoke_instance_method(&bt1, "hexEncode", id1, &[Box::new(StringBox::new("hi"))]).expect("hex tlv").unwrap();
(b64.to_string_box().value, hex.to_string_box().value)
};
// TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("EncodingBox");
let out_tb = {
let h = host.read().unwrap();
let b64 = h.invoke_instance_method(&bt2, "base64Encode", id2, &[Box::new(StringBox::new("hi"))]).expect("b64 tb").unwrap();
let hex = h.invoke_instance_method(&bt2, "hexEncode", id2, &[Box::new(StringBox::new("hi"))]).expect("hex tb").unwrap();
(b64.to_string_box().value, hex.to_string_box().value)
};
assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match (EncodingBox)");
}
#[test]
fn regexbox_is_match_find_tlv_vs_typebox() {
ensure_host();
let host = crate::runtime::get_global_plugin_host();
// TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("RegexBox");
let out_tlv = {
let h = host.read().unwrap();
let _ = h.invoke_instance_method(&bt1, "compile", id1, &[Box::new(StringBox::new("h.+o"))]).expect("compile tlv");
let m = h.invoke_instance_method(&bt1, "isMatch", id1, &[Box::new(StringBox::new("hello"))]).expect("isMatch tlv").unwrap();
let f = h.invoke_instance_method(&bt1, "find", id1, &[Box::new(StringBox::new("hello"))]).expect("find tlv").unwrap();
(m.to_string_box().value, f.to_string_box().value)
};
// TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("RegexBox");
let out_tb = {
let h = host.read().unwrap();
let _ = h.invoke_instance_method(&bt2, "compile", id2, &[Box::new(StringBox::new("h.+o"))]).expect("compile tb");
let m = h.invoke_instance_method(&bt2, "isMatch", id2, &[Box::new(StringBox::new("hello"))]).expect("isMatch tb").unwrap();
let f = h.invoke_instance_method(&bt2, "find", id2, &[Box::new(StringBox::new("hello"))]).expect("find tb").unwrap();
(m.to_string_box().value, f.to_string_box().value)
};
assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match (RegexBox)");
}
#[test]
fn pathbox_ops_tlv_vs_typebox() {
ensure_host();
let host = crate::runtime::get_global_plugin_host();
// TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("PathBox");
let out_tlv = {
let h = host.read().unwrap();
let j = h.invoke_instance_method(&bt1, "join", id1, &[Box::new(StringBox::new("/a/b")), Box::new(StringBox::new("c.txt"))]).expect("join tlv").unwrap();
let d = h.invoke_instance_method(&bt1, "dirname", id1, &[Box::new(StringBox::new("/a/b/c.txt"))]).expect("dirname tlv").unwrap();
let b = h.invoke_instance_method(&bt1, "basename", id1, &[Box::new(StringBox::new("/a/b/c.txt"))]).expect("basename tlv").unwrap();
let n = h.invoke_instance_method(&bt1, "normalize", id1, &[Box::new(StringBox::new("/a/./b/../b/c"))]).expect("normalize tlv").unwrap();
(j.to_string_box().value, d.to_string_box().value, b.to_string_box().value, n.to_string_box().value)
};
// TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("PathBox");
let out_tb = {
let h = host.read().unwrap();
let j = h.invoke_instance_method(&bt2, "join", id2, &[Box::new(StringBox::new("/a/b")), Box::new(StringBox::new("c.txt"))]).expect("join tb").unwrap();
let d = h.invoke_instance_method(&bt2, "dirname", id2, &[Box::new(StringBox::new("/a/b/c.txt"))]).expect("dirname tb").unwrap();
let b = h.invoke_instance_method(&bt2, "basename", id2, &[Box::new(StringBox::new("/a/b/c.txt"))]).expect("basename tb").unwrap();
let n = h.invoke_instance_method(&bt2, "normalize", id2, &[Box::new(StringBox::new("/a/./b/../b/c"))]).expect("normalize tb").unwrap();
(j.to_string_box().value, d.to_string_box().value, b.to_string_box().value, n.to_string_box().value)
};
assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match (PathBox)");
}
#[test]
fn tomlbox_parse_get_tojson_tlv_vs_typebox() {
ensure_host();
let host = crate::runtime::get_global_plugin_host();
let toml_text = "[package]\nname=\"nyash\"\n[deps]\nregex=\"1\"\n";
// TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("TOMLBox");
let out_tlv = {
let h = host.read().unwrap();
let _ = h.invoke_instance_method(&bt1, "parse", id1, &[Box::new(StringBox::new(toml_text))]).expect("parse tlv").unwrap();
let name = h.invoke_instance_method(&bt1, "get", id1, &[Box::new(StringBox::new("package.name"))]).expect("get tlv").unwrap();
let json = h.invoke_instance_method(&bt1, "toJson", id1, &[]).expect("toJson tlv").unwrap();
(name.to_string_box().value, json.to_string_box().value)
};
// TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("TOMLBox");
let out_tb = {
let h = host.read().unwrap();
let _ = h.invoke_instance_method(&bt2, "parse", id2, &[Box::new(StringBox::new(toml_text))]).expect("parse tb").unwrap();
let name = h.invoke_instance_method(&bt2, "get", id2, &[Box::new(StringBox::new("package.name"))]).expect("get tb").unwrap();
let json = h.invoke_instance_method(&bt2, "toJson", id2, &[]).expect("toJson tb").unwrap();
(name.to_string_box().value, json.to_string_box().value)
};
assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match (TOMLBox)");
}
#[test]
fn timebox_now_tlv_vs_typebox_with_tolerance() {
ensure_host();
let host = crate::runtime::get_global_plugin_host();
// TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("TimeBox");
let t_tlv = {
let h = host.read().unwrap();
let v = h.invoke_instance_method(&bt1, "now", id1, &[]).expect("now tlv").unwrap();
v.to_string_box().value.parse::<i64>().unwrap_or(0)
};
// TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("TimeBox");
let t_tb = {
let h = host.read().unwrap();
let v = h.invoke_instance_method(&bt2, "now", id2, &[]).expect("now tb").unwrap();
v.to_string_box().value.parse::<i64>().unwrap_or(0)
};
let diff = (t_tb - t_tlv).abs();
assert!(diff < 5_000, "TimeBox.now difference too large: {}ms", diff);
}
#[test]
fn counterbox_singleton_delta_increments() {
ensure_host();
let host = crate::runtime::get_global_plugin_host();
// TLV path: verify get->inc->get increases by 1
env::set_var("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("CounterBox");
let (a1, b1) = {
let h = host.read().unwrap();
let a = h.invoke_instance_method(&bt1, "get", id1, &[]).expect("get tlv").unwrap();
let _ = h.invoke_instance_method(&bt1, "inc", id1, &[]).expect("inc tlv");
let b = h.invoke_instance_method(&bt1, "get", id1, &[]).expect("get2 tlv").unwrap();
(a.to_string_box().value.parse::<i64>().unwrap_or(0), b.to_string_box().value.parse::<i64>().unwrap_or(0))
};
assert_eq!(b1 - a1, 1, "CounterBox TLV should increment by 1");
// TypeBox path: verify same delta behavior (not comparing absolute values due to singleton)
env::remove_var("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("CounterBox");
let (a2, b2) = {
let h = host.read().unwrap();
let a = h.invoke_instance_method(&bt2, "get", id2, &[]).expect("get tb").unwrap();
let _ = h.invoke_instance_method(&bt2, "inc", id2, &[]).expect("inc tb");
let b = h.invoke_instance_method(&bt2, "get", id2, &[]).expect("get2 tb").unwrap();
(a.to_string_box().value.parse::<i64>().unwrap_or(0), b.to_string_box().value.parse::<i64>().unwrap_or(0))
};
assert_eq!(b2 - a2, 1, "CounterBox TypeBox should increment by 1");
}
#[test]
fn filebox_rw_close_tmpdir_tlv_vs_typebox() {
ensure_host();
let host = crate::runtime::get_global_plugin_host();
// Prepare temp file path
let mut p = std::env::temp_dir();
p.push(format!("nyash_test_{}_{}.txt", std::process::id(), rand_id())) ;
let path_str = p.to_string_lossy().to_string();
// TLV path
env::set_var("NYASH_DISABLE_TYPEBOX", "1");
let (bt1, id1, _hold1) = create_plugin_instance("FileBox");
let out_tlv = {
let h = host.read().unwrap();
let _ = h.invoke_instance_method(&bt1, "open", id1, &[Box::new(StringBox::new(&path_str)), Box::new(StringBox::new("w"))]).expect("open tlv");
let _ = h.invoke_instance_method(&bt1, "write", id1, &[Box::new(StringBox::new("hello"))]).expect("write tlv");
let _ = h.invoke_instance_method(&bt1, "close", id1, &[]).expect("close tlv");
// reopen and read
let _ = h.invoke_instance_method(&bt1, "open", id1, &[Box::new(StringBox::new(&path_str)), Box::new(StringBox::new("r"))]).expect("open2 tlv");
let rd = h.invoke_instance_method(&bt1, "read", id1, &[]).expect("read tlv").unwrap();
let _ = h.invoke_instance_method(&bt1, "close", id1, &[]).expect("close2 tlv");
rd.to_string_box().value
};
// TypeBox path
env::remove_var("NYASH_DISABLE_TYPEBOX");
let (bt2, id2, _hold2) = create_plugin_instance("FileBox");
let out_tb = {
let h = host.read().unwrap();
let _ = h.invoke_instance_method(&bt2, "open", id2, &[Box::new(StringBox::new(&path_str)), Box::new(StringBox::new("w"))]).expect("open tb");
let _ = h.invoke_instance_method(&bt2, "write", id2, &[Box::new(StringBox::new("hello"))]).expect("write tb");
let _ = h.invoke_instance_method(&bt2, "close", id2, &[]).expect("close tb");
let _ = h.invoke_instance_method(&bt2, "open", id2, &[Box::new(StringBox::new(&path_str)), Box::new(StringBox::new("r"))]).expect("open2 tb");
let rd = h.invoke_instance_method(&bt2, "read", id2, &[]).expect("read tb").unwrap();
let _ = h.invoke_instance_method(&bt2, "close", id2, &[]).expect("close2 tb");
rd.to_string_box().value
};
// Cleanup best-effort
let _ = fs::remove_file(&path_str);
assert_eq!(out_tlv, out_tb, "TLV vs TypeBox results should match (FileBox)");
}
fn rand_id() -> u64 {
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
now.as_micros() as u64
}
}