diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index c8aeba0f..ef782d11 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -31,10 +31,14 @@ Quick Next (today) 1) ParserBox 拡張(Stage‑2 の堅牢化・回帰修正)✅ Done 2025‑09‑16 - bool/null リテラルと空 RHS(代入/return/local)を Int(0) フォールバックで正規化。 - simple assignment → Local 正常化を `==` 判定と共に調整。 + - 三項演算子 `cond ? a : b` を `Ternary` ノードに正規化し、自走スモーク追加。 2) EmitterBox 拡張(JSON v0 の安定化)✅ Done 2025‑09‑16 - `meta.usings` を常時出力(空は `[]`)。 - 3) 自己ホスト経路で Ny 実装切替のゲート準備(現状は Python MVP 優先を維持)。 - 4) テスト: + 3) Resolver/BoxIndex の prefix メタ反映 ✅ Done 2025‑09‑16 + - `plugin_meta_by_box` を構築し、`require_prefix` / `expose_short_names` を `resolve_using_target` へ適用。 + - `NYASH_PLUGIN_REQUIRE_PREFIX` が無効でも per-plugin meta で短名禁止を検知。 + 4) 自己ホスト経路で Ny 実装切替のゲート準備(現状は Python MVP 優先を維持)。 + 5) テスト: - `source tools/dev_env.sh pyvm` - `NYASH_VM_USE_PY=1 ./tools/selfhost_stage2_smoke.sh` - `NYASH_VM_USE_PY=1 ./tools/selfhost_stage2_bridge_smoke.sh` diff --git a/apps/selfhost-compiler/boxes/parser_box.nyash b/apps/selfhost-compiler/boxes/parser_box.nyash index 64568b32..5c527c19 100644 --- a/apps/selfhost-compiler/boxes/parser_box.nyash +++ b/apps/selfhost-compiler/boxes/parser_box.nyash @@ -402,7 +402,37 @@ box ParserBox { parse_term2(src, i) { local lhs = me.parse_unary2(src, i) local j = me.gpos_get() local cont = 1 loop(cont == 1) { j = me.skip_ws(src, j) if j >= src.length() { cont = 0 } else { local op = src.substring(j, j+1) if op != "*" && op != "/" { cont = 0 } else { local rhs = me.parse_unary2(src, j+1) j = me.gpos_get() lhs = "{\"type\":\"Binary\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" } } } me.gpos_set(j) return lhs } parse_sum2(src, i) { local lhs = me.parse_term2(src, i) local j = me.gpos_get() local cont = 1 loop(cont == 1) { j = me.skip_ws(src, j) if j >= src.length() { cont = 0 } else { local op = src.substring(j, j+1) if op != "+" && op != "-" { cont = 0 } else { local rhs = me.parse_term2(src, j+1) j = me.gpos_get() lhs = "{\"type\":\"Binary\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" } } } me.gpos_set(j) return lhs } parse_compare2(src, i) { local lhs = me.parse_sum2(src, i) local j = me.gpos_get() j = me.skip_ws(src, j) local two = src.substring(j, j+2) local one = src.substring(j, j+1) local op = "" if two == "==" || two == "!=" || two == "<=" || two == ">=" { op = two j = j + 2 } else { if one == "<" || one == ">" { op = one j = j + 1 } } if op == "" { me.gpos_set(j) return lhs } local rhs = me.parse_sum2(src, j) j = me.gpos_get() me.gpos_set(j) return "{\"type\":\"Compare\",\"op\":\"" + op + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" } - parse_expr2(src, i) { local lhs = me.parse_compare2(src, i) local j = me.gpos_get() local cont = 1 loop(cont == 1) { j = me.skip_ws(src, j) local two = src.substring(j, j+2) if two != "&&" && two != "||" { cont = 0 } else { local rhs = me.parse_compare2(src, j+2) j = me.gpos_get() lhs = "{\"type\":\"Logical\",\"op\":\"" + two + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" } } me.gpos_set(j) return lhs } + parse_expr2(src, i) { + local lhs = me.parse_compare2(src, i) + local j = me.gpos_get() + local cont = 1 + loop(cont == 1) { + j = me.skip_ws(src, j) + local two = src.substring(j, j+2) + if two != "&&" && two != "||" { cont = 0 } else { + local rhs = me.parse_compare2(src, j+2) + j = me.gpos_get() + lhs = "{\"type\":\"Logical\",\"op\":\"" + two + "\",\"lhs\":" + lhs + ",\"rhs\":" + rhs + "}" + } + } + j = me.skip_ws(src, j) + if src.substring(j, j+1) == "?" { + j = j + 1 + j = me.skip_ws(src, j) + local then_expr = me.parse_expr2(src, j) + j = me.gpos_get() + j = me.skip_ws(src, j) + if src.substring(j, j+1) == ":" { j = j + 1 } + j = me.skip_ws(src, j) + local else_expr = me.parse_expr2(src, j) + j = me.gpos_get() + if else_expr.length() == 0 { else_expr = "{\"type\":\"Int\",\"value\":0}" } + me.gpos_set(j) + return "{\"type\":\"Ternary\",\"cond\":" + lhs + ",\"then\":" + then_expr + ",\"else\":" + else_expr + "}" + } + me.gpos_set(j) + return lhs + } parse_args2(src, i) { local j = me.skip_ws(src, i) local n = src.length() diff --git a/src/runner/box_index.rs b/src/runner/box_index.rs index 2662a529..cdb7e342 100644 --- a/src/runner/box_index.rs +++ b/src/runner/box_index.rs @@ -15,6 +15,7 @@ pub struct BoxIndex { pub aliases: HashMap, pub plugin_boxes: HashSet, pub plugin_meta: HashMap, + pub plugin_meta_by_box: HashMap, pub plugins_require_prefix_global: bool, } @@ -43,6 +44,7 @@ impl BoxIndex { // plugin box types (best-effort; may be empty if host not initialized yet) let mut plugin_boxes: HashSet = HashSet::new(); let mut plugin_meta: HashMap = HashMap::new(); + let mut plugin_meta_by_box: HashMap = HashMap::new(); let mut plugins_require_prefix_global = false; // Read per-plugin meta and global flags from nyash.toml when available @@ -59,7 +61,13 @@ impl BoxIndex { let prefix = t.get("prefix").and_then(|x| x.as_str()).map(|s| s.to_string()); let require_prefix = t.get("require_prefix").and_then(|x| x.as_bool()).unwrap_or(false); let expose_short_names = t.get("expose_short_names").and_then(|x| x.as_bool()).unwrap_or(true); - plugin_meta.insert(k.clone(), PluginMeta { prefix, require_prefix, expose_short_names }); + let meta = PluginMeta { prefix, require_prefix, expose_short_names }; + plugin_meta.insert(k.clone(), meta.clone()); + if let Some(arr) = t.get("boxes").and_then(|x| x.as_array()) { + for b in arr { + if let Some(name) = b.as_str() { plugin_meta_by_box.insert(name.to_string(), meta.clone()); } + } + } } } } @@ -68,13 +76,18 @@ impl BoxIndex { let host = crate::runtime::get_global_plugin_host(); if let Ok(h) = host.read() { if let Some(cfg) = h.config_ref() { - for (_lib, def) in &cfg.libraries { - for bt in &def.boxes { plugin_boxes.insert(bt.clone()); } + for (lib, def) in &cfg.libraries { + for bt in &def.boxes { + plugin_boxes.insert(bt.clone()); + if let Some(meta) = plugin_meta.get(lib) { + plugin_meta_by_box.insert(bt.clone(), meta.clone()); + } + } } } } - Self { aliases, plugin_boxes, plugin_meta, plugins_require_prefix_global } + Self { aliases, plugin_boxes, plugin_meta, plugin_meta_by_box, plugins_require_prefix_global } } pub fn is_known_plugin_short(name: &str) -> bool { diff --git a/src/runner/pipeline.rs b/src/runner/pipeline.rs index b77976e7..ce14763a 100644 --- a/src/runner/pipeline.rs +++ b/src/runner/pipeline.rs @@ -8,7 +8,6 @@ */ use super::*; -use super::box_index::BoxIndex; use std::collections::HashMap; /// Using/module resolution context accumulated from config/env/nyash.toml @@ -135,39 +134,24 @@ pub(super) fn resolve_using_target( ) -> Result { if is_path { return Ok(tgt.to_string()); } let trace = verbose || std::env::var("NYASH_RESOLVE_TRACE").ok().as_deref() == Some("1"); - // Strict plugin prefix: if enabled and target matches a known plugin box type - // and is not qualified (contains '.'), require a qualified/prefixed name. - // Strict mode: env or nyash.toml [plugins] require_prefix=true - let mut strict_effective = strict; - if !strict_effective { - if let Ok(text) = std::fs::read_to_string("nyash.toml") { - if let Ok(doc) = toml::from_str::(&text) { - if let Some(tbl) = doc.get("plugins").and_then(|v| v.as_table()) { - if let Some(v) = tbl.get("require_prefix").and_then(|v| v.as_bool()) { if v { strict_effective = true; } } - } - } - } - } + let idx = super::box_index::get_box_index(); + let mut strict_effective = strict || idx.plugins_require_prefix_global; if std::env::var("NYASH_PLUGIN_REQUIRE_PREFIX").ok().as_deref() == Some("1") { strict_effective = true; } - - if strict_effective { - let mut is_plugin_short = super::box_index::BoxIndex::is_known_plugin_short(tgt); - if !is_plugin_short { - // Fallback: heuristic list or env override - if let Ok(raw) = std::env::var("NYASH_KNOWN_PLUGIN_SHORTNAMES") { - let set: std::collections::HashSet = raw.split(',').map(|s| s.trim().to_string()).collect(); - is_plugin_short = set.contains(tgt); - } else { - // Minimal builtins set - const KNOWN: &[&str] = &[ - "ArrayBox","MapBox","StringBox","ConsoleBox","FileBox","PathBox","MathBox","IntegerBox","TOMLBox" - ]; - is_plugin_short = KNOWN.iter().any(|k| *k == tgt); + let meta_for_target = idx.plugin_meta_by_box.get(tgt).cloned(); + let mut require_prefix_target = meta_for_target.as_ref().map(|m| m.require_prefix).unwrap_or(false); + if let Some(m) = &meta_for_target { if !m.expose_short_names { require_prefix_target = true; } } + let mut is_plugin_short = meta_for_target.is_some(); + if !is_plugin_short { + is_plugin_short = idx.plugin_boxes.contains(tgt) || super::box_index::BoxIndex::is_known_plugin_short(tgt); + } + if (strict_effective || require_prefix_target) && is_plugin_short && !tgt.contains('.') { + let mut msg = format!("plugin short name '{}' requires prefix", tgt); + if let Some(meta) = &meta_for_target { + if let Some(pref) = &meta.prefix { + msg.push_str(&format!(" (use '{}.{}')", pref, tgt)); } } - if is_plugin_short && !tgt.contains('.') { - return Err(format!("plugin short name '{}' requires prefix (strict)", tgt)); - } + return Err(msg); } let key = { let base = context_dir.and_then(|p| p.to_str()).unwrap_or(""); @@ -332,3 +316,48 @@ pub(super) fn lint_fields_top(code: &str, strict: bool, verbose: bool) -> Result } Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + use tempfile::tempdir; + + #[test] + fn plugin_meta_requires_prefix_even_when_relaxed() { + let dir = tempdir().expect("tempdir"); + let old = std::env::current_dir().expect("cwd"); + std::env::set_current_dir(dir.path()).expect("chdir"); + let toml = r#" +[plugins] +require_prefix = false + +[plugins."test-plugin"] +prefix = "test" +require_prefix = true +expose_short_names = false +boxes = ["ArrayBox"] +"#; + std::fs::write("nyash.toml", toml).expect("write nyash.toml"); + crate::runner::box_index::refresh_box_index(); + crate::runner::box_index::cache_clear(); + + let res = resolve_using_target( + "ArrayBox", + false, + &[], + &[], + &HashMap::new(), + None, + false, + false, + ); + assert!(res.is_err(), "expected prefix enforcement"); + let err = res.err().unwrap(); + assert!(err.contains("requires prefix")); + assert!(err.contains("test.")); + + std::env::set_current_dir(old).expect("restore cwd"); + crate::runner::box_index::refresh_box_index(); + crate::runner::box_index::cache_clear(); + } +} diff --git a/src/runtime/plugin_loader_v2/enabled/loader.rs b/src/runtime/plugin_loader_v2/enabled/loader.rs index 9cc0de0d..ef292098 100644 --- a/src/runtime/plugin_loader_v2/enabled/loader.rs +++ b/src/runtime/plugin_loader_v2/enabled/loader.rs @@ -1,4 +1,4 @@ -use super::types::{PluginBoxV2, PluginHandleInner, LoadedPluginV2}; +use super::types::{PluginBoxMetadata, PluginBoxV2, PluginHandleInner, LoadedPluginV2}; use crate::bid::{BidResult, BidError}; use crate::box_trait::NyashBox; use crate::config::nyash_toml_v2::{NyashConfigV2, LibraryDefinition}; @@ -146,6 +146,40 @@ impl PluginLoaderV2 { None } + pub fn metadata_for_type_id(&self, type_id: u32) -> Option { + let config = self.config.as_ref()?; + let cfg_path = self.config_path.as_ref()?; + let toml_str = std::fs::read_to_string(cfg_path).ok()?; + let toml_value: toml::Value = toml::from_str(&toml_str).ok()?; + let (lib_name, box_type) = self.find_box_by_type_id(config, &toml_value, type_id)?; + let plugin = { + let plugins = self.plugins.read().ok()?; + plugins.get(lib_name)?.clone() + }; + let spec_key = (lib_name.to_string(), box_type.to_string()); + let mut resolved_type = type_id; + let mut fini_method = None; + if let Some(spec) = self.box_specs.read().ok()?.get(&spec_key).cloned() { + if let Some(tid) = spec.type_id { resolved_type = tid; } + if let Some(fini) = spec.fini_method_id { fini_method = Some(fini); } + } + if resolved_type == type_id || fini_method.is_none() { + if let Some(cfg) = config.get_box_config(lib_name, box_type, &toml_value) { + if resolved_type == type_id { resolved_type = cfg.type_id; } + if fini_method.is_none() { + fini_method = cfg.methods.get("fini").map(|m| m.method_id); + } + } + } + Some(PluginBoxMetadata { + lib_name: lib_name.to_string(), + box_type: box_type.to_string(), + type_id: resolved_type, + invoke_fn: plugin.invoke_fn, + fini_method_id: fini_method, + }) + } + pub fn construct_existing_instance(&self, type_id: u32, instance_id: u32) -> Option> { let config = self.config.as_ref()?; let cfg_path = self.config_path.as_ref()?; diff --git a/src/runtime/plugin_loader_v2/enabled/mod.rs b/src/runtime/plugin_loader_v2/enabled/mod.rs index 36c96258..fa2dc694 100644 --- a/src/runtime/plugin_loader_v2/enabled/mod.rs +++ b/src/runtime/plugin_loader_v2/enabled/mod.rs @@ -4,8 +4,14 @@ mod globals; mod errors; mod host_bridge; -pub use types::{PluginBoxV2, PluginHandleInner, NyashTypeBoxFfi, make_plugin_box_v2, construct_plugin_box}; +pub use types::{PluginBoxMetadata, PluginBoxV2, PluginHandleInner, NyashTypeBoxFfi, make_plugin_box_v2, construct_plugin_box}; pub use loader::PluginLoaderV2; pub use globals::{get_global_loader_v2, init_global_loader_v2, shutdown_plugins_v2}; +pub fn metadata_for_type_id(type_id: u32) -> Option { + let loader = get_global_loader_v2(); + let guard = loader.read().ok()?; + guard.metadata_for_type_id(type_id) +} + pub fn backend_kind() -> &'static str { "enabled" } diff --git a/src/runtime/plugin_loader_v2/enabled/types.rs b/src/runtime/plugin_loader_v2/enabled/types.rs index 5d4ea875..8f5c8868 100644 --- a/src/runtime/plugin_loader_v2/enabled/types.rs +++ b/src/runtime/plugin_loader_v2/enabled/types.rs @@ -1,4 +1,5 @@ -use crate::box_trait::{NyashBox, BoxCore, StringBox}; +use crate::box_trait::{BoxCore, NyashBox, StringBox}; +use super::host_bridge::InvokeFn; use std::any::Any; use std::sync::Arc; @@ -10,14 +11,23 @@ pub struct LoadedPluginV2 { pub(super) box_types: Vec, pub(super) typeboxes: std::collections::HashMap, pub(super) init_fn: Option i32>, - pub(super) invoke_fn: unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32, + pub(super) invoke_fn: InvokeFn, +} + +#[derive(Clone)] +pub struct PluginBoxMetadata { + pub lib_name: String, + pub box_type: String, + pub type_id: u32, + pub invoke_fn: InvokeFn, + pub fini_method_id: Option, } /// v2 Plugin Box handle core #[derive(Debug)] pub struct PluginHandleInner { pub type_id: u32, - pub invoke_fn: unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32, + pub invoke_fn: InvokeFn, pub instance_id: u32, pub fini_method_id: Option, pub(super) finalized: std::sync::atomic::AtomicBool, @@ -104,7 +114,7 @@ impl PluginBoxV2 { } /// Helper to construct a PluginBoxV2 from raw ids and invoke pointer safely -pub fn make_plugin_box_v2(box_type: String, type_id: u32, instance_id: u32, invoke_fn: unsafe extern "C" fn(u32,u32,u32,*const u8,usize,*mut u8,*mut usize) -> i32) -> PluginBoxV2 { +pub fn make_plugin_box_v2(box_type: String, type_id: u32, instance_id: u32, invoke_fn: InvokeFn) -> PluginBoxV2 { PluginBoxV2 { box_type, inner: Arc::new(PluginHandleInner { type_id, invoke_fn, instance_id, fini_method_id: None, finalized: std::sync::atomic::AtomicBool::new(false) }) } } @@ -112,7 +122,7 @@ pub fn make_plugin_box_v2(box_type: String, type_id: u32, instance_id: u32, invo pub fn construct_plugin_box( box_type: String, type_id: u32, - invoke_fn: unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32, + invoke_fn: InvokeFn, instance_id: u32, fini_method_id: Option, ) -> PluginBoxV2 { diff --git a/src/runtime/plugin_loader_v2/stub.rs b/src/runtime/plugin_loader_v2/stub.rs index a0eddc22..65a684db 100644 --- a/src/runtime/plugin_loader_v2/stub.rs +++ b/src/runtime/plugin_loader_v2/stub.rs @@ -3,15 +3,27 @@ use crate::box_trait::NyashBox; use once_cell::sync::Lazy; use std::sync::{Arc, RwLock}; +pub type InvokeFn = unsafe extern "C" fn(u32, u32, u32, *const u8, usize, *mut u8, *mut usize) -> i32; + #[derive(Debug, Clone)] pub struct PluginBoxV2 { pub box_type: String, pub inner: std::sync::Arc, } +#[derive(Debug, Clone)] +pub struct PluginBoxMetadata { + pub lib_name: String, + pub box_type: String, + pub type_id: u32, + pub invoke_fn: InvokeFn, + pub fini_method_id: Option, +} + #[derive(Debug)] pub struct PluginHandleInner { pub type_id: u32, + pub invoke_fn: InvokeFn, pub instance_id: u32, pub fini_method_id: Option, } @@ -25,6 +37,7 @@ impl PluginLoaderV2 { pub fn create_box(&self, _t: &str, _a: &[Box]) -> BidResult> { Err(BidError::PluginError) } pub fn extern_call(&self, _iface_name: &str, _method_name: &str, _args: &[Box]) -> BidResult>> { Err(BidError::PluginError) } pub fn invoke_instance_method(&self, _box_type: &str, _method_name: &str, _instance_id: u32, _args: &[Box]) -> BidResult>> { Err(BidError::PluginError) } + pub fn metadata_for_type_id(&self, _type_id: u32) -> Option { None } pub fn shutdown_singletons(&self) {} } @@ -34,3 +47,25 @@ pub fn init_global_loader_v2(_config_path: &str) -> BidResult<()> { Ok(()) } pub fn shutdown_plugins_v2() -> BidResult<()> { Ok(()) } pub fn backend_kind() -> &'static str { "stub" } + +pub fn metadata_for_type_id(_type_id: u32) -> Option { None } + +pub fn make_plugin_box_v2(box_type: String, type_id: u32, instance_id: u32, invoke_fn: InvokeFn) -> PluginBoxV2 { + PluginBoxV2 { + box_type, + inner: Arc::new(PluginHandleInner { type_id, invoke_fn, instance_id, fini_method_id: None }), + } +} + +pub fn construct_plugin_box( + box_type: String, + type_id: u32, + invoke_fn: InvokeFn, + instance_id: u32, + fini_method_id: Option, +) -> PluginBoxV2 { + PluginBoxV2 { + box_type, + inner: Arc::new(PluginHandleInner { type_id, invoke_fn, instance_id, fini_method_id }), + } +} diff --git a/tools/selfhost_stage2_smoke.sh b/tools/selfhost_stage2_smoke.sh index 810fd192..f37c90b0 100644 --- a/tools/selfhost_stage2_smoke.sh +++ b/tools/selfhost_stage2_smoke.sh @@ -122,5 +122,20 @@ OUT=$(NYASH_USE_NY_COMPILER=1 NYASH_NY_COMPILER_EMIT_ONLY=0 NYASH_VM_USE_PY=${NY set -e echo "$OUT" | rg -q '^Result:\s*3\b' && pass "String.length()" || fail "String.length()" "$OUT" +# J) ternary expression → 10 +cat > "$TMP/selfhost_ternary_basic.nyash" <<'NY' +return (1 < 2) ? 10 : 20 +NY +set +e +NYASH_USE_NY_COMPILER=1 NYASH_NY_COMPILER_EMIT_ONLY=0 NYASH_VM_USE_PY=${NYASH_VM_USE_PY:-1} \ + "$BIN" --backend vm "$TMP/selfhost_ternary_basic.nyash" >/dev/null 2>&1 +CODE=$? +set -e +if [[ "$CODE" -eq 10 ]]; then + pass "Ternary basic" +else + fail "Ternary basic" "exit=$CODE" +fi + echo "All selfhost Stage-2 smokes PASS" >&2 exit 0