Files
hakorune/lang/src/compiler/entry/func_scanner.hako
nyash-codex 2692eafbbf feat(mir): Phase 26-H JoinIR型定義実装完了 - ChatGPT設計
## 実装内容(Step 1-3 完全達成)

### Step 1: src/mir/join_ir.rs 型定義追加
- **JoinFuncId / JoinContId**: 関数・継続ID型
- **JoinFunction**: 関数(引数 = φノード)
- **JoinInst**: Call/Jump/Ret/Compute 最小命令セット
- **MirLikeInst**: 算術・比較命令ラッパー
- **JoinModule**: 複数関数保持コンテナ
- **単体テスト**: 型サニティチェック追加

### Step 2: テストケース追加
- **apps/tests/joinir_min_loop.hako**: 最小ループ+breakカナリア
- **src/tests/mir_joinir_min.rs**: 手書きJoinIR構築テスト
  - MIR → JoinIR手動構築で型妥当性確認
  - #[ignore] で手動実行専用化
  - NYASH_JOINIR_EXPERIMENT=1 トグル制御

### Step 3: 環境変数トグル実装
- **NYASH_JOINIR_EXPERIMENT=1**: 実験モード有効化
- **デフォルト挙動**: 既存MIR/LoopForm経路のみ(破壊的変更なし)
- **トグルON時**: JoinIR手書き構築テスト実行

## Phase 26-H スコープ遵守
 型定義のみ(変換ロジックは未実装)
 最小限の命令セット
 Debug 出力で妥当性確認
 既存パイプライン無影響

## テスト結果
```
$ NYASH_JOINIR_EXPERIMENT=1 cargo test --release mir_joinir_min_manual_construction -- --ignored --nocapture
[joinir/min] MIR module compiled, 3 functions
[joinir/min] JoinIR module constructed:
[joinir/min]  JoinIR型定義は妥当(Phase 26-H)
test result: ok. 1 passed; 0 failed
```

## JoinIR理論の実証
- **φノード = 関数引数**: `fn loop_step(i, k_exit)`
- **merge = join関数**: 分岐後の合流点
- **ループ = 再帰関数**: `loop_step` 自己呼び出し
- **break = 継続呼び出し**: `k_exit(i)`

## 次フェーズ (Phase 27.x)
- LoopForm v2 → JoinIR 自動変換実装
- break/continue ハンドリング
- Exit PHI の JoinIR 引数化

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

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: ChatGPT <noreply@openai.com>
2025-11-23 04:10:12 +09:00

538 lines
20 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) {
// Minimal, SSA-friendly whitespace skipper.
__mir__.log("skip_ws/head", idx, s.length())
local i = idx
local n = s.length()
loop(i < n) {
local ch = s.substring(i, i + 1)
if ch == " " { i = i + 1 continue }
if ch == "\t" { i = i + 1 continue }
if ch == "\n" { i = i + 1 continue }
if ch == "\r" { i = i + 1 continue }
break
}
__mir__.log("skip_ws/exit", i, n)
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) {
// NOTE: keep the control flow simple to reduce SSA/PHI complexity.
// skip_whitespace/trim are already welltested helpers, so we reuse them here.
if params_str == null { return new ArrayBox() }
local params = new ArrayBox()
local pstr = "" + params_str
local n = pstr.length()
local pos = 0
loop(pos < n) {
pos = FuncScannerBox.skip_whitespace(pstr, pos)
if pos >= n { break }
// Find next comma (or end of string).
local next = pos
loop(next < n) {
if pstr.substring(next, next + 1) == "," { break }
next = next + 1
}
// Trim and collect the parameter name.
local pname = FuncScannerBox.trim(pstr.substring(pos, next))
if pname.length() > 0 { params.push(pname) }
pos = next + 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 "" }
__mir__.log("trim/entry", s)
local str = "" + s
local n = str.length()
__mir__.log("trim/pre", n)
// Leading whitespace removal is delegated to skip_whitespace to keep SSA simple.
local b = FuncScannerBox.skip_whitespace(str, 0)
if b >= n { return "" }
// Trailing whitespace: walk backwards until a non-space is found.
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 }
}
__mir__.log("trim/exit", b, e)
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)
}
}