/* * 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 "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 "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")); 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, name); 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, name); 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, name); 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, name); 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")); 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, name); 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, name); 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")); 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, 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, 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")); 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, name); 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, name); 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, name); 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, name); 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")); int local_slot = mach_gen_find_var (s, 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", ""); 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 : ""); 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); /* Try env (outer_frame) first, then global */ JSFunction *cur_fn = JS_VALUE_GET_FUNCTION(frame->function); JSValue env = (cur_fn && cur_fn->kind == JS_FUNC_KIND_MCODE) ? cur_fn->u.mcode.outer_frame : JS_NULL; JSValue val = JS_NULL; if (!JS_IsNull(env)) { val = JS_GetProperty(ctx, env, 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); 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_CallMcodeTreeEnv(JSContext *ctx, cJSON *root, JSValue env) { 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, passing env as outer_frame */ JSValue result = mcode_exec(ctx, code, ctx->global_obj, 0, NULL, env); /* 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); }