diff --git a/Cargo.toml b/Cargo.toml index 948ad3e0..15dffde8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -124,10 +124,10 @@ name = "gui_test_icon_extraction" path = "examples/test_icon_extraction.rs" required-features = ["gui-examples"] -[[example]] -name = "gui_visual_node_prototype" -path = "development/egui_research/experiments/visual_node_prototype.rs" -required-features = ["gui-examples"] +# [[example]] +# name = "gui_visual_node_prototype" +# path = "development/egui_research/experiments/visual_node_prototype.rs" +# required-features = ["gui-examples"] diff --git a/lang/src/shared/common/box_type_inspector_box.hako b/lang/src/shared/common/box_type_inspector_box.hako new file mode 100644 index 00000000..dfc67758 --- /dev/null +++ b/lang/src/shared/common/box_type_inspector_box.hako @@ -0,0 +1,59 @@ +// box_type_inspector_box.hako — Runtime box type metadata helper +// Responsibility: +// - Wrap env.box_introspect.* extern calls for Stage1 code. +// - Provide describe/kind/is_map/is_array helpers without depending on `"" + MapBox`. + +static box BoxTypeInspectorBox { + method _describe(value) { + local args = new ArrayBox() + args.push(value) + local info = hostbridge.extern_invoke("env.box_introspect", "kind", args) + if env.get("HAKO_BOX_INTROSPECT_TRACE") == "1" { + if info != null { + print("[box_introspect:hako] kind=" + ("" + info.get("kind")) + + " is_map=" + ("" + info.get("is_map")) + + " is_array=" + ("" + info.get("is_array"))) + } else { + print("[box_introspect:hako] info=null") + } + } + return info + } + + method kind(value) { + local info = me._describe(value) + if info == null { return "Unknown" } + local k = info.get("kind") + if k == null { return "Unknown" } + return "" + k + } + + method is_map(value) { + local info = me._describe(value) + if info != null { + local flag = info.get("is_map") + if flag != null { return flag } + } + // Fallback: detect MapBox from repr when extern is unavailable + local repr = "" + value + if repr.indexOf("MapBox(") == 0 { return true } + return false + } + + method is_array(value) { + local info = me._describe(value) + if info != null { + local flag = info.get("is_array") + if flag != null { return flag } + } + // Fallback: detect ArrayBox from repr when extern is unavailable + local repr = "" + value + if repr.indexOf("ArrayBox(") == 0 { return true } + return false + } + + method describe(value) { + // Expose full Map so callers can inspect additional fields (type_name/type_id/etc.) + return me._describe(value) + } +} diff --git a/src/backend/mir_interpreter/handlers/extern_provider.rs b/src/backend/mir_interpreter/handlers/extern_provider.rs index b7a125b1..7d741e04 100644 --- a/src/backend/mir_interpreter/handlers/extern_provider.rs +++ b/src/backend/mir_interpreter/handlers/extern_provider.rs @@ -1,6 +1,7 @@ use super::*; use crate::backend::mir_interpreter::utils::error_helpers::ErrorBuilder; use serde_json::Value as JsonValue; +use std::sync::Arc; impl MirInterpreter { #[inline] @@ -212,7 +213,44 @@ impl MirInterpreter { let val = std::env::var(&key).ok(); Some(Ok(match val { Some(s) => VMValue::String(s), None => VMValue::Void })) } - // Legacy global-call form: hostbridge.extern_invoke(name, method, args?) + // Direct env.box_introspect.kind extern (ExternCall form) + "env.box_introspect.kind" => { + use crate::box_trait::{NyashBox, StringBox}; + use crate::runtime::plugin_loader_v2; + + let mut collected: Vec> = Vec::new(); + if let Some(arg_reg) = args.get(0) { + let v = match self.reg_load(*arg_reg) { + Ok(v) => v, + Err(e) => return Some(Err(e)), + }; + match v { + VMValue::BoxRef(b) => collected.push(b.clone_box()), + other => { + collected.push(Box::new(StringBox::new(&other.to_string()))); + } + } + } else { + return Some(Err(self.err_invalid( + "env.box_introspect.kind expects 1 arg", + ))); + } + + #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] + let result = plugin_loader_v2::handle_box_introspect("kind", &collected); + #[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))] + let result: crate::bid::BidResult>> = + Err(crate::bid::BidError::PluginError); + + match result { + Ok(Some(b)) => Some(Ok(VMValue::BoxRef(Arc::from(b)))), + Ok(None) => Some(Ok(VMValue::Void)), + Err(e) => Some(Err(self.err_with_context( + "env.box_introspect.kind", + &format!("{:?}", e), + ))), + } + } "hostbridge.extern_invoke" => { if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") { eprintln!("[hb:entry:provider] hostbridge.extern_invoke"); @@ -386,12 +424,93 @@ impl MirInterpreter { Err(e) => Err(ErrorBuilder::with_context("env.codegen.link_object", &e.to_string())) } } + ("env.box_introspect", "kind") => { + // hostbridge.extern_invoke("env.box_introspect","kind",[value]) + // args layout (regs): [name, method, array_box_or_value, ...] + // For BoxTypeInspectorBox we only care about the first element of the ArrayBox. + use crate::box_trait::{NyashBox, StringBox}; + use crate::runtime::plugin_loader_v2; + + let mut collected: Vec> = Vec::new(); + if let Some(arg_reg) = args.get(2) { + let v = match self.reg_load(*arg_reg) { + Ok(v) => v, + Err(e) => return Some(Err(e)), + }; + match v { + VMValue::BoxRef(b) => { + if let Some(ab) = + b.as_any().downcast_ref::() + { + let idx0: Box = + Box::new(crate::box_trait::IntegerBox::new(0)); + let elem0 = ab.get(idx0); + if std::env::var("NYASH_BOX_INTROSPECT_TRACE") + .ok() + .as_deref() + == Some("1") + { + eprintln!( + "[box_introspect:extern] using ArrayBox[0] value_type={}", + elem0.type_name() + ); + } + collected.push(elem0); + } else { + if std::env::var("NYASH_BOX_INTROSPECT_TRACE") + .ok() + .as_deref() + == Some("1") + { + eprintln!( + "[box_introspect:extern] using BoxRef({}) directly", + b.type_name() + ); + } + collected.push(b.clone_box()); + } + } + other => { + if std::env::var("NYASH_BOX_INTROSPECT_TRACE") + .ok() + .as_deref() + == Some("1") + { + eprintln!( + "[box_introspect:extern] non-box arg kind={:?}", + other + ); + } + collected.push(Box::new(StringBox::new(&other.to_string()))); + } + } + } else { + return Some(Err(self.err_invalid( + "extern_invoke env.box_introspect.kind expects args array", + ))); + } + + #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] + let result = plugin_loader_v2::handle_box_introspect("kind", &collected); + #[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))] + let result: crate::bid::BidResult>> = + Err(crate::bid::BidError::PluginError); + + match result { + Ok(Some(b)) => Ok(VMValue::BoxRef(Arc::from(b))), + Ok(None) => Ok(VMValue::Void), + Err(e) => Err(self.err_with_context( + "env.box_introspect.kind", + &format!("{:?}", e), + )), + } + } _ => { if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") { eprintln!("[hb:unsupported:provider] {}.{}", name, method); } Err(self.err_invalid(format!( - "hostbridge.extern_invoke unsupported for {}.{} [provider]", + "hostbridge.extern_invoke unsupported for {}.{} [provider] (check extern_provider_dispatch / env.* handlers)", name, method ))) }, diff --git a/src/backend/mod.rs b/src/backend/mod.rs index e9709b01..23181d4b 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -23,10 +23,10 @@ pub mod wasm; #[cfg(feature = "wasm-backend")] pub mod wasm_v2; -#[cfg(feature = "llvm-inkwell-legacy")] -pub mod llvm_legacy; -#[cfg(feature = "llvm-inkwell-legacy")] -pub mod llvm; +// #[cfg(feature = "llvm-inkwell-legacy")] +// pub mod llvm_legacy; +// #[cfg(feature = "llvm-inkwell-legacy")] +// pub mod llvm; // Public aliases to make the role of the VM clear in runner/tests pub use mir_interpreter::MirInterpreter; diff --git a/src/runner/json_v0_bridge/lowering/expr.rs b/src/runner/json_v0_bridge/lowering/expr.rs index e939e068..b0065b60 100644 --- a/src/runner/json_v0_bridge/lowering/expr.rs +++ b/src/runner/json_v0_bridge/lowering/expr.rs @@ -80,6 +80,19 @@ impl<'a> VarScope for MapVars<'a> { self.vars.insert(name.to_string(), dst); return Ok(Some(dst)); } + // Phase 25.1b: Treat `env` as a well-known global for env.box_introspect.* etc. + // Similar to hostbridge, we need a placeholder value for the nested method pattern. + if name == "env" { + let dst = f.next_value_id(); + if let Some(bb) = f.get_block_mut(cur_bb) { + bb.add_instruction(MirInstruction::Const { + dst, + value: ConstValue::String("env".into()), + }); + } + self.vars.insert(name.to_string(), dst); + return Ok(Some(dst)); + } if name == "me" { if env.allow_me_dummy { let dst = f.next_value_id(); @@ -363,6 +376,29 @@ pub(super) fn lower_expr_with_scope( } return Ok((dst, cur2)); } + // Phase 25.1b: Handle env.box_introspect.kind(value) pattern + // Pattern: Method { recv: Method { recv: Var("env"), method: "box_introspect" }, method: "kind", args } + if let ExprV0::Method { recv: inner_recv, method: inner_method, args: inner_args } = &**recv { + if matches!(&**inner_recv, ExprV0::Var { name } if name == "env") + && inner_method == "box_introspect" + && inner_args.is_empty() { + + // Lower args for the final method call + let (arg_ids, cur2) = lower_args_with_scope(env, f, cur_bb, args, vars)?; + let dst = f.next_value_id(); + + if let Some(bb) = f.get_block_mut(cur2) { + bb.add_instruction(MirInstruction::ExternCall { + dst: Some(dst), + iface_name: "env.box_introspect".into(), + method_name: method.clone(), + args: arg_ids, + effects: EffectMask::READ, + }); + } + return Ok((dst, cur2)); + } + } let (recv_v, cur) = lower_expr_with_scope(env, f, cur_bb, recv, vars)?; let (arg_ids, cur2) = lower_args_with_scope(env, f, cur, args, vars)?; let dst = f.next_value_id(); diff --git a/src/runtime/plugin_loader_v2/enabled/extern_functions.rs b/src/runtime/plugin_loader_v2/enabled/extern_functions.rs index 5a040242..2ec95889 100644 --- a/src/runtime/plugin_loader_v2/enabled/extern_functions.rs +++ b/src/runtime/plugin_loader_v2/enabled/extern_functions.rs @@ -4,12 +4,18 @@ //! that were previously in a large switch statement in loader.rs use crate::bid::{BidError, BidResult}; -use crate::box_trait::{NyashBox, StringBox, VoidBox}; -use crate::boxes::result::NyashResultBox; +use crate::box_trait::IntegerBox; +use crate::box_trait::{BoolBox, NyashBox, StringBox, VoidBox}; +use crate::boxes::array::ArrayBox; use crate::boxes::future::FutureBox; +use crate::boxes::map_box::MapBox; +use crate::boxes::null_box::NullBox; +use crate::boxes::result::NyashResultBox; use crate::boxes::token_box::TokenBox; -use crate::runtime::modules_registry; use crate::runtime::global_hooks; +use crate::runtime::modules_registry; +use std::collections::hash_map::DefaultHasher; +use std::hash::{Hash, Hasher}; /// Handle external function calls from the runtime pub fn extern_call( @@ -32,6 +38,7 @@ pub fn extern_call( "env.future" => handle_future(method_name, args), "env.mirbuilder" => handle_mirbuilder(method_name, args), "env.codegen" => handle_codegen(method_name, args), + "env.box_introspect" => handle_box_introspect(method_name, args), _ => Err(BidError::PluginError), } } @@ -41,8 +48,12 @@ fn should_trace_call_extern(target: &str, method: &str) -> bool { let key = format!("{}.{}", target, method); for pat in flt.split(',') { let p = pat.trim(); - if p.is_empty() { continue; } - if p == method || p == key { return true; } + if p.is_empty() { + continue; + } + if p == method || p == key { + return true; + } } return false; } @@ -50,7 +61,10 @@ fn should_trace_call_extern(target: &str, method: &str) -> bool { } /// Handle env.console.* methods -fn handle_console(method_name: &str, args: &[Box]) -> BidResult>> { +fn handle_console( + method_name: &str, + args: &[Box], +) -> BidResult>> { match method_name { "log" => { let trace = std::env::var("NYASH_CONSOLE_TRACE").ok().as_deref() == Some("1"); @@ -68,7 +82,10 @@ fn handle_console(method_name: &str, args: &[Box]) -> BidResult]) -> BidResult>> { +fn handle_result( + method_name: &str, + args: &[Box], +) -> BidResult>> { match method_name { "ok" => { // Wrap the first argument as Result.Ok; if missing, use Void @@ -91,7 +108,10 @@ fn handle_result(method_name: &str, args: &[Box]) -> BidResult]) -> BidResult>> { +fn handle_modules( + method_name: &str, + args: &[Box], +) -> BidResult>> { match method_name { "set" => { if args.len() >= 2 { @@ -115,7 +135,10 @@ fn handle_modules(method_name: &str, args: &[Box]) -> BidResult]) -> BidResult>> { +fn handle_task( + method_name: &str, + args: &[Box], +) -> BidResult>> { match method_name { "cancelCurrent" => { let tok = global_hooks::current_group_token(); @@ -153,7 +176,10 @@ fn handle_task_wait(_args: &[Box]) -> BidResult]) -> BidResult>> { +fn handle_debug( + method_name: &str, + args: &[Box], +) -> BidResult>> { match method_name { "trace" => { if std::env::var("NYASH_DEBUG_TRACE").ok().as_deref() == Some("1") { @@ -168,7 +194,10 @@ fn handle_debug(method_name: &str, args: &[Box]) -> BidResult]) -> BidResult>> { +fn handle_runtime( + method_name: &str, + _args: &[Box], +) -> BidResult>> { match method_name { "checkpoint" => { if crate::config::env::runtime_checkpoint_trace() { @@ -182,7 +211,10 @@ fn handle_runtime(method_name: &str, _args: &[Box]) -> BidResult]) -> BidResult>> { +fn handle_future( + method_name: &str, + args: &[Box], +) -> BidResult>> { match method_name { "new" | "birth" => { let fut = FutureBox::new(); @@ -193,10 +225,7 @@ fn handle_future(method_name: &str, args: &[Box]) -> BidResult { if args.len() >= 2 { - if let Some(fut) = args[0] - .as_any() - .downcast_ref::() - { + if let Some(fut) = args[0].as_any().downcast_ref::() { fut.set_result(args[1].clone_box()); } } @@ -207,11 +236,110 @@ fn handle_future(method_name: &str, args: &[Box]) -> BidResult], +) -> BidResult>> { + match method_name { + "kind" => { + let value = args.get(0).ok_or(BidError::PluginError)?; + let info = build_box_info(value.as_ref()); + if std::env::var("NYASH_BOX_INTROSPECT_TRACE") + .ok() + .as_deref() == Some("1") + { + eprintln!( + "[box_introspect:plugin] kind={} type_name={} is_map={} is_array={}", + info.get(Box::new(StringBox::new("kind"))).to_string_box().value, + info.get(Box::new(StringBox::new("type_name"))).to_string_box().value, + info.get(Box::new(StringBox::new("is_map"))).to_string_box().value, + info.get(Box::new(StringBox::new("is_array"))).to_string_box().value, + ); + } + Ok(Some(Box::new(info))) + } + _ => Err(BidError::PluginError), + } +} + +fn build_box_info(value: &dyn NyashBox) -> MapBox { + let info = MapBox::new(); + insert_string(&info, "kind", &classify_kind(value)); + insert_string(&info, "type_name", value.type_name()); + insert_string(&info, "type_id", &format!("{:016x}", type_id_hash(value))); + insert_bool( + &info, + "is_map", + value.as_any().downcast_ref::().is_some(), + ); + insert_bool( + &info, + "is_array", + value.as_any().downcast_ref::().is_some(), + ); + insert_bool( + &info, + "is_null", + value.as_any().downcast_ref::().is_some(), + ); + info +} + +fn insert_string(target: &MapBox, key: &str, value: &str) { + let _ = target.set( + Box::new(StringBox::new(key)), + Box::new(StringBox::new(value)), + ); +} + +fn insert_bool(target: &MapBox, key: &str, value: bool) { + let _ = target.set(Box::new(StringBox::new(key)), Box::new(BoolBox::new(value))); +} + +fn classify_kind(value: &dyn NyashBox) -> String { + if value.as_any().downcast_ref::().is_some() { + return "MapBox".to_string(); + } + if value.as_any().downcast_ref::().is_some() { + return "ArrayBox".to_string(); + } + if value.as_any().downcast_ref::().is_some() { + return "StringBox".to_string(); + } + if value.as_any().downcast_ref::().is_some() { + return "IntegerBox".to_string(); + } + if value.as_any().downcast_ref::().is_some() { + return "BoolBox".to_string(); + } + if value.as_any().downcast_ref::().is_some() { + return "NullBox".to_string(); + } + simplify_type_name(value.type_name()) +} + +fn simplify_type_name(full: &str) -> String { + full.rsplit("::").next().unwrap_or(full).to_string() +} + +fn type_id_hash(value: &dyn NyashBox) -> u64 { + let mut hasher = DefaultHasher::new(); + value.as_any().type_id().hash(&mut hasher); + hasher.finish() +} + /// Handle env.mirbuilder.* methods (Program(JSON v0) → MIR(JSON v0)) -fn handle_mirbuilder(method_name: &str, args: &[Box]) -> BidResult>> { +fn handle_mirbuilder( + method_name: &str, + args: &[Box], +) -> BidResult>> { match method_name { "emit" => { - let program_json = args.get(0).map(|b| b.to_string_box().value).unwrap_or_default(); + let program_json = args + .get(0) + .map(|b| b.to_string_box().value) + .unwrap_or_default(); match crate::host_providers::mir_builder::program_json_to_mir_json(&program_json) { Ok(s) => Ok(Some(Box::new(StringBox::new(&s)) as Box)), Err(_e) => Ok(None), @@ -222,21 +350,36 @@ fn handle_mirbuilder(method_name: &str, args: &[Box]) -> BidResult } /// Handle env.codegen.* methods (MIR(JSON v0) → object via ny-llvmc) -fn handle_codegen(method_name: &str, args: &[Box]) -> BidResult>> { +fn handle_codegen( + method_name: &str, + args: &[Box], +) -> BidResult>> { match method_name { "emit_object" => { - let mir_json = args.get(0).map(|b| b.to_string_box().value).unwrap_or_default(); + let mir_json = args + .get(0) + .map(|b| b.to_string_box().value) + .unwrap_or_default(); // Collect minimal options from env (optional) - let opt_level = std::env::var("HAKO_LLVM_OPT_LEVEL").ok().or_else(|| std::env::var("NYASH_LLVM_OPT_LEVEL").ok()); + let opt_level = std::env::var("HAKO_LLVM_OPT_LEVEL") + .ok() + .or_else(|| std::env::var("NYASH_LLVM_OPT_LEVEL").ok()); let out = None; - let nyrt = std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from); - let opts = crate::host_providers::llvm_codegen::Opts { out, nyrt, opt_level, timeout_ms: None }; + let nyrt = std::env::var("NYASH_EMIT_EXE_NYRT") + .ok() + .map(std::path::PathBuf::from); + let opts = crate::host_providers::llvm_codegen::Opts { + out, + nyrt, + opt_level, + timeout_ms: None, + }; match crate::host_providers::llvm_codegen::mir_json_to_object(&mir_json, opts) { Ok(p) => { // Convert PathBuf → String via lossy conversion (owned) let s = p.to_string_lossy().into_owned(); Ok(Some(Box::new(StringBox::new(s)) as Box)) - }, + } Err(_e) => Ok(None), } }