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:
nyash-codex
2025-11-21 06:25:17 +09:00
parent baf028a94f
commit f9d100ce01
366 changed files with 14322 additions and 5236 deletions

View File

@ -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 Stage1 JSON v0 (Program)
// Bridge(JSON v0) が Program v0 を受け取り MIR に lowering するため、ここでは AST(JSON v0) を出力する。
// 既定で MIR 直出力は行わない(重い経路を避け、一行出力を保証)。
// StageB/selfhost: body_src は Main.main 内のブロック本文なので、ParserBox の block パーサ
// を直接使って Program(JSON) を構成する(トップレベル parser_loop は使わない)
local block_res = p.parse_block2(body_src, 0)
// StageB/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 + "]"

View File

@ -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 StageB.
// 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 Stage1 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 は空のまま返すStageB 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