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

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