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
|
||||
|
||||
@ -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 } }
|
||||
|
||||
@ -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 boundary(Stage‑B 安定性のためここで実装)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 Stage‑B / 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)
|
||||
// Stage‑B 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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 ""
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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" {
|
||||
|
||||
@ -9,6 +9,18 @@
|
||||
// - ctx.using_paths: Array<String>(将来のヒント/本箱では純粋合成のみ)
|
||||
// - ctx.cwd: String(相対の基準)
|
||||
|
||||
// Quick README (Phase 25.1 using設計)
|
||||
// - 役割: using 解決の唯一の窓口(SSOT)。parser/Stage‑B/Stage‑1 は 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_json(nyash.toml 相当)と using_entries_json(UsingCollector 出力)を突き合わせ、
|
||||
// 「解決済み 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 Stage‑1 entry.
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user