/*! * Method router for MirInterpreter — centralized cross-class reroute and * narrow special-method fallbacks. Phase 1: minimal extraction from exec.rs * to keep behavior unchanged while making execution flow easier to reason about. */ use super::{MirFunction, MirInterpreter}; use serde_json::json; use crate::backend::vm::{VMError, VMValue}; #[derive(Debug, Clone)] struct ParsedSig<'a> { class: &'a str, method: &'a str, arity_str: &'a str, } fn parse_method_signature(name: &str) -> Option> { let dot = name.find('.')?; let slash = name.rfind('/')?; 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 }) } fn extract_instance_box_class(arg0: &VMValue) -> Option { if let VMValue::BoxRef(bx) = arg0 { if let Some(inst) = bx.as_any().downcast_ref::() { return Some(inst.class_name.clone()); } } None } fn reroute_to_correct_method( interp: &mut MirInterpreter, recv_cls: &str, parsed: &ParsedSig<'_>, arg_vals: Option<&[VMValue]>, ) -> Option> { 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( "resolve", "class-reroute", interp.cur_fn.as_deref(), None, json!({ "recv_cls": recv_cls, "orig_class": parsed.class, "method": parsed.method, "arity": parsed.arity_str, "target": target, }), ); return Some(interp.exec_function_inner(&f, arg_vals)); } None } /// Try mapping special methods to canonical targets (table-driven). /// Example: toString/0 → str/0(互換: stringify/0)(prefer instance class, then base class without "Instance" suffix). fn try_special_reroute( interp: &mut MirInterpreter, recv_cls: &str, parsed: &ParsedSig<'_>, arg_vals: Option<&[VMValue]>, ) -> Option> { // toString → str(互換: stringify) if parsed.method == "toString" && parsed.arity_str == "0" { // Prefer instance class 'str' first, then base(strip trailing "Instance")。なければ 'stringify' を互換で探す let base = recv_cls.strip_suffix("Instance").unwrap_or(recv_cls); let candidates = [ format!("{}.str/0", recv_cls), format!("{}.str/0", base), format!("{}.stringify/0", recv_cls), format!("{}.stringify/0", base), ]; for name in candidates.iter() { if let Some(f) = interp.functions.get(name).cloned() { // Debug: emit special-reroute event (dev-only) crate::debug::hub::emit( "resolve", "special-reroute", interp.cur_fn.as_deref(), None, json!({ "recv_cls": recv_cls, "orig_class": parsed.class, "method": parsed.method, "arity": parsed.arity_str, "target": name, "reason": if name.ends_with(".str/0") { "toString->str" } else { "toString->stringify" }, }), ); return Some(interp.exec_function_inner(&f, arg_vals)); } } } // equals passthrough (instance/base) // In some user setups, only base class provides equals(other). // Try instance first, then base (strip trailing "Instance"). if parsed.method == "equals" && parsed.arity_str == "1" { let base = recv_cls.strip_suffix("Instance").unwrap_or(recv_cls); let candidates = [ format!("{}.equals/1", recv_cls), format!("{}.equals/1", base), ]; for name in candidates.iter() { if let Some(f) = interp.functions.get(name).cloned() { crate::debug::hub::emit( "resolve", "special-reroute", interp.cur_fn.as_deref(), None, json!({ "recv_cls": recv_cls, "orig_class": parsed.class, "method": parsed.method, "arity": parsed.arity_str, "target": name, "reason": "equals-fallback", }), ); return Some(interp.exec_function_inner(&f, arg_vals)); } } } None } fn try_special_method( recv_cls: &str, parsed: &ParsedSig<'_>, arg_vals: Option<&[VMValue]>, ) -> Option> { // Keep narrow fallbacks minimal, deterministic, and cheap. 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::() { if recv_cls == "JsonToken" { let is = match inst.get_field_ng("type") { Some(crate::value::NyashValue::String(ref s)) => s == "EOF", _ => false, }; 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 }; return Some(Ok(VMValue::Bool(pos >= len))); } } } } } None } /// Pre-execution reroute/short-circuit. /// /// When a direct Call to "Class.method/N" is about to execute, verify that the /// first argument ('me') actually belongs to the same InstanceBox class. If it /// does not, try rerouting to the matching class method. If no matching method /// exists, apply a very narrow fallback for well-known methods (dev-oriented, /// but safe and deterministic) and return a value. Returning Some(Result<..>) /// indicates that the router handled the call (rerouted or short-circuited). /// Returning None means normal execution should continue. pub(super) fn pre_exec_reroute( interp: &mut MirInterpreter, func: &MirFunction, arg_vals: Option<&[VMValue]>, ) -> Option> { 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; } // 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); } // Narrow special fallback (e.g., is_eof) if let Some(r) = try_special_method(&recv_cls, &parsed, arg_vals) { return Some(r); } None }