fix(parser): Part 3-4 - Fix remaining infinite loops in parser subsystems

Part 3 (included):
- parser_box.hako: Fix semicolon consumption loop (lines 401-421)

Part 4 (systematic fix of 7 files):
- parser_number_scan_box.hako: Fix scan_int digit scanning loop
- parser_literal_box.hako: Fix parse_map and parse_array loops (2 loops)
- parser_peek_box.hako: Fix peek expression arms parsing loop
- parser_control_box.hako: Fix parse_block and semicolon loops (2 loops)
- parser_exception_box.hako: Fix try/catch clauses loop
- parser_stmt_box.hako: Fix using statement namespace parsing loop

Root cause: MirBuilder bug with loop(flag == 0/1) + nested if-else
- Generated broken MIR with PHI self-references
- Caused infinite loops in VM execution

Solution: Convert all problematic loops to MirBuilder-friendly pattern:
  loop(true) { if condition { continue } else { break } }

Test results:  Stage-1 CLI now completes without timeout
- Exit code: 0
- MIR dump created successfully
- parse_program2 ws_init: 1 iteration (was infinite)

Total loops fixed across all parts: 18 loops in 11 files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
nyash-codex
2025-11-24 21:34:22 +09:00
parent 54b9597af6
commit b21ee3c854
7 changed files with 182 additions and 220 deletions

View File

@ -10,54 +10,49 @@ static box ParserLiteralBox {
local j = i + 1 // skip opening '{' local j = i + 1 // skip opening '{'
local out = "[" local out = "["
local first = 1 local first = 1
local cont = 1
local guard = 0
local max = 400000
loop(cont == 1) { // MirBuilder-friendly: explicit break instead of flag
if guard > max { cont = 0 } else { guard = guard + 1 } loop(true) {
j = ctx.skip_ws(src, j) j = ctx.skip_ws(src, j)
if j >= n { if j >= n { break }
cont = 0
if src.substring(j, j+1) == "}" {
j = j + 1
break
}
// key (string only for Stage-2)
if src.substring(j, j+1) != "\"" {
// degrade by skipping one char to avoid infinite loop
j = j + 1
continue
}
local key_raw = ctx.read_string_lit(src, j)
j = ctx.gpos_get()
j = ctx.skip_ws(src, j)
if src.substring(j, j+1) == ":" { j = j + 1 }
j = ctx.skip_ws(src, j)
local val_json = ctx.parse_expr2(src, j)
j = ctx.gpos_get()
local key_json = "{\"type\":\"Str\",\"value\":\"" + ctx.esc_json(key_raw) + "\"}"
if first == 1 {
out = out + key_json + "," + val_json
first = 0
} else { } else {
if src.substring(j, j+1) == "}" { out = out + "," + key_json + "," + val_json
j = j + 1 }
cont = 0
} else {
// key (string only for Stage-2)
if src.substring(j, j+1) != "\"" {
// degrade by skipping one char to avoid infinite loop
j = j + 1
continue
}
local key_raw = ctx.read_string_lit(src, j) // optional comma
j = ctx.gpos_get() local before2 = j
j = ctx.skip_ws(src, j) j = ctx.skip_ws(src, j)
if src.substring(j, j+1) == ":" { j = j + 1 } if j < n && src.substring(j, j+1) == "," { j = j + 1 }
j = ctx.skip_ws(src, j)
local val_json = ctx.parse_expr2(src, j)
j = ctx.gpos_get()
local key_json = "{\"type\":\"Str\",\"value\":\"" + ctx.esc_json(key_raw) + "\"}"
if first == 1 { // progress guard (in case of malformed input)
out = out + key_json + "," + val_json if j <= before2 {
first = 0 if j < n { j = j + 1 } else { j = n }
} else {
out = out + "," + key_json + "," + val_json
}
// optional comma
local before2 = j
j = ctx.skip_ws(src, j)
if j < n && src.substring(j, j+1) == "," { j = j + 1 }
// progress guard (in case of malformed input)
if j <= before2 {
if j < n { j = j + 1 } else { j = n }
}
}
} }
} }
@ -72,42 +67,37 @@ static box ParserLiteralBox {
local j = i + 1 // skip opening '[' local j = i + 1 // skip opening '['
local out = "[" local out = "["
local first = 1 local first = 1
local cont = 1
local guard = 0
local max = 400000
loop(cont == 1) { // MirBuilder-friendly: explicit break instead of flag
if guard > max { cont = 0 } else { guard = guard + 1 } loop(true) {
j = ctx.skip_ws(src, j) j = ctx.skip_ws(src, j)
if j >= n { if j >= n { break }
cont = 0
if src.substring(j, j+1) == "]" {
j = j + 1
break
}
local before = j
local ej = ctx.parse_expr2(src, j)
j = ctx.gpos_get()
if first == 1 {
out = out + ej
first = 0
} else { } else {
if src.substring(j, j+1) == "]" { out = out + "," + ej
j = j + 1 }
cont = 0
} else {
local before = j
local ej = ctx.parse_expr2(src, j)
j = ctx.gpos_get()
if first == 1 { // optional comma+whitespace
out = out + ej local before2 = j
first = 0 j = ctx.skip_ws(src, j)
} else { if j < n && src.substring(j, j+1) == "," { j = j + 1 }
out = out + "," + ej
}
// optional comma+whitespace // progress guard
local before2 = j if j <= before {
j = ctx.skip_ws(src, j) if j < n { j = j + 1 } else { j = n }
if j < n && src.substring(j, j+1) == "," { j = j + 1 }
// progress guard
if j <= before {
if j < n { j = j + 1 } else { j = n }
}
}
} }
} }

View File

@ -21,77 +21,73 @@ static box ParserPeekBox {
local arms_json = "[" local arms_json = "["
local first_arm = 1 local first_arm = 1
local else_json = null local else_json = null
local contp = 1
local guardp = 0
local maxp = 400000
loop(contp == 1) { // MirBuilder-friendly: explicit break instead of flag
if guardp > maxp { contp = 0 } else { guardp = guardp + 1 } loop(true) {
j = ctx.skip_ws(src, j) j = ctx.skip_ws(src, j)
if j >= n { if j >= n { break }
contp = 0
} else { if src.substring(j, j+1) == "}" {
if src.substring(j, j+1) == "}" { j = j + 1
break
}
// else arm or labeled arm
if ctx.starts_with_kw(src, j, "else") == 1 {
j = j + 4
j = ctx.skip_ws(src, j)
if src.substring(j, j+2) == "=>" { j = j + 2 }
j = ctx.skip_ws(src, j)
// else body may be a block or bare expr
if src.substring(j, j+1) == "{" {
j = j + 1 j = j + 1
contp = 0 j = ctx.skip_ws(src, j)
else_json = ctx.parse_expr2(src, j)
j = ctx.gpos_get()
j = ctx.skip_ws(src, j)
if src.substring(j, j+1) == "}" { j = j + 1 }
} else { } else {
// else arm or labeled arm else_json = ctx.parse_expr2(src, j)
if ctx.starts_with_kw(src, j, "else") == 1 { j = ctx.gpos_get()
j = j + 4
j = ctx.skip_ws(src, j)
if src.substring(j, j+2) == "=>" { j = j + 2 }
j = ctx.skip_ws(src, j)
// else body may be a block or bare expr
if src.substring(j, j+1) == "{" {
j = j + 1
j = ctx.skip_ws(src, j)
else_json = ctx.parse_expr2(src, j)
j = ctx.gpos_get()
j = ctx.skip_ws(src, j)
if src.substring(j, j+1) == "}" { j = j + 1 }
} else {
else_json = ctx.parse_expr2(src, j)
j = ctx.gpos_get()
}
} else {
// labeled arm: string literal label
if src.substring(j, j+1) != "\"" {
// degrade safely to avoid infinite loop
j = j + 1
continue
}
local label_raw = ctx.read_string_lit(src, j)
j = ctx.gpos_get()
j = ctx.skip_ws(src, j)
if src.substring(j, j+2) == "=>" { j = j + 2 }
j = ctx.skip_ws(src, j)
// arm expr: block or bare expr
local expr_json = "{\"type\":\"Int\",\"value\":0}"
if src.substring(j, j+1) == "{" {
j = j + 1
j = ctx.skip_ws(src, j)
expr_json = ctx.parse_expr2(src, j)
j = ctx.gpos_get()
j = ctx.skip_ws(src, j)
if src.substring(j, j+1) == "}" { j = j + 1 }
} else {
expr_json = ctx.parse_expr2(src, j)
j = ctx.gpos_get()
}
local arm_json = "{\"label\":\"" + ctx.esc_json(label_raw) + "\",\"expr\":" + expr_json + "}"
if first_arm == 1 {
arms_json = arms_json + arm_json
first_arm = 0
} else {
arms_json = arms_json + "," + arm_json
}
}
} }
continue
}
// labeled arm: string literal label
if src.substring(j, j+1) != "\"" {
// degrade safely to avoid infinite loop
j = j + 1
continue
}
local label_raw = ctx.read_string_lit(src, j)
j = ctx.gpos_get()
j = ctx.skip_ws(src, j)
if src.substring(j, j+2) == "=>" { j = j + 2 }
j = ctx.skip_ws(src, j)
// arm expr: block or bare expr
local expr_json = "{\"type\":\"Int\",\"value\":0}"
if src.substring(j, j+1) == "{" {
j = j + 1
j = ctx.skip_ws(src, j)
expr_json = ctx.parse_expr2(src, j)
j = ctx.gpos_get()
j = ctx.skip_ws(src, j)
if src.substring(j, j+1) == "}" { j = j + 1 }
} else {
expr_json = ctx.parse_expr2(src, j)
j = ctx.gpos_get()
}
local arm_json = "{\"label\":\"" + ctx.esc_json(label_raw) + "\",\"expr\":" + expr_json + "}"
if first_arm == 1 {
arms_json = arms_json + arm_json
first_arm = 0
} else {
arms_json = arms_json + "," + arm_json
} }
} }

View File

@ -398,18 +398,8 @@ box ParserBox {
print("[parser/trace:program2] i=" + ("" + i) + " stmt_len=" + ("" + s.length()) + " guard=" + ("" + guard_prog)) print("[parser/trace:program2] i=" + ("" + i) + " stmt_len=" + ("" + s.length()) + " guard=" + ("" + guard_prog))
} }
// ===== 6) CONSUME OPTIONAL SEMICOLONS ===== // ===== 6) CONSUME OPTIONAL SEMICOLONS - MirBuilder-friendly =====
local done_semi = 0 loop(true) {
local guard_semi = 0
local max_semi = 100000
loop(done_semi == 0) {
if guard_semi > max_semi {
done_semi = 1
} else {
guard_semi = guard_semi + 1
}
local before_semi = i local before_semi = i
// Inline ws skip // Inline ws skip
@ -424,13 +414,10 @@ box ParserBox {
if i < n && src.substring(i, i+1) == ";" { if i < n && src.substring(i, i+1) == ";" {
i = i + 1 i = i + 1
} else { continue
done_semi = 1
} }
if i == before_semi { break
done_semi = 1
}
} }
// ===== 7) EMIT STATEMENT ===== // ===== 7) EMIT STATEMENT =====

View File

@ -9,14 +9,13 @@ static box ParserNumberScanBox {
if src == null { return "{\"type\":\"Int\",\"value\":0}@" + ParserCommonUtilsBox.i2s(i) } if src == null { return "{\"type\":\"Int\",\"value\":0}@" + ParserCommonUtilsBox.i2s(i) }
local n = src.length() local n = src.length()
local j = i local j = i
local cont = 1 // MirBuilder-friendly: explicit continue/break instead of flag
local guard = 0 loop(j < n) {
local max = 100000 if ParserCommonUtilsBox.is_digit(src.substring(j, j+1)) {
loop(cont == 1) { j = j + 1
if guard > max { cont = 0 } else { guard = guard + 1 } continue
if j < n { }
if ParserCommonUtilsBox.is_digit(src.substring(j, j+1)) { j = j + 1 } else { cont = 0 } break
} else { cont = 0 }
} }
local s = src.substring(i, j) local s = src.substring(i, j)
if s.length() == 0 { s = "0" } if s.length() == 0 { s = "0" }

View File

@ -250,48 +250,45 @@ static box ParserControlBox {
local body = "[" local body = "["
local first = 1 local first = 1
local cont_block = 1
loop(cont_block == 1) { // MirBuilder-friendly: explicit break instead of flag
loop(true) {
j = ctx.skip_ws(src, j) j = ctx.skip_ws(src, j)
if j >= src.length() { if j >= src.length() { break }
cont_block = 0
} else { if src.substring(j, j+1) == "}" {
if src.substring(j, j+1) == "}" { j = j + 1
break
}
local start_j = j
local s = ctx.parse_stmt2(src, j)
j = ctx.gpos_get()
// Progress guard: ensure forward movement to avoid infinite loop on malformed input
if j <= start_j {
if j < src.length() { j = j + 1 } else { j = src.length() }
ctx.gpos_set(j)
}
// consume optional semicolons (ASI minimal) - MirBuilder-friendly
loop(true) {
local before = j
j = ctx.skip_ws(src, j)
if j < src.length() && src.substring(j, j+1) == ";" {
j = j + 1 j = j + 1
cont_block = 0 continue
}
break
}
if s.length() > 0 {
if first == 1 {
body = body + s
first = 0
} else { } else {
local start_j = j body = body + "," + s
local s = ctx.parse_stmt2(src, j)
j = ctx.gpos_get()
// Progress guard: ensure forward movement to avoid infinite loop on malformed input
if j <= start_j {
if j < src.length() { j = j + 1 } else { j = src.length() }
ctx.gpos_set(j)
}
// consume optional semicolons (ASI minimal)
local done = 0
local guard = 0
local max = 100000
loop(done == 0) {
if guard > max { done = 1 } else { guard = guard + 1 }
local before = j
j = ctx.skip_ws(src, j)
if j < src.length() && src.substring(j, j+1) == ";" { j = j + 1 } else { done = 1 }
if j == before { done = 1 }
}
if s.length() > 0 {
if first == 1 {
body = body + s
first = 0
} else {
body = body + "," + s
}
}
} }
} }
} }

View File

@ -41,15 +41,13 @@ static box ParserExceptionBox {
local catches_json = "[" local catches_json = "["
local catch_first = 1 local catch_first = 1
// zero or more catch // zero or more catch - MirBuilder-friendly
local guard_ct = 0 loop(true) {
local max_ct = 100
local cont_ct = 1
loop(cont_ct == 1) {
if guard_ct > max_ct { cont_ct = 0 } else { guard_ct = guard_ct + 1 }
j = ctx.skip_ws(src, j) j = ctx.skip_ws(src, j)
if ctx.starts_with_kw(src, j, "catch") == 1 { if ctx.starts_with_kw(src, j, "catch") != 1 { break }
{
j = j + 5 j = j + 5
j = ctx.skip_ws(src, j) j = ctx.skip_ws(src, j)
local catch_type = null local catch_type = null
@ -112,8 +110,6 @@ static box ParserExceptionBox {
catch_first = 0 catch_first = 0
} }
} }
} else {
cont_ct = 0
} }
} }

View File

@ -344,19 +344,16 @@ static box ParserStmtBox {
local at = idp.lastIndexOf("@") local at = idp.lastIndexOf("@")
local name = idp.substring(0, at) local name = idp.substring(0, at)
j = ctx.to_int(idp.substring(at+1, idp.length())) j = ctx.to_int(idp.substring(at+1, idp.length()))
local cont = 1 // MirBuilder-friendly: explicit break instead of flag
loop(cont == 1) { loop(true) {
j = ParserStringUtilsBox.skip_ws(src, j) j = ParserStringUtilsBox.skip_ws(src, j)
if src.substring(j, j+1) == "." { if src.substring(j, j+1) != "." { break }
j = j + 1 j = j + 1
j = ParserStringUtilsBox.skip_ws(src, j) j = ParserStringUtilsBox.skip_ws(src, j)
idp = ctx.read_ident2(src, j) idp = ctx.read_ident2(src, j)
at = idp.lastIndexOf("@") at = idp.lastIndexOf("@")
name = name + "." + idp.substring(0, at) name = name + "." + idp.substring(0, at)
j = ctx.to_int(idp.substring(at+1, idp.length())) j = ctx.to_int(idp.substring(at+1, idp.length()))
} else {
cont = 0
}
} }
j = ParserStringUtilsBox.skip_ws(src, j) j = ParserStringUtilsBox.skip_ws(src, j)
local alias2 = null local alias2 = null