macro(if/match): stabilize PeekExpr→If normalization via internal-child; default runner off; propagate child stderr; fix JsonBuilder local_decl; add scope-hints doc; extend PHI smoke; golden for match literal passes

This commit is contained in:
Selfhosting Dev
2025-09-20 05:00:31 +09:00
parent 8cb93b9f1f
commit 166c374eec
14 changed files with 362 additions and 47 deletions

View File

@ -12,7 +12,7 @@ static box ControlFlowBuilder {
if_expr(cond_json, then_expr_json, else_expr_json, res_name) {
local JB = include "apps/lib/json_builder.nyash"
local res_var = JB.variable(res_name)
local decl = JB.local([res_name], [null])
local decl = JB.local_decl([res_name], [null])
local then_s = [ JB.assignment(res_var, then_expr_json) ]
local else_s = [ JB.assignment(res_var, else_expr_json) ]
local ifn = JB.if_(cond_json, then_s, else_s)
@ -31,11 +31,11 @@ static box ControlFlowBuilder {
local PT = include "apps/lib/pattern_builder.nyash"
// scrutinee を一度だけ評価
local decl_scrut = JB.local([scrut_name], [scrut_json])
local decl_scrut = JB.local_decl([scrut_name], [scrut_json])
local scrut_var = JB.variable(scrut_name)
// res の宣言
local res_decl = JB.local([res_name], [null])
local res_decl = JB.local_decl([res_name], [null])
local res_var = JB.variable(res_name)
// デフォルト(任意)を抽出
@ -96,4 +96,3 @@ static box ControlFlowBuilder {
return [ decl_scrut, res_decl, chain ]
}
}

View File

@ -16,18 +16,17 @@ static box JsonBuilder {
return out
}
// JSON string escaping (minimal): backslash, quote, newline, carriage return, tab
// JSON string escaping (minimal): backslash, quote
escape_string(s) {
local out = ""
local i = 0
loop(i < s.length()) {
local ch = s.substring(i, i + 1)
if ch == "\\" { out = out + "\\\\" }
else if ch == "\"" { out = out + "\\\"" }
else if ch == "\n" { out = out + "\\n" }
else if ch == "\r" { out = out + "\\r" }
else if ch == "\t" { out = out + "\\t" }
else { out = out + ch }
if ch == "\\" {
out = out + "\\\\"
} else {
if ch == "\"" { out = out + "\\\"" } else { out = out + ch }
}
i = i + 1
}
return out
@ -70,7 +69,7 @@ static box JsonBuilder {
return "{\"kind\":\"Return\",\"value\":" + v + "}"
}
local(vars, inits) {
local_decl(vars, inits) {
// vars: array[string], inits: array[string|null]
local vs = []
local i = 0

View File

@ -8,7 +8,6 @@ static box MacroBoxSpec {
expand(json, ctx) {
local JB = include "apps/lib/json_builder.nyash"
local CF = include "apps/lib/cf_builder.nyash"
// --- helpers copied/adapted from loop_normalize ---
function parse_value(s, i) {
@ -19,8 +18,14 @@ static box MacroBoxSpec {
local j = i + 1
loop(j < n) {
local c = s.substring(j, j+1)
if c == "\\" { j = j + 2; continue }
if c == "\"" { j = j + 1; break }
if c == "\\" {
j = j + 2
continue
}
if c == "\"" {
j = j + 1
break
}
j = j + 1
}
return ("" + j) + "#" + s.substring(i, j)
@ -189,7 +194,10 @@ static box MacroBoxSpec {
local start = -1
local i2 = 0
loop(i2 + tok.length() <= obj.length()) {
if obj.substring(i2, i2 + tok.length()) == tok { start = i2; break }
if obj.substring(i2, i2 + tok.length()) == tok {
start = i2
break
}
i2 = i2 + 1
}
if start < 0 { return obj }
@ -201,12 +209,38 @@ static box MacroBoxSpec {
return prefix + new_val + suffix
}
function peek_to_if_expr(peek_json) {
local scr = get_field(peek_json, "scrutinee")
local arms_raw = get_field(peek_json, "arms")
local arms = split_array(arms_raw)
local else_expr = get_field(peek_json, "else")
local current = else_expr
local idx = arms.length()
loop(idx > 0) {
idx = idx - 1
local arm_json = arms.get(idx)
local lit_json = get_field(arm_json, "literal")
local body_json = get_field(arm_json, "body")
local cond = JB.binary("==", scr, lit_json)
local then_arr = [body_json]
local else_arr = null
if current != null { else_arr = [current] }
local if_json = JB.if_(cond, then_arr, else_arr)
current = if_json
}
return current
}
function rewrite_stmt_node(njson) {
// Assignment with If RHS → If(assign ...)
if has_kind(njson, "Assignment") {
local tgt = get_field(njson, "target")
local val_json = get_field(njson, "value")
if val_json != null && has_kind(val_json, "PeekExpr") {
val_json = peek_to_if_expr(val_json)
njson = replace_field(njson, "value", val_json)
}
if val_json != null && has_kind(val_json, "If") {
local tgt = get_field(njson, "target")
local cond = get_field(val_json, "condition")
local th = get_field(val_json, "then")
local el = get_field(val_json, "else")
@ -214,55 +248,100 @@ static box MacroBoxSpec {
local th_e = null
if th != null && th.substring(0,1) == "[" {
local parts = split_array(th)
th_e = parts.length() > 0 ? parts.get(0) : JB.literal_null()
if parts.length() > 0 { th_e = parts.get(0) } else { th_e = JB.literal_null() }
}
local el_e = null
if el == null || el == "null" {
el_e = null
} else if el.substring(0,1) == "[" {
local parts2 = split_array(el)
el_e = parts2.length() > 0 ? parts2.get(0) : JB.literal_null()
if parts2.length() > 0 { el_e = parts2.get(0) } else { el_e = JB.literal_null() }
}
local then_s = [ JB.assignment(tgt, th_e) ]
local else_s = null
if el_e != null { else_s = [ JB.assignment(tgt, el_e) ] }
return CF.if_stmt(cond, then_s, else_s)
return JB.if_(cond, then_s, else_s)
}
}
// Return with If value → If(Return ...)
if has_kind(njson, "Return") {
local val_json = get_field(njson, "value")
if val_json != null && val_json != "null" && has_kind(val_json, "PeekExpr") {
val_json = peek_to_if_expr(val_json)
njson = replace_field(njson, "value", val_json)
}
if val_json != null && val_json != "null" && has_kind(val_json, "If") {
local cond = get_field(val_json, "condition")
local th = get_field(val_json, "then")
local el = get_field(val_json, "else")
local th_e = null
if th != null && th.substring(0,1) == "[" { local p = split_array(th); th_e = p.length()>0 ? p.get(0) : JB.literal_null() }
if th != null && th.substring(0,1) == "[" {
local parts = split_array(th)
if parts.length() > 0 { th_e = parts.get(0) } else { th_e = JB.literal_null() }
}
local el_e = null
if el == null || el == "null" { el_e = null } else if el.substring(0,1) == "[" { local p2 = split_array(el); el_e = p2.length()>0 ? p2.get(0) : JB.literal_null() }
if el == null || el == "null" {
el_e = null
} else if el.substring(0,1) == "[" {
local parts2 = split_array(el)
if parts2.length() > 0 { el_e = parts2.get(0) } else { el_e = JB.literal_null() }
}
local then_s = [ JB.return_(th_e) ]
local else_s = null
if el_e != null { else_s = [ JB.return_(el_e) ] }
return CF.if_stmt(cond, then_s, else_s)
return JB.if_(cond, then_s, else_s)
}
}
// Print with If expression → If(Print ...)
if has_kind(njson, "Print") {
local ex_json = get_field(njson, "expression")
if ex_json != null && has_kind(ex_json, "PeekExpr") {
ex_json = peek_to_if_expr(ex_json)
njson = replace_field(njson, "expression", ex_json)
}
if ex_json != null && has_kind(ex_json, "If") {
local cond = get_field(ex_json, "condition")
local th = get_field(ex_json, "then")
local el = get_field(ex_json, "else")
local th_e = null
if th != null && th.substring(0,1) == "[" { local p = split_array(th); th_e = p.length()>0 ? p.get(0) : JB.literal_null() }
if th != null && th.substring(0,1) == "[" {
local parts = split_array(th)
if parts.length() > 0 { th_e = parts.get(0) } else { th_e = JB.literal_null() }
}
local el_e = null
if el == null || el == "null" { el_e = null } else if el.substring(0,1) == "[" { local p2 = split_array(el); el_e = p2.length()>0 ? p2.get(0) : JB.literal_null() }
if el == null || el == "null" {
el_e = null
} else if el.substring(0,1) == "[" {
local parts2 = split_array(el)
if parts2.length() > 0 { el_e = parts2.get(0) } else { el_e = JB.literal_null() }
}
local then_s = [ JB.print_(th_e) ]
local else_s = null
if el_e != null { else_s = [ JB.print_(el_e) ] }
return CF.if_stmt(cond, then_s, else_s)
return JB.if_(cond, then_s, else_s)
}
}
if has_kind(njson, "Local") {
local inits = get_field(njson, "inits")
if inits != null && inits.substring(0,1) == "[" {
local elems = split_array(inits)
local mutated = 0
local idx = 0
loop(idx < elems.length()) {
local init_json = elems.get(idx)
if init_json != "null" && has_kind(init_json, "PeekExpr") {
local replaced = peek_to_if_expr(init_json)
elems.set(idx, replaced)
mutated = 1
}
idx = idx + 1
}
if mutated == 1 {
njson = replace_field(njson, "inits", "[" + JB.join(elems, ",") + "]")
}
}
return njson
}
// Recurse for If/Loop nodes
if has_kind(njson, "If") {
local th = get_field(njson, "then")
@ -271,14 +350,20 @@ static box MacroBoxSpec {
local parts = split_array(th)
local rebuilt = []
local i3 = 0
loop(i3 < parts.length()) { rebuilt.push(rewrite_stmt_node(parts.get(i3))); i3 = i3 + 1 }
loop(i3 < parts.length()) {
rebuilt.push(rewrite_stmt_node(parts.get(i3)))
i3 = i3 + 1
}
njson = replace_field(njson, "then", "[" + JB.join(rebuilt, ",") + "]")
}
if el != null && el != "null" && el.substring(0,1) == "[" {
local parts2 = split_array(el)
local rebuilt2 = []
local j3 = 0
loop(j3 < parts2.length()) { rebuilt2.push(rewrite_stmt_node(parts2.get(j3))); j3 = j3 + 1 }
loop(j3 < parts2.length()) {
rebuilt2.push(rewrite_stmt_node(parts2.get(j3)))
j3 = j3 + 1
}
njson = replace_field(njson, "else", "[" + JB.join(rebuilt2, ",") + "]")
}
return njson
@ -289,7 +374,10 @@ static box MacroBoxSpec {
local parts = split_array(body)
local rebuilt = []
local i4 = 0
loop(i4 < parts.length()) { rebuilt.push(rewrite_stmt_node(parts.get(i4))); i4 = i4 + 1 }
loop(i4 < parts.length()) {
rebuilt.push(rewrite_stmt_node(parts.get(i4)))
i4 = i4 + 1
}
njson = replace_field(njson, "body", "[" + JB.join(rebuilt, ",") + "]")
}
return njson
@ -304,7 +392,10 @@ static box MacroBoxSpec {
local parts = split_array(stm)
local rebuilt = []
local i0 = 0
loop(i0 < parts.length()) { rebuilt.push(rewrite_stmt_node(parts.get(i0))); i0 = i0 + 1 }
loop(i0 < parts.length()) {
rebuilt.push(rewrite_stmt_node(parts.get(i0)))
i0 = i0 + 1
}
return replace_field(json, "statements", "[" + JB.join(rebuilt, ",") + "]")
}
}

View File

@ -0,0 +1,7 @@
local d = 2
local r = match d {
1 => 10
2 => 20
_ => 30
}
print(r)