Extern registry slot+arity; extern trace; vtable Array/String tests
This commit is contained in:
@ -630,6 +630,14 @@ impl VM {
|
||||
nyash_args.push(arg_value.to_nyash_box());
|
||||
}
|
||||
|
||||
// Optional trace
|
||||
if crate::config::env::extern_trace() {
|
||||
if let Some(slot) = crate::runtime::extern_registry::resolve_slot(iface_name, method_name) {
|
||||
eprintln!("[EXT] call {}.{} slot={} argc={}", iface_name, method_name, slot, nyash_args.len());
|
||||
} else {
|
||||
eprintln!("[EXT] call {}.{} argc={}", iface_name, method_name, nyash_args.len());
|
||||
}
|
||||
}
|
||||
// Route through unified plugin host (delegates to v2, handles env.* stubs)
|
||||
let host = crate::runtime::get_global_plugin_host();
|
||||
let host = host.read().map_err(|_| VMError::InvalidInstruction("Plugin host lock poisoned".into()))?;
|
||||
@ -650,6 +658,10 @@ impl VM {
|
||||
let mut msg = String::new();
|
||||
if strict { msg.push_str("ExternCall STRICT: unregistered or unsupported call "); } else { msg.push_str("ExternCall failed: "); }
|
||||
msg.push_str(&format!("{}.{}", iface_name, method_name));
|
||||
// Arity check when known
|
||||
if let Err(detail) = crate::runtime::extern_registry::check_arity(iface_name, method_name, nyash_args.len()) {
|
||||
msg.push_str(&format!(" ({})", detail));
|
||||
}
|
||||
if let Some(spec) = crate::runtime::extern_registry::resolve(iface_name, method_name) {
|
||||
msg.push_str(&format!(" (expected arity {}..{})", spec.min_arity, spec.max_arity));
|
||||
} else {
|
||||
|
||||
@ -125,3 +125,4 @@ pub fn abi_strict() -> bool { std::env::var("NYASH_ABI_STRICT").ok().as_deref()
|
||||
|
||||
// ---- ExternCall strict diagnostics ----
|
||||
pub fn extern_strict() -> bool { std::env::var("NYASH_EXTERN_STRICT").ok().as_deref() == Some("1") }
|
||||
pub fn extern_trace() -> bool { std::env::var("NYASH_EXTERN_TRACE").ok().as_deref() == Some("1") }
|
||||
|
||||
@ -5,20 +5,26 @@
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct ExternSpec { pub iface: &'static str, pub method: &'static str, pub min_arity: u8, pub max_arity: u8 }
|
||||
pub struct ExternSpec {
|
||||
pub iface: &'static str,
|
||||
pub method: &'static str,
|
||||
pub min_arity: u8,
|
||||
pub max_arity: u8,
|
||||
pub slot: Option<u16>,
|
||||
}
|
||||
|
||||
static EXTERNS: Lazy<Vec<ExternSpec>> = Lazy::new(|| vec![
|
||||
// console
|
||||
ExternSpec { iface: "env.console", method: "log", min_arity: 1, max_arity: 1 },
|
||||
ExternSpec { iface: "env.console", method: "log", min_arity: 1, max_arity: 1, slot: Some(10) },
|
||||
// debug
|
||||
ExternSpec { iface: "env.debug", method: "trace", min_arity: 1, max_arity: 255 },
|
||||
ExternSpec { iface: "env.debug", method: "trace", min_arity: 1, max_arity: 255, slot: Some(11) },
|
||||
// runtime
|
||||
ExternSpec { iface: "env.runtime", method: "checkpoint", min_arity: 0, max_arity: 0 },
|
||||
ExternSpec { iface: "env.runtime", method: "checkpoint", min_arity: 0, max_arity: 0, slot: Some(12) },
|
||||
// future (scaffold)
|
||||
ExternSpec { iface: "env.future", method: "new", min_arity: 1, max_arity: 1 },
|
||||
ExternSpec { iface: "env.future", method: "birth", min_arity: 1, max_arity: 1 },
|
||||
ExternSpec { iface: "env.future", method: "set", min_arity: 2, max_arity: 2 },
|
||||
ExternSpec { iface: "env.future", method: "await", min_arity: 1, max_arity: 1 },
|
||||
ExternSpec { iface: "env.future", method: "new", min_arity: 1, max_arity: 1, slot: Some(20) },
|
||||
ExternSpec { iface: "env.future", method: "birth", min_arity: 1, max_arity: 1, slot: Some(20) },
|
||||
ExternSpec { iface: "env.future", method: "set", min_arity: 2, max_arity: 2, slot: Some(21) },
|
||||
ExternSpec { iface: "env.future", method: "await", min_arity: 1, max_arity: 1, slot: Some(22) },
|
||||
]);
|
||||
|
||||
pub fn resolve(iface: &str, method: &str) -> Option<ExternSpec> {
|
||||
@ -35,3 +41,15 @@ pub fn all_ifaces() -> Vec<&'static str> {
|
||||
v.sort(); v.dedup(); v
|
||||
}
|
||||
|
||||
/// Resolve slot id for an extern call (if assigned)
|
||||
pub fn resolve_slot(iface: &str, method: &str) -> Option<u16> {
|
||||
resolve(iface, method).and_then(|s| s.slot)
|
||||
}
|
||||
|
||||
/// Check arity against registry; returns Ok or an explanatory error string
|
||||
pub fn check_arity(iface: &str, method: &str, argc: usize) -> Result<(), String> {
|
||||
if let Some(s) = resolve(iface, method) {
|
||||
if argc as u8 >= s.min_arity && argc as u8 <= s.max_arity { Ok(()) }
|
||||
else { Err(format!("arity {} out of range {}..{}", argc, s.min_arity, s.max_arity)) }
|
||||
} else { Err("unknown extern".to_string()) }
|
||||
}
|
||||
|
||||
45
src/tests/vtable_array_string.rs
Normal file
45
src/tests/vtable_array_string.rs
Normal file
@ -0,0 +1,45 @@
|
||||
#[test]
|
||||
fn vtable_array_and_string_len_get_set() {
|
||||
use crate::backend::vm::VM;
|
||||
use crate::mir::{MirModule, MirFunction, FunctionSignature, MirInstruction, EffectMask, BasicBlockId, ConstValue, MirType};
|
||||
std::env::set_var("NYASH_ABI_VTABLE", "1");
|
||||
|
||||
// Array: set(0, "x"); len(); get(0)
|
||||
let sig = FunctionSignature { name: "main".into(), params: vec![], return_type: MirType::String, effects: EffectMask::PURE };
|
||||
let mut f = MirFunction::new(sig, BasicBlockId::new(0));
|
||||
let bb = f.entry_block;
|
||||
let arr = f.next_value_id();
|
||||
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::NewBox { dst: arr, box_type: "ArrayBox".into(), args: vec![] });
|
||||
let idx0 = f.next_value_id();
|
||||
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: idx0, value: ConstValue::Integer(0) });
|
||||
let sval = f.next_value_id();
|
||||
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Const { dst: sval, value: ConstValue::String("x".into()) });
|
||||
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BoxCall { dst: None, box_val: arr, method: "set".into(), args: vec![idx0, sval], method_id: None, effects: EffectMask::PURE });
|
||||
let lenv = f.next_value_id();
|
||||
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BoxCall { dst: Some(lenv), box_val: arr, method: "len".into(), args: vec![], method_id: None, effects: EffectMask::PURE });
|
||||
// sanity: len should be 1 (not asserted here, just exercise path)
|
||||
let got = f.next_value_id();
|
||||
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::BoxCall { dst: Some(got), box_val: arr, method: "get".into(), args: vec![idx0], method_id: None, effects: EffectMask::PURE });
|
||||
f.get_block_mut(bb).unwrap().add_instruction(MirInstruction::Return { value: Some(got) });
|
||||
let mut m = MirModule::new("tarr".into()); m.add_function(f);
|
||||
let mut vm = VM::new();
|
||||
let out = vm.execute_module(&m).expect("vm exec");
|
||||
assert_eq!(out.to_string_box().value, "x");
|
||||
|
||||
// String: len()
|
||||
let sig2 = FunctionSignature { name: "main".into(), params: vec![], return_type: MirType::Integer, effects: EffectMask::PURE };
|
||||
let mut f2 = MirFunction::new(sig2, BasicBlockId::new(0));
|
||||
let bb2 = f2.entry_block;
|
||||
let s = f2.next_value_id();
|
||||
f2.get_block_mut(bb2).unwrap().add_instruction(MirInstruction::Const { dst: s, value: ConstValue::String("abc".into()) });
|
||||
let sb = f2.next_value_id();
|
||||
f2.get_block_mut(bb2).unwrap().add_instruction(MirInstruction::NewBox { dst: sb, box_type: "StringBox".into(), args: vec![s] });
|
||||
let ln = f2.next_value_id();
|
||||
f2.get_block_mut(bb2).unwrap().add_instruction(MirInstruction::BoxCall { dst: Some(ln), box_val: sb, method: "len".into(), args: vec![], method_id: None, effects: EffectMask::PURE });
|
||||
f2.get_block_mut(bb2).unwrap().add_instruction(MirInstruction::Return { value: Some(ln) });
|
||||
let mut m2 = MirModule::new("tstr".into()); m2.add_function(f2);
|
||||
let mut vm2 = VM::new();
|
||||
let out2 = vm2.execute_module(&m2).expect("vm exec");
|
||||
assert_eq!(out2.to_string_box().value, "3");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user