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:
@ -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 {
|
||||
|
||||
@ -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")))
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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());
|
||||
|
||||
@ -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 → None(Nyashの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
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
84
src/tests/nyash_abi_basic.rs
Normal file
84
src/tests/nyash_abi_basic.rs
Normal 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");
|
||||
}
|
||||
}
|
||||
433
src/tests/typebox_tlv_diff.rs
Normal file
433
src/tests/typebox_tlv_diff.rs
Normal 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user