use super::*; impl MirInterpreter { pub(super) fn execute_extern_function( &mut self, extern_name: &str, args: &[ValueId], ) -> Result { // Normalize arity suffix (e.g., "env.get/1" -> "env.get") let base = super::super::utils::normalize_arity_suffix(extern_name); if let Some(res) = self.extern_provider_dispatch(base, args) { if std::env::var("HAKO_CABI_TRACE").ok().as_deref() == Some("1") { eprintln!("[hb:dispatch:calls] provider {}", base); } return res; } match base { // Minimal console externs "nyash.console.log" | "env.console.log" | "print" | "nyash.builtin.print" => { if let Some(arg_id) = args.get(0) { let v = self.reg_load(*arg_id)?; match &v { VMValue::Void => println!("null"), VMValue::BoxRef(bx) => { if bx .as_any() .downcast_ref::() .is_some() { println!("null"); } else if let Some(sb) = bx.as_any().downcast_ref::() { println!("{}", sb.value); } else { println!("{}", v.to_string()); } } VMValue::String(s) => println!("{}", s), _ => println!("{}", v.to_string()), } } else { println!(""); } Ok(VMValue::Void) } // Direct provider calls (bypass hostbridge.extern_invoke) // Above provider covers env.* family; keep legacy fallbacks below "exit" => { let code = if let Some(arg_id) = args.get(0) { self.reg_load(*arg_id)?.as_integer().unwrap_or(0) } else { 0 }; std::process::exit(code as i32); } "panic" => { let msg = if let Some(arg_id) = args.get(0) { self.reg_load(*arg_id)?.to_string() } else { "VM panic".to_string() }; 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))) } } } }