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:
Selfhosting Dev
2025-09-24 21:45:27 +09:00
parent 6755d9bde1
commit c0978634d9
150 changed files with 2119 additions and 3214 deletions

View File

@ -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.*

View File

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

View File

@ -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"]

View File

@ -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()
}
}

View File

@ -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]) {

View File

@ -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(),

View File

@ -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" => {

View File

@ -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); }
};

View File

@ -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",

View File

@ -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)),

View File

@ -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;

View File

@ -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);
}
}
}
}

View 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);
}
}
}
}

View File

@ -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,

View File

@ -24,3 +24,17 @@ pub fn get(name: &str) -> Option<Box<dyn NyashBox>> {
}
None
}
/// Snapshot names and their stringified values (besteffort).
/// 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
}

View File

@ -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)?;

View File

@ -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
View 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
View 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
View 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
View 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)
}

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