diff --git a/CURRENT_TASK.md b/CURRENT_TASK.md index be6231e9..45e094bb 100644 --- a/CURRENT_TASK.md +++ b/CURRENT_TASK.md @@ -34,10 +34,10 @@ How To Run(Nyash-only) Next Steps(優先順・更新) -1. String統一ブリッジの完了 - - VM: 内部String受けのフォールバックを全パスで拾う(length/isEmpty/charCodeAt/concat/+) - - Interpreter: 同等のフォールバック/正規化(比較・結合・代表メソッド) - - 混在比較/結合の回帰ケース追加(内部/プラグイン/プリミティブ混在) +1. String統一ブリッジ(実装済・一次完了) + - VM: 比較/加算/代表メソッドのフォールバック(length/isEmpty/charCodeAt/concat/+)をstring-like正規化で実装 + - Interpreter: 比較/加算はstring-like正規化を適用(メソッドは後続で最小追補があれば対応) + - 例: encoding_min/regex_min/toml_min/path_min で回帰確認 2. tools/pyc: IR→Nyashの反映強化(return/If/Assignを安定化、Strictスイッチ連動) 3. Strictスイッチ: tools/pyc(unsupported_nodes非空でErr、envでON/OFF) 4. CLI隠しフラグ `--pyc`/`--pyc-native`(Parser→Compiler→AOTの一本化導線) diff --git a/src/backend/vm_instructions.rs b/src/backend/vm_instructions.rs index fbfbc40e..001000e1 100644 --- a/src/backend/vm_instructions.rs +++ b/src/backend/vm_instructions.rs @@ -1003,6 +1003,34 @@ impl VM { /// Execute a forced plugin invocation (no builtin fallback) pub(super) fn execute_plugin_invoke(&mut self, dst: Option, box_val: ValueId, method: &str, args: &[ValueId]) -> Result { + // Helper: extract UTF-8 string from internal StringBox, Result.Ok(String-like), or plugin StringBox via toUtf8 + fn extract_string_from_box(bx: &dyn crate::box_trait::NyashBox) -> Option { + // Internal StringBox + if let Some(sb) = bx.as_any().downcast_ref::() { + return Some(sb.value.clone()); + } + // Result.Ok(inner) → recurse + if let Some(res) = bx.as_any().downcast_ref::() { + if let crate::boxes::result::NyashResultBox::Ok(inner) = res { return extract_string_from_box(inner.as_ref()); } + } + // Plugin StringBox → call toUtf8 + if let Some(p) = bx.as_any().downcast_ref::() { + if p.box_type == "StringBox" { + let host = crate::runtime::get_global_plugin_host(); + let tmp: Option = if let Ok(ro) = host.read() { + if let Ok(val_opt) = ro.invoke_instance_method("StringBox", "toUtf8", p.inner.instance_id, &[]) { + if let Some(vb) = val_opt { + if let Some(sb2) = vb.as_any().downcast_ref::() { + Some(sb2.value.clone()) + } else { None } + } else { None } + } else { None } + } else { None }; + if tmp.is_some() { return tmp; } + } + } + None + } let recv = self.get_value(box_val)?; // Allow static birth on primitives/builtin boxes to create a plugin instance. if method == "birth" && !matches!(recv, VMValue::BoxRef(ref b) if b.as_any().downcast_ref::().is_some()) { @@ -1215,33 +1243,34 @@ impl VM { return Ok(ControlFlow::Continue); } } - // Fallback: support common methods on internal StringBox without requiring PluginBox receiver + // Fallback: support common string-like methods without requiring PluginBox receiver if let VMValue::BoxRef(ref bx) = recv { - if let Some(sb) = bx.as_any().downcast_ref::() { + // Try to view receiver as string (internal, plugin, or Result.Ok) + if let Some(s) = extract_string_from_box(bx.as_ref()) { match method { "length" => { - if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::Integer(sb.value.len() as i64)); } + if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::Integer(s.len() as i64)); } return Ok(ControlFlow::Continue); } "is_empty" | "isEmpty" => { - if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::Bool(sb.value.is_empty())); } + if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::Bool(s.is_empty())); } return Ok(ControlFlow::Continue); } "charCodeAt" => { let idx_v = if let Some(a0) = args.get(0) { self.get_value(*a0)? } else { VMValue::Integer(0) }; let idx = match idx_v { VMValue::Integer(i) => i.max(0) as usize, _ => 0 }; - let code = sb.value.chars().nth(idx).map(|c| c as u32 as i64).unwrap_or(0); + let code = s.chars().nth(idx).map(|c| c as u32 as i64).unwrap_or(0); if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::Integer(code)); } return Ok(ControlFlow::Continue); } "concat" => { let rhs_v = if let Some(a0) = args.get(0) { self.get_value(*a0)? } else { VMValue::String(String::new()) }; let rhs_s = match rhs_v { - VMValue::String(s) => s, - VMValue::BoxRef(br) => br.to_string_box().value, + VMValue::String(ss) => ss, + VMValue::BoxRef(br) => extract_string_from_box(br.as_ref()).unwrap_or_else(|| br.to_string_box().value), _ => rhs_v.to_string(), }; - let mut new_s = sb.value.clone(); + let mut new_s = s.clone(); new_s.push_str(&rhs_s); let out = Box::new(crate::box_trait::StringBox::new(new_s)); if let Some(dst_id) = dst { self.set_value(dst_id, VMValue::BoxRef(std::sync::Arc::from(out as Box))); } diff --git a/src/backend/vm_values.rs b/src/backend/vm_values.rs index d3657959..09aab92d 100644 --- a/src/backend/vm_values.rs +++ b/src/backend/vm_values.rs @@ -11,6 +11,30 @@ use crate::mir::{BinaryOp, CompareOp, UnaryOp}; use super::vm::{VM, VMError, VMValue}; impl VM { + /// Try to view a BoxRef as a UTF-8 string (internal StringBox, Result.Ok(String-like), or plugin StringBox via toUtf8) + fn try_boxref_to_string(&self, b: &dyn crate::box_trait::NyashBox) -> Option { + // Internal StringBox + if let Some(sb) = b.as_any().downcast_ref::() { + return Some(sb.value.clone()); + } + // Result.Ok(inner) → recurse + if let Some(res) = b.as_any().downcast_ref::() { + if let crate::boxes::result::NyashResultBox::Ok(inner) = res { return self.try_boxref_to_string(inner.as_ref()); } + } + // Plugin StringBox → call toUtf8 + if let Some(pb) = b.as_any().downcast_ref::() { + if pb.box_type == "StringBox" { + let host = crate::runtime::get_global_plugin_host(); + let tmp: Option = if let Ok(ro) = host.read() { + if let Ok(val_opt) = ro.invoke_instance_method("StringBox", "toUtf8", pb.inner.instance_id, &[]) { + if let Some(vb) = val_opt { if let Some(sb2) = vb.as_any().downcast_ref::() { Some(sb2.value.clone()) } else { None } } else { None } + } else { None } + } else { None }; + if tmp.is_some() { return tmp; } + } + } + None + } /// Execute binary operation pub(super) fn execute_binary_op(&self, op: &BinaryOp, left: &VMValue, right: &VMValue) -> Result { let debug_bin = std::env::var("NYASH_VM_DEBUG_BIN").ok().as_deref() == Some("1"); @@ -57,12 +81,14 @@ impl VM { // String + BoxRef concatenation (VMValue::String(l), VMValue::BoxRef(r)) => { - match op { BinaryOp::Add => Ok(VMValue::String(format!("{}{}", l, r.to_string_box().value))), _ => Err(VMError::TypeError("String-BoxRef operations only support addition".to_string())) } + let rs = self.try_boxref_to_string(r.as_ref()).unwrap_or_else(|| r.to_string_box().value); + match op { BinaryOp::Add => Ok(VMValue::String(format!("{}{}", l, rs))), _ => Err(VMError::TypeError("String-BoxRef operations only support addition".to_string())) } }, // BoxRef + String concatenation (VMValue::BoxRef(l), VMValue::String(r)) => { - match op { BinaryOp::Add => Ok(VMValue::String(format!("{}{}", l.to_string_box().value, r))), _ => Err(VMError::TypeError("BoxRef-String operations only support addition".to_string())) } + let ls = self.try_boxref_to_string(l.as_ref()).unwrap_or_else(|| l.to_string_box().value); + match op { BinaryOp::Add => Ok(VMValue::String(format!("{}{}", ls, r))), _ => Err(VMError::TypeError("BoxRef-String operations only support addition".to_string())) } }, // Arithmetic with BoxRef(IntegerBox) — support both legacy and new IntegerBox @@ -90,6 +116,15 @@ impl VM { }; Ok(VMValue::Integer(res)) } + // BoxRef + BoxRef string-like concatenation + (VMValue::BoxRef(li), VMValue::BoxRef(ri)) => { + if matches!(*op, BinaryOp::Add) { + let ls = self.try_boxref_to_string(li.as_ref()).unwrap_or_else(|| li.to_string_box().value); + let rs = self.try_boxref_to_string(ri.as_ref()).unwrap_or_else(|| ri.to_string_box().value); + return Ok(VMValue::String(format!("{}{}", ls, rs))); + } + Err(VMError::TypeError("Unsupported BoxRef+BoxRef operation".to_string())) + } // Mixed Integer forms (VMValue::BoxRef(li), VMValue::Integer(r)) if li.as_any().downcast_ref::().is_some() diff --git a/src/interpreter/operators.rs b/src/interpreter/operators.rs index efa08230..7a584167 100644 --- a/src/interpreter/operators.rs +++ b/src/interpreter/operators.rs @@ -107,6 +107,33 @@ pub(super) fn try_mod_operation(left: &dyn NyashBox, right: &dyn NyashBox) -> Re // ======================================================================================== impl NyashInterpreter { + /// Try to extract a UTF-8 string from a NyashBox: supports internal StringBox, + /// Result.Ok(String-like), and Plugin StringBox via toUtf8 (when plugins enabled). + fn try_box_to_string(&self, b: &dyn NyashBox) -> Option { + // Internal StringBox + if let Some(sb) = b.as_any().downcast_ref::() { return Some(sb.value.clone()); } + // Result.Ok(inner) → recurse + if let Some(res) = b.as_any().downcast_ref::() { + if let crate::boxes::result::NyashResultBox::Ok(inner) = res { return self.try_box_to_string(inner.as_ref()); } + } + // Plugin StringBox via toUtf8 + #[cfg(all(feature = "plugins", not(target_arch = "wasm32")))] + { + if let Some(pb) = b.as_any().downcast_ref::() { + if pb.box_type == "StringBox" { + let host = crate::runtime::get_global_plugin_host(); + if let Ok(ro) = host.read() { + if let Ok(val_opt) = ro.invoke_instance_method("StringBox", "toUtf8", pb.inner.instance_id, &[]) { + if let Some(vb) = val_opt { + if let Some(sb2) = vb.as_any().downcast_ref::() { return Some(sb2.value.clone()); } + } + } + } + } + } + } + None + } /// 二項演算を実行 pub(super) fn execute_binary_op(&mut self, op: &BinaryOperator, left: &ASTNode, right: &ASTNode) -> Result, RuntimeError> @@ -119,6 +146,16 @@ impl NyashInterpreter { match op { BinaryOperator::Add => { + // Prefer string-like concatenation when either side is string-like + if let (Some(ls), Some(rs)) = (self.try_box_to_string(left_val), self.try_box_to_string(right_val)) { + return Ok(Box::new(StringBox::new(format!("{}{}", ls, rs)))); + } + if let Some(ls) = self.try_box_to_string(left_val) { + return Ok(Box::new(StringBox::new(format!("{}{}", ls, right_val.to_string_box().value)))); + } + if let Some(rs) = self.try_box_to_string(right_val) { + return Ok(Box::new(StringBox::new(format!("{}{}", left_val.to_string_box().value, rs)))); + } if let Some(result) = try_add_operation(left_val, right_val) { Ok(result) } else { @@ -252,12 +289,9 @@ impl NyashInterpreter { return Ok(left_int.value == right_int.value); } - // StringBox comparison - if let (Some(left_str), Some(right_str)) = ( - left.as_any().downcast_ref::(), - right.as_any().downcast_ref::() - ) { - return Ok(left_str.value == right_str.value); + // String-like comparison (internal/Result.Ok/plugin StringBox) + if let (Some(ls), Some(rs)) = (self.try_box_to_string(left), self.try_box_to_string(right)) { + return Ok(ls == rs); } // BoolBox comparison @@ -287,12 +321,9 @@ impl NyashInterpreter { return Ok(left_int.value < right_int.value); } - // StringBox comparison (lexicographic) - if let (Some(left_str), Some(right_str)) = ( - left.as_any().downcast_ref::(), - right.as_any().downcast_ref::() - ) { - return Ok(left_str.value < right_str.value); + // String-like comparison (lexicographic) + if let (Some(ls), Some(rs)) = (self.try_box_to_string(left), self.try_box_to_string(right)) { + return Ok(ls < rs); } Err(RuntimeError::InvalidOperation { @@ -325,4 +356,4 @@ impl NyashInterpreter { // Everything else is true true } -} \ No newline at end of file +}