feat: using system完全実装+旧スモークテストアーカイブ完了
✅ using nyashstd完全動作(ChatGPT実装) - builtin:nyashstd自動解決 - 環境変数不要でデフォルト有効 - console.log等の基本機能完備 ✅ Fixture plugin追加(テスト用最小構成) ✅ v2スモークテスト構造への移行 ✅ 旧tools/test/smoke/削除(100+ファイル) 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@ -16,12 +16,16 @@ use crate::mir::{
|
||||
};
|
||||
|
||||
pub struct MirInterpreter {
|
||||
// SSA value table
|
||||
// SSA value table (per-function; swapped on call)
|
||||
regs: HashMap<ValueId, VMValue>,
|
||||
// Simple local memory for Load/Store where `ptr` is a ValueId token
|
||||
mem: HashMap<ValueId, VMValue>,
|
||||
// Object field storage for RefGet/RefSet (keyed by reference ValueId)
|
||||
obj_fields: HashMap<ValueId, HashMap<String, VMValue>>,
|
||||
// Function table (current module)
|
||||
functions: HashMap<String, MirFunction>,
|
||||
// Currently executing function name (for call resolution preferences)
|
||||
cur_fn: Option<String>,
|
||||
}
|
||||
|
||||
impl MirInterpreter {
|
||||
@ -30,11 +34,15 @@ impl MirInterpreter {
|
||||
regs: HashMap::new(),
|
||||
mem: HashMap::new(),
|
||||
obj_fields: HashMap::new(),
|
||||
functions: HashMap::new(),
|
||||
cur_fn: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Execute module entry (main) and return boxed result
|
||||
pub fn execute_module(&mut self, module: &MirModule) -> Result<Box<dyn NyashBox>, VMError> {
|
||||
// Snapshot functions for call resolution
|
||||
self.functions = module.functions.clone();
|
||||
let func = module
|
||||
.functions
|
||||
.get("main")
|
||||
@ -44,6 +52,27 @@ impl MirInterpreter {
|
||||
}
|
||||
|
||||
fn execute_function(&mut self, func: &MirFunction) -> Result<VMValue, VMError> {
|
||||
self._exec_function_inner(func, None)
|
||||
}
|
||||
|
||||
fn _exec_function_inner(
|
||||
&mut self,
|
||||
func: &MirFunction,
|
||||
arg_vals: Option<&[VMValue]>,
|
||||
) -> Result<VMValue, VMError> {
|
||||
// Swap in a fresh register file for this call
|
||||
let saved_regs = std::mem::take(&mut self.regs);
|
||||
let saved_fn = self.cur_fn.clone();
|
||||
self.cur_fn = Some(func.signature.name.clone());
|
||||
|
||||
// Bind parameters if provided
|
||||
if let Some(args) = arg_vals {
|
||||
for (i, pid) in func.params.iter().enumerate() {
|
||||
let v = args.get(i).cloned().unwrap_or(VMValue::Void);
|
||||
self.regs.insert(*pid, v);
|
||||
}
|
||||
}
|
||||
|
||||
let mut cur = func.entry_block;
|
||||
let mut last_pred: Option<BasicBlockId> = None;
|
||||
loop {
|
||||
@ -433,12 +462,12 @@ impl MirInterpreter {
|
||||
}
|
||||
}
|
||||
// Handle terminator
|
||||
match &block.terminator {
|
||||
let out = match &block.terminator {
|
||||
Some(MirInstruction::Return { value }) => {
|
||||
if let Some(v) = value {
|
||||
return self.reg_load(*v);
|
||||
self.reg_load(*v)
|
||||
} else {
|
||||
return Ok(VMValue::Void);
|
||||
Ok(VMValue::Void)
|
||||
}
|
||||
}
|
||||
Some(MirInstruction::Jump { target }) => {
|
||||
@ -458,18 +487,24 @@ impl MirInterpreter {
|
||||
continue;
|
||||
}
|
||||
None => {
|
||||
return Err(VMError::InvalidBasicBlock(format!(
|
||||
Err(VMError::InvalidBasicBlock(format!(
|
||||
"unterminated block {:?}",
|
||||
block.id
|
||||
)))
|
||||
}
|
||||
Some(other) => {
|
||||
return Err(VMError::InvalidInstruction(format!(
|
||||
Err(VMError::InvalidInstruction(format!(
|
||||
"invalid terminator in MIR interp: {:?}",
|
||||
other
|
||||
)))
|
||||
}
|
||||
}
|
||||
};
|
||||
// Function finished (return or error)
|
||||
// Restore previous register file and current function
|
||||
let result = out;
|
||||
self.cur_fn = saved_fn;
|
||||
self.regs = saved_regs;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@ -595,13 +630,66 @@ impl MirInterpreter {
|
||||
}
|
||||
|
||||
/// LEGACY: 従来の文字列ベース解決(後方互換性)
|
||||
fn execute_legacy_call(&mut self, func_id: ValueId, _args: &[ValueId]) -> Result<VMValue, VMError> {
|
||||
// 従来の実装: func_idから関数名を取得して呼び出し
|
||||
// 簡易実装 - 実際には関数テーブルやシンボル解決が必要
|
||||
Err(VMError::InvalidInstruction(format!(
|
||||
"Legacy function call (ValueId: {}) not implemented in VM interpreter. Please use Callee-typed calls.",
|
||||
func_id
|
||||
)))
|
||||
fn execute_legacy_call(&mut self, func_id: ValueId, args: &[ValueId]) -> Result<VMValue, VMError> {
|
||||
// 1) 名前を取り出す
|
||||
let name_val = self.reg_load(func_id)?;
|
||||
let raw = match name_val {
|
||||
VMValue::String(ref s) => s.clone(),
|
||||
other => other.to_string(),
|
||||
};
|
||||
// 2) 直接一致を優先
|
||||
let mut pick: Option<String> = None;
|
||||
if self.functions.contains_key(&raw) {
|
||||
pick = Some(raw.clone());
|
||||
} else {
|
||||
let arity = args.len();
|
||||
let mut cands: Vec<String> = Vec::new();
|
||||
// a) 末尾サフィックス一致: ".name/arity"
|
||||
let suf = format!(".{}{}", raw, format!("/{}", arity));
|
||||
for k in self.functions.keys() {
|
||||
if k.ends_with(&suf) { cands.push(k.clone()); }
|
||||
}
|
||||
// b) raw に '/' が含まれ、完全名っぽい場合はそのままも候補に(既に上で除外)
|
||||
if cands.is_empty() && raw.contains('/') && self.functions.contains_key(&raw) {
|
||||
cands.push(raw.clone());
|
||||
}
|
||||
// c) 優先: 現在のボックス名と一致するもの
|
||||
if cands.len() > 1 {
|
||||
if let Some(cur) = &self.cur_fn {
|
||||
let cur_box = cur.split('.').next().unwrap_or("");
|
||||
let scoped: Vec<String> = cands
|
||||
.iter()
|
||||
.filter(|k| k.starts_with(&format!("{}.", cur_box)))
|
||||
.cloned()
|
||||
.collect();
|
||||
if scoped.len() == 1 { cands = scoped; }
|
||||
}
|
||||
}
|
||||
if cands.len() == 1 {
|
||||
pick = Some(cands.remove(0));
|
||||
} else if cands.len() > 1 {
|
||||
cands.sort();
|
||||
pick = Some(cands[0].clone());
|
||||
}
|
||||
}
|
||||
let fname = pick.ok_or_else(|| VMError::InvalidInstruction(format!(
|
||||
"call unresolved: '{}' (arity={})",
|
||||
raw,
|
||||
args.len()
|
||||
)))?;
|
||||
if std::env::var("NYASH_VM_CALL_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[vm] legacy-call resolved '{}' -> '{}'", raw, fname);
|
||||
}
|
||||
let callee = self
|
||||
.functions
|
||||
.get(&fname)
|
||||
.cloned()
|
||||
.ok_or_else(|| VMError::InvalidInstruction(format!("function not found: {}", fname)))?;
|
||||
// 3) 実引数の評価
|
||||
let mut argv: Vec<VMValue> = Vec::new();
|
||||
for a in args { argv.push(self.reg_load(*a)?); }
|
||||
// 4) 実行
|
||||
self._exec_function_inner(&callee, Some(&argv))
|
||||
}
|
||||
|
||||
/// グローバル関数実行(nyash.builtin.*)
|
||||
|
||||
@ -306,7 +306,7 @@ impl UnifiedBoxRegistry {
|
||||
drop(cache);
|
||||
|
||||
// Linear search through all factories
|
||||
for factory in &self.factories {
|
||||
for (fi, factory) in self.factories.iter().enumerate() {
|
||||
if !factory.is_available() {
|
||||
continue;
|
||||
}
|
||||
@ -318,6 +318,14 @@ impl UnifiedBoxRegistry {
|
||||
}
|
||||
|
||||
// Try to create the box (factories with empty box_types() will always be tried)
|
||||
if std::env::var("NYASH_DEBUG_PLUGIN").ok().as_deref() == Some("1") {
|
||||
eprintln!(
|
||||
"[UnifiedBoxRegistry] try factory#{} {:?} for {}",
|
||||
fi,
|
||||
factory.factory_type(),
|
||||
name
|
||||
);
|
||||
}
|
||||
match factory.create_box(name, args) {
|
||||
Ok(boxed) => return Ok(boxed),
|
||||
Err(_) => continue, // Try next factory
|
||||
|
||||
@ -72,6 +72,7 @@ pub mod grammar;
|
||||
pub mod syntax; // syntax sugar config and helpers
|
||||
// Execution runner (CLI coordinator)
|
||||
pub mod runner;
|
||||
pub mod using; // using resolver scaffolding (Phase 15)
|
||||
|
||||
// Expose the macro engine module under a raw identifier; the source lives under `src/macro/`.
|
||||
#[path = "macro/mod.rs"]
|
||||
|
||||
@ -241,7 +241,15 @@ impl MirBuilder {
|
||||
if let Some(&value_id) = self.variable_map.get(&name) {
|
||||
Ok(value_id)
|
||||
} else {
|
||||
Err(format!("Undefined variable: {}", name))
|
||||
// Enhance diagnostics using Using simple registry (Phase 1)
|
||||
let mut msg = format!("Undefined variable: {}", name);
|
||||
let suggest = crate::using::simple_registry::suggest_using_for_symbol(&name);
|
||||
if !suggest.is_empty() {
|
||||
msg.push_str("\nHint: symbol appears in using module(s): ");
|
||||
msg.push_str(&suggest.join(", "));
|
||||
msg.push_str("\nConsider adding 'using <module> [as Alias]' or check nyash.toml [using].");
|
||||
}
|
||||
Err(msg)
|
||||
}
|
||||
}
|
||||
|
||||
@ -451,4 +459,3 @@ impl Default for MirBuilder {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -574,6 +574,38 @@ impl super::MirBuilder {
|
||||
method: String,
|
||||
arguments: Vec<ASTNode>,
|
||||
) -> Result<ValueId, String> {
|
||||
if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") {
|
||||
let kind = match &object {
|
||||
ASTNode::Variable { .. } => "Variable",
|
||||
ASTNode::FieldAccess { .. } => "FieldAccess",
|
||||
ASTNode::This { .. } => "This",
|
||||
ASTNode::Me { .. } => "Me",
|
||||
_ => "Other",
|
||||
};
|
||||
eprintln!("[builder] method-call object kind={} method={}", kind, method);
|
||||
}
|
||||
// Static box method call: BoxName.method(args)
|
||||
if let ASTNode::Variable { name: obj_name, .. } = &object {
|
||||
// If not a local variable and matches a declared box name, treat as static method call
|
||||
let is_local_var = self.variable_map.contains_key(obj_name);
|
||||
// Phase 15.5: Treat unknown identifiers in receiver position as static type names
|
||||
if !is_local_var {
|
||||
if std::env::var("NYASH_STATIC_CALL_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[builder] static-call {}.{}()", obj_name, method);
|
||||
}
|
||||
// Build argument values
|
||||
let mut arg_values: Vec<ValueId> = Vec::new();
|
||||
for a in &arguments {
|
||||
arg_values.push(self.build_expression(a.clone())?);
|
||||
}
|
||||
// Compose lowered function name: BoxName.method/N
|
||||
let func_name = format!("{}.{}{}", obj_name, method, format!("/{}", arg_values.len()));
|
||||
let dst = self.value_gen.next();
|
||||
// Use legacy global-call emission to avoid unified builtin/extern constraints
|
||||
self.emit_legacy_call(Some(dst), CallTarget::Global(func_name), arg_values)?;
|
||||
return Ok(dst);
|
||||
}
|
||||
}
|
||||
// Minimal TypeOp wiring via method-style syntax: value.is("Type") / value.as("Type")
|
||||
if (method == "is" || method == "as") && arguments.len() == 1 {
|
||||
if let Some(type_name) = Self::extract_string_literal(&arguments[0]) {
|
||||
|
||||
@ -119,8 +119,35 @@ impl super::MirBuilder {
|
||||
..
|
||||
} => {
|
||||
if is_static && name == "Main" {
|
||||
// Special entry box: materialize main() as Program and lower others as static functions
|
||||
self.build_static_main_box(name.clone(), methods.clone())
|
||||
} else if is_static {
|
||||
// Generic static box: lower all static methods into standalone MIR functions (BoxName.method/N)
|
||||
self.user_defined_boxes.insert(name.clone());
|
||||
for (method_name, method_ast) in methods.clone() {
|
||||
if let ASTNode::FunctionDeclaration { params, body, .. } = method_ast {
|
||||
let func_name = format!(
|
||||
"{}.{}{}",
|
||||
name,
|
||||
method_name,
|
||||
format!("/{}", params.len())
|
||||
);
|
||||
self.lower_static_method_as_function(
|
||||
func_name,
|
||||
params.clone(),
|
||||
body.clone(),
|
||||
)?;
|
||||
}
|
||||
}
|
||||
// Return void for declaration context
|
||||
let void_val = self.value_gen.next();
|
||||
self.emit_instruction(MirInstruction::Const {
|
||||
dst: void_val,
|
||||
value: ConstValue::Void,
|
||||
})?;
|
||||
Ok(void_val)
|
||||
} else {
|
||||
// Instance box: register type and lower instance methods/ctors as functions
|
||||
self.user_defined_boxes.insert(name.clone());
|
||||
self.build_box_declaration(
|
||||
name.clone(),
|
||||
|
||||
@ -131,15 +131,8 @@ pub(crate) fn execute_file_with_backend(runner: &NyashRunner, filename: &str) {
|
||||
}
|
||||
"vm" => {
|
||||
crate::cli_v!("🚀 Nyash VM Backend - Executing file: {} 🚀", filename);
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
{
|
||||
runner.execute_vm_mode(filename);
|
||||
}
|
||||
#[cfg(not(feature = "vm-legacy"))]
|
||||
{
|
||||
// Legacy VM is disabled; use PyVM harness instead.
|
||||
super::modes::pyvm::execute_pyvm_only(runner, filename);
|
||||
}
|
||||
// Prefer lightweight in-crate MIR interpreter as VM fallback
|
||||
runner.execute_vm_fallback_interpreter(filename);
|
||||
}
|
||||
#[cfg(feature = "cranelift-jit")]
|
||||
"jit-direct" => {
|
||||
|
||||
@ -193,7 +193,7 @@ impl NyashRunner {
|
||||
let verbose = crate::config::env::cli_verbose();
|
||||
let ctx = std::path::Path::new(filename).parent();
|
||||
for (ns, alias) in pending_using.iter() {
|
||||
let value = match resolve_using_target(ns, false, &using_ctx.pending_modules, &using_ctx.using_paths, &using_ctx.aliases, ctx, strict, verbose) {
|
||||
let value = match resolve_using_target(ns, false, &using_ctx.pending_modules, &using_ctx.using_paths, &using_ctx.aliases, &using_ctx.packages, ctx, strict, verbose) {
|
||||
Ok(v) => v,
|
||||
Err(e) => { eprintln!("❌ using: {}", e); std::process::exit(1); }
|
||||
};
|
||||
|
||||
@ -4,9 +4,16 @@
|
||||
#[allow(dead_code)]
|
||||
pub fn run_pyvm_harness(module: &crate::mir::MirModule, tag: &str) -> Result<i32, String> {
|
||||
let py3 = which::which("python3").map_err(|e| format!("python3 not found: {}", e))?;
|
||||
let runner = std::path::Path::new("tools/pyvm_runner.py");
|
||||
if !runner.exists() {
|
||||
return Err(format!("PyVM runner not found: {}", runner.display()));
|
||||
// Resolve runner path relative to CWD or NYASH_ROOT fallback
|
||||
let mut runner_buf = std::path::PathBuf::from("tools/pyvm_runner.py");
|
||||
if !runner_buf.exists() {
|
||||
if let Ok(root) = std::env::var("NYASH_ROOT") {
|
||||
let alt = std::path::Path::new(&root).join("tools/pyvm_runner.py");
|
||||
if alt.exists() { runner_buf = alt; }
|
||||
}
|
||||
}
|
||||
if !runner_buf.exists() {
|
||||
return Err(format!("PyVM runner not found: tools/pyvm_runner.py (cwd) or $NYASH_ROOT/tools/pyvm_runner.py"));
|
||||
}
|
||||
let tmp_dir = std::path::Path::new("tmp");
|
||||
let _ = std::fs::create_dir_all(tmp_dir);
|
||||
@ -38,7 +45,7 @@ pub fn run_pyvm_harness(module: &crate::mir::MirModule, tag: &str) -> Result<i32
|
||||
}
|
||||
let status = cmd
|
||||
.args([
|
||||
runner.to_string_lossy().as_ref(),
|
||||
runner_buf.to_string_lossy().as_ref(),
|
||||
"--in",
|
||||
&mir_json_path.display().to_string(),
|
||||
"--entry",
|
||||
@ -59,9 +66,15 @@ pub fn run_pyvm_harness(module: &crate::mir::MirModule, tag: &str) -> Result<i32
|
||||
#[allow(dead_code)]
|
||||
pub fn run_pyvm_harness_lib(module: &nyash_rust::mir::MirModule, tag: &str) -> Result<i32, String> {
|
||||
let py3 = which::which("python3").map_err(|e| format!("python3 not found: {}", e))?;
|
||||
let runner = std::path::Path::new("tools/pyvm_runner.py");
|
||||
if !runner.exists() {
|
||||
return Err(format!("PyVM runner not found: {}", runner.display()));
|
||||
let mut runner_buf = std::path::PathBuf::from("tools/pyvm_runner.py");
|
||||
if !runner_buf.exists() {
|
||||
if let Ok(root) = std::env::var("NYASH_ROOT") {
|
||||
let alt = std::path::Path::new(&root).join("tools/pyvm_runner.py");
|
||||
if alt.exists() { runner_buf = alt; }
|
||||
}
|
||||
}
|
||||
if !runner_buf.exists() {
|
||||
return Err(format!("PyVM runner not found: tools/pyvm_runner.py (cwd) or $NYASH_ROOT/tools/pyvm_runner.py"));
|
||||
}
|
||||
let tmp_dir = std::path::Path::new("tmp");
|
||||
let _ = std::fs::create_dir_all(tmp_dir);
|
||||
@ -91,7 +104,7 @@ pub fn run_pyvm_harness_lib(module: &nyash_rust::mir::MirModule, tag: &str) -> R
|
||||
}
|
||||
let status = cmd
|
||||
.args([
|
||||
runner.to_string_lossy().as_ref(),
|
||||
runner_buf.to_string_lossy().as_ref(),
|
||||
"--in",
|
||||
&mir_json_path.display().to_string(),
|
||||
"--entry",
|
||||
|
||||
@ -102,6 +102,8 @@ pub fn strip_using_and_register(
|
||||
if t.starts_with("using ") {
|
||||
crate::cli_v!("[using] stripped line: {}", line);
|
||||
let rest0 = t.strip_prefix("using ").unwrap().trim();
|
||||
// Strip trailing inline comments
|
||||
let rest0 = rest0.split('#').next().unwrap_or(rest0).trim();
|
||||
let rest0 = rest0.strip_suffix(';').unwrap_or(rest0).trim();
|
||||
let (target, alias) = if let Some(pos) = rest0.find(" as ") {
|
||||
(rest0[..pos].trim().to_string(), Some(rest0[pos + 4..].trim().to_string()))
|
||||
@ -158,6 +160,7 @@ pub fn strip_using_and_register(
|
||||
&using_ctx.pending_modules,
|
||||
&using_ctx.using_paths,
|
||||
&using_ctx.aliases,
|
||||
&using_ctx.packages,
|
||||
ctx_dir,
|
||||
strict,
|
||||
verbose,
|
||||
@ -174,6 +177,24 @@ pub fn strip_using_and_register(
|
||||
crate::runtime::modules_registry::set(alias.clone(), Box::new(sb));
|
||||
let sb2 = crate::box_trait::StringBox::new(value.clone());
|
||||
crate::runtime::modules_registry::set(ns.clone(), Box::new(sb2));
|
||||
// Optional: autoload dylib when using kind="dylib" and NYASH_USING_DYLIB_AUTOLOAD=1
|
||||
if value.starts_with("dylib:") && std::env::var("NYASH_USING_DYLIB_AUTOLOAD").ok().as_deref() == Some("1") {
|
||||
let lib_path = value.trim_start_matches("dylib:");
|
||||
// Derive lib name from file stem (strip leading 'lib')
|
||||
let p = std::path::Path::new(lib_path);
|
||||
if let Some(stem) = p.file_stem().and_then(|s| s.to_str()) {
|
||||
let mut lib_name = stem.to_string();
|
||||
if lib_name.starts_with("lib") { lib_name = lib_name.trim_start_matches("lib").to_string(); }
|
||||
// Determine box list from using packages (prefer [using.<ns>].bid)
|
||||
let mut boxes: Vec<String> = Vec::new();
|
||||
if let Some(pkg) = using_ctx.packages.get(&ns) {
|
||||
if let Some(b) = &pkg.bid { boxes.push(b.clone()); }
|
||||
}
|
||||
if verbose { eprintln!("[using] autoload dylib: {} as {} boxes=[{}]", lib_path, lib_name, boxes.join(",")); }
|
||||
let host = crate::runtime::plugin_loader_unified::get_global_plugin_host();
|
||||
let _ = host.read().unwrap().load_library_direct(&lib_name, lib_path, &boxes);
|
||||
}
|
||||
}
|
||||
} else if trace {
|
||||
eprintln!("[using] still unresolved: {} as {}", ns, alias);
|
||||
}
|
||||
@ -186,13 +207,31 @@ pub fn strip_using_and_register(
|
||||
&using_ctx.pending_modules,
|
||||
&using_ctx.using_paths,
|
||||
&using_ctx.aliases,
|
||||
&using_ctx.packages,
|
||||
ctx_dir,
|
||||
strict,
|
||||
verbose,
|
||||
) {
|
||||
Ok(value) => {
|
||||
let sb = crate::box_trait::StringBox::new(value.clone());
|
||||
crate::runtime::modules_registry::set(ns, Box::new(sb));
|
||||
let ns_clone = ns.clone();
|
||||
crate::runtime::modules_registry::set(ns_clone, Box::new(sb));
|
||||
// Optional: autoload dylib when using kind="dylib"
|
||||
if value.starts_with("dylib:") && std::env::var("NYASH_USING_DYLIB_AUTOLOAD").ok().as_deref() == Some("1") {
|
||||
let lib_path = value.trim_start_matches("dylib:");
|
||||
let p = std::path::Path::new(lib_path);
|
||||
if let Some(stem) = p.file_stem().and_then(|s| s.to_str()) {
|
||||
let mut lib_name = stem.to_string();
|
||||
if lib_name.starts_with("lib") { lib_name = lib_name.trim_start_matches("lib").to_string(); }
|
||||
let mut boxes: Vec<String> = Vec::new();
|
||||
if let Some(pkg) = using_ctx.packages.get(&ns) {
|
||||
if let Some(b) = &pkg.bid { boxes.push(b.clone()); }
|
||||
}
|
||||
if verbose { eprintln!("[using] autoload dylib: {} as {} boxes=[{}]", lib_path, lib_name, boxes.join(",")); }
|
||||
let host = crate::runtime::plugin_loader_unified::get_global_plugin_host();
|
||||
let _ = host.read().unwrap().load_library_direct(&lib_name, lib_path, &boxes);
|
||||
}
|
||||
}
|
||||
Some(value)
|
||||
}
|
||||
Err(e) => return Err(format!("using: {}", e)),
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
pub mod bench;
|
||||
pub mod llvm;
|
||||
pub mod mir;
|
||||
pub mod vm_fallback;
|
||||
#[cfg(feature = "vm-legacy")]
|
||||
pub mod vm;
|
||||
pub mod pyvm;
|
||||
|
||||
@ -182,10 +182,19 @@ impl NyashRunner {
|
||||
}
|
||||
|
||||
// Optional: PyVM path. When NYASH_VM_USE_PY=1, emit MIR(JSON) and delegate execution to tools/pyvm_runner.py
|
||||
// Safety valve: if runner is not found or fails to launch, gracefully fall back to Rust VM
|
||||
if std::env::var("NYASH_VM_USE_PY").ok().as_deref() == Some("1") {
|
||||
match super::common_util::pyvm::run_pyvm_harness_lib(&module_vm, "vm") {
|
||||
Ok(code) => { process::exit(code); }
|
||||
Err(e) => { eprintln!("❌ PyVM error: {}", e); process::exit(1); }
|
||||
Err(e) => {
|
||||
// Fallback unless explicitly required
|
||||
if std::env::var("NYASH_VM_REQUIRE_PY").ok().as_deref() == Some("1") {
|
||||
eprintln!("❌ PyVM error: {}", e);
|
||||
process::exit(1);
|
||||
} else {
|
||||
eprintln!("[vm] PyVM unavailable ({}). Falling back to Rust VM…", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
55
src/runner/modes/vm_fallback.rs
Normal file
55
src/runner/modes/vm_fallback.rs
Normal file
@ -0,0 +1,55 @@
|
||||
use super::super::NyashRunner;
|
||||
use crate::{parser::NyashParser, mir::MirCompiler, backend::MirInterpreter};
|
||||
use std::{fs, process};
|
||||
|
||||
impl NyashRunner {
|
||||
/// Lightweight VM fallback using the in-crate MIR interpreter.
|
||||
/// - Respects using preprocessing done earlier in the pipeline
|
||||
/// - Relies on global plugin host initialized by runner
|
||||
pub(crate) fn execute_vm_fallback_interpreter(&self, filename: &str) {
|
||||
// Read source
|
||||
let code = match fs::read_to_string(filename) {
|
||||
Ok(s) => s,
|
||||
Err(e) => { eprintln!("❌ Error reading file {}: {}", filename, e); process::exit(1); }
|
||||
};
|
||||
// Using preprocessing (strip + autoload)
|
||||
let mut code2 = code;
|
||||
if crate::config::env::enable_using() {
|
||||
match crate::runner::modes::common_util::resolve::strip_using_and_register(self, &code2, filename) {
|
||||
Ok(s) => { code2 = s; }
|
||||
Err(e) => { eprintln!("❌ {}", e); process::exit(1); }
|
||||
}
|
||||
}
|
||||
// Dev sugar pre-expand: @name = expr → local name = expr
|
||||
code2 = crate::runner::modes::common_util::resolve::preexpand_at_local(&code2);
|
||||
|
||||
// Parse -> expand macros -> compile MIR
|
||||
let ast = match NyashParser::parse_from_string(&code2) {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => { eprintln!("❌ Parse error: {}", e); process::exit(1); }
|
||||
};
|
||||
let ast = crate::r#macro::maybe_expand_and_dump(&ast, false);
|
||||
let mut compiler = MirCompiler::with_options(!self.config.no_optimize);
|
||||
let compile = match compiler.compile(ast) {
|
||||
Ok(c) => c,
|
||||
Err(e) => { eprintln!("❌ MIR compilation error: {}", e); process::exit(1); }
|
||||
};
|
||||
|
||||
// Optional barrier-elision for parity with VM path
|
||||
let mut module_vm = compile.module.clone();
|
||||
if std::env::var("NYASH_VM_ESCAPE_ANALYSIS").ok().as_deref() == Some("1") {
|
||||
let removed = crate::mir::passes::escape::escape_elide_barriers_vm(&mut module_vm);
|
||||
if removed > 0 { crate::cli_v!("[VM-fallback] escape_elide_barriers: removed {} barriers", removed); }
|
||||
}
|
||||
|
||||
// Execute via MIR interpreter
|
||||
let mut vm = MirInterpreter::new();
|
||||
match vm.execute_module(&module_vm) {
|
||||
Ok(_ret) => { /* interpreter already prints via println/console in program */ }
|
||||
Err(e) => {
|
||||
eprintln!("❌ VM fallback error: {}", e);
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,12 +9,14 @@
|
||||
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
use crate::using::spec::{UsingPackage, PackageKind};
|
||||
|
||||
/// Using/module resolution context accumulated from config/env/nyash.toml
|
||||
pub(super) struct UsingContext {
|
||||
pub using_paths: Vec<String>,
|
||||
pub pending_modules: Vec<(String, String)>,
|
||||
pub aliases: std::collections::HashMap<String, String>,
|
||||
pub packages: std::collections::HashMap<String, UsingPackage>,
|
||||
}
|
||||
|
||||
impl NyashRunner {
|
||||
@ -24,50 +26,19 @@ impl NyashRunner {
|
||||
let mut pending_modules: Vec<(String, String)> = Vec::new();
|
||||
let mut aliases: std::collections::HashMap<String, String> =
|
||||
std::collections::HashMap::new();
|
||||
let mut packages: std::collections::HashMap<String, UsingPackage> =
|
||||
std::collections::HashMap::new();
|
||||
|
||||
// Defaults
|
||||
using_paths.extend(["apps", "lib", "."].into_iter().map(|s| s.to_string()));
|
||||
|
||||
// nyash.toml: [modules] and [using.paths]
|
||||
if std::path::Path::new("nyash.toml").exists() {
|
||||
if let Ok(text) = std::fs::read_to_string("nyash.toml") {
|
||||
if let Ok(doc) = toml::from_str::<toml::Value>(&text) {
|
||||
if let Some(mods) = doc.get("modules").and_then(|v| v.as_table()) {
|
||||
fn visit(prefix: &str, tbl: &toml::value::Table, out: &mut Vec<(String, String)>) {
|
||||
for (k, v) in tbl.iter() {
|
||||
let name = if prefix.is_empty() { k.to_string() } else { format!("{}.{}", prefix, k) };
|
||||
if let Some(s) = v.as_str() {
|
||||
out.push((name, s.to_string()));
|
||||
} else if let Some(t) = v.as_table() {
|
||||
visit(&name, t, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
visit("", mods, &mut pending_modules);
|
||||
}
|
||||
if let Some(using_tbl) = doc.get("using").and_then(|v| v.as_table()) {
|
||||
if let Some(paths_arr) = using_tbl.get("paths").and_then(|v| v.as_array()) {
|
||||
for p in paths_arr {
|
||||
if let Some(s) = p.as_str() {
|
||||
let s = s.trim();
|
||||
if !s.is_empty() {
|
||||
using_paths.push(s.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Optional: [aliases] table maps short name -> path or namespace token
|
||||
if let Some(alias_tbl) = doc.get("aliases").and_then(|v| v.as_table()) {
|
||||
for (k, v) in alias_tbl.iter() {
|
||||
if let Some(target) = v.as_str() {
|
||||
aliases.insert(k.to_string(), target.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// nyash.toml: delegate to using resolver (keeps existing behavior)
|
||||
let _ = crate::using::resolver::populate_from_toml(
|
||||
&mut using_paths,
|
||||
&mut pending_modules,
|
||||
&mut aliases,
|
||||
&mut packages,
|
||||
);
|
||||
|
||||
// Env overrides: modules and using paths
|
||||
if let Ok(ms) = std::env::var("NYASH_MODULES") {
|
||||
@ -106,6 +77,7 @@ impl NyashRunner {
|
||||
using_paths,
|
||||
pending_modules,
|
||||
aliases,
|
||||
packages,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -152,6 +124,7 @@ pub(super) fn resolve_using_target(
|
||||
modules: &[(String, String)],
|
||||
using_paths: &[String],
|
||||
aliases: &HashMap<String, String>,
|
||||
packages: &HashMap<String, UsingPackage>,
|
||||
context_dir: Option<&std::path::Path>,
|
||||
strict: bool,
|
||||
verbose: bool,
|
||||
@ -207,13 +180,53 @@ pub(super) fn resolve_using_target(
|
||||
}
|
||||
return Ok(hit);
|
||||
}
|
||||
// Resolve aliases early (provided map)
|
||||
// Resolve aliases early (provided map) — and then recursively resolve the target
|
||||
if let Some(v) = aliases.get(tgt) {
|
||||
if trace {
|
||||
crate::runner::trace::log(format!("[using/resolve] alias '{}' -> '{}'", tgt, v));
|
||||
}
|
||||
crate::runner::box_index::cache_put(&key, v.clone());
|
||||
return Ok(v.clone());
|
||||
// Recurse to resolve the alias target into a concrete path/token
|
||||
let rec = resolve_using_target(v, false, modules, using_paths, aliases, packages, context_dir, strict, verbose)?;
|
||||
crate::runner::box_index::cache_put(&key, rec.clone());
|
||||
return Ok(rec);
|
||||
}
|
||||
// Named packages (nyash.toml [using.<name>])
|
||||
if let Some(pkg) = packages.get(tgt) {
|
||||
match pkg.kind {
|
||||
PackageKind::Dylib => {
|
||||
// Return a marker token to avoid inlining attempts; loader will consume later stages
|
||||
let out = format!("dylib:{}", pkg.path);
|
||||
if trace {
|
||||
crate::runner::trace::log(format!("[using/resolve] dylib '{}' -> '{}'", tgt, out));
|
||||
}
|
||||
crate::runner::box_index::cache_put(&key, out.clone());
|
||||
return Ok(out);
|
||||
}
|
||||
PackageKind::Package => {
|
||||
// Compute entry: main or <dir_last>.nyash
|
||||
let base = std::path::Path::new(&pkg.path);
|
||||
let out = if let Some(m) = &pkg.main {
|
||||
if base.extension().and_then(|s| s.to_str()) == Some("nyash") {
|
||||
// path is a file; ignore main and use as-is
|
||||
pkg.path.clone()
|
||||
} else {
|
||||
base.join(m).to_string_lossy().to_string()
|
||||
}
|
||||
} else {
|
||||
if base.extension().and_then(|s| s.to_str()) == Some("nyash") {
|
||||
pkg.path.clone()
|
||||
} else {
|
||||
let leaf = base.file_name().and_then(|s| s.to_str()).unwrap_or(tgt);
|
||||
base.join(format!("{}.nyash", leaf)).to_string_lossy().to_string()
|
||||
}
|
||||
};
|
||||
if trace {
|
||||
crate::runner::trace::log(format!("[using/resolve] package '{}' -> '{}'", tgt, out));
|
||||
}
|
||||
crate::runner::box_index::cache_put(&key, out.clone());
|
||||
return Ok(out);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Also consult env aliases
|
||||
if let Ok(raw) = std::env::var("NYASH_ALIASES") {
|
||||
@ -267,29 +280,20 @@ pub(super) fn resolve_using_target(
|
||||
}
|
||||
}
|
||||
if cand.is_empty() {
|
||||
// Always emit a concise unresolved note to aid diagnostics in smokes
|
||||
let leaf = tgt.split('.').last().unwrap_or(tgt);
|
||||
let mut cands: Vec<String> = Vec::new();
|
||||
suggest_in_base("apps", leaf, &mut cands);
|
||||
if cands.len() < 5 { suggest_in_base("lib", leaf, &mut cands); }
|
||||
if cands.len() < 5 { suggest_in_base(".", leaf, &mut cands); }
|
||||
if trace {
|
||||
// Try suggest candidates by leaf across bases (apps/lib/.)
|
||||
let leaf = tgt.split('.').last().unwrap_or(tgt);
|
||||
let mut cands: Vec<String> = Vec::new();
|
||||
suggest_in_base("apps", leaf, &mut cands);
|
||||
if cands.len() < 5 {
|
||||
suggest_in_base("lib", leaf, &mut cands);
|
||||
}
|
||||
if cands.len() < 5 {
|
||||
suggest_in_base(".", leaf, &mut cands);
|
||||
}
|
||||
if cands.is_empty() {
|
||||
crate::runner::trace::log(format!(
|
||||
"[using] unresolved '{}' (searched: rel+paths)",
|
||||
tgt
|
||||
));
|
||||
crate::runner::trace::log(format!("[using] unresolved '{}' (searched: rel+paths)", tgt));
|
||||
} else {
|
||||
crate::runner::trace::log(format!(
|
||||
"[using] unresolved '{}' (searched: rel+paths) candidates: {}",
|
||||
tgt,
|
||||
cands.join(", ")
|
||||
));
|
||||
crate::runner::trace::log(format!("[using] unresolved '{}' (searched: rel+paths) candidates: {}", tgt, cands.join(", ")));
|
||||
}
|
||||
} else {
|
||||
eprintln!("[using] not found: '{}'", tgt);
|
||||
}
|
||||
return Ok(tgt.to_string());
|
||||
}
|
||||
@ -485,6 +489,7 @@ boxes = ["ArrayBox"]
|
||||
&[],
|
||||
&[],
|
||||
&HashMap::new(),
|
||||
&std::collections::HashMap::<String, crate::using::spec::UsingPackage>::new(),
|
||||
None,
|
||||
false,
|
||||
false,
|
||||
|
||||
@ -24,3 +24,17 @@ pub fn get(name: &str) -> Option<Box<dyn NyashBox>> {
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Snapshot names and their stringified values (best‑effort).
|
||||
/// Intended for diagnostics; values are obtained via to_string_box().value.
|
||||
pub fn snapshot_names_and_strings() -> Vec<(String, String)> {
|
||||
let mut out = Vec::new();
|
||||
if let Ok(mut map) = REGISTRY.lock() {
|
||||
for (k, v) in map.iter_mut() {
|
||||
// Best-effort stringify
|
||||
let s = v.to_string_box().value;
|
||||
out.push((k.clone(), s));
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
@ -81,6 +81,48 @@ impl PluginHost {
|
||||
self.config.as_ref()
|
||||
}
|
||||
|
||||
/// Load a single library directly from path for `using kind="dylib"` autoload.
|
||||
/// Boxes list is best-effort (may be empty). When empty, TypeBox FFI is used to resolve metadata.
|
||||
pub fn load_library_direct(&self, lib_name: &str, path: &str, boxes: &[String]) -> BidResult<()> {
|
||||
let def = crate::config::nyash_toml_v2::LibraryDefinition {
|
||||
boxes: boxes.to_vec(),
|
||||
path: path.to_string(),
|
||||
};
|
||||
// Ensure loader has a minimal config so find_library_for_box works
|
||||
{
|
||||
let mut l = self.loader.write().unwrap();
|
||||
if l.config.is_none() {
|
||||
let mut cfg = NyashConfigV2 {
|
||||
libraries: std::collections::HashMap::new(),
|
||||
plugin_paths: crate::config::nyash_toml_v2::PluginPaths { search_paths: vec![] },
|
||||
plugins: std::collections::HashMap::new(),
|
||||
box_types: std::collections::HashMap::new(),
|
||||
};
|
||||
cfg.libraries.insert(lib_name.to_string(), crate::config::nyash_toml_v2::LibraryDefinition { boxes: def.boxes.clone(), path: def.path.clone() });
|
||||
l.config = Some(cfg);
|
||||
// No dedicated config file; keep config_path None and rely on box_specs fallback
|
||||
} else if let Some(cfg) = l.config.as_mut() {
|
||||
cfg.libraries.insert(lib_name.to_string(), crate::config::nyash_toml_v2::LibraryDefinition { boxes: def.boxes.clone(), path: def.path.clone() });
|
||||
}
|
||||
// Load the library now
|
||||
l.load_plugin_direct(lib_name, &def)?;
|
||||
// Ingest nyash_box.toml (if present) to populate box_specs: type_id/method ids
|
||||
let nyb_path = std::path::Path::new(path)
|
||||
.parent()
|
||||
.unwrap_or(std::path::Path::new("."))
|
||||
.join("nyash_box.toml");
|
||||
l.ingest_box_specs_from_nyash_box(lib_name, &def.boxes, &nyb_path);
|
||||
// Also register providers in the v2 BoxFactoryRegistry so `new BoxType()` works
|
||||
let registry = crate::runtime::get_global_registry();
|
||||
for bx in &def.boxes {
|
||||
registry.apply_plugin_config(&crate::runtime::PluginConfig {
|
||||
plugins: [(bx.clone(), lib_name.to_string())].into(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Resolve a method handle for a given plugin box type and method name.
|
||||
pub fn resolve_method(&self, box_type: &str, method_name: &str) -> BidResult<MethodHandle> {
|
||||
let cfg = self.config.as_ref().ok_or(BidError::PluginError)?;
|
||||
|
||||
@ -207,6 +207,12 @@ impl PluginLoaderV2 {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Public helper to load a single library definition directly (bypass nyash.toml sweep).
|
||||
/// Useful for `using kind="dylib"` autoload where only path and a few box names are known.
|
||||
pub fn load_plugin_direct(&self, lib_name: &str, lib_def: &LibraryDefinition) -> BidResult<()> {
|
||||
self.load_plugin(lib_name, lib_def)
|
||||
}
|
||||
|
||||
fn load_plugin_from_root(&self, _plugin_name: &str, _root: &str) -> BidResult<()> {
|
||||
Ok(())
|
||||
}
|
||||
@ -248,31 +254,39 @@ impl PluginLoaderV2 {
|
||||
|
||||
/// Lookup per-Box invoke function pointer for given type_id via loaded TypeBox specs
|
||||
pub fn box_invoke_fn_for_type_id(&self, type_id: u32) -> Option<BoxInvokeFn> {
|
||||
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 key = (lib_name.to_string(), box_type.to_string());
|
||||
let map = self.box_specs.read().ok()?;
|
||||
let spec = map.get(&key);
|
||||
if let Some(s) = spec {
|
||||
if s.invoke_id.is_none() && dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] WARN: no per-Box invoke for {}.{} (type_id={}). Calls will fail with E_PLUGIN (-5) until plugin migrates to v2.",
|
||||
lib_name, box_type, type_id
|
||||
);
|
||||
// First try config-based resolution
|
||||
if let (Some(config), Some(cfg_path)) = (self.config.as_ref(), self.config_path.as_ref()) {
|
||||
if let (Ok(toml_str), Ok(toml_value)) = (
|
||||
std::fs::read_to_string(cfg_path),
|
||||
toml::from_str::<toml::Value>(&std::fs::read_to_string(cfg_path).unwrap_or_default()),
|
||||
) {
|
||||
let _ = toml_str; // silence
|
||||
if let Some((lib_name, box_type)) = self.find_box_by_type_id(config, &toml_value, type_id) {
|
||||
let key = (lib_name.to_string(), box_type.to_string());
|
||||
let map = self.box_specs.read().ok()?;
|
||||
if let Some(s) = map.get(&key) {
|
||||
if s.invoke_id.is_none() && dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] WARN: no per-Box invoke for {}.{} (type_id={}). Calls will fail with E_PLUGIN (-5) until plugin migrates to v2.",
|
||||
lib_name, box_type, type_id
|
||||
);
|
||||
}
|
||||
return s.invoke_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
s.invoke_id
|
||||
} else {
|
||||
if dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] INFO: no TypeBox spec loaded for {}.{} (type_id={}).",
|
||||
lib_name, box_type, type_id
|
||||
);
|
||||
}
|
||||
None
|
||||
}
|
||||
// Fallback: scan box_specs for matching type_id (autoload path without central config)
|
||||
if let Ok(map) = self.box_specs.read() {
|
||||
for ((_lib, _bt), spec) in map.iter() {
|
||||
if let Some(tid) = spec.type_id {
|
||||
if tid == type_id {
|
||||
return spec.invoke_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn metadata_for_type_id(&self, type_id: u32) -> Option<PluginBoxMetadata> {
|
||||
@ -315,25 +329,51 @@ impl PluginLoaderV2 {
|
||||
})
|
||||
}
|
||||
|
||||
/// Resolve method_id for (box_type, method_name), consulting config first, then TypeBox resolve() if available.
|
||||
/// Resolve method_id for (box_type, method_name) with graceful fallback when central config is absent.
|
||||
pub(crate) fn resolve_method_id(&self, box_type: &str, method_name: &str) -> BidResult<u32> {
|
||||
use std::ffi::CString;
|
||||
let cfg = self.config.as_ref().ok_or(BidError::PluginError)?;
|
||||
let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml");
|
||||
let toml_value: toml::Value = super::errors::from_toml(toml::from_str(
|
||||
&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?,
|
||||
))?;
|
||||
// 1) config mapping
|
||||
if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) {
|
||||
if let Some(bc) = cfg.get_box_config(&lib_name, box_type, &toml_value) {
|
||||
if let Some(m) = bc.methods.get(method_name) {
|
||||
return Ok(m.method_id);
|
||||
if let Some(cfg) = self.config.as_ref() {
|
||||
let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml");
|
||||
let toml_value: toml::Value = super::errors::from_toml(toml::from_str(
|
||||
&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?,
|
||||
))?;
|
||||
if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) {
|
||||
if let Some(bc) = cfg.get_box_config(&lib_name, box_type, &toml_value) {
|
||||
if let Some(m) = bc.methods.get(method_name) {
|
||||
return Ok(m.method_id);
|
||||
}
|
||||
}
|
||||
let key = (lib_name.to_string(), box_type.to_string());
|
||||
if let Ok(mut map) = self.box_specs.write() {
|
||||
if let Some(spec) = map.get_mut(&key) {
|
||||
if let Some(ms) = spec.methods.get(method_name) {
|
||||
return Ok(ms.method_id);
|
||||
}
|
||||
if let Some(res_fn) = spec.resolve_fn {
|
||||
if let Ok(cstr) = CString::new(method_name) {
|
||||
let mid = res_fn(cstr.as_ptr());
|
||||
if mid != 0 {
|
||||
spec.methods.insert(
|
||||
method_name.to_string(),
|
||||
MethodSpec { method_id: mid, returns_result: false },
|
||||
);
|
||||
if dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] resolve(name) {}.{} -> id {}",
|
||||
box_type, method_name, mid
|
||||
);
|
||||
}
|
||||
return Ok(mid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 2) v2 TypeBox resolve (and cache)
|
||||
let key = (lib_name.to_string(), box_type.to_string());
|
||||
} else {
|
||||
// No config loaded: consult any spec for this box_type
|
||||
if let Ok(mut map) = self.box_specs.write() {
|
||||
if let Some(spec) = map.get_mut(&key) {
|
||||
if let Some((_, spec)) = map.iter_mut().find(|((_, bt), _)| bt == &box_type) {
|
||||
if let Some(ms) = spec.methods.get(method_name) {
|
||||
return Ok(ms.method_id);
|
||||
}
|
||||
@ -341,20 +381,10 @@ impl PluginLoaderV2 {
|
||||
if let Ok(cstr) = CString::new(method_name) {
|
||||
let mid = res_fn(cstr.as_ptr());
|
||||
if mid != 0 {
|
||||
// Cache minimal MethodSpec (returns_result unknown → false)
|
||||
spec.methods.insert(
|
||||
method_name.to_string(),
|
||||
MethodSpec {
|
||||
method_id: mid,
|
||||
returns_result: false,
|
||||
},
|
||||
MethodSpec { method_id: mid, returns_result: false },
|
||||
);
|
||||
if dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] resolve(name) {}.{} -> id {}",
|
||||
box_type, method_name, mid
|
||||
);
|
||||
}
|
||||
return Ok(mid);
|
||||
}
|
||||
}
|
||||
@ -412,6 +442,77 @@ impl PluginLoaderV2 {
|
||||
None
|
||||
}
|
||||
|
||||
/// Best-effort: ingest specs from nyash_box.toml for autoloaded plugins.
|
||||
pub fn ingest_box_specs_from_nyash_box(
|
||||
&self,
|
||||
lib_name: &str,
|
||||
box_names: &[String],
|
||||
nyash_box_toml_path: &std::path::Path,
|
||||
) {
|
||||
if !nyash_box_toml_path.exists() {
|
||||
return;
|
||||
}
|
||||
let Ok(text) = std::fs::read_to_string(nyash_box_toml_path) else { return; };
|
||||
let Ok(doc) = toml::from_str::<toml::Value>(&text) else { return; };
|
||||
if let Ok(mut map) = self.box_specs.write() {
|
||||
for box_type in box_names {
|
||||
let key = (lib_name.to_string(), box_type.to_string());
|
||||
let mut spec = map.get(&key).cloned().unwrap_or_default();
|
||||
// type_id
|
||||
if let Some(tid) = doc
|
||||
.get(box_type)
|
||||
.and_then(|v| v.get("type_id"))
|
||||
.and_then(|v| v.as_integer())
|
||||
{
|
||||
spec.type_id = Some(tid as u32);
|
||||
}
|
||||
// lifecycle.fini
|
||||
if let Some(fini) = doc
|
||||
.get(box_type)
|
||||
.and_then(|v| v.get("lifecycle"))
|
||||
.and_then(|v| v.get("fini"))
|
||||
.and_then(|v| v.get("id"))
|
||||
.and_then(|v| v.as_integer())
|
||||
{
|
||||
spec.fini_method_id = Some(fini as u32);
|
||||
}
|
||||
// lifecycle.birth (treat as method name "birth")
|
||||
if let Some(birth) = doc
|
||||
.get(box_type)
|
||||
.and_then(|v| v.get("lifecycle"))
|
||||
.and_then(|v| v.get("birth"))
|
||||
.and_then(|v| v.get("id"))
|
||||
.and_then(|v| v.as_integer())
|
||||
{
|
||||
spec.methods.insert(
|
||||
"birth".to_string(),
|
||||
MethodSpec { method_id: birth as u32, returns_result: false },
|
||||
);
|
||||
}
|
||||
// methods.*.id
|
||||
if let Some(methods) = doc
|
||||
.get(box_type)
|
||||
.and_then(|v| v.get("methods"))
|
||||
.and_then(|v| v.as_table())
|
||||
{
|
||||
for (mname, mdef) in methods.iter() {
|
||||
if let Some(id) = mdef
|
||||
.get("id")
|
||||
.and_then(|v| v.as_integer())
|
||||
.map(|x| x as u32)
|
||||
{
|
||||
spec.methods.insert(
|
||||
mname.to_string(),
|
||||
MethodSpec { method_id: id, returns_result: mdef.get("returns_result").and_then(|v| v.as_bool()).unwrap_or(false) },
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
map.insert(key, spec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_singleton_handle(&self, lib_name: &str, box_type: &str) -> BidResult<()> {
|
||||
if self
|
||||
.singletons
|
||||
@ -674,20 +775,35 @@ impl PluginLoaderV2 {
|
||||
instance_id: u32,
|
||||
args: &[Box<dyn NyashBox>],
|
||||
) -> BidResult<Option<Box<dyn NyashBox>>> {
|
||||
// Non-recursive direct bridge for minimal methods used by semantics and basic VM paths
|
||||
// Resolve library/type/method ids from cached config
|
||||
let cfg = self.config.as_ref().ok_or(BidError::PluginError)?;
|
||||
let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml");
|
||||
let toml_value: toml::Value =
|
||||
toml::from_str(&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?)
|
||||
.map_err(|_| BidError::PluginError)?;
|
||||
let (lib_name, _lib_def) = cfg
|
||||
.find_library_for_box(box_type)
|
||||
.ok_or(BidError::InvalidType)?;
|
||||
let box_conf = cfg
|
||||
.get_box_config(lib_name, box_type, &toml_value)
|
||||
.ok_or(BidError::InvalidType)?;
|
||||
let type_id = box_conf.type_id;
|
||||
// Resolve (lib_name, type_id) either from config or cached specs
|
||||
let (lib_name, type_id) = if let Some(cfg) = self.config.as_ref() {
|
||||
let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml");
|
||||
let toml_value: toml::Value =
|
||||
toml::from_str(&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?)
|
||||
.map_err(|_| BidError::PluginError)?;
|
||||
if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) {
|
||||
if let Some(bc) = cfg.get_box_config(lib_name, box_type, &toml_value) {
|
||||
(lib_name.to_string(), bc.type_id)
|
||||
} else {
|
||||
let key = (lib_name.to_string(), box_type.to_string());
|
||||
let map = self.box_specs.read().map_err(|_| BidError::PluginError)?;
|
||||
let tid = map
|
||||
.get(&key)
|
||||
.and_then(|s| s.type_id)
|
||||
.ok_or(BidError::InvalidType)?;
|
||||
(lib_name.to_string(), tid)
|
||||
}
|
||||
} else {
|
||||
return Err(BidError::InvalidType);
|
||||
}
|
||||
} else {
|
||||
let map = self.box_specs.read().map_err(|_| BidError::PluginError)?;
|
||||
if let Some(((lib, _), spec)) = map.iter().find(|((_, bt), _)| bt == box_type) {
|
||||
(lib.clone(), spec.type_id.ok_or(BidError::InvalidType)?)
|
||||
} else {
|
||||
return Err(BidError::InvalidType);
|
||||
}
|
||||
};
|
||||
// Resolve method id via config or TypeBox resolve()
|
||||
let method_id = match self.resolve_method_id(box_type, method_name) {
|
||||
Ok(mid) => mid,
|
||||
@ -703,9 +819,15 @@ impl PluginLoaderV2 {
|
||||
};
|
||||
// Get plugin handle
|
||||
let plugins = self.plugins.read().map_err(|_| BidError::PluginError)?;
|
||||
let _plugin = plugins.get(lib_name).ok_or(BidError::PluginError)?;
|
||||
let _plugin = plugins.get(&lib_name).ok_or(BidError::PluginError)?;
|
||||
// Encode TLV args via shared helper (numeric→string→toString)
|
||||
let tlv = crate::runtime::plugin_ffi_common::encode_args(args);
|
||||
if dbg_on() {
|
||||
eprintln!(
|
||||
"[PluginLoaderV2] call {}.{}: type_id={} method_id={} instance_id={}",
|
||||
box_type, method_name, type_id, method_id, instance_id
|
||||
);
|
||||
}
|
||||
let (_code, out_len, out) = super::host_bridge::invoke_alloc(
|
||||
super::super::nyash_plugin_invoke_v2_shim,
|
||||
type_id,
|
||||
@ -784,26 +906,48 @@ impl PluginLoaderV2 {
|
||||
_args: &[Box<dyn NyashBox>],
|
||||
) -> BidResult<Box<dyn NyashBox>> {
|
||||
// Non-recursive: directly call plugin 'birth' and construct PluginBoxV2
|
||||
let cfg = self.config.as_ref().ok_or(BidError::PluginError)?;
|
||||
let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml");
|
||||
let toml_value: toml::Value =
|
||||
toml::from_str(&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?)
|
||||
.map_err(|_| BidError::PluginError)?;
|
||||
let (lib_name, _) = cfg
|
||||
.find_library_for_box(box_type)
|
||||
.ok_or(BidError::InvalidType)?;
|
||||
// Try config mapping first (when available)
|
||||
let (mut type_id_opt, mut birth_id_opt, mut fini_id) = (None, None, None);
|
||||
if let Some(cfg) = self.config.as_ref() {
|
||||
let cfg_path = self.config_path.as_deref().unwrap_or("nyash.toml");
|
||||
let toml_value: toml::Value =
|
||||
toml::from_str(&std::fs::read_to_string(cfg_path).map_err(|_| BidError::PluginError)?)
|
||||
.map_err(|_| BidError::PluginError)?;
|
||||
if let Some((lib_name, _)) = cfg.find_library_for_box(box_type) {
|
||||
if let Some(box_conf) = cfg.get_box_config(lib_name, box_type, &toml_value) {
|
||||
type_id_opt = Some(box_conf.type_id);
|
||||
birth_id_opt = box_conf.methods.get("birth").map(|m| m.method_id);
|
||||
fini_id = box_conf.methods.get("fini").map(|m| m.method_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve type_id and method ids
|
||||
let box_conf = cfg
|
||||
.get_box_config(lib_name, box_type, &toml_value)
|
||||
.ok_or(BidError::InvalidType)?;
|
||||
let type_id = box_conf.type_id;
|
||||
let birth_id = box_conf
|
||||
.methods
|
||||
.get("birth")
|
||||
.map(|m| m.method_id)
|
||||
.ok_or(BidError::InvalidMethod)?;
|
||||
let fini_id = box_conf.methods.get("fini").map(|m| m.method_id);
|
||||
// Fallback: use TypeBox FFI spec if config is missing for this box
|
||||
if type_id_opt.is_none() || birth_id_opt.is_none() {
|
||||
if let Ok(map) = self.box_specs.read() {
|
||||
// Find any spec that matches this box_type
|
||||
if let Some((_, spec)) = map.iter().find(|((_lib, bt), _)| bt == &box_type) {
|
||||
if type_id_opt.is_none() {
|
||||
type_id_opt = spec.type_id;
|
||||
}
|
||||
if birth_id_opt.is_none() {
|
||||
if let Some(ms) = spec.methods.get("birth") {
|
||||
birth_id_opt = Some(ms.method_id);
|
||||
} else if let Some(res_fn) = spec.resolve_fn {
|
||||
if let Ok(cstr) = std::ffi::CString::new("birth") {
|
||||
let mid = res_fn(cstr.as_ptr());
|
||||
if mid != 0 {
|
||||
birth_id_opt = Some(mid);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let type_id = type_id_opt.ok_or(BidError::InvalidType)?;
|
||||
let birth_id = birth_id_opt.ok_or(BidError::InvalidMethod)?;
|
||||
|
||||
// Get loaded plugin invoke
|
||||
let _plugins = self.plugins.read().map_err(|_| BidError::PluginError)?;
|
||||
|
||||
10
src/using/errors.rs
Normal file
10
src/using/errors.rs
Normal file
@ -0,0 +1,10 @@
|
||||
//! Error helpers for using resolver (placeholder)
|
||||
|
||||
#[derive(thiserror::Error, Debug)]
|
||||
pub enum UsingError {
|
||||
#[error("failed to read nyash.toml: {0}")]
|
||||
ReadToml(String),
|
||||
#[error("invalid nyash.toml format: {0}")]
|
||||
ParseToml(String),
|
||||
}
|
||||
|
||||
19
src/using/mod.rs
Normal file
19
src/using/mod.rs
Normal file
@ -0,0 +1,19 @@
|
||||
/*!\
|
||||
Using system — resolution scaffolding (Phase 15 skeleton)\
|
||||
\
|
||||
Centralizes name/path resolution for `using` statements.\
|
||||
This initial cut only reads nyash.toml to populate:\
|
||||
- [using.paths] → search roots for source lookups\
|
||||
- [modules] → logical name → file path mapping\
|
||||
- [aliases] → convenience alias mapping (optional)\
|
||||
\
|
||||
The goal is to keep runner/pipeline lean by delegating nyash.toml parsing here,\
|
||||
without changing default behavior. Future work will add: file/DLL specs, policies,\
|
||||
and plugin metadata fusion (nyash_box.toml / embedded BID).\
|
||||
*/
|
||||
|
||||
pub mod resolver;
|
||||
pub mod spec;
|
||||
pub mod policy;
|
||||
pub mod errors;
|
||||
pub mod simple_registry;
|
||||
7
src/using/policy.rs
Normal file
7
src/using/policy.rs
Normal file
@ -0,0 +1,7 @@
|
||||
//! Using policy (roots/search paths and toggles) — skeleton
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct UsingPolicy {
|
||||
pub search_paths: Vec<String>, // from [using.paths]
|
||||
}
|
||||
|
||||
90
src/using/resolver.rs
Normal file
90
src/using/resolver.rs
Normal file
@ -0,0 +1,90 @@
|
||||
use crate::using::errors::UsingError;
|
||||
use crate::using::policy::UsingPolicy;
|
||||
use crate::using::spec::{PackageKind, UsingPackage};
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// Populate using context vectors from nyash.toml (if present).
|
||||
/// Keeps behavior aligned with existing runner pipeline:
|
||||
/// - Adds [using.paths] entries to `using_paths`
|
||||
/// - Flattens [modules] into (name, path) pairs appended to `pending_modules`
|
||||
/// - Reads optional [aliases] table (k -> v)
|
||||
pub fn populate_from_toml(
|
||||
using_paths: &mut Vec<String>,
|
||||
pending_modules: &mut Vec<(String, String)>,
|
||||
aliases: &mut HashMap<String, String>,
|
||||
packages: &mut HashMap<String, UsingPackage>,
|
||||
) -> Result<UsingPolicy, UsingError> {
|
||||
let mut policy = UsingPolicy::default();
|
||||
let path = std::path::Path::new("nyash.toml");
|
||||
if !path.exists() {
|
||||
return Ok(policy);
|
||||
}
|
||||
let text = std::fs::read_to_string(path)
|
||||
.map_err(|e| UsingError::ReadToml(e.to_string()))?;
|
||||
let doc = toml::from_str::<toml::Value>(&text)
|
||||
.map_err(|e| UsingError::ParseToml(e.to_string()))?;
|
||||
|
||||
// [modules] table flatten: supports nested namespaces (a.b.c = "path")
|
||||
if let Some(mods) = doc.get("modules").and_then(|v| v.as_table()) {
|
||||
fn visit(prefix: &str, tbl: &toml::value::Table, out: &mut Vec<(String, String)>) {
|
||||
for (k, v) in tbl.iter() {
|
||||
let name = if prefix.is_empty() { k.to_string() } else { format!("{}.{}", prefix, k) };
|
||||
if let Some(s) = v.as_str() {
|
||||
out.push((name, s.to_string()));
|
||||
} else if let Some(t) = v.as_table() {
|
||||
visit(&name, t, out);
|
||||
}
|
||||
}
|
||||
}
|
||||
visit("", mods, pending_modules);
|
||||
}
|
||||
|
||||
// [using.paths] array
|
||||
if let Some(using_tbl) = doc.get("using").and_then(|v| v.as_table()) {
|
||||
// paths
|
||||
if let Some(paths_arr) = using_tbl.get("paths").and_then(|v| v.as_array()) {
|
||||
for p in paths_arr {
|
||||
if let Some(s) = p.as_str() {
|
||||
let s = s.trim();
|
||||
if !s.is_empty() {
|
||||
using_paths.push(s.to_string());
|
||||
policy.search_paths.push(s.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// aliases
|
||||
if let Some(alias_tbl) = using_tbl.get("aliases").and_then(|v| v.as_table()) {
|
||||
for (k, v) in alias_tbl.iter() {
|
||||
if let Some(target) = v.as_str() {
|
||||
aliases.insert(k.to_string(), target.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
// named packages: any subtable not paths/aliases is a package
|
||||
for (k, v) in using_tbl.iter() {
|
||||
if k == "paths" || k == "aliases" { continue; }
|
||||
if let Some(tbl) = v.as_table() {
|
||||
let kind = tbl.get("kind").and_then(|x| x.as_str()).map(PackageKind::from_str).unwrap_or(PackageKind::Package);
|
||||
// path is required
|
||||
if let Some(path_s) = tbl.get("path").and_then(|x| x.as_str()) {
|
||||
let path = path_s.to_string();
|
||||
let main = tbl.get("main").and_then(|x| x.as_str()).map(|s| s.to_string());
|
||||
let bid = tbl.get("bid").and_then(|x| x.as_str()).map(|s| s.to_string());
|
||||
packages.insert(k.to_string(), UsingPackage { kind, path, main, bid });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// legacy top-level [aliases] also accepted (migration)
|
||||
if let Some(alias_tbl) = doc.get("aliases").and_then(|v| v.as_table()) {
|
||||
for (k, v) in alias_tbl.iter() {
|
||||
if let Some(target) = v.as_str() {
|
||||
aliases.insert(k.to_string(), target.to_string());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(policy)
|
||||
}
|
||||
71
src/using/simple_registry.rs
Normal file
71
src/using/simple_registry.rs
Normal file
@ -0,0 +1,71 @@
|
||||
//! Simple ModuleRegistry for Phase 1 diagnostics
|
||||
//! Collects published symbols (top-level `static box Name`) from using targets.
|
||||
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use once_cell::sync::Lazy;
|
||||
use std::sync::Mutex;
|
||||
|
||||
static CACHE: Lazy<Mutex<HashMap<String, HashSet<String>>>> =
|
||||
Lazy::new(|| Mutex::new(HashMap::new()));
|
||||
|
||||
/// Return candidate using names whose exported symbols contain `symbol`.
|
||||
/// Uses runtime::modules_registry snapshot (name -> path token) and scans files.
|
||||
pub fn suggest_using_for_symbol(symbol: &str) -> Vec<String> {
|
||||
let mut results: Vec<String> = Vec::new();
|
||||
let snap = crate::runtime::modules_registry::snapshot_names_and_strings();
|
||||
let wanted = symbol.trim();
|
||||
if wanted.is_empty() { return results; }
|
||||
|
||||
for (name, path_token) in snap {
|
||||
// Skip builtin/dylib marker tokens
|
||||
if path_token.starts_with("builtin:") || path_token.starts_with("dylib:") {
|
||||
continue;
|
||||
}
|
||||
// Ensure cache for this key
|
||||
let mut guard = CACHE.lock().ok();
|
||||
let set = guard
|
||||
.as_mut()
|
||||
.map(|m| m.entry(name.clone()).or_insert_with(HashSet::new))
|
||||
.expect("module cache poisoned");
|
||||
if set.is_empty() {
|
||||
if let Some(p) = resolve_path(&path_token) {
|
||||
if let Ok(content) = std::fs::read_to_string(&p) {
|
||||
let syms = scan_static_boxes(&content);
|
||||
for s in syms { set.insert(s); }
|
||||
}
|
||||
}
|
||||
}
|
||||
if set.contains(wanted) {
|
||||
results.push(name);
|
||||
}
|
||||
}
|
||||
results.sort();
|
||||
results.dedup();
|
||||
results
|
||||
}
|
||||
|
||||
fn resolve_path(token: &str) -> Option<std::path::PathBuf> {
|
||||
let mut p = std::path::PathBuf::from(token);
|
||||
if p.is_relative() {
|
||||
if let Ok(abs) = std::fs::canonicalize(&p) { p = abs; }
|
||||
}
|
||||
if p.exists() { Some(p) } else { None }
|
||||
}
|
||||
|
||||
fn scan_static_boxes(content: &str) -> Vec<String> {
|
||||
// Very simple lexer: find lines like `static box Name {`
|
||||
// Avoid matching inside comments by skipping lines that start with //
|
||||
let mut out = Vec::new();
|
||||
for line in content.lines() {
|
||||
let t = line.trim_start();
|
||||
if t.starts_with("//") { continue; }
|
||||
if let Some(rest) = t.strip_prefix("static box ") {
|
||||
let mut name = String::new();
|
||||
for ch in rest.chars() {
|
||||
if ch.is_ascii_alphanumeric() || ch == '_' { name.push(ch); } else { break; }
|
||||
}
|
||||
if !name.is_empty() { out.push(name); }
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
42
src/using/spec.rs
Normal file
42
src/using/spec.rs
Normal file
@ -0,0 +1,42 @@
|
||||
//! Using specification models (skeleton)
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum UsingTarget {
|
||||
/// Logical package name (to be resolved via nyash.toml)
|
||||
Package(String),
|
||||
/// Source file path (absolute or relative)
|
||||
SourcePath(String),
|
||||
/// Dynamic library path (plugin)
|
||||
DylibPath(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UsingSpec {
|
||||
pub target: UsingTarget,
|
||||
pub alias: Option<String>,
|
||||
pub expose: Option<Vec<String>>, // planned
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum PackageKind {
|
||||
Package,
|
||||
Dylib,
|
||||
}
|
||||
|
||||
impl PackageKind {
|
||||
pub fn from_str(s: &str) -> Self {
|
||||
match s {
|
||||
"dylib" => PackageKind::Dylib,
|
||||
_ => PackageKind::Package,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UsingPackage {
|
||||
pub kind: PackageKind,
|
||||
pub path: String,
|
||||
pub main: Option<String>,
|
||||
pub bid: Option<String>,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user