3225 lines
92 KiB
Plaintext
3225 lines
92 KiB
Plaintext
var json = use("json")
|
|
|
|
var mcode = function(ast) {
|
|
// Translation tables
|
|
var binop_map = {
|
|
"+": "add", "-": "subtract", "*": "multiply", "/": "divide",
|
|
"%": "remainder", "&": "bitand", "|": "bitor", "^": "bitxor",
|
|
"<<": "shl", ">>": "shr", ">>>": "ushr",
|
|
"==": "eq", "===": "eq", "!=": "ne", "!==": "ne",
|
|
"<": "lt", "<=": "le", ">": "gt", ">=": "ge",
|
|
"**": "pow", "in": "in"
|
|
}
|
|
|
|
var functino_map = {
|
|
"+!": "add", "-!": "subtract", "*!": "multiply", "/!": "divide",
|
|
"%!": "modulo", "**!": "pow",
|
|
"<!": "lt", ">!": "gt", "<=!": "le", ">=!": "ge",
|
|
"=!": "eq", "!=!": "ne",
|
|
"&!": "bitand", "|!": "bitor", "^!": "bitxor",
|
|
"<<!": "shl", ">>!": "shr", ">>>!": "ushr",
|
|
"&&!": "and", "||!": "or",
|
|
"~!": "bitnot", "[]!": "load"
|
|
}
|
|
|
|
var binop_sym = {
|
|
add: "+", subtract: "-", multiply: "*", divide: "/",
|
|
remainder: "%", pow: "**",
|
|
lt: "<", le: "<=", gt: ">", ge: ">="
|
|
}
|
|
|
|
var compound_map = {
|
|
"+=": "add", "-=": "subtract", "*=": "multiply", "/=": "divide",
|
|
"%=": "remainder", "&=": "bitand", "|=": "bitor", "^=": "bitxor",
|
|
"<<=": "shl", ">>=": "shr", ">>>=": "ushr"
|
|
}
|
|
|
|
var sensory_ops = {
|
|
is_array: "is_array", is_function: "is_func", is_object: "is_record",
|
|
is_stone: "is_stone", is_integer: "is_int", is_text: "is_text",
|
|
is_number: "is_num", is_logical: "is_bool", is_null: "is_null",
|
|
is_blob: "is_blob", is_data: "is_data",
|
|
is_true: "is_true", is_false: "is_false", is_fit: "is_fit",
|
|
is_character: "is_char", is_digit: "is_digit", is_letter: "is_letter",
|
|
is_lower: "is_lower", is_upper: "is_upper", is_whitespace: "is_ws",
|
|
is_actor: "is_actor",
|
|
length: "length"
|
|
}
|
|
|
|
// Numeric intrinsic lowering maps (Tier 1 direct mcode).
|
|
var intrinsic_num_unary_ops = {
|
|
abs: "abs",
|
|
sign: "sign",
|
|
fraction: "fraction",
|
|
integer: "integer",
|
|
whole: "integer",
|
|
neg: "negate"
|
|
}
|
|
var intrinsic_num_binary_ops = {
|
|
modulo: "modulo",
|
|
remainder: "remainder",
|
|
max: "max",
|
|
min: "min"
|
|
}
|
|
var intrinsic_num_place_ops = {
|
|
floor: "floor",
|
|
ceiling: "ceiling",
|
|
round: "round",
|
|
trunc: "trunc"
|
|
}
|
|
|
|
// Compiler state
|
|
var s_instructions = null
|
|
var s_data = null
|
|
var s_functions = null
|
|
var s_vars = null
|
|
var s_this_slot = 0
|
|
var s_nr_args = 0
|
|
var s_nr_close_slots = 0
|
|
var s_nr_local_slots = 0
|
|
var s_next_temp_slot = 0
|
|
var s_max_slot = 0
|
|
var s_label_counter = 0
|
|
var s_func_counter = 0
|
|
var s_loop_break = null
|
|
var s_loop_continue = null
|
|
var s_label_map = {}
|
|
var s_pending_label = null
|
|
var s_is_arrow = false
|
|
var s_function_nr = 0
|
|
var s_scopes = null
|
|
var s_intrinsic_cache = null
|
|
var s_cur_line = 0
|
|
var s_cur_col = 0
|
|
var s_filename = null
|
|
var s_has_disruption = false
|
|
var s_slot_types = {}
|
|
var s_num_err_label = null
|
|
var s_num_err_emitted = false
|
|
|
|
// Shared closure vars for binop helpers (avoids >4 param functions)
|
|
var _bp_dest = 0
|
|
var _bp_left = 0
|
|
var _bp_right = 0
|
|
var _bp_ln = null
|
|
var _bp_rn = null
|
|
var _bp_op_sym = null
|
|
|
|
// State save/restore for nested function compilation
|
|
var save_state = function() {
|
|
return {
|
|
instructions: s_instructions,
|
|
vars: s_vars,
|
|
this_slot: s_this_slot,
|
|
nr_args: s_nr_args,
|
|
nr_close_slots: s_nr_close_slots,
|
|
nr_local_slots: s_nr_local_slots,
|
|
next_temp_slot: s_next_temp_slot,
|
|
max_slot: s_max_slot,
|
|
loop_break: s_loop_break,
|
|
loop_continue: s_loop_continue,
|
|
label_map: s_label_map,
|
|
is_arrow: s_is_arrow,
|
|
function_nr: s_function_nr,
|
|
intrinsic_cache: s_intrinsic_cache,
|
|
cur_line: s_cur_line,
|
|
cur_col: s_cur_col,
|
|
has_disruption: s_has_disruption,
|
|
slot_types: s_slot_types,
|
|
num_err_label: s_num_err_label,
|
|
num_err_emitted: s_num_err_emitted
|
|
}
|
|
}
|
|
|
|
var restore_state = function(saved) {
|
|
s_instructions = saved.instructions
|
|
s_vars = saved.vars
|
|
s_this_slot = saved.this_slot
|
|
s_nr_args = saved.nr_args
|
|
s_nr_close_slots = saved.nr_close_slots
|
|
s_nr_local_slots = saved.nr_local_slots
|
|
s_next_temp_slot = saved.next_temp_slot
|
|
s_max_slot = saved.max_slot
|
|
s_loop_break = saved.loop_break
|
|
s_loop_continue = saved.loop_continue
|
|
s_label_map = saved.label_map
|
|
s_is_arrow = saved.is_arrow
|
|
s_function_nr = saved.function_nr
|
|
s_intrinsic_cache = saved.intrinsic_cache
|
|
s_cur_line = saved.cur_line
|
|
s_cur_col = saved.cur_col
|
|
s_has_disruption = saved.has_disruption
|
|
s_slot_types = saved.slot_types
|
|
s_num_err_label = saved.num_err_label
|
|
s_num_err_emitted = saved.num_err_emitted
|
|
}
|
|
|
|
// Slot allocation
|
|
var alloc_slot = function() {
|
|
var slot = s_next_temp_slot
|
|
s_next_temp_slot = s_next_temp_slot + 1
|
|
if (slot > s_max_slot) {
|
|
s_max_slot = slot
|
|
}
|
|
return slot
|
|
}
|
|
|
|
// Variable tracking
|
|
var add_var = function(name, slot, is_const) {
|
|
push(s_vars, {name: name, slot: slot, is_const: is_const, is_closure: false})
|
|
}
|
|
|
|
var find_var = function(name) {
|
|
var _i = 0
|
|
while (_i < length(s_vars)) {
|
|
if (s_vars[_i].name == name) {
|
|
return s_vars[_i].slot
|
|
}
|
|
_i = _i + 1
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// Intrinsic cache
|
|
var find_intrinsic = function(name) {
|
|
var _i = 0
|
|
while (_i < length(s_intrinsic_cache)) {
|
|
if (s_intrinsic_cache[_i].name == name) {
|
|
return s_intrinsic_cache[_i].slot
|
|
}
|
|
_i = _i + 1
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// Scope helpers
|
|
var find_scope_record = function(fn_nr) {
|
|
if (s_scopes == null) {
|
|
return null
|
|
}
|
|
var _i = 0
|
|
var scope = null
|
|
while (_i < length(s_scopes)) {
|
|
scope = s_scopes[_i]
|
|
if (scope.function_nr == fn_nr) {
|
|
return scope
|
|
}
|
|
_i = _i + 1
|
|
}
|
|
return null
|
|
}
|
|
|
|
// Label generation
|
|
var gen_label = function(prefix) {
|
|
var label = prefix + "_" + text(s_label_counter)
|
|
s_label_counter = s_label_counter + 1
|
|
return label
|
|
}
|
|
|
|
// Position tracking
|
|
var set_pos = function(node) {
|
|
if (node.from_row != null) {
|
|
s_cur_line = node.from_row + 1
|
|
}
|
|
if (node.from_column != null) {
|
|
s_cur_col = node.from_column + 1
|
|
}
|
|
}
|
|
|
|
// Instruction emission
|
|
var add_instr = function(instr) {
|
|
push(instr, s_cur_line)
|
|
push(instr, s_cur_col)
|
|
push(s_instructions, instr)
|
|
}
|
|
|
|
var emit_label = function(label) {
|
|
push(s_instructions, label)
|
|
}
|
|
|
|
var emit_0 = function(op) {
|
|
add_instr([op])
|
|
}
|
|
|
|
var emit_1 = function(op, a) {
|
|
add_instr([op, a])
|
|
}
|
|
|
|
var emit_2 = function(op, a, b) {
|
|
add_instr([op, a, b])
|
|
}
|
|
|
|
var emit_3 = function(op, a, b, c) {
|
|
add_instr([op, a, b, c])
|
|
}
|
|
|
|
var emit_4 = function(op, a, b, c) {
|
|
// 4th operand passed via closure - use emit_4_full instead
|
|
add_instr([op, a, b, c])
|
|
}
|
|
|
|
var emit_4_full = function(op, abcd) {
|
|
var instr = [op, abcd[0], abcd[1], abcd[2], abcd[3]]
|
|
add_instr(instr)
|
|
}
|
|
|
|
var emit_const_num = function(dest, val) {
|
|
add_instr(["access", dest, val])
|
|
}
|
|
|
|
var emit_const_str = function(dest, val) {
|
|
add_instr(["access", dest, val])
|
|
}
|
|
|
|
var emit_const_bool = function(dest, val) {
|
|
if (val) {
|
|
emit_1("true", dest)
|
|
} else {
|
|
emit_1("false", dest)
|
|
}
|
|
}
|
|
|
|
var emit_const_null = function(dest) {
|
|
emit_1("null", dest)
|
|
}
|
|
|
|
var emit_log_error = function(msg) {
|
|
var log_slot = alloc_slot()
|
|
add_instr(["access", log_slot, {kind: "name", name: "log", make: "intrinsic"}])
|
|
var name_slot = alloc_slot()
|
|
emit_const_str(name_slot, "error")
|
|
var msg_slot = alloc_slot()
|
|
emit_const_str(msg_slot, msg)
|
|
var args_arr = alloc_slot()
|
|
add_instr(["array", args_arr, 0])
|
|
emit_2("push", args_arr, msg_slot)
|
|
var result = alloc_slot()
|
|
var frame_slot = alloc_slot()
|
|
emit_3("frame", frame_slot, log_slot, 2)
|
|
var null_slot = alloc_slot()
|
|
emit_1("null", null_slot)
|
|
emit_3("setarg", frame_slot, 0, null_slot)
|
|
emit_3("setarg", frame_slot, 1, name_slot)
|
|
emit_3("setarg", frame_slot, 2, args_arr)
|
|
emit_2("invoke", frame_slot, result)
|
|
}
|
|
|
|
var emit_jump = function(label) {
|
|
add_instr(["jump", label])
|
|
}
|
|
|
|
var emit_jump_cond = function(op, slot, label) {
|
|
add_instr([op, slot, label])
|
|
}
|
|
|
|
// ---- Decomposed op emission helpers ----
|
|
|
|
// Helper: check if an AST node is a known-int literal
|
|
var is_known_int = function(node) {
|
|
if (node == null) { return false }
|
|
return node.kind == "number" && is_integer(node.number)
|
|
}
|
|
|
|
// Helper: check if an AST node is a known-text literal
|
|
var is_known_text = function(node) {
|
|
if (node == null) { return false }
|
|
return node.kind == "text" || node.kind == "text literal"
|
|
}
|
|
|
|
// Helper: check if an AST node is a known-number literal (int or float)
|
|
var is_known_number = function(node) {
|
|
if (node == null) { return false }
|
|
return node.kind == "number"
|
|
}
|
|
|
|
// Helper: check if an AST node is a known-bool literal
|
|
var is_known_bool = function(node) {
|
|
if (node == null) { return false }
|
|
return node.kind == "true" || node.kind == "false"
|
|
}
|
|
|
|
// Helper: check if an AST node is a known-null literal
|
|
var is_known_null = function(node) {
|
|
if (node == null) { return false }
|
|
return node.kind == "null"
|
|
}
|
|
|
|
// Slot-type tracking helpers
|
|
var slot_is_num = function(slot) {
|
|
var t = s_slot_types[text(slot)]
|
|
return t == "num" || t == "int"
|
|
}
|
|
|
|
var slot_is_text = function(slot) {
|
|
return s_slot_types[text(slot)] == "text"
|
|
}
|
|
|
|
var mark_slot = function(slot, typ) {
|
|
s_slot_types[text(slot)] = typ
|
|
}
|
|
|
|
var propagate_slot = function(dest, src) {
|
|
s_slot_types[text(dest)] = s_slot_types[text(src)]
|
|
}
|
|
|
|
// emit_add_decomposed: emit type-dispatched add (text → concat, num → add)
|
|
// reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure
|
|
var emit_add_decomposed = function() {
|
|
var left_is_num = is_known_number(_bp_ln) || slot_is_num(_bp_left)
|
|
var left_is_text = is_known_text(_bp_ln) || slot_is_text(_bp_left)
|
|
var right_is_num = is_known_number(_bp_rn) || slot_is_num(_bp_right)
|
|
var right_is_text = is_known_text(_bp_rn) || slot_is_text(_bp_right)
|
|
|
|
// Both known text → concat
|
|
if (left_is_text && right_is_text) {
|
|
emit_3("concat", _bp_dest, _bp_left, _bp_right)
|
|
mark_slot(_bp_dest, "text")
|
|
return null
|
|
}
|
|
// Both known number → add
|
|
if (left_is_num && right_is_num) {
|
|
emit_3("add", _bp_dest, _bp_left, _bp_right)
|
|
mark_slot(_bp_dest, "num")
|
|
return null
|
|
}
|
|
// One known number, other unknown → emit_numeric_binop (guard on unknown side)
|
|
if (left_is_num || right_is_num) {
|
|
emit_numeric_binop("add")
|
|
mark_slot(_bp_dest, "num")
|
|
return null
|
|
}
|
|
// Unknown types: emit full dispatch
|
|
var t0 = alloc_slot()
|
|
var t1 = alloc_slot()
|
|
var done = gen_label("add_done")
|
|
var check_num = gen_label("add_cn")
|
|
|
|
// Check text path first (since add doubles as concat)
|
|
emit_2("is_text", t0, _bp_left)
|
|
emit_jump_cond("jump_false", t0, check_num)
|
|
emit_2("is_text", t1, _bp_right)
|
|
emit_jump_cond("jump_false", t1, check_num)
|
|
emit_3("concat", _bp_dest, _bp_left, _bp_right)
|
|
emit_jump(done)
|
|
|
|
// Numeric path
|
|
var err = gen_label("add_err")
|
|
emit_label(check_num)
|
|
emit_2("is_num", t0, _bp_left)
|
|
emit_jump_cond("jump_false", t0, err)
|
|
emit_2("is_num", t1, _bp_right)
|
|
emit_jump_cond("jump_false", t1, err)
|
|
emit_3("add", _bp_dest, _bp_left, _bp_right)
|
|
emit_jump(done)
|
|
|
|
emit_label(err)
|
|
emit_log_error("cannot apply '+': operands must both be text or both be numbers")
|
|
emit_0("disrupt")
|
|
emit_label(done)
|
|
return null
|
|
}
|
|
|
|
// emit_numeric_binop: emit type-guarded numeric binary op
|
|
// reads _bp_dest, _bp_left, _bp_right, _bp_ln, _bp_rn from closure
|
|
var emit_numeric_binop = function(op_str) {
|
|
var left_known = is_known_number(_bp_ln) || slot_is_num(_bp_left)
|
|
var right_known = is_known_number(_bp_rn) || slot_is_num(_bp_right)
|
|
var t0 = null
|
|
var done = null
|
|
if (left_known && right_known) {
|
|
emit_3(op_str, _bp_dest, _bp_left, _bp_right)
|
|
mark_slot(_bp_dest, "num")
|
|
return null
|
|
}
|
|
if (s_num_err_label == null) {
|
|
s_num_err_label = gen_label("num_err")
|
|
}
|
|
t0 = alloc_slot()
|
|
if (!left_known) {
|
|
emit_2("is_num", t0, _bp_left)
|
|
emit_jump_cond("jump_false", t0, s_num_err_label)
|
|
mark_slot(_bp_left, "num")
|
|
}
|
|
if (!right_known) {
|
|
emit_2("is_num", t0, _bp_right)
|
|
emit_jump_cond("jump_false", t0, s_num_err_label)
|
|
mark_slot(_bp_right, "num")
|
|
}
|
|
emit_3(op_str, _bp_dest, _bp_left, _bp_right)
|
|
if (!s_num_err_emitted) {
|
|
done = gen_label("num_done")
|
|
emit_jump(done)
|
|
emit_label(s_num_err_label)
|
|
emit_log_error("operands must be numbers")
|
|
emit_0("disrupt")
|
|
emit_label(done)
|
|
s_num_err_emitted = true
|
|
}
|
|
mark_slot(_bp_dest, "num")
|
|
return null
|
|
}
|
|
|
|
// emit_eq_decomposed: VM eq handles all types (int fast path, text memcmp, identity, mixed→false)
|
|
var emit_eq_decomposed = function() {
|
|
emit_3("eq", _bp_dest, _bp_left, _bp_right)
|
|
return null
|
|
}
|
|
|
|
// emit_ne_decomposed: VM ne handles all types (int fast path, text memcmp, identity, mixed→true)
|
|
var emit_ne_decomposed = function() {
|
|
emit_3("ne", _bp_dest, _bp_left, _bp_right)
|
|
return null
|
|
}
|
|
|
|
// emit_relational: VM lt/le/gt/ge handle numbers and text, disrupt on mismatch
|
|
var emit_relational = function(op_str) {
|
|
emit_3(op_str, _bp_dest, _bp_left, _bp_right)
|
|
return null
|
|
}
|
|
|
|
// emit_neg_decomposed: emit type-guarded negate
|
|
var emit_neg_decomposed = function(dest, src, src_node) {
|
|
var t0 = null
|
|
var done = null
|
|
if (is_known_number(src_node) || slot_is_num(src)) {
|
|
emit_2("negate", dest, src)
|
|
mark_slot(dest, "num")
|
|
return null
|
|
}
|
|
if (s_num_err_label == null) {
|
|
s_num_err_label = gen_label("num_err")
|
|
}
|
|
t0 = alloc_slot()
|
|
emit_2("is_num", t0, src)
|
|
emit_jump_cond("jump_false", t0, s_num_err_label)
|
|
mark_slot(src, "num")
|
|
emit_2("negate", dest, src)
|
|
if (!s_num_err_emitted) {
|
|
done = gen_label("num_done")
|
|
emit_jump(done)
|
|
emit_label(s_num_err_label)
|
|
emit_log_error("operands must be numbers")
|
|
emit_0("disrupt")
|
|
emit_label(done)
|
|
s_num_err_emitted = true
|
|
}
|
|
mark_slot(dest, "num")
|
|
return null
|
|
}
|
|
|
|
// Central router: maps op string to decomposition helper
|
|
// Sets _bp_* closure vars then calls helper with reduced args
|
|
var emit_binop = function(op_str, dest, left, right) {
|
|
_bp_dest = dest
|
|
_bp_left = left
|
|
_bp_right = right
|
|
_bp_op_sym = binop_sym[op_str] || op_str
|
|
if (op_str == "add") {
|
|
emit_add_decomposed()
|
|
} else if (op_str == "eq") {
|
|
emit_eq_decomposed()
|
|
} else if (op_str == "ne") {
|
|
emit_ne_decomposed()
|
|
} else if (op_str == "lt" || op_str == "le" || op_str == "gt" || op_str == "ge") {
|
|
emit_relational(op_str)
|
|
} else if (op_str == "subtract" || op_str == "multiply" ||
|
|
op_str == "divide" || op_str == "modulo" || op_str == "remainder" ||
|
|
op_str == "pow") {
|
|
emit_numeric_binop(op_str)
|
|
} else {
|
|
// Passthrough for bitwise, in, etc.
|
|
emit_3(op_str, dest, left, right)
|
|
}
|
|
return null
|
|
}
|
|
|
|
var emit_get_prop = function(dest, obj, prop) {
|
|
add_instr(["load_field", dest, obj, prop])
|
|
}
|
|
|
|
var emit_set_prop = function(obj, prop, val) {
|
|
add_instr(["store_field", obj, val, prop])
|
|
}
|
|
|
|
var emit_get_elem = function(dest, obj, idx, access_kind) {
|
|
if (access_kind == "index") {
|
|
emit_3("load_index", dest, obj, idx)
|
|
} else if (access_kind == "field") {
|
|
emit_3("load_field", dest, obj, idx)
|
|
} else {
|
|
emit_3("load_dynamic", dest, obj, idx)
|
|
}
|
|
}
|
|
|
|
var emit_set_elem = function(obj, idx, val, access_kind) {
|
|
if (access_kind == "index") {
|
|
emit_3("store_index", obj, val, idx)
|
|
} else if (access_kind == "field") {
|
|
emit_3("store_field", obj, val, idx)
|
|
} else {
|
|
emit_3("store_dynamic", obj, val, idx)
|
|
}
|
|
}
|
|
|
|
var emit_call = function(dest, func_slot, args) {
|
|
var argc = length(args)
|
|
var frame_slot = alloc_slot()
|
|
emit_3("frame", frame_slot, func_slot, argc)
|
|
var arg_idx = 1
|
|
var _i = 0
|
|
while (_i < argc) {
|
|
emit_3("setarg", frame_slot, arg_idx, args[_i])
|
|
arg_idx = arg_idx + 1
|
|
_i = _i + 1
|
|
}
|
|
emit_2("invoke", frame_slot, dest)
|
|
}
|
|
|
|
var emit_call_method = function(dest, obj, prop, args) {
|
|
var argc = length(args)
|
|
var check = alloc_slot()
|
|
var record_path = gen_label("record_path")
|
|
var done_label = gen_label("call_done")
|
|
var _i = 0
|
|
var arg_idx = 0
|
|
|
|
// Check if obj is a proxy function (arity 2)
|
|
emit_2("is_proxy", check, obj)
|
|
emit_jump_cond("jump_false", check, record_path)
|
|
|
|
// Function proxy path: call obj(prop_name, [args...]) with this=null
|
|
var null_slot = alloc_slot()
|
|
emit_const_null(null_slot)
|
|
var name_str = alloc_slot()
|
|
emit_const_str(name_str, prop)
|
|
var args_arr = alloc_slot()
|
|
add_instr(["array", args_arr, 0])
|
|
_i = 0
|
|
while (_i < argc) {
|
|
emit_2("push", args_arr, args[_i])
|
|
_i = _i + 1
|
|
}
|
|
var pf = alloc_slot()
|
|
emit_3("frame", pf, obj, 2)
|
|
emit_3("setarg", pf, 0, null_slot)
|
|
emit_3("setarg", pf, 1, name_str)
|
|
emit_3("setarg", pf, 2, args_arr)
|
|
emit_2("invoke", pf, dest)
|
|
emit_jump(done_label)
|
|
|
|
// Record path: load method, call with this=obj
|
|
emit_label(record_path)
|
|
var method_slot = alloc_slot()
|
|
add_instr(["load_field", method_slot, obj, prop])
|
|
var frame_slot = alloc_slot()
|
|
emit_3("frame", frame_slot, method_slot, argc)
|
|
emit_3("setarg", frame_slot, 0, obj)
|
|
arg_idx = 1
|
|
_i = 0
|
|
while (_i < argc) {
|
|
emit_3("setarg", frame_slot, arg_idx, args[_i])
|
|
arg_idx = arg_idx + 1
|
|
_i = _i + 1
|
|
}
|
|
emit_2("invoke", frame_slot, dest)
|
|
|
|
emit_label(done_label)
|
|
}
|
|
|
|
var emit_call_method_dyn = function(dest, obj, key_reg, args) {
|
|
var argc = length(args)
|
|
var check = alloc_slot()
|
|
var record_path = gen_label("dyn_record_path")
|
|
var done_label = gen_label("dyn_call_done")
|
|
var _i = 0
|
|
var arg_idx = 0
|
|
|
|
// Check if obj is a proxy function (arity 2)
|
|
emit_2("is_proxy", check, obj)
|
|
emit_jump_cond("jump_false", check, record_path)
|
|
|
|
// Function proxy path (dynamic key): must be text
|
|
var key_ok = alloc_slot()
|
|
var error_path = gen_label("dyn_error")
|
|
emit_2("is_text", key_ok, key_reg)
|
|
emit_jump_cond("jump_false", key_ok, error_path)
|
|
var null_slot = alloc_slot()
|
|
emit_const_null(null_slot)
|
|
var args_arr = alloc_slot()
|
|
add_instr(["array", args_arr, 0])
|
|
_i = 0
|
|
while (_i < argc) {
|
|
emit_2("push", args_arr, args[_i])
|
|
_i = _i + 1
|
|
}
|
|
var pf = alloc_slot()
|
|
emit_3("frame", pf, obj, 2)
|
|
emit_3("setarg", pf, 0, null_slot)
|
|
emit_3("setarg", pf, 1, key_reg)
|
|
emit_3("setarg", pf, 2, args_arr)
|
|
emit_2("invoke", pf, dest)
|
|
emit_jump(done_label)
|
|
|
|
// Error path: non-text key on function disrupts
|
|
emit_label(error_path)
|
|
emit_log_error("cannot access: key must be text")
|
|
emit_0("disrupt")
|
|
emit_jump(done_label)
|
|
|
|
// Record path: load method dynamically, call with this=obj
|
|
emit_label(record_path)
|
|
var method_slot = alloc_slot()
|
|
emit_3("load_dynamic", method_slot, obj, key_reg)
|
|
var frame_slot = alloc_slot()
|
|
emit_3("frame", frame_slot, method_slot, argc)
|
|
emit_3("setarg", frame_slot, 0, obj)
|
|
arg_idx = 1
|
|
_i = 0
|
|
while (_i < argc) {
|
|
emit_3("setarg", frame_slot, arg_idx, args[_i])
|
|
arg_idx = arg_idx + 1
|
|
_i = _i + 1
|
|
}
|
|
emit_2("invoke", frame_slot, dest)
|
|
|
|
emit_label(done_label)
|
|
}
|
|
|
|
var emit_go_call = function(func_slot, args) {
|
|
var argc = length(args)
|
|
var frame_slot = alloc_slot()
|
|
emit_3("goframe", frame_slot, func_slot, argc)
|
|
var null_slot = alloc_slot()
|
|
emit_1("null", null_slot)
|
|
emit_3("setarg", frame_slot, 0, null_slot)
|
|
var arg_idx = 1
|
|
var _i = 0
|
|
while (_i < argc) {
|
|
emit_3("setarg", frame_slot, arg_idx, args[_i])
|
|
arg_idx = arg_idx + 1
|
|
_i = _i + 1
|
|
}
|
|
emit_1("goinvoke", frame_slot)
|
|
}
|
|
|
|
var emit_go_call_method = function(obj, prop, args) {
|
|
var func_slot = alloc_slot()
|
|
emit_get_prop(func_slot, obj, prop)
|
|
var argc = length(args)
|
|
var frame_slot = alloc_slot()
|
|
emit_3("goframe", frame_slot, func_slot, argc)
|
|
emit_3("setarg", frame_slot, 0, obj)
|
|
var arg_idx = 1
|
|
var _i = 0
|
|
while (_i < argc) {
|
|
emit_3("setarg", frame_slot, arg_idx, args[_i])
|
|
arg_idx = arg_idx + 1
|
|
_i = _i + 1
|
|
}
|
|
emit_1("goinvoke", frame_slot)
|
|
}
|
|
|
|
// Pre-load intrinsics from AST intrinsics array
|
|
var load_intrinsics = function(intrinsics) {
|
|
if (intrinsics == null) {
|
|
return null
|
|
}
|
|
var _i = 0
|
|
var name = null
|
|
var slot = 0
|
|
var lit = null
|
|
while (_i < length(intrinsics)) {
|
|
name = intrinsics[_i]
|
|
if (name == null || length(s_intrinsic_cache) >= 64) {
|
|
_i = _i + 1
|
|
continue
|
|
}
|
|
if (find_intrinsic(name) >= 0) {
|
|
_i = _i + 1
|
|
continue
|
|
}
|
|
slot = alloc_slot()
|
|
lit = {kind: "name", name: name, make: "intrinsic"}
|
|
add_instr(["access", slot, lit])
|
|
push(s_intrinsic_cache, {name: name, slot: slot})
|
|
_i = _i + 1
|
|
}
|
|
}
|
|
|
|
// Intrinsic numeric helpers:
|
|
// preserve native intrinsic behavior for bad argument types by returning null.
|
|
var emit_intrinsic_num_unary = function(op, arg_slot) {
|
|
var dest = alloc_slot()
|
|
var t = alloc_slot()
|
|
var bad = gen_label(op + "_arg_bad")
|
|
var done = gen_label(op + "_arg_done")
|
|
emit_2("is_num", t, arg_slot)
|
|
emit_jump_cond("jump_false", t, bad)
|
|
emit_2(op, dest, arg_slot)
|
|
emit_jump(done)
|
|
emit_label(bad)
|
|
emit_1("null", dest)
|
|
emit_label(done)
|
|
return dest
|
|
}
|
|
|
|
var emit_intrinsic_num_binary = function(op, left_slot, right_slot) {
|
|
var dest = alloc_slot()
|
|
var t0 = alloc_slot()
|
|
var t1 = alloc_slot()
|
|
var bad = gen_label(op + "_arg_bad")
|
|
var done = gen_label(op + "_arg_done")
|
|
emit_2("is_num", t0, left_slot)
|
|
emit_jump_cond("jump_false", t0, bad)
|
|
emit_2("is_num", t1, right_slot)
|
|
emit_jump_cond("jump_false", t1, bad)
|
|
emit_3(op, dest, left_slot, right_slot)
|
|
emit_jump(done)
|
|
emit_label(bad)
|
|
emit_1("null", dest)
|
|
emit_label(done)
|
|
return dest
|
|
}
|
|
|
|
var emit_intrinsic_num_place = function(op, value_slot, place_slot) {
|
|
var dest = alloc_slot()
|
|
var t = alloc_slot()
|
|
var bad = gen_label(op + "_arg_bad")
|
|
var done = gen_label(op + "_arg_done")
|
|
emit_2("is_num", t, value_slot)
|
|
emit_jump_cond("jump_false", t, bad)
|
|
emit_3(op, dest, value_slot, place_slot)
|
|
emit_jump(done)
|
|
emit_label(bad)
|
|
emit_1("null", dest)
|
|
emit_label(done)
|
|
return dest
|
|
}
|
|
|
|
// Scan scope record for variable declarations
|
|
// Closure locals are assigned first so returned frames can be shortened
|
|
var scan_scope = function() {
|
|
var scope = find_scope_record(s_function_nr)
|
|
if (scope == null) {
|
|
return null
|
|
}
|
|
var keys = sort(array(scope))
|
|
var _i = 0
|
|
var name = null
|
|
var v = null
|
|
var make = null
|
|
var is_const = false
|
|
var slot = 0
|
|
|
|
// Pass 1: closure locals first
|
|
_i = 0
|
|
while (_i < length(keys)) {
|
|
name = keys[_i]
|
|
if (name == "function_nr" || name == "nr_close_slots") {
|
|
_i = _i + 1
|
|
continue
|
|
}
|
|
v = scope[name]
|
|
make = v.make
|
|
if (make == null || make == "input") {
|
|
_i = _i + 1
|
|
continue
|
|
}
|
|
if (v.closure == true && find_var(name) < 0) {
|
|
is_const = (make == "def" || make == "function")
|
|
slot = 1 + s_nr_args + s_nr_local_slots
|
|
s_nr_local_slots = s_nr_local_slots + 1
|
|
s_nr_close_slots = s_nr_close_slots + 1
|
|
add_var(name, slot, is_const)
|
|
s_vars[length(s_vars) - 1].is_closure = true
|
|
}
|
|
_i = _i + 1
|
|
}
|
|
|
|
// Pass 2: non-closure locals
|
|
_i = 0
|
|
while (_i < length(keys)) {
|
|
name = keys[_i]
|
|
if (name == "function_nr" || name == "nr_close_slots") {
|
|
_i = _i + 1
|
|
continue
|
|
}
|
|
v = scope[name]
|
|
make = v.make
|
|
if (make == null || make == "input") {
|
|
_i = _i + 1
|
|
continue
|
|
}
|
|
if (v.closure != true && find_var(name) < 0) {
|
|
is_const = (make == "def" || make == "function")
|
|
slot = 1 + s_nr_args + s_nr_local_slots
|
|
s_nr_local_slots = s_nr_local_slots + 1
|
|
add_var(name, slot, is_const)
|
|
}
|
|
_i = _i + 1
|
|
}
|
|
}
|
|
|
|
// Find variable in a parent's saved state
|
|
var find_var_in_saved = function(saved, name) {
|
|
var _i = 0
|
|
while (_i < length(saved.vars)) {
|
|
if (saved.vars[_i].name == name) {
|
|
return saved.vars[_i].slot
|
|
}
|
|
_i = _i + 1
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// --- Inline expansion toggle flags ---
|
|
var inline_arrfor = true
|
|
var inline_filter = true
|
|
var inline_every = true
|
|
var inline_some = true
|
|
var inline_reduce = true
|
|
var inline_map = false
|
|
var inline_find = true
|
|
|
|
// --- Helper: emit arity-dispatched callback invocation ---
|
|
// ctx = {fn, fn_arity, result, null_s, frame, zero, one, az, ao, prefix,
|
|
// known_arity (optional — compile-time arity of callback literal)}
|
|
// args = [slot_for_arg1, slot_for_arg2] — data args (not this)
|
|
// max_args = 1 or 2 — how many data args to support
|
|
var emit_arity_call = function(ctx, args, max_args) {
|
|
var call_one = null
|
|
var call_two = null
|
|
var call_done = null
|
|
var ka = ctx.known_arity
|
|
// When callback arity is known at compile time, emit only the matching
|
|
// call path. This avoids dead branches where parameters are nulled,
|
|
// which confuse the type checker after inlining (e.g. push on null).
|
|
if (ka != null) {
|
|
if (ka >= max_args) {
|
|
ka = max_args
|
|
}
|
|
if (ka == 0) {
|
|
emit_3("frame", ctx.frame, ctx.fn, 0)
|
|
emit_3("setarg", ctx.frame, 0, ctx.null_s)
|
|
emit_2("invoke", ctx.frame, ctx.result)
|
|
} else if (ka == 1 || max_args < 2) {
|
|
emit_3("frame", ctx.frame, ctx.fn, 1)
|
|
emit_3("setarg", ctx.frame, 0, ctx.null_s)
|
|
emit_3("setarg", ctx.frame, 1, args[0])
|
|
emit_2("invoke", ctx.frame, ctx.result)
|
|
} else {
|
|
emit_3("frame", ctx.frame, ctx.fn, 2)
|
|
emit_3("setarg", ctx.frame, 0, ctx.null_s)
|
|
emit_3("setarg", ctx.frame, 1, args[0])
|
|
emit_3("setarg", ctx.frame, 2, args[1])
|
|
emit_2("invoke", ctx.frame, ctx.result)
|
|
}
|
|
return null
|
|
}
|
|
call_one = gen_label(ctx.prefix + "_c1")
|
|
call_two = gen_label(ctx.prefix + "_c2")
|
|
call_done = gen_label(ctx.prefix + "_cd")
|
|
emit_3("eq", ctx.az, ctx.fn_arity, ctx.zero)
|
|
emit_jump_cond("jump_false", ctx.az, call_one)
|
|
emit_3("frame", ctx.frame, ctx.fn, 0)
|
|
emit_3("setarg", ctx.frame, 0, ctx.null_s)
|
|
emit_2("invoke", ctx.frame, ctx.result)
|
|
emit_jump(call_done)
|
|
emit_label(call_one)
|
|
if (max_args >= 2) {
|
|
emit_3("eq", ctx.ao, ctx.fn_arity, ctx.one)
|
|
emit_jump_cond("jump_false", ctx.ao, call_two)
|
|
}
|
|
emit_3("frame", ctx.frame, ctx.fn, 1)
|
|
emit_3("setarg", ctx.frame, 0, ctx.null_s)
|
|
emit_3("setarg", ctx.frame, 1, args[0])
|
|
emit_2("invoke", ctx.frame, ctx.result)
|
|
if (max_args < 2) {
|
|
emit_label(call_done)
|
|
return null
|
|
}
|
|
emit_jump(call_done)
|
|
emit_label(call_two)
|
|
emit_3("frame", ctx.frame, ctx.fn, 2)
|
|
emit_3("setarg", ctx.frame, 0, ctx.null_s)
|
|
emit_3("setarg", ctx.frame, 1, args[0])
|
|
emit_3("setarg", ctx.frame, 2, args[1])
|
|
emit_2("invoke", ctx.frame, ctx.result)
|
|
emit_label(call_done)
|
|
return null
|
|
}
|
|
|
|
// --- Helper: forward loop scaffolding ---
|
|
// L = {arr, len, i, check, item, one, loop_label, done_label}
|
|
// body_fn(L) — called between element load and increment
|
|
var emit_forward_loop = function(L, body_fn) {
|
|
emit_2("int", L.i, 0)
|
|
emit_label(L.loop_label)
|
|
emit_3("lt", L.check, L.i, L.len)
|
|
emit_jump_cond("jump_false", L.check, L.done_label)
|
|
emit_3("load_index", L.item, L.arr, L.i)
|
|
body_fn(L)
|
|
emit_3("add", L.i, L.i, L.one)
|
|
emit_jump(L.loop_label)
|
|
emit_label(L.done_label)
|
|
return null
|
|
}
|
|
|
|
// --- Helper: reverse loop scaffolding ---
|
|
var emit_reverse_loop = function(L, body_fn) {
|
|
var zero = alloc_slot()
|
|
emit_2("int", zero, 0)
|
|
emit_3("subtract", L.i, L.len, L.one)
|
|
emit_label(L.loop_label)
|
|
emit_3("ge", L.check, L.i, zero)
|
|
emit_jump_cond("jump_false", L.check, L.done_label)
|
|
emit_3("load_index", L.item, L.arr, L.i)
|
|
body_fn(L)
|
|
emit_3("subtract", L.i, L.i, L.one)
|
|
emit_jump(L.loop_label)
|
|
emit_label(L.done_label)
|
|
return null
|
|
}
|
|
|
|
// --- Helper: emit a reduce loop body ---
|
|
// r = {acc, i, arr, fn, len, fn_arity, known_arity}; emits loop updating acc in-place.
|
|
// Caller must emit the done_label after calling this.
|
|
var emit_reduce_loop = function(r, forward, done_label) {
|
|
var acc = r.acc
|
|
var i = r.i
|
|
var arr_slot = r.arr
|
|
var fn_slot = r.fn
|
|
var len = r.len
|
|
var fn_arity = r.fn_arity
|
|
var check = alloc_slot()
|
|
var item = alloc_slot()
|
|
var null_s = alloc_slot()
|
|
var one = alloc_slot()
|
|
var zero = alloc_slot()
|
|
var az = alloc_slot()
|
|
var ao = alloc_slot()
|
|
var f = alloc_slot()
|
|
var loop_label = gen_label("reduce_loop")
|
|
var ctx = {fn: fn_slot, fn_arity: fn_arity, result: acc, null_s: null_s,
|
|
frame: f, zero: zero, one: one, az: az, ao: ao, prefix: "reduce",
|
|
known_arity: r.known_arity}
|
|
emit_2("int", one, 1)
|
|
emit_2("int", zero, 0)
|
|
emit_1("null", null_s)
|
|
emit_label(loop_label)
|
|
if (forward) {
|
|
emit_3("lt", check, i, len)
|
|
} else {
|
|
emit_3("ge", check, i, zero)
|
|
}
|
|
emit_jump_cond("jump_false", check, done_label)
|
|
emit_3("load_index", item, arr_slot, i)
|
|
emit_arity_call(ctx, [acc, item], 2)
|
|
if (forward) {
|
|
emit_3("add", i, i, one)
|
|
} else {
|
|
emit_3("subtract", i, i, one)
|
|
}
|
|
emit_jump(loop_label)
|
|
}
|
|
|
|
// --- Inline expansion: arrfor(arr, fn[, rev[, exit]]) ---
|
|
var expand_inline_arrfor = function(dest, args, nargs) {
|
|
var arr_slot = args.arr
|
|
var fn_slot = args.fn
|
|
var len = alloc_slot()
|
|
var i = alloc_slot()
|
|
var check = alloc_slot()
|
|
var item = alloc_slot()
|
|
var fn_arity = alloc_slot()
|
|
var az = alloc_slot()
|
|
var ao = alloc_slot()
|
|
var null_s = alloc_slot()
|
|
var zero = alloc_slot()
|
|
var one = alloc_slot()
|
|
var f = alloc_slot()
|
|
var val = alloc_slot()
|
|
var eq_check = alloc_slot()
|
|
var early_exit = gen_label("arrfor_exit")
|
|
var done_final = gen_label("arrfor_final")
|
|
var rev_label = gen_label("arrfor_rev")
|
|
var final_label = gen_label("arrfor_fwd_done")
|
|
var fwd_L = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
|
|
loop_label: gen_label("arrfor_fwd"), done_label: gen_label("arrfor_fwd_d")}
|
|
var rev_L = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
|
|
loop_label: gen_label("arrfor_rev_l"), done_label: gen_label("arrfor_rev_d")}
|
|
var ctx = {fn: fn_slot, fn_arity: fn_arity, result: val, null_s: null_s,
|
|
frame: f, zero: zero, one: one, az: az, ao: ao, prefix: "arrfor"}
|
|
var body_fn = function(L) {
|
|
emit_arity_call(ctx, [L.item, L.i], 2)
|
|
if (nargs >= 4 && args.exit >= 0) {
|
|
emit_3("eq", eq_check, val, args.exit)
|
|
emit_jump_cond("jump_true", eq_check, early_exit)
|
|
}
|
|
return null
|
|
}
|
|
emit_2("length", len, arr_slot)
|
|
emit_2("int", zero, 0)
|
|
emit_2("int", one, 1)
|
|
emit_1("null", null_s)
|
|
emit_2("length", fn_arity, fn_slot)
|
|
if (nargs <= 2) {
|
|
emit_forward_loop(fwd_L, body_fn)
|
|
} else {
|
|
emit_jump_cond("wary_true", args.rev, rev_label)
|
|
emit_forward_loop(fwd_L, body_fn)
|
|
emit_jump(final_label)
|
|
emit_label(rev_label)
|
|
emit_reverse_loop(rev_L, body_fn)
|
|
emit_label(final_label)
|
|
}
|
|
emit_1("null", dest)
|
|
emit_jump(done_final)
|
|
if (nargs >= 4 && args.exit >= 0) {
|
|
emit_label(early_exit)
|
|
emit_2("move", dest, val)
|
|
}
|
|
emit_label(done_final)
|
|
return dest
|
|
}
|
|
|
|
// --- Inline expansion: every(arr, fn) ---
|
|
var expand_inline_every = function(dest, arr_slot, fn_slot) {
|
|
var len = alloc_slot()
|
|
var i = alloc_slot()
|
|
var check = alloc_slot()
|
|
var item = alloc_slot()
|
|
var fn_arity = alloc_slot()
|
|
var arity_is_zero = alloc_slot()
|
|
var null_s = alloc_slot()
|
|
var zero = alloc_slot()
|
|
var one = alloc_slot()
|
|
var f = alloc_slot()
|
|
var val = alloc_slot()
|
|
var loop_label = gen_label("every_loop")
|
|
var call_one_label = gen_label("every_call_one")
|
|
var call_done_label = gen_label("every_call_done")
|
|
var ret_true = gen_label("every_true")
|
|
var ret_false = gen_label("every_false")
|
|
var done_label = gen_label("every_done")
|
|
emit_2("length", len, arr_slot)
|
|
emit_2("int", i, 0)
|
|
emit_2("int", zero, 0)
|
|
emit_2("int", one, 1)
|
|
emit_1("null", null_s)
|
|
emit_2("length", fn_arity, fn_slot)
|
|
emit_label(loop_label)
|
|
emit_3("lt", check, i, len)
|
|
emit_jump_cond("jump_false", check, ret_true)
|
|
emit_3("load_index", item, arr_slot, i)
|
|
emit_3("eq", arity_is_zero, fn_arity, zero)
|
|
emit_jump_cond("jump_false", arity_is_zero, call_one_label)
|
|
emit_3("frame", f, fn_slot, 0)
|
|
emit_3("setarg", f, 0, null_s)
|
|
emit_2("invoke", f, val)
|
|
emit_jump(call_done_label)
|
|
emit_label(call_one_label)
|
|
emit_3("frame", f, fn_slot, 1)
|
|
emit_3("setarg", f, 0, null_s)
|
|
emit_3("setarg", f, 1, item)
|
|
emit_2("invoke", f, val)
|
|
emit_label(call_done_label)
|
|
emit_jump_cond("wary_false", val, ret_false)
|
|
emit_3("add", i, i, one)
|
|
emit_jump(loop_label)
|
|
emit_label(ret_true)
|
|
emit_1("true", dest)
|
|
emit_jump(done_label)
|
|
emit_label(ret_false)
|
|
emit_1("false", dest)
|
|
emit_label(done_label)
|
|
return dest
|
|
}
|
|
|
|
// --- Inline expansion: some(arr, fn) ---
|
|
var expand_inline_some = function(dest, arr_slot, fn_slot) {
|
|
var len = alloc_slot()
|
|
var i = alloc_slot()
|
|
var check = alloc_slot()
|
|
var item = alloc_slot()
|
|
var fn_arity = alloc_slot()
|
|
var arity_is_zero = alloc_slot()
|
|
var null_s = alloc_slot()
|
|
var zero = alloc_slot()
|
|
var one = alloc_slot()
|
|
var f = alloc_slot()
|
|
var val = alloc_slot()
|
|
var loop_label = gen_label("some_loop")
|
|
var call_one_label = gen_label("some_call_one")
|
|
var call_done_label = gen_label("some_call_done")
|
|
var ret_true = gen_label("some_true")
|
|
var ret_false = gen_label("some_false")
|
|
var done_label = gen_label("some_done")
|
|
emit_2("length", len, arr_slot)
|
|
emit_2("int", i, 0)
|
|
emit_2("int", zero, 0)
|
|
emit_2("int", one, 1)
|
|
emit_1("null", null_s)
|
|
emit_2("length", fn_arity, fn_slot)
|
|
emit_label(loop_label)
|
|
emit_3("lt", check, i, len)
|
|
emit_jump_cond("jump_false", check, ret_false)
|
|
emit_3("load_index", item, arr_slot, i)
|
|
emit_3("eq", arity_is_zero, fn_arity, zero)
|
|
emit_jump_cond("jump_false", arity_is_zero, call_one_label)
|
|
emit_3("frame", f, fn_slot, 0)
|
|
emit_3("setarg", f, 0, null_s)
|
|
emit_2("invoke", f, val)
|
|
emit_jump(call_done_label)
|
|
emit_label(call_one_label)
|
|
emit_3("frame", f, fn_slot, 1)
|
|
emit_3("setarg", f, 0, null_s)
|
|
emit_3("setarg", f, 1, item)
|
|
emit_2("invoke", f, val)
|
|
emit_label(call_done_label)
|
|
emit_jump_cond("wary_true", val, ret_true)
|
|
emit_3("add", i, i, one)
|
|
emit_jump(loop_label)
|
|
emit_label(ret_true)
|
|
emit_1("true", dest)
|
|
emit_jump(done_label)
|
|
emit_label(ret_false)
|
|
emit_1("false", dest)
|
|
emit_label(done_label)
|
|
return dest
|
|
}
|
|
|
|
// --- Inline expansion: filter(arr, fn) ---
|
|
var expand_inline_filter = function(dest, arr_slot, fn_slot) {
|
|
var result = alloc_slot()
|
|
var len = alloc_slot()
|
|
var i = alloc_slot()
|
|
var check = alloc_slot()
|
|
var item = alloc_slot()
|
|
var fn_arity = alloc_slot()
|
|
var az = alloc_slot()
|
|
var ao = alloc_slot()
|
|
var null_s = alloc_slot()
|
|
var zero = alloc_slot()
|
|
var one = alloc_slot()
|
|
var f = alloc_slot()
|
|
var val = alloc_slot()
|
|
var skip = gen_label("filter_skip")
|
|
var ctx = {fn: fn_slot, fn_arity: fn_arity, result: val, null_s: null_s,
|
|
frame: f, zero: zero, one: one, az: az, ao: ao, prefix: "filter"}
|
|
var L = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
|
|
loop_label: gen_label("filter_loop"), done_label: gen_label("filter_done")}
|
|
add_instr(["array", result, 0])
|
|
emit_2("length", len, arr_slot)
|
|
emit_2("int", zero, 0)
|
|
emit_2("int", one, 1)
|
|
emit_1("null", null_s)
|
|
emit_2("length", fn_arity, fn_slot)
|
|
emit_forward_loop(L, function(L) {
|
|
emit_arity_call(ctx, [L.item, L.i], 2)
|
|
emit_jump_cond("wary_false", val, skip)
|
|
emit_2("push", result, L.item)
|
|
emit_label(skip)
|
|
return null
|
|
})
|
|
emit_2("move", dest, result)
|
|
return dest
|
|
}
|
|
|
|
// --- Inline expansion: find(arr, target[, rev[, from]]) ---
|
|
var expand_inline_find = function(dest, args, nargs) {
|
|
var arr_slot = args.arr
|
|
var target = args.target
|
|
var len = alloc_slot()
|
|
var i = alloc_slot()
|
|
var check = alloc_slot()
|
|
var item = alloc_slot()
|
|
var fn_arity = alloc_slot()
|
|
var az = alloc_slot()
|
|
var ao = alloc_slot()
|
|
var null_s = alloc_slot()
|
|
var zero = alloc_slot()
|
|
var one = alloc_slot()
|
|
var f = alloc_slot()
|
|
var val = alloc_slot()
|
|
var is_fn = alloc_slot()
|
|
var eq_check = alloc_slot()
|
|
var fn_mode_label = gen_label("find_fn")
|
|
var found_label = gen_label("find_found")
|
|
var not_found_label = gen_label("find_nf")
|
|
var final_label = gen_label("find_final")
|
|
var vrev = gen_label("find_vrev")
|
|
var vdone = gen_label("find_vdone")
|
|
var frev = gen_label("find_frev")
|
|
var fdone = gen_label("find_fdone")
|
|
var vL = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
|
|
loop_label: gen_label("find_vl"), done_label: gen_label("find_vd")}
|
|
var vrL = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
|
|
loop_label: gen_label("find_vrl"), done_label: gen_label("find_vrd")}
|
|
var fL = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
|
|
loop_label: gen_label("find_fl"), done_label: gen_label("find_fd")}
|
|
var ffL = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
|
|
loop_label: gen_label("find_ffl"), done_label: gen_label("find_ffd")}
|
|
var frL = {arr: arr_slot, len: len, i: i, check: check, item: item, one: one,
|
|
loop_label: gen_label("find_frl"), done_label: gen_label("find_frd")}
|
|
var ctx = {fn: target, fn_arity: fn_arity, result: val, null_s: null_s,
|
|
frame: f, zero: zero, one: one, az: az, ao: ao, prefix: "find"}
|
|
var val_body = function(L) {
|
|
emit_3("eq", eq_check, L.item, target)
|
|
emit_jump_cond("jump_true", eq_check, found_label)
|
|
return null
|
|
}
|
|
var fn_body = function(L) {
|
|
emit_arity_call(ctx, [L.item, L.i], 2)
|
|
emit_jump_cond("wary_true", val, found_label)
|
|
return null
|
|
}
|
|
emit_2("length", len, arr_slot)
|
|
emit_2("int", zero, 0)
|
|
emit_2("int", one, 1)
|
|
emit_1("null", null_s)
|
|
emit_2("is_func", is_fn, target)
|
|
emit_jump_cond("jump_true", is_fn, fn_mode_label)
|
|
// === Value mode ===
|
|
if (nargs <= 2) {
|
|
emit_forward_loop(vL, val_body)
|
|
} else {
|
|
emit_jump_cond("wary_true", args.rev, vrev)
|
|
if (nargs >= 4 && args.from >= 0) {
|
|
emit_2("move", i, args.from)
|
|
}
|
|
if (nargs >= 4 && args.from >= 0) {
|
|
emit_label(vL.loop_label)
|
|
emit_3("lt", vL.check, vL.i, vL.len)
|
|
emit_jump_cond("jump_false", vL.check, vL.done_label)
|
|
emit_3("load_index", vL.item, vL.arr, vL.i)
|
|
val_body(vL)
|
|
emit_3("add", vL.i, vL.i, vL.one)
|
|
emit_jump(vL.loop_label)
|
|
emit_label(vL.done_label)
|
|
} else {
|
|
emit_forward_loop(vL, val_body)
|
|
}
|
|
emit_jump(vdone)
|
|
emit_label(vrev)
|
|
emit_reverse_loop(vrL, val_body)
|
|
emit_label(vdone)
|
|
}
|
|
emit_jump(not_found_label)
|
|
// === Function mode ===
|
|
emit_label(fn_mode_label)
|
|
emit_2("length", fn_arity, target)
|
|
if (nargs <= 2) {
|
|
emit_forward_loop(fL, fn_body)
|
|
} else {
|
|
emit_jump_cond("wary_true", args.rev, frev)
|
|
if (nargs >= 4 && args.from >= 0) {
|
|
emit_2("move", i, args.from)
|
|
}
|
|
if (nargs >= 4 && args.from >= 0) {
|
|
emit_label(ffL.loop_label)
|
|
emit_3("lt", ffL.check, ffL.i, ffL.len)
|
|
emit_jump_cond("jump_false", ffL.check, ffL.done_label)
|
|
emit_3("load_index", ffL.item, ffL.arr, ffL.i)
|
|
fn_body(ffL)
|
|
emit_3("add", ffL.i, ffL.i, ffL.one)
|
|
emit_jump(ffL.loop_label)
|
|
emit_label(ffL.done_label)
|
|
} else {
|
|
emit_forward_loop(ffL, fn_body)
|
|
}
|
|
emit_jump(fdone)
|
|
emit_label(frev)
|
|
emit_reverse_loop(frL, fn_body)
|
|
emit_label(fdone)
|
|
}
|
|
emit_label(not_found_label)
|
|
emit_1("null", dest)
|
|
emit_jump(final_label)
|
|
emit_label(found_label)
|
|
emit_2("move", dest, i)
|
|
emit_label(final_label)
|
|
return dest
|
|
}
|
|
|
|
// --- Inline expansion: array(arr, fn) → map ---
|
|
var expand_inline_map = function(dest, arr_slot, fn_slot) {
|
|
var result = alloc_slot()
|
|
var len = alloc_slot()
|
|
var i = alloc_slot()
|
|
var check = alloc_slot()
|
|
var item = alloc_slot()
|
|
var fn_arity = alloc_slot()
|
|
var arity_is_zero = alloc_slot()
|
|
var arity_is_one = alloc_slot()
|
|
var null_s = alloc_slot()
|
|
var zero = alloc_slot()
|
|
var one = alloc_slot()
|
|
var f = alloc_slot()
|
|
var val = alloc_slot()
|
|
var loop_label = gen_label("map_loop")
|
|
var call_one_label = gen_label("map_call_one")
|
|
var call_two_label = gen_label("map_call_two")
|
|
var call_done_label = gen_label("map_call_done")
|
|
var done_label = gen_label("map_done")
|
|
add_instr(["array", result, 0])
|
|
emit_2("length", len, arr_slot)
|
|
emit_2("int", i, 0)
|
|
emit_2("int", zero, 0)
|
|
emit_2("int", one, 1)
|
|
emit_1("null", null_s)
|
|
emit_2("length", fn_arity, fn_slot)
|
|
emit_label(loop_label)
|
|
emit_3("lt", check, i, len)
|
|
emit_jump_cond("jump_false", check, done_label)
|
|
emit_3("load_index", item, arr_slot, i)
|
|
emit_3("eq", arity_is_zero, fn_arity, zero)
|
|
emit_jump_cond("jump_false", arity_is_zero, call_one_label)
|
|
emit_3("frame", f, fn_slot, 0)
|
|
emit_3("setarg", f, 0, null_s)
|
|
emit_2("invoke", f, val)
|
|
emit_jump(call_done_label)
|
|
emit_label(call_one_label)
|
|
emit_3("eq", arity_is_one, fn_arity, one)
|
|
emit_jump_cond("jump_false", arity_is_one, call_two_label)
|
|
emit_3("frame", f, fn_slot, 1)
|
|
emit_3("setarg", f, 0, null_s)
|
|
emit_3("setarg", f, 1, item)
|
|
emit_2("invoke", f, val)
|
|
emit_jump(call_done_label)
|
|
emit_label(call_two_label)
|
|
emit_3("frame", f, fn_slot, 2)
|
|
emit_3("setarg", f, 0, null_s)
|
|
emit_3("setarg", f, 1, item)
|
|
emit_3("setarg", f, 2, i)
|
|
emit_2("invoke", f, val)
|
|
emit_label(call_done_label)
|
|
emit_2("push", result, val)
|
|
emit_3("add", i, i, one)
|
|
emit_jump(loop_label)
|
|
emit_label(done_label)
|
|
emit_2("move", dest, result)
|
|
return dest
|
|
}
|
|
|
|
// --- Inline expansion: array(arr, intrinsic_op) → map with direct opcode ---
|
|
var expand_inline_map_intrinsic = function(dest, arr_slot, opcode) {
|
|
var result = alloc_slot()
|
|
var len = alloc_slot()
|
|
var i = alloc_slot()
|
|
var check = alloc_slot()
|
|
var item = alloc_slot()
|
|
var val = alloc_slot()
|
|
var zero = alloc_slot()
|
|
var one = alloc_slot()
|
|
var loop_label = gen_label("mapi_loop")
|
|
var done_label = gen_label("mapi_done")
|
|
add_instr(["array", result, 0])
|
|
emit_2("length", len, arr_slot)
|
|
emit_2("int", i, 0)
|
|
emit_2("int", zero, 0)
|
|
emit_2("int", one, 1)
|
|
emit_label(loop_label)
|
|
emit_3("lt", check, i, len)
|
|
emit_jump_cond("jump_false", check, done_label)
|
|
emit_3("load_index", item, arr_slot, i)
|
|
emit_2(opcode, val, item)
|
|
emit_2("push", result, val)
|
|
emit_3("add", i, i, one)
|
|
emit_jump(loop_label)
|
|
emit_label(done_label)
|
|
emit_2("move", dest, result)
|
|
return dest
|
|
}
|
|
|
|
// --- Inline expansion: reduce(arr, fn[, initial[, reverse]]) ---
|
|
var expand_inline_reduce = function(dest, args, nargs) {
|
|
var arr_slot = args.arr
|
|
var fn_slot = args.fn
|
|
var init_slot = args.init
|
|
var rev_slot = args.rev
|
|
var fn_arity = alloc_slot()
|
|
var len = alloc_slot()
|
|
var acc = alloc_slot()
|
|
var i = alloc_slot()
|
|
var check = alloc_slot()
|
|
var zero = alloc_slot()
|
|
var one = alloc_slot()
|
|
var final_label = gen_label("reduce_final")
|
|
var has_init = null
|
|
var no_init_rev = null
|
|
var init_rev = null
|
|
var null_label = null
|
|
var d1 = null
|
|
var d2 = null
|
|
var d3 = null
|
|
var d4 = null
|
|
var r = null
|
|
emit_2("length", len, arr_slot)
|
|
emit_2("length", fn_arity, fn_slot)
|
|
emit_2("int", zero, 0)
|
|
emit_2("int", one, 1)
|
|
r = {acc: acc, i: i, arr: arr_slot, fn: fn_slot, len: len, fn_arity: fn_arity,
|
|
known_arity: args.fn_known_arity}
|
|
if (nargs == 2) {
|
|
null_label = gen_label("reduce_null")
|
|
d1 = gen_label("reduce_d1")
|
|
emit_3("lt", check, zero, len)
|
|
emit_jump_cond("jump_false", check, null_label)
|
|
emit_3("load_index", acc, arr_slot, zero)
|
|
emit_2("move", i, one)
|
|
emit_reduce_loop(r, true, d1)
|
|
emit_label(d1)
|
|
emit_2("move", dest, acc)
|
|
emit_jump(final_label)
|
|
emit_label(null_label)
|
|
emit_1("null", dest)
|
|
emit_label(final_label)
|
|
} else if (nargs == 3) {
|
|
has_init = gen_label("reduce_has_init")
|
|
null_label = gen_label("reduce_null")
|
|
d1 = gen_label("reduce_d1")
|
|
d2 = gen_label("reduce_d2")
|
|
emit_2("is_null", check, init_slot)
|
|
emit_jump_cond("jump_false", check, has_init)
|
|
// No initial, forward
|
|
emit_3("lt", check, zero, len)
|
|
emit_jump_cond("jump_false", check, null_label)
|
|
emit_3("load_index", acc, arr_slot, zero)
|
|
emit_2("move", i, one)
|
|
emit_reduce_loop(r, true, d1)
|
|
emit_label(d1)
|
|
emit_2("move", dest, acc)
|
|
emit_jump(final_label)
|
|
emit_label(null_label)
|
|
emit_1("null", dest)
|
|
emit_jump(final_label)
|
|
// Has initial, forward
|
|
emit_label(has_init)
|
|
emit_2("move", acc, init_slot)
|
|
emit_2("int", i, 0)
|
|
emit_reduce_loop(r, true, d2)
|
|
emit_label(d2)
|
|
emit_2("move", dest, acc)
|
|
emit_label(final_label)
|
|
} else {
|
|
// nargs == 4: full branching
|
|
has_init = gen_label("reduce_has_init")
|
|
no_init_rev = gen_label("reduce_no_init_rev")
|
|
init_rev = gen_label("reduce_init_rev")
|
|
null_label = gen_label("reduce_null")
|
|
d1 = gen_label("reduce_d1")
|
|
d2 = gen_label("reduce_d2")
|
|
d3 = gen_label("reduce_d3")
|
|
d4 = gen_label("reduce_d4")
|
|
emit_2("is_null", check, init_slot)
|
|
emit_jump_cond("jump_false", check, has_init)
|
|
// No initial
|
|
emit_3("lt", check, zero, len)
|
|
emit_jump_cond("jump_false", check, null_label)
|
|
emit_jump_cond("wary_true", rev_slot, no_init_rev)
|
|
// No initial, forward
|
|
emit_3("load_index", acc, arr_slot, zero)
|
|
emit_2("move", i, one)
|
|
emit_reduce_loop(r, true, d1)
|
|
emit_label(d1)
|
|
emit_2("move", dest, acc)
|
|
emit_jump(final_label)
|
|
// No initial, reverse
|
|
emit_label(no_init_rev)
|
|
emit_3("subtract", i, len, one)
|
|
emit_3("load_index", acc, arr_slot, i)
|
|
emit_3("subtract", i, i, one)
|
|
emit_reduce_loop(r, false, d2)
|
|
emit_label(d2)
|
|
emit_2("move", dest, acc)
|
|
emit_jump(final_label)
|
|
emit_label(null_label)
|
|
emit_1("null", dest)
|
|
emit_jump(final_label)
|
|
// Has initial
|
|
emit_label(has_init)
|
|
emit_jump_cond("wary_true", rev_slot, init_rev)
|
|
// Has initial, forward
|
|
emit_2("move", acc, init_slot)
|
|
emit_2("int", i, 0)
|
|
emit_reduce_loop(r, true, d3)
|
|
emit_label(d3)
|
|
emit_2("move", dest, acc)
|
|
emit_jump(final_label)
|
|
// Has initial, reverse
|
|
emit_label(init_rev)
|
|
emit_2("move", acc, init_slot)
|
|
emit_3("subtract", i, len, one)
|
|
emit_reduce_loop(r, false, d4)
|
|
emit_label(d4)
|
|
emit_2("move", dest, acc)
|
|
emit_label(final_label)
|
|
}
|
|
return dest
|
|
}
|
|
|
|
// Forward declarations via var
|
|
var gen_expr = null
|
|
var gen_statement = null
|
|
var gen_function = null
|
|
|
|
// Emit an access instruction for an intrinsic name
|
|
var emit_access_intrinsic = function(dest, name) {
|
|
var lit = {kind: "name", name: name, make: "intrinsic"}
|
|
add_instr(["access", dest, lit])
|
|
}
|
|
|
|
// Binary expression compilation
|
|
var gen_binary = function(node, target) {
|
|
var kind = node.kind
|
|
var left = node.left
|
|
var right = node.right
|
|
var end_label = null
|
|
var left_slot = 0
|
|
var right_slot = 0
|
|
var dest = 0
|
|
var op = null
|
|
|
|
if (kind == "&&") {
|
|
end_label = gen_label("and_end")
|
|
left_slot = gen_expr(left, -1)
|
|
dest = alloc_slot()
|
|
emit_2("move", dest, left_slot)
|
|
emit_jump_cond("wary_false", dest, end_label)
|
|
right_slot = gen_expr(right, -1)
|
|
emit_2("move", dest, right_slot)
|
|
emit_label(end_label)
|
|
return dest
|
|
}
|
|
|
|
if (kind == "||") {
|
|
end_label = gen_label("or_end")
|
|
left_slot = gen_expr(left, -1)
|
|
dest = alloc_slot()
|
|
emit_2("move", dest, left_slot)
|
|
emit_jump_cond("wary_true", dest, end_label)
|
|
right_slot = gen_expr(right, -1)
|
|
emit_2("move", dest, right_slot)
|
|
emit_label(end_label)
|
|
return dest
|
|
}
|
|
|
|
if (kind == "??") {
|
|
end_label = gen_label("nullish_end")
|
|
left_slot = gen_expr(left, -1)
|
|
dest = alloc_slot()
|
|
emit_2("move", dest, left_slot)
|
|
emit_jump_cond("jump_not_null", dest, end_label)
|
|
right_slot = gen_expr(right, -1)
|
|
emit_2("move", dest, right_slot)
|
|
emit_label(end_label)
|
|
return dest
|
|
}
|
|
|
|
// Comma operator
|
|
if (kind == ",") {
|
|
gen_expr(left, -1)
|
|
return gen_expr(right, -1)
|
|
}
|
|
|
|
// Standard binary ops
|
|
left_slot = gen_expr(left, -1)
|
|
right_slot = gen_expr(right, -1)
|
|
// Use target slot for ops without multi-type dispatch (add has text+num paths).
|
|
// Exception: allow + to write directly to target when target == left_slot
|
|
// (self-assign pattern like s = s + x) since concat/add reads before writing.
|
|
dest = (target >= 0 && (kind != "+" || target == left_slot)) ? target : alloc_slot()
|
|
op = binop_map[kind]
|
|
if (op == null) {
|
|
op = "add"
|
|
}
|
|
_bp_ln = left
|
|
_bp_rn = right
|
|
emit_binop(op, dest, left_slot, right_slot)
|
|
return dest
|
|
}
|
|
|
|
// Compound assignment helper
|
|
// parent_vars is an array of saved states for parent chain lookup
|
|
var parent_states = []
|
|
|
|
var gen_compound_assign = function(node, op) {
|
|
var left = node.left
|
|
var right = node.right
|
|
var left_kind = left.kind
|
|
var name = null
|
|
var level = 0
|
|
var left_slot = 0
|
|
var local = 0
|
|
var _lv = 0
|
|
var pstate = null
|
|
var pslot = 0
|
|
var right_slot = 0
|
|
var dest = 0
|
|
var obj = null
|
|
var prop = null
|
|
var obj_slot = 0
|
|
var old_val = 0
|
|
var idx_expr = null
|
|
var idx_slot = 0
|
|
|
|
if (left_kind == "name") {
|
|
name = left.name
|
|
level = left.level
|
|
if (level == null) {
|
|
level = -1
|
|
}
|
|
left_slot = alloc_slot()
|
|
if (level == 0 || level == -1) {
|
|
local = find_var(name)
|
|
if (local >= 0) {
|
|
emit_2("move", left_slot, local)
|
|
level = 0
|
|
}
|
|
}
|
|
if (level > 0) {
|
|
_lv = level - 1
|
|
pstate = parent_states[length(parent_states) - 1 - _lv]
|
|
pslot = find_var_in_saved(pstate, name)
|
|
emit_3("get", left_slot, pslot, level)
|
|
} else if (level == -1) {
|
|
emit_access_intrinsic(left_slot, name)
|
|
}
|
|
right_slot = gen_expr(right, -1)
|
|
dest = alloc_slot()
|
|
_bp_ln = null
|
|
_bp_rn = right
|
|
emit_binop(op, dest, left_slot, right_slot)
|
|
if (level == 0) {
|
|
local = find_var(name)
|
|
if (local >= 0) {
|
|
emit_2("move", local, dest)
|
|
propagate_slot(local, dest)
|
|
}
|
|
} else if (level > 0) {
|
|
_lv = level - 1
|
|
pstate = parent_states[length(parent_states) - 1 - _lv]
|
|
pslot = find_var_in_saved(pstate, name)
|
|
emit_3("put", dest, pslot, level)
|
|
}
|
|
return dest
|
|
} else if (left_kind == ".") {
|
|
obj = left.left
|
|
prop = left.right
|
|
obj_slot = gen_expr(obj, -1)
|
|
old_val = alloc_slot()
|
|
emit_get_prop(old_val, obj_slot, prop)
|
|
right_slot = gen_expr(right, -1)
|
|
dest = alloc_slot()
|
|
_bp_ln = null
|
|
_bp_rn = right
|
|
emit_binop(op, dest, old_val, right_slot)
|
|
emit_set_prop(obj_slot, prop, dest)
|
|
return dest
|
|
} else if (left_kind == "[") {
|
|
obj = left.left
|
|
idx_expr = left.right
|
|
obj_slot = gen_expr(obj, -1)
|
|
idx_slot = gen_expr(idx_expr, -1)
|
|
old_val = alloc_slot()
|
|
emit_get_elem(old_val, obj_slot, idx_slot, left.access_kind)
|
|
right_slot = gen_expr(right, -1)
|
|
dest = alloc_slot()
|
|
_bp_ln = null
|
|
_bp_rn = right
|
|
emit_binop(op, dest, old_val, right_slot)
|
|
emit_set_elem(obj_slot, idx_slot, dest, left.access_kind)
|
|
return dest
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// Assignment compilation
|
|
var gen_assign = function(node) {
|
|
var kind = node.kind
|
|
var left = node.left
|
|
var right = node.right
|
|
var cop = compound_map[kind]
|
|
var arr_expr = null
|
|
var arr_slot = 0
|
|
var val_slot = 0
|
|
var left_kind = null
|
|
var name = null
|
|
var level = 0
|
|
var slot = 0
|
|
var _lv = 0
|
|
var pstate = null
|
|
var pslot = 0
|
|
var obj = null
|
|
var prop = null
|
|
var obj_slot = 0
|
|
var idx_expr = null
|
|
var idx_slot = 0
|
|
var guard_t = 0
|
|
var guard_err = null
|
|
var guard_done = null
|
|
|
|
if (cop != null) {
|
|
return gen_compound_assign(node, cop)
|
|
}
|
|
|
|
// Push syntax: arr[] = val (guarded)
|
|
if (node.push == true) {
|
|
arr_expr = left.left
|
|
arr_slot = gen_expr(arr_expr, -1)
|
|
val_slot = gen_expr(right, -1)
|
|
guard_t = alloc_slot()
|
|
guard_err = gen_label("push_err")
|
|
guard_done = gen_label("push_done")
|
|
emit_2("is_array", guard_t, arr_slot)
|
|
emit_jump_cond("jump_false", guard_t, guard_err)
|
|
emit_2("push", arr_slot, val_slot)
|
|
emit_jump(guard_done)
|
|
emit_label(guard_err)
|
|
emit_log_error("cannot push: target must be an array")
|
|
emit_0("disrupt")
|
|
emit_label(guard_done)
|
|
return val_slot
|
|
}
|
|
|
|
left_kind = left.kind
|
|
|
|
// For local name assignments, try to write directly to the var's slot
|
|
if (left_kind == "name") {
|
|
name = left.name
|
|
level = left.level
|
|
if (level == null) {
|
|
level = -1
|
|
}
|
|
if (level == 0 || level == -1) {
|
|
slot = find_var(name)
|
|
if (slot >= 0) {
|
|
val_slot = gen_expr(right, slot)
|
|
if (val_slot != slot) {
|
|
emit_2("move", slot, val_slot)
|
|
propagate_slot(slot, val_slot)
|
|
}
|
|
return val_slot
|
|
}
|
|
val_slot = gen_expr(right, -1)
|
|
} else {
|
|
val_slot = gen_expr(right, -1)
|
|
if (level > 0) {
|
|
_lv = level - 1
|
|
pstate = parent_states[length(parent_states) - 1 - _lv]
|
|
pslot = find_var_in_saved(pstate, name)
|
|
emit_3("put", val_slot, pslot, level)
|
|
}
|
|
}
|
|
return val_slot
|
|
}
|
|
|
|
val_slot = gen_expr(right, -1)
|
|
if (left_kind == ".") {
|
|
obj = left.left
|
|
prop = left.right
|
|
obj_slot = gen_expr(obj, -1)
|
|
emit_set_prop(obj_slot, prop, val_slot)
|
|
} else if (left_kind == "[") {
|
|
obj = left.left
|
|
idx_expr = left.right
|
|
obj_slot = gen_expr(obj, -1)
|
|
idx_slot = gen_expr(idx_expr, -1)
|
|
emit_set_elem(obj_slot, idx_slot, val_slot, left.access_kind)
|
|
}
|
|
return val_slot
|
|
}
|
|
|
|
// Expression compilation
|
|
gen_expr = function(expr, target) {
|
|
var kind = null
|
|
var slot = 0
|
|
var val = null
|
|
var list = null
|
|
var nexpr = 0
|
|
var expr_slots = null
|
|
var _i = 0
|
|
var arr_slot = 0
|
|
var arr_instr = null
|
|
var fmt_func_slot = 0
|
|
var fmt = null
|
|
var fmt_str_slot = 0
|
|
var result_slot = 0
|
|
var pattern = null
|
|
var flags = null
|
|
var name = null
|
|
var level = 0
|
|
var cached = 0
|
|
var dest = 0
|
|
var _lv = 0
|
|
var pstate = null
|
|
var parent_slot = 0
|
|
var obj = null
|
|
var prop = null
|
|
var obj_slot = 0
|
|
var idx = null
|
|
var idx_slot = 0
|
|
var callee = null
|
|
var args_list = null
|
|
var callee_kind = null
|
|
var fname = null
|
|
var mop = null
|
|
var nargs = 0
|
|
var a0 = 0
|
|
var a1 = 0
|
|
var a2 = 0
|
|
var a3 = 0
|
|
var d = 0
|
|
var top = null
|
|
var arg_slots = null
|
|
var key_expr = null
|
|
var key_slot = 0
|
|
var func_slot = 0
|
|
var operand_slot = 0
|
|
var operand = null
|
|
var postfix = false
|
|
var arith_op = null
|
|
var operand_kind = null
|
|
var one_slot = 0
|
|
var one_node = null
|
|
var old_slot = 0
|
|
var local = 0
|
|
var new_slot = 0
|
|
var pslot = 0
|
|
var idx_expr = null
|
|
var cond = null
|
|
var then_expr = null
|
|
var else_expr = null
|
|
var else_label = null
|
|
var end_label = null
|
|
var cond_slot = 0
|
|
var then_slot = 0
|
|
var else_slot = 0
|
|
var count = 0
|
|
var elem_slots = null
|
|
var instr = null
|
|
var pair = null
|
|
var key = null
|
|
var val_slot = 0
|
|
var key_kind = null
|
|
var kname = null
|
|
var func = null
|
|
var func_id = 0
|
|
var guard_t = 0
|
|
var guard_err = null
|
|
var guard_done = null
|
|
var cb_known = null
|
|
var cb_p = null
|
|
|
|
if (expr == null) {
|
|
return -1
|
|
}
|
|
set_pos(expr)
|
|
kind = expr.kind
|
|
if (kind == null) {
|
|
return -1
|
|
}
|
|
|
|
// Literals
|
|
if (kind == "number") {
|
|
slot = target >= 0 ? target : alloc_slot()
|
|
emit_const_num(slot, expr.number)
|
|
mark_slot(slot, is_integer(expr.number) ? "int" : "num")
|
|
return slot
|
|
}
|
|
if (kind == "text") {
|
|
slot = target >= 0 ? target : alloc_slot()
|
|
val = expr.value
|
|
if (val == null) {
|
|
val = ""
|
|
}
|
|
emit_const_str(slot, val)
|
|
mark_slot(slot, "text")
|
|
return slot
|
|
}
|
|
// Template literal
|
|
if (kind == "text literal") {
|
|
list = expr.list
|
|
nexpr = list != null ? length(list) : 0
|
|
expr_slots = []
|
|
_i = 0
|
|
while (_i < nexpr) {
|
|
push(expr_slots, gen_expr(list[_i], -1))
|
|
_i = _i + 1
|
|
}
|
|
// Create array from expression results
|
|
arr_slot = alloc_slot()
|
|
add_instr(["array", arr_slot, 0])
|
|
_i = 0
|
|
while (_i < nexpr) {
|
|
emit_2("push", arr_slot, expr_slots[_i])
|
|
_i = _i + 1
|
|
}
|
|
// Load format intrinsic
|
|
fmt_func_slot = find_intrinsic("format")
|
|
if (fmt_func_slot < 0) {
|
|
fmt_func_slot = alloc_slot()
|
|
emit_access_intrinsic(fmt_func_slot, "format")
|
|
}
|
|
// Load format string
|
|
fmt = expr.value
|
|
if (fmt == null) {
|
|
fmt = ""
|
|
}
|
|
fmt_str_slot = alloc_slot()
|
|
emit_const_str(fmt_str_slot, fmt)
|
|
// Call format(fmt_str, array)
|
|
result_slot = target >= 0 ? target : alloc_slot()
|
|
emit_call(result_slot, fmt_func_slot, [fmt_str_slot, arr_slot])
|
|
mark_slot(result_slot, "text")
|
|
return result_slot
|
|
}
|
|
if (kind == "regexp") {
|
|
slot = target >= 0 ? target : alloc_slot()
|
|
pattern = expr.pattern
|
|
if (pattern == null) {
|
|
pattern = ""
|
|
}
|
|
flags = expr.flags
|
|
if (flags == null) {
|
|
flags = ""
|
|
}
|
|
add_instr(["regexp", slot, pattern, flags])
|
|
return slot
|
|
}
|
|
if (kind == "true") {
|
|
slot = target >= 0 ? target : alloc_slot()
|
|
emit_const_bool(slot, true)
|
|
mark_slot(slot, "bool")
|
|
return slot
|
|
}
|
|
if (kind == "false") {
|
|
slot = target >= 0 ? target : alloc_slot()
|
|
emit_const_bool(slot, false)
|
|
mark_slot(slot, "bool")
|
|
return slot
|
|
}
|
|
if (kind == "null") {
|
|
slot = target >= 0 ? target : alloc_slot()
|
|
emit_const_null(slot)
|
|
mark_slot(slot, null)
|
|
return slot
|
|
}
|
|
if (kind == "this") {
|
|
return s_this_slot
|
|
}
|
|
|
|
// Variable reference
|
|
if (kind == "name") {
|
|
name = expr.name
|
|
level = expr.level
|
|
if (level == null) {
|
|
level = -1
|
|
}
|
|
if (level == 0 || level == -1) {
|
|
slot = find_var(name)
|
|
if (slot >= 0) {
|
|
return slot
|
|
}
|
|
} else if (level > 0) {
|
|
_lv = level - 1
|
|
pstate = parent_states[length(parent_states) - 1 - _lv]
|
|
parent_slot = find_var_in_saved(pstate, name)
|
|
dest = alloc_slot()
|
|
emit_3("get", dest, parent_slot, level)
|
|
return dest
|
|
}
|
|
// Unbound — check intrinsic cache
|
|
cached = find_intrinsic(name)
|
|
if (cached >= 0) {
|
|
return cached
|
|
}
|
|
dest = alloc_slot()
|
|
emit_access_intrinsic(dest, name)
|
|
return dest
|
|
}
|
|
|
|
// Property access
|
|
if (kind == ".") {
|
|
obj = expr.left
|
|
prop = expr.right
|
|
obj_slot = gen_expr(obj, -1)
|
|
slot = alloc_slot()
|
|
emit_get_prop(slot, obj_slot, prop)
|
|
return slot
|
|
}
|
|
|
|
// Element access
|
|
if (kind == "[") {
|
|
obj = expr.left
|
|
idx = expr.right
|
|
obj_slot = gen_expr(obj, -1)
|
|
idx_slot = gen_expr(idx, -1)
|
|
slot = alloc_slot()
|
|
emit_get_elem(slot, obj_slot, idx_slot, expr.access_kind)
|
|
return slot
|
|
}
|
|
|
|
// Function call
|
|
if (kind == "(") {
|
|
callee = expr.expression
|
|
args_list = expr.list
|
|
callee_kind = callee.kind
|
|
|
|
// Functino: inline operator call
|
|
if (callee_kind == "name" && callee.make == "functino") {
|
|
fname = callee.name
|
|
mop = functino_map[fname]
|
|
nargs = args_list != null ? length(args_list) : 0
|
|
|
|
if (fname == "~!") {
|
|
a0 = gen_expr(args_list[0], -1)
|
|
d = alloc_slot()
|
|
emit_2(mop, d, a0)
|
|
return d
|
|
}
|
|
if (fname == "[]!") {
|
|
a0 = gen_expr(args_list[0], -1)
|
|
a1 = gen_expr(args_list[1], -1)
|
|
d = alloc_slot()
|
|
emit_get_elem(d, a0, a1)
|
|
return d
|
|
}
|
|
if ((fname == "=!" || fname == "!=!") && nargs == 3) {
|
|
a0 = gen_expr(args_list[0], -1)
|
|
a1 = gen_expr(args_list[1], -1)
|
|
a2 = gen_expr(args_list[2], -1)
|
|
d = alloc_slot()
|
|
top = fname == "=!" ? "eq_tol" : "ne_tol"
|
|
emit_4_full(top, [d, a0, a1, a2])
|
|
return d
|
|
}
|
|
if (fname == "&&!") {
|
|
a0 = gen_expr(args_list[0], -1)
|
|
a1 = gen_expr(args_list[1], -1)
|
|
d = alloc_slot()
|
|
emit_3("and", d, a0, a1)
|
|
return d
|
|
}
|
|
if (fname == "||!") {
|
|
a0 = gen_expr(args_list[0], -1)
|
|
a1 = gen_expr(args_list[1], -1)
|
|
d = alloc_slot()
|
|
emit_3("or", d, a0, a1)
|
|
return d
|
|
}
|
|
// Standard 2-arg binary functino
|
|
a0 = gen_expr(args_list[0], -1)
|
|
a1 = gen_expr(args_list[1], -1)
|
|
d = alloc_slot()
|
|
_bp_ln = args_list[0]
|
|
_bp_rn = args_list[1]
|
|
emit_binop(mop, d, a0, a1)
|
|
return d
|
|
}
|
|
|
|
// Tier 1 intrinsic inlining: emit direct opcodes instead of frame/invoke
|
|
if (callee_kind == "name" && callee.intrinsic == true) {
|
|
fname = callee.name
|
|
nargs = args_list != null ? length(args_list) : 0
|
|
mop = intrinsic_num_unary_ops[fname]
|
|
if (mop != null && nargs == 1) {
|
|
a0 = gen_expr(args_list[0], -1)
|
|
return emit_intrinsic_num_unary(mop, a0)
|
|
}
|
|
mop = intrinsic_num_binary_ops[fname]
|
|
if (mop != null && nargs == 2) {
|
|
a0 = gen_expr(args_list[0], -1)
|
|
a1 = gen_expr(args_list[1], -1)
|
|
return emit_intrinsic_num_binary(mop, a0, a1)
|
|
}
|
|
mop = intrinsic_num_place_ops[fname]
|
|
if (mop != null && (nargs == 1 || nargs == 2)) {
|
|
a0 = gen_expr(args_list[0], -1)
|
|
if (nargs == 2) {
|
|
a1 = gen_expr(args_list[1], -1)
|
|
} else {
|
|
a1 = alloc_slot()
|
|
emit_1("null", a1)
|
|
}
|
|
return emit_intrinsic_num_place(mop, a0, a1)
|
|
}
|
|
// 1-arg type check intrinsics → direct opcode
|
|
if (nargs == 1 && sensory_ops[fname] != null) {
|
|
a0 = gen_expr(args_list[0], -1)
|
|
d = alloc_slot()
|
|
emit_2(sensory_ops[fname], d, a0)
|
|
return d
|
|
}
|
|
// 2-arg push: push(arr, val) → guarded direct opcode
|
|
if (nargs == 2 && fname == "push") {
|
|
a0 = gen_expr(args_list[0], -1)
|
|
a1 = gen_expr(args_list[1], -1)
|
|
guard_t = alloc_slot()
|
|
guard_err = gen_label("push_err")
|
|
guard_done = gen_label("push_done")
|
|
emit_2("is_array", guard_t, a0)
|
|
emit_jump_cond("jump_false", guard_t, guard_err)
|
|
emit_2("push", a0, a1)
|
|
emit_jump(guard_done)
|
|
emit_label(guard_err)
|
|
emit_log_error("cannot push: target must be an array")
|
|
emit_0("disrupt")
|
|
emit_label(guard_done)
|
|
return a1
|
|
}
|
|
// apply(fn, arr) → direct opcode
|
|
if (nargs == 2 && fname == "apply") {
|
|
a0 = gen_expr(args_list[0], -1)
|
|
a1 = gen_expr(args_list[1], -1)
|
|
d = alloc_slot()
|
|
emit_3("apply", d, a0, a1)
|
|
return d
|
|
}
|
|
// Callback intrinsics → inline mcode loops
|
|
if (fname == "arrfor" && nargs >= 2 && nargs <= 4 && inline_arrfor) {
|
|
a0 = gen_expr(args_list[0], -1)
|
|
a1 = gen_expr(args_list[1], -1)
|
|
a2 = nargs >= 3 ? gen_expr(args_list[2], -1) : -1
|
|
a3 = nargs >= 4 ? gen_expr(args_list[3], -1) : -1
|
|
d = alloc_slot()
|
|
return expand_inline_arrfor(d, {arr: a0, fn: a1, rev: a2, exit: a3}, nargs)
|
|
}
|
|
if (nargs == 2 && fname == "every" && inline_every) {
|
|
a0 = gen_expr(args_list[0], -1)
|
|
a1 = gen_expr(args_list[1], -1)
|
|
d = alloc_slot()
|
|
return expand_inline_every(d, a0, a1)
|
|
}
|
|
if (nargs == 2 && fname == "some" && inline_some) {
|
|
a0 = gen_expr(args_list[0], -1)
|
|
a1 = gen_expr(args_list[1], -1)
|
|
d = alloc_slot()
|
|
return expand_inline_some(d, a0, a1)
|
|
}
|
|
if (nargs == 2 && fname == "filter" && inline_filter) {
|
|
a0 = gen_expr(args_list[0], -1)
|
|
a1 = gen_expr(args_list[1], -1)
|
|
d = alloc_slot()
|
|
return expand_inline_filter(d, a0, a1)
|
|
}
|
|
if (fname == "find" && nargs >= 2 && nargs <= 4 && inline_find) {
|
|
a0 = gen_expr(args_list[0], -1)
|
|
a1 = gen_expr(args_list[1], -1)
|
|
a2 = nargs >= 3 ? gen_expr(args_list[2], -1) : -1
|
|
a3 = nargs >= 4 ? gen_expr(args_list[3], -1) : -1
|
|
d = alloc_slot()
|
|
return expand_inline_find(d, {arr: a0, target: a1, rev: a2, from: a3}, nargs)
|
|
}
|
|
if (fname == "reduce" && nargs >= 2 && nargs <= 4 && inline_reduce) {
|
|
a0 = gen_expr(args_list[0], -1)
|
|
a1 = gen_expr(args_list[1], -1)
|
|
a2 = nargs >= 3 ? gen_expr(args_list[2], -1) : -1
|
|
a3 = nargs >= 4 ? gen_expr(args_list[3], -1) : -1
|
|
d = alloc_slot()
|
|
cb_known = null
|
|
if (args_list[1].kind == "function") {
|
|
cb_p = args_list[1].list
|
|
if (cb_p == null) cb_p = args_list[1].parameters
|
|
cb_known = cb_p != null ? length(cb_p) : 0
|
|
}
|
|
return expand_inline_reduce(d, {arr: a0, fn: a1, init: a2, rev: a3,
|
|
fn_known_arity: cb_known}, nargs)
|
|
}
|
|
// array(arr, fn) inline expansion removed — array() is too complex to inline
|
|
}
|
|
|
|
// Collect arg slots
|
|
arg_slots = []
|
|
_i = 0
|
|
nargs = args_list != null ? length(args_list) : 0
|
|
while (_i < nargs) {
|
|
push(arg_slots, gen_expr(args_list[_i], -1))
|
|
_i = _i + 1
|
|
}
|
|
dest = alloc_slot()
|
|
if (callee_kind == ".") {
|
|
obj = callee.left
|
|
prop = callee.right
|
|
obj_slot = gen_expr(obj, -1)
|
|
emit_call_method(dest, obj_slot, prop, arg_slots)
|
|
} else if (callee_kind == "[") {
|
|
obj = callee.left
|
|
key_expr = callee.right
|
|
obj_slot = gen_expr(obj, -1)
|
|
key_slot = gen_expr(key_expr, -1)
|
|
emit_call_method_dyn(dest, obj_slot, key_slot, arg_slots)
|
|
} else {
|
|
func_slot = gen_expr(callee, -1)
|
|
emit_call(dest, func_slot, arg_slots)
|
|
}
|
|
return dest
|
|
}
|
|
|
|
// Unary operators
|
|
if (kind == "!") {
|
|
operand_slot = gen_expr(expr.expression, -1)
|
|
slot = alloc_slot()
|
|
emit_2("not", slot, operand_slot)
|
|
return slot
|
|
}
|
|
if (kind == "~") {
|
|
operand_slot = gen_expr(expr.expression, -1)
|
|
slot = alloc_slot()
|
|
emit_2("bitnot", slot, operand_slot)
|
|
return slot
|
|
}
|
|
if (kind == "-unary") {
|
|
operand_slot = gen_expr(expr.expression, -1)
|
|
slot = alloc_slot()
|
|
emit_neg_decomposed(slot, operand_slot, expr.expression)
|
|
return slot
|
|
}
|
|
if (kind == "+unary") {
|
|
return gen_expr(expr.expression, -1)
|
|
}
|
|
|
|
// Increment/Decrement
|
|
if (kind == "++" || kind == "--") {
|
|
operand = expr.expression
|
|
postfix = expr.postfix == true
|
|
arith_op = kind == "++" ? "add" : "subtract"
|
|
operand_kind = operand.kind
|
|
one_slot = alloc_slot()
|
|
emit_2("int", one_slot, 1)
|
|
one_node = {kind: "number", number: 1}
|
|
|
|
if (operand_kind == "name") {
|
|
name = operand.name
|
|
level = operand.level
|
|
if (level == null) {
|
|
level = -1
|
|
}
|
|
old_slot = alloc_slot()
|
|
if (level == 0) {
|
|
local = find_var(name)
|
|
if (local >= 0) {
|
|
emit_2("move", old_slot, local)
|
|
}
|
|
} else if (level > 0) {
|
|
_lv = level - 1
|
|
pstate = parent_states[length(parent_states) - 1 - _lv]
|
|
pslot = find_var_in_saved(pstate, name)
|
|
emit_3("get", old_slot, pslot, level)
|
|
} else {
|
|
emit_access_intrinsic(old_slot, name)
|
|
}
|
|
new_slot = alloc_slot()
|
|
_bp_ln = null
|
|
_bp_rn = one_node
|
|
emit_binop(arith_op, new_slot, old_slot, one_slot)
|
|
if (level == 0) {
|
|
local = find_var(name)
|
|
if (local >= 0) {
|
|
emit_2("move", local, new_slot)
|
|
}
|
|
} else if (level > 0) {
|
|
_lv = level - 1
|
|
pstate = parent_states[length(parent_states) - 1 - _lv]
|
|
pslot = find_var_in_saved(pstate, name)
|
|
emit_3("put", new_slot, pslot, level)
|
|
}
|
|
return postfix ? old_slot : new_slot
|
|
} else if (operand_kind == ".") {
|
|
obj = operand.left
|
|
prop = operand.right
|
|
obj_slot = gen_expr(obj, -1)
|
|
old_slot = alloc_slot()
|
|
emit_get_prop(old_slot, obj_slot, prop)
|
|
new_slot = alloc_slot()
|
|
_bp_ln = null
|
|
_bp_rn = one_node
|
|
emit_binop(arith_op, new_slot, old_slot, one_slot)
|
|
emit_set_prop(obj_slot, prop, new_slot)
|
|
return postfix ? old_slot : new_slot
|
|
} else if (operand_kind == "[") {
|
|
obj = operand.left
|
|
idx_expr = operand.right
|
|
obj_slot = gen_expr(obj, -1)
|
|
idx_slot = gen_expr(idx_expr, -1)
|
|
old_slot = alloc_slot()
|
|
emit_get_elem(old_slot, obj_slot, idx_slot, operand.access_kind)
|
|
new_slot = alloc_slot()
|
|
_bp_ln = null
|
|
_bp_rn = one_node
|
|
emit_binop(arith_op, new_slot, old_slot, one_slot)
|
|
emit_set_elem(obj_slot, idx_slot, new_slot, operand.access_kind)
|
|
return postfix ? old_slot : new_slot
|
|
}
|
|
}
|
|
|
|
// Delete operator
|
|
if (kind == "delete") {
|
|
operand = expr.expression
|
|
operand_kind = operand.kind
|
|
slot = alloc_slot()
|
|
if (operand_kind == ".") {
|
|
obj = operand.left
|
|
prop = operand.right
|
|
obj_slot = gen_expr(obj, -1)
|
|
push(s_instructions, ["delete", slot, obj_slot, prop])
|
|
} else if (operand_kind == "[") {
|
|
obj = operand.left
|
|
idx = operand.right
|
|
obj_slot = gen_expr(obj, -1)
|
|
idx_slot = gen_expr(idx, -1)
|
|
emit_3("delete", slot, obj_slot, idx_slot)
|
|
} else {
|
|
emit_const_bool(slot, true)
|
|
}
|
|
return slot
|
|
}
|
|
|
|
// Ternary
|
|
if (kind == "then") {
|
|
cond = expr.expression
|
|
then_expr = expr.then
|
|
else_expr = expr["else"]
|
|
else_label = gen_label("tern_else")
|
|
end_label = gen_label("tern_end")
|
|
cond_slot = gen_expr(cond, -1)
|
|
emit_jump_cond("wary_false", cond_slot, else_label)
|
|
dest = alloc_slot()
|
|
then_slot = gen_expr(then_expr, -1)
|
|
emit_2("move", dest, then_slot)
|
|
emit_jump(end_label)
|
|
emit_label(else_label)
|
|
else_slot = gen_expr(else_expr, -1)
|
|
emit_2("move", dest, else_slot)
|
|
emit_label(end_label)
|
|
return dest
|
|
}
|
|
|
|
// Array literal
|
|
if (kind == "array") {
|
|
list = expr.list
|
|
count = length(list)
|
|
elem_slots = []
|
|
_i = 0
|
|
while (_i < count) {
|
|
push(elem_slots, gen_expr(list[_i], -1))
|
|
_i = _i + 1
|
|
}
|
|
dest = alloc_slot()
|
|
add_instr(["array", dest, count])
|
|
_i = 0
|
|
while (_i < count) {
|
|
emit_2("push", dest, elem_slots[_i])
|
|
_i = _i + 1
|
|
}
|
|
return dest
|
|
}
|
|
|
|
// Object literal
|
|
if (kind == "record") {
|
|
list = expr.list
|
|
dest = alloc_slot()
|
|
push(s_instructions, ["record", dest, length(list)])
|
|
_i = 0
|
|
while (_i < length(list)) {
|
|
pair = list[_i]
|
|
key = pair.left
|
|
val = pair.right
|
|
val_slot = gen_expr(val, -1)
|
|
key_kind = key.kind
|
|
if (key_kind == "name") {
|
|
emit_set_prop(dest, key.name, val_slot)
|
|
} else if (key_kind == "text") {
|
|
kname = key.value
|
|
if (kname == null) {
|
|
kname = ""
|
|
}
|
|
emit_set_prop(dest, kname, val_slot)
|
|
} else {
|
|
key_slot = gen_expr(key, -1)
|
|
emit_set_elem(dest, key_slot, val_slot)
|
|
}
|
|
_i = _i + 1
|
|
}
|
|
return dest
|
|
}
|
|
|
|
// Function expression
|
|
if (kind == "function") {
|
|
func = gen_function(expr)
|
|
func_id = s_func_counter
|
|
s_func_counter = s_func_counter + 1
|
|
push(s_functions, func)
|
|
dest = alloc_slot()
|
|
emit_2("function", dest, func_id)
|
|
return dest
|
|
}
|
|
|
|
// Assignment operators
|
|
if (kind == "assign" ||
|
|
kind == "+=" || kind == "-=" ||
|
|
kind == "*=" || kind == "/=" ||
|
|
kind == "%=" || kind == "**=" ||
|
|
kind == "&=" || kind == "|=" ||
|
|
kind == "^=" || kind == "<<=" ||
|
|
kind == ">>=" || kind == ">>>=" ||
|
|
kind == "&&=" || kind == "||=" ||
|
|
kind == "??=") {
|
|
return gen_assign(expr)
|
|
}
|
|
|
|
// Binary operators (fallback)
|
|
return gen_binary(expr, target)
|
|
}
|
|
|
|
// Statement compilation
|
|
gen_statement = function(stmt) {
|
|
var kind = null
|
|
var left = null
|
|
var right = null
|
|
var name = null
|
|
var local_slot = 0
|
|
var arr_expr = null
|
|
var arr_slot = 0
|
|
var val_slot = 0
|
|
var list = null
|
|
var _i = 0
|
|
var stmts = null
|
|
var cond = null
|
|
var then_stmts = null
|
|
var else_stmts = null
|
|
var else_label = null
|
|
var end_label = null
|
|
var cond_slot = 0
|
|
var start_label = null
|
|
var old_break = null
|
|
var old_continue = null
|
|
var cond_label = null
|
|
var init = null
|
|
var test = null
|
|
var update = null
|
|
var update_label = null
|
|
var init_kind = null
|
|
var test_slot = 0
|
|
var expr = null
|
|
var slot = 0
|
|
var null_slot = 0
|
|
var call_expr = null
|
|
var callee = null
|
|
var args_list = null
|
|
var arg_slots = null
|
|
var nargs = 0
|
|
var callee_kind = null
|
|
var obj_node = null
|
|
var prop = null
|
|
var obj_slot = 0
|
|
var func_slot = 0
|
|
var cases = null
|
|
var switch_val = 0
|
|
var default_label = null
|
|
var case_labels = null
|
|
var case_node = null
|
|
var case_kind = null
|
|
var case_label = null
|
|
var case_expr = null
|
|
var case_val = 0
|
|
var cmp_slot = 0
|
|
var case_stmts = null
|
|
var _j = 0
|
|
var func = null
|
|
var func_id = 0
|
|
var dest = 0
|
|
var guard_t = 0
|
|
var guard_err = null
|
|
var guard_done = null
|
|
var last_instr = null
|
|
|
|
if (stmt == null) {
|
|
return null
|
|
}
|
|
set_pos(stmt)
|
|
kind = stmt.kind
|
|
if (kind == null) {
|
|
return null
|
|
}
|
|
|
|
if (kind == "var" || kind == "def") {
|
|
left = stmt.left
|
|
right = stmt.right
|
|
name = left.name
|
|
local_slot = find_var(name)
|
|
// Pop: var val = arr[] (guarded)
|
|
if (stmt.pop == true && right != null) {
|
|
arr_expr = right.left
|
|
arr_slot = gen_expr(arr_expr, -1)
|
|
if (local_slot >= 0) {
|
|
guard_t = alloc_slot()
|
|
guard_err = gen_label("pop_err")
|
|
guard_done = gen_label("pop_done")
|
|
emit_2("is_array", guard_t, arr_slot)
|
|
emit_jump_cond("jump_false", guard_t, guard_err)
|
|
emit_2("pop", local_slot, arr_slot)
|
|
emit_jump(guard_done)
|
|
emit_label(guard_err)
|
|
emit_log_error("cannot pop: target must be an array")
|
|
emit_0("disrupt")
|
|
emit_label(guard_done)
|
|
}
|
|
return null
|
|
}
|
|
if (right != null) {
|
|
val_slot = gen_expr(right, local_slot)
|
|
if (local_slot >= 0 && val_slot != local_slot) {
|
|
emit_2("move", local_slot, val_slot)
|
|
}
|
|
} else if (local_slot >= 0) {
|
|
emit_const_null(local_slot)
|
|
}
|
|
return null
|
|
}
|
|
|
|
if (kind == "var_list" || kind == "def_list") {
|
|
list = stmt.list
|
|
_i = 0
|
|
while (_i < length(list)) {
|
|
gen_statement(list[_i])
|
|
_i = _i + 1
|
|
}
|
|
return null
|
|
}
|
|
|
|
if (kind == "block") {
|
|
stmts = stmt.statements
|
|
_i = 0
|
|
while (_i < length(stmts)) {
|
|
gen_statement(stmts[_i])
|
|
_i = _i + 1
|
|
}
|
|
return null
|
|
}
|
|
|
|
if (kind == "if") {
|
|
cond = stmt.expression
|
|
then_stmts = stmt.then
|
|
else_stmts = stmt["else"]
|
|
if (else_stmts == null) {
|
|
else_stmts = stmt.list
|
|
}
|
|
else_label = gen_label("if_else")
|
|
end_label = gen_label("if_end")
|
|
cond_slot = gen_expr(cond, -1)
|
|
emit_jump_cond("wary_false", cond_slot, else_label)
|
|
_i = 0
|
|
while (_i < length(then_stmts)) {
|
|
gen_statement(then_stmts[_i])
|
|
_i = _i + 1
|
|
}
|
|
emit_jump(end_label)
|
|
emit_label(else_label)
|
|
if (else_stmts != null) {
|
|
_i = 0
|
|
while (_i < length(else_stmts)) {
|
|
gen_statement(else_stmts[_i])
|
|
_i = _i + 1
|
|
}
|
|
}
|
|
emit_label(end_label)
|
|
return null
|
|
}
|
|
|
|
if (kind == "label") {
|
|
s_pending_label = stmt.name
|
|
gen_statement(stmt.statement)
|
|
s_pending_label = null
|
|
return null
|
|
}
|
|
|
|
if (kind == "while") {
|
|
cond = stmt.expression
|
|
stmts = stmt.statements
|
|
start_label = gen_label("while_start")
|
|
end_label = gen_label("while_end")
|
|
old_break = s_loop_break
|
|
old_continue = s_loop_continue
|
|
s_loop_break = end_label
|
|
s_loop_continue = start_label
|
|
if (s_pending_label != null) {
|
|
s_label_map[s_pending_label] = {break_target: end_label, continue_target: start_label}
|
|
s_pending_label = null
|
|
}
|
|
emit_label(start_label)
|
|
cond_slot = gen_expr(cond, -1)
|
|
emit_jump_cond("wary_false", cond_slot, end_label)
|
|
_i = 0
|
|
while (_i < length(stmts)) {
|
|
gen_statement(stmts[_i])
|
|
_i = _i + 1
|
|
}
|
|
emit_jump(start_label)
|
|
emit_label(end_label)
|
|
s_loop_break = old_break
|
|
s_loop_continue = old_continue
|
|
return null
|
|
}
|
|
|
|
if (kind == "do") {
|
|
cond = stmt.expression
|
|
stmts = stmt.statements
|
|
start_label = gen_label("do_start")
|
|
cond_label = gen_label("do_cond")
|
|
end_label = gen_label("do_end")
|
|
old_break = s_loop_break
|
|
old_continue = s_loop_continue
|
|
s_loop_break = end_label
|
|
s_loop_continue = cond_label
|
|
if (s_pending_label != null) {
|
|
s_label_map[s_pending_label] = {break_target: end_label, continue_target: cond_label}
|
|
s_pending_label = null
|
|
}
|
|
emit_label(start_label)
|
|
_i = 0
|
|
while (_i < length(stmts)) {
|
|
gen_statement(stmts[_i])
|
|
_i = _i + 1
|
|
}
|
|
emit_label(cond_label)
|
|
cond_slot = gen_expr(cond, -1)
|
|
emit_jump_cond("wary_true", cond_slot, start_label)
|
|
emit_label(end_label)
|
|
s_loop_break = old_break
|
|
s_loop_continue = old_continue
|
|
return null
|
|
}
|
|
|
|
if (kind == "for") {
|
|
init = stmt.init
|
|
test = stmt.test
|
|
update = stmt.update
|
|
stmts = stmt.statements
|
|
start_label = gen_label("for_start")
|
|
update_label = gen_label("for_update")
|
|
end_label = gen_label("for_end")
|
|
old_break = s_loop_break
|
|
old_continue = s_loop_continue
|
|
s_loop_break = end_label
|
|
s_loop_continue = update_label
|
|
if (s_pending_label != null) {
|
|
s_label_map[s_pending_label] = {break_target: end_label, continue_target: update_label}
|
|
s_pending_label = null
|
|
}
|
|
if (init != null) {
|
|
init_kind = init.kind
|
|
if (init_kind == "var" || init_kind == "def") {
|
|
gen_statement(init)
|
|
} else {
|
|
gen_expr(init, -1)
|
|
}
|
|
}
|
|
emit_label(start_label)
|
|
if (test != null) {
|
|
test_slot = gen_expr(test, -1)
|
|
emit_jump_cond("wary_false", test_slot, end_label)
|
|
}
|
|
_i = 0
|
|
while (_i < length(stmts)) {
|
|
gen_statement(stmts[_i])
|
|
_i = _i + 1
|
|
}
|
|
emit_label(update_label)
|
|
if (update != null) {
|
|
gen_expr(update, -1)
|
|
}
|
|
emit_jump(start_label)
|
|
emit_label(end_label)
|
|
s_loop_break = old_break
|
|
s_loop_continue = old_continue
|
|
return null
|
|
}
|
|
|
|
if (kind == "return") {
|
|
expr = stmt.expression
|
|
if (expr != null) {
|
|
slot = gen_expr(expr, -1)
|
|
// Mark tail calls: rename last invoke to tail_invoke
|
|
if (stmt.tail == true && !s_has_disruption) {
|
|
last_instr = s_instructions[length(s_instructions) - 1]
|
|
if (is_array(last_instr) && last_instr[0] == "invoke") {
|
|
last_instr[0] = "tail_invoke"
|
|
}
|
|
}
|
|
emit_1("return", slot)
|
|
} else {
|
|
null_slot = alloc_slot()
|
|
emit_1("null", null_slot)
|
|
emit_1("return", null_slot)
|
|
}
|
|
return null
|
|
}
|
|
|
|
if (kind == "go") {
|
|
call_expr = stmt.expression
|
|
if (call_expr == null || call_expr.kind != "(") {
|
|
return null
|
|
}
|
|
callee = call_expr.expression
|
|
args_list = call_expr.list
|
|
arg_slots = []
|
|
_i = 0
|
|
nargs = args_list != null ? length(args_list) : 0
|
|
while (_i < nargs) {
|
|
push(arg_slots, gen_expr(args_list[_i], -1))
|
|
_i = _i + 1
|
|
}
|
|
callee_kind = callee.kind
|
|
if (callee_kind == ".") {
|
|
obj_node = callee.left
|
|
prop = callee.right
|
|
obj_slot = gen_expr(obj_node, -1)
|
|
emit_go_call_method(obj_slot, prop, arg_slots)
|
|
} else {
|
|
func_slot = gen_expr(callee, -1)
|
|
emit_go_call(func_slot, arg_slots)
|
|
}
|
|
return null
|
|
}
|
|
|
|
if (kind == "disrupt") {
|
|
emit_0("disrupt")
|
|
return null
|
|
}
|
|
|
|
if (kind == "break") {
|
|
if (stmt.name != null && s_label_map[stmt.name] != null) {
|
|
emit_jump(s_label_map[stmt.name].break_target)
|
|
} else if (s_loop_break != null) {
|
|
emit_jump(s_loop_break)
|
|
}
|
|
return null
|
|
}
|
|
|
|
if (kind == "continue") {
|
|
if (stmt.name != null && s_label_map[stmt.name] != null) {
|
|
emit_jump(s_label_map[stmt.name].continue_target)
|
|
} else if (s_loop_continue != null) {
|
|
emit_jump(s_loop_continue)
|
|
}
|
|
return null
|
|
}
|
|
|
|
if (kind == "switch") {
|
|
expr = stmt.expression
|
|
cases = stmt.cases
|
|
switch_val = gen_expr(expr, -1)
|
|
end_label = gen_label("switch_end")
|
|
default_label = null
|
|
old_break = s_loop_break
|
|
s_loop_break = end_label
|
|
// Phase 1: emit comparisons, collect labels
|
|
case_labels = []
|
|
_i = 0
|
|
while (_i < length(cases)) {
|
|
case_node = cases[_i]
|
|
case_kind = case_node.kind
|
|
if (case_kind == "default") {
|
|
default_label = gen_label("switch_default")
|
|
push(case_labels, default_label)
|
|
} else {
|
|
case_label = gen_label("switch_case")
|
|
case_expr = case_node.expression
|
|
case_val = gen_expr(case_expr, -1)
|
|
cmp_slot = alloc_slot()
|
|
_bp_ln = null
|
|
_bp_rn = case_expr
|
|
emit_binop("eq", cmp_slot, switch_val, case_val)
|
|
emit_jump_cond("jump_true", cmp_slot, case_label)
|
|
push(case_labels, case_label)
|
|
}
|
|
_i = _i + 1
|
|
}
|
|
if (default_label != null) {
|
|
emit_jump(default_label)
|
|
} else {
|
|
emit_jump(end_label)
|
|
}
|
|
// Phase 2: emit case bodies
|
|
_i = 0
|
|
while (_i < length(cases)) {
|
|
emit_label(case_labels[_i])
|
|
case_stmts = cases[_i].statements
|
|
_j = 0
|
|
while (_j < length(case_stmts)) {
|
|
gen_statement(case_stmts[_j])
|
|
_j = _j + 1
|
|
}
|
|
_i = _i + 1
|
|
}
|
|
emit_label(end_label)
|
|
s_loop_break = old_break
|
|
return null
|
|
}
|
|
|
|
if (kind == "function") {
|
|
name = stmt.name
|
|
if (name != null) {
|
|
func = gen_function(stmt)
|
|
func_id = s_func_counter
|
|
s_func_counter = s_func_counter + 1
|
|
push(s_functions, func)
|
|
local_slot = find_var(name)
|
|
dest = alloc_slot()
|
|
emit_2("function", dest, func_id)
|
|
if (local_slot >= 0) {
|
|
emit_2("move", local_slot, dest)
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
if (kind == "call") {
|
|
gen_expr(stmt.expression, -1)
|
|
return null
|
|
}
|
|
|
|
gen_expr(stmt, -1)
|
|
return null
|
|
}
|
|
|
|
// Function compilation
|
|
gen_function = function(func_node) {
|
|
var saved = save_state()
|
|
var is_arrow = func_node.arrow == true
|
|
var fn_nr_node = func_node.function_nr
|
|
var params = func_node.list
|
|
var nr_params = 0
|
|
var param_slot = 1
|
|
var _i = 0
|
|
var param = null
|
|
var param_name = null
|
|
var ps = 1
|
|
var default_expr = null
|
|
var end_label = null
|
|
var default_slot = 0
|
|
var hoisted = null
|
|
var fn = null
|
|
var fname = null
|
|
var compiled = null
|
|
var func_id = 0
|
|
var local_slot = 0
|
|
var dest = 0
|
|
var stmts = null
|
|
var body = null
|
|
var null_slot = 0
|
|
var disruption_start = 0
|
|
var disrupt_clause = func_node.disruption
|
|
var null_slot2 = null
|
|
var fn_name = func_node.name
|
|
var result = null
|
|
var saved_label = 0
|
|
var saved_func = 0
|
|
var captured_this = 0
|
|
|
|
push(parent_states, saved)
|
|
|
|
s_instructions = []
|
|
s_vars = []
|
|
s_intrinsic_cache = []
|
|
s_slot_types = {}
|
|
s_num_err_label = null
|
|
s_num_err_emitted = false
|
|
s_loop_break = null
|
|
s_loop_continue = null
|
|
s_label_map = {}
|
|
|
|
s_is_arrow = is_arrow
|
|
s_has_disruption = disrupt_clause != null && is_array(disrupt_clause)
|
|
|
|
s_function_nr = fn_nr_node != null ? fn_nr_node : 0
|
|
|
|
// Parameters
|
|
if (params == null) {
|
|
params = func_node.parameters
|
|
}
|
|
nr_params = params != null ? length(params) : 0
|
|
s_nr_args = nr_params
|
|
s_this_slot = 0
|
|
s_nr_close_slots = 0
|
|
s_nr_local_slots = 0
|
|
|
|
param_slot = 1
|
|
_i = 0
|
|
while (_i < nr_params) {
|
|
param = params[_i]
|
|
param_name = param.name
|
|
if (param_name == null && is_text(param)) {
|
|
param_name = param
|
|
}
|
|
if (param_name != null) {
|
|
add_var(param_name, param_slot, true)
|
|
param_slot = param_slot + 1
|
|
}
|
|
_i = _i + 1
|
|
}
|
|
|
|
s_next_temp_slot = 1 + s_nr_args
|
|
s_max_slot = 1 + s_nr_args
|
|
|
|
// Scan scope
|
|
scan_scope()
|
|
|
|
s_next_temp_slot = 1 + s_nr_args + s_nr_local_slots
|
|
if (s_next_temp_slot > s_max_slot) {
|
|
s_max_slot = s_next_temp_slot
|
|
}
|
|
|
|
// Arrow functions capture the enclosing this via closure
|
|
if (is_arrow) {
|
|
captured_this = alloc_slot()
|
|
emit_3("get", captured_this, saved.this_slot, 1)
|
|
s_this_slot = captured_this
|
|
}
|
|
|
|
// Default parameter initialization
|
|
ps = 1
|
|
_i = 0
|
|
while (_i < nr_params) {
|
|
param = params[_i]
|
|
default_expr = param.expression
|
|
if (default_expr != null) {
|
|
end_label = gen_label("default_end")
|
|
emit_jump_cond("jump_not_null", ps, end_label)
|
|
default_slot = gen_expr(default_expr, -1)
|
|
emit_2("move", ps, default_slot)
|
|
emit_label(end_label)
|
|
}
|
|
ps = ps + 1
|
|
_i = _i + 1
|
|
}
|
|
|
|
// Pre-load intrinsics
|
|
load_intrinsics(func_node.intrinsics)
|
|
|
|
// Compile hoisted function declarations
|
|
hoisted = func_node.functions
|
|
if (hoisted != null) {
|
|
_i = 0
|
|
while (_i < length(hoisted)) {
|
|
fn = hoisted[_i]
|
|
fname = fn.name
|
|
if (fname != null) {
|
|
compiled = gen_function(fn)
|
|
func_id = s_func_counter
|
|
s_func_counter = s_func_counter + 1
|
|
push(s_functions, compiled)
|
|
local_slot = find_var(fname)
|
|
dest = alloc_slot()
|
|
emit_2("function", dest, func_id)
|
|
if (local_slot >= 0) {
|
|
emit_2("move", local_slot, dest)
|
|
}
|
|
}
|
|
_i = _i + 1
|
|
}
|
|
}
|
|
|
|
// Compile body
|
|
stmts = func_node.statements
|
|
if (stmts == null) {
|
|
body = func_node.body
|
|
if (body != null) {
|
|
stmts = body.statements
|
|
if (stmts == null) {
|
|
stmts = body
|
|
}
|
|
}
|
|
}
|
|
if (stmts != null && is_array(stmts)) {
|
|
_i = 0
|
|
while (_i < length(stmts)) {
|
|
gen_statement(stmts[_i])
|
|
_i = _i + 1
|
|
}
|
|
}
|
|
|
|
// Implicit return null
|
|
null_slot = alloc_slot()
|
|
emit_1("null", null_slot)
|
|
emit_1("return", null_slot)
|
|
|
|
// Compile disruption clause
|
|
if (disrupt_clause != null && is_array(disrupt_clause)) {
|
|
emit_label(gen_label("disruption"))
|
|
disruption_start = length(s_instructions)
|
|
_i = 0
|
|
while (_i < length(disrupt_clause)) {
|
|
gen_statement(disrupt_clause[_i])
|
|
_i = _i + 1
|
|
}
|
|
null_slot2 = alloc_slot()
|
|
emit_1("null", null_slot2)
|
|
emit_1("return", null_slot2)
|
|
}
|
|
|
|
// Build result
|
|
if (fn_name == null) {
|
|
fn_name = "<anonymous>"
|
|
}
|
|
|
|
result = {
|
|
name: fn_name,
|
|
nr_args: nr_params,
|
|
nr_close_slots: s_nr_close_slots,
|
|
nr_slots: s_max_slot + 1,
|
|
disruption_pc: disruption_start,
|
|
instructions: s_instructions
|
|
}
|
|
|
|
if (s_filename != null) {
|
|
result.filename = s_filename
|
|
}
|
|
|
|
// Propagate counters back
|
|
saved_label = s_label_counter
|
|
saved_func = s_func_counter
|
|
|
|
// Pop parent state
|
|
pop(parent_states)
|
|
restore_state(saved)
|
|
s_label_counter = saved_label
|
|
s_func_counter = saved_func
|
|
|
|
return result
|
|
}
|
|
|
|
// Program compilation (top-level entry)
|
|
var gen_program = function(ast) {
|
|
var filename = ast.filename
|
|
var hoisted = ast.functions
|
|
var _i = 0
|
|
var fn = null
|
|
var name = null
|
|
var compiled = null
|
|
var func_id = 0
|
|
var local_slot = 0
|
|
var dest = 0
|
|
var statements = ast.statements
|
|
var last_expr_slot = -1
|
|
var stmt = null
|
|
var kind = null
|
|
var null_slot = 0
|
|
var result = null
|
|
|
|
s_filename = filename
|
|
|
|
s_instructions = []
|
|
s_data = {}
|
|
s_functions = []
|
|
s_vars = []
|
|
s_intrinsic_cache = []
|
|
s_scopes = ast.scopes
|
|
s_this_slot = 0
|
|
s_nr_args = 0
|
|
s_nr_close_slots = 0
|
|
s_nr_local_slots = 0
|
|
s_next_temp_slot = 1
|
|
s_max_slot = 1
|
|
s_label_counter = 0
|
|
s_func_counter = 0
|
|
s_slot_types = {}
|
|
s_num_err_label = null
|
|
s_num_err_emitted = false
|
|
s_loop_break = null
|
|
s_loop_continue = null
|
|
s_label_map = {}
|
|
s_function_nr = 0
|
|
|
|
// Scan scope
|
|
scan_scope()
|
|
|
|
s_next_temp_slot = 1 + s_nr_local_slots
|
|
if (s_next_temp_slot > s_max_slot) {
|
|
s_max_slot = s_next_temp_slot
|
|
}
|
|
|
|
// Compile hoisted function declarations from ast.functions
|
|
if (hoisted != null) {
|
|
_i = 0
|
|
while (_i < length(hoisted)) {
|
|
fn = hoisted[_i]
|
|
name = fn.name
|
|
if (name != null) {
|
|
compiled = gen_function(fn)
|
|
func_id = s_func_counter
|
|
s_func_counter = s_func_counter + 1
|
|
push(s_functions, compiled)
|
|
local_slot = find_var(name)
|
|
dest = alloc_slot()
|
|
emit_2("function", dest, func_id)
|
|
if (local_slot >= 0) {
|
|
emit_2("move", local_slot, dest)
|
|
}
|
|
}
|
|
_i = _i + 1
|
|
}
|
|
}
|
|
|
|
// Generate main code
|
|
_i = 0
|
|
while (_i < length(statements)) {
|
|
stmt = statements[_i]
|
|
kind = stmt.kind
|
|
if (kind != null) {
|
|
if (kind == "call") {
|
|
last_expr_slot = gen_expr(stmt.expression, -1)
|
|
} else if (kind == "return" || kind == "disrupt" ||
|
|
kind == "break" || kind == "continue") {
|
|
gen_statement(stmt)
|
|
last_expr_slot = -1
|
|
} else if (kind == "var" || kind == "def" ||
|
|
kind == "var_list" || kind == "def_list" ||
|
|
kind == "function" || kind == "block" ||
|
|
kind == "if" || kind == "while" ||
|
|
kind == "do" || kind == "for" ||
|
|
kind == "switch") {
|
|
gen_statement(stmt)
|
|
last_expr_slot = -1
|
|
} else {
|
|
last_expr_slot = gen_expr(stmt, -1)
|
|
}
|
|
} else {
|
|
gen_statement(stmt)
|
|
}
|
|
_i = _i + 1
|
|
}
|
|
|
|
if (last_expr_slot >= 0) {
|
|
emit_1("return", last_expr_slot)
|
|
} else {
|
|
null_slot = alloc_slot()
|
|
emit_1("null", null_slot)
|
|
emit_1("return", null_slot)
|
|
}
|
|
|
|
result = {}
|
|
result.name = filename != null ? filename : "<eval>"
|
|
result.data = s_data
|
|
result.functions = s_functions
|
|
result.main = {
|
|
nr_args: 0,
|
|
nr_close_slots: 0,
|
|
nr_slots: s_max_slot + 1,
|
|
instructions: s_instructions
|
|
}
|
|
|
|
if (filename != null) {
|
|
result.filename = filename
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
return gen_program(ast)
|
|
}
|
|
|
|
return mcode
|