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:
nyash-codex
2025-11-21 06:25:17 +09:00
parent baf028a94f
commit f9d100ce01
366 changed files with 14322 additions and 5236 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,4 +12,3 @@ pub fn normalize_arity_suffix(name: &str) -> &str {
None => name,
}
}

View File

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