Files
cell/source/mcode.c

3462 lines
140 KiB
C

/*
* QuickJS Javascript Engine
*
* Copyright (c) 2017-2025 Fabrice Bellard
* Copyright (c) 2017-2025 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "quickjs-internal.h"
typedef struct MachGenState {
cJSON *instructions;
cJSON *data;
cJSON *functions;
int this_slot; /* always 0 */
int nr_args;
int nr_close_slots; /* captured from parents */
int nr_local_slots;
int next_temp_slot;
int max_slot;
MachVarInfo *vars;
int var_count;
int var_capacity;
int label_counter;
int func_counter;
struct MachGenState *parent;
const char *loop_break;
const char *loop_continue;
int is_arrow;
/* AST semantic annotations */
int function_nr;
cJSON *scopes;
/* Intrinsic (global) name cache */
struct { const char *name; int slot; } intrinsic_cache[64];
int intrinsic_count;
/* Error tracking */
cJSON *errors;
int has_error;
/* Line tracking for debug info */
int cur_line, cur_col;
const char *filename;
} MachGenState;
static int mach_gen_expr (MachGenState *s, cJSON *expr, int target);
static void mach_gen_statement (MachGenState *s, cJSON *stmt);
static int mach_gen_alloc_slot (MachGenState *s);
/* Look up an intrinsic in the cache, return slot or -1 */
static int mach_gen_find_intrinsic (MachGenState *s, const char *name) {
for (int i = 0; i < s->intrinsic_count; i++) {
if (strcmp (s->intrinsic_cache[i].name, name) == 0)
return s->intrinsic_cache[i].slot;
}
return -1;
}
/* Pre-load intrinsics from the AST intrinsics array */
static void mach_gen_load_intrinsics (MachGenState *s, cJSON *intrinsics) {
if (!intrinsics) return;
cJSON *item;
cJSON_ArrayForEach (item, intrinsics) {
const char *name = cJSON_GetStringValue (item);
if (!name || s->intrinsic_count >= 64) continue;
if (mach_gen_find_intrinsic (s, name) >= 0) continue;
int slot = mach_gen_alloc_slot (s);
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("access"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (slot));
cJSON *lit = cJSON_CreateObject ();
cJSON_AddStringToObject (lit, "kind", "name");
cJSON_AddStringToObject (lit, "name", name);
cJSON_AddStringToObject (lit, "make", "intrinsic");
cJSON_AddItemToArray (instr, lit);
cJSON_AddItemToArray (s->instructions, instr);
s->intrinsic_cache[s->intrinsic_count].name = name;
s->intrinsic_cache[s->intrinsic_count].slot = slot;
s->intrinsic_count++;
}
}
/* Allocate a temporary slot */
static int mach_gen_alloc_slot (MachGenState *s) {
int slot = s->next_temp_slot++;
if (slot > s->max_slot) s->max_slot = slot;
return slot;
}
/* Add a variable to the tracking table */
static void mach_gen_add_var (MachGenState *s, const char *name, int slot, int is_const) {
if (s->var_count >= s->var_capacity) {
int new_cap = s->var_capacity ? s->var_capacity * 2 : 16;
s->vars = sys_realloc (s->vars, new_cap * sizeof(MachVarInfo));
s->var_capacity = new_cap;
}
MachVarInfo *v = &s->vars[s->var_count++];
v->name = sys_malloc (strlen (name) + 1);
strcpy (v->name, name);
v->slot = slot;
v->is_const = is_const;
v->is_closure = 0;
}
/* Find a variable in the current scope only */
static int mach_gen_find_var (MachGenState *s, const char *name) {
for (int i = 0; i < s->var_count; i++) {
if (strcmp (s->vars[i].name, name) == 0) {
return s->vars[i].slot;
}
}
return -1;
}
/* Add an error to the state */
static void mach_gen_error (MachGenState *s, cJSON *node, const char *fmt, ...) {
va_list ap;
char buf[256];
va_start (ap, fmt);
vsnprintf (buf, sizeof(buf), fmt, ap);
va_end (ap);
cJSON *err = cJSON_CreateObject ();
cJSON_AddStringToObject (err, "message", buf);
cJSON *line_obj = cJSON_GetObjectItemCaseSensitive (node, "from_row");
cJSON *col_obj = cJSON_GetObjectItemCaseSensitive (node, "from_column");
if (line_obj)
cJSON_AddNumberToObject (err, "line", cJSON_GetNumberValue (line_obj) + 1);
if (col_obj)
cJSON_AddNumberToObject (err, "column", cJSON_GetNumberValue (col_obj) + 1);
if (!s->errors)
s->errors = cJSON_CreateArray ();
cJSON_AddItemToArray (s->errors, err);
s->has_error = 1;
}
/* Scan AST scope record for variable declarations.
Variables are direct keys on the scope object with a "make" field. */
static void mach_gen_scan_scope (MachGenState *s) {
cJSON *scope = mach_find_scope_record (s->scopes, s->function_nr);
if (!scope) return;
cJSON *v;
cJSON_ArrayForEach (v, scope) {
const char *name = v->string;
if (!name || strcmp (name, "function_nr") == 0 || strcmp (name, "nr_close_slots") == 0) continue;
const char *make = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (v, "make"));
if (!make || strcmp (make, "input") == 0) continue;
if (mach_gen_find_var (s, name) < 0) {
int is_const = (strcmp (make, "def") == 0 || strcmp (make, "function") == 0);
int slot = 1 + s->nr_args + s->nr_local_slots++;
mach_gen_add_var (s, name, slot, is_const);
cJSON *closure_flag = cJSON_GetObjectItemCaseSensitive (v, "closure");
s->vars[s->var_count - 1].is_closure = (closure_flag && cJSON_IsTrue (closure_flag));
}
}
}
static char *mach_gen_label (MachGenState *s, const char *prefix) {
char *label = sys_malloc (64);
snprintf (label, 64, "%s_%d", prefix, s->label_counter++);
return label;
}
static void mach_gen_emit_label (MachGenState *s, const char *label) {
cJSON *item = cJSON_CreateString (label);
cJSON_AddItemToArray (s->instructions, item);
}
static void mach_gen_set_pos (MachGenState *s, cJSON *node) {
cJSON *r = cJSON_GetObjectItemCaseSensitive (node, "from_row");
cJSON *c = cJSON_GetObjectItemCaseSensitive (node, "from_column");
if (r) s->cur_line = (int)r->valuedouble + 1;
if (c) s->cur_col = (int)c->valuedouble + 1;
}
static void mach_gen_add_instr (MachGenState *s, cJSON *instr) {
cJSON_AddItemToArray (instr, cJSON_CreateNumber (s->cur_line));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (s->cur_col));
cJSON_AddItemToArray (s->instructions, instr);
}
static void mach_gen_emit_0 (MachGenState *s, const char *op) {
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString (op));
mach_gen_add_instr (s, instr);
}
static void mach_gen_emit_1 (MachGenState *s, const char *op, int a) {
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString (op));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (a));
mach_gen_add_instr (s, instr);
}
static void mach_gen_emit_2 (MachGenState *s, const char *op, int a, int b) {
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString (op));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (a));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (b));
mach_gen_add_instr (s, instr);
}
static void mach_gen_emit_3 (MachGenState *s, const char *op, int a, int b, int c) {
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString (op));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (a));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (b));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (c));
mach_gen_add_instr (s, instr);
}
static void mach_gen_emit_4 (MachGenState *s, const char *op, int a, int b, int c, int d) {
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString (op));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (a));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (b));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (c));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (d));
mach_gen_add_instr (s, instr);
}
static void mach_gen_emit_const_num (MachGenState *s, int dest, double val) {
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("access"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (val));
mach_gen_add_instr (s, instr);
}
static void mach_gen_emit_const_str (MachGenState *s, int dest, const char *val) {
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("access"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest));
cJSON_AddItemToArray (instr, cJSON_CreateString (val ? val : ""));
mach_gen_add_instr (s, instr);
}
static void mach_gen_emit_const_bool (MachGenState *s, int dest, int val) {
mach_gen_emit_1 (s, val ? "true" : "false", dest);
}
static void mach_gen_emit_const_null (MachGenState *s, int dest) {
mach_gen_emit_1 (s, "null", dest);
}
static void mach_gen_emit_jump (MachGenState *s, const char *label) {
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("jump"));
cJSON_AddItemToArray (instr, cJSON_CreateString (label));
mach_gen_add_instr (s, instr);
}
static void mach_gen_emit_jump_cond (MachGenState *s, const char *op, int slot, const char *label) {
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString (op));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (slot));
cJSON_AddItemToArray (instr, cJSON_CreateString (label));
mach_gen_add_instr (s, instr);
}
static void mach_gen_emit_get_prop (MachGenState *s, int dest, int obj, const char *prop) {
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("load"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj));
cJSON_AddItemToArray (instr, cJSON_CreateString (prop));
mach_gen_add_instr (s, instr);
}
static void mach_gen_emit_set_prop (MachGenState *s, int obj, const char *prop, int val) {
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("store"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (val));
cJSON_AddItemToArray (instr, cJSON_CreateString (prop));
mach_gen_add_instr (s, instr);
}
static void mach_gen_emit_get_elem (MachGenState *s, int dest, int obj, int idx) {
mach_gen_emit_3 (s, "load", dest, obj, idx);
}
static void mach_gen_emit_set_elem (MachGenState *s, int obj, int idx, int val) {
mach_gen_emit_3 (s, "store", obj, val, idx);
}
static void mach_gen_emit_call (MachGenState *s, int dest, int func_slot, cJSON *args) {
int argc = cJSON_GetArraySize (args);
int frame_slot = mach_gen_alloc_slot (s);
mach_gen_emit_3 (s, "frame", frame_slot, func_slot, argc);
int null_slot = mach_gen_alloc_slot (s);
mach_gen_emit_1 (s, "null", null_slot);
mach_gen_emit_3 (s, "setarg", frame_slot, 0, null_slot);
int arg_idx = 1;
cJSON *arg;
cJSON_ArrayForEach (arg, args) {
mach_gen_emit_3 (s, "setarg", frame_slot, arg_idx++, arg->valueint);
}
mach_gen_emit_2 (s, "invoke", frame_slot, dest);
}
static void mach_gen_emit_call_method (MachGenState *s, int dest, int obj, const char *prop, cJSON *args) {
/* Emit a single callmethod instruction:
["callmethod", dest, obj_reg, "method_name", arg_reg0, arg_reg1, ...] */
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("callmethod"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj));
cJSON_AddItemToArray (instr, cJSON_CreateString (prop));
cJSON *arg;
cJSON_ArrayForEach (arg, args) {
cJSON_AddItemToArray (instr, cJSON_CreateNumber (arg->valueint));
}
mach_gen_add_instr (s, instr);
}
static void mach_gen_emit_call_method_dyn (MachGenState *s, int dest, int obj, int key_reg, cJSON *args) {
/* Emit a dynamic callmethod instruction:
["callmethod_dyn", dest, obj_reg, key_reg, arg_reg0, arg_reg1, ...] */
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("callmethod_dyn"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (key_reg));
cJSON *arg;
cJSON_ArrayForEach (arg, args) {
cJSON_AddItemToArray (instr, cJSON_CreateNumber (arg->valueint));
}
mach_gen_add_instr (s, instr);
}
static void mach_gen_emit_go_call (MachGenState *s, int func_slot, cJSON *args) {
int argc = cJSON_GetArraySize (args);
int frame_slot = mach_gen_alloc_slot (s);
mach_gen_emit_3 (s, "goframe", frame_slot, func_slot, argc);
int null_slot = mach_gen_alloc_slot (s);
mach_gen_emit_1 (s, "null", null_slot);
mach_gen_emit_3 (s, "setarg", frame_slot, 0, null_slot);
int arg_idx = 1;
cJSON *arg;
cJSON_ArrayForEach (arg, args) {
mach_gen_emit_3 (s, "setarg", frame_slot, arg_idx++, arg->valueint);
}
mach_gen_emit_1 (s, "goinvoke", frame_slot);
}
static void mach_gen_emit_go_call_method (MachGenState *s, int obj, const char *prop, cJSON *args) {
int func_slot = mach_gen_alloc_slot (s);
mach_gen_emit_get_prop (s, func_slot, obj, prop);
int argc = cJSON_GetArraySize (args);
int frame_slot = mach_gen_alloc_slot (s);
mach_gen_emit_3 (s, "goframe", frame_slot, func_slot, argc);
mach_gen_emit_3 (s, "setarg", frame_slot, 0, obj);
int arg_idx = 1;
cJSON *arg;
cJSON_ArrayForEach (arg, args) {
mach_gen_emit_3 (s, "setarg", frame_slot, arg_idx++, arg->valueint);
}
mach_gen_emit_1 (s, "goinvoke", frame_slot);
}
static const char *functino_to_mcode_op (const char *name) {
if (strcmp (name, "+!") == 0) return "add";
if (strcmp (name, "-!") == 0) return "subtract";
if (strcmp (name, "*!") == 0) return "multiply";
if (strcmp (name, "/!") == 0) return "divide";
if (strcmp (name, "%!") == 0) return "modulo";
if (strcmp (name, "**!") == 0) return "pow";
if (strcmp (name, "<!") == 0) return "lt";
if (strcmp (name, ">!") == 0) return "gt";
if (strcmp (name, "<=!") == 0) return "le";
if (strcmp (name, ">=!") == 0) return "ge";
if (strcmp (name, "=!") == 0) return "eq";
if (strcmp (name, "!=!") == 0) return "ne";
if (strcmp (name, "&!") == 0) return "bitand";
if (strcmp (name, "|!") == 0) return "bitor";
if (strcmp (name, "^!") == 0) return "bitxor";
if (strcmp (name, "<<!") == 0) return "shl";
if (strcmp (name, ">>!") == 0) return "shr";
if (strcmp (name, ">>>!") == 0) return "ushr";
if (strcmp (name, "&&!") == 0) return "and";
if (strcmp (name, "||!") == 0) return "or";
if (strcmp (name, "~!") == 0) return "bitnot";
if (strcmp (name, "[]!") == 0) return "load";
return NULL;
}
static const char *mach_gen_binop_to_string (const char *kind) {
if (strcmp (kind, "+") == 0) return "add";
if (strcmp (kind, "-") == 0) return "subtract";
if (strcmp (kind, "*") == 0) return "multiply";
if (strcmp (kind, "/") == 0) return "divide";
if (strcmp (kind, "%") == 0) return "modulo";
if (strcmp (kind, "&") == 0) return "bitand";
if (strcmp (kind, "|") == 0) return "bitor";
if (strcmp (kind, "^") == 0) return "bitxor";
if (strcmp (kind, "<<") == 0) return "shl";
if (strcmp (kind, ">>") == 0) return "shr";
if (strcmp (kind, ">>>") == 0) return "ushr";
if (strcmp (kind, "==") == 0 || strcmp (kind, "===") == 0) return "eq";
if (strcmp (kind, "!=") == 0 || strcmp (kind, "!==") == 0) return "ne";
if (strcmp (kind, "<") == 0) return "lt";
if (strcmp (kind, "<=") == 0) return "le";
if (strcmp (kind, ">") == 0) return "gt";
if (strcmp (kind, ">=") == 0) return "ge";
if (strcmp (kind, "**") == 0) return "pow";
if (strcmp (kind, "in") == 0) return "in";
return "add";
}
static int mach_gen_binary (MachGenState *s, cJSON *node) {
const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (node, "kind"));
cJSON *left = cJSON_GetObjectItemCaseSensitive (node, "left");
cJSON *right = cJSON_GetObjectItemCaseSensitive (node, "right");
if (strcmp (kind, "&&") == 0) {
char *end_label = mach_gen_label (s, "and_end");
int left_slot = mach_gen_expr (s, left, -1);
int dest = mach_gen_alloc_slot (s);
mach_gen_emit_2 (s, "move", dest, left_slot);
mach_gen_emit_jump_cond (s, "jump_false", dest, end_label);
int right_slot = mach_gen_expr (s, right, -1);
mach_gen_emit_2 (s, "move", dest, right_slot);
mach_gen_emit_label (s, end_label);
sys_free (end_label);
return dest;
}
if (strcmp (kind, "||") == 0) {
char *end_label = mach_gen_label (s, "or_end");
int left_slot = mach_gen_expr (s, left, -1);
int dest = mach_gen_alloc_slot (s);
mach_gen_emit_2 (s, "move", dest, left_slot);
mach_gen_emit_jump_cond (s, "jump_true", dest, end_label);
int right_slot = mach_gen_expr (s, right, -1);
mach_gen_emit_2 (s, "move", dest, right_slot);
mach_gen_emit_label (s, end_label);
sys_free (end_label);
return dest;
}
if (strcmp (kind, "??") == 0) {
char *end_label = mach_gen_label (s, "nullish_end");
int left_slot = mach_gen_expr (s, left, -1);
int dest = mach_gen_alloc_slot (s);
mach_gen_emit_2 (s, "move", dest, left_slot);
mach_gen_emit_jump_cond (s, "jump_not_null", dest, end_label);
int right_slot = mach_gen_expr (s, right, -1);
mach_gen_emit_2 (s, "move", dest, right_slot);
mach_gen_emit_label (s, end_label);
sys_free (end_label);
return dest;
}
/* Comma operator: evaluate left (discard), evaluate right (keep) */
if (strcmp (kind, ",") == 0) {
mach_gen_expr (s, left, -1);
return mach_gen_expr (s, right, -1);
}
int left_slot = mach_gen_expr (s, left, -1);
int right_slot = mach_gen_expr (s, right, -1);
int dest = mach_gen_alloc_slot (s);
const char *op = mach_gen_binop_to_string (kind);
mach_gen_emit_3 (s, op, dest, left_slot, right_slot);
return dest;
}
static int mach_gen_compound_assign (MachGenState *s, cJSON *node, const char *op) {
cJSON *left = cJSON_GetObjectItemCaseSensitive (node, "left");
cJSON *right = cJSON_GetObjectItemCaseSensitive (node, "right");
const char *left_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "kind"));
if (strcmp (left_kind, "name") == 0) {
const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "name"));
const char *sn = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "scope_name"));
const char *ln = sn ? sn : name;
cJSON *level_node = cJSON_GetObjectItemCaseSensitive (left, "level");
int level = level_node ? (int)cJSON_GetNumberValue (level_node) : -1;
int left_slot = mach_gen_alloc_slot (s);
if (level == 0 || level == -1) {
int local = mach_gen_find_var (s, ln);
if (local >= 0) {
mach_gen_emit_2 (s, "move", left_slot, local);
level = 0; /* treat as local for the store below */
}
}
if (level > 0) {
MachGenState *target = s;
for (int i = 0; i < level; i++) target = target->parent;
int slot = mach_gen_find_var (target, ln);
mach_gen_emit_3 (s, "get", left_slot, slot, level);
} else if (level == -1) {
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("access"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (left_slot));
cJSON *lit = cJSON_CreateObject ();
cJSON_AddStringToObject (lit, "kind", "name");
cJSON_AddStringToObject (lit, "name", name);
cJSON_AddStringToObject (lit, "make", "intrinsic");
cJSON_AddItemToArray (instr, lit);
mach_gen_add_instr (s, instr);
}
int right_slot = mach_gen_expr (s, right, -1);
int dest = mach_gen_alloc_slot (s);
mach_gen_emit_3 (s, op, dest, left_slot, right_slot);
if (level == 0) {
int local = mach_gen_find_var (s, ln);
if (local >= 0) mach_gen_emit_2 (s, "move", local, dest);
} else if (level > 0) {
MachGenState *target = s;
for (int i = 0; i < level; i++) target = target->parent;
int slot = mach_gen_find_var (target, ln);
mach_gen_emit_3 (s, "put", dest, slot, level);
} else {
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("set_var"));
cJSON_AddItemToArray (instr, cJSON_CreateString (name));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest));
cJSON_AddItemToArray (s->instructions, instr);
}
return dest;
} else if (strcmp (left_kind, ".") == 0) {
cJSON *obj = cJSON_GetObjectItemCaseSensitive (left, "left");
const char *prop = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "right"));
int obj_slot = mach_gen_expr (s, obj, -1);
int old_val = mach_gen_alloc_slot (s);
mach_gen_emit_get_prop (s, old_val, obj_slot, prop);
int right_slot = mach_gen_expr (s, right, -1);
int dest = mach_gen_alloc_slot (s);
mach_gen_emit_3 (s, op, dest, old_val, right_slot);
mach_gen_emit_set_prop (s, obj_slot, prop, dest);
return dest;
} else if (strcmp (left_kind, "[") == 0) {
cJSON *obj = cJSON_GetObjectItemCaseSensitive (left, "left");
cJSON *idx_expr = cJSON_GetObjectItemCaseSensitive (left, "right");
int obj_slot = mach_gen_expr (s, obj, -1);
int idx_slot = mach_gen_expr (s, idx_expr, -1);
int old_val = mach_gen_alloc_slot (s);
mach_gen_emit_get_elem (s, old_val, obj_slot, idx_slot);
int right_slot = mach_gen_expr (s, right, -1);
int dest = mach_gen_alloc_slot (s);
mach_gen_emit_3 (s, op, dest, old_val, right_slot);
mach_gen_emit_set_elem (s, obj_slot, idx_slot, dest);
return dest;
}
return -1;
}
static int mach_gen_assign (MachGenState *s, cJSON *node) {
const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (node, "kind"));
cJSON *left = cJSON_GetObjectItemCaseSensitive (node, "left");
cJSON *right = cJSON_GetObjectItemCaseSensitive (node, "right");
if (strcmp (kind, "+=") == 0) return mach_gen_compound_assign (s, node, "add");
if (strcmp (kind, "-=") == 0) return mach_gen_compound_assign (s, node, "subtract");
if (strcmp (kind, "*=") == 0) return mach_gen_compound_assign (s, node, "multiply");
if (strcmp (kind, "/=") == 0) return mach_gen_compound_assign (s, node, "divide");
if (strcmp (kind, "%=") == 0) return mach_gen_compound_assign (s, node, "modulo");
if (strcmp (kind, "&=") == 0) return mach_gen_compound_assign (s, node, "bitand");
if (strcmp (kind, "|=") == 0) return mach_gen_compound_assign (s, node, "bitor");
if (strcmp (kind, "^=") == 0) return mach_gen_compound_assign (s, node, "bitxor");
if (strcmp (kind, "<<=") == 0) return mach_gen_compound_assign (s, node, "shl");
if (strcmp (kind, ">>=") == 0) return mach_gen_compound_assign (s, node, "shr");
if (strcmp (kind, ">>>=") == 0) return mach_gen_compound_assign (s, node, "ushr");
/* Push: arr[] = val */
cJSON *push_flag = cJSON_GetObjectItemCaseSensitive (node, "push");
if (push_flag && cJSON_IsTrue (push_flag)) {
cJSON *arr_expr = cJSON_GetObjectItemCaseSensitive (left, "left");
int arr_slot = mach_gen_expr (s, arr_expr, -1);
int val_slot = mach_gen_expr (s, right, -1);
mach_gen_emit_2 (s, "push", arr_slot, val_slot);
return val_slot;
}
int val_slot = mach_gen_expr (s, right, -1);
const char *left_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "kind"));
if (strcmp (left_kind, "name") == 0) {
const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "name"));
const char *sn = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "scope_name"));
const char *ln = sn ? sn : name;
cJSON *level_node = cJSON_GetObjectItemCaseSensitive (left, "level");
int level = level_node ? (int)cJSON_GetNumberValue (level_node) : -1;
if (level == 0 || level == -1) {
int slot = mach_gen_find_var (s, ln);
if (slot >= 0) mach_gen_emit_2 (s, "move", slot, val_slot);
else if (level == -1) {
/* No annotation and not local — set global */
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("set_var"));
cJSON_AddItemToArray (instr, cJSON_CreateString (name));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (val_slot));
mach_gen_add_instr (s, instr);
}
} else if (level > 0) {
MachGenState *target = s;
for (int i = 0; i < level; i++) target = target->parent;
int slot = mach_gen_find_var (target, ln);
mach_gen_emit_3 (s, "put", val_slot, slot, level);
} else {
mach_gen_error (s, node, "cannot assign to unbound variable '%s'", name);
}
} else if (strcmp (left_kind, ".") == 0) {
cJSON *obj = cJSON_GetObjectItemCaseSensitive (left, "left");
const char *prop = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "right"));
int obj_slot = mach_gen_expr (s, obj, -1);
mach_gen_emit_set_prop (s, obj_slot, prop, val_slot);
} else if (strcmp (left_kind, "[") == 0) {
cJSON *obj = cJSON_GetObjectItemCaseSensitive (left, "left");
cJSON *idx_expr = cJSON_GetObjectItemCaseSensitive (left, "right");
int obj_slot = mach_gen_expr (s, obj, -1);
int idx_slot = mach_gen_expr (s, idx_expr, -1);
mach_gen_emit_set_elem (s, obj_slot, idx_slot, val_slot);
}
return val_slot;
}
static cJSON *mach_gen_function (MachGenState *parent, cJSON *func_node);
static int mach_gen_expr (MachGenState *s, cJSON *expr, int target) {
if (!expr) return -1;
mach_gen_set_pos (s, expr);
const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (expr, "kind"));
if (!kind) return -1;
/* Literals — use target slot if provided */
if (strcmp (kind, "number") == 0) {
int slot = target >= 0 ? target : mach_gen_alloc_slot (s);
double val = cJSON_GetNumberValue (cJSON_GetObjectItemCaseSensitive (expr, "number"));
mach_gen_emit_const_num (s, slot, val);
return slot;
}
if (strcmp (kind, "text") == 0) {
int slot = target >= 0 ? target : mach_gen_alloc_slot (s);
const char *val = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (expr, "value"));
mach_gen_emit_const_str (s, slot, val ? val : "");
return slot;
}
/* Template literal with expressions: kind="text literal"
Format: value = "hello {0} world {1}", list = [expr0, expr1]
Compile as: format(fmt_string, [expr0, expr1, ...]) */
if (strcmp (kind, "text literal") == 0) {
cJSON *list = cJSON_GetObjectItemCaseSensitive (expr, "list");
int nexpr = list ? cJSON_GetArraySize (list) : 0;
/* Evaluate each expression */
int *expr_slots = NULL;
if (nexpr > 0) {
expr_slots = alloca (nexpr * sizeof (int));
for (int i = 0; i < nexpr; i++) {
cJSON *item = cJSON_GetArrayItem (list, i);
expr_slots[i] = mach_gen_expr (s, item, -1);
}
}
/* Create array from expression results using the "array" opcode */
int arr_slot = mach_gen_alloc_slot (s);
{
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("array"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (arr_slot));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (nexpr));
for (int i = 0; i < nexpr; i++)
cJSON_AddItemToArray (instr, cJSON_CreateNumber (expr_slots[i]));
mach_gen_add_instr (s, instr);
}
/* Load format intrinsic */
int fmt_func_slot = mach_gen_find_intrinsic (s, "format");
if (fmt_func_slot < 0) {
fmt_func_slot = mach_gen_alloc_slot (s);
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("access"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (fmt_func_slot));
cJSON *lit = cJSON_CreateObject ();
cJSON_AddStringToObject (lit, "kind", "name");
cJSON_AddStringToObject (lit, "name", "format");
cJSON_AddStringToObject (lit, "make", "intrinsic");
cJSON_AddItemToArray (instr, lit);
mach_gen_add_instr (s, instr);
}
/* Load format string */
const char *fmt = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (expr, "value"));
int fmt_str_slot = mach_gen_alloc_slot (s);
mach_gen_emit_const_str (s, fmt_str_slot, fmt ? fmt : "");
/* Call format(fmt_str, array) */
int result_slot = target >= 0 ? target : mach_gen_alloc_slot (s);
{
cJSON *call_args = cJSON_CreateArray ();
cJSON_AddItemToArray (call_args, cJSON_CreateNumber (fmt_str_slot));
cJSON_AddItemToArray (call_args, cJSON_CreateNumber (arr_slot));
mach_gen_emit_call (s, result_slot, fmt_func_slot, call_args);
cJSON_Delete (call_args);
}
return result_slot;
}
if (strcmp (kind, "regexp") == 0) {
int slot = target >= 0 ? target : mach_gen_alloc_slot (s);
const char *pattern = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (expr, "pattern"));
const char *flags = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (expr, "flags"));
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("regexp"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (slot));
cJSON_AddItemToArray (instr, cJSON_CreateString (pattern ? pattern : ""));
cJSON_AddItemToArray (instr, cJSON_CreateString (flags ? flags : ""));
mach_gen_add_instr (s, instr);
return slot;
}
if (strcmp (kind, "true") == 0) {
int slot = target >= 0 ? target : mach_gen_alloc_slot (s);
mach_gen_emit_const_bool (s, slot, 1);
return slot;
}
if (strcmp (kind, "false") == 0) {
int slot = target >= 0 ? target : mach_gen_alloc_slot (s);
mach_gen_emit_const_bool (s, slot, 0);
return slot;
}
if (strcmp (kind, "null") == 0) {
int slot = target >= 0 ? target : mach_gen_alloc_slot (s);
mach_gen_emit_const_null (s, slot);
return slot;
}
if (strcmp (kind, "this") == 0) {
return s->this_slot;
}
/* Variable reference — uses parser-provided level annotation */
if (strcmp (kind, "name") == 0) {
const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (expr, "name"));
const char *scope_name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (expr, "scope_name"));
const char *lookup_name = scope_name ? scope_name : name;
cJSON *level_node = cJSON_GetObjectItemCaseSensitive (expr, "level");
int level = level_node ? (int)cJSON_GetNumberValue (level_node) : -1;
if (level == 0 || level == -1) {
/* level 0 = known local; level -1 = no annotation, try local first */
int slot = mach_gen_find_var (s, lookup_name);
if (slot >= 0) return slot;
} else if (level > 0) {
MachGenState *target = s;
for (int i = 0; i < level; i++) target = target->parent;
int parent_slot = mach_gen_find_var (target, lookup_name);
int dest = mach_gen_alloc_slot (s);
mach_gen_emit_3 (s, "get", dest, parent_slot, level);
return dest;
}
/* Unbound — check intrinsic cache first, then emit access with intrinsic */
int cached = mach_gen_find_intrinsic (s, name);
if (cached >= 0) return cached;
int dest = mach_gen_alloc_slot (s);
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("access"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest));
cJSON *lit = cJSON_CreateObject ();
cJSON_AddStringToObject (lit, "kind", "name");
cJSON_AddStringToObject (lit, "name", name);
cJSON_AddStringToObject (lit, "make", "intrinsic");
cJSON_AddItemToArray (instr, lit);
mach_gen_add_instr (s, instr);
return dest;
}
/* Property access */
if (strcmp (kind, ".") == 0) {
cJSON *obj = cJSON_GetObjectItemCaseSensitive (expr, "left");
const char *prop = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (expr, "right"));
int obj_slot = mach_gen_expr (s, obj, -1);
int slot = mach_gen_alloc_slot (s);
mach_gen_emit_get_prop (s, slot, obj_slot, prop);
return slot;
}
/* Element access */
if (strcmp (kind, "[") == 0) {
cJSON *obj = cJSON_GetObjectItemCaseSensitive (expr, "left");
cJSON *idx = cJSON_GetObjectItemCaseSensitive (expr, "right");
int obj_slot = mach_gen_expr (s, obj, -1);
int idx_slot = mach_gen_expr (s, idx, -1);
int slot = mach_gen_alloc_slot (s);
mach_gen_emit_get_elem (s, slot, obj_slot, idx_slot);
return slot;
}
/* Function call */
if (strcmp (kind, "(") == 0) {
cJSON *callee = cJSON_GetObjectItemCaseSensitive (expr, "expression");
cJSON *args_list = cJSON_GetObjectItemCaseSensitive (expr, "list");
/* Functino: inline operator call */
const char *callee_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (callee, "kind"));
if (callee_kind && strcmp (callee_kind, "name") == 0) {
const char *callee_make = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (callee, "make"));
if (callee_make && strcmp (callee_make, "functino") == 0) {
const char *fname = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (callee, "name"));
const char *mop = functino_to_mcode_op (fname);
int nargs = args_list ? cJSON_GetArraySize (args_list) : 0;
if (strcmp (fname, "~!") == 0) {
int a0 = mach_gen_expr (s, cJSON_GetArrayItem (args_list, 0), -1);
int d = mach_gen_alloc_slot (s);
mach_gen_emit_2 (s, mop, d, a0);
return d;
}
if (strcmp (fname, "[]!") == 0) {
int a0 = mach_gen_expr (s, cJSON_GetArrayItem (args_list, 0), -1);
int a1 = mach_gen_expr (s, cJSON_GetArrayItem (args_list, 1), -1);
int d = mach_gen_alloc_slot (s);
mach_gen_emit_get_elem (s, d, a0, a1);
return d;
}
if ((strcmp (fname, "=!") == 0 || strcmp (fname, "!=!") == 0) && nargs == 3) {
int a0 = mach_gen_expr (s, cJSON_GetArrayItem (args_list, 0), -1);
int a1 = mach_gen_expr (s, cJSON_GetArrayItem (args_list, 1), -1);
int a2 = mach_gen_expr (s, cJSON_GetArrayItem (args_list, 2), -1);
int d = mach_gen_alloc_slot (s);
const char *top = (strcmp (fname, "=!") == 0) ? "eq_tol" : "ne_tol";
mach_gen_emit_4 (s, top, d, a0, a1, a2);
return d;
}
if (strcmp (fname, "&&!") == 0) {
int a0 = mach_gen_expr (s, cJSON_GetArrayItem (args_list, 0), -1);
int a1 = mach_gen_expr (s, cJSON_GetArrayItem (args_list, 1), -1);
int d = mach_gen_alloc_slot (s);
mach_gen_emit_3 (s, "and", d, a0, a1);
return d;
}
if (strcmp (fname, "||!") == 0) {
int a0 = mach_gen_expr (s, cJSON_GetArrayItem (args_list, 0), -1);
int a1 = mach_gen_expr (s, cJSON_GetArrayItem (args_list, 1), -1);
int d = mach_gen_alloc_slot (s);
mach_gen_emit_3 (s, "or", d, a0, a1);
return d;
}
/* Standard 2-arg binary functino */
{
int a0 = mach_gen_expr (s, cJSON_GetArrayItem (args_list, 0), -1);
int a1 = mach_gen_expr (s, cJSON_GetArrayItem (args_list, 1), -1);
int d = mach_gen_alloc_slot (s);
mach_gen_emit_3 (s, mop, d, a0, a1);
return d;
}
}
}
cJSON *arg_slots = cJSON_CreateArray ();
cJSON *arg;
cJSON_ArrayForEach (arg, args_list) {
int arg_slot = mach_gen_expr (s, arg, -1);
cJSON_AddItemToArray (arg_slots, cJSON_CreateNumber (arg_slot));
}
int dest = mach_gen_alloc_slot (s);
if (strcmp (callee_kind, ".") == 0) {
cJSON *obj = cJSON_GetObjectItemCaseSensitive (callee, "left");
const char *prop = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (callee, "right"));
int obj_slot = mach_gen_expr (s, obj, -1);
mach_gen_emit_call_method (s, dest, obj_slot, prop, arg_slots);
} else if (strcmp (callee_kind, "[") == 0) {
cJSON *obj = cJSON_GetObjectItemCaseSensitive (callee, "left");
cJSON *key_expr = cJSON_GetObjectItemCaseSensitive (callee, "right");
int obj_slot = mach_gen_expr (s, obj, -1);
int key_slot = mach_gen_expr (s, key_expr, -1);
mach_gen_emit_call_method_dyn (s, dest, obj_slot, key_slot, arg_slots);
} else {
int func_slot = mach_gen_expr (s, callee, -1);
mach_gen_emit_call (s, dest, func_slot, arg_slots);
}
cJSON_Delete (arg_slots);
return dest;
}
/* Unary operators */
if (strcmp (kind, "!") == 0) {
cJSON *operand = cJSON_GetObjectItemCaseSensitive (expr, "expression");
int operand_slot = mach_gen_expr (s, operand, -1);
int slot = mach_gen_alloc_slot (s);
mach_gen_emit_2 (s, "not", slot, operand_slot);
return slot;
}
if (strcmp (kind, "~") == 0) {
cJSON *operand = cJSON_GetObjectItemCaseSensitive (expr, "expression");
int operand_slot = mach_gen_expr (s, operand, -1);
int slot = mach_gen_alloc_slot (s);
mach_gen_emit_2 (s, "bitnot", slot, operand_slot);
return slot;
}
if (strcmp (kind, "-unary") == 0) {
cJSON *operand = cJSON_GetObjectItemCaseSensitive (expr, "expression");
int operand_slot = mach_gen_expr (s, operand, -1);
int slot = mach_gen_alloc_slot (s);
mach_gen_emit_2 (s, "neg", slot, operand_slot);
return slot;
}
if (strcmp (kind, "+unary") == 0) {
cJSON *operand = cJSON_GetObjectItemCaseSensitive (expr, "expression");
return mach_gen_expr (s, operand, -1);
}
/* Increment/Decrement */
if (strcmp (kind, "++") == 0 || strcmp (kind, "--") == 0) {
cJSON *operand = cJSON_GetObjectItemCaseSensitive (expr, "expression");
cJSON *is_postfix = cJSON_GetObjectItemCaseSensitive (expr, "postfix");
int postfix = is_postfix && cJSON_IsTrue (is_postfix);
const char *arith_op = (strcmp (kind, "++") == 0) ? "add" : "subtract";
const char *operand_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (operand, "kind"));
int one_slot = mach_gen_alloc_slot (s);
mach_gen_emit_2 (s, "int", one_slot, 1);
if (strcmp (operand_kind, "name") == 0) {
const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (operand, "name"));
const char *inc_sn = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (operand, "scope_name"));
const char *inc_ln = inc_sn ? inc_sn : name;
cJSON *level_node = cJSON_GetObjectItemCaseSensitive (operand, "level");
int level = level_node ? (int)cJSON_GetNumberValue (level_node) : -1;
int old_slot = mach_gen_alloc_slot (s);
/* Load current value */
if (level == 0) {
int local = mach_gen_find_var (s, inc_ln);
if (local >= 0) mach_gen_emit_2 (s, "move", old_slot, local);
} else if (level > 0) {
MachGenState *target = s;
for (int i = 0; i < level; i++) target = target->parent;
int slot = mach_gen_find_var (target, inc_ln);
mach_gen_emit_3 (s, "get", old_slot, slot, level);
} else {
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("access"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (old_slot));
cJSON *lit = cJSON_CreateObject ();
cJSON_AddStringToObject (lit, "kind", "name");
cJSON_AddStringToObject (lit, "name", name);
cJSON_AddStringToObject (lit, "make", "intrinsic");
cJSON_AddItemToArray (instr, lit);
mach_gen_add_instr (s, instr);
}
int new_slot = mach_gen_alloc_slot (s);
mach_gen_emit_3 (s, arith_op, new_slot, old_slot, one_slot);
/* Store new value */
if (level == 0) {
int local = mach_gen_find_var (s, inc_ln);
if (local >= 0) mach_gen_emit_2 (s, "move", local, new_slot);
} else if (level > 0) {
MachGenState *target = s;
for (int i = 0; i < level; i++) target = target->parent;
int slot = mach_gen_find_var (target, inc_ln);
mach_gen_emit_3 (s, "put", new_slot, slot, level);
}
return postfix ? old_slot : new_slot;
} else if (strcmp (operand_kind, ".") == 0) {
cJSON *obj = cJSON_GetObjectItemCaseSensitive (operand, "left");
const char *prop = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (operand, "right"));
int obj_slot = mach_gen_expr (s, obj, -1);
int old_slot = mach_gen_alloc_slot (s);
mach_gen_emit_get_prop (s, old_slot, obj_slot, prop);
int new_slot = mach_gen_alloc_slot (s);
mach_gen_emit_3 (s, arith_op, new_slot, old_slot, one_slot);
mach_gen_emit_set_prop (s, obj_slot, prop, new_slot);
return postfix ? old_slot : new_slot;
} else if (strcmp (operand_kind, "[") == 0) {
cJSON *obj = cJSON_GetObjectItemCaseSensitive (operand, "left");
cJSON *idx_expr = cJSON_GetObjectItemCaseSensitive (operand, "right");
int obj_slot = mach_gen_expr (s, obj, -1);
int idx_slot = mach_gen_expr (s, idx_expr, -1);
int old_slot = mach_gen_alloc_slot (s);
mach_gen_emit_get_elem (s, old_slot, obj_slot, idx_slot);
int new_slot = mach_gen_alloc_slot (s);
mach_gen_emit_3 (s, arith_op, new_slot, old_slot, one_slot);
mach_gen_emit_set_elem (s, obj_slot, idx_slot, new_slot);
return postfix ? old_slot : new_slot;
}
}
/* Delete operator */
if (strcmp (kind, "delete") == 0) {
cJSON *operand = cJSON_GetObjectItemCaseSensitive (expr, "expression");
const char *operand_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (operand, "kind"));
int slot = mach_gen_alloc_slot (s);
if (strcmp (operand_kind, ".") == 0) {
cJSON *obj = cJSON_GetObjectItemCaseSensitive (operand, "left");
const char *prop = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (operand, "right"));
int obj_slot = mach_gen_expr (s, obj, -1);
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("delete"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (slot));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj_slot));
cJSON_AddItemToArray (instr, cJSON_CreateString (prop));
cJSON_AddItemToArray (s->instructions, instr);
} else if (strcmp (operand_kind, "[") == 0) {
cJSON *obj = cJSON_GetObjectItemCaseSensitive (operand, "left");
cJSON *idx = cJSON_GetObjectItemCaseSensitive (operand, "right");
int obj_slot = mach_gen_expr (s, obj, -1);
int idx_slot = mach_gen_expr (s, idx, -1);
mach_gen_emit_3 (s, "delete", slot, obj_slot, idx_slot);
} else {
mach_gen_emit_const_bool (s, slot, 1);
}
return slot;
}
/* Ternary */
if (strcmp (kind, "then") == 0) {
cJSON *cond = cJSON_GetObjectItemCaseSensitive (expr, "expression");
cJSON *then_expr = cJSON_GetObjectItemCaseSensitive (expr, "then");
cJSON *else_expr = cJSON_GetObjectItemCaseSensitive (expr, "else");
char *else_label = mach_gen_label (s, "tern_else");
char *end_label = mach_gen_label (s, "tern_end");
int cond_slot = mach_gen_expr (s, cond, -1);
mach_gen_emit_jump_cond (s, "jump_false", cond_slot, else_label);
int dest = mach_gen_alloc_slot (s);
int then_slot = mach_gen_expr (s, then_expr, -1);
mach_gen_emit_2 (s, "move", dest, then_slot);
mach_gen_emit_jump (s, end_label);
mach_gen_emit_label (s, else_label);
int else_slot = mach_gen_expr (s, else_expr, -1);
mach_gen_emit_2 (s, "move", dest, else_slot);
mach_gen_emit_label (s, end_label);
sys_free (else_label);
sys_free (end_label);
return dest;
}
/* Array literal */
if (strcmp (kind, "array") == 0) {
cJSON *list = cJSON_GetObjectItemCaseSensitive (expr, "list");
int count = cJSON_GetArraySize (list);
cJSON *elem_slots = cJSON_CreateArray ();
cJSON *elem;
cJSON_ArrayForEach (elem, list) {
int slot = mach_gen_expr (s, elem, -1);
cJSON_AddItemToArray (elem_slots, cJSON_CreateNumber (slot));
}
int dest = mach_gen_alloc_slot (s);
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("array"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (count));
cJSON *el;
cJSON_ArrayForEach (el, elem_slots) {
cJSON_AddItemToArray (instr, cJSON_CreateNumber (el->valueint));
}
cJSON_AddItemToArray (s->instructions, instr);
cJSON_Delete (elem_slots);
return dest;
}
/* Object literal */
if (strcmp (kind, "record") == 0) {
cJSON *list = cJSON_GetObjectItemCaseSensitive (expr, "list");
int dest = mach_gen_alloc_slot (s);
cJSON *instr = cJSON_CreateArray ();
cJSON_AddItemToArray (instr, cJSON_CreateString ("record"));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest));
cJSON_AddItemToArray (instr, cJSON_CreateNumber (0));
cJSON_AddItemToArray (s->instructions, instr);
cJSON *pair;
cJSON_ArrayForEach (pair, list) {
cJSON *key = cJSON_GetObjectItemCaseSensitive (pair, "left");
cJSON *val = cJSON_GetObjectItemCaseSensitive (pair, "right");
int val_slot = mach_gen_expr (s, val, -1);
const char *key_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (key, "kind"));
if (key_kind && strcmp (key_kind, "name") == 0) {
const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (key, "name"));
mach_gen_emit_set_prop (s, dest, name, val_slot);
} else if (key_kind && strcmp (key_kind, "text") == 0) {
const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (key, "value"));
mach_gen_emit_set_prop (s, dest, name ? name : "", val_slot);
} else {
int key_slot = mach_gen_expr (s, key, -1);
mach_gen_emit_set_elem (s, dest, key_slot, val_slot);
}
}
return dest;
}
/* Function expression */
if (strcmp (kind, "function") == 0) {
cJSON *func = mach_gen_function (s, expr);
int func_id = s->func_counter++;
cJSON_AddItemToArray (s->functions, func);
int dest = mach_gen_alloc_slot (s);
mach_gen_emit_2 (s, "function", dest, func_id);
return dest;
}
/* Assignment operators */
if (strcmp (kind, "assign") == 0 ||
strcmp (kind, "+=") == 0 || strcmp (kind, "-=") == 0 ||
strcmp (kind, "*=") == 0 || strcmp (kind, "/=") == 0 ||
strcmp (kind, "%=") == 0 || strcmp (kind, "**=") == 0 ||
strcmp (kind, "&=") == 0 || strcmp (kind, "|=") == 0 ||
strcmp (kind, "^=") == 0 || strcmp (kind, "<<=") == 0 ||
strcmp (kind, ">>=") == 0 || strcmp (kind, ">>>=") == 0 ||
strcmp (kind, "&&=") == 0 || strcmp (kind, "||=") == 0 ||
strcmp (kind, "??=") == 0) {
return mach_gen_assign (s, expr);
}
/* Binary operators */
return mach_gen_binary (s, expr);
}
static void mach_gen_statement (MachGenState *s, cJSON *stmt) {
if (!stmt) return;
mach_gen_set_pos (s, stmt);
const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (stmt, "kind"));
if (!kind) return;
if (strcmp (kind, "var") == 0 || strcmp (kind, "def") == 0) {
cJSON *left = cJSON_GetObjectItemCaseSensitive (stmt, "left");
cJSON *right = cJSON_GetObjectItemCaseSensitive (stmt, "right");
const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "name"));
const char *scope_name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "scope_name"));
const char *lookup_name = scope_name ? scope_name : name;
int local_slot = mach_gen_find_var (s, lookup_name);
/* Pop: var val = arr[] */
cJSON *pop_flag = cJSON_GetObjectItemCaseSensitive (stmt, "pop");
if (pop_flag && cJSON_IsTrue (pop_flag) && right) {
cJSON *arr_expr = cJSON_GetObjectItemCaseSensitive (right, "left");
int arr_slot = mach_gen_expr (s, arr_expr, -1);
if (local_slot >= 0)
mach_gen_emit_2 (s, "pop", local_slot, arr_slot);
return;
}
if (right) {
int val_slot = mach_gen_expr (s, right, local_slot);
if (local_slot >= 0 && val_slot != local_slot)
mach_gen_emit_2 (s, "move", local_slot, val_slot);
} else if (local_slot >= 0) {
mach_gen_emit_const_null (s, local_slot);
}
return;
}
if (strcmp (kind, "var_list") == 0 || strcmp (kind, "def_list") == 0) {
cJSON *list = cJSON_GetObjectItemCaseSensitive (stmt, "list");
cJSON *child;
cJSON_ArrayForEach (child, list) { mach_gen_statement (s, child); }
return;
}
if (strcmp (kind, "block") == 0) {
cJSON *stmts = cJSON_GetObjectItemCaseSensitive (stmt, "statements");
cJSON *child;
cJSON_ArrayForEach (child, stmts) { mach_gen_statement (s, child); }
return;
}
if (strcmp (kind, "if") == 0) {
cJSON *cond = cJSON_GetObjectItemCaseSensitive (stmt, "expression");
cJSON *then_stmts = cJSON_GetObjectItemCaseSensitive (stmt, "then");
cJSON *else_stmts = cJSON_GetObjectItemCaseSensitive (stmt, "else");
/* Parser uses "list" for else-if chains */
if (!else_stmts) else_stmts = cJSON_GetObjectItemCaseSensitive (stmt, "list");
char *else_label = mach_gen_label (s, "if_else");
char *end_label = mach_gen_label (s, "if_end");
int cond_slot = mach_gen_expr (s, cond, -1);
mach_gen_emit_jump_cond (s, "jump_false", cond_slot, else_label);
cJSON *child;
cJSON_ArrayForEach (child, then_stmts) { mach_gen_statement (s, child); }
mach_gen_emit_jump (s, end_label);
mach_gen_emit_label (s, else_label);
if (else_stmts) {
cJSON_ArrayForEach (child, else_stmts) { mach_gen_statement (s, child); }
}
mach_gen_emit_label (s, end_label);
sys_free (else_label);
sys_free (end_label);
return;
}
if (strcmp (kind, "while") == 0) {
cJSON *cond = cJSON_GetObjectItemCaseSensitive (stmt, "expression");
cJSON *stmts = cJSON_GetObjectItemCaseSensitive (stmt, "statements");
char *start_label = mach_gen_label (s, "while_start");
char *end_label = mach_gen_label (s, "while_end");
const char *old_break = s->loop_break;
const char *old_continue = s->loop_continue;
s->loop_break = end_label;
s->loop_continue = start_label;
mach_gen_emit_label (s, start_label);
int cond_slot = mach_gen_expr (s, cond, -1);
mach_gen_emit_jump_cond (s, "jump_false", cond_slot, end_label);
cJSON *child;
cJSON_ArrayForEach (child, stmts) { mach_gen_statement (s, child); }
mach_gen_emit_jump (s, start_label);
mach_gen_emit_label (s, end_label);
s->loop_break = old_break;
s->loop_continue = old_continue;
sys_free (start_label);
sys_free (end_label);
return;
}
if (strcmp (kind, "do") == 0) {
cJSON *cond = cJSON_GetObjectItemCaseSensitive (stmt, "expression");
cJSON *stmts = cJSON_GetObjectItemCaseSensitive (stmt, "statements");
char *start_label = mach_gen_label (s, "do_start");
char *cond_label = mach_gen_label (s, "do_cond");
char *end_label = mach_gen_label (s, "do_end");
const char *old_break = s->loop_break;
const char *old_continue = s->loop_continue;
s->loop_break = end_label;
s->loop_continue = cond_label;
mach_gen_emit_label (s, start_label);
cJSON *child;
cJSON_ArrayForEach (child, stmts) { mach_gen_statement (s, child); }
mach_gen_emit_label (s, cond_label);
int cond_slot = mach_gen_expr (s, cond, -1);
mach_gen_emit_jump_cond (s, "jump_true", cond_slot, start_label);
mach_gen_emit_label (s, end_label);
s->loop_break = old_break;
s->loop_continue = old_continue;
sys_free (start_label);
sys_free (cond_label);
sys_free (end_label);
return;
}
if (strcmp (kind, "for") == 0) {
cJSON *init = cJSON_GetObjectItemCaseSensitive (stmt, "init");
cJSON *test = cJSON_GetObjectItemCaseSensitive (stmt, "test");
cJSON *update = cJSON_GetObjectItemCaseSensitive (stmt, "update");
cJSON *stmts = cJSON_GetObjectItemCaseSensitive (stmt, "statements");
char *start_label = mach_gen_label (s, "for_start");
char *update_label = mach_gen_label (s, "for_update");
char *end_label = mach_gen_label (s, "for_end");
const char *old_break = s->loop_break;
const char *old_continue = s->loop_continue;
s->loop_break = end_label;
s->loop_continue = update_label;
if (init) {
const char *init_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (init, "kind"));
if (init_kind && (strcmp (init_kind, "var") == 0 || strcmp (init_kind, "def") == 0))
mach_gen_statement (s, init);
else
mach_gen_expr (s, init, -1);
}
mach_gen_emit_label (s, start_label);
if (test) {
int test_slot = mach_gen_expr (s, test, -1);
mach_gen_emit_jump_cond (s, "jump_false", test_slot, end_label);
}
cJSON *child;
cJSON_ArrayForEach (child, stmts) { mach_gen_statement (s, child); }
mach_gen_emit_label (s, update_label);
if (update) mach_gen_expr (s, update, -1);
mach_gen_emit_jump (s, start_label);
mach_gen_emit_label (s, end_label);
s->loop_break = old_break;
s->loop_continue = old_continue;
sys_free (start_label);
sys_free (update_label);
sys_free (end_label);
return;
}
if (strcmp (kind, "return") == 0) {
cJSON *expr = cJSON_GetObjectItemCaseSensitive (stmt, "expression");
if (expr) {
int slot = mach_gen_expr (s, expr, -1);
mach_gen_emit_1 (s, "return", slot);
} else {
int null_slot = mach_gen_alloc_slot (s);
mach_gen_emit_1 (s, "null", null_slot);
mach_gen_emit_1 (s, "return", null_slot);
}
return;
}
if (strcmp (kind, "go") == 0) {
cJSON *call_expr = cJSON_GetObjectItemCaseSensitive (stmt, "expression");
if (!call_expr) { mach_gen_error (s, stmt, "'go' requires a function call expression"); return; }
const char *call_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (call_expr, "kind"));
if (!call_kind || strcmp (call_kind, "(") != 0) { mach_gen_error (s, stmt, "'go' requires a function call expression"); return; }
cJSON *callee = cJSON_GetObjectItemCaseSensitive (call_expr, "expression");
cJSON *args_list = cJSON_GetObjectItemCaseSensitive (call_expr, "list");
cJSON *arg_slots = cJSON_CreateArray ();
cJSON *arg;
cJSON_ArrayForEach (arg, args_list) {
int arg_slot = mach_gen_expr (s, arg, -1);
cJSON_AddItemToArray (arg_slots, cJSON_CreateNumber (arg_slot));
}
const char *callee_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (callee, "kind"));
if (callee_kind && strcmp (callee_kind, ".") == 0) {
cJSON *obj_node = cJSON_GetObjectItemCaseSensitive (callee, "left");
const char *prop = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (callee, "right"));
int obj_slot = mach_gen_expr (s, obj_node, -1);
mach_gen_emit_go_call_method (s, obj_slot, prop, arg_slots);
} else {
int func_slot = mach_gen_expr (s, callee, -1);
mach_gen_emit_go_call (s, func_slot, arg_slots);
}
cJSON_Delete (arg_slots);
return;
}
if (strcmp (kind, "disrupt") == 0) {
mach_gen_emit_0 (s, "disrupt");
return;
}
if (strcmp (kind, "break") == 0) {
if (s->loop_break) mach_gen_emit_jump (s, s->loop_break);
return;
}
if (strcmp (kind, "continue") == 0) {
if (s->loop_continue) mach_gen_emit_jump (s, s->loop_continue);
return;
}
if (strcmp (kind, "switch") == 0) {
cJSON *expr = cJSON_GetObjectItemCaseSensitive (stmt, "expression");
cJSON *cases = cJSON_GetObjectItemCaseSensitive (stmt, "cases");
int switch_val = mach_gen_expr (s, expr, -1);
char *end_label = mach_gen_label (s, "switch_end");
char *default_label = NULL;
const char *old_break = s->loop_break;
s->loop_break = end_label;
cJSON *case_node;
cJSON_ArrayForEach (case_node, cases) {
const char *case_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (case_node, "kind"));
if (strcmp (case_kind, "default") == 0) {
default_label = mach_gen_label (s, "switch_default");
} else {
char *case_label = mach_gen_label (s, "switch_case");
cJSON *case_expr = cJSON_GetObjectItemCaseSensitive (case_node, "expression");
int case_val = mach_gen_expr (s, case_expr, -1);
int cmp_slot = mach_gen_alloc_slot (s);
mach_gen_emit_3 (s, "eq", cmp_slot, switch_val, case_val);
mach_gen_emit_jump_cond (s, "jump_true", cmp_slot, case_label);
cJSON_AddStringToObject (case_node, "_label", case_label);
sys_free (case_label);
}
}
if (default_label) mach_gen_emit_jump (s, default_label);
else mach_gen_emit_jump (s, end_label);
cJSON_ArrayForEach (case_node, cases) {
const char *case_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (case_node, "kind"));
if (strcmp (case_kind, "default") == 0) {
mach_gen_emit_label (s, default_label);
sys_free (default_label);
} else {
const char *label = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (case_node, "_label"));
mach_gen_emit_label (s, label);
}
cJSON *case_stmts = cJSON_GetObjectItemCaseSensitive (case_node, "statements");
cJSON *child;
cJSON_ArrayForEach (child, case_stmts) { mach_gen_statement (s, child); }
}
mach_gen_emit_label (s, end_label);
s->loop_break = old_break;
sys_free (end_label);
return;
}
if (strcmp (kind, "function") == 0) {
cJSON *name_obj = cJSON_GetObjectItemCaseSensitive (stmt, "name");
if (name_obj && cJSON_IsString (name_obj)) {
const char *name = cJSON_GetStringValue (name_obj);
cJSON *func = mach_gen_function (s, stmt);
int func_id = s->func_counter++;
cJSON_AddItemToArray (s->functions, func);
int local_slot = mach_gen_find_var (s, name);
int dest = mach_gen_alloc_slot (s);
mach_gen_emit_2 (s, "function", dest, func_id);
if (local_slot >= 0) mach_gen_emit_2 (s, "move", local_slot, dest);
}
return;
}
if (strcmp (kind, "call") == 0) {
cJSON *expr = cJSON_GetObjectItemCaseSensitive (stmt, "expression");
mach_gen_expr (s, expr, -1);
return;
}
mach_gen_expr (s, stmt, -1);
}
static cJSON *mach_gen_function (MachGenState *parent, cJSON *func_node) {
MachGenState s = {0};
s.instructions = cJSON_CreateArray ();
s.data = parent->data;
s.functions = parent->functions;
s.parent = parent;
s.label_counter = parent->label_counter;
s.func_counter = parent->func_counter;
s.scopes = parent->scopes;
s.errors = parent->errors;
s.has_error = parent->has_error;
s.filename = parent->filename;
cJSON *result = cJSON_CreateObject ();
cJSON *name_obj = cJSON_GetObjectItemCaseSensitive (func_node, "name");
if (name_obj && cJSON_IsString (name_obj))
cJSON_AddStringToObject (result, "name", cJSON_GetStringValue (name_obj));
else
cJSON_AddStringToObject (result, "name", "<anonymous>");
if (s.filename)
cJSON_AddStringToObject (result, "filename", s.filename);
cJSON *is_arrow = cJSON_GetObjectItemCaseSensitive (func_node, "arrow");
s.is_arrow = is_arrow && cJSON_IsTrue (is_arrow);
/* Read function_nr from AST node */
cJSON *fn_nr_node = cJSON_GetObjectItemCaseSensitive (func_node, "function_nr");
s.function_nr = fn_nr_node ? (int)cJSON_GetNumberValue (fn_nr_node) : 0;
/* Parameters */
cJSON *params = cJSON_GetObjectItemCaseSensitive (func_node, "list");
if (!params) params = cJSON_GetObjectItemCaseSensitive (func_node, "parameters");
s.nr_args = cJSON_GetArraySize (params);
s.this_slot = 0;
s.nr_close_slots = 0;
s.nr_local_slots = 0;
/* Use nr_slots from AST to pre-allocate var capacity */
cJSON *ns = cJSON_GetObjectItemCaseSensitive (func_node, "nr_slots");
int ast_nr_slots = ns ? (int)cJSON_GetNumberValue (ns) : 0;
if (ast_nr_slots > 0) {
s.var_capacity = ast_nr_slots;
s.vars = sys_malloc (s.var_capacity * sizeof(MachVarInfo));
}
int param_slot = 1;
cJSON *param;
cJSON_ArrayForEach (param, params) {
const char *param_name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (param, "name"));
if (!param_name) param_name = cJSON_GetStringValue (param);
if (param_name) {
mach_gen_add_var (&s, param_name, param_slot, 1);
param_slot++;
}
}
s.next_temp_slot = 1 + s.nr_args;
s.max_slot = 1 + s.nr_args;
cJSON_AddNumberToObject (result, "nr_args", s.nr_args);
/* Scan scope record for variable declarations */
mach_gen_scan_scope (&s);
s.next_temp_slot = 1 + s.nr_args + s.nr_local_slots;
if (s.next_temp_slot > s.max_slot) s.max_slot = s.next_temp_slot;
/* Emit default parameter initialization */
{
int ps = 1;
cJSON *pp;
cJSON_ArrayForEach(pp, params) {
cJSON *default_expr = cJSON_GetObjectItemCaseSensitive(pp, "expression");
if (default_expr) {
char *end_label = mach_gen_label(&s, "default_end");
mach_gen_emit_jump_cond(&s, "jump_not_null", ps, end_label);
int default_slot = mach_gen_expr(&s, default_expr, -1);
mach_gen_emit_2(&s, "move", ps, default_slot);
mach_gen_emit_label(&s, end_label);
sys_free(end_label);
}
ps++;
}
}
/* Pre-load intrinsics (global names) */
mach_gen_load_intrinsics (&s, cJSON_GetObjectItemCaseSensitive (func_node, "intrinsics"));
/* Compile hoisted function declarations from func_node["functions"] */
cJSON *hoisted = cJSON_GetObjectItemCaseSensitive (func_node, "functions");
if (hoisted) {
cJSON *fn;
cJSON_ArrayForEach (fn, hoisted) {
const char *fname = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (fn, "name"));
if (fname) {
cJSON *compiled = mach_gen_function (&s, fn);
int func_id = s.func_counter++;
cJSON_AddItemToArray (s.functions, compiled);
int local_slot = mach_gen_find_var (&s, fname);
int dest = mach_gen_alloc_slot (&s);
mach_gen_emit_2 (&s, "function", dest, func_id);
if (local_slot >= 0) mach_gen_emit_2 (&s, "move", local_slot, dest);
}
}
}
/* Compile body */
cJSON *stmts = cJSON_GetObjectItemCaseSensitive (func_node, "statements");
if (!stmts) {
cJSON *body = cJSON_GetObjectItemCaseSensitive (func_node, "body");
if (body) {
stmts = cJSON_GetObjectItemCaseSensitive (body, "statements");
if (!stmts) stmts = body;
}
}
cJSON *stmt;
if (stmts && cJSON_IsArray (stmts)) {
cJSON_ArrayForEach (stmt, stmts) { mach_gen_statement (&s, stmt); }
}
{
int null_slot = mach_gen_alloc_slot (&s);
mach_gen_emit_1 (&s, "null", null_slot);
mach_gen_emit_1 (&s, "return", null_slot);
}
/* Compile disruption clause if present */
int disruption_start = 0;
cJSON *disruption = cJSON_GetObjectItemCaseSensitive (func_node, "disruption");
if (disruption && cJSON_IsArray (disruption)) {
disruption_start = cJSON_GetArraySize (s.instructions);
cJSON_ArrayForEach (stmt, disruption) { mach_gen_statement (&s, stmt); }
int null_slot2 = mach_gen_alloc_slot (&s);
mach_gen_emit_1 (&s, "null", null_slot2);
mach_gen_emit_1 (&s, "return", null_slot2);
}
cJSON_AddNumberToObject (result, "disruption_pc", disruption_start);
cJSON *fn_scope = mach_find_scope_record (s.scopes, s.function_nr);
cJSON *fn_ncs = fn_scope ? cJSON_GetObjectItemCaseSensitive (fn_scope, "nr_close_slots") : NULL;
cJSON_AddNumberToObject (result, "nr_close_slots", fn_ncs ? (int)cJSON_GetNumberValue (fn_ncs) : 0);
cJSON_AddNumberToObject (result, "nr_slots", s.max_slot + 1);
cJSON_AddItemToObject (result, "instructions", s.instructions);
parent->label_counter = s.label_counter;
parent->func_counter = s.func_counter;
if (s.errors && s.errors != parent->errors) {
if (!parent->errors) { parent->errors = s.errors; }
else {
cJSON *err;
cJSON_ArrayForEach (err, s.errors) { cJSON_AddItemToArray (parent->errors, cJSON_Duplicate (err, 1)); }
cJSON_Delete (s.errors);
}
}
parent->has_error = parent->has_error || s.has_error;
for (int i = 0; i < s.var_count; i++) sys_free (s.vars[i].name);
if (s.vars) sys_free (s.vars);
return result;
}
static cJSON *mach_gen_program (MachGenState *s, cJSON *ast) {
cJSON *result = cJSON_CreateObject ();
const char *filename = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (ast, "filename"));
cJSON_AddStringToObject (result, "name", filename ? filename : "<eval>");
s->filename = filename;
if (filename)
cJSON_AddStringToObject (result, "filename", filename);
s->data = cJSON_AddObjectToObject (result, "data");
s->functions = cJSON_AddArrayToObject (result, "functions");
/* Read scopes from AST */
s->scopes = cJSON_GetObjectItemCaseSensitive (ast, "scopes");
s->this_slot = 0;
s->nr_args = 0;
s->nr_close_slots = 0;
s->nr_local_slots = 0;
s->next_temp_slot = 1;
s->max_slot = 1;
/* Use nr_slots from AST to pre-allocate var capacity */
cJSON *ns = cJSON_GetObjectItemCaseSensitive (ast, "nr_slots");
int ast_nr_slots = ns ? (int)cJSON_GetNumberValue (ns) : 0;
if (ast_nr_slots > 0) {
s->var_capacity = ast_nr_slots;
s->vars = sys_malloc (s->var_capacity * sizeof(MachVarInfo));
}
/* Scan scope record for variable declarations */
mach_gen_scan_scope (s);
s->next_temp_slot = 1 + s->nr_local_slots;
if (s->next_temp_slot > s->max_slot) s->max_slot = s->next_temp_slot;
/* Intrinsics are loaded lazily at point of use instead of pre-loading,
to avoid loading nested function intrinsics in the wrong scope. */
/* Compile hoisted function declarations from ast["functions"] */
cJSON *hoisted = cJSON_GetObjectItemCaseSensitive (ast, "functions");
if (hoisted) {
cJSON *fn;
cJSON_ArrayForEach (fn, hoisted) {
const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (fn, "name"));
if (name) {
cJSON *compiled = mach_gen_function (s, fn);
int func_id = s->func_counter++;
cJSON_AddItemToArray (s->functions, compiled);
int local_slot = mach_gen_find_var (s, name);
int dest = mach_gen_alloc_slot (s);
mach_gen_emit_2 (s, "function", dest, func_id);
if (local_slot >= 0) mach_gen_emit_2 (s, "move", local_slot, dest);
}
}
}
/* Generate main code */
cJSON *statements = cJSON_GetObjectItemCaseSensitive (ast, "statements");
int last_expr_slot = -1;
cJSON *stmt;
cJSON_ArrayForEach (stmt, statements) {
const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (stmt, "kind"));
if (kind) {
if (strcmp (kind, "call") == 0) {
cJSON *expr = cJSON_GetObjectItemCaseSensitive (stmt, "expression");
last_expr_slot = mach_gen_expr (s, expr, -1);
} else if (strcmp (kind, "return") == 0 || strcmp (kind, "disrupt") == 0 ||
strcmp (kind, "break") == 0 || strcmp (kind, "continue") == 0) {
mach_gen_statement (s, stmt);
last_expr_slot = -1;
} else if (strcmp (kind, "var") == 0 || strcmp (kind, "def") == 0 ||
strcmp (kind, "var_list") == 0 || strcmp (kind, "def_list") == 0 ||
strcmp (kind, "function") == 0 || strcmp (kind, "block") == 0 ||
strcmp (kind, "if") == 0 || strcmp (kind, "while") == 0 ||
strcmp (kind, "do") == 0 || strcmp (kind, "for") == 0 ||
strcmp (kind, "switch") == 0) {
mach_gen_statement (s, stmt);
last_expr_slot = -1;
} else {
last_expr_slot = mach_gen_expr (s, stmt, -1);
}
} else {
mach_gen_statement (s, stmt);
}
}
if (last_expr_slot >= 0) {
mach_gen_emit_1 (s, "return", last_expr_slot);
} else {
int null_slot = mach_gen_alloc_slot (s);
mach_gen_emit_1 (s, "null", null_slot);
mach_gen_emit_1 (s, "return", null_slot);
}
cJSON *main_obj = cJSON_CreateObject ();
cJSON_AddNumberToObject (main_obj, "nr_args", 0);
cJSON_AddNumberToObject (main_obj, "nr_close_slots", 0);
cJSON_AddNumberToObject (main_obj, "nr_slots", s->max_slot + 1);
cJSON_AddItemToObject (main_obj, "instructions", s->instructions);
cJSON_AddItemToObject (result, "main", main_obj);
return result;
}
cJSON *JS_McodeTree (cJSON *ast) {
if (!ast) return NULL;
MachGenState s = {0};
s.instructions = cJSON_CreateArray ();
s.errors = NULL;
s.has_error = 0;
cJSON *mach = mach_gen_program (&s, ast);
for (int i = 0; i < s.var_count; i++) sys_free (s.vars[i].name);
if (s.vars) sys_free (s.vars);
if (!mach) {
if (s.errors) cJSON_Delete (s.errors);
return NULL;
}
if (s.errors)
cJSON_AddItemToObject (mach, "errors", s.errors);
return mach;
}
char *JS_Mcode (const char *ast_json) {
cJSON *ast = cJSON_Parse (ast_json);
if (!ast) return NULL;
cJSON *mach = JS_McodeTree (ast);
cJSON_Delete (ast);
if (!mach) return NULL;
char *json = cJSON_PrintUnformatted (mach);
cJSON_Delete (mach);
return json;
}
/* ============================================================
MCODE JSON Interpreter
============================================================ */
/* Parse a single MCODE function from cJSON into a JSMCode struct */
/* Parse a single function (no recursive function parsing) */
JSMCode *jsmcode_parse_one(cJSON *func_def) {
JSMCode *code = js_mallocz_rt(sizeof(JSMCode));
if (!code) return NULL;
code->nr_args = (uint16_t)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(func_def, "nr_args"));
code->nr_slots = (uint16_t)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(func_def, "nr_slots"));
/* Build instruction array from cJSON linked list */
cJSON *instrs_arr = cJSON_GetObjectItemCaseSensitive(func_def, "instructions");
int raw_count = cJSON_GetArraySize(instrs_arr);
code->instrs = js_mallocz_rt(raw_count * sizeof(cJSON *));
code->instr_count = 0;
/* First pass: count labels and build instruction array */
uint32_t label_cap = 32;
code->labels = js_mallocz_rt(label_cap * sizeof(*code->labels));
code->label_count = 0;
cJSON *item;
uint32_t idx = 0;
cJSON_ArrayForEach(item, instrs_arr) {
if (cJSON_IsString(item)) {
/* Label marker — record position, don't add to instruction array */
if (code->label_count >= label_cap) {
label_cap *= 2;
code->labels = js_realloc_rt(code->labels, label_cap * sizeof(*code->labels));
}
code->labels[code->label_count].name = item->valuestring;
code->labels[code->label_count].index = idx;
code->label_count++;
} else {
/* Instruction (array) */
code->instrs[idx++] = item;
}
}
code->instr_count = idx;
/* Extract line table from trailing numbers in each instruction array */
if (idx > 0) {
code->line_table = js_mallocz_rt(idx * sizeof(MachLineEntry));
for (uint32_t i = 0; i < idx; i++) {
cJSON *instr = code->instrs[i];
int n = cJSON_GetArraySize(instr);
if (n >= 2) {
cJSON *line_item = cJSON_GetArrayItem(instr, n - 2);
cJSON *col_item = cJSON_GetArrayItem(instr, n - 1);
if (cJSON_IsNumber(line_item) && cJSON_IsNumber(col_item)) {
code->line_table[i].line = (uint16_t)line_item->valuedouble;
code->line_table[i].col = (uint16_t)col_item->valuedouble;
}
}
}
}
/* Extract name and filename from function definition */
code->name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(func_def, "name"));
code->filename = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(func_def, "filename"));
/* Extract disruption_pc */
cJSON *dpc = cJSON_GetObjectItemCaseSensitive(func_def, "disruption_pc");
code->disruption_pc = dpc ? (uint16_t)cJSON_GetNumberValue(dpc) : 0;
return code;
}
/* Parse MCODE: main + all functions from the global functions array.
All JSMCode structs share the same functions[] pointer. */
JSMCode *jsmcode_parse(cJSON *func_def, cJSON *all_functions) {
/* Parse the global functions array first (flat, non-recursive) */
uint32_t func_count = all_functions ? cJSON_GetArraySize(all_functions) : 0;
JSMCode **parsed_funcs = NULL;
if (func_count > 0) {
parsed_funcs = js_mallocz_rt(func_count * sizeof(JSMCode *));
for (uint32_t i = 0; i < func_count; i++) {
cJSON *fn = cJSON_GetArrayItem(all_functions, i);
parsed_funcs[i] = jsmcode_parse_one(fn);
/* Each function shares the same functions array */
parsed_funcs[i]->func_count = func_count;
parsed_funcs[i]->functions = parsed_funcs;
}
}
/* Parse the main function */
JSMCode *code = jsmcode_parse_one(func_def);
code->func_count = func_count;
code->functions = parsed_funcs;
return code;
}
/* Free a top-level JSMCode and all its shared functions.
Only call this on the main code returned by jsmcode_parse. */
void jsmcode_free(JSMCode *code) {
if (!code) return;
/* Free all parsed functions (they share the same functions array) */
if (code->functions) {
for (uint32_t i = 0; i < code->func_count; i++) {
if (code->functions[i]) {
/* Don't free functions[i]->functions — it's the shared pointer */
if (code->functions[i]->instrs) js_free_rt(code->functions[i]->instrs);
if (code->functions[i]->labels) js_free_rt(code->functions[i]->labels);
if (code->functions[i]->line_table) js_free_rt(code->functions[i]->line_table);
js_free_rt(code->functions[i]);
}
}
js_free_rt(code->functions);
}
if (code->instrs) js_free_rt(code->instrs);
if (code->labels) js_free_rt(code->labels);
if (code->line_table) js_free_rt(code->line_table);
if (code->json_root) cJSON_Delete(code->json_root);
js_free_rt(code);
}
/* Resolve label name → instruction index */
static uint32_t mcode_resolve_label(JSMCode *code, const char *name) {
for (uint32_t i = 0; i < code->label_count; i++) {
if (strcmp(code->labels[i].name, name) == 0)
return code->labels[i].index;
}
return code->instr_count; /* past end = implicit return */
}
/* Create a MCODE function object.
outer_frame must be set by the caller AFTER refreshing from GC root,
since js_mallocz can trigger GC which invalidates stale JSValues. */
JSValue js_new_mcode_function(JSContext *ctx, JSMCode *code) {
JSFunction *fn = js_mallocz(ctx, sizeof(JSFunction));
if (!fn) return JS_EXCEPTION;
fn->header = objhdr_make(0, OBJ_FUNCTION, 0, 0, 0, 0);
fn->kind = JS_FUNC_KIND_MCODE;
fn->length = code->nr_args;
fn->name = JS_NULL;
fn->u.mcode.code = code;
fn->u.mcode.outer_frame = JS_NULL;
fn->u.mcode.env_record = JS_NULL;
return JS_MKPTR(fn);
}
/* Main MCODE interpreter — executes pre-parsed JSMCode */
JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj,
int argc, JSValue *argv, JSValue outer_frame) {
/* Protect argv, this_obj, outer_frame from GC by pushing onto value_stack.
alloc_frame_register and js_new_mcode_function can trigger GC. */
int vs_save = ctx->value_stack_top;
int nargs_copy = (argc < code->nr_args) ? argc : code->nr_args;
ctx->value_stack[vs_save] = this_obj;
ctx->value_stack[vs_save + 1] = outer_frame;
for (int i = 0; i < nargs_copy; i++)
ctx->value_stack[vs_save + 2 + i] = argv[i];
ctx->value_stack_top = vs_save + 2 + nargs_copy;
JSFrameRegister *frame = alloc_frame_register(ctx, code->nr_slots);
if (!frame) { ctx->value_stack_top = vs_save; return JS_EXCEPTION; }
/* Protect frame from GC */
JSGCRef frame_ref;
JS_AddGCRef(ctx, &frame_ref);
frame_ref.val = JS_MKPTR(frame);
/* Create a function object for the main frame so return can find the code */
JSValue main_func = js_new_mcode_function(ctx, code);
if (JS_IsException(main_func)) {
ctx->value_stack_top = vs_save;
JS_DeleteGCRef(ctx, &frame_ref);
return JS_ThrowInternalError(ctx, "failed to allocate main function for MCODE");
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
/* Set outer_frame AFTER allocation so it uses the post-GC value */
JSFunction *main_fn = JS_VALUE_GET_FUNCTION(main_func);
main_fn->u.mcode.outer_frame = ctx->value_stack[vs_save + 1];
frame->function = main_func;
/* Setup initial frame from GC-safe value_stack */
frame->slots[0] = ctx->value_stack[vs_save]; /* slot 0 is this */
for (int i = 0; i < nargs_copy; i++) {
frame->slots[1 + i] = ctx->value_stack[vs_save + 2 + i];
}
ctx->value_stack_top = vs_save;
uint32_t pc = 0;
JSValue result = JS_NULL;
for (;;) {
/* Check for interrupt */
if (reg_vm_check_interrupt(ctx)) {
result = JS_ThrowInternalError(ctx, "interrupted");
goto done;
}
if (pc >= code->instr_count) {
/* Implicit return null */
result = JS_NULL;
if (JS_IsNull(frame->caller)) goto done;
/* Pop frame — read return info from CALLER */
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
int ret_info = JS_VALUE_GET_INT(caller->address);
frame->caller = JS_NULL;
frame = caller;
frame_ref.val = JS_MKPTR(frame);
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
code = fn->u.mcode.code;
pc = ret_info >> 16;
frame->slots[ret_info & 0xFFFF] = result;
continue;
}
cJSON *instr = code->instrs[pc++];
cJSON *op_item = cJSON_GetArrayItem(instr, 0);
const char *op = op_item->valuestring;
/* Operand extraction helpers — items 1,2,3 */
cJSON *a1 = cJSON_GetArrayItem(instr, 1);
cJSON *a2 = cJSON_GetArrayItem(instr, 2);
cJSON *a3 = cJSON_GetArrayItem(instr, 3);
/* ---- Constants ---- */
if (strcmp(op, "access") == 0) {
int dest = (int)a1->valuedouble;
if (cJSON_IsObject(a2)) {
/* Intrinsic: {"kind":"name","name":"...","make":"intrinsic"} */
const char *iname = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(a2, "name"));
if (iname) {
JSValue key = JS_NewString(ctx, iname);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
JSValue val = JS_GetProperty(ctx, ctx->global_obj, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsNull(val)) {
key = JS_NewString(ctx, iname);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
int has = JS_HasProperty(ctx, ctx->global_obj, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (has <= 0) {
JS_ThrowReferenceError(ctx, "'%s' is not defined", iname);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
}
}
frame->slots[dest] = val;
} else {
frame->slots[dest] = JS_NULL;
}
} else if (cJSON_IsString(a2)) {
JSValue str = JS_NewString(ctx, a2->valuestring);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[dest] = str;
} else {
frame->slots[dest] = JS_NewFloat64(ctx, a2->valuedouble);
}
}
else if (strcmp(op, "int") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_NewFloat64(ctx, a2->valuedouble);
}
else if (strcmp(op, "null") == 0) {
frame->slots[(int)a1->valuedouble] = JS_NULL;
}
else if (strcmp(op, "true") == 0) {
frame->slots[(int)a1->valuedouble] = JS_TRUE;
}
else if (strcmp(op, "false") == 0) {
frame->slots[(int)a1->valuedouble] = JS_FALSE;
}
/* ---- Movement ---- */
else if (strcmp(op, "move") == 0) {
frame->slots[(int)a1->valuedouble] = frame->slots[(int)a2->valuedouble];
}
/* ---- Arithmetic (inline) ---- */
else if (strcmp(op, "add") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
int64_t r = (int64_t)JS_VALUE_GET_INT(left) + (int64_t)JS_VALUE_GET_INT(right);
frame->slots[dest] = (r >= INT32_MIN && r <= INT32_MAX)
? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r);
} else if (JS_IsNumber(left) && JS_IsNumber(right)) {
double a, b;
JS_ToFloat64(ctx, &a, left);
JS_ToFloat64(ctx, &b, right);
frame->slots[dest] = JS_NewFloat64(ctx, a + b);
} else if (JS_IsText(left) && JS_IsText(right)) {
JSValue res = JS_ConcatString(ctx, left, right);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[dest] = res;
} else { goto disrupt; }
}
else if (strcmp(op, "subtract") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
int64_t r = (int64_t)JS_VALUE_GET_INT(left) - (int64_t)JS_VALUE_GET_INT(right);
frame->slots[dest] = (r >= INT32_MIN && r <= INT32_MAX)
? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r);
} else if (JS_IsNumber(left) && JS_IsNumber(right)) {
double a, b;
JS_ToFloat64(ctx, &a, left);
JS_ToFloat64(ctx, &b, right);
frame->slots[dest] = JS_NewFloat64(ctx, a - b);
} else { goto disrupt; }
}
else if (strcmp(op, "multiply") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
int64_t r = (int64_t)JS_VALUE_GET_INT(left) * (int64_t)JS_VALUE_GET_INT(right);
frame->slots[dest] = (r >= INT32_MIN && r <= INT32_MAX)
? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r);
} else if (JS_IsNumber(left) && JS_IsNumber(right)) {
double a, b;
JS_ToFloat64(ctx, &a, left);
JS_ToFloat64(ctx, &b, right);
frame->slots[dest] = JS_NewFloat64(ctx, a * b);
} else { goto disrupt; }
}
else if (strcmp(op, "divide") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
int32_t ia = JS_VALUE_GET_INT(left), ib = JS_VALUE_GET_INT(right);
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 (JS_IsNumber(left) && JS_IsNumber(right)) {
double a, b;
JS_ToFloat64(ctx, &a, left);
JS_ToFloat64(ctx, &b, right);
if (b == 0.0) { frame->slots[dest] = JS_NULL; }
else frame->slots[dest] = JS_NewFloat64(ctx, a / b);
} else { goto disrupt; }
}
else if (strcmp(op, "integer_divide") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
int32_t ib = JS_VALUE_GET_INT(right);
if (ib == 0) { frame->slots[dest] = JS_NULL; }
else frame->slots[dest] = JS_NewInt32(ctx, JS_VALUE_GET_INT(left) / ib);
} else if (JS_IsNumber(left) && JS_IsNumber(right)) {
double a, b;
JS_ToFloat64(ctx, &a, left);
JS_ToFloat64(ctx, &b, right);
if (b == 0.0) { frame->slots[dest] = JS_NULL; }
else frame->slots[dest] = JS_NewInt32(ctx, (int32_t)(a / b));
} else { goto disrupt; }
}
else if (strcmp(op, "modulo") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
int32_t ib = JS_VALUE_GET_INT(right);
if (ib == 0) { frame->slots[dest] = JS_NULL; }
else frame->slots[dest] = JS_NewInt32(ctx, JS_VALUE_GET_INT(left) % ib);
} else if (JS_IsNumber(left) && JS_IsNumber(right)) {
double a, b;
JS_ToFloat64(ctx, &a, left);
JS_ToFloat64(ctx, &b, right);
if (b == 0.0) { frame->slots[dest] = JS_NULL; }
else frame->slots[dest] = JS_NewFloat64(ctx, fmod(a, b));
} else { goto disrupt; }
}
else if (strcmp(op, "remainder") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
int32_t ib = JS_VALUE_GET_INT(right);
if (ib == 0) { frame->slots[dest] = JS_NULL; }
else frame->slots[dest] = JS_NewInt32(ctx, JS_VALUE_GET_INT(left) % ib);
} else if (JS_IsNumber(left) && JS_IsNumber(right)) {
double a, b;
JS_ToFloat64(ctx, &a, left);
JS_ToFloat64(ctx, &b, right);
if (b == 0.0) { frame->slots[dest] = JS_NULL; }
else frame->slots[dest] = JS_NewFloat64(ctx, remainder(a, b));
} else { goto disrupt; }
}
else if (strcmp(op, "pow") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (JS_IsNumber(left) && JS_IsNumber(right)) {
double a, b;
JS_ToFloat64(ctx, &a, left);
JS_ToFloat64(ctx, &b, right);
frame->slots[dest] = JS_NewFloat64(ctx, pow(a, b));
} else { goto disrupt; }
}
else if (strcmp(op, "max") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
int32_t ia = JS_VALUE_GET_INT(left), ib = JS_VALUE_GET_INT(right);
frame->slots[dest] = JS_NewInt32(ctx, ia > ib ? ia : ib);
} else if (JS_IsNumber(left) && JS_IsNumber(right)) {
double a, b;
JS_ToFloat64(ctx, &a, left);
JS_ToFloat64(ctx, &b, right);
frame->slots[dest] = JS_NewFloat64(ctx, a > b ? a : b);
} else { goto disrupt; }
}
else if (strcmp(op, "min") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
int32_t ia = JS_VALUE_GET_INT(left), ib = JS_VALUE_GET_INT(right);
frame->slots[dest] = JS_NewInt32(ctx, ia < ib ? ia : ib);
} else if (JS_IsNumber(left) && JS_IsNumber(right)) {
double a, b;
JS_ToFloat64(ctx, &a, left);
JS_ToFloat64(ctx, &b, right);
frame->slots[dest] = JS_NewFloat64(ctx, a < b ? a : b);
} else { goto disrupt; }
}
else if (strcmp(op, "neg") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
if (!JS_IsNumber(v)) { goto disrupt; }
if (JS_IsInt(v)) {
int32_t i = JS_VALUE_GET_INT(v);
if (i == INT32_MIN) frame->slots[dest] = JS_NewFloat64(ctx, -(double)i);
else frame->slots[dest] = JS_NewInt32(ctx, -i);
} else {
double d;
JS_ToFloat64(ctx, &d, v);
frame->slots[dest] = JS_NewFloat64(ctx, -d);
}
}
else if (strcmp(op, "abs") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
if (!JS_IsNumber(v)) { goto disrupt; }
if (JS_IsInt(v)) {
int32_t i = JS_VALUE_GET_INT(v);
if (i == INT32_MIN) frame->slots[dest] = JS_NewFloat64(ctx, (double)INT32_MAX + 1.0);
else frame->slots[dest] = JS_NewInt32(ctx, i < 0 ? -i : i);
} else {
double d;
JS_ToFloat64(ctx, &d, v);
frame->slots[dest] = JS_NewFloat64(ctx, fabs(d));
}
}
else if (strcmp(op, "sign") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
if (!JS_IsNumber(v)) { goto disrupt; }
if (JS_IsInt(v)) {
int32_t i = JS_VALUE_GET_INT(v);
frame->slots[dest] = JS_NewInt32(ctx, (i > 0) - (i < 0));
} else {
double d;
JS_ToFloat64(ctx, &d, v);
frame->slots[dest] = JS_NewInt32(ctx, (d > 0) - (d < 0));
}
}
else if (strcmp(op, "fraction") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
if (!JS_IsNumber(v)) { goto disrupt; }
if (JS_IsInt(v)) {
frame->slots[dest] = JS_NewInt32(ctx, 0);
} else {
double d;
JS_ToFloat64(ctx, &d, v);
frame->slots[dest] = JS_NewFloat64(ctx, d - trunc(d));
}
}
else if (strcmp(op, "integer") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
if (!JS_IsNumber(v)) { goto disrupt; }
if (JS_IsInt(v)) {
frame->slots[dest] = v;
} else {
double d;
JS_ToFloat64(ctx, &d, v);
frame->slots[dest] = JS_NewFloat64(ctx, trunc(d));
}
}
else if (strcmp(op, "ceiling") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
JSValue p = frame->slots[(int)a3->valuedouble];
if (!JS_IsNumber(v) || !JS_IsNumber(p)) { goto disrupt; }
double d, place;
JS_ToFloat64(ctx, &d, v);
JS_ToFloat64(ctx, &place, p);
frame->slots[dest] = JS_NewFloat64(ctx, ceil(d * place) / place);
}
else if (strcmp(op, "floor") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
JSValue p = frame->slots[(int)a3->valuedouble];
if (!JS_IsNumber(v) || !JS_IsNumber(p)) { goto disrupt; }
double d, place;
JS_ToFloat64(ctx, &d, v);
JS_ToFloat64(ctx, &place, p);
frame->slots[dest] = JS_NewFloat64(ctx, floor(d * place) / place);
}
else if (strcmp(op, "round") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
JSValue p = frame->slots[(int)a3->valuedouble];
if (!JS_IsNumber(v) || !JS_IsNumber(p)) { goto disrupt; }
double d, place;
JS_ToFloat64(ctx, &d, v);
JS_ToFloat64(ctx, &place, p);
frame->slots[dest] = JS_NewFloat64(ctx, round(d * place) / place);
}
else if (strcmp(op, "trunc") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
JSValue p = frame->slots[(int)a3->valuedouble];
if (!JS_IsNumber(v) || !JS_IsNumber(p)) { goto disrupt; }
double d, place;
JS_ToFloat64(ctx, &d, v);
JS_ToFloat64(ctx, &place, p);
frame->slots[dest] = JS_NewFloat64(ctx, trunc(d * place) / place);
}
/* ---- Text ---- */
else if (strcmp(op, "concat") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (!JS_IsText(left) || !JS_IsText(right)) { goto disrupt; }
JSValue res = JS_ConcatString(ctx, left, right);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[dest] = res;
}
else if (strcmp(op, "concat_space") == 0) {
int dest = (int)a1->valuedouble;
int left_slot = (int)a2->valuedouble;
int right_slot = (int)a3->valuedouble;
JSValue left = frame->slots[left_slot];
JSValue right = frame->slots[right_slot];
if (!JS_IsText(left) || !JS_IsText(right)) { goto disrupt; }
JSValue space_str = JS_NewString(ctx, " ");
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
JSValue space = JS_ConcatString(ctx, frame->slots[left_slot], space_str);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
JSValue res = JS_ConcatString(ctx, space, frame->slots[right_slot]);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[dest] = res;
}
else if (strcmp(op, "length") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
if (!JS_IsText(v)) { goto disrupt; }
frame->slots[dest] = JS_NewInt32(ctx, js_string_value_len(v));
}
else if (strcmp(op, "lower") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
if (!JS_IsText(v)) { goto disrupt; }
int vs_base = ctx->value_stack_top;
ctx->value_stack[vs_base] = v;
ctx->value_stack_top = vs_base + 1;
JSValue res = js_cell_text_lower(ctx, JS_NULL, 1, &ctx->value_stack[vs_base]);
ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[dest] = res;
}
else if (strcmp(op, "upper") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
if (!JS_IsText(v)) { goto disrupt; }
int vs_base = ctx->value_stack_top;
ctx->value_stack[vs_base] = v;
ctx->value_stack_top = vs_base + 1;
JSValue res = js_cell_text_upper(ctx, JS_NULL, 1, &ctx->value_stack[vs_base]);
ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[dest] = res;
}
else if (strcmp(op, "character") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
if (!JS_IsNumber(v)) { goto disrupt; }
int vs_base = ctx->value_stack_top;
ctx->value_stack[vs_base] = v;
ctx->value_stack_top = vs_base + 1;
JSValue res = js_cell_character(ctx, JS_NULL, 1, &ctx->value_stack[vs_base]);
ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[dest] = res;
}
else if (strcmp(op, "codepoint") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
if (!JS_IsText(v)) { goto disrupt; }
int vs_base = ctx->value_stack_top;
ctx->value_stack[vs_base] = v;
ctx->value_stack_top = vs_base + 1;
JSValue res = js_cell_text_codepoint(ctx, JS_NULL, 1, &ctx->value_stack[vs_base]);
ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[dest] = res;
}
/* ---- Comparison (inline) ---- */
else if (strcmp(op, "eq") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (left == right) {
frame->slots[dest] = JS_TRUE;
} else if (JS_VALUE_IS_BOTH_INT(left, right)) {
frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) == JS_VALUE_GET_INT(right));
} else if (JS_IsNumber(left) && JS_IsNumber(right)) {
double a, b;
JS_ToFloat64(ctx, &a, left);
JS_ToFloat64(ctx, &b, right);
frame->slots[dest] = JS_NewBool(ctx, a == b);
} else if (JS_IsText(left) && JS_IsText(right)) {
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, left, right, TRUE) == 0);
} else if (JS_IsNull(left) && JS_IsNull(right)) {
frame->slots[dest] = JS_TRUE;
} else if (JS_VALUE_GET_TAG(left) == JS_TAG_BOOL && JS_VALUE_GET_TAG(right) == JS_TAG_BOOL) {
frame->slots[dest] = JS_NewBool(ctx, left == right);
} else {
frame->slots[dest] = JS_FALSE;
}
}
else if (strcmp(op, "ne") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (left == right) {
frame->slots[dest] = JS_FALSE;
} else if (JS_VALUE_IS_BOTH_INT(left, right)) {
frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) != JS_VALUE_GET_INT(right));
} else if (JS_IsNumber(left) && JS_IsNumber(right)) {
double a, b;
JS_ToFloat64(ctx, &a, left);
JS_ToFloat64(ctx, &b, right);
frame->slots[dest] = JS_NewBool(ctx, a != b);
} else if (JS_IsText(left) && JS_IsText(right)) {
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, left, right, TRUE) != 0);
} else if (JS_IsNull(left) && JS_IsNull(right)) {
frame->slots[dest] = JS_FALSE;
} else if (JS_VALUE_GET_TAG(left) == JS_TAG_BOOL && JS_VALUE_GET_TAG(right) == JS_TAG_BOOL) {
frame->slots[dest] = JS_NewBool(ctx, left != right);
} else {
frame->slots[dest] = JS_TRUE;
}
}
else if (strcmp(op, "eq_tol") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
cJSON *a4 = cJSON_GetArrayItem(instr, 4);
JSValue tol = frame->slots[(int)a4->valuedouble];
if (JS_IsNumber(left) && JS_IsNumber(right) && JS_IsNumber(tol)) {
double a, b, t;
JS_ToFloat64(ctx, &a, left);
JS_ToFloat64(ctx, &b, right);
JS_ToFloat64(ctx, &t, tol);
frame->slots[dest] = JS_NewBool(ctx, fabs(a - b) <= t);
} else if (JS_IsText(left) && JS_IsText(right) && JS_VALUE_GET_TAG(tol) == JS_TAG_BOOL && JS_VALUE_GET_BOOL(tol)) {
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value_nocase(ctx, left, right) == 0);
} else {
/* Fall through to standard eq */
if (left == right) frame->slots[dest] = JS_TRUE;
else if (JS_IsText(left) && JS_IsText(right))
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, left, right, TRUE) == 0);
else frame->slots[dest] = JS_FALSE;
}
}
else if (strcmp(op, "ne_tol") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
cJSON *a4 = cJSON_GetArrayItem(instr, 4);
JSValue tol = frame->slots[(int)a4->valuedouble];
if (JS_IsNumber(left) && JS_IsNumber(right) && JS_IsNumber(tol)) {
double a, b, t;
JS_ToFloat64(ctx, &a, left);
JS_ToFloat64(ctx, &b, right);
JS_ToFloat64(ctx, &t, tol);
frame->slots[dest] = JS_NewBool(ctx, fabs(a - b) > t);
} else if (JS_IsText(left) && JS_IsText(right) && JS_VALUE_GET_TAG(tol) == JS_TAG_BOOL && JS_VALUE_GET_BOOL(tol)) {
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value_nocase(ctx, left, right) != 0);
} else {
if (left == right) frame->slots[dest] = JS_FALSE;
else if (JS_IsText(left) && JS_IsText(right))
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, left, right, TRUE) != 0);
else frame->slots[dest] = JS_TRUE;
}
}
else if (strcmp(op, "and") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
frame->slots[dest] = JS_ToBool(ctx, left) ? right : left;
}
else if (strcmp(op, "or") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
frame->slots[dest] = JS_ToBool(ctx, left) ? left : right;
}
else if (strcmp(op, "lt") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) < JS_VALUE_GET_INT(right));
} else if (JS_IsNumber(left) && JS_IsNumber(right)) {
double a, b;
JS_ToFloat64(ctx, &a, left);
JS_ToFloat64(ctx, &b, right);
frame->slots[dest] = JS_NewBool(ctx, a < b);
} else if (JS_IsText(left) && JS_IsText(right)) {
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, left, right, FALSE) < 0);
} else { goto disrupt; }
}
else if (strcmp(op, "le") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) <= JS_VALUE_GET_INT(right));
} else if (JS_IsNumber(left) && JS_IsNumber(right)) {
double a, b;
JS_ToFloat64(ctx, &a, left);
JS_ToFloat64(ctx, &b, right);
frame->slots[dest] = JS_NewBool(ctx, a <= b);
} else if (JS_IsText(left) && JS_IsText(right)) {
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, left, right, FALSE) <= 0);
} else { goto disrupt; }
}
else if (strcmp(op, "gt") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) > JS_VALUE_GET_INT(right));
} else if (JS_IsNumber(left) && JS_IsNumber(right)) {
double a, b;
JS_ToFloat64(ctx, &a, left);
JS_ToFloat64(ctx, &b, right);
frame->slots[dest] = JS_NewBool(ctx, a > b);
} else if (JS_IsText(left) && JS_IsText(right)) {
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, left, right, FALSE) > 0);
} else { goto disrupt; }
}
else if (strcmp(op, "ge") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (JS_VALUE_IS_BOTH_INT(left, right)) {
frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(left) >= JS_VALUE_GET_INT(right));
} else if (JS_IsNumber(left) && JS_IsNumber(right)) {
double a, b;
JS_ToFloat64(ctx, &a, left);
JS_ToFloat64(ctx, &b, right);
frame->slots[dest] = JS_NewBool(ctx, a >= b);
} else if (JS_IsText(left) && JS_IsText(right)) {
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, left, right, FALSE) >= 0);
} else { goto disrupt; }
}
/* ---- in operator ---- */
else if (strcmp(op, "in") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
int ret = JS_HasPropertyKey(ctx, right, left);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) { goto disrupt; }
frame->slots[dest] = JS_NewBool(ctx, ret);
}
/* ---- Sensory (type checks) ---- */
else if (strcmp(op, "text?") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_VALUE_IS_TEXT(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
}
else if (strcmp(op, "function?") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_IsFunction(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
}
else if (strcmp(op, "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, "integer?") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_IsInt(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
}
else if (strcmp(op, "array?") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_IsArray(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
}
else if (strcmp(op, "record?") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_IsRecord(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
}
else if (strcmp(op, "logical?") == 0) {
int dest = (int)a1->valuedouble;
int tag = JS_VALUE_GET_TAG(frame->slots[(int)a2->valuedouble]);
frame->slots[dest] = (tag == JS_TAG_BOOL) ? JS_TRUE : JS_FALSE;
}
else if (strcmp(op, "true?") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = (frame->slots[(int)a2->valuedouble] == JS_TRUE) ? JS_TRUE : JS_FALSE;
}
else if (strcmp(op, "false?") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = (frame->slots[(int)a2->valuedouble] == JS_FALSE) ? JS_TRUE : JS_FALSE;
}
else if (strcmp(op, "blob?") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_IsBlob(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
}
else if (strcmp(op, "character?") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
frame->slots[dest] = (JS_IsText(v) && js_string_value_len(v) == 1) ? JS_TRUE : JS_FALSE;
}
else if (strcmp(op, "data?") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
frame->slots[dest] = (JS_IsRecord(v) || JS_IsArray(v)) ? JS_TRUE : JS_FALSE;
}
else if (strcmp(op, "digit?") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
if (JS_IsText(v) && js_string_value_len(v) == 1) {
uint32_t c = js_string_value_get(v, 0);
frame->slots[dest] = (c >= '0' && c <= '9') ? JS_TRUE : JS_FALSE;
} else {
frame->slots[dest] = JS_FALSE;
}
}
else if (strcmp(op, "fit?") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
if (JS_IsInt(v)) {
frame->slots[dest] = JS_TRUE;
} else if (JS_IsNumber(v)) {
double d;
JS_ToFloat64(ctx, &d, v);
frame->slots[dest] = (d == (double)(int32_t)d) ? JS_TRUE : JS_FALSE;
} else {
frame->slots[dest] = JS_FALSE;
}
}
else if (strcmp(op, "letter?") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
if (JS_IsText(v) && js_string_value_len(v) == 1) {
uint32_t c = js_string_value_get(v, 0);
frame->slots[dest] = ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) ? JS_TRUE : JS_FALSE;
} else {
frame->slots[dest] = JS_FALSE;
}
}
else if (strcmp(op, "pattern?") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_FALSE; /* TODO: pattern type check */
}
else if (strcmp(op, "stone?") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
if (JS_IsPtr(v)) {
objhdr_t hdr = *(objhdr_t *)JS_VALUE_GET_PTR(v);
frame->slots[dest] = objhdr_s(hdr) ? JS_TRUE : JS_FALSE;
} else {
/* Primitives are immutable */
frame->slots[dest] = JS_TRUE;
}
}
else if (strcmp(op, "upper?") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
if (JS_IsText(v) && js_string_value_len(v) == 1) {
uint32_t c = js_string_value_get(v, 0);
frame->slots[dest] = (c >= 'A' && c <= 'Z') ? JS_TRUE : JS_FALSE;
} else {
frame->slots[dest] = JS_FALSE;
}
}
else if (strcmp(op, "whitespace?") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
if (JS_IsText(v) && js_string_value_len(v) == 1) {
uint32_t c = js_string_value_get(v, 0);
frame->slots[dest] = (c == ' ' || c == '\t' || c == '\n' || c == '\r') ? JS_TRUE : JS_FALSE;
} else {
frame->slots[dest] = JS_FALSE;
}
}
/* ---- Logical / Bitwise ---- */
else if (strcmp(op, "not") == 0) {
int dest = (int)a1->valuedouble;
int b = JS_ToBool(ctx, frame->slots[(int)a2->valuedouble]);
frame->slots[dest] = JS_NewBool(ctx, !b);
}
else if (strcmp(op, "bitnot") == 0) {
int dest = (int)a1->valuedouble;
int32_t i; JS_ToInt32(ctx, &i, frame->slots[(int)a2->valuedouble]);
frame->slots[dest] = JS_NewInt32(ctx, ~i);
}
else if (strcmp(op, "bitand") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (!JS_IsNumber(left) || !JS_IsNumber(right)) { goto disrupt; }
int32_t ia, ib;
JS_ToInt32(ctx, &ia, left);
JS_ToInt32(ctx, &ib, right);
frame->slots[dest] = JS_NewInt32(ctx, ia & ib);
}
else if (strcmp(op, "bitor") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (!JS_IsNumber(left) || !JS_IsNumber(right)) { goto disrupt; }
int32_t ia, ib;
JS_ToInt32(ctx, &ia, left);
JS_ToInt32(ctx, &ib, right);
frame->slots[dest] = JS_NewInt32(ctx, ia | ib);
}
else if (strcmp(op, "bitxor") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (!JS_IsNumber(left) || !JS_IsNumber(right)) { goto disrupt; }
int32_t ia, ib;
JS_ToInt32(ctx, &ia, left);
JS_ToInt32(ctx, &ib, right);
frame->slots[dest] = JS_NewInt32(ctx, ia ^ ib);
}
else if (strcmp(op, "shl") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (!JS_IsNumber(left) || !JS_IsNumber(right)) { goto disrupt; }
int32_t ia, ib;
JS_ToInt32(ctx, &ia, left);
JS_ToInt32(ctx, &ib, right);
frame->slots[dest] = JS_NewInt32(ctx, ia << (ib & 31));
}
else if (strcmp(op, "shr") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (!JS_IsNumber(left) || !JS_IsNumber(right)) { goto disrupt; }
int32_t ia, ib;
JS_ToInt32(ctx, &ia, left);
JS_ToInt32(ctx, &ib, right);
frame->slots[dest] = JS_NewInt32(ctx, ia >> (ib & 31));
}
else if (strcmp(op, "ushr") == 0) {
int dest = (int)a1->valuedouble;
JSValue left = frame->slots[(int)a2->valuedouble];
JSValue right = frame->slots[(int)a3->valuedouble];
if (!JS_IsNumber(left) || !JS_IsNumber(right)) { goto disrupt; }
int32_t ia, ib;
JS_ToInt32(ctx, &ia, left);
JS_ToInt32(ctx, &ib, right);
frame->slots[dest] = JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31));
}
/* ---- Control flow ---- */
else if (strcmp(op, "jump") == 0) {
const char *label = cJSON_IsString(a1) ? a1->valuestring : NULL;
if (label) pc = mcode_resolve_label(code, label);
}
else if (strcmp(op, "jump_true") == 0) {
int slot = (int)a1->valuedouble;
const char *label = cJSON_IsString(a2) ? a2->valuestring : NULL;
if (label && JS_ToBool(ctx, frame->slots[slot]))
pc = mcode_resolve_label(code, label);
}
else if (strcmp(op, "jump_false") == 0) {
int slot = (int)a1->valuedouble;
const char *label = cJSON_IsString(a2) ? a2->valuestring : NULL;
if (label && !JS_ToBool(ctx, frame->slots[slot]))
pc = mcode_resolve_label(code, label);
}
else if (strcmp(op, "jump_null") == 0) {
int slot = (int)a1->valuedouble;
const char *label = cJSON_IsString(a2) ? a2->valuestring : NULL;
if (label && JS_IsNull(frame->slots[slot]))
pc = mcode_resolve_label(code, label);
}
else if (strcmp(op, "jump_not_null") == 0) {
int slot = (int)a1->valuedouble;
const char *label = cJSON_IsString(a2) ? a2->valuestring : NULL;
if (label && !JS_IsNull(frame->slots[slot]))
pc = mcode_resolve_label(code, label);
}
else if (strcmp(op, "jump_empty") == 0) {
int slot = (int)a1->valuedouble;
const char *label = cJSON_IsString(a2) ? a2->valuestring : NULL;
if (label && JS_IsNull(frame->slots[slot]))
pc = mcode_resolve_label(code, label);
}
else if (strcmp(op, "wary_true") == 0) {
int slot = (int)a1->valuedouble;
const char *label = cJSON_IsString(a2) ? a2->valuestring : NULL;
JSValue v = frame->slots[slot];
if (v == JS_TRUE) {
if (label) pc = mcode_resolve_label(code, label);
} else if (v != JS_FALSE) {
goto disrupt;
}
}
else if (strcmp(op, "wary_false") == 0) {
int slot = (int)a1->valuedouble;
const char *label = cJSON_IsString(a2) ? a2->valuestring : NULL;
JSValue v = frame->slots[slot];
if (v == JS_FALSE) {
if (label) pc = mcode_resolve_label(code, label);
} else if (v != JS_TRUE) {
goto disrupt;
}
}
/* ---- Property/element access (unified) ---- */
else if (strcmp(op, "load") == 0) {
int dest = (int)a1->valuedouble;
int obj_reg = (int)a2->valuedouble;
JSValue obj = frame->slots[obj_reg];
if (JS_IsFunction(obj)) {
JSFunction *fn_chk = JS_VALUE_GET_FUNCTION(obj);
if (fn_chk->length != 2) {
JS_ThrowTypeError(ctx, "cannot read property of non-proxy function");
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
}
}
JSValue val;
if (cJSON_IsString(a3)) {
JSValue key = JS_NewString(ctx, a3->valuestring);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
obj = frame->slots[obj_reg];
val = JS_GetProperty(ctx, obj, key);
} else {
JSValue idx = frame->slots[(int)a3->valuedouble];
if (JS_IsInt(idx))
val = JS_GetPropertyUint32(ctx, obj, JS_VALUE_GET_INT(idx));
else
val = JS_GetProperty(ctx, obj, idx);
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(val)) goto disrupt;
frame->slots[dest] = val;
}
else if (strcmp(op, "store") == 0) {
int obj_reg = (int)a1->valuedouble;
int val_reg = (int)a2->valuedouble;
JSValue obj = frame->slots[obj_reg];
JSValue val = frame->slots[val_reg];
if (JS_IsFunction(obj)) {
JS_ThrowTypeError(ctx, "cannot set property of function");
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
}
if (cJSON_IsString(a3)) {
JSValue key = JS_NewString(ctx, a3->valuestring);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
obj = frame->slots[obj_reg];
val = frame->slots[val_reg];
int ret = JS_SetProperty(ctx, obj, key, val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
} else {
JSValue idx = frame->slots[(int)a3->valuedouble];
int ret;
if (JS_IsInt(idx)) {
ret = JS_SetPropertyUint32(ctx, obj, JS_VALUE_GET_INT(idx), val);
} else if (JS_IsArray(obj)) {
JS_ThrowTypeError(ctx, "array index must be a number");
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
} else if (JS_IsRecord(obj) && !JS_IsText(idx) && !JS_IsRecord(idx)) {
JS_ThrowTypeError(ctx, "object key must be a string or object");
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
} else {
ret = JS_SetProperty(ctx, obj, idx, val);
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
}
}
else if (strcmp(op, "delete") == 0) {
int dest = (int)a1->valuedouble;
int obj_reg = (int)a2->valuedouble;
JSValue obj = frame->slots[obj_reg];
JSValue key;
if (cJSON_IsString(a3)) {
key = JS_NewString(ctx, a3->valuestring);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
obj = frame->slots[obj_reg];
} else {
key = frame->slots[(int)a3->valuedouble];
}
int ret = JS_DeleteProperty(ctx, obj, key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (ret < 0) goto disrupt;
frame->slots[dest] = JS_NewBool(ctx, ret >= 0);
}
/* ---- Closure access ---- */
else if (strcmp(op, "get") == 0) {
int dest = (int)a1->valuedouble;
int slot = (int)a2->valuedouble;
int depth = (int)a3->valuedouble;
/* Walk outer_frame chain from the current function's outer_frame */
JSFunction *cur_fn = JS_VALUE_GET_FUNCTION(frame->function);
JSValue of = (cur_fn && cur_fn->kind == JS_FUNC_KIND_MCODE) ? cur_fn->u.mcode.outer_frame : JS_NULL;
for (int d = 1; d < depth && !JS_IsNull(of); d++) {
JSFrameRegister *outer = (JSFrameRegister *)JS_VALUE_GET_PTR(of);
JSFunction *outer_fn = JS_VALUE_GET_FUNCTION(outer->function);
of = (outer_fn && outer_fn->kind == JS_FUNC_KIND_MCODE) ? outer_fn->u.mcode.outer_frame : JS_NULL;
}
if (!JS_IsNull(of)) {
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(of);
frame->slots[dest] = target->slots[slot];
} else {
frame->slots[dest] = JS_NULL;
}
}
else if (strcmp(op, "put") == 0) {
int src = (int)a1->valuedouble;
int slot = (int)a2->valuedouble;
int depth = (int)a3->valuedouble;
JSFunction *cur_fn = JS_VALUE_GET_FUNCTION(frame->function);
JSValue of = (cur_fn && cur_fn->kind == JS_FUNC_KIND_MCODE) ? cur_fn->u.mcode.outer_frame : JS_NULL;
for (int d = 1; d < depth && !JS_IsNull(of); d++) {
JSFrameRegister *outer = (JSFrameRegister *)JS_VALUE_GET_PTR(of);
JSFunction *outer_fn = JS_VALUE_GET_FUNCTION(outer->function);
of = (outer_fn && outer_fn->kind == JS_FUNC_KIND_MCODE) ? outer_fn->u.mcode.outer_frame : JS_NULL;
}
if (!JS_IsNull(of)) {
JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(of);
target->slots[slot] = frame->slots[src];
}
}
/* ---- Function calls ---- */
else if (strcmp(op, "frame") == 0) {
int frame_reg = (int)a1->valuedouble;
JSValue func_val = frame->slots[(int)a2->valuedouble];
int call_argc = a3 ? (int)a3->valuedouble : 0;
if (!JS_IsFunction(func_val)) {
goto disrupt;
}
JSFunction *fn = JS_VALUE_GET_FUNCTION(func_val);
int nr_slots;
if (fn->kind == JS_FUNC_KIND_MCODE) {
nr_slots = fn->u.mcode.code->nr_slots;
} else {
nr_slots = call_argc + 2;
}
JSFrameRegister *new_frame = alloc_frame_register(ctx, nr_slots);
if (!new_frame) { goto disrupt; }
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
func_val = frame->slots[(int)a2->valuedouble];
new_frame->function = func_val;
frame->slots[frame_reg] = JS_MKPTR(new_frame);
}
else if (strcmp(op, "setarg") == 0) {
int frame_reg = (int)a1->valuedouble;
int arg_idx = (int)a2->valuedouble;
int val_reg = (int)a3->valuedouble;
JSValue target = frame->slots[frame_reg];
if (!JS_IsFunction(target)) {
JSFrameRegister *call_frame = (JSFrameRegister *)JS_VALUE_GET_PTR(target);
call_frame->slots[arg_idx] = frame->slots[val_reg];
}
}
else if (strcmp(op, "invoke") == 0) {
int frame_reg = (int)a1->valuedouble;
int ret_reg = (int)a2->valuedouble;
JSValue target = frame->slots[frame_reg];
JSFrameRegister *new_frame = (JSFrameRegister *)JS_VALUE_GET_PTR(target);
JSFunction *fn = JS_VALUE_GET_FUNCTION(new_frame->function);
if (fn->kind == JS_FUNC_KIND_MCODE) {
/* Store return address: pc << 16 | ret_slot */
frame->address = JS_NewInt32(ctx, (pc << 16) | ret_reg);
new_frame->caller = JS_MKPTR(frame);
/* Switch to new frame */
frame = new_frame;
frame_ref.val = JS_MKPTR(frame);
code = fn->u.mcode.code;
pc = 0;
} else {
/* C or bytecode function — collect args on value stack (GC-safe) */
int nr_slots = (int)objhdr_cap56(new_frame->hdr);
int c_argc = (nr_slots >= 2) ? nr_slots - 2 : 0;
int vs_base = ctx->value_stack_top;
for (int i = 0; i < c_argc; i++) {
ctx->value_stack[vs_base + i] = new_frame->slots[i + 1];
}
ctx->value_stack_top = vs_base + c_argc;
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue c_result = JS_Call(ctx, new_frame->function, new_frame->slots[0], c_argc, &ctx->value_stack[vs_base]);
ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->reg_current_frame = JS_NULL;
if (JS_IsException(c_result)) { goto disrupt; }
frame->slots[ret_reg] = c_result;
}
}
/* ---- Method call (handles function proxies) ---- */
else if (strcmp(op, "callmethod") == 0) {
/* ["callmethod", dest, obj_reg, "method_name", arg0_reg, arg1_reg, ...] */
int dest = (int)a1->valuedouble;
int obj_reg = (int)a2->valuedouble;
JSValue obj = frame->slots[obj_reg];
const char *method_name = a3->valuestring;
/* Count arg registers (items after a3, minus trailing line/col) */
int nargs = 0;
for (cJSON *p = a3->next; p; p = p->next)
nargs++;
nargs -= 2; /* subtract line and col metadata */
if (nargs < 0) nargs = 0;
if (JS_IsFunction(obj)) {
/* Proxy call: obj(name, [args...]) */
/* Store key on value stack immediately to protect from GC */
int vs_base = ctx->value_stack_top;
ctx->value_stack[vs_base] = JS_NewString(ctx, method_name);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->value_stack_top = vs_base + 1; /* protect key from GC */
JSValue arr = JS_NewArray(ctx);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(arr)) goto disrupt;
frame->slots[dest] = arr; /* protect from GC */
cJSON *p = a3->next;
for (int i = 0; i < nargs; i++, p = p->next) {
if (cJSON_IsString(p)) break; /* hit line/col */
int areg = (int)p->valuedouble;
JS_SetPropertyUint32(ctx, frame->slots[dest], i, frame->slots[areg]);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
}
ctx->value_stack[vs_base + 1] = frame->slots[dest];
ctx->value_stack_top = vs_base + 2;
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue ret = JS_CallInternal(ctx, frame->slots[obj_reg], JS_NULL, 2, &ctx->value_stack[vs_base], 0);
ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->reg_current_frame = JS_NULL;
if (JS_IsException(ret)) goto disrupt;
frame->slots[dest] = ret;
} else {
/* Record method call: get property, call with this=obj */
JSValue key = JS_NewString(ctx, method_name);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
JSValue method = JS_GetProperty(ctx, frame->slots[obj_reg], key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(method)) goto disrupt;
if (!JS_IsFunction(method)) {
frame->slots[dest] = JS_NULL;
} else {
JSFunction *fn = JS_VALUE_GET_FUNCTION(method);
if (fn->kind == JS_FUNC_KIND_MCODE) {
/* mcode function — set up frame and jump */
frame->slots[dest] = method; /* protect from GC */
JSFrameRegister *new_frame = alloc_frame_register(ctx, fn->u.mcode.code->nr_slots);
if (!new_frame) {
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
method = frame->slots[dest]; /* re-read after GC */
fn = JS_VALUE_GET_FUNCTION(method);
new_frame->function = method;
new_frame->slots[0] = frame->slots[obj_reg]; /* this */
cJSON *p = a3->next;
for (int i = 0; i < nargs && i < fn->u.mcode.code->nr_slots - 1; i++, p = p->next) {
if (cJSON_IsString(p)) break;
new_frame->slots[1 + i] = frame->slots[(int)p->valuedouble];
}
frame->address = JS_NewInt32(ctx, (pc << 16) | dest);
new_frame->caller = JS_MKPTR(frame);
frame = new_frame;
frame_ref.val = JS_MKPTR(frame);
code = fn->u.mcode.code;
pc = 0;
} else {
/* C or bytecode function */
int vs_base = ctx->value_stack_top;
cJSON *p = a3->next;
for (int i = 0; i < nargs; i++, p = p->next) {
if (cJSON_IsString(p)) break;
ctx->value_stack[vs_base + i] = frame->slots[(int)p->valuedouble];
}
ctx->value_stack_top = vs_base + nargs;
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue ret = JS_Call(ctx, method, frame->slots[obj_reg], nargs, &ctx->value_stack[vs_base]);
ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->reg_current_frame = JS_NULL;
if (JS_IsException(ret)) goto disrupt;
frame->slots[dest] = ret;
}
}
}
}
else if (strcmp(op, "callmethod_dyn") == 0) {
/* ["callmethod_dyn", dest, obj_reg, key_reg, arg0_reg, ...] */
int dest = (int)a1->valuedouble;
int obj_reg = (int)a2->valuedouble;
int key_reg = (int)a3->valuedouble;
JSValue obj = frame->slots[obj_reg];
JSValue key = frame->slots[key_reg];
/* Count arg registers (items after a3, minus trailing line/col) */
int nargs = 0;
for (cJSON *p = a3->next; p; p = p->next)
nargs++;
nargs -= 2;
if (nargs < 0) nargs = 0;
if (JS_IsFunction(obj) && JS_VALUE_IS_TEXT(key)) {
/* Proxy call: obj(key, [args...]) */
int vs_base = ctx->value_stack_top;
ctx->value_stack[vs_base] = key; /* protect key on value stack */
ctx->value_stack_top = vs_base + 1; /* protect key from GC */
JSValue arr = JS_NewArray(ctx);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(arr)) goto disrupt;
frame->slots[dest] = arr; /* protect from GC */
cJSON *p = a3->next;
for (int i = 0; i < nargs; i++, p = p->next) {
int areg = (int)p->valuedouble;
JS_SetPropertyUint32(ctx, frame->slots[dest], i, frame->slots[areg]);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
}
ctx->value_stack[vs_base + 1] = frame->slots[dest];
ctx->value_stack_top = vs_base + 2;
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue ret = JS_CallInternal(ctx, frame->slots[obj_reg], JS_NULL, 2, &ctx->value_stack[vs_base], 0);
ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->reg_current_frame = JS_NULL;
if (JS_IsException(ret)) goto disrupt;
frame->slots[dest] = ret;
} else if (JS_IsFunction(obj)) {
JS_ThrowTypeError(ctx, "cannot use non-text bracket notation on function");
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
} else {
/* Record method call: get property, call with this=obj */
JSValue method = JS_GetProperty(ctx, frame->slots[obj_reg], key);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(method)) goto disrupt;
if (!JS_IsFunction(method)) {
frame->slots[dest] = JS_NULL;
} else {
JSFunction *fn = JS_VALUE_GET_FUNCTION(method);
if (fn->kind == JS_FUNC_KIND_MCODE) {
frame->slots[dest] = method; /* protect method from GC */
JSFrameRegister *new_frame = alloc_frame_register(ctx, fn->u.mcode.code->nr_slots);
if (!new_frame) {
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto disrupt;
}
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
method = frame->slots[dest]; /* re-read after GC */
fn = JS_VALUE_GET_FUNCTION(method);
new_frame->function = method;
new_frame->slots[0] = frame->slots[obj_reg]; /* this */
cJSON *p = a3->next;
for (int i = 0; i < nargs && i < fn->u.mcode.code->nr_slots - 1; i++, p = p->next) {
new_frame->slots[1 + i] = frame->slots[(int)p->valuedouble];
}
frame->address = JS_NewInt32(ctx, (pc << 16) | dest);
new_frame->caller = JS_MKPTR(frame);
frame = new_frame;
frame_ref.val = JS_MKPTR(frame);
code = fn->u.mcode.code;
pc = 0;
} else {
int vs_base = ctx->value_stack_top;
cJSON *p = a3->next;
for (int i = 0; i < nargs; i++, p = p->next) {
ctx->value_stack[vs_base + i] = frame->slots[(int)p->valuedouble];
}
ctx->value_stack_top = vs_base + nargs;
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
JSValue ret = JS_Call(ctx, method, frame->slots[obj_reg], nargs, &ctx->value_stack[vs_base]);
ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->reg_current_frame = JS_NULL;
if (JS_IsException(ret)) goto disrupt;
frame->slots[dest] = ret;
}
}
}
}
/* ---- Tail calls ---- */
else if (strcmp(op, "goframe") == 0) {
int frame_reg = (int)a1->valuedouble;
int func_reg = (int)a2->valuedouble;
int call_argc = a3 ? (int)a3->valuedouble : 0;
JSValue func_val = frame->slots[func_reg];
if (!JS_IsFunction(func_val)) {
goto disrupt;
}
JSFunction *fn = JS_VALUE_GET_FUNCTION(func_val);
int nr_slots;
if (fn->kind == JS_FUNC_KIND_MCODE) {
nr_slots = fn->u.mcode.code->nr_slots;
} else {
nr_slots = call_argc + 2;
}
JSFrameRegister *new_frame = alloc_frame_register(ctx, nr_slots);
if (!new_frame) { goto disrupt; }
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
func_val = frame->slots[func_reg];
new_frame->function = func_val;
frame->slots[frame_reg] = JS_MKPTR(new_frame);
}
else if (strcmp(op, "goinvoke") == 0) {
int frame_reg = (int)a1->valuedouble;
JSValue target = frame->slots[frame_reg];
if (JS_IsFunction(target)) {
result = JS_ThrowInternalError(ctx, "C function tail call not supported in MCODE");
goto disrupt;
}
JSFrameRegister *new_frame = (JSFrameRegister *)JS_VALUE_GET_PTR(target);
JSFunction *fn = JS_VALUE_GET_FUNCTION(new_frame->function);
if (fn->kind != JS_FUNC_KIND_MCODE) {
goto disrupt;
}
/* Tail call — bypass current frame */
new_frame->caller = frame->caller;
new_frame->address = frame->address;
frame = new_frame;
frame_ref.val = JS_MKPTR(frame);
code = fn->u.mcode.code;
pc = 0;
}
/* ---- Return ---- */
else if (strcmp(op, "return") == 0) {
result = frame->slots[(int)a1->valuedouble];
if (JS_IsNull(frame->caller)) goto done;
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
int ret_info = JS_VALUE_GET_INT(caller->address);
frame->caller = JS_NULL;
frame = caller;
frame_ref.val = JS_MKPTR(frame);
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
code = fn->u.mcode.code;
pc = ret_info >> 16;
frame->slots[ret_info & 0xFFFF] = result;
}
else if (strcmp(op, "return_value") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = result;
}
/* ---- Apply ---- */
else if (strcmp(op, "apply") == 0) {
int func_slot = (int)a1->valuedouble;
int arr_slot = (int)a2->valuedouble;
if (!JS_IsFunction(frame->slots[func_slot]) || !JS_IsArray(frame->slots[arr_slot])) { goto disrupt; }
JSValue len_val = JS_GetProperty(ctx, frame->slots[arr_slot], JS_KEY_length);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
int len = JS_IsNumber(len_val) ? (int)JS_VALUE_GET_INT(len_val) : 0;
if (len > 256) len = 256;
int vs_base = ctx->value_stack_top;
for (int i = 0; i < len; i++) {
ctx->value_stack[vs_base + i] = JS_GetPropertyUint32(ctx, frame->slots[arr_slot], i);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
}
ctx->value_stack_top = vs_base + len;
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
result = JS_Call(ctx, frame->slots[func_slot], JS_NULL, len, &ctx->value_stack[vs_base]);
ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(result)) { goto disrupt; }
}
/* ---- Object/Array creation ---- */
else if (strcmp(op, "record") == 0) {
int dest = (int)a1->valuedouble;
JSValue rec = JS_NewObject(ctx);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(rec)) { goto disrupt; }
frame->slots[dest] = rec;
}
else if (strcmp(op, "array") == 0) {
int dest = (int)a1->valuedouble;
int nr_elems = a2 ? (int)a2->valuedouble : 0;
JSValue arr = JS_NewArray(ctx);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(arr)) { goto disrupt; }
frame->slots[dest] = arr;
for (int i = 0; i < nr_elems; i++) {
cJSON *elem = cJSON_GetArrayItem(instr, 3 + i);
if (elem) {
int elem_slot = (int)elem->valuedouble;
JS_SetPropertyUint32(ctx, frame->slots[dest], i, frame->slots[elem_slot]);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
}
}
}
else if (strcmp(op, "function") == 0) {
int dest = (int)a1->valuedouble;
int func_id = (int)a2->valuedouble;
if ((uint32_t)func_id < code->func_count && code->functions[func_id]) {
JSValue fn_val = js_new_mcode_function(ctx, code->functions[func_id]);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
JSFunction *fn = JS_VALUE_GET_FUNCTION(fn_val);
fn->u.mcode.outer_frame = frame_ref.val;
frame->slots[dest] = fn_val;
} else {
frame->slots[dest] = JS_NULL;
}
}
/* ---- Blob ---- */
else if (strcmp(op, "blob") == 0) {
int dest = (int)a1->valuedouble;
int nr_bits = a2 ? (int)a2->valuedouble : 0;
blob *bd = blob_new((size_t)(nr_bits < 0 ? 0 : nr_bits));
if (!bd) { goto disrupt; }
JSValue bv = js_new_blob(ctx, bd);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(bv)) { goto disrupt; }
frame->slots[dest] = bv;
}
/* ---- Pretext ---- */
else if (strcmp(op, "pretext") == 0) {
int dest = (int)a1->valuedouble;
int nr_chars = a2 ? (int)a2->valuedouble : 16;
JSText *s = pretext_init(ctx, nr_chars);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (!s) { goto disrupt; }
frame->slots[dest] = JS_MKPTR(s);
}
/* ---- Append (to pretext) ---- */
else if (strcmp(op, "append") == 0) {
int pt_slot = (int)a1->valuedouble;
int right_slot = (int)a2->valuedouble;
if (!JS_IsText(frame->slots[pt_slot]) || !JS_IsText(frame->slots[right_slot])) { goto disrupt; }
int vs_base = ctx->value_stack_top;
ctx->value_stack[vs_base] = frame->slots[pt_slot];
ctx->value_stack[vs_base + 1] = frame->slots[right_slot];
ctx->value_stack_top = vs_base + 2;
JSText *s = JS_VALUE_GET_PTR(ctx->value_stack[vs_base]);
s = pretext_concat_value(ctx, s, ctx->value_stack[vs_base + 1]);
ctx->value_stack_top = vs_base;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (!s) { goto disrupt; }
frame->slots[pt_slot] = JS_MKPTR(s);
}
/* ---- Stone ---- */
else if (strcmp(op, "stone") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
JSValue stoned = JS_Stone(ctx, v);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[dest] = stoned;
}
/* ---- Regexp literal ---- */
else if (strcmp(op, "regexp") == 0) {
int dest = (int)a1->valuedouble;
const char *pattern = a2 ? a2->valuestring : "";
cJSON *a3 = cJSON_GetArrayItem(instr, 3);
const char *flags_str = a3 ? a3->valuestring : "";
if (!pattern) pattern = "";
if (!flags_str) flags_str = "";
int vs_base = ctx->value_stack_top;
ctx->value_stack[vs_base] = JS_NewString(ctx, pattern); /* pat_val */
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->value_stack_top = vs_base + 1; /* protect pattern from GC */
ctx->value_stack[vs_base + 1] = *flags_str ? JS_NewString(ctx, flags_str) : JS_NULL; /* flags_val */
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->value_stack_top = vs_base + 2;
JSValue bc = js_compile_regexp(ctx, ctx->value_stack[vs_base], ctx->value_stack[vs_base + 1]);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(bc)) { ctx->value_stack_top = vs_base; goto disrupt; }
JSValue re_obj = js_regexp_constructor_internal(ctx, ctx->value_stack[vs_base], bc);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->value_stack_top = vs_base;
if (JS_IsException(re_obj)) { goto disrupt; }
frame->slots[dest] = re_obj;
}
/* ---- Push (append to array) ---- */
else if (strcmp(op, "push") == 0) {
int arr_slot = (int)a1->valuedouble;
int val_slot = (int)a2->valuedouble;
if (!JS_IsArray(frame->slots[arr_slot])) { goto disrupt; }
JSGCRef arr_gc;
JS_PushGCRef(ctx, &arr_gc);
arr_gc.val = frame->slots[arr_slot];
JSGCRef val_gc;
JS_PushGCRef(ctx, &val_gc);
val_gc.val = frame->slots[val_slot];
int rc = JS_ArrayPush(ctx, &arr_gc.val, val_gc.val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
JS_PopGCRef(ctx, &val_gc);
JS_PopGCRef(ctx, &arr_gc);
if (rc < 0) goto disrupt;
frame->slots[arr_slot] = arr_gc.val;
}
/* ---- Pop (remove last from array) ---- */
else if (strcmp(op, "pop") == 0) {
int dest = (int)a1->valuedouble;
JSValue arr = frame->slots[(int)a2->valuedouble];
if (!JS_IsArray(arr)) { goto disrupt; }
JSValue popped = JS_ArrayPop(ctx, arr);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
frame->slots[dest] = popped;
}
/* ---- Disruption ---- */
else if (strcmp(op, "disrupt") == 0) {
goto disrupt;
}
/* ---- Unknown opcode ---- */
else {
result = JS_ThrowInternalError(ctx, "unknown MCODE opcode: %s", op);
goto done;
}
continue;
disrupt:
/* Search frame chain for a disruption handler.
Use frame_pc to track each frame's execution point:
- For the faulting frame, it's the current pc.
- For unwound caller frames, read from frame->address. */
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); /* re-chase after GC */
{
uint32_t frame_pc = pc;
for (;;) {
JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function);
JSMCode *fn_code = fn->u.mcode.code;
/* Only enter handler if we're not already inside it */
if (fn_code->disruption_pc > 0 && frame_pc < fn_code->disruption_pc) {
code = fn_code;
pc = fn_code->disruption_pc;
break;
}
if (JS_IsNull(frame->caller)) {
result = JS_Throw(ctx, JS_NewString(ctx, "unhandled disruption"));
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
goto done;
}
/* Unwind one frame — read caller's saved pc from its address field */
JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller);
frame->caller = JS_NULL;
frame = caller;
frame_ref.val = JS_MKPTR(frame);
frame_pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16);
}
}
}
done:
if (JS_IsException(result)) {
ctx->reg_current_frame = frame_ref.val;
ctx->current_register_pc = pc > 0 ? pc - 1 : 0;
}
JS_DeleteGCRef(ctx, &frame_ref);
return result;
}
JSValue JS_CallMcodeTree(JSContext *ctx, cJSON *root) {
if (!root) return JS_ThrowSyntaxError(ctx, "invalid MCODE tree");
cJSON *main_obj = cJSON_GetObjectItemCaseSensitive(root, "main");
if (!main_obj) {
cJSON_Delete(root);
return JS_ThrowSyntaxError(ctx, "MCODE tree missing 'main' section");
}
cJSON *functions = cJSON_GetObjectItemCaseSensitive(root, "functions");
/* Parse main code */
JSMCode *code = jsmcode_parse(main_obj, functions);
if (!code) {
cJSON_Delete(root);
return JS_ThrowInternalError(ctx, "failed to parse MCODE");
}
code->json_root = root; /* Keep tree alive — instrs point into it */
/* Execute with global_obj as this */
JSValue result = mcode_exec(ctx, code, ctx->global_obj, 0, NULL, JS_NULL);
/* Clear frame ref before freeing mcode — stack trace data is inside code */
ctx->reg_current_frame = JS_NULL;
jsmcode_free(code);
return result;
}
JSValue JS_CallMcode(JSContext *ctx, const char *mcode_json) {
cJSON *root = cJSON_Parse(mcode_json);
if (!root) return JS_ThrowSyntaxError(ctx, "invalid MCODE JSON");
return JS_CallMcodeTree(ctx, root);
}