Files
hakorune/lang/src/compiler/parser/parser_box.hako
nyash-codex 66b2a115ae fix(vm): implement StringBox.lastIndexOf + PHI bug fix + Stage-B compiler完全動作 🎉
## 🎯 主要修正

### 1️⃣ StringBox.lastIndexOf実装 (Stage-B compiler blocker解消)
- **問題**: `lang/src/compiler/parser/parser_box.hako:85`で`lastIndexOf`使用も未実装
- **修正**: `src/backend/mir_interpreter/handlers/boxes_string.rs:51-60`に追加
- **実装**: `rfind()`で最後の出現位置を検索、-1でnot found表現

### 2️⃣ VM SSA/PHI bug完全修正 (ループ内メソッド呼び出し)
- **原因**: メソッド内ループ×外側ループ呼び出しでPHI生成失敗
- **修正箇所**:
  - `src/mir/loop_builder.rs`: Exit PHI生成実装
  - `src/mir/phi_core/loop_phi.rs`: PHI incoming修正
  - `src/mir/phi_core/common.rs`: ユーティリティ追加

### 3️⃣ カナリアテスト追加
- **新規**: `tools/smokes/v2/profiles/quick/core/vm_nested_loop_method_call.sh`
- **構成**: Level 0/5b/5a/5 (段階的バグ検出)
- **結果**: 全テストPASS、Level 5で`[SUCCESS] VM SSA/PHI bug FIXED!`表示

### 4️⃣ using連鎖解決修正
- **問題**: `using sh_core`が子モジュールに伝播しない
- **修正**: 6ファイルに明示的`using`追加
  - compiler_stageb.hako, parser_box.hako
  - parser_stmt_box.hako, parser_control_box.hako
  - parser_exception_box.hako, parser_expr_box.hako

### 5️⃣ ParserBoxワークアラウンド
- **問題**: `skip_ws()`メソッド呼び出しでVMバグ発生
- **対応**: 3箇所でインライン化(PHI修正までの暫定対応)

## 🎉 動作確認

```bash
# Stage-B compiler完全動作!
$ bash /tmp/run_stageb.sh
{"version":0,"kind":"Program","body":[{"type":"Return","expr":{"type":"Int","value":42}}]}

# カナリアテスト全PASS
$ bash tools/smokes/v2/profiles/quick/core/vm_nested_loop_method_call.sh
[PASS] level0_simple_loop (.008s)
[PASS] level5b_inline_nested_loop (.007s)
[PASS] level5a_method_no_loop (.007s)
[SUCCESS] Level 5: VM SSA/PHI bug FIXED!
[PASS] level5_method_with_loop (VM BUG canary) (.008s)
```

## 🏆 技術的ハイライト

1. **最小再現**: Level 0→5bの段階的テストでバグパターン完全特定
2. **Task先生調査**: 表面エラーから真因(lastIndexOf未実装)発見
3. **適切実装**: `boxes_string.rs`のStringBox専用ハンドラに追加
4. **完全検証**: Stage-B compilerでJSON出力成功を実証

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-02 10:58:09 +09:00

304 lines
7.8 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Moved from apps/selfhost-compiler/boxes/parser/parser_box.hako
// ParserBox — Stage1 JSON v0 generator (coordinator, delegates to specialized boxes)
// Responsibility: Coordinate parsing, manage state, delegate to specialized boxes
// API: parse_program2(src) -> JSON
using sh_core as StringHelpers // Required: ParserStringUtilsBox depends on this (using chain unresolved)
using lang.compiler.parser.scan.parser_string_utils_box
using lang.compiler.parser.scan.parser_ident_scan_box
using lang.compiler.parser.scan.parser_string_scan_box
using lang.compiler.parser.using.using_collector_box
using lang.compiler.parser.expr.parser_expr_box
using lang.compiler.parser.stmt.parser_stmt_box
using lang.compiler.parser.stmt.parser_control_box
box ParserBox {
gpos
usings_json
externs_json
stage3
birth() {
me.gpos = 0
me.usings_json = "[]"
me.externs_json = "[]"
me.stage3 = 0
return 0
}
stage3_enable(flag) {
if flag == null { flag = 0 }
if flag == 0 { me.stage3 = 0 } else { me.stage3 = 1 }
return 0
}
stage3_enabled() {
if me.stage3 == 1 { return 1 }
return 0
}
// === State management ===
gpos_set(i) { me.gpos = i return 0 }
gpos_get() { return me.gpos }
// === JSON utilities ===
esc_json(s) {
local out = ""
local i = 0
local n = s.length()
loop(i < n) {
local ch = s.substring(i, i+1)
if ch == "\\" { out = out + "\\\\" }
else { if ch == "\"" { out = out + "\\\"" }
else { out = out + ch } }
i = i + 1
}
return out
}
// === Delegation to ParserStringUtilsBox ===
is_digit(ch) { return ParserStringUtilsBox.is_digit(ch) }
is_space(ch) { return ParserStringUtilsBox.is_space(ch) }
is_alpha(ch) { return ParserStringUtilsBox.is_alpha(ch) }
starts_with(src, i, pat) { return ParserStringUtilsBox.starts_with(src, i, pat) }
index_of(src, i, pat) { return ParserStringUtilsBox.index_of(src, i, pat) }
trim(s) { return ParserStringUtilsBox.trim(s) }
starts_with_kw(src, i, kw) { return ParserStringUtilsBox.starts_with_kw(src, i, kw) }
i2s(v) { return ParserStringUtilsBox.i2s(v) }
to_int(s) { return ParserStringUtilsBox.to_int(s) }
skip_ws(src, i) { return ParserStringUtilsBox.skip_ws(src, i) }
// === Delegation to scanner boxes ===
read_ident2(src, i) { return ParserIdentScanBox.scan_ident(src, i) }
read_string_lit(src, i) {
local pair = ParserStringScanBox.scan(src, i)
local at = pair.lastIndexOf("@")
local content = pair.substring(0, at)
local pos = 0
if at >= 0 { pos = me.to_int(pair.substring(at+1, pair.length())) }
else { pos = i }
me.gpos_set(pos)
return content
}
// === using system ===
add_using(kind, target, alias) {
local cur = me.usings_json
if cur == null || cur.length() == 0 { cur = "[]" }
local name = ""
local path = null
if kind == "path" {
path = target
if alias != null {
name = alias
} else {
local p = target
local idx = -1
local t = 0
loop(t < p.length()) {
if p.substring(t,t+1) == "/" { idx = t }
t = t + 1
}
if idx >= 0 { p = p.substring(idx+1, p.length()) }
if p.length() > 5 && me.starts_with(p, p.length()-5, ".hako") == 1 {
p = p.substring(0, p.length()-5)
} else {
if p.length() > 6 && me.starts_with(p, p.length()-6, ".nyash") == 1 {
p = p.substring(0, p.length()-6)
}
}
name = p
}
} else {
name = target
if alias != null { name = alias }
}
local entry = "{\"name\":\"" + me.esc_json(name) + "\""
if path != null { entry = entry + ",\"path\":\"" + me.esc_json(path) + "\"" }
entry = entry + "}"
if cur == "[]" {
me.usings_json = "[" + entry + "]"
return 0
}
local pos = cur.lastIndexOf("]")
if pos < 0 {
me.usings_json = "[" + entry + "]"
return 0
}
me.usings_json = cur.substring(0, pos) + "," + entry + "]"
return 0
}
extract_usings(src) {
me.usings_json = UsingCollectorBox.collect(src)
return 0
}
get_usings_json() {
return me.usings_json
}
// === extern_c annotations ===
add_extern_c(symbol, func) {
// Entry shape: {"symbol":"hako_add","func":"Name/Arity"}
local sym = symbol
if sym == null { sym = "" }
local func_name = func
if func_name == null { func_name = "" }
local entry = "{\"symbol\":\"" + me.esc_json(sym) + "\",\"func\":\"" + me.esc_json(func_name) + "\"}"
local cur = me.externs_json
if cur == null || cur.length() == 0 { cur = "[]" }
if cur == "[]" {
me.externs_json = "[" + entry + "]"
return 0
}
local pos = cur.lastIndexOf("]")
if pos < 0 {
me.externs_json = "[" + entry + "]"
return 0
}
me.externs_json = cur.substring(0, pos) + "," + entry + "]"
return 0
}
extract_externs(_src) {
// MVP: rely on ParserStmtBox to call add_extern_c during parse; here no-op for now.
return 0
}
get_externs_json() {
return me.externs_json
}
// === Delegation to ParserExprBox ===
parse_expr2(src, i) {
return ParserExprBox.parse_expr2(src, i, me)
}
// === Delegation to ParserStmtBox ===
parse_stmt2(src, i) {
return ParserStmtBox.parse(src, i, me)
}
// === Delegation to ParserControlBox ===
parse_block2(src, i) {
return ParserControlBox.parse_block(src, i, me)
}
// === Top-level program parser ===
parse_program2(src) {
// Inline skip_ws to avoid VM bug: method-with-loop called from within loop
local i = 0
local n = src.length()
if i < n {
local ws_cont_init = 1
loop(ws_cont_init == 1) {
if i < n {
local ch = src.substring(i, i + 1)
if ch == " " || ch == "\n" || ch == "\r" || ch == "\t" { i = i + 1 }
else { ws_cont_init = 0 }
} else { ws_cont_init = 0 }
}
}
local body = "["
local first = 1
local cont_prog = 1
loop(cont_prog == 1) {
// Inline skip_ws instead of calling me.skip_ws(src, i)
if i < n {
local ws_cont_1 = 1
loop(ws_cont_1 == 1) {
if i < n {
local ch1 = src.substring(i, i + 1)
if ch1 == " " || ch1 == "\n" || ch1 == "\r" || ch1 == "\t" { i = i + 1 }
else { ws_cont_1 = 0 }
} else { ws_cont_1 = 0 }
}
}
if i >= src.length() {
cont_prog = 0
} else {
local start_i = i
local s = me.parse_stmt2(src, i)
i = me.gpos_get()
// Progress guard
if i <= start_i {
if i < src.length() { i = i + 1 }
else { i = src.length() }
me.gpos_set(i)
}
// consume optional semicolons
local done2 = 0
local guard2 = 0
local max2 = 100000
loop(done2 == 0) {
if guard2 > max2 { done2 = 1 }
else { guard2 = guard2 + 1 }
local before2 = i
// Inline skip_ws instead of calling me.skip_ws(src, i)
if i < n {
local ws_cont_2 = 1
loop(ws_cont_2 == 1) {
if i < n {
local ch2 = src.substring(i, i + 1)
if ch2 == " " || ch2 == "\n" || ch2 == "\r" || ch2 == "\t" { i = i + 1 }
else { ws_cont_2 = 0 }
} else { ws_cont_2 = 0 }
}
}
if i < src.length() && src.substring(i, i+1) == ";" {
i = i + 1
} else {
done2 = 1
}
if i == before2 { done2 = 1 }
}
if s.length() > 0 {
if first == 1 {
body = body + s
first = 0
} else {
body = body + "," + s
}
}
}
}
body = body + "]"
return "{\"version\":0,\"kind\":\"Program\",\"body\":" + body + "}"
}
}
static box ParserStub {
main(args) {
return 0
}
}