// 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 (params) { ... } outside of main (same box Main) // Output: [{"name":"","params":[...],"body_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" && me._kw_boundary_before(s, i) == 1 && me._kw_boundary_after(s, i + 3) == 1 { local cursor = i + 3 cursor = me._skip_whitespace(s, cursor) local name_start = cursor loop(cursor < n) { local ch_name = s.substring(cursor, cursor + 1) if me._is_ident_char(ch_name) == 1 { cursor = cursor + 1 } else { break } } local box_name = s.substring(name_start, cursor) if box_name == "" { next_i = cursor } else { cursor = me._skip_whitespace(s, cursor) if cursor >= n || s.substring(cursor, cursor + 1) != "{" { next_i = cursor } else { local close_idx = me._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) { local i = idx local n = s.length() loop(i < n) { local ch = s.substring(i, i + 1) if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { i = i + 1 } else { break } } return i } // Helper: キーワード前の境界チェック("box" などが識別子の一部でないことを確認) // 戻り値: 1=境界OK(キーワードとして有効), 0=境界NG(識別子の一部) method _kw_boundary_before(s, idx) { if idx <= 0 { return 1 } local ch = s.substring(idx - 1, idx) if me._is_ident_char(ch) == 1 { return 0 } return 1 } // Helper: キーワード後の境界チェック("box" などが識別子の一部でないことを確認) // 戻り値: 1=境界OK(キーワードとして有効), 0=境界NG(識別子の一部) method _kw_boundary_after(s, idx) { if idx >= s.length() { return 1 } local ch = s.substring(idx, idx + 1) if me._is_ident_char(ch) == 1 { return 0 } return 1 } // Helper: 識別子として有効な文字かチェック(0-9, A-Z, a-z, _) // 戻り値: 1=識別子文字, 0=非識別子文字 method _is_ident_char(ch) { 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 = me._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 "" } }