fix merge
This commit is contained in:
@@ -11,7 +11,7 @@ type: "docs"
|
||||
|
||||
### Variables and Constants
|
||||
|
||||
Variables are declared with `var`, constants with `def`. All declarations must be initialized and must appear at the function body level — not inside `if`, `while`, `for`, or bare `{}` blocks.
|
||||
Variables are declared with `var`, constants with `def`. All declarations must be initialized and must appear at the function body level — not inside `if`, `while`, `for`, or `do` blocks.
|
||||
|
||||
```javascript
|
||||
var x = 10
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Hidden vars come from env:
|
||||
// CLI mode (cell_init): os, args, core_path, use_mcode
|
||||
// Actor spawn (script_startup): os, json, nota, wota, actorsym, init, core_path
|
||||
// CLI mode (cell_init): os, args, shop_path, use_mcode
|
||||
// Actor spawn (script_startup): os, json, nota, wota, actorsym, init, shop_path
|
||||
var core_path = shop_path + '/packages/core'
|
||||
// args[0] = script name, args[1..] = user args
|
||||
var load_internal = os.load_internal
|
||||
function use_embed(name) {
|
||||
return load_internal("js_" + name + "_use")
|
||||
@@ -158,7 +160,7 @@ if (args != null) {
|
||||
// Actor spawn mode — load engine.cm with full actor env
|
||||
load_engine({
|
||||
os: os, actorsym: actorsym, init: init,
|
||||
core_path: core_path, json: json, nota: nota, wota: wota,
|
||||
shop_path: shop_path, json: json, nota: nota, wota: wota,
|
||||
analyze: analyze, run_ast_fn: run_ast
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// Hidden vars (os, actorsym, init, core_path, analyze, run_ast_fn, json) come from env
|
||||
// Hidden vars (os, actorsym, init, shop_path, analyze, run_ast_fn, json) come from env
|
||||
// In actor spawn mode, also: nota, wota
|
||||
var ACTORDATA = actorsym
|
||||
var SYSYM = '__SYSTEM__'
|
||||
@@ -53,9 +53,9 @@ function ends_with(str, suffix) {
|
||||
var fd = use_embed('fd')
|
||||
var js = use_embed('js')
|
||||
|
||||
// Derive shop path from core_path (core_path = ~/.cell/packages/core)
|
||||
var packages_path = core_path + '/..'
|
||||
var shop_path = packages_path + '/..'
|
||||
// shop_path comes from env (bootstrap.cm passes it through)
|
||||
var packages_path = shop_path + '/packages'
|
||||
var core_path = packages_path + '/core'
|
||||
|
||||
var use_cache = {}
|
||||
use_cache['core/os'] = os
|
||||
|
||||
479
mcode.cm
479
mcode.cm
@@ -227,6 +227,463 @@ var mcode = function(ast) {
|
||||
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"
|
||||
}
|
||||
|
||||
// emit_add_decomposed: int path -> text path -> float path -> disrupt
|
||||
var emit_add_decomposed = function(dest, left, right, left_node, right_node) {
|
||||
var t0 = 0
|
||||
var t1 = 0
|
||||
var left_is_int = is_known_int(left_node)
|
||||
var left_is_text = is_known_text(left_node)
|
||||
var left_is_num = is_known_number(left_node)
|
||||
var right_is_int = is_known_int(right_node)
|
||||
var right_is_text = is_known_text(right_node)
|
||||
var right_is_num = is_known_number(right_node)
|
||||
var not_int = null
|
||||
var not_text = null
|
||||
var done = null
|
||||
var err = null
|
||||
|
||||
// Both sides known int
|
||||
if (left_is_int && right_is_int) {
|
||||
emit_3("add_int", dest, left, right)
|
||||
return null
|
||||
}
|
||||
// Both sides known text
|
||||
if (left_is_text && right_is_text) {
|
||||
emit_3("concat", dest, left, right)
|
||||
return null
|
||||
}
|
||||
// Both sides known number (but not both int)
|
||||
if (left_is_num && right_is_num) {
|
||||
if (left_is_int && right_is_int) {
|
||||
emit_3("add_int", dest, left, right)
|
||||
} else {
|
||||
emit_3("add_float", dest, left, right)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
not_int = gen_label("add_ni")
|
||||
not_text = gen_label("add_nt")
|
||||
done = gen_label("add_done")
|
||||
err = gen_label("add_err")
|
||||
|
||||
// Int path
|
||||
t0 = alloc_slot()
|
||||
if (!left_is_int) {
|
||||
emit_2("is_int", t0, left)
|
||||
emit_jump_cond("jump_false", t0, not_int)
|
||||
}
|
||||
t1 = alloc_slot()
|
||||
if (!right_is_int) {
|
||||
emit_2("is_int", t1, right)
|
||||
emit_jump_cond("jump_false", t1, not_int)
|
||||
}
|
||||
emit_3("add_int", dest, left, right)
|
||||
emit_jump(done)
|
||||
|
||||
// Text path
|
||||
emit_label(not_int)
|
||||
if (!left_is_text) {
|
||||
emit_2("is_text", t0, left)
|
||||
emit_jump_cond("jump_false", t0, not_text)
|
||||
}
|
||||
if (!right_is_text) {
|
||||
emit_2("is_text", t1, right)
|
||||
emit_jump_cond("jump_false", t1, not_text)
|
||||
}
|
||||
emit_3("concat", dest, left, right)
|
||||
emit_jump(done)
|
||||
|
||||
// Float path
|
||||
emit_label(not_text)
|
||||
if (!left_is_num) {
|
||||
emit_2("is_num", t0, left)
|
||||
emit_jump_cond("jump_false", t0, err)
|
||||
}
|
||||
if (!right_is_num) {
|
||||
emit_2("is_num", t1, right)
|
||||
emit_jump_cond("jump_false", t1, err)
|
||||
}
|
||||
emit_3("add_float", dest, left, right)
|
||||
emit_jump(done)
|
||||
|
||||
emit_label(err)
|
||||
emit_0("disrupt")
|
||||
emit_label(done)
|
||||
return null
|
||||
}
|
||||
|
||||
// emit_numeric_binop: int path -> float path -> disrupt
|
||||
var emit_numeric_binop = function(int_op, float_op, dest, left, right, left_node, right_node) {
|
||||
var t0 = 0
|
||||
var t1 = 0
|
||||
var left_is_int = is_known_int(left_node)
|
||||
var left_is_num = is_known_number(left_node)
|
||||
var right_is_int = is_known_int(right_node)
|
||||
var right_is_num = is_known_number(right_node)
|
||||
var not_int = null
|
||||
var done = null
|
||||
var err = null
|
||||
|
||||
// Both sides known int
|
||||
if (left_is_int && right_is_int) {
|
||||
emit_3(int_op, dest, left, right)
|
||||
return null
|
||||
}
|
||||
// Both sides known number (but not both int)
|
||||
if (left_is_num && right_is_num) {
|
||||
emit_3(float_op, dest, left, right)
|
||||
return null
|
||||
}
|
||||
|
||||
not_int = gen_label("num_ni")
|
||||
done = gen_label("num_done")
|
||||
err = gen_label("num_err")
|
||||
|
||||
t0 = alloc_slot()
|
||||
if (!left_is_int) {
|
||||
emit_2("is_int", t0, left)
|
||||
emit_jump_cond("jump_false", t0, not_int)
|
||||
}
|
||||
t1 = alloc_slot()
|
||||
if (!right_is_int) {
|
||||
emit_2("is_int", t1, right)
|
||||
emit_jump_cond("jump_false", t1, not_int)
|
||||
}
|
||||
emit_3(int_op, dest, left, right)
|
||||
emit_jump(done)
|
||||
|
||||
emit_label(not_int)
|
||||
if (!left_is_num) {
|
||||
emit_2("is_num", t0, left)
|
||||
emit_jump_cond("jump_false", t0, err)
|
||||
}
|
||||
if (!right_is_num) {
|
||||
emit_2("is_num", t1, right)
|
||||
emit_jump_cond("jump_false", t1, err)
|
||||
}
|
||||
emit_3(float_op, dest, left, right)
|
||||
emit_jump(done)
|
||||
|
||||
emit_label(err)
|
||||
emit_0("disrupt")
|
||||
emit_label(done)
|
||||
return null
|
||||
}
|
||||
|
||||
// emit_eq_decomposed: identical -> int -> float -> text -> null -> bool -> mismatch(false)
|
||||
var emit_eq_decomposed = function(dest, left, right, left_node, right_node) {
|
||||
var t0 = 0
|
||||
var t1 = 0
|
||||
var done = gen_label("eq_done")
|
||||
var not_int = gen_label("eq_ni")
|
||||
var not_num = gen_label("eq_nn")
|
||||
var not_text = gen_label("eq_nt")
|
||||
var not_null = gen_label("eq_nnl")
|
||||
var not_bool = gen_label("eq_nb")
|
||||
|
||||
// Identical check
|
||||
emit_3("is_identical", dest, left, right)
|
||||
emit_jump_cond("jump_true", dest, done)
|
||||
|
||||
// Int path
|
||||
t0 = alloc_slot()
|
||||
emit_2("is_int", t0, left)
|
||||
emit_jump_cond("jump_false", t0, not_int)
|
||||
t1 = alloc_slot()
|
||||
emit_2("is_int", t1, right)
|
||||
emit_jump_cond("jump_false", t1, not_int)
|
||||
emit_3("eq_int", dest, left, right)
|
||||
emit_jump(done)
|
||||
|
||||
// Float path
|
||||
emit_label(not_int)
|
||||
emit_2("is_num", t0, left)
|
||||
emit_jump_cond("jump_false", t0, not_num)
|
||||
emit_2("is_num", t1, right)
|
||||
emit_jump_cond("jump_false", t1, not_num)
|
||||
emit_3("eq_float", dest, left, right)
|
||||
emit_jump(done)
|
||||
|
||||
// Text path
|
||||
emit_label(not_num)
|
||||
emit_2("is_text", t0, left)
|
||||
emit_jump_cond("jump_false", t0, not_text)
|
||||
emit_2("is_text", t1, right)
|
||||
emit_jump_cond("jump_false", t1, not_text)
|
||||
emit_3("eq_text", dest, left, right)
|
||||
emit_jump(done)
|
||||
|
||||
// Null path
|
||||
emit_label(not_text)
|
||||
emit_2("is_null", t0, left)
|
||||
emit_jump_cond("jump_false", t0, not_null)
|
||||
emit_2("is_null", t1, right)
|
||||
emit_jump_cond("jump_false", t1, not_null)
|
||||
emit_1("true", dest)
|
||||
emit_jump(done)
|
||||
|
||||
// Bool path
|
||||
emit_label(not_null)
|
||||
emit_2("is_bool", t0, left)
|
||||
emit_jump_cond("jump_false", t0, not_bool)
|
||||
emit_2("is_bool", t1, right)
|
||||
emit_jump_cond("jump_false", t1, not_bool)
|
||||
emit_3("eq_bool", dest, left, right)
|
||||
emit_jump(done)
|
||||
|
||||
// Mismatch -> false
|
||||
emit_label(not_bool)
|
||||
emit_1("false", dest)
|
||||
emit_label(done)
|
||||
return null
|
||||
}
|
||||
|
||||
// emit_ne_decomposed: identical -> int -> float -> text -> null -> bool -> mismatch(true)
|
||||
var emit_ne_decomposed = function(dest, left, right, left_node, right_node) {
|
||||
var t0 = 0
|
||||
var t1 = 0
|
||||
var done = gen_label("ne_done")
|
||||
var not_ident = gen_label("ne_nid")
|
||||
var not_int = gen_label("ne_ni")
|
||||
var not_num = gen_label("ne_nn")
|
||||
var not_text = gen_label("ne_nt")
|
||||
var not_null = gen_label("ne_nnl")
|
||||
var not_bool = gen_label("ne_nb")
|
||||
|
||||
// Identical -> false
|
||||
emit_3("is_identical", dest, left, right)
|
||||
emit_jump_cond("jump_true", dest, not_ident)
|
||||
// If jump_true doesn't fire, dest already holds false, continue to checks
|
||||
emit_jump(not_int)
|
||||
|
||||
emit_label(not_ident)
|
||||
emit_1("false", dest)
|
||||
emit_jump(done)
|
||||
|
||||
// Int path
|
||||
emit_label(not_int)
|
||||
t0 = alloc_slot()
|
||||
emit_2("is_int", t0, left)
|
||||
emit_jump_cond("jump_false", t0, not_num)
|
||||
t1 = alloc_slot()
|
||||
emit_2("is_int", t1, right)
|
||||
emit_jump_cond("jump_false", t1, not_num)
|
||||
emit_3("ne_int", dest, left, right)
|
||||
emit_jump(done)
|
||||
|
||||
// Float path
|
||||
emit_label(not_num)
|
||||
emit_2("is_num", t0, left)
|
||||
emit_jump_cond("jump_false", t0, not_text)
|
||||
emit_2("is_num", t1, right)
|
||||
emit_jump_cond("jump_false", t1, not_text)
|
||||
emit_3("ne_float", dest, left, right)
|
||||
emit_jump(done)
|
||||
|
||||
// Text path
|
||||
emit_label(not_text)
|
||||
emit_2("is_text", t0, left)
|
||||
emit_jump_cond("jump_false", t0, not_null)
|
||||
emit_2("is_text", t1, right)
|
||||
emit_jump_cond("jump_false", t1, not_null)
|
||||
emit_3("ne_text", dest, left, right)
|
||||
emit_jump(done)
|
||||
|
||||
// Null path
|
||||
emit_label(not_null)
|
||||
emit_2("is_null", t0, left)
|
||||
emit_jump_cond("jump_false", t0, not_bool)
|
||||
emit_2("is_null", t1, right)
|
||||
emit_jump_cond("jump_false", t1, not_bool)
|
||||
emit_1("false", dest)
|
||||
emit_jump(done)
|
||||
|
||||
// Bool path
|
||||
var mismatch = gen_label("ne_mis")
|
||||
emit_label(not_bool)
|
||||
emit_2("is_bool", t0, left)
|
||||
emit_jump_cond("jump_false", t0, mismatch)
|
||||
emit_2("is_bool", t1, right)
|
||||
emit_jump_cond("jump_false", t1, mismatch)
|
||||
emit_3("ne_bool", dest, left, right)
|
||||
emit_jump(done)
|
||||
|
||||
// Mismatch -> true (ne of different types is true)
|
||||
emit_label(mismatch)
|
||||
emit_1("true", dest)
|
||||
emit_label(done)
|
||||
return null
|
||||
}
|
||||
|
||||
// emit_relational: int -> float -> text -> disrupt
|
||||
var emit_relational = function(int_op, float_op, text_op, dest, left, right, left_node, right_node) {
|
||||
var t0 = 0
|
||||
var t1 = 0
|
||||
var left_is_int = is_known_int(left_node)
|
||||
var left_is_num = is_known_number(left_node)
|
||||
var left_is_text = is_known_text(left_node)
|
||||
var right_is_int = is_known_int(right_node)
|
||||
var right_is_num = is_known_number(right_node)
|
||||
var right_is_text = is_known_text(right_node)
|
||||
var not_int = null
|
||||
var not_num = null
|
||||
var done = null
|
||||
var err = null
|
||||
|
||||
// Both known int
|
||||
if (left_is_int && right_is_int) {
|
||||
emit_3(int_op, dest, left, right)
|
||||
return null
|
||||
}
|
||||
// Both known number
|
||||
if (left_is_num && right_is_num) {
|
||||
emit_3(float_op, dest, left, right)
|
||||
return null
|
||||
}
|
||||
// Both known text
|
||||
if (left_is_text && right_is_text) {
|
||||
emit_3(text_op, dest, left, right)
|
||||
return null
|
||||
}
|
||||
|
||||
not_int = gen_label("rel_ni")
|
||||
not_num = gen_label("rel_nn")
|
||||
done = gen_label("rel_done")
|
||||
err = gen_label("rel_err")
|
||||
|
||||
t0 = alloc_slot()
|
||||
emit_2("is_int", t0, left)
|
||||
emit_jump_cond("jump_false", t0, not_int)
|
||||
t1 = alloc_slot()
|
||||
emit_2("is_int", t1, right)
|
||||
emit_jump_cond("jump_false", t1, not_int)
|
||||
emit_3(int_op, dest, left, right)
|
||||
emit_jump(done)
|
||||
|
||||
emit_label(not_int)
|
||||
emit_2("is_num", t0, left)
|
||||
emit_jump_cond("jump_false", t0, not_num)
|
||||
emit_2("is_num", t1, right)
|
||||
emit_jump_cond("jump_false", t1, not_num)
|
||||
emit_3(float_op, dest, left, right)
|
||||
emit_jump(done)
|
||||
|
||||
emit_label(not_num)
|
||||
emit_2("is_text", t0, left)
|
||||
emit_jump_cond("jump_false", t0, err)
|
||||
emit_2("is_text", t1, right)
|
||||
emit_jump_cond("jump_false", t1, err)
|
||||
emit_3(text_op, dest, left, right)
|
||||
emit_jump(done)
|
||||
|
||||
emit_label(err)
|
||||
emit_0("disrupt")
|
||||
emit_label(done)
|
||||
return null
|
||||
}
|
||||
|
||||
// emit_neg_decomposed: int path -> float path -> disrupt
|
||||
var emit_neg_decomposed = function(dest, src, src_node) {
|
||||
var t0 = 0
|
||||
var not_int = null
|
||||
var done = null
|
||||
var err = null
|
||||
|
||||
if (is_known_int(src_node)) {
|
||||
emit_2("neg_int", dest, src)
|
||||
return null
|
||||
}
|
||||
if (is_known_number(src_node)) {
|
||||
emit_2("neg_float", dest, src)
|
||||
return null
|
||||
}
|
||||
|
||||
not_int = gen_label("neg_ni")
|
||||
done = gen_label("neg_done")
|
||||
err = gen_label("neg_err")
|
||||
|
||||
t0 = alloc_slot()
|
||||
emit_2("is_int", t0, src)
|
||||
emit_jump_cond("jump_false", t0, not_int)
|
||||
emit_2("neg_int", dest, src)
|
||||
emit_jump(done)
|
||||
|
||||
emit_label(not_int)
|
||||
emit_2("is_num", t0, src)
|
||||
emit_jump_cond("jump_false", t0, err)
|
||||
emit_2("neg_float", dest, src)
|
||||
emit_jump(done)
|
||||
|
||||
emit_label(err)
|
||||
emit_0("disrupt")
|
||||
emit_label(done)
|
||||
return null
|
||||
}
|
||||
|
||||
// Central router: maps op string to decomposition helper
|
||||
var emit_binop = function(op_str, dest, left, right, left_node, right_node) {
|
||||
if (op_str == "add") {
|
||||
emit_add_decomposed(dest, left, right, left_node, right_node)
|
||||
} else if (op_str == "subtract") {
|
||||
emit_numeric_binop("sub_int", "sub_float", dest, left, right, left_node, right_node)
|
||||
} else if (op_str == "multiply") {
|
||||
emit_numeric_binop("mul_int", "mul_float", dest, left, right, left_node, right_node)
|
||||
} else if (op_str == "divide") {
|
||||
emit_numeric_binop("div_int", "div_float", dest, left, right, left_node, right_node)
|
||||
} else if (op_str == "modulo") {
|
||||
emit_numeric_binop("mod_int", "mod_float", dest, left, right, left_node, right_node)
|
||||
} else if (op_str == "eq") {
|
||||
emit_eq_decomposed(dest, left, right, left_node, right_node)
|
||||
} else if (op_str == "ne") {
|
||||
emit_ne_decomposed(dest, left, right, left_node, right_node)
|
||||
} else if (op_str == "lt") {
|
||||
emit_relational("lt_int", "lt_float", "lt_text", dest, left, right, left_node, right_node)
|
||||
} else if (op_str == "le") {
|
||||
emit_relational("le_int", "le_float", "le_text", dest, left, right, left_node, right_node)
|
||||
} else if (op_str == "gt") {
|
||||
emit_relational("gt_int", "gt_float", "gt_text", dest, left, right, left_node, right_node)
|
||||
} else if (op_str == "ge") {
|
||||
emit_relational("ge_int", "ge_float", "ge_text", dest, left, right, left_node, right_node)
|
||||
} else {
|
||||
// Passthrough for bitwise, pow, in, etc.
|
||||
emit_3(op_str, dest, left, right)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
var emit_get_prop = function(dest, obj, prop) {
|
||||
add_instr(["load", dest, obj, prop])
|
||||
}
|
||||
@@ -463,7 +920,7 @@ var mcode = function(ast) {
|
||||
if (op == null) {
|
||||
op = "add"
|
||||
}
|
||||
emit_3(op, dest, left_slot, right_slot)
|
||||
emit_binop(op, dest, left_slot, right_slot, left, right)
|
||||
return dest
|
||||
}
|
||||
|
||||
@@ -515,7 +972,7 @@ var mcode = function(ast) {
|
||||
}
|
||||
right_slot = gen_expr(right, -1)
|
||||
dest = alloc_slot()
|
||||
emit_3(op, dest, left_slot, right_slot)
|
||||
emit_binop(op, dest, left_slot, right_slot, null, right)
|
||||
if (level == 0) {
|
||||
local = find_var(name)
|
||||
if (local >= 0) {
|
||||
@@ -538,7 +995,7 @@ var mcode = function(ast) {
|
||||
emit_get_prop(old_val, obj_slot, prop)
|
||||
right_slot = gen_expr(right, -1)
|
||||
dest = alloc_slot()
|
||||
emit_3(op, dest, old_val, right_slot)
|
||||
emit_binop(op, dest, old_val, right_slot, null, right)
|
||||
emit_set_prop(obj_slot, prop, dest)
|
||||
return dest
|
||||
} else if (left_kind == "[") {
|
||||
@@ -550,7 +1007,7 @@ var mcode = function(ast) {
|
||||
emit_get_elem(old_val, obj_slot, idx_slot)
|
||||
right_slot = gen_expr(right, -1)
|
||||
dest = alloc_slot()
|
||||
emit_3(op, dest, old_val, right_slot)
|
||||
emit_binop(op, dest, old_val, right_slot, null, right)
|
||||
emit_set_elem(obj_slot, idx_slot, dest)
|
||||
return dest
|
||||
}
|
||||
@@ -679,6 +1136,7 @@ var mcode = function(ast) {
|
||||
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
|
||||
@@ -899,7 +1357,7 @@ var mcode = function(ast) {
|
||||
a0 = gen_expr(args_list[0], -1)
|
||||
a1 = gen_expr(args_list[1], -1)
|
||||
d = alloc_slot()
|
||||
emit_3(mop, d, a0, a1)
|
||||
emit_binop(mop, d, a0, a1, args_list[0], args_list[1])
|
||||
return d
|
||||
}
|
||||
|
||||
@@ -946,7 +1404,7 @@ var mcode = function(ast) {
|
||||
if (kind == "-unary") {
|
||||
operand_slot = gen_expr(expr.expression, -1)
|
||||
slot = alloc_slot()
|
||||
emit_2("neg", slot, operand_slot)
|
||||
emit_neg_decomposed(slot, operand_slot, expr.expression)
|
||||
return slot
|
||||
}
|
||||
if (kind == "+unary") {
|
||||
@@ -961,6 +1419,7 @@ var mcode = function(ast) {
|
||||
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
|
||||
@@ -983,7 +1442,7 @@ var mcode = function(ast) {
|
||||
emit_access_intrinsic(old_slot, name)
|
||||
}
|
||||
new_slot = alloc_slot()
|
||||
emit_3(arith_op, new_slot, old_slot, one_slot)
|
||||
emit_binop(arith_op, new_slot, old_slot, one_slot, null, one_node)
|
||||
if (level == 0) {
|
||||
local = find_var(name)
|
||||
if (local >= 0) {
|
||||
@@ -1003,7 +1462,7 @@ var mcode = function(ast) {
|
||||
old_slot = alloc_slot()
|
||||
emit_get_prop(old_slot, obj_slot, prop)
|
||||
new_slot = alloc_slot()
|
||||
emit_3(arith_op, new_slot, old_slot, one_slot)
|
||||
emit_binop(arith_op, new_slot, old_slot, one_slot, null, one_node)
|
||||
emit_set_prop(obj_slot, prop, new_slot)
|
||||
return postfix ? old_slot : new_slot
|
||||
} else if (operand_kind == "[") {
|
||||
@@ -1014,7 +1473,7 @@ var mcode = function(ast) {
|
||||
old_slot = alloc_slot()
|
||||
emit_get_elem(old_slot, obj_slot, idx_slot)
|
||||
new_slot = alloc_slot()
|
||||
emit_3(arith_op, new_slot, old_slot, one_slot)
|
||||
emit_binop(arith_op, new_slot, old_slot, one_slot, null, one_node)
|
||||
emit_set_elem(obj_slot, idx_slot, new_slot)
|
||||
return postfix ? old_slot : new_slot
|
||||
}
|
||||
@@ -1452,7 +1911,7 @@ var mcode = function(ast) {
|
||||
case_expr = case_node.expression
|
||||
case_val = gen_expr(case_expr, -1)
|
||||
cmp_slot = alloc_slot()
|
||||
emit_3("eq", cmp_slot, switch_val, case_val)
|
||||
emit_binop("eq", cmp_slot, switch_val, case_val, null, case_expr)
|
||||
emit_jump_cond("jump_true", cmp_slot, case_label)
|
||||
push(case_labels, case_label)
|
||||
}
|
||||
|
||||
BIN
mcode.mach
BIN
mcode.mach
Binary file not shown.
153
parse.cm
153
parse.cm
@@ -9,6 +9,9 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
var tok = null
|
||||
var got_lf = false
|
||||
var prev_tok = null
|
||||
var _control_depth = 0
|
||||
var _control_type = null
|
||||
var _expecting_body = false
|
||||
|
||||
var advance = function() {
|
||||
var t = null
|
||||
@@ -176,6 +179,9 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
var sub_ast = null
|
||||
var sub_stmt = null
|
||||
var sub_expr = null
|
||||
var meth_old_cd = 0
|
||||
var meth_old_ct = null
|
||||
var meth_old_eb = false
|
||||
|
||||
if (k == "number") {
|
||||
node = ast_node("number", start)
|
||||
@@ -399,6 +405,12 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
else if (tok.kind == "eof") parse_error(tok, "unterminated method parameter list")
|
||||
if (length(params) > 4) parse_error(tok, "functions cannot have more than 4 parameters")
|
||||
fn.arity = length(params)
|
||||
meth_old_cd = _control_depth
|
||||
meth_old_ct = _control_type
|
||||
meth_old_eb = _expecting_body
|
||||
_control_depth = 0
|
||||
_control_type = null
|
||||
_expecting_body = false
|
||||
if (tok.kind == "{") {
|
||||
advance()
|
||||
fn.statements = parse_block_statements()
|
||||
@@ -407,6 +419,9 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
} else {
|
||||
parse_error(tok, "expected '{' for method body")
|
||||
}
|
||||
_control_depth = meth_old_cd
|
||||
_control_type = meth_old_ct
|
||||
_expecting_body = meth_old_eb
|
||||
fn.function_nr = function_nr
|
||||
function_nr = function_nr + 1
|
||||
ast_node_end(fn)
|
||||
@@ -484,6 +499,9 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
var index = null
|
||||
var arg = null
|
||||
var args_list = null
|
||||
var one_node = null
|
||||
var binop_node = null
|
||||
var op = null
|
||||
if (node == null) return null
|
||||
while (true) {
|
||||
start = tok
|
||||
@@ -559,6 +577,10 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
var node = null
|
||||
var expr = null
|
||||
var k = tok.kind
|
||||
var operand = null
|
||||
var one_node = null
|
||||
var binop_node = null
|
||||
var op = null
|
||||
if (k == "!") {
|
||||
advance()
|
||||
node = ast_node("!", start)
|
||||
@@ -587,19 +609,22 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
ast_node_end(node)
|
||||
return node
|
||||
}
|
||||
if (k == "++") {
|
||||
if (k == "++" || k == "--") {
|
||||
advance()
|
||||
node = ast_node("++", start)
|
||||
node.expression = parse_unary()
|
||||
node.postfix = false
|
||||
ast_node_end(node)
|
||||
return node
|
||||
}
|
||||
if (k == "--") {
|
||||
advance()
|
||||
node = ast_node("--", start)
|
||||
node.expression = parse_unary()
|
||||
node.postfix = false
|
||||
operand = parse_unary()
|
||||
one_node = ast_node("number", start)
|
||||
one_node.number = 1
|
||||
one_node.value = "1"
|
||||
ast_node_end(one_node)
|
||||
op = "+"
|
||||
if (k == "--") op = "-"
|
||||
binop_node = ast_node(op, start)
|
||||
binop_node.left = operand
|
||||
binop_node.right = one_node
|
||||
ast_node_end(binop_node)
|
||||
node = ast_node("assign", start)
|
||||
node.left = operand
|
||||
node.right = binop_node
|
||||
ast_node_end(node)
|
||||
return node
|
||||
}
|
||||
@@ -684,6 +709,13 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
"&&=": "&&=", "||=": "||="
|
||||
}
|
||||
|
||||
var compound_binop = {
|
||||
"+=": "+", "-=": "-", "*=": "*", "/=": "/", "%=": "%",
|
||||
"<<=": "<<", ">>=": ">>", ">>>=": ">>>",
|
||||
"&=": "&", "^=": "^", "|=": "|", "**=": "**",
|
||||
"&&=": "&&", "||=": "||"
|
||||
}
|
||||
|
||||
parse_assign = function(unused) {
|
||||
var left_node = parse_ternary()
|
||||
var start = null
|
||||
@@ -692,6 +724,8 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
var node = null
|
||||
var left_kind = null
|
||||
var right_kind = null
|
||||
var binop = null
|
||||
var binop_node = null
|
||||
if (left_node == null) return null
|
||||
start = tok
|
||||
kind = assign_ops[tok.kind]
|
||||
@@ -704,12 +738,23 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
|
||||
advance()
|
||||
right_node = parse_assign()
|
||||
node = ast_node(kind, start)
|
||||
node.left = left_node
|
||||
node.right = right_node
|
||||
|
||||
if (left_node.kind == "[" && left_node.right == null) node.push = true
|
||||
if (right_node != null && right_node.kind == "[" && right_node.right == null) node.pop = true
|
||||
binop = compound_binop[kind]
|
||||
if (binop != null) {
|
||||
binop_node = ast_node(binop, start)
|
||||
binop_node.left = left_node
|
||||
binop_node.right = right_node
|
||||
ast_node_end(binop_node)
|
||||
node = ast_node("assign", start)
|
||||
node.left = left_node
|
||||
node.right = binop_node
|
||||
} else {
|
||||
node = ast_node(kind, start)
|
||||
node.left = left_node
|
||||
node.right = right_node
|
||||
if (left_node.kind == "[" && left_node.right == null) node.push = true
|
||||
if (right_node != null && right_node.kind == "[" && right_node.right == null) node.pop = true
|
||||
}
|
||||
|
||||
ast_node_end(node)
|
||||
return node
|
||||
@@ -788,6 +833,9 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
var prev_names = null
|
||||
var pname = null
|
||||
var old_dis = 0
|
||||
var old_cd = _control_depth
|
||||
var old_ct = _control_type
|
||||
var old_eb = _expecting_body
|
||||
|
||||
if (in_disruption) {
|
||||
parse_error(tok, "cannot define function inside disruption clause")
|
||||
@@ -834,6 +882,9 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
if (length(params) > 4) parse_error(tok, "functions cannot have more than 4 parameters")
|
||||
node.arity = length(params)
|
||||
|
||||
_control_depth = 0
|
||||
_control_type = null
|
||||
_expecting_body = false
|
||||
if (tok.kind == "{") {
|
||||
advance()
|
||||
stmts = parse_block_statements()
|
||||
@@ -859,6 +910,9 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
}
|
||||
}
|
||||
|
||||
_control_depth = old_cd
|
||||
_control_type = old_ct
|
||||
_expecting_body = old_eb
|
||||
node.function_nr = function_nr
|
||||
function_nr = function_nr + 1
|
||||
ast_node_end(node)
|
||||
@@ -875,6 +929,9 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
var expr = null
|
||||
var prev_names = null
|
||||
var pname = null
|
||||
var old_cd = _control_depth
|
||||
var old_ct = _control_type
|
||||
var old_eb = _expecting_body
|
||||
node.arrow = true
|
||||
|
||||
if (in_disruption) {
|
||||
@@ -925,6 +982,9 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
advance()
|
||||
}
|
||||
|
||||
_control_depth = 0
|
||||
_control_type = null
|
||||
_expecting_body = false
|
||||
if (tok.kind == "{") {
|
||||
advance()
|
||||
stmts = parse_block_statements()
|
||||
@@ -940,6 +1000,9 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
node.statements = stmts
|
||||
}
|
||||
|
||||
_control_depth = old_cd
|
||||
_control_type = old_ct
|
||||
_expecting_body = old_eb
|
||||
node.function_nr = function_nr
|
||||
function_nr = function_nr + 1
|
||||
ast_node_end(node)
|
||||
@@ -971,8 +1034,25 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
var elif = null
|
||||
var p1_tok = null
|
||||
var labeled_stmt = null
|
||||
var depth = 0
|
||||
var saved_ct = null
|
||||
var saved_cd = 0
|
||||
var saved_eb = false
|
||||
|
||||
if (k == "{") {
|
||||
if (!_expecting_body) {
|
||||
parse_error(start, "bare block '{ ... }' is not a valid statement; use a function, if, while, or for instead")
|
||||
advance()
|
||||
depth = 1
|
||||
while (tok.kind != "eof" && depth > 0) {
|
||||
if (tok.kind == "{") depth = depth + 1
|
||||
else if (tok.kind == "}") depth = depth - 1
|
||||
if (depth > 0) advance()
|
||||
}
|
||||
if (tok.kind == "}") advance()
|
||||
return null
|
||||
}
|
||||
_expecting_body = false
|
||||
node = ast_node("block", start)
|
||||
advance()
|
||||
stmts = parse_block_statements()
|
||||
@@ -983,6 +1063,9 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
}
|
||||
|
||||
if (k == "var" || k == "def") {
|
||||
if (_control_depth > 0) {
|
||||
parse_error(start, "'" + k + "' declarations must appear at function body level, not inside '" + _control_type + "'; move this declaration before the '" + _control_type + "' statement")
|
||||
}
|
||||
kind_name = k
|
||||
is_def = (k == "def")
|
||||
advance()
|
||||
@@ -1009,6 +1092,8 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
}
|
||||
} else if (is_def) {
|
||||
parse_error(start, "missing initializer for constant '" + var_name + "'")
|
||||
} else {
|
||||
parse_error(start, "'var' declarations must be initialized; use 'var " + var_name + " = null' if no value is needed")
|
||||
}
|
||||
ast_node_end(node)
|
||||
push(decls, node)
|
||||
@@ -1037,6 +1122,11 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
else parse_error(tok, "expected ')' after if condition")
|
||||
then_stmts = []
|
||||
node.then = then_stmts
|
||||
saved_ct = _control_type
|
||||
saved_cd = _control_depth
|
||||
_control_type = "if"
|
||||
_control_depth = _control_depth + 1
|
||||
_expecting_body = true
|
||||
body = parse_statement()
|
||||
if (body != null) push(then_stmts, body)
|
||||
else_ifs = []
|
||||
@@ -1044,15 +1134,22 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
if (tok.kind == "else") {
|
||||
advance()
|
||||
if (tok.kind == "if") {
|
||||
_control_depth = saved_cd
|
||||
_control_type = saved_ct
|
||||
elif = parse_statement()
|
||||
if (elif != null) push(else_ifs, elif)
|
||||
ast_node_end(node)
|
||||
return node
|
||||
} else {
|
||||
else_stmts = []
|
||||
node.else = else_stmts
|
||||
_expecting_body = true
|
||||
body = parse_statement()
|
||||
if (body != null) push(else_stmts, body)
|
||||
}
|
||||
}
|
||||
_control_depth = saved_cd
|
||||
_control_type = saved_ct
|
||||
ast_node_end(node)
|
||||
return node
|
||||
}
|
||||
@@ -1068,8 +1165,15 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
else parse_error(tok, "expected ')' after while condition")
|
||||
stmts = []
|
||||
node.statements = stmts
|
||||
saved_ct = _control_type
|
||||
saved_cd = _control_depth
|
||||
_control_type = "while"
|
||||
_control_depth = _control_depth + 1
|
||||
_expecting_body = true
|
||||
body = parse_statement()
|
||||
if (body != null) push(stmts, body)
|
||||
_control_depth = saved_cd
|
||||
_control_type = saved_ct
|
||||
ast_node_end(node)
|
||||
return node
|
||||
}
|
||||
@@ -1079,8 +1183,15 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
advance()
|
||||
stmts = []
|
||||
node.statements = stmts
|
||||
saved_ct = _control_type
|
||||
saved_cd = _control_depth
|
||||
_control_type = "do"
|
||||
_control_depth = _control_depth + 1
|
||||
_expecting_body = true
|
||||
body = parse_statement()
|
||||
if (body != null) push(stmts, body)
|
||||
_control_depth = saved_cd
|
||||
_control_type = saved_ct
|
||||
if (tok.kind == "while") advance()
|
||||
else parse_error(tok, "expected 'while' after do body")
|
||||
if (tok.kind == "(") advance()
|
||||
@@ -1101,6 +1212,7 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
else parse_error(tok, "expected '(' after for")
|
||||
if (tok.kind != ";") {
|
||||
if (tok.kind == "var" || tok.kind == "def") {
|
||||
parse_error(tok, "'" + tok.kind + "' declarations cannot appear in the for initializer; declare variables before the for loop")
|
||||
init = parse_statement()
|
||||
node.init = init
|
||||
} else {
|
||||
@@ -1124,8 +1236,15 @@ var parse = function(tokens, src, filename, tokenizer) {
|
||||
else parse_error(tok, "expected ')' after for clauses")
|
||||
stmts = []
|
||||
node.statements = stmts
|
||||
saved_ct = _control_type
|
||||
saved_cd = _control_depth
|
||||
_control_type = "for"
|
||||
_control_depth = _control_depth + 1
|
||||
_expecting_body = true
|
||||
body = parse_statement()
|
||||
if (body != null) push(stmts, body)
|
||||
_control_depth = saved_cd
|
||||
_control_type = saved_ct
|
||||
ast_node_end(node)
|
||||
return node
|
||||
}
|
||||
|
||||
BIN
parse.mach
BIN
parse.mach
Binary file not shown.
295
qbe.cm
295
qbe.cm
@@ -705,6 +705,257 @@ var ushr = function(p, ctx, a, b) {
|
||||
return shift_op(p, ctx, a, b, "shr")
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Decomposed per-type-path operations
|
||||
// These map directly to the new IR ops emitted by mcode.cm.
|
||||
// ============================================================
|
||||
|
||||
// --- Arithmetic (int path) ---
|
||||
// add_int: assume both operands are tagged ints. Overflow -> float.
|
||||
var add_int = function(p, ctx, a, b) {
|
||||
return ` %${p}.ia =l sar ${a}, 1
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.sum =l add %${p}.ia, %${p}.ib
|
||||
%${p}.lo =w csltl %${p}.sum, ${int32_min}
|
||||
%${p}.hi =w csgtl %${p}.sum, ${int32_max}
|
||||
%${p}.ov =w or %${p}.lo, %${p}.hi
|
||||
jnz %${p}.ov, @${p}.ov, @${p}.ok
|
||||
@${p}.ok
|
||||
%${p}.rw =w copy %${p}.sum
|
||||
%${p}.rext =l extuw %${p}.rw
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.ov
|
||||
%${p}.fd =d sltof %${p}.sum
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
var sub_int = function(p, ctx, a, b) {
|
||||
return ` %${p}.ia =l sar ${a}, 1
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.diff =l sub %${p}.ia, %${p}.ib
|
||||
%${p}.lo =w csltl %${p}.diff, ${int32_min}
|
||||
%${p}.hi =w csgtl %${p}.diff, ${int32_max}
|
||||
%${p}.ov =w or %${p}.lo, %${p}.hi
|
||||
jnz %${p}.ov, @${p}.ov, @${p}.ok
|
||||
@${p}.ok
|
||||
%${p}.rw =w copy %${p}.diff
|
||||
%${p}.rext =l extuw %${p}.rw
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.ov
|
||||
%${p}.fd =d sltof %${p}.diff
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
var mul_int = function(p, ctx, a, b) {
|
||||
return ` %${p}.ia =l sar ${a}, 1
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.prod =l mul %${p}.ia, %${p}.ib
|
||||
%${p}.lo =w csltl %${p}.prod, ${int32_min}
|
||||
%${p}.hi =w csgtl %${p}.prod, ${int32_max}
|
||||
%${p}.ov =w or %${p}.lo, %${p}.hi
|
||||
jnz %${p}.ov, @${p}.ov, @${p}.ok
|
||||
@${p}.ok
|
||||
%${p}.rw =w copy %${p}.prod
|
||||
%${p}.rext =l extuw %${p}.rw
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.ov
|
||||
%${p}.fd =d sltof %${p}.prod
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fd)
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
var div_int = function(p, ctx, a, b) {
|
||||
return ` %${p}.ia =w copy 0
|
||||
%${p}.tmp =l sar ${a}, 1
|
||||
%${p}.ia =w copy %${p}.tmp
|
||||
%${p}.ib =w copy 0
|
||||
%${p}.tmp2 =l sar ${b}, 1
|
||||
%${p}.ib =w copy %${p}.tmp2
|
||||
%${p}.div0 =w ceqw %${p}.ib, 0
|
||||
jnz %${p}.div0, @${p}.null, @${p}.chk
|
||||
@${p}.null
|
||||
%${p} =l copy ${js_null}
|
||||
jmp @${p}.done
|
||||
@${p}.chk
|
||||
%${p}.rem =w rem %${p}.ia, %${p}.ib
|
||||
%${p}.exact =w ceqw %${p}.rem, 0
|
||||
jnz %${p}.exact, @${p}.idiv, @${p}.fdiv
|
||||
@${p}.idiv
|
||||
%${p}.q =w div %${p}.ia, %${p}.ib
|
||||
%${p}.qext =l extuw %${p}.q
|
||||
%${p} =l shl %${p}.qext, 1
|
||||
jmp @${p}.done
|
||||
@${p}.fdiv
|
||||
%${p}.da =d swtof %${p}.ia
|
||||
%${p}.db =d swtof %${p}.ib
|
||||
%${p}.dr =d div %${p}.da, %${p}.db
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.dr)
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
var mod_int = function(p, ctx, a, b) {
|
||||
return ` %${p}.ia =w copy 0
|
||||
%${p}.tmp =l sar ${a}, 1
|
||||
%${p}.ia =w copy %${p}.tmp
|
||||
%${p}.ib =w copy 0
|
||||
%${p}.tmp2 =l sar ${b}, 1
|
||||
%${p}.ib =w copy %${p}.tmp2
|
||||
%${p}.div0 =w ceqw %${p}.ib, 0
|
||||
jnz %${p}.div0, @${p}.null, @${p}.do_mod
|
||||
@${p}.null
|
||||
%${p} =l copy ${js_null}
|
||||
jmp @${p}.done
|
||||
@${p}.do_mod
|
||||
%${p}.r =w rem %${p}.ia, %${p}.ib
|
||||
%${p}.rext =l extuw %${p}.r
|
||||
%${p} =l shl %${p}.rext, 1
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
var neg_int = function(p, ctx, v) {
|
||||
return ` %${p}.sl =l sar ${v}, 1
|
||||
%${p}.iw =w copy %${p}.sl
|
||||
%${p}.is_min =w ceqw %${p}.iw, ${int32_min}
|
||||
jnz %${p}.is_min, @${p}.ov, @${p}.ok
|
||||
@${p}.ov
|
||||
%${p}.fd =d swtof %${p}.iw
|
||||
%${p}.fdn =d neg %${p}.fd
|
||||
%${p} =l call $__JS_NewFloat64(l ${ctx}, d %${p}.fdn)
|
||||
jmp @${p}.done
|
||||
@${p}.ok
|
||||
%${p}.ni =w sub 0, %${p}.iw
|
||||
%${p}.niext =l extuw %${p}.ni
|
||||
%${p} =l shl %${p}.niext, 1
|
||||
@${p}.done
|
||||
`
|
||||
}
|
||||
|
||||
// --- Arithmetic (float path) ---
|
||||
var add_float = function(p, ctx, a, b) {
|
||||
return ` %${p} =l call $qbe_float_add(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var sub_float = function(p, ctx, a, b) {
|
||||
return ` %${p} =l call $qbe_float_sub(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var mul_float = function(p, ctx, a, b) {
|
||||
return ` %${p} =l call $qbe_float_mul(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var div_float = function(p, ctx, a, b) {
|
||||
return ` %${p} =l call $qbe_float_div(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var mod_float = function(p, ctx, a, b) {
|
||||
return ` %${p} =l call $qbe_float_mod(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
var neg_float = function(p, ctx, v) {
|
||||
return ` %${p} =l call $qbe_float_neg(l ${ctx}, l ${v})
|
||||
`
|
||||
}
|
||||
|
||||
// --- Text concat ---
|
||||
var concat = function(p, ctx, a, b) {
|
||||
return ` %${p} =l call $JS_ConcatString(l ${ctx}, l ${a}, l ${b})
|
||||
`
|
||||
}
|
||||
|
||||
// --- Comparisons (int path) ---
|
||||
var cmp_int = function(p, a, b, qbe_op) {
|
||||
return ` %${p}.ia =l sar ${a}, 1
|
||||
%${p}.ib =l sar ${b}, 1
|
||||
%${p}.iaw =w copy %${p}.ia
|
||||
%${p}.ibw =w copy %${p}.ib
|
||||
%${p}.cr =w ${qbe_op} %${p}.iaw, %${p}.ibw
|
||||
%${p}.crext =l extuw %${p}.cr
|
||||
%${p}.sh =l shl %${p}.crext, 5
|
||||
%${p} =l or %${p}.sh, 3
|
||||
`
|
||||
}
|
||||
|
||||
var eq_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "ceqw") }
|
||||
var ne_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "cnew") }
|
||||
var lt_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "csltw") }
|
||||
var le_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "cslew") }
|
||||
var gt_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "csgtw") }
|
||||
var ge_int = function(p, ctx, a, b) { return cmp_int(p, a, b, "csgew") }
|
||||
|
||||
// --- Comparisons (float path) ---
|
||||
var cmp_float = function(p, ctx, a, b, op_id) {
|
||||
return ` %${p}.fcr =w call $qbe_float_cmp(l ${ctx}, w ${op_id}, l ${a}, l ${b})
|
||||
%${p}.fcrext =l extuw %${p}.fcr
|
||||
%${p}.fsh =l shl %${p}.fcrext, 5
|
||||
%${p} =l or %${p}.fsh, 3
|
||||
`
|
||||
}
|
||||
|
||||
var eq_float = function(p, ctx, a, b) { return cmp_float(p, ctx, a, b, 0) }
|
||||
var ne_float = function(p, ctx, a, b) { return cmp_float(p, ctx, a, b, 1) }
|
||||
var lt_float = function(p, ctx, a, b) { return cmp_float(p, ctx, a, b, 2) }
|
||||
var le_float = function(p, ctx, a, b) { return cmp_float(p, ctx, a, b, 3) }
|
||||
var gt_float = function(p, ctx, a, b) { return cmp_float(p, ctx, a, b, 4) }
|
||||
var ge_float = function(p, ctx, a, b) { return cmp_float(p, ctx, a, b, 5) }
|
||||
|
||||
// --- Comparisons (text path) ---
|
||||
var cmp_text = function(p, ctx, a, b, qbe_op, eq_only) {
|
||||
return ` %${p}.scmp =w call $js_string_compare_value(l ${ctx}, l ${a}, l ${b}, w ${eq_only})
|
||||
%${p}.tcr =w ${qbe_op} %${p}.scmp, 0
|
||||
%${p}.tcrext =l extuw %${p}.tcr
|
||||
%${p}.tsh =l shl %${p}.tcrext, 5
|
||||
%${p} =l or %${p}.tsh, 3
|
||||
`
|
||||
}
|
||||
|
||||
var eq_text = function(p, ctx, a, b) { return cmp_text(p, ctx, a, b, "ceqw", 1) }
|
||||
var ne_text = function(p, ctx, a, b) { return cmp_text(p, ctx, a, b, "cnew", 1) }
|
||||
var lt_text = function(p, ctx, a, b) { return cmp_text(p, ctx, a, b, "csltw", 0) }
|
||||
var le_text = function(p, ctx, a, b) { return cmp_text(p, ctx, a, b, "cslew", 0) }
|
||||
var gt_text = function(p, ctx, a, b) { return cmp_text(p, ctx, a, b, "csgtw", 0) }
|
||||
var ge_text = function(p, ctx, a, b) { return cmp_text(p, ctx, a, b, "csgew", 0) }
|
||||
|
||||
// --- Comparisons (bool path) ---
|
||||
var eq_bool = function(p, a, b) {
|
||||
return ` %${p}.cr =w ceql ${a}, ${b}
|
||||
%${p}.crext =l extuw %${p}.cr
|
||||
%${p}.sh =l shl %${p}.crext, 5
|
||||
%${p} =l or %${p}.sh, 3
|
||||
`
|
||||
}
|
||||
|
||||
var ne_bool = function(p, a, b) {
|
||||
return ` %${p}.cr =w cnel ${a}, ${b}
|
||||
%${p}.crext =l extuw %${p}.cr
|
||||
%${p}.sh =l shl %${p}.crext, 5
|
||||
%${p} =l or %${p}.sh, 3
|
||||
`
|
||||
}
|
||||
|
||||
// --- Type guard: is_identical ---
|
||||
var is_identical = function(p, a, b) {
|
||||
return ` %${p}.cr =w ceql ${a}, ${b}
|
||||
%${p}.crext =l extuw %${p}.cr
|
||||
%${p}.sh =l shl %${p}.crext, 5
|
||||
%${p} =l or %${p}.sh, 3
|
||||
`
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Module export
|
||||
// ============================================================
|
||||
@@ -760,5 +1011,47 @@ return {
|
||||
bxor: bxor,
|
||||
shl: shl,
|
||||
shr: shr,
|
||||
ushr: ushr
|
||||
ushr: ushr,
|
||||
// decomposed arithmetic (int path)
|
||||
add_int: add_int,
|
||||
sub_int: sub_int,
|
||||
mul_int: mul_int,
|
||||
div_int: div_int,
|
||||
mod_int: mod_int,
|
||||
neg_int: neg_int,
|
||||
// decomposed arithmetic (float path)
|
||||
add_float: add_float,
|
||||
sub_float: sub_float,
|
||||
mul_float: mul_float,
|
||||
div_float: div_float,
|
||||
mod_float: mod_float,
|
||||
neg_float: neg_float,
|
||||
// text concat
|
||||
concat: concat,
|
||||
// decomposed comparisons (int)
|
||||
eq_int: eq_int,
|
||||
ne_int: ne_int,
|
||||
lt_int: lt_int,
|
||||
le_int: le_int,
|
||||
gt_int: gt_int,
|
||||
ge_int: ge_int,
|
||||
// decomposed comparisons (float)
|
||||
eq_float: eq_float,
|
||||
ne_float: ne_float,
|
||||
lt_float: lt_float,
|
||||
le_float: le_float,
|
||||
gt_float: gt_float,
|
||||
ge_float: ge_float,
|
||||
// decomposed comparisons (text)
|
||||
eq_text: eq_text,
|
||||
ne_text: ne_text,
|
||||
lt_text: lt_text,
|
||||
le_text: le_text,
|
||||
gt_text: gt_text,
|
||||
ge_text: ge_text,
|
||||
// decomposed comparisons (bool)
|
||||
eq_bool: eq_bool,
|
||||
ne_bool: ne_bool,
|
||||
// type guard
|
||||
is_identical: is_identical
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@ int run_c_test_suite(JSContext *ctx);
|
||||
static int run_test_suite(size_t heap_size);
|
||||
|
||||
cell_rt *root_cell = NULL;
|
||||
static char *shop_path = NULL;
|
||||
static char *core_path = NULL;
|
||||
static JSRuntime *g_runtime = NULL;
|
||||
|
||||
@@ -39,31 +40,52 @@ static const char* get_home_dir(void) {
|
||||
return home;
|
||||
}
|
||||
|
||||
// Find and verify the cell shop at ~/.cell
|
||||
int find_cell_shop(void)
|
||||
// Find and verify the cell shop
|
||||
// Precedence: override (--shop flag) > CELL_SHOP env var > ~/.cell
|
||||
int find_cell_shop(const char *override_path)
|
||||
{
|
||||
const char *home = get_home_dir();
|
||||
if (!home) {
|
||||
printf("ERROR: Could not determine home directory. Set HOME environment variable.\n");
|
||||
return 0;
|
||||
if (override_path) {
|
||||
shop_path = strdup(override_path);
|
||||
} else {
|
||||
const char *env = getenv("CELL_SHOP");
|
||||
if (env) {
|
||||
shop_path = strdup(env);
|
||||
} else {
|
||||
const char *home = get_home_dir();
|
||||
if (!home) {
|
||||
printf("ERROR: Could not determine home directory. Set HOME environment variable.\n");
|
||||
return 0;
|
||||
}
|
||||
size_t path_len = strlen(home) + strlen("/" CELL_SHOP_DIR) + 1;
|
||||
shop_path = malloc(path_len);
|
||||
if (!shop_path) {
|
||||
printf("ERROR: Could not allocate memory for shop path\n");
|
||||
return 0;
|
||||
}
|
||||
snprintf(shop_path, path_len, "%s/" CELL_SHOP_DIR, home);
|
||||
}
|
||||
}
|
||||
|
||||
// Build path to ~/.cell/core
|
||||
size_t path_len = strlen(home) + strlen("/" CELL_SHOP_DIR "/" CELL_CORE_DIR) + 1;
|
||||
core_path = malloc(path_len);
|
||||
// Derive core_path from shop_path
|
||||
size_t core_len = strlen(shop_path) + strlen("/" CELL_CORE_DIR) + 1;
|
||||
core_path = malloc(core_len);
|
||||
if (!core_path) {
|
||||
printf("ERROR: Could not allocate memory for core path\n");
|
||||
free(shop_path);
|
||||
shop_path = NULL;
|
||||
return 0;
|
||||
}
|
||||
snprintf(core_path, path_len, "%s/" CELL_SHOP_DIR "/" CELL_CORE_DIR, home);
|
||||
snprintf(core_path, core_len, "%s/" CELL_CORE_DIR, shop_path);
|
||||
|
||||
// Check if the core directory exists
|
||||
struct stat st;
|
||||
if (stat(core_path, &st) != 0 || !S_ISDIR(st.st_mode)) {
|
||||
printf("ERROR: Cell shop not found at %s/" CELL_SHOP_DIR "\n", home);
|
||||
printf("ERROR: Cell shop not found at %s\n", shop_path);
|
||||
printf("Run 'cell install' to set up the cell environment.\n");
|
||||
free(core_path);
|
||||
free(shop_path);
|
||||
core_path = NULL;
|
||||
shop_path = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -188,8 +210,8 @@ void script_startup(cell_rt *prt)
|
||||
JS_SetPropertyStr(js, hidden_env, "args", JS_NULL);
|
||||
JS_SetPropertyStr(js, hidden_env, "use_mcode", JS_NewBool(js, 0));
|
||||
|
||||
if (core_path) {
|
||||
JS_SetPropertyStr(js, hidden_env, "core_path", JS_NewString(js, core_path));
|
||||
if (shop_path) {
|
||||
JS_SetPropertyStr(js, hidden_env, "shop_path", JS_NewString(js, shop_path));
|
||||
}
|
||||
|
||||
// Stone the environment
|
||||
@@ -260,6 +282,7 @@ static void print_usage(const char *prog)
|
||||
printf("Usage: %s [options] <script> [args...]\n\n", prog);
|
||||
printf("Run a cell script (.ce actor or .cm module).\n\n");
|
||||
printf("Options:\n");
|
||||
printf(" --shop <path> Set shop path (overrides CELL_SHOP env var)\n");
|
||||
printf(" --mcode <script> [args] Run through mcode compilation pipeline\n");
|
||||
printf(" --test [heap_size] Run C test suite\n");
|
||||
printf(" -h, --help Show this help message\n");
|
||||
@@ -291,12 +314,26 @@ int cell_init(int argc, char **argv)
|
||||
/* Default: run script through bootstrap pipeline */
|
||||
int use_mcode = 0;
|
||||
int arg_start = 1;
|
||||
if (argc >= 3 && strcmp(argv[1], "--mcode") == 0) {
|
||||
use_mcode = 1;
|
||||
arg_start = 2;
|
||||
const char *shop_override = NULL;
|
||||
|
||||
// Parse flags (order-independent)
|
||||
while (arg_start < argc && argv[arg_start][0] == '-') {
|
||||
if (strcmp(argv[arg_start], "--mcode") == 0) {
|
||||
use_mcode = 1;
|
||||
arg_start++;
|
||||
} else if (strcmp(argv[arg_start], "--shop") == 0) {
|
||||
if (arg_start + 1 >= argc) {
|
||||
printf("ERROR: --shop requires a path argument\n");
|
||||
return 1;
|
||||
}
|
||||
shop_override = argv[arg_start + 1];
|
||||
arg_start += 2;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!find_cell_shop()) return 1;
|
||||
if (!find_cell_shop(shop_override)) return 1;
|
||||
|
||||
actor_initialize();
|
||||
|
||||
@@ -357,7 +394,7 @@ int cell_init(int argc, char **argv)
|
||||
|
||||
JSValue hidden_env = JS_NewObject(ctx);
|
||||
JS_SetPropertyStr(ctx, hidden_env, "os", js_os_use(ctx));
|
||||
JS_SetPropertyStr(ctx, hidden_env, "core_path", JS_NewString(ctx, core_path));
|
||||
JS_SetPropertyStr(ctx, hidden_env, "shop_path", JS_NewString(ctx, shop_path));
|
||||
JS_SetPropertyStr(ctx, hidden_env, "use_mcode", JS_NewBool(ctx, use_mcode));
|
||||
JS_SetPropertyStr(ctx, hidden_env, "actorsym", JS_DupValue(ctx, cli_rt->actor_sym_ref.val));
|
||||
JS_SetPropertyStr(ctx, hidden_env, "json", js_json_use(ctx));
|
||||
|
||||
214
source/mcode.c
214
source/mcode.c
@@ -486,6 +486,220 @@ JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
|
||||
frame->slots[dest] = JS_NewFloat64(ctx, -d);
|
||||
}
|
||||
}
|
||||
/* ---- Compiler-internal type guards ---- */
|
||||
else if (strcmp(op, "is_int") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = JS_IsInt(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
|
||||
}
|
||||
else if (strcmp(op, "is_num") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = JS_IsNumber(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
|
||||
}
|
||||
else if (strcmp(op, "is_text") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = JS_IsText(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
|
||||
}
|
||||
else if (strcmp(op, "is_null") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = JS_IsNull(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
|
||||
}
|
||||
else if (strcmp(op, "is_bool") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = (JS_VALUE_GET_TAG(frame->slots[(int)a2->valuedouble]) == JS_TAG_BOOL) ? JS_TRUE : JS_FALSE;
|
||||
}
|
||||
else if (strcmp(op, "is_identical") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = (frame->slots[(int)a2->valuedouble] == frame->slots[(int)a3->valuedouble]) ? JS_TRUE : JS_FALSE;
|
||||
}
|
||||
|
||||
/* ---- Specialized arithmetic (int) ---- */
|
||||
else if (strcmp(op, "add_int") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
int64_t r = (int64_t)JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) + (int64_t)JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]);
|
||||
frame->slots[dest] = (r >= INT32_MIN && r <= INT32_MAX) ? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r);
|
||||
}
|
||||
else if (strcmp(op, "sub_int") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
int64_t r = (int64_t)JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) - (int64_t)JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]);
|
||||
frame->slots[dest] = (r >= INT32_MIN && r <= INT32_MAX) ? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r);
|
||||
}
|
||||
else if (strcmp(op, "mul_int") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
int64_t r = (int64_t)JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) * (int64_t)JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]);
|
||||
frame->slots[dest] = (r >= INT32_MIN && r <= INT32_MAX) ? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r);
|
||||
}
|
||||
else if (strcmp(op, "div_int") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
int32_t ia = JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]);
|
||||
int32_t ib = JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]);
|
||||
if (ib == 0) frame->slots[dest] = JS_NULL;
|
||||
else if (ia % ib == 0) frame->slots[dest] = JS_NewInt32(ctx, ia / ib);
|
||||
else frame->slots[dest] = JS_NewFloat64(ctx, (double)ia / (double)ib);
|
||||
}
|
||||
else if (strcmp(op, "mod_int") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
int32_t ib = JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]);
|
||||
if (ib == 0) frame->slots[dest] = JS_NULL;
|
||||
else frame->slots[dest] = JS_NewInt32(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) % ib);
|
||||
}
|
||||
else if (strcmp(op, "neg_int") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
int32_t i = JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]);
|
||||
if (i == INT32_MIN) frame->slots[dest] = JS_NewFloat64(ctx, -(double)i);
|
||||
else frame->slots[dest] = JS_NewInt32(ctx, -i);
|
||||
}
|
||||
|
||||
/* ---- Specialized arithmetic (float) ---- */
|
||||
else if (strcmp(op, "add_float") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
double a, b;
|
||||
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
|
||||
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
|
||||
frame->slots[dest] = JS_NewFloat64(ctx, a + b);
|
||||
}
|
||||
else if (strcmp(op, "sub_float") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
double a, b;
|
||||
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
|
||||
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
|
||||
frame->slots[dest] = JS_NewFloat64(ctx, a - b);
|
||||
}
|
||||
else if (strcmp(op, "mul_float") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
double a, b;
|
||||
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
|
||||
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
|
||||
frame->slots[dest] = JS_NewFloat64(ctx, a * b);
|
||||
}
|
||||
else if (strcmp(op, "div_float") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
double a, b;
|
||||
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
|
||||
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
|
||||
if (b == 0.0) frame->slots[dest] = JS_NULL;
|
||||
else frame->slots[dest] = JS_NewFloat64(ctx, a / b);
|
||||
}
|
||||
else if (strcmp(op, "mod_float") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
double a, b;
|
||||
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
|
||||
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
|
||||
if (b == 0.0) frame->slots[dest] = JS_NULL;
|
||||
else frame->slots[dest] = JS_NewFloat64(ctx, fmod(a, b));
|
||||
}
|
||||
else if (strcmp(op, "neg_float") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
double d;
|
||||
JS_ToFloat64(ctx, &d, frame->slots[(int)a2->valuedouble]);
|
||||
frame->slots[dest] = JS_NewFloat64(ctx, -d);
|
||||
}
|
||||
|
||||
/* ---- Specialized comparisons (int) ---- */
|
||||
else if (strcmp(op, "eq_int") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) == JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]));
|
||||
}
|
||||
else if (strcmp(op, "ne_int") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) != JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]));
|
||||
}
|
||||
else if (strcmp(op, "lt_int") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) < JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]));
|
||||
}
|
||||
else if (strcmp(op, "le_int") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) <= JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]));
|
||||
}
|
||||
else if (strcmp(op, "gt_int") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) > JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]));
|
||||
}
|
||||
else if (strcmp(op, "ge_int") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) >= JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]));
|
||||
}
|
||||
|
||||
/* ---- Specialized comparisons (float) ---- */
|
||||
else if (strcmp(op, "eq_float") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
double a, b;
|
||||
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
|
||||
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
|
||||
frame->slots[dest] = JS_NewBool(ctx, a == b);
|
||||
}
|
||||
else if (strcmp(op, "ne_float") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
double a, b;
|
||||
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
|
||||
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
|
||||
frame->slots[dest] = JS_NewBool(ctx, a != b);
|
||||
}
|
||||
else if (strcmp(op, "lt_float") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
double a, b;
|
||||
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
|
||||
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
|
||||
frame->slots[dest] = JS_NewBool(ctx, a < b);
|
||||
}
|
||||
else if (strcmp(op, "le_float") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
double a, b;
|
||||
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
|
||||
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
|
||||
frame->slots[dest] = JS_NewBool(ctx, a <= b);
|
||||
}
|
||||
else if (strcmp(op, "gt_float") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
double a, b;
|
||||
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
|
||||
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
|
||||
frame->slots[dest] = JS_NewBool(ctx, a > b);
|
||||
}
|
||||
else if (strcmp(op, "ge_float") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
double a, b;
|
||||
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
|
||||
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
|
||||
frame->slots[dest] = JS_NewBool(ctx, a >= b);
|
||||
}
|
||||
|
||||
/* ---- Specialized comparisons (text) ---- */
|
||||
else if (strcmp(op, "eq_text") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble], TRUE) == 0);
|
||||
}
|
||||
else if (strcmp(op, "ne_text") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble], TRUE) != 0);
|
||||
}
|
||||
else if (strcmp(op, "lt_text") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble], FALSE) < 0);
|
||||
}
|
||||
else if (strcmp(op, "le_text") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble], FALSE) <= 0);
|
||||
}
|
||||
else if (strcmp(op, "gt_text") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble], FALSE) > 0);
|
||||
}
|
||||
else if (strcmp(op, "ge_text") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble], FALSE) >= 0);
|
||||
}
|
||||
|
||||
/* ---- Specialized comparisons (bool) ---- */
|
||||
else if (strcmp(op, "eq_bool") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = (frame->slots[(int)a2->valuedouble] == frame->slots[(int)a3->valuedouble]) ? JS_TRUE : JS_FALSE;
|
||||
}
|
||||
else if (strcmp(op, "ne_bool") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
frame->slots[dest] = (frame->slots[(int)a2->valuedouble] != frame->slots[(int)a3->valuedouble]) ? JS_TRUE : JS_FALSE;
|
||||
}
|
||||
|
||||
else if (strcmp(op, "abs") == 0) {
|
||||
int dest = (int)a1->valuedouble;
|
||||
JSValue v = frame->slots[(int)a2->valuedouble];
|
||||
|
||||
165
tests/decl_restrictions.ce
Normal file
165
tests/decl_restrictions.ce
Normal file
@@ -0,0 +1,165 @@
|
||||
// Declaration restriction tests
|
||||
// Run: ./cell tests/decl_restrictions.ce
|
||||
|
||||
var tokenize = use("tokenize")
|
||||
var parse = use("parse")
|
||||
|
||||
var passed = 0
|
||||
var failed = 0
|
||||
var error_names = []
|
||||
var error_reasons = []
|
||||
var fail_msg = ""
|
||||
|
||||
var _i = 0
|
||||
for (_i = 0; _i < 20; _i++) {
|
||||
error_names[] = null
|
||||
error_reasons[] = null
|
||||
}
|
||||
|
||||
var fail = function(msg) {
|
||||
fail_msg = msg
|
||||
disrupt
|
||||
}
|
||||
|
||||
var run = function(name, fn) {
|
||||
fail_msg = ""
|
||||
fn()
|
||||
passed = passed + 1
|
||||
} disruption {
|
||||
error_names[failed] = name
|
||||
error_reasons[failed] = fail_msg == "" ? "disruption" : fail_msg
|
||||
failed = failed + 1
|
||||
}
|
||||
|
||||
var parse_snippet = function(src) {
|
||||
var result = tokenize(src, "<test>")
|
||||
var ast = parse(result.tokens, src, "<test>", tokenize)
|
||||
return ast
|
||||
}
|
||||
|
||||
var has_error = function(ast, substring) {
|
||||
if (ast.errors == null) return false
|
||||
var i = 0
|
||||
while (i < length(ast.errors)) {
|
||||
if (search(ast.errors[i].message, substring) != null) return true
|
||||
i = i + 1
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
var has_no_errors = function(ast) {
|
||||
return ast.errors == null || length(ast.errors) == 0
|
||||
}
|
||||
|
||||
// === BARE BLOCK ===
|
||||
|
||||
run("bare block rejected", function() {
|
||||
var ast = parse_snippet("{ var x = 1 }")
|
||||
if (!has_error(ast, "bare block")) fail("expected 'bare block' error, got: " + text(ast.errors))
|
||||
})
|
||||
|
||||
// === VAR IN IF (braces) ===
|
||||
|
||||
run("var in if braces", function() {
|
||||
var ast = parse_snippet("if (true) { var x = 1 }")
|
||||
if (!has_error(ast, "not inside 'if'")) fail("expected 'not inside if' error, got: " + text(ast.errors))
|
||||
})
|
||||
|
||||
// === VAR IN IF (no braces) ===
|
||||
|
||||
run("var in if no braces", function() {
|
||||
var ast = parse_snippet("if (true) var x = 1")
|
||||
if (!has_error(ast, "not inside 'if'")) fail("expected 'not inside if' error, got: " + text(ast.errors))
|
||||
})
|
||||
|
||||
// === VAR IN WHILE ===
|
||||
|
||||
run("var in while", function() {
|
||||
var ast = parse_snippet("while (true) { var x = 1; break }")
|
||||
if (!has_error(ast, "not inside 'while'")) fail("expected 'not inside while' error, got: " + text(ast.errors))
|
||||
})
|
||||
|
||||
// === VAR IN FOR INIT ===
|
||||
|
||||
run("var in for init", function() {
|
||||
var ast = parse_snippet("for (var i = 0; i < 1; i++) {}")
|
||||
if (!has_error(ast, "for initializer")) fail("expected 'for initializer' error, got: " + text(ast.errors))
|
||||
})
|
||||
|
||||
// === VAR IN FOR BODY ===
|
||||
|
||||
run("var in for body", function() {
|
||||
var ast = parse_snippet("var i = 0; for (i = 0; i < 1; i++) { var x = 1 }")
|
||||
if (!has_error(ast, "not inside 'for'")) fail("expected 'not inside for' error, got: " + text(ast.errors))
|
||||
})
|
||||
|
||||
// === VAR IN DO ===
|
||||
|
||||
run("var in do", function() {
|
||||
var ast = parse_snippet("do { var x = 1; break } while (true)")
|
||||
if (!has_error(ast, "not inside 'do'")) fail("expected 'not inside do' error, got: " + text(ast.errors))
|
||||
})
|
||||
|
||||
// === DEF IN IF ===
|
||||
|
||||
run("def in if", function() {
|
||||
var ast = parse_snippet("if (true) { def x = 1 }")
|
||||
if (!has_error(ast, "not inside 'if'")) fail("expected 'not inside if' error, got: " + text(ast.errors))
|
||||
})
|
||||
|
||||
// === UNINITIALIZED VAR ===
|
||||
|
||||
run("uninitialized var", function() {
|
||||
var ast = parse_snippet("var x")
|
||||
if (!has_error(ast, "must be initialized")) fail("expected 'must be initialized' error, got: " + text(ast.errors))
|
||||
})
|
||||
|
||||
// === NESTED: VAR IN IF INSIDE WHILE ===
|
||||
|
||||
run("nested var in if inside while", function() {
|
||||
var ast = parse_snippet("while (true) { if (true) { var x = 1 }; break }")
|
||||
if (!has_error(ast, "not inside 'if'")) fail("expected 'not inside if' error, got: " + text(ast.errors))
|
||||
})
|
||||
|
||||
// === VALID: NESTED FUNCTION ===
|
||||
|
||||
run("valid nested fn in control flow", function() {
|
||||
var ast = parse_snippet("if (true) { var fn = function() { var x = 1; return x } }")
|
||||
// The var inside the function is fine; only the var fn = ... inside if should error
|
||||
if (!has_error(ast, "not inside 'if'")) fail("expected error for var fn inside if")
|
||||
// But there should NOT be an error about var x inside the function body
|
||||
var i = 0
|
||||
var bad = false
|
||||
if (ast.errors != null) {
|
||||
while (i < length(ast.errors)) {
|
||||
if (search(ast.errors[i].message, "var x") != null) bad = true
|
||||
i = i + 1
|
||||
}
|
||||
}
|
||||
if (bad) fail("should not error on var inside nested function")
|
||||
})
|
||||
|
||||
// === VALID: NORMAL VAR ===
|
||||
|
||||
run("valid normal var", function() {
|
||||
var ast = parse_snippet("var x = 1")
|
||||
if (!has_no_errors(ast)) fail("expected no errors, got: " + text(ast.errors))
|
||||
})
|
||||
|
||||
// === VALID: VAR IN FUNCTION BODY ===
|
||||
|
||||
run("valid var in function body", function() {
|
||||
var ast = parse_snippet("var fn = function() { var x = 1; return x }")
|
||||
if (!has_no_errors(ast)) fail("expected no errors, got: " + text(ast.errors))
|
||||
})
|
||||
|
||||
// === SUMMARY ===
|
||||
|
||||
print(text(passed) + " passed, " + text(failed) + " failed out of " + text(passed + failed))
|
||||
var _j = 0
|
||||
if (failed > 0) {
|
||||
print("")
|
||||
for (_j = 0; _j < failed; _j++) {
|
||||
print(" FAIL " + error_names[_j] + ": " + error_reasons[_j])
|
||||
}
|
||||
}
|
||||
191
vm_suite.ce
191
vm_suite.ce
@@ -1058,6 +1058,197 @@ run("modulo equals", function() {
|
||||
if (x != 1) fail("modulo equals failed")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// COMPOUND ASSIGNMENT ON PROPERTIES
|
||||
// ============================================================================
|
||||
|
||||
run("plus equals on property", function() {
|
||||
var obj = {x: 10}
|
||||
obj.x += 5
|
||||
if (obj.x != 15) fail("obj.x += 5")
|
||||
})
|
||||
|
||||
run("minus equals on property", function() {
|
||||
var obj = {x: 10}
|
||||
obj.x -= 3
|
||||
if (obj.x != 7) fail("obj.x -= 3")
|
||||
})
|
||||
|
||||
run("times equals on property", function() {
|
||||
var obj = {x: 4}
|
||||
obj.x *= 3
|
||||
if (obj.x != 12) fail("obj.x *= 3")
|
||||
})
|
||||
|
||||
run("divide equals on property", function() {
|
||||
var obj = {x: 12}
|
||||
obj.x /= 3
|
||||
if (obj.x != 4) fail("obj.x /= 3")
|
||||
})
|
||||
|
||||
run("modulo equals on property", function() {
|
||||
var obj = {x: 10}
|
||||
obj.x %= 3
|
||||
if (obj.x != 1) fail("obj.x %= 3")
|
||||
})
|
||||
|
||||
run("compound assign preserves other properties", function() {
|
||||
var obj = {x: 10, y: 20}
|
||||
obj.x += 5
|
||||
if (obj.x != 15) fail("x wrong")
|
||||
if (obj.y != 20) fail("y changed")
|
||||
})
|
||||
|
||||
run("compound assign nested property", function() {
|
||||
var obj = {inner: {val: 10}}
|
||||
obj.inner.val += 5
|
||||
if (obj.inner.val != 15) fail("nested += failed")
|
||||
})
|
||||
|
||||
run("compound assign chained", function() {
|
||||
var obj = {x: 1}
|
||||
obj.x += 2
|
||||
obj.x += 3
|
||||
obj.x += 4
|
||||
if (obj.x != 10) fail("chained += failed")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// COMPOUND ASSIGNMENT ON ARRAY ELEMENTS
|
||||
// ============================================================================
|
||||
|
||||
run("plus equals on array element", function() {
|
||||
var arr = [100, 200, 300]
|
||||
arr[0] += 50
|
||||
if (arr[0] != 150) fail("arr[0] += 50")
|
||||
})
|
||||
|
||||
run("minus equals on array element", function() {
|
||||
var arr = [100, 200, 300]
|
||||
arr[1] -= 50
|
||||
if (arr[1] != 150) fail("arr[1] -= 50")
|
||||
})
|
||||
|
||||
run("times equals on array element", function() {
|
||||
var arr = [5]
|
||||
arr[0] *= 6
|
||||
if (arr[0] != 30) fail("arr[0] *= 6")
|
||||
})
|
||||
|
||||
run("compound assign computed property", function() {
|
||||
var obj = {a: 10, b: 20}
|
||||
var key = "a"
|
||||
obj[key] += 5
|
||||
if (obj.a != 15) fail("obj[key] += 5")
|
||||
})
|
||||
|
||||
run("compound assign array element preserves others", function() {
|
||||
var arr = [10, 20, 30]
|
||||
arr[1] += 5
|
||||
if (arr[0] != 10) fail("arr[0] changed")
|
||||
if (arr[1] != 25) fail("arr[1] wrong")
|
||||
if (arr[2] != 30) fail("arr[2] changed")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// PREFIX INCREMENT/DECREMENT ON PROPERTIES
|
||||
// ============================================================================
|
||||
|
||||
run("prefix increment on property", function() {
|
||||
var obj = {x: 5}
|
||||
var y = ++obj.x
|
||||
if (y != 6) fail("return value")
|
||||
if (obj.x != 6) fail("side effect")
|
||||
})
|
||||
|
||||
run("prefix decrement on property", function() {
|
||||
var obj = {x: 5}
|
||||
var y = --obj.x
|
||||
if (y != 4) fail("return value")
|
||||
if (obj.x != 4) fail("side effect")
|
||||
})
|
||||
|
||||
run("prefix increment on array element", function() {
|
||||
var arr = [10]
|
||||
var y = ++arr[0]
|
||||
if (y != 11) fail("return value")
|
||||
if (arr[0] != 11) fail("side effect")
|
||||
})
|
||||
|
||||
run("prefix decrement on array element", function() {
|
||||
var arr = [10]
|
||||
var y = --arr[0]
|
||||
if (y != 9) fail("return value")
|
||||
if (arr[0] != 9) fail("side effect")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// INCREMENT/DECREMENT IN LOOPS AND EXPRESSIONS
|
||||
// ============================================================================
|
||||
|
||||
run("compound assign in for loop", function() {
|
||||
var sum = 0
|
||||
var i = 0
|
||||
for (i = 0; i < 5; i++) {
|
||||
sum += i
|
||||
}
|
||||
if (sum != 10) fail("sum should be 10")
|
||||
})
|
||||
|
||||
run("compound assign property in loop", function() {
|
||||
var obj = {count: 0}
|
||||
var i = 0
|
||||
for (i = 0; i < 5; i++) {
|
||||
obj.count += 1
|
||||
}
|
||||
if (obj.count != 5) fail("count should be 5")
|
||||
})
|
||||
|
||||
run("prefix increment in expression", function() {
|
||||
var x = 5
|
||||
var y = 10 + ++x
|
||||
if (y != 16) fail("10 + ++x should be 16")
|
||||
if (x != 6) fail("x should be 6")
|
||||
})
|
||||
|
||||
run("compound assign bitwise and-equals", function() {
|
||||
var x = 15
|
||||
x &= 6
|
||||
if (x != 6) fail("15 & 6 should be 6")
|
||||
})
|
||||
|
||||
run("compound assign bitwise or-equals", function() {
|
||||
var x = 5
|
||||
x |= 3
|
||||
if (x != 7) fail("5 | 3 should be 7")
|
||||
})
|
||||
|
||||
run("compound assign bitwise xor-equals", function() {
|
||||
var x = 5
|
||||
x ^= 3
|
||||
if (x != 6) fail("5 ^ 3 should be 6")
|
||||
})
|
||||
|
||||
run("compound assign left shift-equals", function() {
|
||||
var x = 1
|
||||
x <<= 3
|
||||
if (x != 8) fail("1 << 3 should be 8")
|
||||
})
|
||||
|
||||
run("compound assign right shift-equals", function() {
|
||||
var x = 16
|
||||
x >>= 2
|
||||
if (x != 4) fail("16 >> 2 should be 4")
|
||||
})
|
||||
|
||||
run("compound assign bitwise on property", function() {
|
||||
var obj = {flags: 5}
|
||||
obj.flags |= 8
|
||||
if (obj.flags != 13) fail("5 | 8 should be 13")
|
||||
obj.flags &= 12
|
||||
if (obj.flags != 12) fail("13 & 12 should be 12")
|
||||
})
|
||||
|
||||
// ============================================================================
|
||||
// EDGE CASES AND SPECIAL VALUES
|
||||
// ============================================================================
|
||||
|
||||
Reference in New Issue
Block a user