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:
@ -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)
|
||||
|
||||
@ -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, "\"")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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 } }
|
||||
|
||||
83
lang/src/mir/builder/internal/loop_scan_box.hako
Normal file
83
lang/src/mir/builder/internal/loop_scan_box.hako
Normal 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
|
||||
}
|
||||
}
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 }
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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")
|
||||
|
||||
Reference in New Issue
Block a user