hv1: early-exit at main (no plugin init); tokenizer: Stage-3 single-quote + full escapes (\/ \b \f \' \r fix); builder: route BinOp via SSOT emit_binop_to_dst; hv1 verify canary route (builder→Core); docs: phase-20.39 updates

This commit is contained in:
nyash-codex
2025-11-04 20:46:43 +09:00
parent 31ce798341
commit 44a5158a14
53 changed files with 2237 additions and 179 deletions

View File

@ -81,6 +81,29 @@ box ParserBox {
read_ident2(src, i) { return ParserIdentScanBox.scan_ident(src, i) }
read_string_lit(src, i) {
local q0 = src.substring(i, i + 1)
// Check for single quote (Stage-3 only)
if q0 == "'" {
if me.stage3_enabled() == 1 {
// Single-quote string in Stage-3
local pair = ParserStringScanBox.scan_with_quote(src, i, "'")
local at = pair.lastIndexOf("@")
local content = pair.substring(0, at)
local pos = 0
if at >= 0 { pos = me.to_int(pair.substring(at+1, pair.length())) }
else { pos = i }
me.gpos_set(pos)
return content
} else {
// Single-quote not allowed, degrade gracefully
// Return empty string and advance 1 char
me.gpos_set(i + 1)
return ""
}
}
// Double-quote string (existing path)
local pair = ParserStringScanBox.scan(src, i)
local at = pair.lastIndexOf("@")
local content = pair.substring(0, at)

View File

@ -7,11 +7,12 @@
using lang.compiler.parser.scan.parser_common_utils_box as ParserCommonUtilsBox
static box ParserStringScanBox {
scan(src, i) {
// Generic scanner with quote abstraction (quote is "\"" or "'")
scan_with_quote(src, i, quote) {
if src == null { return "@" + ParserCommonUtilsBox.i2s(i) }
local n = src.length()
local j = i
if j >= n || src.substring(j, j+1) != "\"" { return "@" + ParserCommonUtilsBox.i2s(i) }
if j >= n || src.substring(j, j+1) != quote { return "@" + ParserCommonUtilsBox.i2s(i) }
j = j + 1
local out = ""
local guard = 0
@ -19,25 +20,58 @@ static box ParserStringScanBox {
loop(j < n) {
if guard > max { break } else { guard = guard + 1 }
local ch = src.substring(j, j+1)
if ch == "\"" {
// End of string: found matching quote
if ch == quote {
j = j + 1
return out + "@" + ParserCommonUtilsBox.i2s(j)
}
// Escape sequence
if ch == "\\" && j + 1 < n {
local nx = src.substring(j+1, j+2)
if nx == "\"" { out = out + "\"" j = j + 2 }
else {
if nx == "\\" { out = out + "\\" j = j + 2 } else {
if nx == "n" { out = out + "\n" j = j + 2 } else {
if nx == "r" { out = out + "\n" j = j + 2 } else {
if nx == "t" { out = out + "\t" j = j + 2 } else {
if nx == "u" && j + 5 < n { out = out + src.substring(j, j+6) j = j + 6 }
else { out = out + nx j = j + 2 }
}
}
}
}
}
// Decode escape
if nx == "\\" {
out = out + "\\"
j = j + 2
} else { if nx == "\"" {
out = out + "\""
j = j + 2
} else { if nx == "'" {
out = out + "'"
j = j + 2
} else { if nx == "/" {
out = out + "/"
j = j + 2
} else { if nx == "b" {
// Backspace (0x08) - for MVP, skip (empty string)
out = out + ""
j = j + 2
} else { if nx == "f" {
// Form feed (0x0C) - for MVP, skip (empty string)
out = out + ""
j = j + 2
} else { if nx == "n" {
out = out + "\n"
j = j + 2
} else { if nx == "r" {
// FIX: \r should be CR (0x0D), not LF (0x0A)
// Keep as "\r" literal for MVP
out = out + "\r"
j = j + 2
} else { if nx == "t" {
out = out + "\t"
j = j + 2
} else { if nx == "u" && j + 5 < n {
// \uXXXX: MVP - concatenate as-is (6 chars)
out = out + src.substring(j, j+6)
j = j + 6
} else {
// Unknown escape: tolerate (keep backslash + char)
out = out + "\\" + nx
j = j + 2
} } } } } } } } } }
} else {
out = out + ch
j = j + 1
@ -46,5 +80,10 @@ static box ParserStringScanBox {
// if unterminated, return what we have and the last pos to avoid infinite loops
return out + "@" + ParserCommonUtilsBox.i2s(j)
}
// Existing: backward-compatible wrapper
scan(src, i) {
return me.scan_with_quote(src, i, "\"")
}
}

View File

@ -86,6 +86,8 @@ static box MirBuilderBox {
using "hako.mir.builder.internal.lower_return_binop_varvar" as LowerReturnBinOpVarVarBox
using "hako.mir.builder.internal.lower_return_binop" as LowerReturnBinOpBox
using "hako.mir.builder.internal.lower_return_int" as LowerReturnIntBox
// Prefer loop lowers first to catch loop-specific patterns (sum_bc/continue/break normalization)
{ local out_loop2 = LowerLoopSumBcBox.try_lower(s); if out_loop2 != null { return out_loop2 } }
{ local out_if2b = LowerIfNestedBox.try_lower(s); if out_if2b != null { return out_if2b } }
{ local out_if2 = LowerIfThenElseFollowingReturnBox.try_lower(s); if out_if2 != null { return out_if2 } }
{ local out_if = LowerIfCompareBox.try_lower(s); if out_if != null { return out_if } }
@ -93,7 +95,6 @@ static box MirBuilderBox {
{ local out_ifbv = LowerIfCompareFoldVarIntBox.try_lower(s); if out_ifbv != null { return out_ifbv } }
{ local out_ifvi = LowerIfCompareVarIntBox.try_lower(s); if out_ifvi != null { return out_ifvi } }
{ local out_ifvv = LowerIfCompareVarVarBox.try_lower(s); if out_ifvv != null { return out_ifvv } }
{ local out_loop2 = LowerLoopSumBcBox.try_lower(s); if out_loop2 != null { return out_loop2 } }
{ local out_loopp = LowerLoopCountParamBox.try_lower(s); if out_loopp != null { return out_loopp } }
{ local out_loop = LowerLoopSimpleBox.try_lower(s); if out_loop != null { return out_loop } }
{ local out_var = LowerReturnVarLocalBox.try_lower(s); if out_var != null { return out_var } }

View File

@ -0,0 +1,83 @@
// loop_scan_box.hako — If/Compare + then/else 範囲スキャンの小箱
using "hako.mir.builder.internal.prog_scan" as ProgScanBox
using ProgScanBox as Scan
using selfhost.shared.json.utils.json_frag as JsonFragBox
static box LoopScanBox {
// 抽出: cond Compare から Var 名lhs/rhs いずれか)を取得
find_loop_var_name(s, k_cmp) {
local varname = null
local kl = ("" + s).indexOf("\"lhs\":{", k_cmp)
local kr = ("" + s).indexOf("\"rhs\":{", k_cmp)
if kl >= 0 && ("" + s).indexOf("\"type\":\"Var\"", kl) >= 0 { varname = Scan.read_quoted_after_key(s, kl, "name") }
if varname == null && kr >= 0 && ("" + s).indexOf("\"type\":\"Var\"", kr) >= 0 { varname = Scan.read_quoted_after_key(s, kr, "name") }
return varname
}
// '!=' + else [Break/Continue] パターンからX値を抽出最小サブセット
// sentinel: "Break" | "Continue"
extract_ne_else_sentinel_value(s, sentinel, k_loop, varname) {
local st = "\"type\":\"" + sentinel + "\""
local ks = ("" + s).indexOf(st, k_loop)
if ks < 0 { return null }
// 直前の If と Compare を見つける(同一 then/else 内の近傍に限定)
local kif = ("" + s).lastIndexOf("\"type\":\"If\"", ks)
if kif < 0 { return null }
local kcmp = ("" + s).lastIndexOf("\"type\":\"Compare\"", ks)
if kcmp < 0 || kcmp < kif { return null }
local op = Scan.read_quoted_after_key(s, kcmp, "op"); if op == null || op != "!=" { return null }
// else 範囲の配列区間を特定
local kth = JsonFragBox.index_of_from(s, "\"then\":", kif); if kth < 0 { return null }
local lb_then = JsonFragBox.index_of_from(s, "[", kth); if lb_then < 0 { return null }
local rb_then = JsonFragBox._seek_array_end(s, lb_then); if rb_then < 0 { return null }
local kel = JsonFragBox.index_of_from(s, "\"else\":", rb_then); if kel < 0 { return null }
local lb_else = JsonFragBox.index_of_from(s, "[", kel); if lb_else < 0 { return null }
local rb_else = JsonFragBox._seek_array_end(s, lb_else); if rb_else < 0 { return null }
// sentinel が else ブロック中にあること
if !(ks > lb_else && ks < rb_else) { return null }
// 比較の反対側 Int を抽出lhs=Var(varname) → rhs Int、rhs=Var → lhs Int
local has_lhs = ("" + s).indexOf("\"lhs\":{\"type\":\"Var\",\"name\":\"" + varname + "\"}", kcmp) >= 0
local has_rhs = ("" + s).indexOf("\"rhs\":{\"type\":\"Var\",\"name\":\"" + varname + "\"}", kcmp) >= 0
if !has_lhs && !has_rhs { return null }
if has_lhs {
local kr = ("" + s).indexOf("\"rhs\":{", kcmp); if kr < 0 { return null }
local kt = ("" + s).indexOf("\"type\":\"Int\"", kr); if kt < 0 { return null }
local sentinel_val = Scan.read_value_int_after(s, kt)
// Safety check: must be valid numeric string
if sentinel_val == null { return null }
local sval_str = "" + sentinel_val
if sval_str.length() == 0 { return null }
if sval_str.length() > 10 { return null }
// Must be numeric (basic check)
local i = 0; local len = sval_str.length()
loop(i < len) {
local ch = sval_str.substring(i, i + 1)
if ch < "0" || ch > "9" {
if !(i == 0 && ch == "-") { return null }
}
i = i + 1
}
return sentinel_val
}
// rhs が変数
local kl = ("" + s).indexOf("\"lhs\":{", kcmp); if kl < 0 { return null }
local kt2 = ("" + s).indexOf("\"type\":\"Int\"", kl); if kt2 < 0 { return null }
local sentinel_val2 = Scan.read_value_int_after(s, kt2)
// Safety check for rhs case
if sentinel_val2 == null { return null }
local sval_str2 = "" + sentinel_val2
if sval_str2.length() == 0 { return null }
if sval_str2.length() > 10 { return null }
local i2 = 0; local len2 = sval_str2.length()
loop(i2 < len2) {
local ch2 = sval_str2.substring(i2, i2 + 1)
if ch2 < "0" || ch2 > "9" {
if !(i2 == 0 && ch2 == "-") { return null }
}
i2 = i2 + 1
}
return sentinel_val2
}
}

View File

@ -3,47 +3,137 @@
using "hako.mir.builder.internal.prog_scan" as ProgScanBox
using ProgScanBox as Scan
using selfhost.shared.mir.loopform as LoopFormBox
using selfhost.shared.common.string_helpers as StringHelpers
using selfhost.shared.json.utils.json_frag as JsonFragBox
using selfhost.mir.builder.internal.pattern_util_box as PatternUtilBox
using "hako.mir.builder.internal.loop_scan" as LoopScanBox
static box LowerLoopCountParamBox {
try_lower(program_json) {
local s = "" + program_json
// Local i = Int init
local k_local_i = s.indexOf("\"type\":\"Local\"")
if k_local_i < 0 { return null }
if s.indexOf("\"name\":\"i\"", k_local_i) < 0 { return null }
local k_init = s.indexOf("\"type\":\"Int\"", k_local_i)
if k_init < 0 { return null }
local init = Scan.read_value_int_after(s, k_init)
if init == null { return null }
// Loop Compare i < Int limit
local k_loop = s.indexOf("\"type\":\"Loop\"", k_local_i)
// Discover loop variable name from Compare first
// We'll accept either lhs Var(name) or rhs Var(name)
local k_loop = s.indexOf("\"type\":\"Loop\"", 0)
if k_loop < 0 { return null }
local k_cmp = s.indexOf("\"type\":\"Compare\"", k_loop)
if k_cmp < 0 { return null }
if s.indexOf("\"op\":\"<\"", k_cmp) < 0 { return null }
if s.indexOf("\"lhs\":{\"type\":\"Var\",\"name\":\"i\"}", k_cmp) < 0 { return null }
local k_lim_t = s.indexOf("\"type\":\"Int\"", k_cmp)
if k_lim_t < 0 { return null }
local limit = Scan.read_value_int_after(s, k_lim_t)
if limit == null { return null }
local varname = LoopScanBox.find_loop_var_name(s, k_cmp)
if varname == null { return null }
// Local <varname> = (Int init | Var initName)
local k_local_i = s.indexOf("\"type\":\"Local\"")
if k_local_i < 0 { return null }
if s.indexOf("\"name\":\"" + varname + "\"", k_local_i) < 0 { return null }
local init = null
{
local k_init_int = s.indexOf("\"type\":\"Int\"", k_local_i)
local k_loop_next = s.indexOf("\"type\":\"Loop\"", k_local_i)
if k_init_int >= 0 && (k_loop_next < 0 || k_init_int < k_loop_next) {
init = Scan.read_value_int_after(s, k_init_int)
} else {
local k_init_var = s.indexOf("\"type\":\"Var\"", k_local_i)
if k_init_var >= 0 && (k_loop_next < 0 || k_init_var < k_loop_next) {
local vname = Scan.read_quoted_after_key(s, k_init_var, "name")
if vname != null { init = PatternUtilBox.find_local_int_before(s, vname, k_local_i) }
}
}
}
if init == null { return null }
// Loop Compare normalize: accept < / <= / > / >= with Var(varname) on either side
// op: accept '<'/'<=' with i on lhs; '>'/'>=' with i on lhs (descending); swapped '>'/'>=' with i on rhs (ascending)
local op = Scan.read_quoted_after_key(s, k_cmp, "op")
if op == null { return null }
local has_lhs_i = s.indexOf("\"lhs\":{\"type\":\"Var\",\"name\":\"" + varname + "\"}", k_cmp) >= 0
local has_rhs_i = s.indexOf("\"rhs\":{\"type\":\"Var\",\"name\":\"" + varname + "\"}", k_cmp) >= 0
if !has_lhs_i && !has_rhs_i { return null }
local cmp = null
local limit = null
if has_lhs_i {
if op == "<" || op == "<=" {
// i < L / i <= L
local k_rhs = s.indexOf("\"rhs\":{", k_cmp); if k_rhs < 0 { return null }
local k_lim_t = s.indexOf("\"type\":\"Int\"", k_rhs)
if k_lim_t >= 0 {
limit = Scan.read_value_int_after(s, k_lim_t)
} else {
// rhs Var → reverse-lookup Local Int
local k_rv = s.indexOf("\"type\":\"Var\"", k_rhs); if k_rv < 0 { return null }
local lname = Scan.read_quoted_after_key(s, k_rhs, "name"); if lname == null { return null }
limit = PatternUtilBox.find_local_int_before(s, lname, k_cmp)
}
if limit == null { return null }
if op == "<=" { limit = StringHelpers.int_to_str(JsonFragBox._str_to_int(limit) + 1) }
cmp = "Lt"
} else if op == ">" || op == ">=" {
// i > L / i >= L (descending)
local k_rhs2 = s.indexOf("\"rhs\":{", k_cmp); if k_rhs2 < 0 { return null }
local k_lim_t3 = s.indexOf("\"type\":\"Int\"", k_rhs2)
if k_lim_t3 >= 0 {
limit = Scan.read_value_int_after(s, k_lim_t3)
} else {
local k_rv2 = s.indexOf("\"type\":\"Var\"", k_rhs2); if k_rv2 < 0 { return null }
local lname2 = Scan.read_quoted_after_key(s, k_rhs2, "name"); if lname2 == null { return null }
limit = PatternUtilBox.find_local_int_before(s, lname2, k_cmp)
}
if limit == null { return null }
cmp = (op == ">") ? "Gt" : "Ge"
} else { return null }
} else {
// swapped (Int on lhs, Var i on rhs): L > i / L >= i (ascending)
if op != ">" && op != ">=" { return null }
local k_lhs = s.indexOf("\"lhs\":{", k_cmp); if k_lhs < 0 { return null }
local k_lim_t2 = s.indexOf("\"type\":\"Int\"", k_lhs)
if k_lim_t2 >= 0 {
limit = Scan.read_value_int_after(s, k_lim_t2)
} else {
local k_lv = s.indexOf("\"type\":\"Var\"", k_lhs); if k_lv < 0 { return null }
local lname3 = Scan.read_quoted_after_key(s, k_lhs, "name"); if lname3 == null { return null }
limit = PatternUtilBox.find_local_int_before(s, lname3, k_cmp)
}
if limit == null { return null }
if op == ">=" { limit = StringHelpers.int_to_str(JsonFragBox._str_to_int(limit) + 1) }
cmp = "Lt"
}
// Body increment: Local i = Binary('+', Var i, Int step)
local k_body_i = s.indexOf("\"name\":\"i\"", k_loop)
local k_body_i = s.indexOf("\"name\":\"" + varname + "\"", k_loop)
if k_body_i < 0 { return null }
local k_bop = s.indexOf("\"type\":\"Binary\"", k_body_i)
if k_bop < 0 { return null }
if s.indexOf("\"op\":\"+\"", k_bop) < 0 { return null }
if s.indexOf("\"lhs\":{\"type\":\"Var\",\"name\":\"i\"}", k_bop) < 0 { return null }
local k_step_t = s.indexOf("\"type\":\"Int\"", k_bop)
if k_step_t < 0 { return null }
local step = Scan.read_value_int_after(s, k_step_t)
if step == null { return null }
// Body increment: Local i = Binary(op '+' or '-', Var i, (Int step | Var stepName))
local bop_plus = (s.indexOf("\"op\":\"+\"", k_bop) >= 0)
local bop_minus = (s.indexOf("\"op\":\"-\"", k_bop) >= 0)
if (!bop_plus && !bop_minus) { return null }
if s.indexOf("\"lhs\":{\"type\":\"Var\",\"name\":\"" + varname + "\"}", k_bop) < 0 { return null }
// Build via LoopFormBox.build2 ({ mode:"count", init, limit, step })
local step = null
// Prefer rhs Int if present; otherwise try rhs Var and reverse-lookup its Local Int
{
local k_rhsb = s.indexOf("\"rhs\":{", k_bop); if k_rhsb < 0 { return null }
local k_t_int = s.indexOf("\"type\":\"Int\"", k_rhsb)
if k_t_int >= 0 {
step = Scan.read_value_int_after(s, k_t_int)
} else {
local k_t_var = s.indexOf("\"type\":\"Var\"", k_rhsb)
if k_t_var < 0 { return null }
local vname = Scan.read_quoted_after_key(s, k_rhsb, "name"); if vname == null { return null }
step = PatternUtilBox.find_local_int_before(s, vname, k_bop)
}
}
if step == null { return null }
if bop_minus {
// Encode subtraction as Add with negative step
local si = JsonFragBox._str_to_int(step)
step = StringHelpers.int_to_str(0 - si)
}
// limit normalized above
// Build via LoopFormBox.build2 ({ mode:"count", init, limit, step, cmp })
local opts = new MapBox()
opts.set("mode", "count")
opts.set("init", init)
opts.set("limit", limit)
opts.set("step", step)
opts.set("cmp", cmp)
return LoopFormBox.build2(opts)
}
}

View File

@ -2,35 +2,72 @@
// Notes: minimal scanner that extracts limit N from Program(JSON v0) Loop cond rhs Int.
using selfhost.shared.mir.loopform as LoopFormBox
using "hako.mir.builder.internal.prog_scan" as ProgScanBox
using ProgScanBox as Scan
using selfhost.shared.common.string_helpers as StringHelpers
using selfhost.shared.json.utils.json_frag as JsonFragBox
using "hako.mir.builder.internal.loop_scan" as LoopScanBox
static box LowerLoopSimpleBox {
try_lower(program_json) {
local s = "" + program_json
// Find Loop with cond Compare op '<' and rhs Int value
// Find Loop with cond Compare and normalize to canonical (<) with dynamic var name
local k_loop = s.indexOf("\"type\":\"Loop\"")
if k_loop < 0 { return null }
local k_cmp = s.indexOf("\"type\":\"Compare\"", k_loop)
if k_cmp < 0 { return null }
// op must be '<'
local k_op = s.indexOf("\"op\":\"<\"", k_cmp)
if k_op < 0 { return null }
// rhs Int value
local k_rhs = s.indexOf("\"rhs\":{", k_cmp)
if k_rhs < 0 { return null }
local k_ti = s.indexOf("\"type\":\"Int\"", k_rhs)
if k_ti < 0 { return null }
// Scan numeric after "value":
local k_v = s.indexOf("\"value\":", k_ti)
if k_v < 0 { return null }
local i = k_v + 8
// skip spaces
loop(i < s.length()) { if s.substring(i,i+1) != " " { break } i = i + 1 }
local j = i
if j < s.length() && s.substring(j,j+1) == "-" { j = j + 1 }
local had = 0
loop(j < s.length()) { local ch = s.substring(j,j+1); if ch >= "0" && ch <= "9" { had = 1 j = j + 1 } else { break } }
if had == 0 { return null }
local limit = s.substring(i, j)
// discover loop var name from cond (lhs or rhs Var)
local varname = LoopScanBox.find_loop_var_name(s, k_cmp)
if varname == null { return null }
// op: accept '<' / '<=' as-is, '!=' (with var on lhs) as '<', and swapped '>' / '>=' (with var on rhs)
local op = Scan.read_quoted_after_key(s, k_cmp, "op")
if op == null { return null }
// Determine where Var(varname) is and extract the Int from the opposite side
local has_lhs_i = s.indexOf("\"lhs\":{\"type\":\"Var\",\"name\":\"" + varname + "\"}", k_cmp) >= 0
local has_rhs_i = s.indexOf("\"rhs\":{\"type\":\"Var\",\"name\":\"" + varname + "\"}", k_cmp) >= 0
if !has_lhs_i && !has_rhs_i { return null }
local swapped = 0
local limit = null
if has_lhs_i {
// rhs Int value
local k_rhs = s.indexOf("\"rhs\":{", k_cmp); if k_rhs < 0 { return null }
local k_ti = s.indexOf("\"type\":\"Int\"", k_rhs); if k_ti < 0 { return null }
limit = Scan.read_value_int_after(s, k_ti)
if limit == null { return null }
} else {
// Var is on rhs; lhs must be Int
swapped = 1
local k_lhs = s.indexOf("\"lhs\":{", k_cmp); if k_lhs < 0 { return null }
local k_ti2 = s.indexOf("\"type\":\"Int\"", k_lhs); if k_ti2 < 0 { return null }
limit = Scan.read_value_int_after(s, k_ti2)
if limit == null { return null }
}
// Normalize to canonical '<' with possible +1 adjustment
if swapped == 0 {
if op == "<" {
// ok
} else if op == "<=" {
limit = StringHelpers.int_to_str(JsonFragBox._str_to_int(limit) + 1)
} else if op == "!=" {
// With init=0 and step=1 counting loop, i != L is equivalent to i < L
// keep limit as-is
} else {
return null
}
} else {
// swapped: we expect op to be '>' or '>='
if op == ">" {
// L > i ≡ i < L
} else if op == ">=" {
// L >= i ≡ i <= L ≡ i < (L+1)
limit = StringHelpers.int_to_str(JsonFragBox._str_to_int(limit) + 1)
} else {
return null
}
}
// Delegate to shared loop form builder (counting mode) via build2
local opts = new MapBox()

View File

@ -11,29 +11,57 @@
using "hako.mir.builder.internal.prog_scan" as ProgScanBox
using ProgScanBox as Scan
using selfhost.shared.mir.loopform as LoopFormBox
using selfhost.shared.common.string_helpers as StringHelpers
using selfhost.shared.json.utils.json_frag as JsonFragBox
using "hako.mir.builder.internal.loop_scan" as LoopScanBox
static box LowerLoopSumBcBox {
try_lower(program_json) {
local s = "" + program_json
// Loop and Compare(i < Int limit)
local trace = env.get("HAKO_MIR_BUILDER_TRACE_SUMBC")
if trace != null && ("" + trace) == "1" {
print("[sum_bc] enter lower")
}
// Loop and Compare normalize to canonical (<) with dynamic var name
local k_loop = s.indexOf("\"type\":\"Loop\"")
if k_loop < 0 { return null }
local k_cmp = s.indexOf("\"type\":\"Compare\"", k_loop)
if k_cmp < 0 { return null }
// op "<"
// discover loop var name from cond (lhs or rhs Var)
local varname = null
{
local kl = s.indexOf("\"lhs\":{", k_cmp); local kr = s.indexOf("\"rhs\":{", k_cmp)
if kl >= 0 && s.indexOf("\"type\":\"Var\"", kl) >= 0 { varname = Scan.read_quoted_after_key(s, kl, "name") }
if varname == null && kr >= 0 && s.indexOf("\"type\":\"Var\"", kr) >= 0 { varname = Scan.read_quoted_after_key(s, kr, "name") }
}
if varname == null { return null }
if trace != null && ("" + trace) == "1" {
print("[sum_bc] var=" + varname)
}
// op: accept '<'/'<=' with var on lhs; also accept swapped '>'/'>=' with var on rhs
local op = Scan.read_quoted_after_key(s, k_cmp, "op")
if op == null || op != "<" { return null }
// lhs must mention Var("i"); we check weakly by searching name:"i"
if s.indexOf("\"lhs\":{\"type\":\"Var\",\"name\":\"i\"}", k_cmp) < 0 { return null }
// rhs Int limit
local k_rhs = s.indexOf("\"rhs\":{", k_cmp)
if k_rhs < 0 { return null }
local k_ti = s.indexOf("\"type\":\"Int\"", k_rhs)
if k_ti < 0 { return null }
local limit = Scan.read_value_int_after(s, k_ti)
if limit == null { return null }
if op == null { return null }
local has_lhs_i = s.indexOf("\"lhs\":{\"type\":\"Var\",\"name\":\"" + varname + "\"}", k_cmp) >= 0
local has_rhs_i = s.indexOf("\"rhs\":{\"type\":\"Var\",\"name\":\"" + varname + "\"}", k_cmp) >= 0
if !has_lhs_i && !has_rhs_i { return null }
local limit = null
if has_lhs_i {
if op != "<" && op != "<=" { return null }
// rhs Int limit
local k_rhs = s.indexOf("\"rhs\":{", k_cmp); if k_rhs < 0 { return null }
local k_ti = s.indexOf("\"type\":\"Int\"", k_rhs); if k_ti < 0 { return null }
limit = Scan.read_value_int_after(s, k_ti); if limit == null { return null }
if op == "<=" { limit = StringHelpers.int_to_str(JsonFragBox._str_to_int(limit) + 1) }
} else {
// swapped: Int on lhs, Var i on rhs, op should be '>' or '>='
if op != ">" && op != ">=" { return null }
local k_lhs = s.indexOf("\"lhs\":{", k_cmp); if k_lhs < 0 { return null }
local k_ti2 = s.indexOf("\"type\":\"Int\"", k_lhs); if k_ti2 < 0 { return null }
limit = Scan.read_value_int_after(s, k_ti2); if limit == null { return null }
if op == ">=" { limit = StringHelpers.int_to_str(JsonFragBox._str_to_int(limit) + 1) }
}
// Break sentinel: If(cond Compare i==X) then Break
// Break sentinel: If(cond Compare var==X or X==var) then Break
local break_value = null
{
local kb = s.indexOf("\"type\":\"Break\"", k_loop)
@ -43,14 +71,24 @@ static box LowerLoopSumBcBox {
if kbc >= 0 {
// Ensure op=="==" and lhs Var i
local bop = Scan.read_quoted_after_key(s, kbc, "op")
if bop != null && bop == "==" && s.indexOf("\"lhs\":{\"type\":\"Var\",\"name\":\"i\"}", kbc) >= 0 {
local kbi = s.indexOf("\"type\":\"Int\"", kbc)
if kbi >= 0 { break_value = Scan.read_value_int_after(s, kbi) }
if bop != null && bop == "==" {
local lhs_i = s.indexOf("\"lhs\":{\"type\":\"Var\",\"name\":\"" + varname + "\"}", kbc) >= 0
local rhs_i = s.indexOf("\"rhs\":{\"type\":\"Var\",\"name\":\"" + varname + "\"}", kbc) >= 0
if lhs_i {
local kbi = s.indexOf("\"type\":\"Int\"", s.indexOf("\"rhs\":{", kbc))
if kbi >= 0 { break_value = Scan.read_value_int_after(s, kbi) }
} else if rhs_i {
local kbi2 = s.indexOf("\"type\":\"Int\"", s.indexOf("\"lhs\":{", kbc))
if kbi2 >= 0 { break_value = Scan.read_value_int_after(s, kbi2) }
}
} else if bop != null && bop == "!=" {
// Delegate to loop-scan helper for '!=' + else [Break]
if break_value == null { break_value = LoopScanBox.extract_ne_else_sentinel_value(s, "Break", k_loop, varname) }
}
}
}
}
// Continue sentinel: If(cond Compare i==Y) then Continue
// Continue sentinel: If(cond Compare var==Y or Y==var) then Continue
local skip_value = null
{
local kc = s.indexOf("\"type\":\"Continue\"", k_loop)
@ -58,9 +96,19 @@ static box LowerLoopSumBcBox {
local kcc = s.lastIndexOf("\"type\":\"Compare\"", kc)
if kcc >= 0 {
local cop = Scan.read_quoted_after_key(s, kcc, "op")
if cop != null && cop == "==" && s.indexOf("\"lhs\":{\"type\":\"Var\",\"name\":\"i\"}", kcc) >= 0 {
local kci = s.indexOf("\"type\":\"Int\"", kcc)
if kci >= 0 { skip_value = Scan.read_value_int_after(s, kci) }
if cop != null && cop == "==" {
local lhs_i2 = s.indexOf("\"lhs\":{\"type\":\"Var\",\"name\":\"" + varname + "\"}", kcc) >= 0
local rhs_i2 = s.indexOf("\"rhs\":{\"type\":\"Var\",\"name\":\"" + varname + "\"}", kcc) >= 0
if lhs_i2 {
local kci = s.indexOf("\"type\":\"Int\"", s.indexOf("\"rhs\":{", kcc))
if kci >= 0 { skip_value = Scan.read_value_int_after(s, kci) }
} else if rhs_i2 {
local kci2 = s.indexOf("\"type\":\"Int\"", s.indexOf("\"lhs\":{", kcc))
if kci2 >= 0 { skip_value = Scan.read_value_int_after(s, kci2) }
}
} else if cop != null && cop == "!=" {
// Delegate to loop-scan helper for '!=' + else [Continue]
if skip_value == null { skip_value = LoopScanBox.extract_ne_else_sentinel_value(s, "Continue", k_loop, varname) }
}
}
}
@ -70,12 +118,23 @@ static box LowerLoopSumBcBox {
if skip_value == null { skip_value = 2 }
if break_value == null { break_value = limit }
// Trace detected values
if trace != null && ("" + trace) == "1" {
local skip_str = "" + skip_value
local break_str = "" + break_value
local limit_str = "" + limit
print("[sum_bc] limit=" + limit_str + " skip=" + skip_str + " break=" + break_str)
}
// Use build2 map form for clarity
local opts = new MapBox()
opts.set("mode", "sum_bc")
opts.set("limit", limit)
opts.set("skip", skip_value)
opts.set("break", break_value)
if trace != null && ("" + trace) == "1" {
print("[sum_bc] building MIR with LoopFormBox")
}
return LoopFormBox.build2(opts)
}
}

View File

@ -1,9 +1,27 @@
// pattern_util_box.hako — Shared utilities for MirBuilder lowers
using selfhost.shared.json.utils.json_frag as JsonFragBox
using selfhost.shared.common.string_helpers as StringHelpers
static box PatternUtilBox {
map_cmp(sym) { if sym=="<" {return "Lt"} if sym==">" {return "Gt"} if sym=="<=" {return "Le"} if sym==">=" {return "Ge"} if sym=="==" {return "Eq"} if sym=="!=" {return "Ne"} return null }
// Normalize limit for canonical (i < limit) form.
// When swapped==0, expects op in {'<','<='}; when swapped==1 (Int on lhs, Var on rhs), expects op in {'>','>='}.
// Returns adjusted limit string or null if unsupported.
normalize_limit_for_lt(op, swapped, limit_str) {
local op1 = "" + op
local ls = "" + limit_str
if swapped == 0 {
if op1 == "<" { return ls }
if op1 == "<=" { return StringHelpers.int_to_str(JsonFragBox._str_to_int(ls) + 1) }
if op1 == "!=" { return ls } // safe for count(init=0,step=1)
return null
} else {
if op1 == ">" { return ls }
if op1 == ">=" { return StringHelpers.int_to_str(JsonFragBox._str_to_int(ls) + 1) }
return null
}
}
find_local_int_before(s, name, before_pos) {
local pos=0; local last=-1
loop(true){ local k=JsonFragBox.index_of_from(s, "\"type\":\"Local\"",pos); if k<0||k>=before_pos{break}; local kn=JsonFragBox.index_of_from(s, "\"name\":\"",k); if kn>=0{ local ii=kn+8; local nn=s.length(); local jj=ii; loop(jj<nn){ if s.substring(jj,jj+1)=="\"" {break} jj=jj+1 } if s.substring(ii,jj)==name { last=k } } pos=k+1 }

View File

@ -183,6 +183,46 @@ static box LoopFormBox {
return MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
}
// Extended param variant: allow custom compare op ("Lt"/"Le"/"Gt"/"Ge") via opts
// and negative step (handled by passing negative string for step)
loop_count_param_ex(init, limit, step, cmp) {
local cmpop = "" + cmp
if cmpop == null || cmpop == "" { cmpop = "Lt" }
// Preheader
local pre = new ArrayBox()
pre.push(MirSchemaBox.inst_const(1, init))
pre.push(MirSchemaBox.inst_const(2, limit))
pre.push(MirSchemaBox.inst_const(3, step))
pre.push(MirSchemaBox.inst_jump(1))
// Header
local header = new ArrayBox()
local inc = new ArrayBox(); inc.push(MirSchemaBox.phi_incoming(0, 1)); inc.push(MirSchemaBox.phi_incoming(3, 12))
header.push(MirSchemaBox.inst_phi(10, inc))
header.push(MirSchemaBox.inst_compare(cmpop, 10, 2, 11))
header.push(MirSchemaBox.inst_branch(11, 2, 4))
// Body
local body = new ArrayBox()
body.push(MirSchemaBox.inst_binop("Add", 10, 3, 12))
body.push(MirSchemaBox.inst_jump(3))
// Latch
local latch = new ArrayBox(); latch.push(MirSchemaBox.inst_jump(1))
// Exit
local exit = new ArrayBox(); exit.push(MirSchemaBox.inst_ret(10))
local blocks = new ArrayBox()
blocks.push(MirSchemaBox.block(0, pre))
blocks.push(MirSchemaBox.block(1, header))
blocks.push(MirSchemaBox.block(2, body))
blocks.push(MirSchemaBox.block(3, latch))
blocks.push(MirSchemaBox.block(4, exit))
return MirSchemaBox.module(MirSchemaBox.fn_main(blocks))
}
// Unified entry — build(mode, limit, skip_value, break_value)
// mode:
// - "count" : counting loop that returns final i (uses loop_count)
@ -218,7 +258,13 @@ static box LoopFormBox {
local step = opts.get("step")
local skip_v = opts.get("skip")
local break_v = opts.get("break")
if mode == "count" { if init == null { init = 0 } if step == null { step = 1 } return me.loop_count_param(init, limit, step) }
if mode == "count" {
if init == null { init = 0 }
if step == null { step = 1 }
local cmp = opts.get("cmp") // optional: "Lt"/"Le"/"Gt"/"Ge"
if cmp == null { cmp = "Lt" }
return me.loop_count_param_ex(init, limit, step, cmp)
}
if mode == "sum_bc" { return me.loop_counter(limit, skip_v, break_v) }
print("[loopform/unsupported-mode] " + mode)
return null

View File

@ -55,11 +55,12 @@ static box NyVmDispatcher {
local iter = 0
loop (iter < max_iter) {
iter = iter + 1
local bjson = block_map.get("" + current_bb)
if bjson == null { print("[core] block not found: " + ("" + current_bb)) return -1 }
using selfhost.shared.common.string_helpers as StringHelpers
local bjson = block_map.get(StringHelpers.int_to_str(current_bb))
if bjson == null { print("[core] block not found: " + StringHelpers.int_to_str(current_bb)) return -1 }
// Execute instructions in this block
local insts = NyVmJsonV0Reader.block_instructions_json(bjson)
if insts == "" { print("[core] empty instructions at bb" + ("" + current_bb)) return -1 }
if insts == "" { print("[core] empty instructions at bb" + StringHelpers.int_to_str(current_bb)) return -1 }
// First pass: apply phi nodes
{

View File

@ -108,7 +108,8 @@ static box NyVmJsonV0Reader {
local blk = arr.substring(pos, end+1)
local id = me.read_block_id(blk)
if id >= 0 {
out.set("" + id, blk)
using selfhost.shared.common.string_helpers as StringHelpers
out.set(StringHelpers.int_to_str(id), blk)
}
pos = end + 1
}

View File

@ -15,10 +15,22 @@ static box NyVmOpMirCall {
return -1
}
_arr_key(recv_id) { return "arrsize:r" + ("" + recv_id) }
_arr_val_key(recv_id, idx) { return "arrval:r" + ("" + recv_id) + ":" + ("" + idx) }
_map_key(recv_id) { return "maplen:r" + ("" + recv_id) }
_map_entry_slot(recv_id, key) { return "mapentry:r" + ("" + recv_id) + ":" + key }
_arr_key(recv_id) {
using selfhost.shared.common.string_helpers as StringHelpers
return "arrsize:r" + StringHelpers.int_to_str(recv_id)
}
_arr_val_key(recv_id, idx) {
using selfhost.shared.common.string_helpers as StringHelpers
return "arrval:r" + StringHelpers.int_to_str(recv_id) + ":" + StringHelpers.int_to_str(idx)
}
_map_key(recv_id) {
using selfhost.shared.common.string_helpers as StringHelpers
return "maplen:r" + StringHelpers.int_to_str(recv_id)
}
_map_entry_slot(recv_id, key) {
using selfhost.shared.common.string_helpers as StringHelpers
return "mapentry:r" + StringHelpers.int_to_str(recv_id) + ":" + key
}
_extract_mir_call_obj(inst_json) {
local key = "\"mir_call\":"
@ -235,7 +247,8 @@ static box NyVmOpMirCall {
local arg_vid = me._read_arg_vid(m, 0, "[core/mir_call] console missing arg", "[core/mir_call] console bad arg")
if arg_vid == null { return -1 }
local val = NyVmState.get_reg(state, arg_vid)
print("" + val)
using selfhost.vm.hakorune-vm.str_cast as StrCast
print(StrCast.to_str(val))
return 0
}
@ -390,7 +403,8 @@ static box NyVmOpMirCall {
local key_vid = me._read_arg_vid(m, 0, "[core/mir_call] map has missing key", "[core/mir_call] map has bad key")
if key_vid == null { return -1 }
local key_val = NyVmState.get_reg(state, key_vid)
local key = "" + key_val
using selfhost.vm.hakorune-vm.str_cast as StrCast
local key = StrCast.to_str(key_val)
local slot = me._map_entry_slot(recv_id, key)
local has_key = mem.get(slot) != null
local result = 0
@ -409,7 +423,8 @@ static box NyVmOpMirCall {
local i = 0
local n = all_keys.length()
loop(i < n) {
local k = "" + all_keys.get(i)
using selfhost.vm.hakorune-vm.str_cast as StrCast
local k = StrCast.to_str(all_keys.get(i))
// startsWith(prefix)
local ok = 0
if k.length() >= prefix.length() {
@ -437,7 +452,8 @@ static box NyVmOpMirCall {
local i = 0
local n = all_keys.length()
loop(i < n) {
local k = "" + all_keys.get(i)
using selfhost.vm.hakorune-vm.str_cast as StrCast
local k = StrCast.to_str(all_keys.get(i))
local ok = 0
if k.length() >= prefix.length() {
if k.substring(0, prefix.length()) == prefix { ok = 1 }
@ -462,7 +478,8 @@ static box NyVmOpMirCall {
local key_vid = me._read_arg_vid(m, 0, "[core/mir_call] map delete missing key", "[core/mir_call] map delete bad key")
if key_vid == null { return -1 }
local key_val = NyVmState.get_reg(state, key_vid)
local key = "" + key_val
using selfhost.vm.hakorune-vm.str_cast as StrCast
local key = StrCast.to_str(key_val)
local slot = me._map_entry_slot(recv_id, key)
local deleted = mem.get(slot)
if deleted != null {
@ -482,7 +499,8 @@ static box NyVmOpMirCall {
local i = 0
local n = all_keys.length()
loop(i < n) {
local k = "" + all_keys.get(i)
using selfhost.vm.hakorune-vm.str_cast as StrCast
local k = StrCast.to_str(all_keys.get(i))
local ok = 0
if k.length() >= prefix.length() {
if k.substring(0, prefix.length()) == prefix { ok = 1 }
@ -503,7 +521,8 @@ static box NyVmOpMirCall {
local key_val = NyVmState.get_reg(state, key_vid)
// Validate key: null/void are invalid暗黙変換なし
if key_val == null || key_val == void { return me._fail(state, "[core/map/key_type]") }
local key = "" + key_val
using selfhost.vm.hakorune-vm.str_cast as StrCast
local key = StrCast.to_str(key_val)
local slot = me._map_entry_slot(recv_id, key)
local had = mem.get(slot) != null
// Validate value: void は不正。null は許可(値として保存)。
@ -522,7 +541,8 @@ static box NyVmOpMirCall {
local key_vid = me._read_arg_vid(m, 0, "[core/mir_call] map get missing key", "[core/mir_call] map get bad key")
if key_vid == null { return -1 }
local key_val = NyVmState.get_reg(state, key_vid)
local key = "" + key_val
using selfhost.vm.hakorune-vm.str_cast as StrCast
local key = StrCast.to_str(key_val)
local slot = me._map_entry_slot(recv_id, key)
local value = mem.get(slot)
local opt = me._read_optionality(m)
@ -584,7 +604,8 @@ static box NyVmOpMirCall {
local dst = me._read_dst(inst_json, "method(size)")
if dst == null { return -1 }
local recv_val = NyVmState.get_reg(state, recv_id)
local s = "" + recv_val
using selfhost.vm.hakorune-vm.str_cast as StrCast
local s = StrCast.to_str(recv_val)
NyVmState.set_reg(state, dst, s.length())
return 0
}
@ -595,9 +616,11 @@ static box NyVmOpMirCall {
local idx_vid = me._read_arg_vid(m, 0, "[core/mir_call] method(indexOf) missing needle", "[core/mir_call] method(indexOf) bad needle")
if idx_vid == null { return -1 }
local recv_val = NyVmState.get_reg(state, recv_id)
local s = "" + recv_val
using selfhost.vm.hakorune-vm.str_cast as StrCast
local s = StrCast.to_str(recv_val)
local needle_val = NyVmState.get_reg(state, idx_vid)
local needle = "" + needle_val
using selfhost.vm.hakorune-vm.str_cast as StrCast
local needle = StrCast.to_str(needle_val)
local pos = s.indexOf(needle)
if pos >= 0 {
NyVmState.set_reg(state, dst, pos)
@ -622,9 +645,11 @@ static box NyVmOpMirCall {
local idx_vid = me._read_arg_vid(m, 0, "[core/mir_call] method(lastIndexOf) missing needle", "[core/mir_call] method(lastIndexOf) bad needle")
if idx_vid == null { return -1 }
local recv_val = NyVmState.get_reg(state, recv_id)
local s = "" + recv_val
using selfhost.vm.hakorune-vm.str_cast as StrCast
local s = StrCast.to_str(recv_val)
local needle_val = NyVmState.get_reg(state, idx_vid)
local needle = "" + needle_val
using selfhost.vm.hakorune-vm.str_cast as StrCast
local needle = StrCast.to_str(needle_val)
local pos = s.lastIndexOf(needle)
NyVmState.set_reg(state, dst, pos)
return 0
@ -638,7 +663,8 @@ static box NyVmOpMirCall {
local end_vid = me._read_arg_vid(m, 1, "[core/mir_call] method(substring) missing end", "[core/mir_call] method(substring) bad end")
if end_vid == null { return -1 }
local recv_val = NyVmState.get_reg(state, recv_id)
local s = "" + recv_val
using selfhost.vm.hakorune-vm.str_cast as StrCast
local s = StrCast.to_str(recv_val)
local start = NyVmState.get_reg(state, start_vid)
local end = NyVmState.get_reg(state, end_vid)
// Bounds check
@ -656,7 +682,8 @@ static box NyVmOpMirCall {
local idx_vid = me._read_arg_vid(m, 0, "[core/mir_call] method(charAt) missing index", "[core/mir_call] method(charAt) bad index")
if idx_vid == null { return -1 }
local recv_val = NyVmState.get_reg(state, recv_id)
local s = "" + recv_val
using selfhost.vm.hakorune-vm.str_cast as StrCast
local s = StrCast.to_str(recv_val)
local idx = NyVmState.get_reg(state, idx_vid)
// Bounds check
if idx < 0 || idx >= s.length() {
@ -675,11 +702,14 @@ static box NyVmOpMirCall {
local replacement_vid = me._read_arg_vid(m, 1, "[core/mir_call] method(replace) missing replacement", "[core/mir_call] method(replace) bad replacement")
if replacement_vid == null { return -1 }
local recv_val = NyVmState.get_reg(state, recv_id)
local s = "" + recv_val
using selfhost.vm.hakorune-vm.str_cast as StrCast
local s = StrCast.to_str(recv_val)
local pattern_val = NyVmState.get_reg(state, pattern_vid)
local pattern = "" + pattern_val
using selfhost.vm.hakorune-vm.str_cast as StrCast
local pattern = StrCast.to_str(pattern_val)
local replacement_val = NyVmState.get_reg(state, replacement_vid)
local replacement = "" + replacement_val
using selfhost.vm.hakorune-vm.str_cast as StrCast
local replacement = StrCast.to_str(replacement_val)
// Simple replace: find first occurrence and replace
local pos = s.indexOf(pattern)
local result = s
@ -737,7 +767,8 @@ static box NyVmOpMirCall {
local arg_vid = me._read_first_arg(m)
if arg_vid == null { return me._fail(state, "[core/mir_call] int_to_str missing arg") }
local v = NyVmState.get_reg(state, arg_vid)
local s = "" + v
using selfhost.vm.hakorune-vm.str_cast as StrCast
local s = StrCast.to_str(v)
local dst = me._read_dst(inst_json, "modulefn StringHelpers.int_to_str/1")
if dst == null { return -1 }
NyVmState.set_reg(state, dst, s)
@ -749,12 +780,14 @@ static box NyVmOpMirCall {
local v = NyVmState.get_reg(state, arg_vid)
// Accept already-integer values; else convert numeric strings only
local outv = 0
if ("" + v) == ("" + StringHelpers.to_i64(v)) {
using selfhost.vm.hakorune-vm.str_cast as StrCast
if StrCast.to_str(v) == StrCast.to_str(StringHelpers.to_i64(v)) {
// naive guard: to_i64 roundtrip textual equality — accept v
outv = StringHelpers.to_i64(v)
} else {
// Fallback strict check: only digit strings with optional sign
local s = "" + v
using selfhost.vm.hakorune-vm.str_cast as StrCast
local s = StrCast.to_str(v)
local i = 0
if s.length() > 0 && (s.substring(0,1) == "-" || s.substring(0,1) == "+") { i = 1 }
local ok = (s.length() > i)

View File

@ -7,7 +7,10 @@ static box NyVmState {
s.set("mem", new MapBox())
return s
}
_reg_key(id) { return "r" + ("" + id) }
_reg_key(id) {
using selfhost.shared.common.string_helpers as StringHelpers
return "r" + StringHelpers.int_to_str(id)
}
get_reg(s, id) {
local key = me._reg_key(id)
local regs = s.get("regs")