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
}
}