Files
hakorune/lang/src/compiler/entry/func_scanner.hako
nyash-codex 471052ad8d feat(debug): __mir__.log追加+VM実行テスト - loopバグ確定
## 🔍 func_scanner.hakoに__mir__.log追加
```hako
method skip_whitespace(s, idx) {
  __mir__.log("skip_ws/head", i, n)
  loop(1 == 1) {
    __mir__.log("skip_ws/loop", i, n)  ← 実行されない
    ...
  }
  __mir__.log("skip_ws/exit", i, n)
}
```

## 📊 CLI実行結果(MIRログ)
```
[MIR-LOG] skip_ws/head: %26=Integer(0) %28=Integer(6)
[MIR-LOG] skip_ws/exit: %26=Integer(0) %28=Integer(6)
```
-  i=0, n=6(両方Integer, 値は正しい)
-  `skip_ws/loop`が**一度も出ない**
- → **loop本体が実行されていないことがMIRレベルで確定**

## 🧪 Rustテスト更新
1. **ソースを束ねる**: func_scanner.hako + test file
   - FuncScannerBox関数がmoduleに含まれるように修正
2. **VM実行追加**: execute_module()でバグ再現確認
   - 期待: rc=0 (PASS), 実際: rc=1 (FAIL)

## 🎯 次のステップ
- MIRダンプでLoopForm展開を確認
- VM interpreter/LoopForm実行を調査

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 08:22:43 +09:00

540 lines
21 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.

// FuncScannerBox — Function definition scanner for Stage-B compiler
// Policy: Extract method definitions from Hako source (conservative, minimal)
// Toggle: HAKO_STAGEB_FUNC_SCAN=1
// Scope: method <name>(params) { ... } outside of main (same box Main)
// Output: [{"name":"<name>","params":[...],"body_json":"<Program JSON>","box":"Main"}]
using "lang.compiler.parser.box" as ParserBox
static box FuncScannerBox {
// Scan source for method definitions (excluding Main.main)
method scan_functions(source, box_name) {
return me.scan_methods(source, box_name, 1, 0)
}
// Scan all static box definitions and collect their methods.
method scan_all_boxes(source) {
// source が null の場合はスキャン対象がないので空配列を返すよ。
if source == null { return new ArrayBox() }
local defs = new ArrayBox()
local s = "" + source
local n = s.length()
local i = 0
// ────────────────────────────────────────────────────────────
// State flags: コメント/文字列スキップ状態管理4本
// ────────────────────────────────────────────────────────────
// in_str: 文字列リテラル内("..." の中)= 1, それ以外 = 0
// esc: 文字列内エスケープ直後(次の文字をスキップ)= 1, それ以外 = 0
// in_line: 行コメント内(// から改行まで)= 1, それ以外 = 0
// in_block: ブロックコメント内(/* から */ まで)= 1, それ以外 = 0
local in_str = 0
local esc = 0
local in_line = 0
local in_block = 0
// Region + next_i 形式に統一して、ループ本体での多重 continue による
// SSA/PHI の複雑さを下げる。
loop(i < n) {
local next_i = i + 1
local ch = s.substring(i, i + 1)
// ────────────────────────────────────────────────────────────
// State 1: 行コメントモード(// から改行まで)
// ────────────────────────────────────────────────────────────
if in_line == 1 {
if ch == "\n" { in_line = 0 } // 改行で行コメント終了
// ────────────────────────────────────────────────────────────
// State 2: ブロックコメントモード(/* から */ まで)
// ────────────────────────────────────────────────────────────
} else if in_block == 1 {
if ch == "*" && i + 1 < n && s.substring(i + 1, i + 2) == "/" {
in_block = 0 // */ でブロックコメント終了
next_i = i + 2
}
// ────────────────────────────────────────────────────────────
// State 3: 文字列リテラルモード("..." の中)
// ────────────────────────────────────────────────────────────
} else if in_str == 1 {
if esc == 1 {
esc = 0 // エスケープ直後文字処理完了
} else if ch == "\\" {
esc = 1 // バックスラッシュでエスケープ開始
} else if ch == "\"" {
in_str = 0 // 閉じクォートで文字列終了
}
// ────────────────────────────────────────────────────────────
// State 4: 通常モード(コメント/文字列の外)→ box キーワード検出
// ────────────────────────────────────────────────────────────
} else {
// 文字列リテラル開始(" 検出)
if ch == "\"" {
in_str = 1 // 文字列モードへ遷移
// コメント開始判定(/ の次の文字で行/ブロック判別)
} else if ch == "/" && i + 1 < n {
local ch2 = s.substring(i + 1, i + 2)
if ch2 == "/" {
in_line = 1 // 行コメントモードへ遷移(// 検出)
next_i = i + 2
} else if ch2 == "*" {
in_block = 1 // ブロックコメントモードへ遷移(/* 検出)
next_i = i + 2
}
// box キーワード検出(境界チェック付き)
} else {
if i + 3 <= n && s.substring(i, i + 3) == "box" && FuncScannerBox.kw_boundary_before(s, i) == 1 && FuncScannerBox.kw_boundary_after(s, i + 3) == 1 {
local cursor = i + 3
print("[DEBUG] box keyword found at i=" + ("" + i))
cursor = FuncScannerBox.skip_whitespace(s, cursor)
print("[DEBUG] after skip_whitespace: cursor=" + ("" + cursor))
local name_start = cursor
loop(cursor < n) {
local ch_name = s.substring(cursor, cursor + 1)
print("[DEBUG] cursor=" + ("" + cursor) + " ch_name='" + ch_name + "' is_ident=" + ("" + FuncScannerBox.is_ident_char(ch_name)))
if FuncScannerBox.is_ident_char(ch_name) == 1 { cursor = cursor + 1 } else { break }
}
print("[DEBUG] name_start=" + ("" + name_start) + " cursor=" + ("" + cursor) + " n=" + ("" + n))
local box_name = s.substring(name_start, cursor)
print("[DEBUG] box_name='" + box_name + "' len=" + ("" + box_name.length()))
if box_name == "" {
next_i = cursor
} else {
cursor = FuncScannerBox.skip_whitespace(s, cursor)
if cursor >= n || s.substring(cursor, cursor + 1) != "{" {
next_i = cursor
} else {
local close_idx = FuncScannerBox.find_matching_brace(s, cursor)
if close_idx < 0 {
// マッチしない場合はこれ以上スキャンしても意味がないのでループ終了。
i = n
next_i = n
} else {
local body = s.substring(cursor + 1, close_idx)
local skip_main = 0
if box_name == "Main" { skip_main = 1 }
local include_me = 0
if box_name != "Main" { include_me = 1 }
local defs_box = me._scan_methods(body, box_name, skip_main, include_me)
me._append_defs(defs, defs_box)
next_i = close_idx + 1
}
}
}
}
}
}
i = next_i
}
return defs
}
method _scan_methods(source, box_name, skip_main, include_me) {
local methods = new ArrayBox()
local s = "" + source
local n = s.length()
local i = 0
loop(i < n) {
// Search for "method "
local k = -1
{
local pat = "method "
local m = pat.length()
local j = i
loop(j + m <= n) {
if s.substring(j, j + m) == pat { k = j break }
j = j + 1
}
}
if k < 0 { break }
i = k + 7
// Extract method name (alphanumeric until '(')
local name_start = i
local name_end = -1
{
local j = i
loop(j < n) {
local ch = s.substring(j, j + 1)
if ch == "(" { name_end = j break }
j = j + 1
}
}
if name_end < 0 { break }
local method_name = s.substring(name_start, name_end)
if skip_main == 1 && box_name == "Main" && method_name == "main" { i = name_end continue }
// Find '(' after name
local lparen = name_end
// Find matching ')' for params (skip strings)
local rparen = -1
{
local j = lparen + 1
local in_str = 0
local esc = 0
loop(j < n) {
local ch = s.substring(j, j + 1)
if in_str == 1 {
if esc == 1 { esc = 0 j = j + 1 continue }
if ch == "\\" { esc = 1 j = j + 1 continue }
if ch == "\"" { in_str = 0 j = j + 1 continue }
j = j + 1
continue
}
if ch == "\"" { in_str = 1 j = j + 1 continue }
if ch == ")" { rparen = j break }
j = j + 1
}
}
if rparen < 0 { break }
// Extract params (minimal: comma-separated names)
local params_str = s.substring(lparen + 1, rparen)
// static helper として扱うme ではなく FuncScannerBox に直接委譲)。
local params = FuncScannerBox._parse_params(params_str)
if include_me == 1 {
local first = ""
if params.length() > 0 { first = "" + params.get(0) }
if first != "me" {
local merged = new ArrayBox()
merged.push("me")
local pi2 = 0
local pn2 = params.length()
loop(pi2 < pn2) {
merged.push(params.get(pi2))
pi2 = pi2 + 1
}
params = merged
}
}
// Find opening '{' after ')'
local lbrace = -1
{
local j = rparen + 1
local in_str = 0
local esc = 0
loop(j < n) {
local ch = s.substring(j, j + 1)
if in_str == 1 {
if esc == 1 { esc = 0 j = j + 1 continue }
if ch == "\\" { esc = 1 j = j + 1 continue }
if ch == "\"" { in_str = 0 j = j + 1 continue }
j = j + 1
continue
}
if ch == "\"" { in_str = 1 j = j + 1 continue }
if ch == "{" { lbrace = j break }
j = j + 1
}
}
if lbrace < 0 { break }
// Find matching '}' (balanced)
local rbrace = -1
{
local depth = 0
local j = lbrace
local in_str = 0
local esc = 0
loop(j < n) {
local ch = s.substring(j, j + 1)
if in_str == 1 {
if esc == 1 { esc = 0 j = j + 1 continue }
if ch == "\\" { esc = 1 j = j + 1 continue }
if ch == "\"" { in_str = 0 j = j + 1 continue }
j = j + 1
continue
}
if ch == "\"" { in_str = 1 j = j + 1 continue }
if ch == "{" { depth = depth + 1 j = j + 1 continue }
if ch == "}" {
depth = depth - 1
j = j + 1
if depth == 0 { rbrace = j - 1 break }
continue
}
j = j + 1
}
}
if rbrace < 0 { break }
// Extract method body (inside braces)
local method_body = s.substring(lbrace + 1, rbrace)
// コメント除去と trim も static helper として扱う。
method_body = FuncScannerBox._strip_comments(method_body)
method_body = FuncScannerBox._trim(method_body)
// Parse method body to JSON (statement list)
local body_json = null
if method_body.length() > 0 {
local p = new ParserBox()
p.stage3_enable(1)
body_json = p.parse_program2(method_body)
}
if body_json != null && body_json != "" {
local def = new MapBox()
def.set("name", method_name)
def.set("params", params)
def.set("body_json", body_json)
def.set("box", box_name)
methods.push(def)
}
i = rbrace
}
return methods
}
method _append_defs(dst, defs_box) {
if defs_box == null { return }
local i = 0
loop(i < defs_box.length()) {
dst.push(defs_box.get(i))
i = i + 1
}
}
// ────────────────────────────────────────────────────────────
// Core Text Scanning Helpers (SSOT: single source of truth)
// Stage-B および他の箱からも参照される共通処理群
// ────────────────────────────────────────────────────────────
// Helper: 空白文字space/tab/newline/CRを idx 位置からスキップ
// 戻り値: スキップ後の位置(空白でない文字の位置、または文字列末尾)
method skip_whitespace(s, idx) {
print("🔥🔥🔥 SENTINEL_SKIP_WS_CALLED!!! 🔥🔥🔥")
print("[skip_ws] START idx=" + ("" + idx) + " s.length()=" + ("" + s.length()))
local i = idx
local n = s.length()
print("[skip_ws] i=" + ("" + i) + " n=" + ("" + n))
__mir__.log("skip_ws/head", i, n)
// WORKAROUND: Changed from loop(i < n) to loop with internal if check
// Original: loop(i < n) { ... } was not executing body even when condition was true!
loop(1 == 1) {
__mir__.log("skip_ws/loop", i, n)
print("[skip_ws] LOOP-TOP i=" + ("" + i))
if i >= n { break }
local ch = s.substring(i, i + 1)
print("[skip_ws] LOOP i=" + ("" + i) + " ch='" + ch + "'")
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { i = i + 1 } else { break }
}
__mir__.log("skip_ws/exit", i, n)
print("[skip_ws] RETURN i=" + ("" + i))
return i
}
// Helper: キーワード前の境界チェック("box" などが識別子の一部でないことを確認)
// 戻り値: 1=境界OKキーワードとして有効, 0=境界NG識別子の一部
method kw_boundary_before(s, idx) {
print("🎯 SENTINEL_KW_BOUNDARY_BEFORE_CALLED!!!")
if idx <= 0 { return 1 }
local ch = s.substring(idx - 1, idx)
if FuncScannerBox.is_ident_char(ch) == 1 { return 0 }
return 1
}
// Helper: キーワード後の境界チェック("box" などが識別子の一部でないことを確認)
// 戻り値: 1=境界OKキーワードとして有効, 0=境界NG識別子の一部
method kw_boundary_after(s, idx) {
print("🎯 SENTINEL_KW_BOUNDARY_AFTER_CALLED!!!")
if idx >= s.length() { return 1 }
local ch = s.substring(idx, idx + 1)
if FuncScannerBox.is_ident_char(ch) == 1 { return 0 }
return 1
}
// Helper: 識別子として有効な文字かチェック0-9, A-Z, a-z, _
// 戻り値: 1=識別子文字, 0=非識別子文字
method is_ident_char(ch) {
print("🔤 SENTINEL_IS_IDENT_CHAR_CALLED!!!")
if ch == null || ch == "" { return 0 }
if ch >= "0" && ch <= "9" { return 1 }
if ch >= "A" && ch <= "Z" { return 1 }
if ch >= "a" && ch <= "z" { return 1 }
if ch == "_" { return 1 }
return 0
}
method find_matching_brace(s, open_idx) {
local n = s.length()
if open_idx < 0 || open_idx >= n { return -1 }
// open_idx 以降だけを対象に走査することで、前方の構造に依存しないようにする。
local subs = s.substring(open_idx, n)
local m = subs.length()
local i = 0
local depth = 0
local in_str = 0
local esc = 0
local in_line = 0
local in_block = 0
loop(i < m) {
local ch = subs.substring(i, i + 1)
if in_line == 1 {
if ch == "\n" { in_line = 0 }
i = i + 1
continue
}
if in_block == 1 {
if ch == "*" && i + 1 < n && s.substring(i + 1, i + 2) == "/" {
in_block = 0
i = i + 2
continue
}
i = i + 1
continue
}
if in_str == 1 {
if esc == 1 { esc = 0 i = i + 1 continue }
if ch == "\\" { esc = 1 i = i + 1 continue }
if ch == "\"" { in_str = 0 i = i + 1 continue }
i = i + 1
continue
}
if ch == "\"" { in_str = 1 i = i + 1 continue }
if ch == "/" && i + 1 < m {
local ch2 = subs.substring(i + 1, i + 2)
if ch2 == "/" { in_line = 1 i = i + 2 continue }
if ch2 == "*" { in_block = 1 i = i + 2 continue }
}
if ch == "{" {
depth = depth + 1
i = i + 1
continue
}
if ch == "}" {
depth = depth - 1
i = i + 1
if depth == 0 { return open_idx + i - 1 }
continue
}
i = i + 1
}
return -1
}
// Helper: カンマ区切りパラメータリストをパース(例: "a, b, c" → ["a", "b", "c"]
// FuncScannerBox._trim を使用static helper パターン)
// 戻り値: ArrayBoxトリム済みパラメータ名のリスト
method parse_params(params_str) {
local params = new ArrayBox()
local pstr = "" + params_str
local pn = pstr.length()
local pstart = 0
loop(pstart < pn) {
// Skip whitespace
loop(pstart < pn) {
local ch = pstr.substring(pstart, pstart + 1)
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { pstart = pstart + 1 } else { break }
}
if pstart >= pn { break }
// Find next comma or end
local pend = pstart
loop(pend < pn) {
local ch = pstr.substring(pend, pend + 1)
if ch == "," { break }
pend = pend + 1
}
// Extract param name (trim)
local pname = pstr.substring(pstart, pend)
pname = FuncScannerBox.trim(pname)
if pname.length() > 0 { params.push(pname) }
pstart = pend + 1
}
return params
}
// Helper: "//" と "/* */" コメントを削除(文字列リテラル内は保持、改行は保持)
// 戻り値: コメント削除後のソースコードnull の場合は空文字)
method strip_comments(source) {
// source が null の場合はそのまま空文字として扱う(コメント除去する対象がないだけ)
if source == null { return "" }
local s = "" + source
local out = ""
local i = 0
local n = s.length()
local in_str = 0
local esc = 0
local in_line = 0
local in_block = 0
loop(i < n) {
local ch = s.substring(i, i + 1)
if in_line == 1 {
if ch == "\n" { in_line = 0 out = out + ch }
i = i + 1
continue
}
if in_block == 1 {
if ch == "*" && i + 1 < n && s.substring(i + 1, i + 2) == "/" { in_block = 0 i = i + 2 continue }
i = i + 1
continue
}
if in_str == 1 {
if esc == 1 { out = out + ch esc = 0 i = i + 1 continue }
if ch == "\\" { out = out + ch esc = 1 i = i + 1 continue }
if ch == "\"" { out = out + ch in_str = 0 i = i + 1 continue }
out = out + ch
i = i + 1
continue
}
if ch == "\"" { out = out + ch in_str = 1 i = i + 1 continue }
if ch == "/" && i + 1 < n {
local ch2 = s.substring(i + 1, i + 2)
if ch2 == "/" { in_line = 1 i = i + 2 continue }
if ch2 == "*" { in_block = 1 i = i + 2 continue }
}
out = out + ch
i = i + 1
}
return out
}
// Helper: 前後の空白文字space/tab/newline/CRを削除
// 戻り値: トリム済み文字列null の場合は空文字)
method trim(s) {
if s == null { return "" }
local str = "" + s
local n = str.length()
local b = 0
loop(b < n) {
local ch = str.substring(b, b + 1)
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { b = b + 1 } else { break }
}
local e = n
loop(e > b) {
local ch = str.substring(e - 1, e)
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { e = e - 1 } else { break }
}
if e > b { return str.substring(b, e) }
return ""
}
// ────────────────────────────────────────────────────────────
// Static Helper Aliases (external API with underscore prefix)
// Stage-B および他の箱から static 呼び出しで使える薄いラッパー
// ────────────────────────────────────────────────────────────
// Static helper: パラメータリストパース(外部 API
method _parse_params(params_str) {
return FuncScannerBox.parse_params(params_str)
}
// Static helper: コメント削除(外部 API
method _strip_comments(source) {
return FuncScannerBox.strip_comments(source)
}
// Static helper: 前後空白削除(外部 API
method _trim(s) {
return FuncScannerBox.trim(s)
}
}