// if_match_normalize_macro.nyash // Scaffold: identity expansion for now. Future: introduce join variable and // canonical If/Match normalization (scrutinee once, guard fused) as documented // in docs/guides/if-match-normalize.md. static box MacroBoxSpec { name() { return "IfMatchNormalize" } expand(json, ctx) { local JB = include "apps/lib/json_builder.nyash" // --- helpers copied/adapted from loop_normalize --- function parse_value(s, i) { local n = s.length() if i >= n { return ("" + i) + "#" + "" } local ch = s.substring(i, i+1) if ch == "\"" { 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 } j = j + 1 } return ("" + j) + "#" + s.substring(i, j) } if ch == "{" { local depth = 1 local j = i + 1 local in_str = false loop(j < n && depth > 0) { local c = s.substring(j, j+1) if c == "\"" { local k = j - 1 local esc = false if k >= 0 && s.substring(k, k+1) == "\\" { esc = true } if not esc { in_str = not in_str } j = j + 1 continue } if not in_str { if c == "{" { depth = depth + 1 } if c == "}" { depth = depth - 1 } } j = j + 1 } return ("" + j) + "#" + s.substring(i, j) } if ch == "[" { local depth = 1 local j = i + 1 local in_str = false loop(j < n && depth > 0) { local c = s.substring(j, j+1) if c == "\"" { local k = j - 1 local esc = false if k >= 0 && s.substring(k, k+1) == "\\" { esc = true } if not esc { in_str = not in_str } j = j + 1 continue } if not in_str { if c == "[" { depth = depth + 1 } if c == "]" { depth = depth - 1 } } j = j + 1 } return ("" + j) + "#" + s.substring(i, j) } local j = i loop(j < n) { local c = s.substring(j, j+1) if c == "," || c == "]" || c == "}" || c == "\n" || c == "\r" || c == "\t" || c == " " { break } j = j + 1 } return ("" + j) + "#" + s.substring(i, j) } function pair_idx(pair) { local i = 0 local n = pair.length() local val = 0 loop(i < n) { local ch = pair.substring(i, i+1) if ch == "#" { break } local d = 0 if ch == "0" { d = 0 } if ch == "1" { d = 1 } if ch == "2" { d = 2 } if ch == "3" { d = 3 } if ch == "4" { d = 4 } if ch == "5" { d = 5 } if ch == "6" { d = 6 } if ch == "7" { d = 7 } if ch == "8" { d = 8 } if ch == "9" { d = 9 } val = val * 10 + d i = i + 1 } return val } function pair_json(pair) { local i = 0 local n = pair.length() loop(i < n) { if pair.substring(i, i+1) == "#" { return pair.substring(i+1, n) } i = i + 1 } return "" } function find_field_pos(obj, key) { local tok = "\"" + key + "\":" local i = 0 loop(i + tok.length() <= obj.length()) { if obj.substring(i, i + tok.length()) == tok { return i + tok.length() } i = i + 1 } return -1 } function has_kind(obj, k) { local tok = "\"kind\":\"" + k + "\"" local i = 0 loop(i + tok.length() <= obj.length()) { if obj.substring(i, i + tok.length()) == tok { return true } i = i + 1 } return false } function split_array(arr_json) { // arr_json like "[ ... ]" if arr_json.length() < 2 { return [] } local inner = arr_json.substring(1, arr_json.length()-1) local out = [] local i = 0 local n = inner.length() local in_str = false local d_obj = 0 local d_arr = 0 local start = 0 loop(i < n) { local c = inner.substring(i, i+1) if c == "\"" { local k = i - 1 local esc = false if k >= 0 && inner.substring(k, k+1) == "\\" { esc = true } if not esc { in_str = not in_str } } else if not in_str { if c == "{" { d_obj = d_obj + 1 } if c == "}" { d_obj = d_obj - 1 } if c == "[" { d_arr = d_arr + 1 } if c == "]" { d_arr = d_arr - 1 } if c == "," && d_obj == 0 && d_arr == 0 { out.push(inner.substring(start, i)) start = i + 1 } } i = i + 1 } if start < n { out.push(inner.substring(start, n)) } // trim whitespace local j = 0 loop(j < out.length()) { local e = out.get(j) local a = 0 local b = e.length() loop(a < b && (e.substring(a,a+1)==" " || e.substring(a,a+1)=="\n" || e.substring(a,a+1)=="\t" || e.substring(a,a+1)=="\r")) { a = a + 1 } loop(b > a && (e.substring(b-1,b)==" " || e.substring(b-1,b)=="\n" || e.substring(b-1,b)=="\t" || e.substring(b-1,b)=="\r")) { b = b - 1 } out.set(j, e.substring(a,b)) j = j + 1 } return out } function get_field(obj, key) { local p = find_field_pos(obj, key) if p < 0 { return null } local pr = parse_value(obj, p) return pair_json(pr) } function replace_field(obj, key, new_val) { local tok = "\"" + key + "\":" local start = -1 local i2 = 0 loop(i2 + tok.length() <= obj.length()) { if obj.substring(i2, i2 + tok.length()) == tok { start = i2; break } i2 = i2 + 1 } if start < 0 { return obj } local p = start + tok.length() local pr = parse_value(obj, p) local endi = pair_idx(pr) local prefix = obj.substring(0, start + tok.length()) local suffix = obj.substring(endi, obj.length()) return prefix + new_val + suffix } function rewrite_stmt_node(njson) { // Assignment with If RHS → If(assign ...) if has_kind(njson, "Assignment") { local val_json = get_field(njson, "value") 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") // then/else are arrays; take first element as expr 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() } 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() } local then_s = [ JB.assignment(tgt, th_e) ] local else_s = null if el_e != null { else_s = [ JB.assignment(tgt, el_e) ] } 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, "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() } 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() } local then_s = [ JB.return_(th_e) ] local else_s = null if el_e != null { else_s = [ JB.return_(el_e) ] } 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, "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() } 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() } local then_s = [ JB.print_(th_e) ] local else_s = null if el_e != null { else_s = [ JB.print_(el_e) ] } return JB.if_(cond, then_s, else_s) } } // Recurse for If/Loop nodes if has_kind(njson, "If") { local th = get_field(njson, "then") local el = get_field(njson, "else") if th != null && th.substring(0,1) == "[" { 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 } 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 } njson = replace_field(njson, "else", "[" + JB.join(rebuilt2, ",") + "]") } return njson } if has_kind(njson, "Loop") { local body = get_field(njson, "body") if body != null && body.substring(0,1) == "[" { 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 } njson = replace_field(njson, "body", "[" + JB.join(rebuilt, ",") + "]") } return njson } return njson } // entry: expect Program at top if has_kind(json, "Program") { local stm = get_field(json, "statements") if stm != null && stm.substring(0,1) == "[" { 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 } return replace_field(json, "statements", "[" + JB.join(rebuilt, ",") + "]") } } return json } }