2025-11-11 22:35:45 +09:00
|
|
|
// 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 {
|
2025-11-15 22:32:13 +09:00
|
|
|
// Scan source for method definitions (excluding Main.main)
|
2025-11-11 22:35:45 +09:00
|
|
|
method scan_functions(source, box_name) {
|
2025-11-15 22:32:13 +09:00
|
|
|
return me._scan_methods(source, box_name, 1, 0)
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-19 00:02:41 +09:00
|
|
|
// 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
|
2025-11-15 22:32:13 +09:00
|
|
|
local n = s.length()
|
|
|
|
|
local i = 0
|
|
|
|
|
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 }
|
|
|
|
|
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 < 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 }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if 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 == "" {
|
|
|
|
|
i = cursor
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
cursor = me._skip_whitespace(s, cursor)
|
|
|
|
|
if cursor >= n || s.substring(cursor, cursor + 1) != "{" {
|
|
|
|
|
i = cursor
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
local close_idx = me._find_matching_brace(s, cursor)
|
|
|
|
|
if close_idx < 0 { break }
|
|
|
|
|
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)
|
|
|
|
|
i = close_idx + 1
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
i = i + 1
|
|
|
|
|
}
|
|
|
|
|
return defs
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
method _scan_methods(source, box_name, skip_main, include_me) {
|
2025-11-11 22:35:45 +09:00
|
|
|
local methods = new ArrayBox()
|
|
|
|
|
local s = "" + source
|
|
|
|
|
local n = s.length()
|
|
|
|
|
local i = 0
|
|
|
|
|
|
|
|
|
|
loop(i < n) {
|
2025-11-15 22:32:13 +09:00
|
|
|
// Search for "method "
|
2025-11-11 22:35:45 +09:00
|
|
|
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 }
|
2025-11-15 22:32:13 +09:00
|
|
|
i = k + 7
|
2025-11-11 22:35:45 +09:00
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
|
2025-11-15 22:32:13 +09:00
|
|
|
if skip_main == 1 && box_name == "Main" && method_name == "main" { i = name_end continue }
|
2025-11-11 22:35:45 +09:00
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
local params = me._parse_params(params_str)
|
2025-11-15 22:32:13 +09:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-11-11 22:35:45 +09:00
|
|
|
|
|
|
|
|
// 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)
|
|
|
|
|
|
|
|
|
|
method_body = me._strip_comments(method_body)
|
|
|
|
|
method_body = me._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
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-15 22:32:13 +09:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
local i = open_idx
|
|
|
|
|
local depth = 0
|
|
|
|
|
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 }
|
|
|
|
|
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 < 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 }
|
|
|
|
|
}
|
|
|
|
|
if ch == "{" {
|
|
|
|
|
depth = depth + 1
|
|
|
|
|
i = i + 1
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if ch == "}" {
|
|
|
|
|
depth = depth - 1
|
|
|
|
|
i = i + 1
|
|
|
|
|
if depth == 0 { return i - 1 }
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
i = i + 1
|
|
|
|
|
}
|
|
|
|
|
return -1
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-11 22:35:45 +09:00
|
|
|
// Helper: parse comma-separated parameter names
|
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-19 00:02:41 +09:00
|
|
|
// Helper: strip comments from source
|
|
|
|
|
method _strip_comments(source) {
|
|
|
|
|
// source が null の場合はそのまま空文字として扱う(コメント除去する対象がないだけ)
|
|
|
|
|
if source == null { return "" }
|
|
|
|
|
local s = "" + source
|
2025-11-11 22:35:45 +09:00
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-15 22:32:13 +09:00
|
|
|
// Helper: trim whitespace
|
2025-11-11 22:35:45 +09:00
|
|
|
method _trim(s) {
|
2025-11-15 22:32:13 +09:00
|
|
|
if s == null { return "" }
|
2025-11-11 22:35:45 +09:00
|
|
|
local str = "" + s
|
|
|
|
|
local n = str.length()
|
|
|
|
|
local b = 0
|
|
|
|
|
loop(b < n) {
|
|
|
|
|
local ch = str.substring(b, b + 1)
|
2025-11-15 22:32:13 +09:00
|
|
|
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { b = b + 1 } else { break }
|
2025-11-11 22:35:45 +09:00
|
|
|
}
|
|
|
|
|
local e = n
|
|
|
|
|
loop(e > b) {
|
|
|
|
|
local ch = str.substring(e - 1, e)
|
2025-11-15 22:32:13 +09:00
|
|
|
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { e = e - 1 } else { break }
|
2025-11-11 22:35:45 +09:00
|
|
|
}
|
|
|
|
|
if e > b { return str.substring(b, e) }
|
|
|
|
|
return ""
|
|
|
|
|
}
|
|
|
|
|
}
|