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

View File

@ -51,11 +51,14 @@ box ParserBox {
// === JSON utilities ===
esc_json(s) {
// Defensive: accept null/void and normalize to string once
if s == null { return "" }
local str = "" + s
local out = ""
local i = 0
local n = s.length()
local n = str.length()
loop(i < n) {
local ch = s.substring(i, i+1)
local ch = str.substring(i, i+1)
if ch == "\\" { out = out + "\\\\" }
else { if ch == "\"" { out = out + "\\\"" }
else { out = out + ch } }

View File

@ -1,20 +1,116 @@
// Moved from apps/selfhost-compiler/boxes/parser/scan/parser_string_utils_box.hako
// ParserStringUtilsBox — Delegation to StringHelpers (unified string utilities)
// ParserStringUtilsBox — Minimal self-contained string helpers
// Responsibility: Backward compatibility wrapper for parser code
// Notes: All functionality now provided by apps/selfhost/common/string_helpers.hako
using sh_core as StringHelpers
// Notes: sh_core への依存で VM 差分が出たため、ここに最小実装を内包する。
static box ParserStringUtilsBox {
// Delegate all methods to StringHelpers (centralized implementation)
i2s(v) { return StringHelpers.int_to_str(v) }
is_digit(ch) { return StringHelpers.is_digit(ch) }
is_space(ch) { return StringHelpers.is_space(ch) }
is_alpha(ch) { return StringHelpers.is_alpha(ch) }
starts_with(src, i, pat) { return StringHelpers.starts_with(src, i, pat) }
starts_with_kw(src, i, kw) { return StringHelpers.starts_with_kw(src, i, kw) }
index_of(src, i, pat) { return StringHelpers.index_of(src, i, pat) }
trim(s) { return StringHelpers.trim(s) }
to_int(s) { return StringHelpers.to_i64(s) }
skip_ws(src, i) { return StringHelpers.skip_ws(src, i) }
// Numeric to string (minimal)
i2s(v) {
local n = ParserStringUtilsBox.to_int(v)
if n == 0 { return "0" }
if n < 0 { return "-" + ParserStringUtilsBox.i2s(0 - n) }
local out = ""
loop(n > 0) {
local d = n % 10
out = "0123456789".substring(d, d + 1) + out
n = n / 10
}
return out
}
is_digit(ch) { return ch >= "0" && ch <= "9" }
is_space(ch) { return ch == " " || ch == "\t" || ch == "\n" || ch == "\r" }
is_alpha(ch) { return (ch >= "A" && ch <= "Z") || (ch >= "a" && ch <= "z") || ch == "_" }
starts_with(src, i, pat) {
if src == null || pat == null { return 0 }
local n = src.length()
local m = pat.length()
if i + m > n { return 0 }
local k = 0
loop(k < m) {
if src.substring(i + k, i + k + 1) != pat.substring(k, k + 1) { return 0 }
k = k + 1
}
return 1
}
// Keyword match with boundaryStageB 安定性のためここで実装)
starts_with_kw(src, i, kw) {
if src == null || kw == null { return 0 }
local n = src.length()
local m = kw.length()
if i + m > n { return 0 }
local k = 0
loop(k < m) {
if src.substring(i + k, i + k + 1) != kw.substring(k, k + 1) { return 0 }
k = k + 1
}
local j = i + m
if j >= n { return 1 }
local ch = src.substring(j, j + 1)
if ParserStringUtilsBox.is_alpha(ch) == 1 || ParserStringUtilsBox.is_digit(ch) == 1 { return 0 }
return 1
}
index_of(src, i, pat) {
if src == null || pat == null { return -1 }
local n = src.length()
local m = pat.length()
if m == 0 { return i }
local j = i
loop(j + m <= n) {
if ParserStringUtilsBox.starts_with(src, j, pat) == 1 { return j }
j = j + 1
}
return -1
}
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 ""
}
to_int(s) {
if s == null { return 0 }
local str = "" + s
local n = str.length()
if n == 0 { return 0 }
local neg = 0
local i = 0
if str.substring(0, 1) == "-" { neg = 1 i = 1 }
local acc = 0
loop(i < n) {
local ch = str.substring(i, i + 1)
if ch < "0" || ch > "9" { break }
acc = acc * 10 + ("0123456789".indexOf(ch))
i = i + 1
}
if neg == 1 { return 0 - acc }
return acc
}
skip_ws(src, i) {
if src == null { return i }
local n = src.length()
local j = i
loop(j < n) {
local ch = src.substring(j, j + 1)
if ParserStringUtilsBox.is_space(ch) == 1 { j = j + 1 } else { break }
}
return j
}
}

View File

@ -7,15 +7,16 @@ using sh_core as StringHelpers // Required: using chain resolution not implemen
static box ParserControlBox {
// Parse: if (cond) { ... } else { ... }
parse_if(src, i, stmt_start, ctx) {
// Shallow recursion guard for StageB / selfhost: protect parse_if from
// accidental self-recursion via malformed input or control flow bugs.
// Depth guard (allow nesting, cap runaway recursion)
local if_depth_before = 0
{
local depth = env.get("HAKO_STAGEB_IF_DEPTH")
if depth != null && ("" + depth) != "0" {
local depth_raw = env.get("HAKO_STAGEB_IF_DEPTH")
if depth_raw != null { if_depth_before = StringHelpers.to_i64("" + depth_raw) }
if if_depth_before >= 64 {
print("[stageb/recursion] ParserControlBox.parse_if recursion detected")
return "{\"type\":\"If\",\"cond\":{\"type\":\"Bool\",\"value\":false},\"then\":[]}"
}
env.set("HAKO_STAGEB_IF_DEPTH", "1")
env.set("HAKO_STAGEB_IF_DEPTH", StringHelpers.int_to_str(if_depth_before + 1))
}
local j = i + 2 // skip "if"
j = ctx.skip_ws(src, j)
@ -52,7 +53,7 @@ static box ParserControlBox {
}
ctx.gpos_set(j)
env.set("HAKO_STAGEB_IF_DEPTH", "0")
env.set("HAKO_STAGEB_IF_DEPTH", StringHelpers.int_to_str(if_depth_before))
if else_json == null {
return "{\"type\":\"If\",\"cond\":" + cond + ",\"then\":" + then_json + "}"
} else {
@ -63,14 +64,15 @@ static box ParserControlBox {
// Parse: loop(cond) { ... }
// Dev tracing: set HAKO_PARSER_TRACE_LOOP=1 to enable debug prints
parse_loop(src, i, stmt_start, ctx) {
// Shallow recursion guard for parse_loop
local loop_depth_before = 0
{
local depth = env.get("HAKO_STAGEB_LOOP_DEPTH")
if depth != null && ("" + depth) != "0" {
local depth_raw = env.get("HAKO_STAGEB_LOOP_DEPTH")
if depth_raw != null { loop_depth_before = StringHelpers.to_i64("" + depth_raw) }
if loop_depth_before >= 64 {
print("[stageb/recursion] ParserControlBox.parse_loop recursion detected")
return "{\"type\":\"Loop\",\"cond\":{\"type\":\"Bool\",\"value\":false},\"body\":[]}"
}
env.set("HAKO_STAGEB_LOOP_DEPTH", "1")
env.set("HAKO_STAGEB_LOOP_DEPTH", StringHelpers.int_to_str(loop_depth_before + 1))
}
local trace = 0 // dev-only (disabled in Stage-B runtime: no env API here)
@ -154,7 +156,7 @@ static box ParserControlBox {
if j < src.length() { j = j + 1 } else { j = src.length() }
}
ctx.gpos_set(j)
env.set("HAKO_STAGEB_LOOP_DEPTH", "0")
env.set("HAKO_STAGEB_LOOP_DEPTH", StringHelpers.int_to_str(loop_depth_before))
return "{\"type\":\"Loop\",\"cond\":" + cond + ",\"body\":" + body_json + "}"
}
@ -222,15 +224,27 @@ static box ParserControlBox {
// Parse: { stmt1; stmt2; ... }
parse_block(src, i, ctx) {
local depth_before = 0
{
local depth = env.get("HAKO_STAGEB_BLOCK_DEPTH")
if depth != null && ("" + depth) != "0" {
local depth_raw = env.get("HAKO_STAGEB_BLOCK_DEPTH")
if depth_raw != null { depth_before = StringHelpers.to_i64("" + depth_raw) }
if depth_before >= 64 {
print("[stageb/recursion] ParserControlBox.parse_block recursion detected")
return "[]@" + ctx.i2s(i)
}
env.set("HAKO_STAGEB_BLOCK_DEPTH", "1")
env.set("HAKO_STAGEB_BLOCK_DEPTH", StringHelpers.int_to_str(depth_before + 1))
}
local j = ctx.skip_ws(src, i)
// StageB safety: inline whitespace skip in case ctx.skip_ws fails to advance on VM
{
local n = src.length()
local jj = j
loop(jj < n) {
local ch = src.substring(jj, jj+1)
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { jj = jj + 1 } else { break }
}
j = jj
}
if src.substring(j, j+1) != "{" { return "[]@" + ctx.i2s(j) }
j = j + 1
@ -283,7 +297,7 @@ static box ParserControlBox {
}
body = body + "]"
env.set("HAKO_STAGEB_BLOCK_DEPTH", "0")
env.set("HAKO_STAGEB_BLOCK_DEPTH", StringHelpers.int_to_str(depth_before))
return body + "@" + ctx.i2s(j)
}
}

View File

@ -15,12 +15,23 @@ static box ParserStmtBox {
local depth = env.get("HAKO_STAGEB_STMT_DEPTH")
if depth != null && ("" + depth) != "0" {
print("[stageb/recursion] ParserStmtBox.parse recursion detected")
// Fail-fast: return empty stmt and keep position unchanged
return ""
// Allow re-entry: reset depth to 1 and continueドロップしない
env.set("HAKO_STAGEB_STMT_DEPTH", "1")
} else {
env.set("HAKO_STAGEB_STMT_DEPTH", "1")
}
env.set("HAKO_STAGEB_STMT_DEPTH", "1")
}
local j = ctx.skip_ws(src, i)
// VM 差分で skip_ws が前進しないパスがあったため、手動で最小限の空白スキップをフォローする。
{
local n = src.length()
local jj = j
loop(jj < n) {
local ch = src.substring(jj, jj+1)
if ch == " " || ch == "\t" || ch == "\n" || ch == "\r" { jj = jj + 1 } else { break }
}
j = jj
}
{
local tr = env.get("NYASH_PARSER_TRACE")
if tr != null && ("" + tr) == "1" {
@ -86,49 +97,6 @@ static box ParserStmtBox {
return out_using
}
// assignment: IDENT '=' expr
if j < src.length() && ctx.is_alpha(src.substring(j, j+1)) {
{
local tr = env.get("NYASH_PARSER_TRACE")
if tr != null && ("" + tr) == "1" {
print("[parser/stmt] kind=assign-or-local j=" + ("" + j))
}
}
local idp0 = ctx.read_ident2(src, j)
local at0 = idp0.lastIndexOf("@")
if at0 > 0 {
local name0 = idp0.substring(0, at0)
local k0 = ctx.to_int(idp0.substring(at0+1, idp0.length()))
k0 = ctx.skip_ws(src, k0)
if k0 < src.length() && src.substring(k0, k0+1) == "=" {
local eq_two = "="
if k0 + 1 < src.length() { eq_two = src.substring(k0, k0+2) }
if eq_two != "==" {
k0 = k0 + 1
k0 = ctx.skip_ws(src, k0)
local default_local = "{\"type\":\"Int\",\"value\":0}"
local expr_json0 = default_local
local end_pos0 = k0
if k0 < src.length() {
local ahead = src.substring(k0, k0+1)
if ahead != "}" && ahead != ";" {
expr_json0 = ctx.parse_expr2(src, k0)
end_pos0 = ctx.gpos_get()
}
}
k0 = end_pos0
if k0 <= stmt_start {
if k0 < src.length() { k0 = k0 + 1 } else { k0 = src.length() }
}
ctx.gpos_set(k0)
// Reset recursion guard before returning
env.set("HAKO_STAGEB_STMT_DEPTH", "0")
return "{\"type\":\"Local\",\"name\":\"" + name0 + "\",\"expr\":" + expr_json0 + "}"
}
}
}
}
// return statement
if ctx.starts_with_kw(src, j, "return") == 1 {
{
@ -288,6 +256,49 @@ static box ParserStmtBox {
return out_try
}
// assignment: IDENT '=' exprキーワードより後段に配置して、'loop' などを誤認しないようにする)
if j < src.length() && ctx.is_alpha(src.substring(j, j+1)) {
{
local tr = env.get("NYASH_PARSER_TRACE")
if tr != null && ("" + tr) == "1" {
print("[parser/stmt] kind=assign-or-local j=" + ("" + j))
}
}
local idp0 = ctx.read_ident2(src, j)
local at0 = idp0.lastIndexOf("@")
if at0 > 0 {
local name0 = idp0.substring(0, at0)
local k0 = ctx.to_int(idp0.substring(at0+1, idp0.length()))
k0 = ctx.skip_ws(src, k0)
if k0 < src.length() && src.substring(k0, k0+1) == "=" {
local eq_two = "="
if k0 + 1 < src.length() { eq_two = src.substring(k0, k0+2) }
if eq_two != "==" {
k0 = k0 + 1
k0 = ctx.skip_ws(src, k0)
local default_local = "{\"type\":\"Int\",\"value\":0}"
local expr_json0 = default_local
local end_pos0 = k0
if k0 < src.length() {
local ahead = src.substring(k0, k0+1)
if ahead != "}" && ahead != ";" {
expr_json0 = ctx.parse_expr2(src, k0)
end_pos0 = ctx.gpos_get()
}
}
k0 = end_pos0
if k0 <= stmt_start {
if k0 < src.length() { k0 = k0 + 1 } else { k0 = src.length() }
}
ctx.gpos_set(k0)
// Reset recursion guard before returning
env.set("HAKO_STAGEB_STMT_DEPTH", "0")
return "{\"type\":\"Local\",\"name\":\"" + name0 + "\",\"expr\":" + expr_json0 + "}"
}
}
}
}
// Fallback: expression or unknown token
{
local tr = env.get("NYASH_PARSER_TRACE")
@ -365,6 +376,8 @@ static box ParserStmtBox {
if j < src.length() { j = j + 1 } else { j = src.length() }
}
ctx.gpos_set(j)
// Reset recursion guard before returning (using statements participate in stmt depth tracking)
env.set("HAKO_STAGEB_STMT_DEPTH", "0")
return ""
}
}

View File

@ -1,4 +1,8 @@
// UsingResolverBox — static, stateful resolver helpersインスタンス禁止・VM互換
// Boundary memo:
// - entry/using_resolver_box: file I/O + using 収集、modules_list を MapBox へ積む役。
// - pipeline_v2/using_resolver_box: modules_json 上で alias/path をテキスト検索で解決する役。
// RegexFlow の単一路ループのみで continue/backedge 分岐を持たないため、Region+next_i 化は不要と判断。
// State layout (Map): {
// alias_paths: Map, alias_names: Map, alias_keys: Array,
// modules_map: Map, modules_keys: Array

View File

@ -107,6 +107,10 @@ static box Stage1Cli {
// CLI dispatcher (stub)
method stage1_main(args) {
if env.get("STAGE1_CLI_DEBUG") == "1" {
local argc = 0; if args != null { argc = args.size() }
print("[stage1-cli/debug] stage1_main ENTRY: argc=" + ("" + argc) + " env_emits={prog=" + ("" + env.get("STAGE1_EMIT_PROGRAM_JSON")) + ",mir=" + ("" + env.get("STAGE1_EMIT_MIR_JSON")) + "} backend=" + ("" + env.get("STAGE1_BACKEND")))
}
{
local use_cli = env.get("NYASH_USE_STAGE1_CLI")
if use_cli == null || ("" + use_cli) != "1" {

View File

@ -9,6 +9,18 @@
// - ctx.using_paths: Array<String>(将来のヒント/本箱では純粋合成のみ)
// - ctx.cwd: String相対の基準
// Quick README (Phase 25.1 using設計)
// - 役割: using 解決の唯一の窓口SSOT。parser/StageB/Stage1 は IO や名前探索をここに委譲する。
// - I/F:
// - resolve(name, ctx) -> path|null : modules/cwd/using_paths から純粋に合成して返すIOなし
// - resolve_modules(modules_json, using_entries_json, ctx) -> modules_json_resolved|null :
// modules_jsonnyash.toml 相当)と using_entries_jsonUsingCollector 出力)を突き合わせ、
// 「解決済み modules_json」同名重複などの調停結果を返す。IOなし。
// - resolve_prefix(using_entries_json, modules_json, ctx) -> prefix_string :
// using_entries_json を modules_json を使ってパス解決し、ファイル読まずに
// 「prefix 文字列空でよいを組み立てるための情報」を返すスコープMVPは空文字返しでOK
// - ポリシー: IO/ファイル読み込みは絶対にしない。より重い処理は entry/pipeline 側の責務。
static box UsingResolveSSOTBox {
/// Resolve a module name to a file path string (or null when not found).
/// name: requested module name (e.g., "hako.mir.builder.internal.lower_return_int")
@ -55,4 +67,16 @@ static box UsingResolveSSOTBox {
if base.endsWith("/") { return base + leaf }
return base + "/" + leaf
}
// Merge modules_json + using_entries_json into resolved modules table (pure, no IO).
resolve_modules(modules_json, using_entries_json, ctx) {
// MVP: just echo back modules_json (keep behavior unchanged) to establish interface.
return modules_json
}
// Build prefix string from using_entries_json with modules_json (pure, no IO).
resolve_prefix(using_entries_json, modules_json, ctx) {
// MVP: no file read, so prefix is empty. Interface is reserved for Stage1 entry.
return ""
}
}