diff --git a/apps/selfhost-compiler/boxes/emitter_box.nyash b/apps/selfhost-compiler/boxes/emitter_box.nyash index c02ec4b9..bb132879 100644 --- a/apps/selfhost-compiler/boxes/emitter_box.nyash +++ b/apps/selfhost-compiler/boxes/emitter_box.nyash @@ -1,15 +1,23 @@ // EmitterBox — thin wrapper to emit JSON v0 (extracted) box EmitterBox { emit_program(json, usings_json) { - if usings_json == null { return json } - // Attach meta.usings when available and non-empty (bridge ignores unknown fields) - if usings_json.length() == 0 { return json } - if usings_json == "[]" { return json } - // naive injection: insert before the final '}' of the top-level object + if json == null { return json } + // Normalize usings payload to at least an empty array so meta.usings is always present + local payload = usings_json + if payload == null { payload = "[]" } + if payload.length() == 0 { payload = "[]" } + // Inject meta.usings before closing brace of top-level object local n = json.lastIndexOf("}") if n < 0 { return json } local head = json.substring(0, n) - return head + ",\"meta\":{\"usings\":" + usings_json + "}}" + local tail = json.substring(n, json.length()) + local needs_comma = 1 + if head.length() == 0 { needs_comma = 0 } else { + local last = head.substring(head.length() - 1, head.length()) + if last == "{" || last == "," { needs_comma = 0 } + } + if needs_comma == 1 { head = head + "," } + return head + "\"meta\":{\"usings\":" + payload + "}" + tail } } diff --git a/apps/selfhost-compiler/boxes/parser_box.nyash b/apps/selfhost-compiler/boxes/parser_box.nyash index 86bf5861..64568b32 100644 --- a/apps/selfhost-compiler/boxes/parser_box.nyash +++ b/apps/selfhost-compiler/boxes/parser_box.nyash @@ -231,6 +231,10 @@ box ParserBox { parse_factor2(src, i) { local j = me.skip_ws(src, i) + if j >= src.length() { me.gpos_set(j) return "{\"type\":\"Int\",\"value\":0}" } + if me.starts_with_kw(src, j, "true") == 1 { me.gpos_set(j + 4) return "{\"type\":\"Bool\",\"value\":true}" } + if me.starts_with_kw(src, j, "false") == 1 { me.gpos_set(j + 5) return "{\"type\":\"Bool\",\"value\":false}" } + if me.starts_with_kw(src, j, "null") == 1 { me.gpos_set(j + 4) return "{\"type\":\"Null\"}" } local ch = src.substring(j, j+1) // Parenthesized if ch == "(" { @@ -465,32 +469,54 @@ box ParserBox { return "" } // simple assignment: IDENT '=' expr ; → JSON v0 Local{name, expr} (Stage‑2 uses Local for updates) - if me.is_alpha(src.substring(j, j+1)) { + if j < src.length() && me.is_alpha(src.substring(j, j+1)) { local idp0 = me.read_ident2(src, j) local at0 = idp0.lastIndexOf("@") if at0 > 0 { local name0 = idp0.substring(0, at0) local k0 = me.to_int(idp0.substring(at0+1, idp0.length())) k0 = me.skip_ws(src, k0) - if src.substring(k0, k0+1) == "=" { - k0 = k0 + 1 - k0 = me.skip_ws(src, k0) - local e0 = me.parse_expr2(src, k0) - k0 = me.gpos_get() - if k0 <= stmt_start { if k0 < src.length() { k0 = k0 + 1 } else { k0 = src.length() } } - me.gpos_set(k0) - return "{\"type\":\"Local\",\"name\":\"" + name0 + "\",\"expr\":" + e0 + "}" + 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 = me.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 = me.parse_expr2(src, k0) + end_pos0 = me.gpos_get() + } + } + k0 = end_pos0 + if k0 <= stmt_start { if k0 < src.length() { k0 = k0 + 1 } else { k0 = src.length() } } + me.gpos_set(k0) + return "{\"type\":\"Local\",\"name\":\"" + name0 + "\",\"expr\":" + expr_json0 + "}" + } } } } if me.starts_with_kw(src, j, "return") == 1 { j = j + 6 j = me.skip_ws(src, j) - local e = me.parse_expr2(src, j) - j = me.gpos_get() + local default_ret = "{\"type\":\"Int\",\"value\":0}" + local expr_json_ret = default_ret + local end_pos_ret = j + if j < src.length() { + local ahead_ret = src.substring(j, j+1) + if ahead_ret != "}" && ahead_ret != ";" { + expr_json_ret = me.parse_expr2(src, j) + end_pos_ret = me.gpos_get() + } + } + j = end_pos_ret if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } } me.gpos_set(j) - return "{\"type\":\"Return\",\"expr\":" + e + "}" + return "{\"type\":\"Return\",\"expr\":" + expr_json_ret + "}" } if me.starts_with_kw(src, j, "local") == 1 { j = j + 5 @@ -500,13 +526,22 @@ box ParserBox { local name = idp.substring(0, at) j = me.to_int(idp.substring(at+1, idp.length())) j = me.skip_ws(src, j) - if src.substring(j, j+1) == "=" { j = j + 1 } + if j < src.length() && src.substring(j, j+1) == "=" { j = j + 1 } j = me.skip_ws(src, j) - local e2 = me.parse_expr2(src, j) - j = me.gpos_get() + local default_local = "{\"type\":\"Int\",\"value\":0}" + local expr_json_local = default_local + local end_pos_local = j + if j < src.length() { + local ahead_local = src.substring(j, j+1) + if ahead_local != "}" && ahead_local != ";" { + expr_json_local = me.parse_expr2(src, j) + end_pos_local = me.gpos_get() + } + } + j = end_pos_local if j <= stmt_start { if j < src.length() { j = j + 1 } else { j = src.length() } } me.gpos_set(j) - return "{\"type\":\"Local\",\"name\":\"" + name + "\",\"expr\":" + e2 + "}" + return "{\"type\":\"Local\",\"name\":\"" + name + "\",\"expr\":" + expr_json_local + "}" } if me.starts_with_kw(src, j, "if") == 1 { j = j + 2