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:
@ -10,54 +10,49 @@ static box ParserLiteralBox {
|
||||
local j = i + 1 // skip opening '{'
|
||||
local out = "["
|
||||
local first = 1
|
||||
local cont = 1
|
||||
local guard = 0
|
||||
local max = 400000
|
||||
|
||||
loop(cont == 1) {
|
||||
if guard > max { cont = 0 } else { guard = guard + 1 }
|
||||
// MirBuilder-friendly: explicit break instead of flag
|
||||
loop(true) {
|
||||
j = ctx.skip_ws(src, j)
|
||||
|
||||
if j >= n {
|
||||
cont = 0
|
||||
if j >= n { break }
|
||||
|
||||
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 {
|
||||
if src.substring(j, j+1) == "}" {
|
||||
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
|
||||
}
|
||||
out = out + "," + key_json + "," + val_json
|
||||
}
|
||||
|
||||
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) + "\"}"
|
||||
// optional comma
|
||||
local before2 = j
|
||||
j = ctx.skip_ws(src, j)
|
||||
if j < n && src.substring(j, j+1) == "," { j = j + 1 }
|
||||
|
||||
if first == 1 {
|
||||
out = out + key_json + "," + val_json
|
||||
first = 0
|
||||
} 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 }
|
||||
}
|
||||
}
|
||||
// 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 out = "["
|
||||
local first = 1
|
||||
local cont = 1
|
||||
local guard = 0
|
||||
local max = 400000
|
||||
|
||||
loop(cont == 1) {
|
||||
if guard > max { cont = 0 } else { guard = guard + 1 }
|
||||
// MirBuilder-friendly: explicit break instead of flag
|
||||
loop(true) {
|
||||
j = ctx.skip_ws(src, j)
|
||||
|
||||
if j >= n {
|
||||
cont = 0
|
||||
if j >= n { break }
|
||||
|
||||
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 {
|
||||
if src.substring(j, j+1) == "]" {
|
||||
j = j + 1
|
||||
cont = 0
|
||||
} else {
|
||||
local before = j
|
||||
local ej = ctx.parse_expr2(src, j)
|
||||
j = ctx.gpos_get()
|
||||
out = out + "," + ej
|
||||
}
|
||||
|
||||
if first == 1 {
|
||||
out = out + ej
|
||||
first = 0
|
||||
} else {
|
||||
out = out + "," + ej
|
||||
}
|
||||
// optional comma+whitespace
|
||||
local before2 = j
|
||||
j = ctx.skip_ws(src, j)
|
||||
if j < n && src.substring(j, j+1) == "," { j = j + 1 }
|
||||
|
||||
// optional comma+whitespace
|
||||
local before2 = j
|
||||
j = ctx.skip_ws(src, j)
|
||||
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 }
|
||||
}
|
||||
}
|
||||
// progress guard
|
||||
if j <= before {
|
||||
if j < n { j = j + 1 } else { j = n }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -21,77 +21,73 @@ static box ParserPeekBox {
|
||||
local arms_json = "["
|
||||
local first_arm = 1
|
||||
local else_json = null
|
||||
local contp = 1
|
||||
local guardp = 0
|
||||
local maxp = 400000
|
||||
|
||||
loop(contp == 1) {
|
||||
if guardp > maxp { contp = 0 } else { guardp = guardp + 1 }
|
||||
// MirBuilder-friendly: explicit break instead of flag
|
||||
loop(true) {
|
||||
j = ctx.skip_ws(src, j)
|
||||
|
||||
if j >= n {
|
||||
contp = 0
|
||||
} else {
|
||||
if src.substring(j, j+1) == "}" {
|
||||
if j >= n { break }
|
||||
|
||||
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
|
||||
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 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 = 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
|
||||
}
|
||||
}
|
||||
else_json = ctx.parse_expr2(src, j)
|
||||
j = ctx.gpos_get()
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -398,18 +398,8 @@ box ParserBox {
|
||||
print("[parser/trace:program2] i=" + ("" + i) + " stmt_len=" + ("" + s.length()) + " guard=" + ("" + guard_prog))
|
||||
}
|
||||
|
||||
// ===== 6) CONSUME OPTIONAL SEMICOLONS =====
|
||||
local done_semi = 0
|
||||
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
|
||||
}
|
||||
|
||||
// ===== 6) CONSUME OPTIONAL SEMICOLONS - MirBuilder-friendly =====
|
||||
loop(true) {
|
||||
local before_semi = i
|
||||
|
||||
// Inline ws skip
|
||||
@ -424,13 +414,10 @@ box ParserBox {
|
||||
|
||||
if i < n && src.substring(i, i+1) == ";" {
|
||||
i = i + 1
|
||||
} else {
|
||||
done_semi = 1
|
||||
continue
|
||||
}
|
||||
|
||||
if i == before_semi {
|
||||
done_semi = 1
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// ===== 7) EMIT STATEMENT =====
|
||||
|
||||
@ -9,14 +9,13 @@ static box ParserNumberScanBox {
|
||||
if src == null { return "{\"type\":\"Int\",\"value\":0}@" + ParserCommonUtilsBox.i2s(i) }
|
||||
local n = src.length()
|
||||
local j = i
|
||||
local cont = 1
|
||||
local guard = 0
|
||||
local max = 100000
|
||||
loop(cont == 1) {
|
||||
if guard > max { cont = 0 } else { guard = guard + 1 }
|
||||
if j < n {
|
||||
if ParserCommonUtilsBox.is_digit(src.substring(j, j+1)) { j = j + 1 } else { cont = 0 }
|
||||
} else { cont = 0 }
|
||||
// MirBuilder-friendly: explicit continue/break instead of flag
|
||||
loop(j < n) {
|
||||
if ParserCommonUtilsBox.is_digit(src.substring(j, j+1)) {
|
||||
j = j + 1
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
local s = src.substring(i, j)
|
||||
if s.length() == 0 { s = "0" }
|
||||
|
||||
@ -250,48 +250,45 @@ static box ParserControlBox {
|
||||
|
||||
local body = "["
|
||||
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)
|
||||
|
||||
if j >= src.length() {
|
||||
cont_block = 0
|
||||
} else {
|
||||
if src.substring(j, j+1) == "}" {
|
||||
if j >= src.length() { break }
|
||||
|
||||
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
|
||||
cont_block = 0
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if s.length() > 0 {
|
||||
if first == 1 {
|
||||
body = body + s
|
||||
first = 0
|
||||
} else {
|
||||
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)
|
||||
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
|
||||
}
|
||||
}
|
||||
body = body + "," + s
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,15 +41,13 @@ static box ParserExceptionBox {
|
||||
local catches_json = "["
|
||||
local catch_first = 1
|
||||
|
||||
// zero or more catch
|
||||
local guard_ct = 0
|
||||
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 }
|
||||
// zero or more catch - MirBuilder-friendly
|
||||
loop(true) {
|
||||
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 = ctx.skip_ws(src, j)
|
||||
local catch_type = null
|
||||
@ -112,8 +110,6 @@ static box ParserExceptionBox {
|
||||
catch_first = 0
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cont_ct = 0
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -344,19 +344,16 @@ static box ParserStmtBox {
|
||||
local at = idp.lastIndexOf("@")
|
||||
local name = idp.substring(0, at)
|
||||
j = ctx.to_int(idp.substring(at+1, idp.length()))
|
||||
local cont = 1
|
||||
loop(cont == 1) {
|
||||
// MirBuilder-friendly: explicit break instead of flag
|
||||
loop(true) {
|
||||
j = ParserStringUtilsBox.skip_ws(src, j)
|
||||
if src.substring(j, j+1) == "." {
|
||||
j = j + 1
|
||||
j = ParserStringUtilsBox.skip_ws(src, j)
|
||||
idp = ctx.read_ident2(src, j)
|
||||
at = idp.lastIndexOf("@")
|
||||
name = name + "." + idp.substring(0, at)
|
||||
j = ctx.to_int(idp.substring(at+1, idp.length()))
|
||||
} else {
|
||||
cont = 0
|
||||
}
|
||||
if src.substring(j, j+1) != "." { break }
|
||||
j = j + 1
|
||||
j = ParserStringUtilsBox.skip_ws(src, j)
|
||||
idp = ctx.read_ident2(src, j)
|
||||
at = idp.lastIndexOf("@")
|
||||
name = name + "." + idp.substring(0, at)
|
||||
j = ctx.to_int(idp.substring(at+1, idp.length()))
|
||||
}
|
||||
j = ParserStringUtilsBox.skip_ws(src, j)
|
||||
local alias2 = null
|
||||
|
||||
Reference in New Issue
Block a user