chore: Phase 25.1 完了 - LoopForm v2/Stage1 CLI/環境変数削減 + Phase 26-D からの変更
Phase 25.1 完了成果: - ✅ LoopForm v2 テスト・ドキュメント・コメント完備 - 4ケース(A/B/C/D)完全テストカバレッジ - 最小再現ケース作成(SSAバグ調査用) - SSOT文書作成(loopform_ssot.md) - 全ソースに [LoopForm] コメントタグ追加 - ✅ Stage-1 CLI デバッグ環境構築 - stage1_cli.hako 実装 - stage1_bridge.rs ブリッジ実装 - デバッグツール作成(stage1_debug.sh/stage1_minimal.sh) - アーキテクチャ改善提案文書 - ✅ 環境変数削減計画策定 - 25変数の完全調査・分類 - 6段階削減ロードマップ(25→5、80%削減) - 即時削除可能変数特定(NYASH_CONFIG/NYASH_DEBUG) Phase 26-D からの累積変更: - PHI実装改善(ExitPhiBuilder/HeaderPhiBuilder等) - MIRビルダーリファクタリング - 型伝播・最適化パス改善 - その他約300ファイルの累積変更 🎯 技術的成果: - SSAバグ根本原因特定(条件分岐内loop変数変更) - Region+next_iパターン適用完了(UsingCollectorBox等) - LoopFormパターン文書化・テスト化完了 - セルフホスティング基盤強化 Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: ChatGPT <noreply@openai.com> Co-Authored-By: Task Assistant <task@anthropic.com>
This commit is contained in:
@ -795,8 +795,8 @@ static box StageBFuncScannerBox {
|
||||
}
|
||||
|
||||
local body = s.substring(open_idx + 1, close_idx)
|
||||
// Include Main.main as well so defs covers entry + helpers
|
||||
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 = StageBFuncScannerBox._scan_methods(body, box_name, skip_main, include_me)
|
||||
@ -979,16 +979,29 @@ static box StageBFuncScannerBox {
|
||||
method_body = FuncScannerBox._strip_comments(method_body)
|
||||
method_body = FuncScannerBox._trim(method_body)
|
||||
|
||||
// Parse method body to JSON (statement list) using block parser
|
||||
// Parse method body to JSON (statement list)
|
||||
// Policy: block parser first (braced) to avoid VM dispatch misrouting, then Program parser as backup.
|
||||
local body_json = null
|
||||
if method_body.length() > 0 {
|
||||
local p = new ParserBox()
|
||||
p.stage3_enable(1)
|
||||
local block_src = "{" + method_body + "}"
|
||||
local block_res = p.parse_block2(block_src, 0)
|
||||
local at = block_res.lastIndexOf("@")
|
||||
if at >= 0 {
|
||||
body_json = block_res.substring(0, at)
|
||||
// 1st: block parser with explicit braces (LoopForm-friendly, avoids ctx misbinding)
|
||||
{
|
||||
local p = new ParserBox()
|
||||
p.stage3_enable(1)
|
||||
local block_src = "{" + method_body + "}"
|
||||
local block_res = p.parse_block2(block_src, 0)
|
||||
local at = block_res.lastIndexOf("@")
|
||||
if at >= 0 { body_json = block_res.substring(0, at) }
|
||||
}
|
||||
// 2nd: program parser trims body JSON if block path produced nothing (opt-in)
|
||||
if body_json == null || body_json == "" || body_json == "[]" {
|
||||
local allow_prog = env.get("HAKO_STAGEB_FUNC_SCAN_PROG_FALLBACK")
|
||||
if allow_prog != null && ("" + allow_prog) == "1" {
|
||||
local p2 = new ParserBox()
|
||||
p2.stage3_enable(1)
|
||||
local prog_json = p2.parse_program2(method_body)
|
||||
local extracted = me._extract_body_from_program(prog_json)
|
||||
if extracted.length() > 0 { body_json = extracted }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1017,6 +1030,49 @@ static box StageBFuncScannerBox {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Helper: extract Program.body array from Program(JSON) string
|
||||
_extract_body_from_program(prog_json) {
|
||||
if prog_json == null { return "" }
|
||||
local s = "" + prog_json
|
||||
local n = s.length()
|
||||
local head = "\"body\":"
|
||||
local hlen = head.length()
|
||||
local start = -1
|
||||
{
|
||||
local i = 0
|
||||
loop(i + hlen <= n) {
|
||||
if s.substring(i, i + hlen) == head { start = i + hlen break }
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
if start < 0 { return "" }
|
||||
|
||||
// Find '[' after "body":
|
||||
local open = -1
|
||||
{
|
||||
local j = start
|
||||
loop(j < n) {
|
||||
local ch = s.substring(j, j + 1)
|
||||
if ch == "[" { open = j break }
|
||||
j = j + 1
|
||||
}
|
||||
}
|
||||
if open < 0 { return "" }
|
||||
|
||||
local depth = 0
|
||||
local k = open
|
||||
loop(k < n) {
|
||||
local ch2 = s.substring(k, k + 1)
|
||||
if ch2 == "[" { depth = depth + 1 }
|
||||
else if ch2 == "]" {
|
||||
depth = depth - 1
|
||||
if depth == 0 { return s.substring(open, k + 1) }
|
||||
}
|
||||
k = k + 1
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// Helper: 空白文字スキップ(FuncScannerBox に委譲)
|
||||
_skip_whitespace(s, idx) {
|
||||
return FuncScannerBox.skip_whitespace(s, idx)
|
||||
@ -1151,14 +1207,30 @@ static box StageBDriverBox {
|
||||
// 6) Parse and emit Stage‑1 JSON v0 (Program)
|
||||
// Bridge(JSON v0) が Program v0 を受け取り MIR に lowering するため、ここでは AST(JSON v0) を出力する。
|
||||
// 既定で MIR 直出力は行わない(重い経路を避け、一行出力を保証)。
|
||||
// Stage‑B/selfhost: body_src は Main.main 内のブロック本文なので、ParserBox の block パーサ
|
||||
// を直接使って Program(JSON) を構成する(トップレベル parser_loop は使わない)。
|
||||
local block_res = p.parse_block2(body_src, 0)
|
||||
// Stage‑B/selfhost: body_src は Main.main 内のブロック本文(外側の {} は含まない)。
|
||||
// 安定性向上のため Program パーサを優先し、空の場合のみ block パーサで再度包んで解釈する。
|
||||
local ast_json = "{\"version\":0,\"kind\":\"Program\",\"body\":[]}"
|
||||
{
|
||||
local at = block_res.lastIndexOf("@")
|
||||
if at >= 0 {
|
||||
local body_json = block_res.substring(0, at)
|
||||
local body_json = null
|
||||
|
||||
// Prefer block parser with explicit braces to avoid VM dispatch drift seen on program parser.
|
||||
if body_src != null {
|
||||
local block_src = "{" + body_src + "}"
|
||||
local block_res = p.parse_block2(block_src, 0)
|
||||
local at = block_res.lastIndexOf("@")
|
||||
if at >= 0 { body_json = block_res.substring(0, at) }
|
||||
}
|
||||
|
||||
// Optional: enable program parser fallback only when explicitly requested
|
||||
if (body_json == null || body_json == "" || body_json == "[]") {
|
||||
local allow_prog = env.get("HAKO_STAGEB_PROGRAM_PARSE_FALLBACK")
|
||||
if allow_prog != null && ("" + allow_prog) == "1" {
|
||||
local prog_json = p.parse_program2(body_src)
|
||||
body_json = StageBFuncScannerBox._extract_body_from_program(prog_json)
|
||||
}
|
||||
}
|
||||
|
||||
if body_json != null && body_json != "" {
|
||||
ast_json = "{\"version\":0,\"kind\":\"Program\",\"body\":" + body_json + "}"
|
||||
}
|
||||
}
|
||||
@ -1246,6 +1318,8 @@ static box StageBDriverBox {
|
||||
local mparams = def.get("params")
|
||||
local mbody = "" + def.get("body_json")
|
||||
local mbox = "" + def.get("box")
|
||||
// Wrap body in Block node to align with Program.body shape expectations
|
||||
local wrapped_body = "{\"type\":\"Block\",\"body\":" + mbody + "}"
|
||||
// Build params array JSON
|
||||
local params_arr = "["
|
||||
local pi = 0
|
||||
@ -1257,7 +1331,7 @@ static box StageBDriverBox {
|
||||
}
|
||||
params_arr = params_arr + "]"
|
||||
if mi > 0 { defs_json = defs_json + "," }
|
||||
defs_json = defs_json + "{\"name\":\"" + mname + "\",\"params\":" + params_arr + ",\"body\":" + mbody + ",\"box\":\"" + mbox + "\"}"
|
||||
defs_json = defs_json + "{\"name\":\"" + mname + "\",\"params\":" + params_arr + ",\"body\":" + wrapped_body + ",\"box\":\"" + mbox + "\"}"
|
||||
mi = mi + 1
|
||||
}
|
||||
defs_json = defs_json + "]"
|
||||
|
||||
@ -3,6 +3,9 @@
|
||||
// - Collect line-based using declarations via UsingCollectorBox.
|
||||
// - Resolve namespace targets against `nyash.toml` `[modules]` entries (fed via env).
|
||||
// - Read the referenced .hako files and return a concatenated prefix for Stage‑B.
|
||||
// Structure tags:
|
||||
// [Region] JSON/ArrayBox/MapBox ループはすべて Region+next_i 形に統一して SSA を安定化。
|
||||
// [LoopForm] carrier=pinned を明示し、continue/break を next_i で合流させる。
|
||||
//
|
||||
// Env requirements:
|
||||
// - HAKO_STAGEB_MODULES_LIST: `name=path` entries joined by "|||".
|
||||
@ -13,6 +16,10 @@ using lang.compiler.parser.scan.parser_common_utils_box as ParserCommonUtilsBox
|
||||
using selfhost.shared.json.utils.json_frag as JsonFragBox
|
||||
|
||||
static box Stage1UsingResolverBox {
|
||||
// Entrypoint: collect `using` entries and stitch a prefix.
|
||||
// - HAKO_STAGEB_APPLY_USINGS=0 → prefix ""(defs/body には何も足さない)
|
||||
// - Depth guard prevents recursive self-calls from cascading.
|
||||
// [Region] entries 走査は pos/next_pos で一意に前進。pinned: entries/seen, carrier: i。
|
||||
resolve_for_source(src) {
|
||||
// Shallow recursion guard to prevent accidental self-recursion in Stage‑1 using resolver.
|
||||
{
|
||||
@ -24,50 +31,88 @@ static box Stage1UsingResolverBox {
|
||||
env.set("HAKO_STAGEB_USING_DEPTH", "1")
|
||||
}
|
||||
|
||||
if src == null { return "" }
|
||||
local apply_flag = env.get("HAKO_STAGEB_APPLY_USINGS")
|
||||
if apply_flag != null && ("" + apply_flag) == "0" { return "" }
|
||||
|
||||
local entries = me._collect_using_entries(src)
|
||||
if entries == null || entries.length() == 0 { return "" }
|
||||
|
||||
local modules = me._build_module_map()
|
||||
local seen = new MapBox()
|
||||
local prefix = ""
|
||||
if src != null {
|
||||
local apply_flag = env.get("HAKO_STAGEB_APPLY_USINGS")
|
||||
// apply_flag==0 なら prefix は空のまま返す(Stage‑B defs/body に変更なし)
|
||||
if apply_flag == null || ("" + apply_flag) != "0" {
|
||||
local entries = me._collect_using_entries(src)
|
||||
if entries != null && entries.length() > 0 {
|
||||
local modules = me._build_module_map()
|
||||
local seen = new MapBox()
|
||||
|
||||
local i = 0
|
||||
local n = entries.length()
|
||||
loop(i < n) {
|
||||
local entry = entries.get(i)
|
||||
local name = "" + entry.get("name")
|
||||
if name == "" { i = i + 1 continue }
|
||||
local i = 0
|
||||
local n = entries.length()
|
||||
loop(i < n) {
|
||||
// Region+next_i 形で多経路を末尾合流させる(SSA/PHI 安定化)
|
||||
local next_i = i + 1
|
||||
local entry = entries.get(i)
|
||||
local name = "" + entry.get("name")
|
||||
|
||||
local path = entry.get("path")
|
||||
if path == null || ("" + path) == "" {
|
||||
path = me._lookup_module_path(modules, name)
|
||||
} else {
|
||||
path = "" + path
|
||||
// 早期スキップ条件をまとめて evaluate
|
||||
local should_emit = 1
|
||||
if name == "" { should_emit = 0 }
|
||||
|
||||
// path 解決(明示 path 優先、無ければ modules マップ)
|
||||
local path = null
|
||||
if should_emit == 1 {
|
||||
path = entry.get("path")
|
||||
if path == null || ("" + path) == "" {
|
||||
path = me._lookup_module_path(modules, name)
|
||||
} else {
|
||||
path = "" + path
|
||||
}
|
||||
if path == null || path == "" { should_emit = 0 }
|
||||
}
|
||||
|
||||
// abs path 化
|
||||
local abs_path = null
|
||||
if should_emit == 1 {
|
||||
abs_path = me._abs_path(path)
|
||||
if abs_path == null || abs_path == "" { should_emit = 0 }
|
||||
}
|
||||
|
||||
// 重複 guard
|
||||
if should_emit == 1 {
|
||||
if seen.get(abs_path) == "1" { should_emit = 0 }
|
||||
}
|
||||
|
||||
// ファイル読み込み
|
||||
if should_emit == 1 {
|
||||
local code = me._read_file(abs_path)
|
||||
if code == null { should_emit = 0 }
|
||||
if should_emit == 1 {
|
||||
seen.set(abs_path, "1")
|
||||
prefix = prefix + "\n" + code + "\n"
|
||||
}
|
||||
}
|
||||
|
||||
i = next_i
|
||||
}
|
||||
}
|
||||
}
|
||||
if path == null || path == "" { i = i + 1 continue }
|
||||
|
||||
local abs_path = me._abs_path(path)
|
||||
if abs_path == null || abs_path == "" { i = i + 1 continue }
|
||||
if seen.get(abs_path) == "1" { i = i + 1 continue }
|
||||
|
||||
local code = me._read_file(abs_path)
|
||||
if code == null { i = i + 1 continue }
|
||||
|
||||
seen.set(abs_path, "1")
|
||||
prefix = prefix + "\n" + code + "\n"
|
||||
i = i + 1
|
||||
}
|
||||
|
||||
// Clear depth guard before returning
|
||||
// Clear depth guard before returning(適用なしでも guard を戻す)
|
||||
env.set("HAKO_STAGEB_USING_DEPTH", "0")
|
||||
return prefix
|
||||
}
|
||||
|
||||
// Program(JSON v0) に対する using 解決(IOなし、SSOT委譲)。
|
||||
// - modules_json: nyash.toml [modules] 相当の JSON
|
||||
// - using_entries_json: UsingCollectorBox.collect の生出力
|
||||
// - ctx: resolve_ssot_box 向けのヒント(modules/cwd/using_paths など)
|
||||
resolve_for_program_json(program_json, modules_json, using_entries_json, ctx) {
|
||||
using hako.using.resolve.ssot as UsingResolveSSOTBox
|
||||
// MVP: IO せずに SSOT が shape を整えるだけ。現状は透過返し。
|
||||
local resolved = UsingResolveSSOTBox.resolve_modules(modules_json, using_entries_json, ctx)
|
||||
// 返すものは modules_json をそのまま(今は純粋パス)。将来 prefix/string を付与する。
|
||||
return resolved
|
||||
}
|
||||
|
||||
// Collect entries from UsingCollectorBox JSON output.
|
||||
// Responsibility: JSON を配列にパースするだけ(path 解決やファイル読み込みは呼び元)。
|
||||
// [Region] JSON スキャンは pos/next_pos を明示し、early-exit も next_pos=n で一本化。
|
||||
_collect_using_entries(src) {
|
||||
local json = UsingCollectorBox.collect(src)
|
||||
if json == null || json == "" || json == "[]" { return null }
|
||||
@ -75,27 +120,36 @@ static box Stage1UsingResolverBox {
|
||||
local pos = 0
|
||||
local n = json.length()
|
||||
loop(pos < n) {
|
||||
// Region+next_pos 形(break を next_pos=n で表現)
|
||||
local next_pos = n
|
||||
local name_idx = JsonFragBox.index_of_from(json, "\"name\":\"", pos)
|
||||
if name_idx < 0 { break }
|
||||
local name = JsonFragBox.read_string_after(json, name_idx + 7)
|
||||
local obj_end = JsonFragBox.index_of_from(json, "}", name_idx)
|
||||
if obj_end < 0 { obj_end = n }
|
||||
if name_idx < 0 {
|
||||
next_pos = n
|
||||
} else {
|
||||
local name = JsonFragBox.read_string_after(json, name_idx + 7)
|
||||
local obj_end = JsonFragBox.index_of_from(json, "}", name_idx)
|
||||
if obj_end < 0 { obj_end = n }
|
||||
|
||||
local path = null
|
||||
local path_idx = JsonFragBox.index_of_from(json, "\"path\":\"", name_idx)
|
||||
if path_idx >= 0 && path_idx < obj_end {
|
||||
path = JsonFragBox.read_string_after(json, path_idx + 7)
|
||||
local path = null
|
||||
local path_idx = JsonFragBox.index_of_from(json, "\"path\":\"", name_idx)
|
||||
if path_idx >= 0 && path_idx < obj_end {
|
||||
path = JsonFragBox.read_string_after(json, path_idx + 7)
|
||||
}
|
||||
|
||||
local entry = new MapBox()
|
||||
entry.set("name", name)
|
||||
if path != null { entry.set("path", path) }
|
||||
out.push(entry)
|
||||
next_pos = obj_end + 1
|
||||
}
|
||||
|
||||
local entry = new MapBox()
|
||||
entry.set("name", name)
|
||||
if path != null { entry.set("path", path) }
|
||||
out.push(entry)
|
||||
pos = obj_end + 1
|
||||
pos = next_pos
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// Build modules map from env `HAKO_STAGEB_MODULES_LIST` ("name=path" joined by "|||").
|
||||
// Responsibility: env を MapBox に積むだけ。存在しない/空なら空 Map を返す。
|
||||
// [Region] delim 走査も start/next_start で一本化し、continue/break を排除した形に固定。
|
||||
_build_module_map() {
|
||||
local map = new MapBox()
|
||||
local raw = env.get("HAKO_STAGEB_MODULES_LIST")
|
||||
@ -105,13 +159,20 @@ static box Stage1UsingResolverBox {
|
||||
|
||||
local delim = "|||"
|
||||
local start = 0
|
||||
loop(true) {
|
||||
local cont = 1
|
||||
loop(cont == 1) {
|
||||
local next_start = str.length()
|
||||
local next = ParserCommonUtilsBox.index_of(str, start, delim)
|
||||
local seg = ""
|
||||
if next >= 0 { seg = str.substring(start, next) } else { seg = str.substring(start, str.length()) }
|
||||
if next >= 0 {
|
||||
seg = str.substring(start, next)
|
||||
next_start = next + delim.length()
|
||||
} else {
|
||||
seg = str.substring(start, str.length())
|
||||
cont = 0
|
||||
}
|
||||
me._push_module_entry(map, seg)
|
||||
if next < 0 { break }
|
||||
start = next + delim.length()
|
||||
start = next_start
|
||||
}
|
||||
return map
|
||||
}
|
||||
@ -152,7 +213,14 @@ static box Stage1UsingResolverBox {
|
||||
if path == null { return null }
|
||||
@fb = new FileBox()
|
||||
@ok = fb.open(path, "r")
|
||||
if ok != 1 { return null }
|
||||
if ok != 1 {
|
||||
// Dev note: keep quiet by default; enable verbose by setting HAKO_STAGEB_USING_DEBUG=1.
|
||||
local dbg = env.get("HAKO_STAGEB_USING_DEBUG")
|
||||
if dbg != null && ("" + dbg) == "1" {
|
||||
print("[stageb/using_resolver] failed to open file: " + path)
|
||||
}
|
||||
return null
|
||||
}
|
||||
@content = fb.read()
|
||||
fb.close()
|
||||
return content
|
||||
|
||||
Reference in New Issue
Block a user