fix(mir): SSA違反修正 & StringBox is_space/starts_with実装

Task A: ローカル変数SSA違反修正
- src/mir/builder/stmts.rs: Copy命令で一意ValueId割り当て
- 元のエラー "Invalid value: use of undefined value" 解決
- using_resolver_box.hako が正常動作確認

Task B: StringBox新メソッド実装
- plugins/nyash-string-plugin: is_space/starts_with追加
- M_IS_SPACE (7), M_STARTS_WITH (8) 実装
- string_helpers.hako仕様に準拠

残存問題: do_break()のunreachableブロック生成
→ 次のコミットで修正予定

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-18 02:32:43 +09:00
parent 4ff9bd4791
commit 4aea27891d
2 changed files with 67 additions and 3 deletions

View File

@ -25,6 +25,8 @@ const M_CHAR_CODE_AT: u32 = 3;
const M_CONCAT: u32 = 4; // concat(other: String|Handle) -> Handle(new) const M_CONCAT: u32 = 4; // concat(other: String|Handle) -> Handle(new)
const M_FROM_UTF8: u32 = 5; // fromUtf8(data: String|Bytes) -> Handle(new) const M_FROM_UTF8: u32 = 5; // fromUtf8(data: String|Bytes) -> Handle(new)
const M_TO_UTF8: u32 = 6; // toUtf8() -> String const M_TO_UTF8: u32 = 6; // toUtf8() -> String
const M_IS_SPACE: u32 = 7; // is_space(ch: String) -> bool
const M_STARTS_WITH: u32 = 8; // starts_with(src: String, i: i64, pat: String) -> bool
const M_FINI: u32 = u32::MAX; const M_FINI: u32 = u32::MAX;
const TYPE_ID_STRING: u32 = 10; // Match nyash.toml type_id const TYPE_ID_STRING: u32 = 10; // Match nyash.toml type_id
@ -223,6 +225,8 @@ extern "C" fn string_resolve(name: *const c_char) -> u32 {
"concat" => M_CONCAT, "concat" => M_CONCAT,
"fromUtf8" => M_FROM_UTF8, "fromUtf8" => M_FROM_UTF8,
"toUtf8" | "toString" => M_TO_UTF8, // Map toString to toUtf8 "toUtf8" | "toString" => M_TO_UTF8, // Map toString to toUtf8
"is_space" => M_IS_SPACE,
"starts_with" => M_STARTS_WITH,
_ => 0, _ => 0,
} }
} }
@ -331,6 +335,52 @@ extern "C" fn string_invoke_id(
return E_PLUGIN; return E_PLUGIN;
} }
} }
M_IS_SPACE => {
// is_space(ch: String) -> bool
// Check if single character is whitespace: " ", "\t", "\n", "\r"
let ch = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
let is_space = ch == " " || ch == "\t" || ch == "\n" || ch == "\r";
return write_tlv_bool(is_space, result, result_len);
}
M_STARTS_WITH => {
// starts_with(src: String, i: i64, pat: String) -> bool
// Check if 'src' starts with 'pat' at position 'i'
// Args: [0] = src (String), [1] = i (i64), [2] = pat (String)
let src = match read_arg_string(args, args_len, 0) {
Some(s) => s,
None => return E_ARGS,
};
let i = match read_arg_i64(args, args_len, 1) {
Some(v) if v >= 0 => v as usize,
_ => return E_ARGS,
};
let pat = match read_arg_string(args, args_len, 2) {
Some(s) => s,
None => return E_ARGS,
};
let src_len = src.len();
let pat_len = pat.len();
// Check bounds: i + pat.length() > src.length() → false
if i + pat_len > src_len {
return write_tlv_bool(false, result, result_len);
}
// Character-by-character comparison
let src_bytes = src.as_bytes();
let pat_bytes = pat.as_bytes();
for k in 0..pat_len {
if src_bytes[i + k] != pat_bytes[k] {
return write_tlv_bool(false, result, result_len);
}
}
return write_tlv_bool(true, result, result_len);
}
_ => E_METHOD, _ => E_METHOD,
} }
} }

View File

@ -193,18 +193,32 @@ impl super::MirBuilder {
let mut last_value = None; let mut last_value = None;
for (i, var_name) in variables.iter().enumerate() { for (i, var_name) in variables.iter().enumerate() {
let var_id = if i < initial_values.len() && initial_values[i].is_some() { let var_id = if i < initial_values.len() && initial_values[i].is_some() {
// Use initializer's ValueId directly to avoid SSA aliasing/undefined use // Evaluate the initializer expression
let init_expr = initial_values[i].as_ref().unwrap(); let init_expr = initial_values[i].as_ref().unwrap();
let init_val = self.build_expression(*init_expr.clone())?; let init_val = self.build_expression(*init_expr.clone())?;
init_val
// FIX: Allocate a new ValueId for this local variable
// and emit a Copy instruction to establish SSA form
let var_id = self.value_gen.next();
self.emit_instruction(crate::mir::MirInstruction::Copy {
dst: var_id,
src: init_val
})?;
// Propagate metadata (type/origin) from initializer to variable
crate::mir::builder::metadata::propagate::propagate(self, init_val, var_id);
var_id
} else { } else {
// Create a concrete register for uninitialized locals (Void) // Create a concrete register for uninitialized locals (Void)
crate::mir::builder::emission::constant::emit_void(self) crate::mir::builder::emission::constant::emit_void(self)
}; };
self.variable_map.insert(var_name.clone(), var_id); self.variable_map.insert(var_name.clone(), var_id);
last_value = Some(var_id); last_value = Some(var_id);
} }
Ok(last_value.unwrap_or_else(|| self.next_value_id())) Ok(last_value.unwrap_or_else(|| self.value_gen.next()))
} }
// Return statement // Return statement