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

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