diff --git a/docs/language.md b/docs/language.md index 62224d14..962450de 100644 --- a/docs/language.md +++ b/docs/language.md @@ -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 diff --git a/internal/bootstrap.cm b/internal/bootstrap.cm index a8cc77a5..598e4a14 100644 --- a/internal/bootstrap.cm +++ b/internal/bootstrap.cm @@ -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 }) } diff --git a/internal/engine.cm b/internal/engine.cm index 3304f090..9cc62776 100644 --- a/internal/engine.cm +++ b/internal/engine.cm @@ -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 diff --git a/mcode.cm b/mcode.cm index a4720e73..d8a30ecd 100644 --- a/mcode.cm +++ b/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) } diff --git a/mcode.mach b/mcode.mach index bde182bc..e3fdf4a9 100644 Binary files a/mcode.mach and b/mcode.mach differ diff --git a/parse.cm b/parse.cm index da590c27..7e490459 100644 --- a/parse.cm +++ b/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 } diff --git a/parse.mach b/parse.mach index 02f25e6f..76d89a78 100644 Binary files a/parse.mach and b/parse.mach differ diff --git a/qbe.cm b/qbe.cm index 25fe173c..6dbc6947 100644 --- a/qbe.cm +++ b/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 } diff --git a/source/cell.c b/source/cell.c index 788335e8..31e61094 100644 --- a/source/cell.c +++ b/source/cell.c @@ -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]