chore: Phase 25.1 完了 - LoopForm v2/Stage1 CLI/環境変数削減 + Phase 26-D からの変更
Phase 25.1 完了成果: - ✅ LoopForm v2 テスト・ドキュメント・コメント完備 - 4ケース(A/B/C/D)完全テストカバレッジ - 最小再現ケース作成(SSAバグ調査用) - SSOT文書作成(loopform_ssot.md) - 全ソースに [LoopForm] コメントタグ追加 - ✅ Stage-1 CLI デバッグ環境構築 - stage1_cli.hako 実装 - stage1_bridge.rs ブリッジ実装 - デバッグツール作成(stage1_debug.sh/stage1_minimal.sh) - アーキテクチャ改善提案文書 - ✅ 環境変数削減計画策定 - 25変数の完全調査・分類 - 6段階削減ロードマップ(25→5、80%削減) - 即時削除可能変数特定(NYASH_CONFIG/NYASH_DEBUG) Phase 26-D からの累積変更: - PHI実装改善(ExitPhiBuilder/HeaderPhiBuilder等) - MIRビルダーリファクタリング - 型伝播・最適化パス改善 - その他約300ファイルの累積変更 🎯 技術的成果: - SSAバグ根本原因特定(条件分岐内loop変数変更) - Region+next_iパターン適用完了(UsingCollectorBox等) - LoopFormパターン文書化・テスト化完了 - セルフホスティング基盤強化 Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: ChatGPT <noreply@openai.com> Co-Authored-By: Task Assistant <task@anthropic.com>
This commit is contained in:
@ -20,9 +20,7 @@ impl MirInterpreter {
|
||||
if self.call_depth > MAX_CALL_DEPTH {
|
||||
eprintln!(
|
||||
"[vm-call-depth] exceeded {} in fn={} (depth={})",
|
||||
MAX_CALL_DEPTH,
|
||||
func.signature.name,
|
||||
self.call_depth
|
||||
MAX_CALL_DEPTH, func.signature.name, self.call_depth
|
||||
);
|
||||
self.call_depth = self.call_depth.saturating_sub(1);
|
||||
return Err(VMError::InvalidInstruction(format!(
|
||||
@ -31,7 +29,9 @@ impl MirInterpreter {
|
||||
)));
|
||||
}
|
||||
// Phase 1: delegate cross-class reroute / narrow fallbacks to method_router
|
||||
if let Some(r) = super::method_router::pre_exec_reroute(self, func, arg_vals) { return r; }
|
||||
if let Some(r) = super::method_router::pre_exec_reroute(self, func, arg_vals) {
|
||||
return r;
|
||||
}
|
||||
let saved_regs = mem::take(&mut self.regs);
|
||||
let saved_fn = self.cur_fn.clone();
|
||||
self.cur_fn = Some(func.signature.name.clone());
|
||||
@ -59,7 +59,11 @@ impl MirInterpreter {
|
||||
let max_steps: u64 = std::env::var("HAKO_VM_MAX_STEPS")
|
||||
.ok()
|
||||
.and_then(|v| v.parse::<u64>().ok())
|
||||
.or_else(|| std::env::var("NYASH_VM_MAX_STEPS").ok().and_then(|v| v.parse::<u64>().ok()))
|
||||
.or_else(|| {
|
||||
std::env::var("NYASH_VM_MAX_STEPS")
|
||||
.ok()
|
||||
.and_then(|v| v.parse::<u64>().ok())
|
||||
})
|
||||
.unwrap_or(1_000_000);
|
||||
let mut steps: u64 = 0;
|
||||
|
||||
@ -133,9 +137,7 @@ impl MirInterpreter {
|
||||
if trace_phi {
|
||||
eprintln!(
|
||||
"[vm-trace-phi] enter bb={:?} last_pred={:?} preds={:?}",
|
||||
block.id,
|
||||
last_pred,
|
||||
block.predecessors
|
||||
block.id, last_pred, block.predecessors
|
||||
);
|
||||
}
|
||||
for inst in block.phi_instructions() {
|
||||
@ -154,7 +156,11 @@ impl MirInterpreter {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
// Dev safety valve: tolerate undefined phi inputs by substituting Void
|
||||
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED").ok().as_deref() == Some("1") {
|
||||
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
if Self::trace_enabled() {
|
||||
eprintln!("[vm-trace] phi tolerate undefined input {:?} -> Void (err={:?})", val, e);
|
||||
}
|
||||
@ -179,17 +185,41 @@ impl MirInterpreter {
|
||||
// Strict PHI (default ON). Can be disabled with HAKO_VM_PHI_STRICT=0 (or NYASH_VM_PHI_STRICT=0).
|
||||
let strict = {
|
||||
let on = |s: &str| {
|
||||
matches!(s, "1"|"true"|"on"|"yes"|"y"|"t"|"enabled"|"enable")
|
||||
matches!(
|
||||
s,
|
||||
"1" | "true" | "on" | "yes" | "y" | "t" | "enabled" | "enable"
|
||||
)
|
||||
};
|
||||
let off = |s: &str| {
|
||||
matches!(s, "0"|"false"|"off"|"no"|"n"|"f"|"disabled"|"disable")
|
||||
matches!(
|
||||
s,
|
||||
"0" | "false"
|
||||
| "off"
|
||||
| "no"
|
||||
| "n"
|
||||
| "f"
|
||||
| "disabled"
|
||||
| "disable"
|
||||
)
|
||||
};
|
||||
if let Ok(v) = std::env::var("HAKO_VM_PHI_STRICT") {
|
||||
let v = v.to_ascii_lowercase();
|
||||
if off(&v) { false } else if on(&v) { true } else { true }
|
||||
if off(&v) {
|
||||
false
|
||||
} else if on(&v) {
|
||||
true
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else if let Ok(v) = std::env::var("NYASH_VM_PHI_STRICT") {
|
||||
let v = v.to_ascii_lowercase();
|
||||
if off(&v) { false } else if on(&v) { true } else { true }
|
||||
if off(&v) {
|
||||
false
|
||||
} else if on(&v) {
|
||||
true
|
||||
} else {
|
||||
true
|
||||
}
|
||||
} else {
|
||||
true
|
||||
}
|
||||
@ -213,7 +243,11 @@ impl MirInterpreter {
|
||||
let v = match self.reg_load(*val0) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED").ok().as_deref() == Some("1") {
|
||||
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
if Self::trace_enabled() {
|
||||
eprintln!("[vm-trace] phi missing pred match {:?}; fallback first input {:?} -> Void (err={:?})", pred, val0, e);
|
||||
}
|
||||
@ -243,7 +277,11 @@ impl MirInterpreter {
|
||||
let v = match self.reg_load(*val) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED").ok().as_deref() == Some("1") {
|
||||
if std::env::var("NYASH_VM_PHI_TOLERATE_UNDEFINED")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
if Self::trace_enabled() {
|
||||
eprintln!("[vm-trace] phi tolerate undefined default input {:?} -> Void (err={:?})", val, e);
|
||||
}
|
||||
@ -255,10 +293,7 @@ impl MirInterpreter {
|
||||
};
|
||||
self.regs.insert(dst_id, v);
|
||||
if Self::trace_enabled() {
|
||||
eprintln!(
|
||||
"[vm-trace] phi dst={:?} take default val={:?}",
|
||||
dst_id, val
|
||||
);
|
||||
eprintln!("[vm-trace] phi dst={:?} take default val={:?}", dst_id, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -32,7 +32,8 @@ impl MirInterpreter {
|
||||
if let Some(op_fn) = self.functions.get("AddOperator.apply/2").cloned() {
|
||||
if !in_guard {
|
||||
if crate::config::env::operator_box_add_adopt() {
|
||||
let out = self.exec_function_inner(&op_fn, Some(&[a.clone(), b.clone()]))?;
|
||||
let out =
|
||||
self.exec_function_inner(&op_fn, Some(&[a.clone(), b.clone()]))?;
|
||||
self.regs.insert(dst, out);
|
||||
return Ok(());
|
||||
} else {
|
||||
|
||||
@ -20,7 +20,9 @@ impl MirInterpreter {
|
||||
let s_opt: Option<String> = match v0.clone() {
|
||||
VMValue::String(s) => Some(s),
|
||||
VMValue::BoxRef(b) => {
|
||||
if let Some(sb) = b.as_any().downcast_ref::<crate::boxes::basic::StringBox>() {
|
||||
if let Some(sb) =
|
||||
b.as_any().downcast_ref::<crate::boxes::basic::StringBox>()
|
||||
{
|
||||
Some(sb.value.clone())
|
||||
} else {
|
||||
None
|
||||
@ -29,10 +31,13 @@ impl MirInterpreter {
|
||||
_ => None,
|
||||
};
|
||||
if let Some(s) = s_opt {
|
||||
let boxed: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::boxes::basic::StringBox::new(s));
|
||||
let boxed: Box<dyn crate::box_trait::NyashBox> =
|
||||
Box::new(crate::boxes::basic::StringBox::new(s));
|
||||
let created_vm = VMValue::from_nyash_box(boxed);
|
||||
self.regs.insert(dst, created_vm);
|
||||
if Self::box_trace_enabled() { self.box_trace_emit_new(box_type, args.len()); }
|
||||
if Self::box_trace_enabled() {
|
||||
self.box_trace_emit_new(box_type, args.len());
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
@ -88,7 +93,7 @@ impl MirInterpreter {
|
||||
Err(e) => {
|
||||
return Err(self.err_with_context(
|
||||
&format!("PluginInvoke {}.{}", p.box_type, method),
|
||||
&format!("{:?}", e)
|
||||
&format!("{:?}", e),
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -115,7 +120,10 @@ impl MirInterpreter {
|
||||
return Ok(());
|
||||
}
|
||||
if let VMValue::BoxRef(b) = self.reg_load(box_val)? {
|
||||
if b.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
|
||||
if b.as_any()
|
||||
.downcast_ref::<crate::box_trait::VoidBox>()
|
||||
.is_some()
|
||||
{
|
||||
self.write_string(dst, "null".to_string());
|
||||
return Ok(());
|
||||
}
|
||||
@ -125,7 +133,8 @@ impl MirInterpreter {
|
||||
if Self::box_trace_enabled() {
|
||||
let cls = match self.reg_load(box_val).unwrap_or(VMValue::Void) {
|
||||
VMValue::BoxRef(b) => {
|
||||
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>()
|
||||
{
|
||||
inst.class_name.clone()
|
||||
} else {
|
||||
b.type_name().to_string()
|
||||
@ -167,7 +176,10 @@ impl MirInterpreter {
|
||||
}
|
||||
}
|
||||
VMValue::BoxRef(ref b) => {
|
||||
if b.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
|
||||
if b.as_any()
|
||||
.downcast_ref::<crate::box_trait::VoidBox>()
|
||||
.is_some()
|
||||
{
|
||||
if let Some(val) = super::boxes_void_guards::handle_void_method(method) {
|
||||
self.write_result(dst, val);
|
||||
return Ok(());
|
||||
@ -252,9 +264,12 @@ impl MirInterpreter {
|
||||
// Determine receiver class name when possible
|
||||
let recv_cls: Option<String> = match self.reg_load(box_val).ok() {
|
||||
Some(VMValue::BoxRef(b)) => {
|
||||
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>()
|
||||
{
|
||||
Some(inst.class_name.clone())
|
||||
} else { None }
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
@ -262,13 +277,19 @@ impl MirInterpreter {
|
||||
let prefix = format!("{}.", want);
|
||||
cands.retain(|k| k.starts_with(&prefix));
|
||||
}
|
||||
if cands.len() == 1 { self.functions.get(&cands[0]).cloned() } else { None }
|
||||
if cands.len() == 1 {
|
||||
self.functions.get(&cands[0]).cloned()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} {
|
||||
// Build argv: pass receiver as first arg (me)
|
||||
let recv_vm = self.reg_load(box_val)?;
|
||||
let mut argv: Vec<VMValue> = Vec::with_capacity(1 + args.len());
|
||||
argv.push(recv_vm);
|
||||
for a in args { argv.push(self.reg_load(*a)?); }
|
||||
for a in args {
|
||||
argv.push(self.reg_load(*a)?);
|
||||
}
|
||||
let ret = self.exec_function_inner(&func, Some(&argv))?;
|
||||
self.write_result(dst, ret);
|
||||
return Ok(());
|
||||
@ -312,5 +333,4 @@ impl MirInterpreter {
|
||||
) -> Result<bool, VMError> {
|
||||
super::boxes_array::try_handle_array_box(self, dst, box_val, method, args)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -8,56 +8,58 @@ pub(super) fn try_handle_array_box(
|
||||
method: &str,
|
||||
args: &[ValueId],
|
||||
) -> Result<bool, VMError> {
|
||||
let recv = this.reg_load(box_val)?;
|
||||
let recv_box_any: Box<dyn NyashBox> = match recv.clone() {
|
||||
VMValue::BoxRef(b) => b.share_box(),
|
||||
other => other.to_nyash_box(),
|
||||
};
|
||||
if let Some(ab) = recv_box_any
|
||||
.as_any()
|
||||
.downcast_ref::<crate::boxes::array::ArrayBox>()
|
||||
{
|
||||
match method {
|
||||
"birth" => {
|
||||
// No-op constructor init
|
||||
this.write_void(dst);
|
||||
return Ok(true);
|
||||
}
|
||||
"push" => {
|
||||
this.validate_args_exact("push", args, 1)?;
|
||||
let val = this.load_as_box(args[0])?;
|
||||
let _ = ab.push(val);
|
||||
this.write_void(dst);
|
||||
return Ok(true);
|
||||
}
|
||||
"pop" => {
|
||||
if !args.is_empty() { return Err(this.err_invalid("pop expects 0 args")); }
|
||||
let ret = ab.pop();
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"len" | "length" | "size" => {
|
||||
let ret = ab.length();
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"get" => {
|
||||
this.validate_args_exact("get", args, 1)?;
|
||||
let idx = this.load_as_box(args[0])?;
|
||||
let ret = ab.get(idx);
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"set" => {
|
||||
this.validate_args_exact("set", args, 2)?;
|
||||
let idx = this.load_as_box(args[0])?;
|
||||
let val = this.load_as_box(args[1])?;
|
||||
let _ = ab.set(idx, val);
|
||||
this.write_void(dst);
|
||||
return Ok(true);
|
||||
}
|
||||
_ => {}
|
||||
let recv = this.reg_load(box_val)?;
|
||||
let recv_box_any: Box<dyn NyashBox> = match recv.clone() {
|
||||
VMValue::BoxRef(b) => b.share_box(),
|
||||
other => other.to_nyash_box(),
|
||||
};
|
||||
if let Some(ab) = recv_box_any
|
||||
.as_any()
|
||||
.downcast_ref::<crate::boxes::array::ArrayBox>()
|
||||
{
|
||||
match method {
|
||||
"birth" => {
|
||||
// No-op constructor init
|
||||
this.write_void(dst);
|
||||
return Ok(true);
|
||||
}
|
||||
"push" => {
|
||||
this.validate_args_exact("push", args, 1)?;
|
||||
let val = this.load_as_box(args[0])?;
|
||||
let _ = ab.push(val);
|
||||
this.write_void(dst);
|
||||
return Ok(true);
|
||||
}
|
||||
"pop" => {
|
||||
if !args.is_empty() {
|
||||
return Err(this.err_invalid("pop expects 0 args"));
|
||||
}
|
||||
let ret = ab.pop();
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"len" | "length" | "size" => {
|
||||
let ret = ab.length();
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"get" => {
|
||||
this.validate_args_exact("get", args, 1)?;
|
||||
let idx = this.load_as_box(args[0])?;
|
||||
let ret = ab.get(idx);
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"set" => {
|
||||
this.validate_args_exact("set", args, 2)?;
|
||||
let idx = this.load_as_box(args[0])?;
|
||||
let val = this.load_as_box(args[1])?;
|
||||
let _ = ab.set(idx, val);
|
||||
this.write_void(dst);
|
||||
return Ok(true);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
@ -14,26 +14,39 @@ pub(super) fn try_handle_instance_box(
|
||||
other => other.to_nyash_box(),
|
||||
};
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && method == "toString" {
|
||||
eprintln!("[vm-trace] instance-check recv_box_any.type={} args_len={}", recv_box_any.type_name(), args.len());
|
||||
eprintln!(
|
||||
"[vm-trace] instance-check recv_box_any.type={} args_len={}",
|
||||
recv_box_any.type_name(),
|
||||
args.len()
|
||||
);
|
||||
}
|
||||
if let Some(inst) = recv_box_any.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
if let Some(inst) = recv_box_any
|
||||
.as_any()
|
||||
.downcast_ref::<crate::instance_v2::InstanceBox>()
|
||||
{
|
||||
// Development guard: ensure JsonScanner core fields have sensible defaults
|
||||
if inst.class_name == "JsonScanner" {
|
||||
// populate missing fields to avoid Void in comparisons inside is_eof/advance
|
||||
if inst.get_field_ng("position").is_none() {
|
||||
let _ = inst.set_field_ng("position".to_string(), crate::value::NyashValue::Integer(0));
|
||||
let _ =
|
||||
inst.set_field_ng("position".to_string(), crate::value::NyashValue::Integer(0));
|
||||
}
|
||||
if inst.get_field_ng("length").is_none() {
|
||||
let _ = inst.set_field_ng("length".to_string(), crate::value::NyashValue::Integer(0));
|
||||
let _ =
|
||||
inst.set_field_ng("length".to_string(), crate::value::NyashValue::Integer(0));
|
||||
}
|
||||
if inst.get_field_ng("line").is_none() {
|
||||
let _ = inst.set_field_ng("line".to_string(), crate::value::NyashValue::Integer(1));
|
||||
}
|
||||
if inst.get_field_ng("column").is_none() {
|
||||
let _ = inst.set_field_ng("column".to_string(), crate::value::NyashValue::Integer(1));
|
||||
let _ =
|
||||
inst.set_field_ng("column".to_string(), crate::value::NyashValue::Integer(1));
|
||||
}
|
||||
if inst.get_field_ng("text").is_none() {
|
||||
let _ = inst.set_field_ng("text".to_string(), crate::value::NyashValue::String(String::new()));
|
||||
let _ = inst.set_field_ng(
|
||||
"text".to_string(),
|
||||
crate::value::NyashValue::String(String::new()),
|
||||
);
|
||||
}
|
||||
}
|
||||
// JsonNodeInstance narrow bridges removed: rely on builder rewrite and instance dispatch
|
||||
@ -47,11 +60,26 @@ pub(super) fn try_handle_instance_box(
|
||||
);
|
||||
}
|
||||
// Resolve lowered method function: "Class.method/arity"
|
||||
let primary = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len()));
|
||||
let primary = format!(
|
||||
"{}.{}{}",
|
||||
inst.class_name,
|
||||
method,
|
||||
format!("/{}", args.len())
|
||||
);
|
||||
// Alternate naming: "ClassInstance.method/arity"
|
||||
let alt = format!("{}Instance.{}{}", inst.class_name, method, format!("/{}", args.len()));
|
||||
let alt = format!(
|
||||
"{}Instance.{}{}",
|
||||
inst.class_name,
|
||||
method,
|
||||
format!("/{}", args.len())
|
||||
);
|
||||
// Static method variant that takes 'me' explicitly as first arg: "Class.method/(arity+1)"
|
||||
let static_variant = format!("{}.{}{}", inst.class_name, method, format!("/{}", args.len() + 1));
|
||||
let static_variant = format!(
|
||||
"{}.{}{}",
|
||||
inst.class_name,
|
||||
method,
|
||||
format!("/{}", args.len() + 1)
|
||||
);
|
||||
// Special-case: toString() → stringify/0 if present
|
||||
// Prefer base class (strip trailing "Instance") stringify when available.
|
||||
let (stringify_base, stringify_inst) = if method == "toString" && args.is_empty() {
|
||||
@ -64,27 +92,43 @@ pub(super) fn try_handle_instance_box(
|
||||
Some(format!("{}.stringify/0", base_name)),
|
||||
Some(format!("{}.stringify/0", inst.class_name)),
|
||||
)
|
||||
} else { (None, None) };
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!(
|
||||
"[vm-trace] instance-dispatch class={} method={} arity={} candidates=[{}, {}, {}]",
|
||||
inst.class_name, method, args.len(), primary, alt, static_variant
|
||||
inst.class_name,
|
||||
method,
|
||||
args.len(),
|
||||
primary,
|
||||
alt,
|
||||
static_variant
|
||||
);
|
||||
}
|
||||
|
||||
// Prefer stringify for toString() if present (semantic alias). Try instance first, then base.
|
||||
let func_opt = if let Some(ref sname) = stringify_inst {
|
||||
this.functions.get(sname).cloned()
|
||||
} else { None }
|
||||
.or_else(|| stringify_base.as_ref().and_then(|n| this.functions.get(n).cloned()))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.or_else(|| {
|
||||
stringify_base
|
||||
.as_ref()
|
||||
.and_then(|n| this.functions.get(n).cloned())
|
||||
})
|
||||
.or_else(|| this.functions.get(&primary).cloned())
|
||||
.or_else(|| this.functions.get(&alt).cloned())
|
||||
.or_else(|| this.functions.get(&static_variant).cloned());
|
||||
|
||||
if let Some(func) = func_opt {
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[vm-trace] instance-dispatch hit -> {}", func.signature.name);
|
||||
eprintln!(
|
||||
"[vm-trace] instance-dispatch hit -> {}",
|
||||
func.signature.name
|
||||
);
|
||||
}
|
||||
// Build argv: me + args (works for both instance and static(me, ...))
|
||||
let mut argv: Vec<VMValue> = Vec::with_capacity(1 + args.len());
|
||||
@ -95,7 +139,9 @@ pub(super) fn try_handle_instance_box(
|
||||
}
|
||||
}
|
||||
argv.push(recv_vm.clone());
|
||||
for a in args { argv.push(this.reg_load(*a)?); }
|
||||
for a in args {
|
||||
argv.push(this.reg_load(*a)?);
|
||||
}
|
||||
let ret = this.exec_function_inner(&func, Some(&argv))?;
|
||||
this.write_result(dst, ret);
|
||||
return Ok(true);
|
||||
@ -120,19 +166,28 @@ pub(super) fn try_handle_instance_box(
|
||||
if filtered.len() == 1 {
|
||||
let fname = &filtered[0];
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[vm-trace] instance-dispatch fallback (scoped) -> {}", fname);
|
||||
eprintln!(
|
||||
"[vm-trace] instance-dispatch fallback (scoped) -> {}",
|
||||
fname
|
||||
);
|
||||
}
|
||||
if let Some(func) = this.functions.get(fname).cloned() {
|
||||
let mut argv: Vec<VMValue> = Vec::with_capacity(1 + args.len());
|
||||
if method == "birth" && crate::config::env::using_is_dev() {
|
||||
if matches!(recv_vm, VMValue::Void) {
|
||||
return Err(this.err_invalid("Dev assert: birth(me==Void) is forbidden"));
|
||||
return Err(
|
||||
this.err_invalid("Dev assert: birth(me==Void) is forbidden")
|
||||
);
|
||||
}
|
||||
}
|
||||
argv.push(recv_vm.clone());
|
||||
for a in args { argv.push(this.reg_load(*a)?); }
|
||||
for a in args {
|
||||
argv.push(this.reg_load(*a)?);
|
||||
}
|
||||
let ret = this.exec_function_inner(&func, Some(&argv))?;
|
||||
if let Some(d) = dst { this.regs.insert(d, ret); }
|
||||
if let Some(d) = dst {
|
||||
this.regs.insert(d, ret);
|
||||
}
|
||||
return Ok(true);
|
||||
}
|
||||
} else if filtered.len() > 1 {
|
||||
@ -143,7 +198,11 @@ pub(super) fn try_handle_instance_box(
|
||||
} else {
|
||||
// No same-class candidate: do not dispatch cross-class
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[vm-trace] instance-dispatch no same-class candidate for tail .{}{}", method, format!("/{}", args.len()));
|
||||
eprintln!(
|
||||
"[vm-trace] instance-dispatch no same-class candidate for tail .{}{}",
|
||||
method,
|
||||
format!("/{}", args.len())
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -8,115 +8,130 @@ pub(super) fn try_handle_map_box(
|
||||
method: &str,
|
||||
args: &[ValueId],
|
||||
) -> Result<bool, VMError> {
|
||||
let recv = this.reg_load(box_val)?;
|
||||
let recv_box_any: Box<dyn NyashBox> = match recv.clone() {
|
||||
VMValue::BoxRef(b) => b.share_box(),
|
||||
other => other.to_nyash_box(),
|
||||
};
|
||||
if let Some(mb) = recv_box_any
|
||||
.as_any()
|
||||
.downcast_ref::<crate::boxes::map_box::MapBox>()
|
||||
{
|
||||
match method {
|
||||
"birth" => {
|
||||
// No-op constructor init for MapBox
|
||||
this.write_void(dst);
|
||||
return Ok(true);
|
||||
}
|
||||
// Field bridge: treat getField/setField as get/set with string key
|
||||
"getField" => {
|
||||
this.validate_args_exact("MapBox.getField", args, 1)?;
|
||||
let k_vm = this.reg_load(args[0])?;
|
||||
// Field access expects a String key; otherwise return a stable tag.
|
||||
if !matches!(k_vm, VMValue::String(_)) {
|
||||
this.write_result(dst, VMValue::String("[map/bad-key] field name must be string".to_string()));
|
||||
return Ok(true);
|
||||
}
|
||||
let k = this.load_as_box(args[0])?;
|
||||
let ret = mb.get(k);
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"setField" => {
|
||||
this.validate_args_exact("MapBox.setField", args, 2)?;
|
||||
let k_vm = this.reg_load(args[0])?;
|
||||
if !matches!(k_vm, VMValue::String(_)) {
|
||||
this.write_result(dst, VMValue::String("[map/bad-key] field name must be string".to_string()));
|
||||
return Ok(true);
|
||||
}
|
||||
let k = this.load_as_box(args[0])?;
|
||||
let v = this.load_as_box(args[1])?;
|
||||
let ret = mb.set(k, v);
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"set" => {
|
||||
this.validate_args_exact("MapBox.set", args, 2)?;
|
||||
let k_vm = this.reg_load(args[0])?;
|
||||
if !matches!(k_vm, VMValue::String(_)) {
|
||||
this.write_result(dst, VMValue::String("[map/bad-key] key must be string".to_string()));
|
||||
return Ok(true);
|
||||
}
|
||||
let k = this.load_as_box(args[0])?;
|
||||
let v = this.load_as_box(args[1])?;
|
||||
let ret = mb.set(k, v);
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"get" => {
|
||||
this.validate_args_exact("MapBox.get", args, 1)?;
|
||||
let k_vm = this.reg_load(args[0])?;
|
||||
if !matches!(k_vm, VMValue::String(_)) {
|
||||
this.write_result(dst, VMValue::String("[map/bad-key] key must be string".to_string()));
|
||||
return Ok(true);
|
||||
}
|
||||
let k = this.load_as_box(args[0])?;
|
||||
let ret = mb.get(k);
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"has" => {
|
||||
this.validate_args_exact("MapBox.has", args, 1)?;
|
||||
let k = this.load_as_box(args[0])?;
|
||||
let ret = mb.has(k);
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"delete" => {
|
||||
this.validate_args_exact("MapBox.delete", args, 1)?;
|
||||
let k_vm = this.reg_load(args[0])?;
|
||||
if !matches!(k_vm, VMValue::String(_)) {
|
||||
this.write_result(dst, VMValue::String("[map/bad-key] key must be string".to_string()));
|
||||
return Ok(true);
|
||||
}
|
||||
let k = this.load_as_box(args[0])?;
|
||||
let ret = mb.delete(k);
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"len" | "length" | "size" => {
|
||||
let ret = mb.size();
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"keys" => {
|
||||
let ret = mb.keys();
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"values" => {
|
||||
let ret = mb.values();
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"clear" => {
|
||||
// Reset map to empty; return a neutral value
|
||||
let ret = mb.clear();
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
_ => {}
|
||||
let recv = this.reg_load(box_val)?;
|
||||
let recv_box_any: Box<dyn NyashBox> = match recv.clone() {
|
||||
VMValue::BoxRef(b) => b.share_box(),
|
||||
other => other.to_nyash_box(),
|
||||
};
|
||||
if let Some(mb) = recv_box_any
|
||||
.as_any()
|
||||
.downcast_ref::<crate::boxes::map_box::MapBox>()
|
||||
{
|
||||
match method {
|
||||
"birth" => {
|
||||
// No-op constructor init for MapBox
|
||||
this.write_void(dst);
|
||||
return Ok(true);
|
||||
}
|
||||
// Field bridge: treat getField/setField as get/set with string key
|
||||
"getField" => {
|
||||
this.validate_args_exact("MapBox.getField", args, 1)?;
|
||||
let k_vm = this.reg_load(args[0])?;
|
||||
// Field access expects a String key; otherwise return a stable tag.
|
||||
if !matches!(k_vm, VMValue::String(_)) {
|
||||
this.write_result(
|
||||
dst,
|
||||
VMValue::String("[map/bad-key] field name must be string".to_string()),
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
let k = this.load_as_box(args[0])?;
|
||||
let ret = mb.get(k);
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"setField" => {
|
||||
this.validate_args_exact("MapBox.setField", args, 2)?;
|
||||
let k_vm = this.reg_load(args[0])?;
|
||||
if !matches!(k_vm, VMValue::String(_)) {
|
||||
this.write_result(
|
||||
dst,
|
||||
VMValue::String("[map/bad-key] field name must be string".to_string()),
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
let k = this.load_as_box(args[0])?;
|
||||
let v = this.load_as_box(args[1])?;
|
||||
let ret = mb.set(k, v);
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"set" => {
|
||||
this.validate_args_exact("MapBox.set", args, 2)?;
|
||||
let k_vm = this.reg_load(args[0])?;
|
||||
if !matches!(k_vm, VMValue::String(_)) {
|
||||
this.write_result(
|
||||
dst,
|
||||
VMValue::String("[map/bad-key] key must be string".to_string()),
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
let k = this.load_as_box(args[0])?;
|
||||
let v = this.load_as_box(args[1])?;
|
||||
let ret = mb.set(k, v);
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"get" => {
|
||||
this.validate_args_exact("MapBox.get", args, 1)?;
|
||||
let k_vm = this.reg_load(args[0])?;
|
||||
if !matches!(k_vm, VMValue::String(_)) {
|
||||
this.write_result(
|
||||
dst,
|
||||
VMValue::String("[map/bad-key] key must be string".to_string()),
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
let k = this.load_as_box(args[0])?;
|
||||
let ret = mb.get(k);
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"has" => {
|
||||
this.validate_args_exact("MapBox.has", args, 1)?;
|
||||
let k = this.load_as_box(args[0])?;
|
||||
let ret = mb.has(k);
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"delete" => {
|
||||
this.validate_args_exact("MapBox.delete", args, 1)?;
|
||||
let k_vm = this.reg_load(args[0])?;
|
||||
if !matches!(k_vm, VMValue::String(_)) {
|
||||
this.write_result(
|
||||
dst,
|
||||
VMValue::String("[map/bad-key] key must be string".to_string()),
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
let k = this.load_as_box(args[0])?;
|
||||
let ret = mb.delete(k);
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"len" | "length" | "size" => {
|
||||
let ret = mb.size();
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"keys" => {
|
||||
let ret = mb.keys();
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"values" => {
|
||||
let ret = mb.values();
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"clear" => {
|
||||
// Reset map to empty; return a neutral value
|
||||
let ret = mb.clear();
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
@ -9,8 +9,8 @@ pub(super) fn try_handle_object_fields(
|
||||
) -> Result<bool, VMError> {
|
||||
// Local helpers to bridge NyashValue <-> VMValue for InstanceBox fields
|
||||
fn vm_to_nv(v: &VMValue) -> crate::value::NyashValue {
|
||||
use crate::value::NyashValue as NV;
|
||||
use super::VMValue as VV;
|
||||
use crate::value::NyashValue as NV;
|
||||
match v {
|
||||
VV::Integer(i) => NV::Integer(*i),
|
||||
VV::Float(f) => NV::Float(*f),
|
||||
@ -18,12 +18,12 @@ pub(super) fn try_handle_object_fields(
|
||||
VV::String(s) => NV::String(s.clone()),
|
||||
VV::Void => NV::Void,
|
||||
VV::Future(_) => NV::Void, // not expected in fields
|
||||
VV::BoxRef(_) => NV::Void, // store minimal; complex object fields are not required here
|
||||
VV::BoxRef(_) => NV::Void, // store minimal; complex object fields are not required here
|
||||
}
|
||||
}
|
||||
fn nv_to_vm(v: &crate::value::NyashValue) -> VMValue {
|
||||
use crate::value::NyashValue as NV;
|
||||
use super::VMValue as VV;
|
||||
use crate::value::NyashValue as NV;
|
||||
match v {
|
||||
NV::Integer(i) => VV::Integer(*i),
|
||||
NV::Float(f) => VV::Float(*f),
|
||||
@ -48,7 +48,8 @@ pub(super) fn try_handle_object_fields(
|
||||
|
||||
// Create a temporary value to hold the singleton
|
||||
let temp_id = ValueId(999999999); // Temporary ID for singleton
|
||||
this.regs.insert(temp_id, VMValue::from_nyash_box(Box::new(instance_clone)));
|
||||
this.regs
|
||||
.insert(temp_id, VMValue::from_nyash_box(Box::new(instance_clone)));
|
||||
temp_id
|
||||
} else {
|
||||
box_val
|
||||
@ -59,18 +60,27 @@ pub(super) fn try_handle_object_fields(
|
||||
|
||||
// MapBox special-case: bridge to MapBox.get, with string-only key
|
||||
if let Ok(VMValue::BoxRef(bref)) = this.reg_load(actual_box_val) {
|
||||
if bref.as_any().downcast_ref::<crate::boxes::map_box::MapBox>().is_some() {
|
||||
if bref
|
||||
.as_any()
|
||||
.downcast_ref::<crate::boxes::map_box::MapBox>()
|
||||
.is_some()
|
||||
{
|
||||
let key_vm = this.reg_load(args[0])?;
|
||||
if let VMValue::String(_) = key_vm {
|
||||
let k = key_vm.to_nyash_box();
|
||||
let map = bref.share_box();
|
||||
if let Some(mb) = map.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
||||
if let Some(mb) =
|
||||
map.as_any().downcast_ref::<crate::boxes::map_box::MapBox>()
|
||||
{
|
||||
let ret = mb.get(k);
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
} else {
|
||||
this.write_string(dst, "[map/bad-key] field name must be string".to_string());
|
||||
this.write_string(
|
||||
dst,
|
||||
"[map/bad-key] field name must be string".to_string(),
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
@ -94,15 +104,22 @@ pub(super) fn try_handle_object_fields(
|
||||
};
|
||||
// Prefer InstanceBox internal storage (structural correctness)
|
||||
if let VMValue::BoxRef(bref) = this.reg_load(actual_box_val)? {
|
||||
if let Some(inst) = bref.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
if let Some(inst) = bref
|
||||
.as_any()
|
||||
.downcast_ref::<crate::instance_v2::InstanceBox>()
|
||||
{
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[vm-trace] getField instance class={}", inst.class_name);
|
||||
}
|
||||
// Special-case bridge: JsonParser.length -> tokens.length()
|
||||
if inst.class_name == "JsonParser" && fname == "length" {
|
||||
if let Some(tokens_shared) = inst.get_field("tokens") {
|
||||
let tokens_box: Box<dyn crate::box_trait::NyashBox> = tokens_shared.share_box();
|
||||
if let Some(arr) = tokens_box.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||
let tokens_box: Box<dyn crate::box_trait::NyashBox> =
|
||||
tokens_shared.share_box();
|
||||
if let Some(arr) = tokens_box
|
||||
.as_any()
|
||||
.downcast_ref::<crate::boxes::array::ArrayBox>()
|
||||
{
|
||||
let len_box = arr.length();
|
||||
this.write_result(dst, VMValue::from_nyash_box(len_box));
|
||||
return Ok(true);
|
||||
@ -112,7 +129,9 @@ pub(super) fn try_handle_object_fields(
|
||||
// First: prefer fields_ng (NyashValue) when present
|
||||
if let Some(nv) = inst.get_field_ng(&fname) {
|
||||
// Dev trace: JsonToken field get
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") && inst.class_name == "JsonToken" {
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1")
|
||||
&& inst.class_name == "JsonToken"
|
||||
{
|
||||
eprintln!("[vm-trace] JsonToken.getField name={} nv={:?}", fname, nv);
|
||||
}
|
||||
// Treat complex Box-like values as "missing" for internal storage so that
|
||||
@ -129,13 +148,18 @@ pub(super) fn try_handle_object_fields(
|
||||
);
|
||||
if !is_missing {
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[vm-trace] getField internal {}.{} -> {:?}", inst.class_name, fname, nv);
|
||||
eprintln!(
|
||||
"[vm-trace] getField internal {}.{} -> {:?}",
|
||||
inst.class_name, fname, nv
|
||||
);
|
||||
}
|
||||
// Special-case: NV::Box should surface as VMValue::BoxRef
|
||||
if let crate::value::NyashValue::Box(ref arc_m) = nv {
|
||||
if let Ok(guard) = arc_m.lock() {
|
||||
let cloned: Box<dyn crate::box_trait::NyashBox> = guard.clone_box();
|
||||
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> = std::sync::Arc::from(cloned);
|
||||
let cloned: Box<dyn crate::box_trait::NyashBox> =
|
||||
guard.clone_box();
|
||||
let arc: std::sync::Arc<dyn crate::box_trait::NyashBox> =
|
||||
std::sync::Arc::from(cloned);
|
||||
this.write_result(dst, VMValue::BoxRef(arc));
|
||||
} else {
|
||||
this.write_void(dst);
|
||||
@ -170,8 +194,12 @@ pub(super) fn try_handle_object_fields(
|
||||
_ => None,
|
||||
};
|
||||
if let Some(v) = def {
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[vm-trace] getField default JsonScanner.{} -> {:?}", fname, v);
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1")
|
||||
{
|
||||
eprintln!(
|
||||
"[vm-trace] getField default JsonScanner.{} -> {:?}",
|
||||
fname, v
|
||||
);
|
||||
}
|
||||
this.write_result(dst, v);
|
||||
return Ok(true);
|
||||
@ -229,10 +257,14 @@ pub(super) fn try_handle_object_fields(
|
||||
// JsonScanner fields inside JsonScanner.{is_eof,current,advance}, provide
|
||||
// pragmatic defaults to avoid Void comparisons during bring-up.
|
||||
if let VMValue::Void = v {
|
||||
let guard_on = std::env::var("NYASH_VM_SCANNER_DEFAULTS").ok().as_deref() == Some("1");
|
||||
let guard_on =
|
||||
std::env::var("NYASH_VM_SCANNER_DEFAULTS").ok().as_deref() == Some("1");
|
||||
let fn_ctx = this.cur_fn.as_deref().unwrap_or("");
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[vm-trace] getField guard_check ctx={} guard_on={} name={}", fn_ctx, guard_on, fname);
|
||||
eprintln!(
|
||||
"[vm-trace] getField guard_check ctx={} guard_on={} name={}",
|
||||
fn_ctx, guard_on, fname
|
||||
);
|
||||
}
|
||||
if guard_on {
|
||||
let fn_ctx = this.cur_fn.as_deref().unwrap_or("");
|
||||
@ -243,7 +275,10 @@ pub(super) fn try_handle_object_fields(
|
||||
if is_scanner_ctx {
|
||||
// Try class-aware default first
|
||||
if let Ok(VMValue::BoxRef(bref2)) = this.reg_load(actual_box_val) {
|
||||
if let Some(inst2) = bref2.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
if let Some(inst2) = bref2
|
||||
.as_any()
|
||||
.downcast_ref::<crate::instance_v2::InstanceBox>()
|
||||
{
|
||||
if inst2.class_name == "JsonScanner" {
|
||||
let fallback = match fname.as_str() {
|
||||
"position" | "length" => Some(VMValue::Integer(0)),
|
||||
@ -252,8 +287,13 @@ pub(super) fn try_handle_object_fields(
|
||||
_ => None,
|
||||
};
|
||||
if let Some(val) = fallback {
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[vm-trace] getField final_default {} -> {:?}", fname, val);
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
eprintln!(
|
||||
"[vm-trace] getField final_default {} -> {:?}",
|
||||
fname, val
|
||||
);
|
||||
}
|
||||
v = val;
|
||||
}
|
||||
@ -280,7 +320,11 @@ pub(super) fn try_handle_object_fields(
|
||||
}
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
|
||||
if let VMValue::BoxRef(b) = &v {
|
||||
eprintln!("[vm-trace] getField legacy {} -> BoxRef({})", fname, b.type_name());
|
||||
eprintln!(
|
||||
"[vm-trace] getField legacy {} -> BoxRef({})",
|
||||
fname,
|
||||
b.type_name()
|
||||
);
|
||||
} else {
|
||||
eprintln!("[vm-trace] getField legacy {} -> {:?}", fname, v);
|
||||
}
|
||||
@ -299,9 +343,13 @@ pub(super) fn try_handle_object_fields(
|
||||
// class name unknown here; use receiver type name if possible
|
||||
let cls = match this.reg_load(actual_box_val).unwrap_or(VMValue::Void) {
|
||||
VMValue::BoxRef(b) => {
|
||||
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
if let Some(inst) =
|
||||
b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>()
|
||||
{
|
||||
inst.class_name.clone()
|
||||
} else { b.type_name().to_string() }
|
||||
} else {
|
||||
b.type_name().to_string()
|
||||
}
|
||||
}
|
||||
_ => "<unknown>".to_string(),
|
||||
};
|
||||
@ -322,7 +370,8 @@ pub(super) fn try_handle_object_fields(
|
||||
|
||||
// Create a temporary value to hold the singleton
|
||||
let temp_id = ValueId(999999998); // Temporary ID for singleton (different from getField)
|
||||
this.regs.insert(temp_id, VMValue::from_nyash_box(Box::new(instance_clone)));
|
||||
this.regs
|
||||
.insert(temp_id, VMValue::from_nyash_box(Box::new(instance_clone)));
|
||||
temp_id
|
||||
} else {
|
||||
box_val
|
||||
@ -333,19 +382,28 @@ pub(super) fn try_handle_object_fields(
|
||||
|
||||
// MapBox special-case: bridge to MapBox.set, with string-only key
|
||||
if let Ok(VMValue::BoxRef(bref)) = this.reg_load(actual_box_val) {
|
||||
if bref.as_any().downcast_ref::<crate::boxes::map_box::MapBox>().is_some() {
|
||||
if bref
|
||||
.as_any()
|
||||
.downcast_ref::<crate::boxes::map_box::MapBox>()
|
||||
.is_some()
|
||||
{
|
||||
let key_vm = this.reg_load(args[0])?;
|
||||
if let VMValue::String(_) = key_vm {
|
||||
let k = key_vm.to_nyash_box();
|
||||
let v = this.reg_load(args[1])?.to_nyash_box();
|
||||
let map = bref.share_box();
|
||||
if let Some(mb) = map.as_any().downcast_ref::<crate::boxes::map_box::MapBox>() {
|
||||
if let Some(mb) =
|
||||
map.as_any().downcast_ref::<crate::boxes::map_box::MapBox>()
|
||||
{
|
||||
let _ = mb.set(k, v);
|
||||
this.write_void(dst);
|
||||
return Ok(true);
|
||||
}
|
||||
} else {
|
||||
this.write_string(dst, "[map/bad-key] field name must be string".to_string());
|
||||
this.write_string(
|
||||
dst,
|
||||
"[map/bad-key] field name must be string".to_string(),
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
}
|
||||
@ -358,9 +416,15 @@ pub(super) fn try_handle_object_fields(
|
||||
// Dev trace: JsonToken field set
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
|
||||
if let VMValue::BoxRef(bref) = this.reg_load(actual_box_val)? {
|
||||
if let Some(inst) = bref.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
if let Some(inst) = bref
|
||||
.as_any()
|
||||
.downcast_ref::<crate::instance_v2::InstanceBox>()
|
||||
{
|
||||
if inst.class_name == "JsonToken" {
|
||||
eprintln!("[vm-trace] JsonToken.setField name={} vmval={:?}", fname, valv);
|
||||
eprintln!(
|
||||
"[vm-trace] JsonToken.setField name={} vmval={:?}",
|
||||
fname, valv
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -377,9 +441,13 @@ pub(super) fn try_handle_object_fields(
|
||||
};
|
||||
let cls = match this.reg_load(actual_box_val).unwrap_or(VMValue::Void) {
|
||||
VMValue::BoxRef(b) => {
|
||||
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
if let Some(inst) =
|
||||
b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>()
|
||||
{
|
||||
inst.class_name.clone()
|
||||
} else { b.type_name().to_string() }
|
||||
} else {
|
||||
b.type_name().to_string()
|
||||
}
|
||||
}
|
||||
_ => "<unknown>".to_string(),
|
||||
};
|
||||
@ -387,28 +455,52 @@ pub(super) fn try_handle_object_fields(
|
||||
}
|
||||
// Prefer InstanceBox internal storage
|
||||
if let VMValue::BoxRef(bref) = this.reg_load(actual_box_val)? {
|
||||
if let Some(inst) = bref.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
if let Some(inst) = bref
|
||||
.as_any()
|
||||
.downcast_ref::<crate::instance_v2::InstanceBox>()
|
||||
{
|
||||
// Primitives → 内部保存
|
||||
if matches!(valv, VMValue::Integer(_) | VMValue::Float(_) | VMValue::Bool(_) | VMValue::String(_) | VMValue::Void) {
|
||||
if matches!(
|
||||
valv,
|
||||
VMValue::Integer(_)
|
||||
| VMValue::Float(_)
|
||||
| VMValue::Bool(_)
|
||||
| VMValue::String(_)
|
||||
| VMValue::Void
|
||||
) {
|
||||
let _ = inst.set_field_ng(fname.clone(), vm_to_nv(&valv));
|
||||
return Ok(true);
|
||||
}
|
||||
// BoxRef のうち、Integer/Float/Bool/String はプリミティブに剥がして内部保存
|
||||
if let VMValue::BoxRef(bx) = &valv {
|
||||
if let Some(ib) = bx.as_any().downcast_ref::<crate::box_trait::IntegerBox>() {
|
||||
let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::Integer(ib.value));
|
||||
if let Some(ib) = bx.as_any().downcast_ref::<crate::box_trait::IntegerBox>()
|
||||
{
|
||||
let _ = inst.set_field_ng(
|
||||
fname.clone(),
|
||||
crate::value::NyashValue::Integer(ib.value),
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
if let Some(fb) = bx.as_any().downcast_ref::<crate::boxes::FloatBox>() {
|
||||
let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::Float(fb.value));
|
||||
let _ = inst.set_field_ng(
|
||||
fname.clone(),
|
||||
crate::value::NyashValue::Float(fb.value),
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
if let Some(bb) = bx.as_any().downcast_ref::<crate::box_trait::BoolBox>() {
|
||||
let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::Bool(bb.value));
|
||||
let _ = inst.set_field_ng(
|
||||
fname.clone(),
|
||||
crate::value::NyashValue::Bool(bb.value),
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||
let _ = inst.set_field_ng(fname.clone(), crate::value::NyashValue::String(sb.value.clone()));
|
||||
if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>()
|
||||
{
|
||||
let _ = inst.set_field_ng(
|
||||
fname.clone(),
|
||||
crate::value::NyashValue::String(sb.value.clone()),
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
// For complex Box values (InstanceBox/MapBox/ArrayBox...), store into
|
||||
@ -419,10 +511,7 @@ pub(super) fn try_handle_object_fields(
|
||||
}
|
||||
}
|
||||
let key = this.object_key_for(actual_box_val);
|
||||
this.obj_fields
|
||||
.entry(key)
|
||||
.or_default()
|
||||
.insert(fname, valv);
|
||||
this.obj_fields.entry(key).or_default().insert(fname, valv);
|
||||
Ok(true)
|
||||
}
|
||||
_ => Ok(false),
|
||||
|
||||
@ -52,7 +52,7 @@ pub(super) fn invoke_plugin_box(
|
||||
}
|
||||
Err(e) => Err(this.err_with_context(
|
||||
&format!("BoxCall {}.{}", p.box_type, method),
|
||||
&format!("{:?}", e)
|
||||
&format!("{:?}", e),
|
||||
)),
|
||||
}
|
||||
} else if recv_box.type_name() == "StringBox" {
|
||||
@ -99,10 +99,7 @@ pub(super) fn invoke_plugin_box(
|
||||
if let Some(arg_id) = args.get(0) {
|
||||
let ch = this.reg_load(*arg_id)?.to_string();
|
||||
let c = ch.chars().next().unwrap_or('\0');
|
||||
let is_alpha =
|
||||
('A'..='Z').contains(&c) ||
|
||||
('a'..='z').contains(&c) ||
|
||||
c == '_';
|
||||
let is_alpha = ('A'..='Z').contains(&c) || ('a'..='z').contains(&c) || c == '_';
|
||||
this.write_result(dst, VMValue::Bool(is_alpha));
|
||||
Ok(())
|
||||
} else {
|
||||
@ -115,14 +112,21 @@ pub(super) fn invoke_plugin_box(
|
||||
// Special-case: minimal runtime fallback for common InstanceBox methods when
|
||||
// lowered functions are not available (dev robustness). Keeps behavior stable
|
||||
// without changing semantics in the normal path.
|
||||
if let Some(inst) = recv_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
if let Some(inst) = recv_box
|
||||
.as_any()
|
||||
.downcast_ref::<crate::instance_v2::InstanceBox>()
|
||||
{
|
||||
// Generic current() fallback: if object has integer 'position' and string 'text',
|
||||
// return one character at that position (or empty at EOF). This covers JsonScanner
|
||||
// and compatible scanners without relying on class name.
|
||||
if method == "current" && args.is_empty() {
|
||||
if let Some(crate::value::NyashValue::Integer(pos)) = inst.get_field_ng("position") {
|
||||
if let Some(crate::value::NyashValue::String(text)) = inst.get_field_ng("text") {
|
||||
let s = if pos < 0 || (pos as usize) >= text.len() { String::new() } else {
|
||||
if let Some(crate::value::NyashValue::Integer(pos)) = inst.get_field_ng("position")
|
||||
{
|
||||
if let Some(crate::value::NyashValue::String(text)) = inst.get_field_ng("text")
|
||||
{
|
||||
let s = if pos < 0 || (pos as usize) >= text.len() {
|
||||
String::new()
|
||||
} else {
|
||||
let bytes = text.as_bytes();
|
||||
let i = pos as usize;
|
||||
let j = (i + 1).min(bytes.len());
|
||||
@ -137,7 +141,11 @@ pub(super) fn invoke_plugin_box(
|
||||
// Generic toString fallback for any non-plugin box
|
||||
if method == "toString" {
|
||||
// Map VoidBox.toString → "null" for JSON-friendly semantics
|
||||
let s = if recv_box.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
|
||||
let s = if recv_box
|
||||
.as_any()
|
||||
.downcast_ref::<crate::box_trait::VoidBox>()
|
||||
.is_some()
|
||||
{
|
||||
"null".to_string()
|
||||
} else {
|
||||
recv_box.to_string_box().value
|
||||
@ -148,7 +156,10 @@ pub(super) fn invoke_plugin_box(
|
||||
// Minimal runtime fallback for common InstanceBox.is_eof when lowered function is not present.
|
||||
// This avoids cross-class leaks and hard errors in union-like flows.
|
||||
if method == "is_eof" && args.is_empty() {
|
||||
if let Some(inst) = recv_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
if let Some(inst) = recv_box
|
||||
.as_any()
|
||||
.downcast_ref::<crate::instance_v2::InstanceBox>()
|
||||
{
|
||||
if inst.class_name == "JsonToken" {
|
||||
let is = match inst.get_field_ng("type") {
|
||||
Some(crate::value::NyashValue::String(ref s)) => s == "EOF",
|
||||
@ -158,8 +169,14 @@ pub(super) fn invoke_plugin_box(
|
||||
return Ok(());
|
||||
}
|
||||
if inst.class_name == "JsonScanner" {
|
||||
let pos = match inst.get_field_ng("position") { Some(crate::value::NyashValue::Integer(i)) => i, _ => 0 };
|
||||
let len = match inst.get_field_ng("length") { Some(crate::value::NyashValue::Integer(i)) => i, _ => 0 };
|
||||
let pos = match inst.get_field_ng("position") {
|
||||
Some(crate::value::NyashValue::Integer(i)) => i,
|
||||
_ => 0,
|
||||
};
|
||||
let len = match inst.get_field_ng("length") {
|
||||
Some(crate::value::NyashValue::Integer(i)) => i,
|
||||
_ => 0,
|
||||
};
|
||||
let is = pos >= len;
|
||||
this.write_result(dst, VMValue::Bool(is));
|
||||
return Ok(());
|
||||
@ -167,7 +184,10 @@ pub(super) fn invoke_plugin_box(
|
||||
}
|
||||
}
|
||||
// Dynamic fallback for user-defined InstanceBox: dispatch to lowered function "Class.method/Arity"
|
||||
if let Some(inst) = recv_box.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
if let Some(inst) = recv_box
|
||||
.as_any()
|
||||
.downcast_ref::<crate::instance_v2::InstanceBox>()
|
||||
{
|
||||
let class_name = inst.class_name.clone();
|
||||
let arity = args.len(); // function name arity excludes 'me'
|
||||
let fname = format!("{}.{}{}", class_name, method, format!("/{}", arity));
|
||||
@ -190,10 +210,10 @@ pub(super) fn invoke_plugin_box(
|
||||
this.write_string(dst, String::new());
|
||||
return Ok(());
|
||||
}
|
||||
// VoidBox graceful handling for common container-like methods
|
||||
// Treat null.receiver.* as safe no-ops that return null/0 where appropriate
|
||||
if recv_box.type_name() == "VoidBox" {
|
||||
match method {
|
||||
// VoidBox graceful handling for common container-like methods
|
||||
// Treat null.receiver.* as safe no-ops that return null/0 where appropriate
|
||||
if recv_box.type_name() == "VoidBox" {
|
||||
match method {
|
||||
"object_get" | "array_get" | "toString" => {
|
||||
this.write_void(dst);
|
||||
return Ok(());
|
||||
|
||||
@ -7,207 +7,231 @@ pub(super) fn try_handle_string_box(
|
||||
method: &str,
|
||||
args: &[ValueId],
|
||||
) -> Result<bool, VMError> {
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[vm-trace] try_handle_string_box(method={})", method);
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[vm-trace] try_handle_string_box(method={})", method);
|
||||
}
|
||||
let recv = this.reg_load(box_val)?;
|
||||
// Ultra-fast path: raw VM string receiver for length/size (no boxing at all)
|
||||
if (method == "length" || method == "size")
|
||||
&& std::env::var("NYASH_VM_FAST").ok().as_deref() == Some("1")
|
||||
{
|
||||
if let VMValue::String(ref raw) = recv {
|
||||
let use_cp = std::env::var("NYASH_STR_CP").ok().as_deref() == Some("1");
|
||||
let n = if use_cp {
|
||||
raw.chars().count() as i64
|
||||
} else {
|
||||
raw.len() as i64
|
||||
};
|
||||
this.write_result(dst, VMValue::Integer(n));
|
||||
return Ok(true);
|
||||
}
|
||||
let recv = this.reg_load(box_val)?;
|
||||
// Ultra-fast path: raw VM string receiver for length/size (no boxing at all)
|
||||
if (method == "length" || method == "size")
|
||||
&& std::env::var("NYASH_VM_FAST").ok().as_deref() == Some("1")
|
||||
{
|
||||
if let VMValue::String(ref raw) = recv {
|
||||
}
|
||||
// Handle ONLY when the receiver is actually a string.
|
||||
// Do NOT coerce arbitrary boxes to StringBox (e.g., ArrayBox.length()).
|
||||
let sb_norm_opt: Option<crate::box_trait::StringBox> = match recv.clone() {
|
||||
VMValue::String(s) => Some(crate::box_trait::StringBox::new(s)),
|
||||
VMValue::BoxRef(b) => {
|
||||
if b.as_any()
|
||||
.downcast_ref::<crate::box_trait::StringBox>()
|
||||
.is_some()
|
||||
{
|
||||
Some(b.to_string_box())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let Some(sb_norm) = sb_norm_opt else {
|
||||
return Ok(false);
|
||||
};
|
||||
// Only handle known string methods here (receiver is confirmed string)
|
||||
match method {
|
||||
"length" | "size" => {
|
||||
// Bench/profile fast path: return VMValue::Integer directly (avoid boxing overhead)
|
||||
if std::env::var("NYASH_VM_FAST").ok().as_deref() == Some("1") {
|
||||
let use_cp = std::env::var("NYASH_STR_CP").ok().as_deref() == Some("1");
|
||||
let n = if use_cp { raw.chars().count() as i64 } else { raw.len() as i64 };
|
||||
let n = if use_cp {
|
||||
sb_norm.value.chars().count() as i64
|
||||
} else {
|
||||
sb_norm.value.len() as i64
|
||||
};
|
||||
this.write_result(dst, VMValue::Integer(n));
|
||||
return Ok(true);
|
||||
}
|
||||
let ret = sb_norm.length();
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
// Handle ONLY when the receiver is actually a string.
|
||||
// Do NOT coerce arbitrary boxes to StringBox (e.g., ArrayBox.length()).
|
||||
let sb_norm_opt: Option<crate::box_trait::StringBox> = match recv.clone() {
|
||||
VMValue::String(s) => Some(crate::box_trait::StringBox::new(s)),
|
||||
VMValue::BoxRef(b) => {
|
||||
if b.as_any().downcast_ref::<crate::box_trait::StringBox>().is_some() {
|
||||
Some(b.to_string_box())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
let Some(sb_norm) = sb_norm_opt else { return Ok(false) };
|
||||
// Only handle known string methods here (receiver is confirmed string)
|
||||
match method {
|
||||
"length" | "size" => {
|
||||
// Bench/profile fast path: return VMValue::Integer directly (avoid boxing overhead)
|
||||
if std::env::var("NYASH_VM_FAST").ok().as_deref() == Some("1") {
|
||||
let use_cp = std::env::var("NYASH_STR_CP").ok().as_deref() == Some("1");
|
||||
let n = if use_cp { sb_norm.value.chars().count() as i64 } else { sb_norm.value.len() as i64 };
|
||||
this.write_result(dst, VMValue::Integer(n));
|
||||
return Ok(true);
|
||||
}
|
||||
let ret = sb_norm.length();
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"replace" => {
|
||||
// replace(old, new) -> string with first occurrence replaced (Rust replace = all; match Core minimal
|
||||
this.validate_args_exact("replace", args, 2)?;
|
||||
let old_s = this.reg_load(args[0])?.to_string();
|
||||
let new_s = this.reg_load(args[1])?.to_string();
|
||||
// Core policy: replace only the first occurrence
|
||||
let out = if let Some(pos) = sb_norm.value.find(&old_s) {
|
||||
let mut s = String::with_capacity(sb_norm.value.len() + new_s.len());
|
||||
s.push_str(&sb_norm.value[..pos]);
|
||||
s.push_str(&new_s);
|
||||
s.push_str(&sb_norm.value[pos + old_s.len()..]);
|
||||
s
|
||||
} else {
|
||||
sb_norm.value.clone()
|
||||
};
|
||||
this.write_result(dst, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(out))));
|
||||
return Ok(true);
|
||||
}
|
||||
"trim" => {
|
||||
let ret = sb_norm.trim();
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"indexOf" => {
|
||||
// Support both 1-arg indexOf(search) and 2-arg indexOf(search, fromIndex)
|
||||
let (needle, from_index) = match args.len() {
|
||||
1 => {
|
||||
// indexOf(search) - search from beginning
|
||||
let n = this.reg_load(args[0])?.to_string();
|
||||
(n, 0)
|
||||
}
|
||||
2 => {
|
||||
// indexOf(search, fromIndex) - search from specified position
|
||||
let n = this.reg_load(args[0])?.to_string();
|
||||
let from = this.reg_load(args[1])?.as_integer().unwrap_or(0);
|
||||
(n, from.max(0) as usize)
|
||||
}
|
||||
_ => {
|
||||
return Err(this.err_invalid(
|
||||
"indexOf expects 1 or 2 args (search [, fromIndex])"
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
// Search for needle starting from from_index
|
||||
let search_str = if from_index >= sb_norm.value.len() {
|
||||
""
|
||||
} else {
|
||||
&sb_norm.value[from_index..]
|
||||
};
|
||||
|
||||
let idx = search_str.find(&needle)
|
||||
.map(|i| (from_index + i) as i64)
|
||||
.unwrap_or(-1);
|
||||
|
||||
this.write_result(dst, VMValue::Integer(idx));
|
||||
return Ok(true);
|
||||
}
|
||||
"contains" => {
|
||||
// contains(search) -> boolean (true if found, false otherwise)
|
||||
// Implemented as indexOf(search) >= 0
|
||||
this.validate_args_exact("contains", args, 1)?;
|
||||
let needle = this.reg_load(args[0])?.to_string();
|
||||
let found = sb_norm.value.contains(&needle);
|
||||
this.write_result(dst, VMValue::Bool(found));
|
||||
return Ok(true);
|
||||
}
|
||||
"lastIndexOf" => {
|
||||
// lastIndexOf(substr) -> last index or -1
|
||||
this.validate_args_exact("lastIndexOf", args, 1)?;
|
||||
let needle = this.reg_load(args[0])?.to_string();
|
||||
let idx = sb_norm.value.rfind(&needle).map(|i| i as i64).unwrap_or(-1);
|
||||
this.write_result(dst, VMValue::Integer(idx));
|
||||
return Ok(true);
|
||||
}
|
||||
"stringify" => {
|
||||
// JSON-style stringify for strings: quote and escape common characters
|
||||
let mut quoted = String::with_capacity(sb_norm.value.len() + 2);
|
||||
quoted.push('"');
|
||||
for ch in sb_norm.value.chars() {
|
||||
match ch {
|
||||
'"' => quoted.push_str("\\\""),
|
||||
'\\' => quoted.push_str("\\\\"),
|
||||
'\n' => quoted.push_str("\\n"),
|
||||
'\r' => quoted.push_str("\\r"),
|
||||
'\t' => quoted.push_str("\\t"),
|
||||
c if c.is_control() => quoted.push(' '),
|
||||
c => quoted.push(c),
|
||||
}
|
||||
}
|
||||
quoted.push('"');
|
||||
this.write_result(dst, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(quoted))));
|
||||
return Ok(true);
|
||||
}
|
||||
"substring" => {
|
||||
// Support both 1-arg (start to end) and 2-arg (start, end) forms
|
||||
let (s_idx, e_idx) = match args.len() {
|
||||
1 => {
|
||||
// substring(start) - from start to end of string
|
||||
let s = this.reg_load(args[0])?.as_integer().unwrap_or(0);
|
||||
let len = sb_norm.value.chars().count() as i64;
|
||||
(s, len)
|
||||
}
|
||||
2 => {
|
||||
// substring(start, end) - half-open interval [start, end)
|
||||
let s = this.reg_load(args[0])?.as_integer().unwrap_or(0);
|
||||
let e = this.reg_load(args[1])?.as_integer().unwrap_or(0);
|
||||
(s, e)
|
||||
}
|
||||
_ => {
|
||||
return Err(this.err_invalid(
|
||||
"substring expects 1 or 2 args (start [, end])"
|
||||
));
|
||||
}
|
||||
};
|
||||
let len = sb_norm.value.chars().count() as i64;
|
||||
let start = s_idx.max(0).min(len) as usize;
|
||||
let end = e_idx.max(start as i64).min(len) as usize;
|
||||
let chars: Vec<char> = sb_norm.value.chars().collect();
|
||||
let sub: String = chars[start..end].iter().collect();
|
||||
this.write_result(dst, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(sub))));
|
||||
return Ok(true);
|
||||
}
|
||||
"concat" => {
|
||||
this.validate_args_exact("concat", args, 1)?;
|
||||
let rhs = this.reg_load(args[0])?;
|
||||
let new_s = format!("{}{}", sb_norm.value, rhs.to_string());
|
||||
this.write_result(dst, VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(new_s))));
|
||||
return Ok(true);
|
||||
}
|
||||
"is_digit_char" => {
|
||||
// Accept either 0-arg (use first char of receiver) or 1-arg (string/char to test)
|
||||
let ch_opt = if args.is_empty() {
|
||||
sb_norm.value.chars().next()
|
||||
} else if args.len() == 1 {
|
||||
let s = this.reg_load(args[0])?.to_string();
|
||||
s.chars().next()
|
||||
} else {
|
||||
return Err(this.err_invalid("is_digit_char expects 0 or 1 arg"));
|
||||
};
|
||||
let is_digit = ch_opt.map(|c| c.is_ascii_digit()).unwrap_or(false);
|
||||
this.write_result(dst, VMValue::Bool(is_digit));
|
||||
return Ok(true);
|
||||
}
|
||||
"is_hex_digit_char" => {
|
||||
let ch_opt = if args.is_empty() {
|
||||
sb_norm.value.chars().next()
|
||||
} else if args.len() == 1 {
|
||||
let s = this.reg_load(args[0])?.to_string();
|
||||
s.chars().next()
|
||||
} else {
|
||||
return Err(this.err_invalid("is_hex_digit_char expects 0 or 1 arg"));
|
||||
};
|
||||
let is_hex = ch_opt.map(|c| c.is_ascii_hexdigit()).unwrap_or(false);
|
||||
this.write_result(dst, VMValue::Bool(is_hex));
|
||||
return Ok(true);
|
||||
}
|
||||
_ => {}
|
||||
"replace" => {
|
||||
// replace(old, new) -> string with first occurrence replaced (Rust replace = all; match Core minimal
|
||||
this.validate_args_exact("replace", args, 2)?;
|
||||
let old_s = this.reg_load(args[0])?.to_string();
|
||||
let new_s = this.reg_load(args[1])?.to_string();
|
||||
// Core policy: replace only the first occurrence
|
||||
let out = if let Some(pos) = sb_norm.value.find(&old_s) {
|
||||
let mut s = String::with_capacity(sb_norm.value.len() + new_s.len());
|
||||
s.push_str(&sb_norm.value[..pos]);
|
||||
s.push_str(&new_s);
|
||||
s.push_str(&sb_norm.value[pos + old_s.len()..]);
|
||||
s
|
||||
} else {
|
||||
sb_norm.value.clone()
|
||||
};
|
||||
this.write_result(
|
||||
dst,
|
||||
VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(out))),
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
Ok(false)
|
||||
"trim" => {
|
||||
let ret = sb_norm.trim();
|
||||
this.write_result(dst, VMValue::from_nyash_box(ret));
|
||||
return Ok(true);
|
||||
}
|
||||
"indexOf" => {
|
||||
// Support both 1-arg indexOf(search) and 2-arg indexOf(search, fromIndex)
|
||||
let (needle, from_index) = match args.len() {
|
||||
1 => {
|
||||
// indexOf(search) - search from beginning
|
||||
let n = this.reg_load(args[0])?.to_string();
|
||||
(n, 0)
|
||||
}
|
||||
2 => {
|
||||
// indexOf(search, fromIndex) - search from specified position
|
||||
let n = this.reg_load(args[0])?.to_string();
|
||||
let from = this.reg_load(args[1])?.as_integer().unwrap_or(0);
|
||||
(n, from.max(0) as usize)
|
||||
}
|
||||
_ => {
|
||||
return Err(
|
||||
this.err_invalid("indexOf expects 1 or 2 args (search [, fromIndex])")
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
// Search for needle starting from from_index
|
||||
let search_str = if from_index >= sb_norm.value.len() {
|
||||
""
|
||||
} else {
|
||||
&sb_norm.value[from_index..]
|
||||
};
|
||||
|
||||
let idx = search_str
|
||||
.find(&needle)
|
||||
.map(|i| (from_index + i) as i64)
|
||||
.unwrap_or(-1);
|
||||
|
||||
this.write_result(dst, VMValue::Integer(idx));
|
||||
return Ok(true);
|
||||
}
|
||||
"contains" => {
|
||||
// contains(search) -> boolean (true if found, false otherwise)
|
||||
// Implemented as indexOf(search) >= 0
|
||||
this.validate_args_exact("contains", args, 1)?;
|
||||
let needle = this.reg_load(args[0])?.to_string();
|
||||
let found = sb_norm.value.contains(&needle);
|
||||
this.write_result(dst, VMValue::Bool(found));
|
||||
return Ok(true);
|
||||
}
|
||||
"lastIndexOf" => {
|
||||
// lastIndexOf(substr) -> last index or -1
|
||||
this.validate_args_exact("lastIndexOf", args, 1)?;
|
||||
let needle = this.reg_load(args[0])?.to_string();
|
||||
let idx = sb_norm.value.rfind(&needle).map(|i| i as i64).unwrap_or(-1);
|
||||
this.write_result(dst, VMValue::Integer(idx));
|
||||
return Ok(true);
|
||||
}
|
||||
"stringify" => {
|
||||
// JSON-style stringify for strings: quote and escape common characters
|
||||
let mut quoted = String::with_capacity(sb_norm.value.len() + 2);
|
||||
quoted.push('"');
|
||||
for ch in sb_norm.value.chars() {
|
||||
match ch {
|
||||
'"' => quoted.push_str("\\\""),
|
||||
'\\' => quoted.push_str("\\\\"),
|
||||
'\n' => quoted.push_str("\\n"),
|
||||
'\r' => quoted.push_str("\\r"),
|
||||
'\t' => quoted.push_str("\\t"),
|
||||
c if c.is_control() => quoted.push(' '),
|
||||
c => quoted.push(c),
|
||||
}
|
||||
}
|
||||
quoted.push('"');
|
||||
this.write_result(
|
||||
dst,
|
||||
VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(quoted))),
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
"substring" => {
|
||||
// Support both 1-arg (start to end) and 2-arg (start, end) forms
|
||||
let (s_idx, e_idx) = match args.len() {
|
||||
1 => {
|
||||
// substring(start) - from start to end of string
|
||||
let s = this.reg_load(args[0])?.as_integer().unwrap_or(0);
|
||||
let len = sb_norm.value.chars().count() as i64;
|
||||
(s, len)
|
||||
}
|
||||
2 => {
|
||||
// substring(start, end) - half-open interval [start, end)
|
||||
let s = this.reg_load(args[0])?.as_integer().unwrap_or(0);
|
||||
let e = this.reg_load(args[1])?.as_integer().unwrap_or(0);
|
||||
(s, e)
|
||||
}
|
||||
_ => {
|
||||
return Err(this.err_invalid("substring expects 1 or 2 args (start [, end])"));
|
||||
}
|
||||
};
|
||||
let len = sb_norm.value.chars().count() as i64;
|
||||
let start = s_idx.max(0).min(len) as usize;
|
||||
let end = e_idx.max(start as i64).min(len) as usize;
|
||||
let chars: Vec<char> = sb_norm.value.chars().collect();
|
||||
let sub: String = chars[start..end].iter().collect();
|
||||
this.write_result(
|
||||
dst,
|
||||
VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(sub))),
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
"concat" => {
|
||||
this.validate_args_exact("concat", args, 1)?;
|
||||
let rhs = this.reg_load(args[0])?;
|
||||
let new_s = format!("{}{}", sb_norm.value, rhs.to_string());
|
||||
this.write_result(
|
||||
dst,
|
||||
VMValue::from_nyash_box(Box::new(crate::box_trait::StringBox::new(new_s))),
|
||||
);
|
||||
return Ok(true);
|
||||
}
|
||||
"is_digit_char" => {
|
||||
// Accept either 0-arg (use first char of receiver) or 1-arg (string/char to test)
|
||||
let ch_opt = if args.is_empty() {
|
||||
sb_norm.value.chars().next()
|
||||
} else if args.len() == 1 {
|
||||
let s = this.reg_load(args[0])?.to_string();
|
||||
s.chars().next()
|
||||
} else {
|
||||
return Err(this.err_invalid("is_digit_char expects 0 or 1 arg"));
|
||||
};
|
||||
let is_digit = ch_opt.map(|c| c.is_ascii_digit()).unwrap_or(false);
|
||||
this.write_result(dst, VMValue::Bool(is_digit));
|
||||
return Ok(true);
|
||||
}
|
||||
"is_hex_digit_char" => {
|
||||
let ch_opt = if args.is_empty() {
|
||||
sb_norm.value.chars().next()
|
||||
} else if args.len() == 1 {
|
||||
let s = this.reg_load(args[0])?.to_string();
|
||||
s.chars().next()
|
||||
} else {
|
||||
return Err(this.err_invalid("is_hex_digit_char expects 0 or 1 arg"));
|
||||
};
|
||||
let is_hex = ch_opt.map(|c| c.is_ascii_hexdigit()).unwrap_or(false);
|
||||
this.write_result(dst, VMValue::Bool(is_hex));
|
||||
return Ok(true);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
@ -22,9 +22,15 @@ impl MirInterpreter {
|
||||
match &v {
|
||||
VMValue::Void => println!("null"),
|
||||
VMValue::BoxRef(bx) => {
|
||||
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
|
||||
if bx
|
||||
.as_any()
|
||||
.downcast_ref::<crate::box_trait::VoidBox>()
|
||||
.is_some()
|
||||
{
|
||||
println!("null");
|
||||
} else if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||
} else if let Some(sb) =
|
||||
bx.as_any().downcast_ref::<crate::box_trait::StringBox>()
|
||||
{
|
||||
println!("{}", sb.value);
|
||||
} else {
|
||||
println!("{}", v.to_string());
|
||||
@ -56,8 +62,12 @@ impl MirInterpreter {
|
||||
};
|
||||
panic!("{}", msg);
|
||||
}
|
||||
"hostbridge.extern_invoke" => Err(self.err_invalid("hostbridge.extern_invoke should be routed via extern_provider_dispatch")),
|
||||
_ => Err(self.err_with_context("extern function", &format!("Unknown: {}", extern_name))),
|
||||
"hostbridge.extern_invoke" => Err(self.err_invalid(
|
||||
"hostbridge.extern_invoke should be routed via extern_provider_dispatch",
|
||||
)),
|
||||
_ => {
|
||||
Err(self.err_with_context("extern function", &format!("Unknown: {}", extern_name)))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,9 @@ impl MirInterpreter {
|
||||
// Module-local/global function: execute by function table if present (use original name)
|
||||
if let Some(func) = self.functions.get(func_name).cloned() {
|
||||
let mut argv: Vec<VMValue> = Vec::with_capacity(args.len());
|
||||
for a in args { argv.push(self.reg_load(*a)?); }
|
||||
for a in args {
|
||||
argv.push(self.reg_load(*a)?);
|
||||
}
|
||||
return self.exec_function_inner(&func, Some(&argv));
|
||||
}
|
||||
|
||||
@ -60,13 +62,19 @@ impl MirInterpreter {
|
||||
let s = self.reg_load(*a0)?.to_string();
|
||||
let opts = crate::host_providers::llvm_codegen::Opts {
|
||||
out: None,
|
||||
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from),
|
||||
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL").ok().or(Some("0".to_string())),
|
||||
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT")
|
||||
.ok()
|
||||
.map(std::path::PathBuf::from),
|
||||
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL")
|
||||
.ok()
|
||||
.or(Some("0".to_string())),
|
||||
timeout_ms: None,
|
||||
};
|
||||
match crate::host_providers::llvm_codegen::mir_json_to_object(&s, opts) {
|
||||
Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())),
|
||||
Err(e) => Err(self.err_with_context("env.codegen.emit_object", &e.to_string())),
|
||||
Err(e) => {
|
||||
Err(self.err_with_context("env.codegen.emit_object", &e.to_string()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Err(self.err_invalid("env.codegen.emit_object expects 1 arg"))
|
||||
@ -74,32 +82,51 @@ impl MirInterpreter {
|
||||
}
|
||||
"env.codegen.link_object" | "env.codegen.link_object/3" => {
|
||||
// C-API route only; args[2] is expected to be an ArrayBox [obj_path, exe_out?]
|
||||
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
|
||||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
|
||||
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1")
|
||||
|| std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
|
||||
.ok()
|
||||
.as_deref()
|
||||
!= Some("1")
|
||||
{
|
||||
return Err(self.err_invalid("env.codegen.link_object: C-API route disabled"));
|
||||
}
|
||||
if args.len() < 3 { return Err(self.err_arg_count("env.codegen.link_object", 3, args.len())); }
|
||||
if args.len() < 3 {
|
||||
return Err(self.err_arg_count("env.codegen.link_object", 3, args.len()));
|
||||
}
|
||||
let v = self.reg_load(args[2])?;
|
||||
let (obj_path, exe_out) = match v {
|
||||
VMValue::BoxRef(b) => {
|
||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||
let idx0: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(0));
|
||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>()
|
||||
{
|
||||
let idx0: Box<dyn crate::box_trait::NyashBox> =
|
||||
Box::new(crate::box_trait::IntegerBox::new(0));
|
||||
let elem0 = ab.get(idx0).to_string_box().value;
|
||||
let mut exe: Option<String> = None;
|
||||
let idx1: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(1));
|
||||
let idx1: Box<dyn crate::box_trait::NyashBox> =
|
||||
Box::new(crate::box_trait::IntegerBox::new(1));
|
||||
let e1 = ab.get(idx1).to_string_box().value;
|
||||
if !e1.is_empty() { exe = Some(e1); }
|
||||
if !e1.is_empty() {
|
||||
exe = Some(e1);
|
||||
}
|
||||
(elem0, exe)
|
||||
} else { (b.to_string_box().value, None) }
|
||||
} else {
|
||||
(b.to_string_box().value, None)
|
||||
}
|
||||
}
|
||||
_ => (v.to_string(), None),
|
||||
};
|
||||
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
|
||||
let obj = std::path::PathBuf::from(obj_path);
|
||||
let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
|
||||
match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) {
|
||||
let exe = exe_out
|
||||
.map(std::path::PathBuf::from)
|
||||
.unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
|
||||
match crate::host_providers::llvm_codegen::link_object_capi(
|
||||
&obj,
|
||||
&exe,
|
||||
extra.as_deref(),
|
||||
) {
|
||||
Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())),
|
||||
Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string()))
|
||||
Err(e) => Err(self.err_with_context("env.codegen.link_object", &e.to_string())),
|
||||
}
|
||||
}
|
||||
"nyash.builtin.error" => {
|
||||
|
||||
@ -22,18 +22,40 @@ impl MirInterpreter {
|
||||
if let Some(block) = func.blocks.get(&bb) {
|
||||
let mut last_recv: Option<ValueId> = None;
|
||||
for inst in &block.instructions {
|
||||
if let crate::mir::MirInstruction::NewBox { dst, box_type, .. } = inst {
|
||||
if box_type == box_name { last_recv = Some(*dst); }
|
||||
if let crate::mir::MirInstruction::NewBox {
|
||||
dst,
|
||||
box_type,
|
||||
..
|
||||
} = inst
|
||||
{
|
||||
if box_type == box_name {
|
||||
last_recv = Some(*dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(rid) = last_recv {
|
||||
if let Ok(v) = self.reg_load(rid) { v } else { return Err(e); }
|
||||
if let Ok(v) = self.reg_load(rid) {
|
||||
v
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
} else {
|
||||
// Dev fallback (guarded): use args[0] as surrogate receiver if explicitly allowed
|
||||
let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK").ok().as_deref() == Some("1")
|
||||
|| std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1");
|
||||
if tolerate { if let Some(a0) = args.get(0) { self.reg_load(*a0)? } else { return Err(e); } }
|
||||
else { return Err(e); }
|
||||
let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
|| std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref()
|
||||
== Some("1");
|
||||
if tolerate {
|
||||
if let Some(a0) = args.get(0) {
|
||||
self.reg_load(*a0)?
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Err(e);
|
||||
@ -43,10 +65,18 @@ impl MirInterpreter {
|
||||
}
|
||||
} else {
|
||||
// Dev fallback (guarded): use args[0] as surrogate receiver if explicitly allowed
|
||||
let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK").ok().as_deref() == Some("1")
|
||||
let tolerate = std::env::var("NYASH_VM_RECV_ARG_FALLBACK").ok().as_deref()
|
||||
== Some("1")
|
||||
|| std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1");
|
||||
if tolerate { if let Some(a0) = args.get(0) { self.reg_load(*a0)? } else { return Err(e); } }
|
||||
else { return Err(e); }
|
||||
if tolerate {
|
||||
if let Some(a0) = args.get(0) {
|
||||
self.reg_load(*a0)?
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
} else {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -57,7 +87,9 @@ impl MirInterpreter {
|
||||
// ArrayBox bridge
|
||||
if let Some(arr) = bx.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||
match method {
|
||||
"birth" => { return Ok(VMValue::Void); }
|
||||
"birth" => {
|
||||
return Ok(VMValue::Void);
|
||||
}
|
||||
"push" => {
|
||||
if let Some(a0) = args.get(0) {
|
||||
let v = self.load_as_box(*a0)?;
|
||||
@ -162,14 +194,20 @@ impl MirInterpreter {
|
||||
"substring" => {
|
||||
let start = if let Some(a0) = args.get(0) {
|
||||
self.reg_load(*a0)?.as_integer().unwrap_or(0)
|
||||
} else { 0 };
|
||||
} else {
|
||||
0
|
||||
};
|
||||
let end = if let Some(a1) = args.get(1) {
|
||||
self.reg_load(*a1)?.as_integer().unwrap_or(s.len() as i64)
|
||||
} else { s.len() as i64 };
|
||||
} else {
|
||||
s.len() as i64
|
||||
};
|
||||
let len = s.len() as i64;
|
||||
let i0 = start.max(0).min(len) as usize;
|
||||
let i1 = end.max(0).min(len) as usize;
|
||||
if i0 > i1 { return Ok(VMValue::String(String::new())); }
|
||||
if i0 > i1 {
|
||||
return Ok(VMValue::String(String::new()));
|
||||
}
|
||||
// Note: operating on bytes; Nyash strings are UTF‑8, but tests are ASCII only here
|
||||
let bytes = s.as_bytes();
|
||||
let sub = String::from_utf8(bytes[i0..i1].to_vec()).unwrap_or_default();
|
||||
@ -209,8 +247,7 @@ impl MirInterpreter {
|
||||
"is_space" => {
|
||||
if let Some(arg_id) = args.get(0) {
|
||||
let ch = self.reg_load(*arg_id)?.to_string();
|
||||
let is_ws =
|
||||
ch == " " || ch == "\t" || ch == "\n" || ch == "\r";
|
||||
let is_ws = ch == " " || ch == "\t" || ch == "\n" || ch == "\r";
|
||||
Ok(VMValue::Bool(is_ws))
|
||||
} else {
|
||||
Err(self.err_invalid("is_space requires 1 argument"))
|
||||
@ -221,10 +258,9 @@ impl MirInterpreter {
|
||||
if let Some(arg_id) = args.get(0) {
|
||||
let ch = self.reg_load(*arg_id)?.to_string();
|
||||
let c = ch.chars().next().unwrap_or('\0');
|
||||
let is_alpha =
|
||||
('A'..='Z').contains(&c) ||
|
||||
('a'..='z').contains(&c) ||
|
||||
c == '_';
|
||||
let is_alpha = ('A'..='Z').contains(&c)
|
||||
|| ('a'..='z').contains(&c)
|
||||
|| c == '_';
|
||||
Ok(VMValue::Bool(is_alpha))
|
||||
} else {
|
||||
Err(self.err_invalid("is_alpha requires 1 argument"))
|
||||
@ -234,8 +270,8 @@ impl MirInterpreter {
|
||||
}
|
||||
} else if let Some(p) = box_ref
|
||||
.as_any()
|
||||
.downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>()
|
||||
{
|
||||
.downcast_ref::<crate::runtime::plugin_loader_v2::PluginBoxV2>(
|
||||
) {
|
||||
let host = crate::runtime::plugin_loader_unified::get_global_plugin_host();
|
||||
let host = host.read().unwrap();
|
||||
let argv = self.load_args_as_boxes(args)?;
|
||||
@ -249,14 +285,17 @@ impl MirInterpreter {
|
||||
Ok(None) => Ok(VMValue::Void),
|
||||
Err(e) => Err(self.err_with_context(
|
||||
&format!("Plugin method {}.{}", p.box_type, method),
|
||||
&format!("{:?}", e)
|
||||
&format!("{:?}", e),
|
||||
)),
|
||||
}
|
||||
} else {
|
||||
Err(self.err_method_not_found(&box_ref.type_name(), method))
|
||||
}
|
||||
}
|
||||
_ => Err(self.err_with_context("method call", &format!("{} not supported on {:?}", method, receiver))),
|
||||
_ => Err(self.err_with_context(
|
||||
"method call",
|
||||
&format!("{} not supported on {:?}", method, receiver),
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,9 +4,9 @@
|
||||
|
||||
use super::*;
|
||||
|
||||
mod externs;
|
||||
mod global;
|
||||
mod method;
|
||||
mod externs;
|
||||
// legacy by-name resolver has been removed (Phase 2 complete)
|
||||
|
||||
impl MirInterpreter {
|
||||
@ -19,18 +19,43 @@ impl MirInterpreter {
|
||||
) -> Result<(), VMError> {
|
||||
if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") {
|
||||
match callee {
|
||||
Some(Callee::Global(n)) => eprintln!("[hb:path] call Callee::Global {} argc={}", n, args.len()),
|
||||
Some(Callee::Method{ box_name, method, ..}) => eprintln!("[hb:path] call Callee::Method {}.{} argc={}", box_name, method, args.len()),
|
||||
Some(Callee::Constructor{ box_type }) => eprintln!("[hb:path] call Callee::Constructor {} argc={}", box_type, args.len()),
|
||||
Some(Callee::Closure{ .. }) => eprintln!("[hb:path] call Callee::Closure argc={}", args.len()),
|
||||
Some(Callee::Value(_)) => eprintln!("[hb:path] call Callee::Value argc={}", args.len()),
|
||||
Some(Callee::Extern(n)) => eprintln!("[hb:path] call Callee::Extern {} argc={}", n, args.len()),
|
||||
None => eprintln!("[hb:path] call Legacy func_id={:?} argc={}", func, args.len()),
|
||||
Some(Callee::Global(n)) => {
|
||||
eprintln!("[hb:path] call Callee::Global {} argc={}", n, args.len())
|
||||
}
|
||||
Some(Callee::Method {
|
||||
box_name, method, ..
|
||||
}) => eprintln!(
|
||||
"[hb:path] call Callee::Method {}.{} argc={}",
|
||||
box_name,
|
||||
method,
|
||||
args.len()
|
||||
),
|
||||
Some(Callee::Constructor { box_type }) => eprintln!(
|
||||
"[hb:path] call Callee::Constructor {} argc={}",
|
||||
box_type,
|
||||
args.len()
|
||||
),
|
||||
Some(Callee::Closure { .. }) => {
|
||||
eprintln!("[hb:path] call Callee::Closure argc={}", args.len())
|
||||
}
|
||||
Some(Callee::Value(_)) => {
|
||||
eprintln!("[hb:path] call Callee::Value argc={}", args.len())
|
||||
}
|
||||
Some(Callee::Extern(n)) => {
|
||||
eprintln!("[hb:path] call Callee::Extern {} argc={}", n, args.len())
|
||||
}
|
||||
None => eprintln!(
|
||||
"[hb:path] call Legacy func_id={:?} argc={}",
|
||||
func,
|
||||
args.len()
|
||||
),
|
||||
}
|
||||
}
|
||||
// SSOT fast-path: route hostbridge.extern_invoke to extern dispatcher regardless of resolution form
|
||||
if let Some(Callee::Global(func_name)) = callee {
|
||||
if func_name == "hostbridge.extern_invoke" || func_name.starts_with("hostbridge.extern_invoke/") {
|
||||
if func_name == "hostbridge.extern_invoke"
|
||||
|| func_name.starts_with("hostbridge.extern_invoke/")
|
||||
{
|
||||
let v = self.execute_extern_function("hostbridge.extern_invoke", args)?;
|
||||
self.write_result(dst, v);
|
||||
return Ok(());
|
||||
@ -44,7 +69,9 @@ impl MirInterpreter {
|
||||
if let VMValue::String(ref s) = name_val {
|
||||
if let Some(f) = self.functions.get(s).cloned() {
|
||||
let mut argv: Vec<VMValue> = Vec::with_capacity(args.len());
|
||||
for a in args { argv.push(self.reg_load(*a)?); }
|
||||
for a in args {
|
||||
argv.push(self.reg_load(*a)?);
|
||||
}
|
||||
self.exec_function_inner(&f, Some(&argv))?
|
||||
} else {
|
||||
return Err(self.err_with_context("call", &format!(
|
||||
@ -53,7 +80,10 @@ impl MirInterpreter {
|
||||
)));
|
||||
}
|
||||
} else {
|
||||
return Err(self.err_with_context("call", "by-name calls unsupported without Callee attachment"));
|
||||
return Err(self.err_with_context(
|
||||
"call",
|
||||
"by-name calls unsupported without Callee attachment",
|
||||
));
|
||||
}
|
||||
};
|
||||
self.write_result(dst, call_result);
|
||||
@ -67,10 +97,15 @@ impl MirInterpreter {
|
||||
) -> Result<VMValue, VMError> {
|
||||
match callee {
|
||||
Callee::Global(func_name) => self.execute_global_function(func_name, args),
|
||||
Callee::Method { box_name, method, receiver, .. } => {
|
||||
self.execute_method_callee(box_name, method, receiver, args)
|
||||
Callee::Method {
|
||||
box_name,
|
||||
method,
|
||||
receiver,
|
||||
..
|
||||
} => self.execute_method_callee(box_name, method, receiver, args),
|
||||
Callee::Constructor { box_type } => {
|
||||
Err(self.err_unsupported(&format!("Constructor calls for {}", box_type)))
|
||||
}
|
||||
Callee::Constructor { box_type } => Err(self.err_unsupported(&format!("Constructor calls for {}", box_type))),
|
||||
Callee::Closure { .. } => Err(self.err_unsupported("Closure creation in VM")),
|
||||
Callee::Value(func_val_id) => {
|
||||
let _ = self.reg_load(*func_val_id)?;
|
||||
@ -79,5 +114,4 @@ impl MirInterpreter {
|
||||
Callee::Extern(extern_name) => self.execute_extern_function(extern_name, args),
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -10,8 +10,12 @@ impl MirInterpreter {
|
||||
let key = format!("{}.{}", target, method);
|
||||
for pat in flt.split(',') {
|
||||
let p = pat.trim();
|
||||
if p.is_empty() { continue; }
|
||||
if p == method || p == key { return true; }
|
||||
if p.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if p == method || p == key {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@ -23,7 +27,9 @@ impl MirInterpreter {
|
||||
if let JsonValue::Object(ref mut m) = v {
|
||||
if !m.contains_key("version") {
|
||||
m.insert("version".to_string(), JsonValue::from(0));
|
||||
if let Ok(out) = serde_json::to_string(&v) { return out; }
|
||||
if let Ok(out) = serde_json::to_string(&v) {
|
||||
return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
s.to_string()
|
||||
@ -64,9 +70,15 @@ impl MirInterpreter {
|
||||
VMValue::Void => println!("null"),
|
||||
VMValue::String(s) => println!("{}", s),
|
||||
VMValue::BoxRef(bx) => {
|
||||
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
|
||||
if bx
|
||||
.as_any()
|
||||
.downcast_ref::<crate::box_trait::VoidBox>()
|
||||
.is_some()
|
||||
{
|
||||
println!("null");
|
||||
} else if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||
} else if let Some(sb) =
|
||||
bx.as_any().downcast_ref::<crate::box_trait::StringBox>()
|
||||
{
|
||||
println!("{}", sb.value);
|
||||
} else {
|
||||
println!("{}", v.to_string());
|
||||
@ -90,9 +102,15 @@ impl MirInterpreter {
|
||||
VMValue::Void => eprintln!("[warn] null"),
|
||||
VMValue::String(s) => eprintln!("[warn] {}", s),
|
||||
VMValue::BoxRef(bx) => {
|
||||
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
|
||||
if bx
|
||||
.as_any()
|
||||
.downcast_ref::<crate::box_trait::VoidBox>()
|
||||
.is_some()
|
||||
{
|
||||
eprintln!("[warn] null");
|
||||
} else if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||
} else if let Some(sb) =
|
||||
bx.as_any().downcast_ref::<crate::box_trait::StringBox>()
|
||||
{
|
||||
eprintln!("[warn] {}", sb.value);
|
||||
} else {
|
||||
eprintln!("[warn] {}", v.to_string());
|
||||
@ -116,9 +134,15 @@ impl MirInterpreter {
|
||||
VMValue::Void => eprintln!("[error] null"),
|
||||
VMValue::String(s) => eprintln!("[error] {}", s),
|
||||
VMValue::BoxRef(bx) => {
|
||||
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
|
||||
if bx
|
||||
.as_any()
|
||||
.downcast_ref::<crate::box_trait::VoidBox>()
|
||||
.is_some()
|
||||
{
|
||||
eprintln!("[error] null");
|
||||
} else if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||
} else if let Some(sb) =
|
||||
bx.as_any().downcast_ref::<crate::box_trait::StringBox>()
|
||||
{
|
||||
eprintln!("[error] {}", sb.value);
|
||||
} else {
|
||||
eprintln!("[error] {}", v.to_string());
|
||||
@ -140,15 +164,29 @@ impl MirInterpreter {
|
||||
if std::env::var("HAKO_V1_EXTERN_PROVIDER").ok().as_deref() == Some("1") {
|
||||
return Some(Ok(VMValue::String(String::new())));
|
||||
}
|
||||
if args.is_empty() { return Some(Err(ErrorBuilder::arg_count_mismatch("env.mirbuilder.emit", 1, args.len()))); }
|
||||
let program_json = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
|
||||
if args.is_empty() {
|
||||
return Some(Err(ErrorBuilder::arg_count_mismatch(
|
||||
"env.mirbuilder.emit",
|
||||
1,
|
||||
args.len(),
|
||||
)));
|
||||
}
|
||||
let program_json = match self.reg_load(args[0]) {
|
||||
Ok(v) => v.to_string(),
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
|
||||
// Phase 21.8: Read imports from environment variable if present
|
||||
let imports = if let Ok(imports_json) = std::env::var("HAKO_MIRBUILDER_IMPORTS") {
|
||||
match serde_json::from_str::<std::collections::HashMap<String, String>>(&imports_json) {
|
||||
match serde_json::from_str::<std::collections::HashMap<String, String>>(
|
||||
&imports_json,
|
||||
) {
|
||||
Ok(map) => map,
|
||||
Err(e) => {
|
||||
eprintln!("[mirbuilder/imports] Failed to parse HAKO_MIRBUILDER_IMPORTS: {}", e);
|
||||
eprintln!(
|
||||
"[mirbuilder/imports] Failed to parse HAKO_MIRBUILDER_IMPORTS: {}",
|
||||
e
|
||||
);
|
||||
std::collections::HashMap::new()
|
||||
}
|
||||
}
|
||||
@ -156,10 +194,17 @@ impl MirInterpreter {
|
||||
std::collections::HashMap::new()
|
||||
};
|
||||
|
||||
let res = match crate::host_providers::mir_builder::program_json_to_mir_json_with_imports(&program_json, imports) {
|
||||
Ok(s) => Ok(VMValue::String(Self::patch_mir_json_version(&s))),
|
||||
Err(e) => Err(ErrorBuilder::with_context("env.mirbuilder.emit", &e.to_string())),
|
||||
};
|
||||
let res =
|
||||
match crate::host_providers::mir_builder::program_json_to_mir_json_with_imports(
|
||||
&program_json,
|
||||
imports,
|
||||
) {
|
||||
Ok(s) => Ok(VMValue::String(Self::patch_mir_json_version(&s))),
|
||||
Err(e) => Err(ErrorBuilder::with_context(
|
||||
"env.mirbuilder.emit",
|
||||
&e.to_string(),
|
||||
)),
|
||||
};
|
||||
Some(res)
|
||||
}
|
||||
"env.codegen.emit_object" => {
|
||||
@ -167,55 +212,114 @@ impl MirInterpreter {
|
||||
if std::env::var("HAKO_V1_EXTERN_PROVIDER").ok().as_deref() == Some("1") {
|
||||
return Some(Ok(VMValue::String(String::new())));
|
||||
}
|
||||
if args.is_empty() { return Some(Err(ErrorBuilder::arg_count_mismatch("env.codegen.emit_object", 1, args.len()))); }
|
||||
let mir_json_raw = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
|
||||
if args.is_empty() {
|
||||
return Some(Err(ErrorBuilder::arg_count_mismatch(
|
||||
"env.codegen.emit_object",
|
||||
1,
|
||||
args.len(),
|
||||
)));
|
||||
}
|
||||
let mir_json_raw = match self.reg_load(args[0]) {
|
||||
Ok(v) => v.to_string(),
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
// Normalize to v1 shape if missing/legacy (prevents harness NoneType errors)
|
||||
let mir_json = Self::patch_mir_json_version(&mir_json_raw);
|
||||
let opts = crate::host_providers::llvm_codegen::Opts {
|
||||
out: None,
|
||||
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from),
|
||||
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL").ok().or(Some("0".to_string())),
|
||||
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT")
|
||||
.ok()
|
||||
.map(std::path::PathBuf::from),
|
||||
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL")
|
||||
.ok()
|
||||
.or(Some("0".to_string())),
|
||||
timeout_ms: None,
|
||||
};
|
||||
let res = match crate::host_providers::llvm_codegen::mir_json_to_object(&mir_json, opts) {
|
||||
let res = match crate::host_providers::llvm_codegen::mir_json_to_object(
|
||||
&mir_json, opts,
|
||||
) {
|
||||
Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())),
|
||||
Err(e) => Err(ErrorBuilder::with_context("env.codegen.emit_object", &e.to_string())),
|
||||
Err(e) => Err(ErrorBuilder::with_context(
|
||||
"env.codegen.emit_object",
|
||||
&e.to_string(),
|
||||
)),
|
||||
};
|
||||
Some(res)
|
||||
}
|
||||
"env.codegen.link_object" => {
|
||||
// Only supported on C-API route; expect 1 or 2 args: obj_path [, exe_out]
|
||||
let obj_path = match args.get(0) {
|
||||
Some(v) => match self.reg_load(*v) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) },
|
||||
None => return Some(Err(self.err_invalid("env.codegen.link_object expects 1+ args"))),
|
||||
Some(v) => match self.reg_load(*v) {
|
||||
Ok(v) => v.to_string(),
|
||||
Err(e) => return Some(Err(e)),
|
||||
},
|
||||
None => {
|
||||
return Some(Err(
|
||||
self.err_invalid("env.codegen.link_object expects 1+ args")
|
||||
))
|
||||
}
|
||||
};
|
||||
let exe_out = match args.get(1) {
|
||||
Some(v) => Some(match self.reg_load(*v) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) }),
|
||||
Some(v) => Some(match self.reg_load(*v) {
|
||||
Ok(v) => v.to_string(),
|
||||
Err(e) => return Some(Err(e)),
|
||||
}),
|
||||
None => None,
|
||||
};
|
||||
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
|
||||
// Require C-API toggles
|
||||
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
|
||||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
|
||||
return Some(Err(ErrorBuilder::invalid_instruction("env.codegen.link_object: C-API route disabled")));
|
||||
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1")
|
||||
|| std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
|
||||
.ok()
|
||||
.as_deref()
|
||||
!= Some("1")
|
||||
{
|
||||
return Some(Err(ErrorBuilder::invalid_instruction(
|
||||
"env.codegen.link_object: C-API route disabled",
|
||||
)));
|
||||
}
|
||||
let obj = std::path::PathBuf::from(obj_path);
|
||||
let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
|
||||
match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) {
|
||||
let exe = exe_out
|
||||
.map(std::path::PathBuf::from)
|
||||
.unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
|
||||
match crate::host_providers::llvm_codegen::link_object_capi(
|
||||
&obj,
|
||||
&exe,
|
||||
extra.as_deref(),
|
||||
) {
|
||||
Ok(()) => Some(Ok(VMValue::String(exe.to_string_lossy().into_owned()))),
|
||||
Err(e) => Some(Err(ErrorBuilder::with_context("env.codegen.link_object", &e.to_string()))),
|
||||
Err(e) => Some(Err(ErrorBuilder::with_context(
|
||||
"env.codegen.link_object",
|
||||
&e.to_string(),
|
||||
))),
|
||||
}
|
||||
}
|
||||
// Environment
|
||||
"env.get" => {
|
||||
if args.is_empty() { return Some(Err(ErrorBuilder::arg_count_mismatch("env.get", 1, args.len()))); }
|
||||
let key = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
|
||||
if args.is_empty() {
|
||||
return Some(Err(ErrorBuilder::arg_count_mismatch(
|
||||
"env.get",
|
||||
1,
|
||||
args.len(),
|
||||
)));
|
||||
}
|
||||
let key = match self.reg_load(args[0]) {
|
||||
Ok(v) => v.to_string(),
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
let val = std::env::var(&key).ok();
|
||||
Some(Ok(match val { Some(s) => VMValue::String(s), None => VMValue::Void }))
|
||||
Some(Ok(match val {
|
||||
Some(s) => VMValue::String(s),
|
||||
None => VMValue::Void,
|
||||
}))
|
||||
}
|
||||
"env.set" => {
|
||||
if args.len() < 2 {
|
||||
return Some(Err(ErrorBuilder::arg_count_mismatch("env.set", 2, args.len())));
|
||||
return Some(Err(ErrorBuilder::arg_count_mismatch(
|
||||
"env.set",
|
||||
2,
|
||||
args.len(),
|
||||
)));
|
||||
}
|
||||
let key = match self.reg_load(args[0]) {
|
||||
Ok(v) => v.to_string(),
|
||||
@ -247,24 +351,24 @@ impl MirInterpreter {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Some(Err(self.err_invalid(
|
||||
"env.box_introspect.kind expects 1 arg",
|
||||
)));
|
||||
return Some(Err(
|
||||
self.err_invalid("env.box_introspect.kind expects 1 arg")
|
||||
));
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
let result = plugin_loader_v2::handle_box_introspect("kind", &collected);
|
||||
#[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))]
|
||||
let result: crate::bid::BidResult<Option<Box<dyn crate::box_trait::NyashBox>>> =
|
||||
Err(crate::bid::BidError::PluginError);
|
||||
let result: crate::bid::BidResult<
|
||||
Option<Box<dyn crate::box_trait::NyashBox>>,
|
||||
> = Err(crate::bid::BidError::PluginError);
|
||||
|
||||
match result {
|
||||
Ok(Some(b)) => Some(Ok(VMValue::BoxRef(Arc::from(b)))),
|
||||
Ok(None) => Some(Ok(VMValue::Void)),
|
||||
Err(e) => Some(Err(self.err_with_context(
|
||||
"env.box_introspect.kind",
|
||||
&format!("{:?}", e),
|
||||
))),
|
||||
Err(e) => Some(Err(
|
||||
self.err_with_context("env.box_introspect.kind", &format!("{:?}", e))
|
||||
)),
|
||||
}
|
||||
}
|
||||
"hostbridge.extern_invoke" => {
|
||||
@ -272,17 +376,30 @@ impl MirInterpreter {
|
||||
eprintln!("[hb:entry:provider] hostbridge.extern_invoke");
|
||||
}
|
||||
if args.len() < 2 {
|
||||
return Some(Err(self.err_invalid("extern_invoke expects at least 2 args")));
|
||||
return Some(Err(
|
||||
self.err_invalid("extern_invoke expects at least 2 args")
|
||||
));
|
||||
}
|
||||
let name = match self.reg_load(args[0]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
|
||||
let method = match self.reg_load(args[1]) { Ok(v) => v.to_string(), Err(e) => return Some(Err(e)) };
|
||||
let name = match self.reg_load(args[0]) {
|
||||
Ok(v) => v.to_string(),
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
let method = match self.reg_load(args[1]) {
|
||||
Ok(v) => v.to_string(),
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
// Extract first payload arg (optional)
|
||||
let mut first_arg_str: Option<String> = None;
|
||||
if let Some(a2) = args.get(2) {
|
||||
let v = match self.reg_load(*a2) { Ok(v) => v, Err(e) => return Some(Err(e)) };
|
||||
let v = match self.reg_load(*a2) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return Some(Err(e)),
|
||||
};
|
||||
match v {
|
||||
VMValue::BoxRef(b) => {
|
||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||
if let Some(ab) =
|
||||
b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>()
|
||||
{
|
||||
let idx: Box<dyn crate::box_trait::NyashBox> =
|
||||
Box::new(crate::box_trait::IntegerBox::new(0));
|
||||
let elem = ab.get(idx);
|
||||
@ -299,16 +416,27 @@ impl MirInterpreter {
|
||||
eprintln!("[hb:dispatch:provider] {} {}", name, method);
|
||||
}
|
||||
let out = match (name.as_str(), method.as_str()) {
|
||||
("env.codegen", "link_object") if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") => {
|
||||
("env.codegen", "link_object")
|
||||
if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") =>
|
||||
{
|
||||
// Trace payload shape before actual handling
|
||||
if let Some(a2) = args.get(2) {
|
||||
let v = match self.reg_load(*a2) { Ok(v) => v, Err(_) => VMValue::Void };
|
||||
let v = match self.reg_load(*a2) {
|
||||
Ok(v) => v,
|
||||
Err(_) => VMValue::Void,
|
||||
};
|
||||
match &v {
|
||||
VMValue::BoxRef(b) => {
|
||||
if b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>().is_some() {
|
||||
if b.as_any()
|
||||
.downcast_ref::<crate::boxes::array::ArrayBox>()
|
||||
.is_some()
|
||||
{
|
||||
eprintln!("[hb:provider:args] link_object third=ArrayBox");
|
||||
} else {
|
||||
eprintln!("[hb:provider:args] link_object third=BoxRef({})", b.type_name());
|
||||
eprintln!(
|
||||
"[hb:provider:args] link_object third=BoxRef({})",
|
||||
b.type_name()
|
||||
);
|
||||
}
|
||||
}
|
||||
other => {
|
||||
@ -327,13 +455,19 @@ impl MirInterpreter {
|
||||
};
|
||||
match v {
|
||||
VMValue::BoxRef(b) => {
|
||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||
let idx0: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(0));
|
||||
if let Some(ab) =
|
||||
b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>()
|
||||
{
|
||||
let idx0: Box<dyn crate::box_trait::NyashBox> =
|
||||
Box::new(crate::box_trait::IntegerBox::new(0));
|
||||
let elem0 = ab.get(idx0).to_string_box().value;
|
||||
let mut exe: Option<String> = None;
|
||||
let idx1: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(1));
|
||||
let idx1: Box<dyn crate::box_trait::NyashBox> =
|
||||
Box::new(crate::box_trait::IntegerBox::new(1));
|
||||
let e1 = ab.get(idx1).to_string_box().value;
|
||||
if !e1.is_empty() { exe = Some(e1); }
|
||||
if !e1.is_empty() {
|
||||
exe = Some(e1);
|
||||
}
|
||||
(elem0, exe)
|
||||
} else {
|
||||
(b.to_string_box().value, None)
|
||||
@ -342,25 +476,47 @@ impl MirInterpreter {
|
||||
_ => (v.to_string(), None),
|
||||
}
|
||||
} else {
|
||||
return Some(Err(self.err_invalid("extern_invoke env.codegen.link_object expects args array")));
|
||||
return Some(Err(self.err_invalid(
|
||||
"extern_invoke env.codegen.link_object expects args array",
|
||||
)));
|
||||
};
|
||||
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
|
||||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
|
||||
return Some(Err(ErrorBuilder::invalid_instruction("env.codegen.link_object: C-API route disabled")));
|
||||
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1")
|
||||
|| std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
|
||||
.ok()
|
||||
.as_deref()
|
||||
!= Some("1")
|
||||
{
|
||||
return Some(Err(ErrorBuilder::invalid_instruction(
|
||||
"env.codegen.link_object: C-API route disabled",
|
||||
)));
|
||||
}
|
||||
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
|
||||
let obj = std::path::PathBuf::from(objs);
|
||||
let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
|
||||
match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) {
|
||||
let exe = exe_out
|
||||
.map(std::path::PathBuf::from)
|
||||
.unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
|
||||
match crate::host_providers::llvm_codegen::link_object_capi(
|
||||
&obj,
|
||||
&exe,
|
||||
extra.as_deref(),
|
||||
) {
|
||||
Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())),
|
||||
Err(e) => Err(ErrorBuilder::with_context("env.codegen.link_object", &e.to_string()))
|
||||
Err(e) => Err(ErrorBuilder::with_context(
|
||||
"env.codegen.link_object",
|
||||
&e.to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
("env.mirbuilder", "emit") => {
|
||||
if let Some(s) = first_arg_str {
|
||||
// Phase 21.8: Read imports from environment variable if present
|
||||
let imports = if let Ok(imports_json) = std::env::var("HAKO_MIRBUILDER_IMPORTS") {
|
||||
match serde_json::from_str::<std::collections::HashMap<String, String>>(&imports_json) {
|
||||
let imports = if let Ok(imports_json) =
|
||||
std::env::var("HAKO_MIRBUILDER_IMPORTS")
|
||||
{
|
||||
match serde_json::from_str::<
|
||||
std::collections::HashMap<String, String>,
|
||||
>(&imports_json)
|
||||
{
|
||||
Ok(map) => map,
|
||||
Err(e) => {
|
||||
eprintln!("[mirbuilder/imports] Failed to parse HAKO_MIRBUILDER_IMPORTS: {}", e);
|
||||
@ -383,16 +539,21 @@ impl MirInterpreter {
|
||||
if let Some(s) = first_arg_str {
|
||||
let opts = crate::host_providers::llvm_codegen::Opts {
|
||||
out: None,
|
||||
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT").ok().map(std::path::PathBuf::from),
|
||||
nyrt: std::env::var("NYASH_EMIT_EXE_NYRT")
|
||||
.ok()
|
||||
.map(std::path::PathBuf::from),
|
||||
opt_level: std::env::var("HAKO_LLVM_OPT_LEVEL").ok(),
|
||||
timeout_ms: None,
|
||||
};
|
||||
match crate::host_providers::llvm_codegen::mir_json_to_object(&s, opts) {
|
||||
match crate::host_providers::llvm_codegen::mir_json_to_object(&s, opts)
|
||||
{
|
||||
Ok(p) => Ok(VMValue::String(p.to_string_lossy().into_owned())),
|
||||
Err(e) => Err(self.err_with_context("env.codegen.emit_object", &e.to_string())),
|
||||
Err(e) => Err(self
|
||||
.err_with_context("env.codegen.emit_object", &e.to_string())),
|
||||
}
|
||||
} else {
|
||||
Err(self.err_invalid("extern_invoke env.codegen.emit_object expects 1 arg"))
|
||||
Err(self
|
||||
.err_invalid("extern_invoke env.codegen.emit_object expects 1 arg"))
|
||||
}
|
||||
}
|
||||
("env.codegen", "link_object") => {
|
||||
@ -408,12 +569,18 @@ impl MirInterpreter {
|
||||
};
|
||||
match v {
|
||||
VMValue::BoxRef(b) => {
|
||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||
let idx0: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(0));
|
||||
if let Some(ab) =
|
||||
b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>()
|
||||
{
|
||||
let idx0: Box<dyn crate::box_trait::NyashBox> =
|
||||
Box::new(crate::box_trait::IntegerBox::new(0));
|
||||
obj_s = Some(ab.get(idx0).to_string_box().value);
|
||||
let idx1: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(1));
|
||||
let idx1: Box<dyn crate::box_trait::NyashBox> =
|
||||
Box::new(crate::box_trait::IntegerBox::new(1));
|
||||
let s1 = ab.get(idx1).to_string_box().value;
|
||||
if !s1.is_empty() { exe_s = Some(s1); }
|
||||
if !s1.is_empty() {
|
||||
exe_s = Some(s1);
|
||||
}
|
||||
} else {
|
||||
obj_s = Some(b.to_string_box().value);
|
||||
}
|
||||
@ -426,18 +593,37 @@ impl MirInterpreter {
|
||||
}
|
||||
let objs = match obj_s {
|
||||
Some(s) => s,
|
||||
None => return Some(Err(self.err_invalid("extern_invoke env.codegen.link_object expects args"))),
|
||||
None => {
|
||||
return Some(Err(self.err_invalid(
|
||||
"extern_invoke env.codegen.link_object expects args",
|
||||
)))
|
||||
}
|
||||
};
|
||||
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
|
||||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
|
||||
return Some(Err(ErrorBuilder::invalid_instruction("env.codegen.link_object: C-API route disabled")));
|
||||
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1")
|
||||
|| std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
|
||||
.ok()
|
||||
.as_deref()
|
||||
!= Some("1")
|
||||
{
|
||||
return Some(Err(ErrorBuilder::invalid_instruction(
|
||||
"env.codegen.link_object: C-API route disabled",
|
||||
)));
|
||||
}
|
||||
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
|
||||
let obj = std::path::PathBuf::from(objs);
|
||||
let exe = exe_s.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
|
||||
match crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref()) {
|
||||
let exe = exe_s
|
||||
.map(std::path::PathBuf::from)
|
||||
.unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
|
||||
match crate::host_providers::llvm_codegen::link_object_capi(
|
||||
&obj,
|
||||
&exe,
|
||||
extra.as_deref(),
|
||||
) {
|
||||
Ok(()) => Ok(VMValue::String(exe.to_string_lossy().into_owned())),
|
||||
Err(e) => Err(ErrorBuilder::with_context("env.codegen.link_object", &e.to_string()))
|
||||
Err(e) => Err(ErrorBuilder::with_context(
|
||||
"env.codegen.link_object",
|
||||
&e.to_string(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
("env.box_introspect", "kind") => {
|
||||
@ -487,9 +673,7 @@ impl MirInterpreter {
|
||||
}
|
||||
}
|
||||
other => {
|
||||
if std::env::var("NYASH_BOX_INTROSPECT_TRACE")
|
||||
.ok()
|
||||
.as_deref()
|
||||
if std::env::var("NYASH_BOX_INTROSPECT_TRACE").ok().as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
eprintln!(
|
||||
@ -509,16 +693,15 @@ impl MirInterpreter {
|
||||
#[cfg(all(feature = "plugins", not(target_arch = "wasm32")))]
|
||||
let result = plugin_loader_v2::handle_box_introspect("kind", &collected);
|
||||
#[cfg(any(not(feature = "plugins"), target_arch = "wasm32"))]
|
||||
let result: crate::bid::BidResult<Option<Box<dyn NyashBox>>> =
|
||||
Err(crate::bid::BidError::PluginError);
|
||||
let result: crate::bid::BidResult<
|
||||
Option<Box<dyn NyashBox>>,
|
||||
> = Err(crate::bid::BidError::PluginError);
|
||||
|
||||
match result {
|
||||
Ok(Some(b)) => Ok(VMValue::BoxRef(Arc::from(b))),
|
||||
Ok(None) => Ok(VMValue::Void),
|
||||
Err(e) => Err(self.err_with_context(
|
||||
"env.box_introspect.kind",
|
||||
&format!("{:?}", e),
|
||||
)),
|
||||
Err(e) => Err(self
|
||||
.err_with_context("env.box_introspect.kind", &format!("{:?}", e))),
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@ -529,7 +712,7 @@ impl MirInterpreter {
|
||||
"hostbridge.extern_invoke unsupported for {}.{} [provider] (check extern_provider_dispatch / env.* handlers)",
|
||||
name, method
|
||||
)))
|
||||
},
|
||||
}
|
||||
};
|
||||
Some(out)
|
||||
}
|
||||
|
||||
@ -10,7 +10,9 @@ impl MirInterpreter {
|
||||
if let JsonValue::Object(ref mut m) = v {
|
||||
if !m.contains_key("version") {
|
||||
m.insert("version".to_string(), JsonValue::from(0));
|
||||
if let Ok(out) = serde_json::to_string(&v) { return out; }
|
||||
if let Ok(out) = serde_json::to_string(&v) {
|
||||
return out;
|
||||
}
|
||||
}
|
||||
}
|
||||
s.to_string()
|
||||
@ -52,23 +54,47 @@ impl MirInterpreter {
|
||||
if let Some(a0) = args.get(0) {
|
||||
let v = self.reg_load(*a0)?;
|
||||
// Dev-only: mirror print-trace for extern console.log
|
||||
if Self::print_trace_enabled() { self.print_trace_emit(&v); }
|
||||
if Self::print_trace_enabled() {
|
||||
self.print_trace_emit(&v);
|
||||
}
|
||||
// Treat VM Void and BoxRef(VoidBox) as JSON null for dev ergonomics
|
||||
match &v {
|
||||
VMValue::Void => { println!("null"); self.write_void(dst); return Ok(()); }
|
||||
VMValue::Void => {
|
||||
println!("null");
|
||||
self.write_void(dst);
|
||||
return Ok(());
|
||||
}
|
||||
VMValue::BoxRef(bx) => {
|
||||
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
|
||||
println!("null"); self.write_void(dst); return Ok(());
|
||||
if bx
|
||||
.as_any()
|
||||
.downcast_ref::<crate::box_trait::VoidBox>()
|
||||
.is_some()
|
||||
{
|
||||
println!("null");
|
||||
self.write_void(dst);
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||
println!("{}", sb.value); self.write_void(dst); return Ok(());
|
||||
if let Some(sb) =
|
||||
bx.as_any().downcast_ref::<crate::box_trait::StringBox>()
|
||||
{
|
||||
println!("{}", sb.value);
|
||||
self.write_void(dst);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
VMValue::String(s) => { println!("{}", s); self.write_void(dst); return Ok(()); }
|
||||
VMValue::String(s) => {
|
||||
println!("{}", s);
|
||||
self.write_void(dst);
|
||||
return Ok(());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
// Operator Box (Stringify) – dev flag gated
|
||||
if std::env::var("NYASH_OPERATOR_BOX_STRINGIFY").ok().as_deref() == Some("1") {
|
||||
if std::env::var("NYASH_OPERATOR_BOX_STRINGIFY")
|
||||
.ok()
|
||||
.as_deref()
|
||||
== Some("1")
|
||||
{
|
||||
if let Some(op) = self.functions.get("StringifyOperator.apply/1").cloned() {
|
||||
let out = self.exec_function_inner(&op, Some(&[v.clone()]))?;
|
||||
println!("{}", out.to_string());
|
||||
@ -151,20 +177,28 @@ impl MirInterpreter {
|
||||
Ok(())
|
||||
}
|
||||
("env.mirbuilder", "emit") => {
|
||||
let ret = self.extern_provider_dispatch("env.mirbuilder.emit", args).unwrap_or(Ok(VMValue::Void))?;
|
||||
let ret = self
|
||||
.extern_provider_dispatch("env.mirbuilder.emit", args)
|
||||
.unwrap_or(Ok(VMValue::Void))?;
|
||||
self.write_result(dst, ret);
|
||||
Ok(())
|
||||
}
|
||||
("env.codegen", "emit_object") => {
|
||||
let ret = self.extern_provider_dispatch("env.codegen.emit_object", args).unwrap_or(Ok(VMValue::Void))?;
|
||||
let ret = self
|
||||
.extern_provider_dispatch("env.codegen.emit_object", args)
|
||||
.unwrap_or(Ok(VMValue::Void))?;
|
||||
self.write_result(dst, ret);
|
||||
Ok(())
|
||||
}
|
||||
("env.codegen", "link_object") => {
|
||||
// Args in third param (ArrayBox): [obj_path, exe_out?]
|
||||
// Note: This branch is used for ExternCall form; provider toggles must be ON.
|
||||
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1") ||
|
||||
std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI").ok().as_deref() != Some("1") {
|
||||
if std::env::var("NYASH_LLVM_USE_CAPI").ok().as_deref() != Some("1")
|
||||
|| std::env::var("HAKO_V1_EXTERN_PROVIDER_C_ABI")
|
||||
.ok()
|
||||
.as_deref()
|
||||
!= Some("1")
|
||||
{
|
||||
return Err(self.err_invalid("env.codegen.link_object: C-API route disabled"));
|
||||
}
|
||||
// Extract array payload
|
||||
@ -172,13 +206,19 @@ impl MirInterpreter {
|
||||
let v = self.reg_load(*a2)?;
|
||||
match v {
|
||||
VMValue::BoxRef(b) => {
|
||||
if let Some(ab) = b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>() {
|
||||
let idx0: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(0));
|
||||
if let Some(ab) =
|
||||
b.as_any().downcast_ref::<crate::boxes::array::ArrayBox>()
|
||||
{
|
||||
let idx0: Box<dyn crate::box_trait::NyashBox> =
|
||||
Box::new(crate::box_trait::IntegerBox::new(0));
|
||||
let elem0 = ab.get(idx0).to_string_box().value;
|
||||
let mut exe: Option<String> = None;
|
||||
let idx1: Box<dyn crate::box_trait::NyashBox> = Box::new(crate::box_trait::IntegerBox::new(1));
|
||||
let idx1: Box<dyn crate::box_trait::NyashBox> =
|
||||
Box::new(crate::box_trait::IntegerBox::new(1));
|
||||
let e1 = ab.get(idx1).to_string_box().value;
|
||||
if !e1.is_empty() { exe = Some(e1); }
|
||||
if !e1.is_empty() {
|
||||
exe = Some(e1);
|
||||
}
|
||||
(elem0, exe)
|
||||
} else {
|
||||
(b.to_string_box().value, None)
|
||||
@ -187,13 +227,18 @@ impl MirInterpreter {
|
||||
_ => (v.to_string(), None),
|
||||
}
|
||||
} else {
|
||||
return Err(self.err_invalid("extern_invoke env.codegen.link_object expects args array"));
|
||||
return Err(self
|
||||
.err_invalid("extern_invoke env.codegen.link_object expects args array"));
|
||||
};
|
||||
let extra = std::env::var("HAKO_AOT_LDFLAGS").ok();
|
||||
let obj = std::path::PathBuf::from(obj_path);
|
||||
let exe = exe_out.map(std::path::PathBuf::from).unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
|
||||
let exe = exe_out
|
||||
.map(std::path::PathBuf::from)
|
||||
.unwrap_or_else(|| std::env::temp_dir().join("hako_link_out.exe"));
|
||||
crate::host_providers::llvm_codegen::link_object_capi(&obj, &exe, extra.as_deref())
|
||||
.map_err(|e| self.err_with_context("env.codegen.link_object", &e.to_string()))?;
|
||||
.map_err(|e| {
|
||||
self.err_with_context("env.codegen.link_object", &e.to_string())
|
||||
})?;
|
||||
self.write_result(dst, VMValue::String(exe.to_string_lossy().into_owned()));
|
||||
Ok(())
|
||||
}
|
||||
@ -208,17 +253,18 @@ impl MirInterpreter {
|
||||
("hostbridge", "extern_invoke") => {
|
||||
if let Some(res) = self.extern_provider_dispatch("hostbridge.extern_invoke", args) {
|
||||
match res {
|
||||
Ok(v) => { self.write_result(dst, v); }
|
||||
Err(e) => { return Err(e); }
|
||||
Ok(v) => {
|
||||
self.write_result(dst, v);
|
||||
}
|
||||
Err(e) => {
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
return Err(self.err_invalid("hostbridge.extern_invoke unsupported [externals]"));
|
||||
}
|
||||
_ => Err(self.err_invalid(format!(
|
||||
"ExternCall {}.{} not supported",
|
||||
iface, method
|
||||
))),
|
||||
_ => Err(self.err_invalid(format!("ExternCall {}.{} not supported", iface, method))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,16 +13,28 @@ impl MirInterpreter {
|
||||
let v = self.reg_load(value)?;
|
||||
// Align with calls.rs behavior: Void/BoxRef(VoidBox) prints as null; raw String/StringBox unquoted
|
||||
match &v {
|
||||
VMValue::Void => { println!("null"); return Ok(()); }
|
||||
VMValue::Void => {
|
||||
println!("null");
|
||||
return Ok(());
|
||||
}
|
||||
VMValue::BoxRef(bx) => {
|
||||
if bx.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() {
|
||||
println!("null"); return Ok(());
|
||||
if bx
|
||||
.as_any()
|
||||
.downcast_ref::<crate::box_trait::VoidBox>()
|
||||
.is_some()
|
||||
{
|
||||
println!("null");
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(sb) = bx.as_any().downcast_ref::<crate::box_trait::StringBox>() {
|
||||
println!("{}", sb.value); return Ok(());
|
||||
println!("{}", sb.value);
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
VMValue::String(s) => { println!("{}", s); return Ok(()); }
|
||||
VMValue::String(s) => {
|
||||
println!("{}", s);
|
||||
return Ok(());
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
println!("{}", v.to_string());
|
||||
|
||||
@ -3,9 +3,7 @@ use super::*;
|
||||
// VM dispatch trace macro (used across handlers)
|
||||
macro_rules! trace_dispatch {
|
||||
($method:expr, $handler:expr) => {
|
||||
if $method == "length"
|
||||
&& std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1")
|
||||
{
|
||||
if $method == "length" && std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[vm-trace] length dispatch handler={}", $handler);
|
||||
}
|
||||
};
|
||||
@ -14,15 +12,15 @@ macro_rules! trace_dispatch {
|
||||
mod arithmetic;
|
||||
mod boxes;
|
||||
mod boxes_array;
|
||||
mod boxes_string;
|
||||
mod boxes_instance;
|
||||
mod boxes_map;
|
||||
mod boxes_object_fields;
|
||||
mod boxes_instance;
|
||||
mod boxes_plugin;
|
||||
mod boxes_string;
|
||||
mod boxes_void_guards;
|
||||
mod calls;
|
||||
mod externals;
|
||||
mod extern_provider;
|
||||
mod externals;
|
||||
mod memory;
|
||||
mod misc;
|
||||
|
||||
@ -90,10 +88,7 @@ impl MirInterpreter {
|
||||
}
|
||||
MirInstruction::DebugLog { message, values } => {
|
||||
// Dev-only: MIR-level debug logging (no new values defined).
|
||||
if std::env::var("NYASH_MIR_DEBUG_LOG")
|
||||
.ok()
|
||||
.as_deref() == Some("1")
|
||||
{
|
||||
if std::env::var("NYASH_MIR_DEBUG_LOG").ok().as_deref() == Some("1") {
|
||||
eprint!("[MIR-LOG] {}:", message);
|
||||
for vid in values {
|
||||
let v = self.reg_load(*vid).unwrap_or(VMValue::Void);
|
||||
|
||||
@ -8,10 +8,26 @@ impl MirInterpreter {
|
||||
match v {
|
||||
VMValue::Void => "void",
|
||||
VMValue::BoxRef(b) => {
|
||||
if b.as_any().downcast_ref::<crate::boxes::null_box::NullBox>().is_some() { "null" }
|
||||
else if b.as_any().downcast_ref::<crate::boxes::missing_box::MissingBox>().is_some() { "missing" }
|
||||
else if b.as_any().downcast_ref::<crate::box_trait::VoidBox>().is_some() { "void" }
|
||||
else { "" }
|
||||
if b.as_any()
|
||||
.downcast_ref::<crate::boxes::null_box::NullBox>()
|
||||
.is_some()
|
||||
{
|
||||
"null"
|
||||
} else if b
|
||||
.as_any()
|
||||
.downcast_ref::<crate::boxes::missing_box::MissingBox>()
|
||||
.is_some()
|
||||
{
|
||||
"missing"
|
||||
} else if b
|
||||
.as_any()
|
||||
.downcast_ref::<crate::box_trait::VoidBox>()
|
||||
.is_some()
|
||||
{
|
||||
"void"
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
_ => "",
|
||||
}
|
||||
@ -23,11 +39,7 @@ impl MirInterpreter {
|
||||
if std::env::var("NYASH_VM_TRACE").ok().as_deref() == Some("1")
|
||||
|| std::env::var("NYASH_VM_TRACE_EXEC").ok().as_deref() == Some("1")
|
||||
{
|
||||
let keys: Vec<String> = self
|
||||
.regs
|
||||
.keys()
|
||||
.map(|k| format!("{:?}", k))
|
||||
.collect();
|
||||
let keys: Vec<String> = self.regs.keys().map(|k| format!("{:?}", k)).collect();
|
||||
eprintln!(
|
||||
"[vm-trace] reg_load undefined id={:?} fn={} last_block={:?} last_inst={:?} regs={}",
|
||||
id,
|
||||
@ -39,8 +51,16 @@ impl MirInterpreter {
|
||||
}
|
||||
// Dev-time safety valve: tolerate undefined registers as Void when enabled
|
||||
let tolerate = std::env::var("NYASH_VM_TOLERATE_VOID").ok().as_deref() == Some("1")
|
||||
|| std::env::var("HAKO_PHI_VERIFY").ok().map(|v| v.to_ascii_lowercase()).map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false)
|
||||
|| std::env::var("NYASH_PHI_VERIFY").ok().map(|v| v.to_ascii_lowercase()).map(|v| v=="1"||v=="true"||v=="on").unwrap_or(false);
|
||||
|| std::env::var("HAKO_PHI_VERIFY")
|
||||
.ok()
|
||||
.map(|v| v.to_ascii_lowercase())
|
||||
.map(|v| v == "1" || v == "true" || v == "on")
|
||||
.unwrap_or(false)
|
||||
|| std::env::var("NYASH_PHI_VERIFY")
|
||||
.ok()
|
||||
.map(|v| v.to_ascii_lowercase())
|
||||
.map(|v| v == "1" || v == "true" || v == "on")
|
||||
.unwrap_or(false);
|
||||
if tolerate {
|
||||
return Ok(VMValue::Void);
|
||||
}
|
||||
@ -87,21 +107,43 @@ impl MirInterpreter {
|
||||
v
|
||||
};
|
||||
(norm(a), norm(b))
|
||||
} else { (a, b) };
|
||||
} else {
|
||||
(a, b)
|
||||
};
|
||||
// Dev: nullish trace for binop
|
||||
if crate::config::env::null_missing_box_enabled() && Self::box_trace_enabled() {
|
||||
let (ak, bk) = (crate::backend::abi_util::tag_of_vm(&a), crate::backend::abi_util::tag_of_vm(&b));
|
||||
let (ak, bk) = (
|
||||
crate::backend::abi_util::tag_of_vm(&a),
|
||||
crate::backend::abi_util::tag_of_vm(&b),
|
||||
);
|
||||
let (an, bn) = (Self::tag_nullish(&a), Self::tag_nullish(&b));
|
||||
let op_s = match op { BinaryOp::Add=>"Add", BinaryOp::Sub=>"Sub", BinaryOp::Mul=>"Mul", BinaryOp::Div=>"Div", BinaryOp::Mod=>"Mod", BinaryOp::BitAnd=>"BitAnd", BinaryOp::BitOr=>"BitOr", BinaryOp::BitXor=>"BitXor", BinaryOp::And=>"And", BinaryOp::Or=>"Or", BinaryOp::Shl=>"Shl", BinaryOp::Shr=>"Shr" };
|
||||
let op_s = match op {
|
||||
BinaryOp::Add => "Add",
|
||||
BinaryOp::Sub => "Sub",
|
||||
BinaryOp::Mul => "Mul",
|
||||
BinaryOp::Div => "Div",
|
||||
BinaryOp::Mod => "Mod",
|
||||
BinaryOp::BitAnd => "BitAnd",
|
||||
BinaryOp::BitOr => "BitOr",
|
||||
BinaryOp::BitXor => "BitXor",
|
||||
BinaryOp::And => "And",
|
||||
BinaryOp::Or => "Or",
|
||||
BinaryOp::Shl => "Shl",
|
||||
BinaryOp::Shr => "Shr",
|
||||
};
|
||||
eprintln!("{{\"ev\":\"binop\",\"op\":\"{}\",\"a_k\":\"{}\",\"b_k\":\"{}\",\"a_n\":\"{}\",\"b_n\":\"{}\"}}", op_s, ak, bk, an, bn);
|
||||
}
|
||||
Ok(match (op, a, b) {
|
||||
// Dev-only safety valves for Add (guarded by tolerance or --dev):
|
||||
// - Treat Void as 0 for numeric +
|
||||
// - Treat Void as empty string for string +
|
||||
(Add, VMValue::Void, Integer(y)) | (Add, Integer(y), VMValue::Void) if tolerate => Integer(y),
|
||||
(Add, VMValue::Void, Integer(y)) | (Add, Integer(y), VMValue::Void) if tolerate => {
|
||||
Integer(y)
|
||||
}
|
||||
(Add, VMValue::Void, Float(y)) | (Add, Float(y), VMValue::Void) if tolerate => Float(y),
|
||||
(Add, String(s), VMValue::Void) | (Add, VMValue::Void, String(s)) if tolerate => String(s),
|
||||
(Add, String(s), VMValue::Void) | (Add, VMValue::Void, String(s)) if tolerate => {
|
||||
String(s)
|
||||
}
|
||||
// Dev-only safety valve for Sub (guarded): treat Void as 0
|
||||
(Sub, Integer(x), VMValue::Void) if tolerate => Integer(x),
|
||||
(Sub, VMValue::Void, Integer(y)) if tolerate => Integer(0 - y),
|
||||
@ -129,7 +171,7 @@ impl MirInterpreter {
|
||||
(BitOr, Integer(x), Integer(y)) => Integer(x | y),
|
||||
(BitXor, Integer(x), Integer(y)) => Integer(x ^ y),
|
||||
(And, VMValue::Bool(x), VMValue::Bool(y)) => VMValue::Bool(x && y),
|
||||
(Or, VMValue::Bool(x), VMValue::Bool(y)) => VMValue::Bool(x || y),
|
||||
(Or, VMValue::Bool(x), VMValue::Bool(y)) => VMValue::Bool(x || y),
|
||||
(Shl, Integer(x), Integer(y)) => Integer(x.wrapping_shl(y as u32)),
|
||||
(Shr, Integer(x), Integer(y)) => Integer(x.wrapping_shr(y as u32)),
|
||||
(opk, va, vb) => {
|
||||
@ -142,7 +184,7 @@ impl MirInterpreter {
|
||||
return Err(VMError::TypeError(format!(
|
||||
"unsupported binop {:?} on {:?} and {:?}",
|
||||
opk, va, vb
|
||||
)))
|
||||
)));
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -162,7 +204,9 @@ impl MirInterpreter {
|
||||
v
|
||||
};
|
||||
(norm(a), norm(b))
|
||||
} else { (a, b) };
|
||||
} else {
|
||||
(a, b)
|
||||
};
|
||||
// Dev-only safety valve: tolerate Void in comparisons when enabled or in --dev
|
||||
// → treat Void as 0 for numeric, empty for string
|
||||
let (a2, b2) = if tolerate {
|
||||
@ -195,9 +239,19 @@ impl MirInterpreter {
|
||||
};
|
||||
// Dev: nullish trace for compare
|
||||
if crate::config::env::null_missing_box_enabled() && Self::box_trace_enabled() {
|
||||
let (ak, bk) = (crate::backend::abi_util::tag_of_vm(&a2), crate::backend::abi_util::tag_of_vm(&b2));
|
||||
let (ak, bk) = (
|
||||
crate::backend::abi_util::tag_of_vm(&a2),
|
||||
crate::backend::abi_util::tag_of_vm(&b2),
|
||||
);
|
||||
let (an, bn) = (Self::tag_nullish(&a2), Self::tag_nullish(&b2));
|
||||
let op_s = match op { CompareOp::Eq=>"Eq", CompareOp::Ne=>"Ne", CompareOp::Lt=>"Lt", CompareOp::Le=>"Le", CompareOp::Gt=>"Gt", CompareOp::Ge=>"Ge" };
|
||||
let op_s = match op {
|
||||
CompareOp::Eq => "Eq",
|
||||
CompareOp::Ne => "Ne",
|
||||
CompareOp::Lt => "Lt",
|
||||
CompareOp::Le => "Le",
|
||||
CompareOp::Gt => "Gt",
|
||||
CompareOp::Ge => "Ge",
|
||||
};
|
||||
eprintln!("{{\"ev\":\"cmp\",\"op\":\"{}\",\"a_k\":\"{}\",\"b_k\":\"{}\",\"a_n\":\"{}\",\"b_n\":\"{}\"}}", op_s, ak, bk, an, bn);
|
||||
}
|
||||
let result = match (op, &a3, &b3) {
|
||||
@ -230,7 +284,6 @@ impl MirInterpreter {
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// ---- Box trace (dev-only observer) ----
|
||||
@ -243,11 +296,15 @@ impl MirInterpreter {
|
||||
fn box_trace_filter_match(class_name: &str) -> bool {
|
||||
if let Ok(filt) = std::env::var("NYASH_BOX_TRACE_FILTER") {
|
||||
let want = filt.trim();
|
||||
if want.is_empty() { return true; }
|
||||
if want.is_empty() {
|
||||
return true;
|
||||
}
|
||||
// comma/space separated tokens; match if any token is contained in class
|
||||
for tok in want.split(|c: char| c == ',' || c.is_whitespace()) {
|
||||
let t = tok.trim();
|
||||
if !t.is_empty() && class_name.contains(t) { return true; }
|
||||
if !t.is_empty() && class_name.contains(t) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
false
|
||||
} else {
|
||||
@ -272,34 +329,49 @@ impl MirInterpreter {
|
||||
}
|
||||
|
||||
pub(super) fn box_trace_emit_new(&self, class_name: &str, argc: usize) {
|
||||
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { return; }
|
||||
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) {
|
||||
return;
|
||||
}
|
||||
eprintln!(
|
||||
"{{\"ev\":\"new\",\"class\":\"{}\",\"argc\":{}}}",
|
||||
Self::json_escape(class_name), argc
|
||||
Self::json_escape(class_name),
|
||||
argc
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn box_trace_emit_call(&self, class_name: &str, method: &str, argc: usize) {
|
||||
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { return; }
|
||||
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) {
|
||||
return;
|
||||
}
|
||||
eprintln!(
|
||||
"{{\"ev\":\"call\",\"class\":\"{}\",\"method\":\"{}\",\"argc\":{}}}",
|
||||
Self::json_escape(class_name), Self::json_escape(method), argc
|
||||
Self::json_escape(class_name),
|
||||
Self::json_escape(method),
|
||||
argc
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn box_trace_emit_get(&self, class_name: &str, field: &str, val_kind: &str) {
|
||||
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { return; }
|
||||
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) {
|
||||
return;
|
||||
}
|
||||
eprintln!(
|
||||
"{{\"ev\":\"get\",\"class\":\"{}\",\"field\":\"{}\",\"val\":\"{}\"}}",
|
||||
Self::json_escape(class_name), Self::json_escape(field), Self::json_escape(val_kind)
|
||||
Self::json_escape(class_name),
|
||||
Self::json_escape(field),
|
||||
Self::json_escape(val_kind)
|
||||
);
|
||||
}
|
||||
|
||||
pub(super) fn box_trace_emit_set(&self, class_name: &str, field: &str, val_kind: &str) {
|
||||
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) { return; }
|
||||
if !Self::box_trace_enabled() || !Self::box_trace_filter_match(class_name) {
|
||||
return;
|
||||
}
|
||||
eprintln!(
|
||||
"{{\"ev\":\"set\",\"class\":\"{}\",\"field\":\"{}\",\"val\":\"{}\"}}",
|
||||
Self::json_escape(class_name), Self::json_escape(field), Self::json_escape(val_kind)
|
||||
Self::json_escape(class_name),
|
||||
Self::json_escape(field),
|
||||
Self::json_escape(val_kind)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -312,7 +384,9 @@ impl MirInterpreter {
|
||||
}
|
||||
|
||||
pub(super) fn print_trace_emit(&self, val: &VMValue) {
|
||||
if !Self::print_trace_enabled() { return; }
|
||||
if !Self::print_trace_enabled() {
|
||||
return;
|
||||
}
|
||||
let (kind, class, nullish) = match val {
|
||||
VMValue::Integer(_) => ("Integer", "".to_string(), None),
|
||||
VMValue::Float(_) => ("Float", "".to_string(), None),
|
||||
@ -324,17 +398,43 @@ impl MirInterpreter {
|
||||
// Prefer InstanceBox.class_name when available
|
||||
if let Some(inst) = b.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
let tag = if crate::config::env::null_missing_box_enabled() {
|
||||
if b.as_any().downcast_ref::<crate::boxes::null_box::NullBox>().is_some() { Some("null") }
|
||||
else if b.as_any().downcast_ref::<crate::boxes::missing_box::MissingBox>().is_some() { Some("missing") }
|
||||
else { None }
|
||||
} else { None };
|
||||
if b.as_any()
|
||||
.downcast_ref::<crate::boxes::null_box::NullBox>()
|
||||
.is_some()
|
||||
{
|
||||
Some("null")
|
||||
} else if b
|
||||
.as_any()
|
||||
.downcast_ref::<crate::boxes::missing_box::MissingBox>()
|
||||
.is_some()
|
||||
{
|
||||
Some("missing")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
("BoxRef", inst.class_name.clone(), tag)
|
||||
} else {
|
||||
let tag = if crate::config::env::null_missing_box_enabled() {
|
||||
if b.as_any().downcast_ref::<crate::boxes::null_box::NullBox>().is_some() { Some("null") }
|
||||
else if b.as_any().downcast_ref::<crate::boxes::missing_box::MissingBox>().is_some() { Some("missing") }
|
||||
else { None }
|
||||
} else { None };
|
||||
if b.as_any()
|
||||
.downcast_ref::<crate::boxes::null_box::NullBox>()
|
||||
.is_some()
|
||||
{
|
||||
Some("null")
|
||||
} else if b
|
||||
.as_any()
|
||||
.downcast_ref::<crate::boxes::missing_box::MissingBox>()
|
||||
.is_some()
|
||||
{
|
||||
Some("missing")
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
("BoxRef", b.type_name().to_string(), tag)
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,8 +12,8 @@
|
||||
*/
|
||||
|
||||
use super::{MirFunction, MirInterpreter};
|
||||
use serde_json::json;
|
||||
use crate::backend::vm::{VMError, VMValue};
|
||||
use serde_json::json;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ParsedSig<'a> {
|
||||
@ -25,16 +25,25 @@ struct ParsedSig<'a> {
|
||||
fn parse_method_signature(name: &str) -> Option<ParsedSig<'_>> {
|
||||
let dot = name.find('.')?;
|
||||
let slash = name.rfind('/')?;
|
||||
if dot >= slash { return None; }
|
||||
if dot >= slash {
|
||||
return None;
|
||||
}
|
||||
let class = &name[..dot];
|
||||
let method = &name[dot + 1..slash];
|
||||
let arity_str = &name[slash + 1..];
|
||||
Some(ParsedSig { class, method, arity_str })
|
||||
Some(ParsedSig {
|
||||
class,
|
||||
method,
|
||||
arity_str,
|
||||
})
|
||||
}
|
||||
|
||||
fn extract_instance_box_class(arg0: &VMValue) -> Option<String> {
|
||||
if let VMValue::BoxRef(bx) = arg0 {
|
||||
if let Some(inst) = bx.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
if let Some(inst) = bx
|
||||
.as_any()
|
||||
.downcast_ref::<crate::instance_v2::InstanceBox>()
|
||||
{
|
||||
return Some(inst.class_name.clone());
|
||||
}
|
||||
}
|
||||
@ -47,7 +56,12 @@ fn reroute_to_correct_method(
|
||||
parsed: &ParsedSig<'_>,
|
||||
arg_vals: Option<&[VMValue]>,
|
||||
) -> Option<Result<VMValue, VMError>> {
|
||||
let target = format!("{}.{}{}", recv_cls, parsed.method, format!("/{}", parsed.arity_str));
|
||||
let target = format!(
|
||||
"{}.{}{}",
|
||||
recv_cls,
|
||||
parsed.method,
|
||||
format!("/{}", parsed.arity_str)
|
||||
);
|
||||
if let Some(f) = interp.functions.get(&target).cloned() {
|
||||
// Debug: emit class-reroute event (dev-only)
|
||||
crate::debug::hub::emit(
|
||||
@ -149,7 +163,10 @@ fn try_special_method(
|
||||
if parsed.method == "is_eof" && parsed.arity_str == "0" {
|
||||
if let Some(args) = arg_vals {
|
||||
if let VMValue::BoxRef(bx) = &args[0] {
|
||||
if let Some(inst) = bx.as_any().downcast_ref::<crate::instance_v2::InstanceBox>() {
|
||||
if let Some(inst) = bx
|
||||
.as_any()
|
||||
.downcast_ref::<crate::instance_v2::InstanceBox>()
|
||||
{
|
||||
if recv_cls == "JsonToken" {
|
||||
let is = match inst.get_field_ng("type") {
|
||||
Some(crate::value::NyashValue::String(ref s)) => s == "EOF",
|
||||
@ -158,8 +175,14 @@ fn try_special_method(
|
||||
return Some(Ok(VMValue::Bool(is)));
|
||||
}
|
||||
if recv_cls == "JsonScanner" {
|
||||
let pos = match inst.get_field_ng("position") { Some(crate::value::NyashValue::Integer(i)) => i, _ => 0 };
|
||||
let len = match inst.get_field_ng("length") { Some(crate::value::NyashValue::Integer(i)) => i, _ => 0 };
|
||||
let pos = match inst.get_field_ng("position") {
|
||||
Some(crate::value::NyashValue::Integer(i)) => i,
|
||||
_ => 0,
|
||||
};
|
||||
let len = match inst.get_field_ng("length") {
|
||||
Some(crate::value::NyashValue::Integer(i)) => i,
|
||||
_ => 0,
|
||||
};
|
||||
return Some(Ok(VMValue::Bool(pos >= len)));
|
||||
}
|
||||
}
|
||||
@ -183,16 +206,35 @@ pub(super) fn pre_exec_reroute(
|
||||
func: &MirFunction,
|
||||
arg_vals: Option<&[VMValue]>,
|
||||
) -> Option<Result<VMValue, VMError>> {
|
||||
let args = match arg_vals { Some(a) => a, None => return None };
|
||||
if args.is_empty() { return None; }
|
||||
let parsed = match parse_method_signature(func.signature.name.as_str()) { Some(p) => p, None => return None };
|
||||
let recv_cls = match extract_instance_box_class(&args[0]) { Some(c) => c, None => return None };
|
||||
let args = match arg_vals {
|
||||
Some(a) => a,
|
||||
None => return None,
|
||||
};
|
||||
if args.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let parsed = match parse_method_signature(func.signature.name.as_str()) {
|
||||
Some(p) => p,
|
||||
None => return None,
|
||||
};
|
||||
let recv_cls = match extract_instance_box_class(&args[0]) {
|
||||
Some(c) => c,
|
||||
None => return None,
|
||||
};
|
||||
// Always consider special re-routes (e.g., toString→stringify) even when class matches
|
||||
if let Some(r) = try_special_reroute(interp, &recv_cls, &parsed, arg_vals) { return Some(r); }
|
||||
if recv_cls == parsed.class { return None; }
|
||||
if let Some(r) = try_special_reroute(interp, &recv_cls, &parsed, arg_vals) {
|
||||
return Some(r);
|
||||
}
|
||||
if recv_cls == parsed.class {
|
||||
return None;
|
||||
}
|
||||
// Class mismatch: reroute to same method on the receiver's class
|
||||
if let Some(r) = reroute_to_correct_method(interp, &recv_cls, &parsed, arg_vals) { return Some(r); }
|
||||
if let Some(r) = reroute_to_correct_method(interp, &recv_cls, &parsed, arg_vals) {
|
||||
return Some(r);
|
||||
}
|
||||
// Narrow special fallback (e.g., is_eof)
|
||||
if let Some(r) = try_special_method(&recv_cls, &parsed, arg_vals) { return Some(r); }
|
||||
if let Some(r) = try_special_method(&recv_cls, &parsed, arg_vals) {
|
||||
return Some(r);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
@ -71,20 +71,32 @@ impl MirInterpreter {
|
||||
}
|
||||
|
||||
/// Register static box declarations (called from vm.rs during setup)
|
||||
pub fn register_static_box_decl(&mut self, name: String, decl: crate::core::model::BoxDeclaration) {
|
||||
pub fn register_static_box_decl(
|
||||
&mut self,
|
||||
name: String,
|
||||
decl: crate::core::model::BoxDeclaration,
|
||||
) {
|
||||
self.static_box_decls.insert(name, decl);
|
||||
}
|
||||
|
||||
/// Ensure static box singleton instance exists, create if not
|
||||
/// Returns mutable reference to the singleton instance
|
||||
fn ensure_static_box_instance(&mut self, box_name: &str) -> Result<&mut crate::instance_v2::InstanceBox, VMError> {
|
||||
fn ensure_static_box_instance(
|
||||
&mut self,
|
||||
box_name: &str,
|
||||
) -> Result<&mut crate::instance_v2::InstanceBox, VMError> {
|
||||
// Check if instance already exists
|
||||
if !self.static_boxes.contains_key(box_name) {
|
||||
// Get declaration
|
||||
let decl = self.static_box_decls.get(box_name)
|
||||
.ok_or_else(|| VMError::InvalidInstruction(
|
||||
format!("static box declaration not found: {}", box_name)
|
||||
))?
|
||||
let decl = self
|
||||
.static_box_decls
|
||||
.get(box_name)
|
||||
.ok_or_else(|| {
|
||||
VMError::InvalidInstruction(format!(
|
||||
"static box declaration not found: {}",
|
||||
box_name
|
||||
))
|
||||
})?
|
||||
.clone();
|
||||
|
||||
// Create instance from declaration
|
||||
@ -97,15 +109,20 @@ impl MirInterpreter {
|
||||
self.static_boxes.insert(box_name.to_string(), instance);
|
||||
|
||||
if std::env::var("NYASH_VM_STATIC_TRACE").ok().as_deref() == Some("1") {
|
||||
eprintln!("[vm-static] created singleton instance for static box: {}", box_name);
|
||||
eprintln!(
|
||||
"[vm-static] created singleton instance for static box: {}",
|
||||
box_name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Return mutable reference
|
||||
self.static_boxes.get_mut(box_name)
|
||||
.ok_or_else(|| VMError::InvalidInstruction(
|
||||
format!("static box instance not found after creation: {}", box_name)
|
||||
self.static_boxes.get_mut(box_name).ok_or_else(|| {
|
||||
VMError::InvalidInstruction(format!(
|
||||
"static box instance not found after creation: {}",
|
||||
box_name
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
/// Check if a function name represents a static box method
|
||||
@ -169,7 +186,12 @@ impl MirInterpreter {
|
||||
// Build helpful error message
|
||||
let mut names: Vec<&String> = module.functions.keys().collect();
|
||||
names.sort();
|
||||
let avail = names.into_iter().take(12).cloned().collect::<Vec<_>>().join(", ");
|
||||
let avail = names
|
||||
.into_iter()
|
||||
.take(12)
|
||||
.cloned()
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let tried = candidates.join(", ");
|
||||
let msg = format!(
|
||||
"entry function not found. searched: [{}]. available: [{}]. hint: define 'static box Main {{ method main(args){{ ... }} }}' or set NYASH_ENTRY=Name",
|
||||
@ -200,9 +222,13 @@ impl MirInterpreter {
|
||||
argv_list = out;
|
||||
}
|
||||
} else if let Ok(s) = std::env::var("NYASH_SCRIPT_ARGS_JSON") {
|
||||
if let Ok(v) = serde_json::from_str::<Vec<String>>(&s) { argv_list = v; }
|
||||
if let Ok(v) = serde_json::from_str::<Vec<String>>(&s) {
|
||||
argv_list = v;
|
||||
}
|
||||
} else if let Ok(s) = std::env::var("NYASH_ARGV") {
|
||||
if let Ok(v) = serde_json::from_str::<Vec<String>>(&s) { argv_list = v; }
|
||||
if let Ok(v) = serde_json::from_str::<Vec<String>>(&s) {
|
||||
argv_list = v;
|
||||
}
|
||||
}
|
||||
// Construct ArrayBox of StringBox
|
||||
let array = crate::boxes::array::ArrayBox::new();
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
//! レジスタ値の読み込み+型変換チェーンを統一します。
|
||||
|
||||
use super::super::*;
|
||||
use crate::mir::ValueId;
|
||||
use crate::box_trait::NyashBox;
|
||||
use crate::mir::ValueId;
|
||||
|
||||
impl MirInterpreter {
|
||||
/// レジスタ値をBox<dyn NyashBox>として読み込む
|
||||
@ -53,7 +53,13 @@ impl MirInterpreter {
|
||||
VMValue::Bool(_) => "Bool",
|
||||
VMValue::String(_) => "String",
|
||||
VMValue::Void => "Void",
|
||||
VMValue::BoxRef(b) => return Err(self.err_type_mismatch("load_as_int", "Integer", &b.type_name())),
|
||||
VMValue::BoxRef(b) => {
|
||||
return Err(self.err_type_mismatch(
|
||||
"load_as_int",
|
||||
"Integer",
|
||||
&b.type_name(),
|
||||
))
|
||||
}
|
||||
VMValue::Future(_) => "Future",
|
||||
};
|
||||
Err(self.err_type_mismatch("load_as_int", "Integer", type_name))
|
||||
@ -83,7 +89,9 @@ impl MirInterpreter {
|
||||
VMValue::Bool(_) => "Bool",
|
||||
VMValue::String(_) => "String",
|
||||
VMValue::Void => "Void",
|
||||
VMValue::BoxRef(b) => return Err(self.err_type_mismatch("load_as_bool", "Bool", &b.type_name())),
|
||||
VMValue::BoxRef(b) => {
|
||||
return Err(self.err_type_mismatch("load_as_bool", "Bool", &b.type_name()))
|
||||
}
|
||||
VMValue::Future(_) => "Future",
|
||||
};
|
||||
Err(self.err_type_mismatch("load_as_bool", "Bool", type_name))
|
||||
|
||||
@ -42,7 +42,10 @@ impl ErrorBuilder {
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub fn type_mismatch(method: &str, expected: &str, actual: &str) -> VMError {
|
||||
VMError::InvalidInstruction(format!("{} expects {} type, got {}", method, expected, actual))
|
||||
VMError::InvalidInstruction(format!(
|
||||
"{} expects {} type, got {}",
|
||||
method, expected, actual
|
||||
))
|
||||
}
|
||||
|
||||
/// Index out of bounds error
|
||||
@ -60,7 +63,10 @@ impl ErrorBuilder {
|
||||
#[inline]
|
||||
#[allow(dead_code)]
|
||||
pub fn out_of_bounds(method: &str, index: usize, len: usize) -> VMError {
|
||||
VMError::InvalidInstruction(format!("{} index out of bounds: {} >= {}", method, index, len))
|
||||
VMError::InvalidInstruction(format!(
|
||||
"{} index out of bounds: {} >= {}",
|
||||
method, index, len
|
||||
))
|
||||
}
|
||||
|
||||
/// Unsupported operation error
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
//! MIR Interpreter共通ユーティリティ
|
||||
|
||||
pub mod destination_helpers;
|
||||
pub mod arg_validation;
|
||||
pub mod receiver_helpers;
|
||||
pub mod error_helpers;
|
||||
pub mod conversion_helpers;
|
||||
pub mod destination_helpers;
|
||||
pub mod error_helpers;
|
||||
pub mod naming;
|
||||
pub mod receiver_helpers;
|
||||
// Phase 21.2: adapter_dev removed - all adapter functions now in .hako implementation
|
||||
|
||||
// Selective re-export (only naming is widely used via utils::normalize_arity_suffix)
|
||||
|
||||
@ -12,4 +12,3 @@ pub fn normalize_arity_suffix(name: &str) -> &str {
|
||||
None => name,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,9 +23,7 @@ impl MirInterpreter {
|
||||
let receiver_value = self.reg_load(receiver)?;
|
||||
match receiver_value {
|
||||
VMValue::BoxRef(b) => Ok(b),
|
||||
_ => Err(VMError::InvalidInstruction(
|
||||
"receiver must be Box".into(),
|
||||
)),
|
||||
_ => Err(VMError::InvalidInstruction("receiver must be Box".into())),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user