Files
cell/source/mcode.c
2026-02-11 13:15:04 -06:00

2030 lines
84 KiB
C

/*
* QuickJS Javascript Engine
*
* Copyright (c) 2017-2025 Fabrice Bellard
* Copyright (c) 2017-2025 Charlie Gordon
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "quickjs-internal.h"
/* ============================================================
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 using JSGCRef.
alloc_frame_register and js_new_mcode_function can trigger GC. */
int nargs_copy = (argc < code->nr_args) ? argc : code->nr_args;
JSGCRef this_gc, of_gc;
JS_PushGCRef(ctx, &this_gc);
this_gc.val = this_obj;
JS_PushGCRef(ctx, &of_gc);
of_gc.val = outer_frame;
JSGCRef arg_gcs[nargs_copy > 0 ? nargs_copy : 1];
for (int i = 0; i < nargs_copy; i++) {
JS_PushGCRef(ctx, &arg_gcs[i]);
arg_gcs[i].val = argv[i];
}
JSFrameRegister *frame = alloc_frame_register(ctx, code->nr_slots);
if (!frame) {
for (int i = nargs_copy - 1; i >= 0; i--) JS_PopGCRef(ctx, &arg_gcs[i]);
JS_PopGCRef(ctx, &of_gc);
JS_PopGCRef(ctx, &this_gc);
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)) {
JS_DeleteGCRef(ctx, &frame_ref);
for (int i = nargs_copy - 1; i >= 0; i--) JS_PopGCRef(ctx, &arg_gcs[i]);
JS_PopGCRef(ctx, &of_gc);
JS_PopGCRef(ctx, &this_gc);
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 = of_gc.val;
frame->function = main_func;
/* Setup initial frame from GC-safe refs */
frame->slots[0] = this_gc.val; /* slot 0 is this */
for (int i = 0; i < nargs_copy; i++) {
frame->slots[1 + i] = arg_gcs[i].val;
}
for (int i = nargs_copy - 1; i >= 0; i--) JS_PopGCRef(ctx, &arg_gcs[i]);
JS_PopGCRef(ctx, &of_gc);
JS_PopGCRef(ctx, &this_gc);
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);
}
}
/* ---- Compiler-internal type guards ---- */
else if (strcmp(op, "is_int") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_IsInt(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
}
else if (strcmp(op, "is_num") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_IsNumber(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
}
else if (strcmp(op, "is_text") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_IsText(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
}
else if (strcmp(op, "is_null") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_IsNull(frame->slots[(int)a2->valuedouble]) ? JS_TRUE : JS_FALSE;
}
else if (strcmp(op, "is_bool") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = (JS_VALUE_GET_TAG(frame->slots[(int)a2->valuedouble]) == JS_TAG_BOOL) ? JS_TRUE : JS_FALSE;
}
else if (strcmp(op, "is_identical") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = (frame->slots[(int)a2->valuedouble] == frame->slots[(int)a3->valuedouble]) ? JS_TRUE : JS_FALSE;
}
/* ---- Specialized arithmetic (int) ---- */
else if (strcmp(op, "add_int") == 0) {
int dest = (int)a1->valuedouble;
int64_t r = (int64_t)JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) + (int64_t)JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]);
frame->slots[dest] = (r >= INT32_MIN && r <= INT32_MAX) ? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r);
}
else if (strcmp(op, "sub_int") == 0) {
int dest = (int)a1->valuedouble;
int64_t r = (int64_t)JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) - (int64_t)JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]);
frame->slots[dest] = (r >= INT32_MIN && r <= INT32_MAX) ? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r);
}
else if (strcmp(op, "mul_int") == 0) {
int dest = (int)a1->valuedouble;
int64_t r = (int64_t)JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) * (int64_t)JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]);
frame->slots[dest] = (r >= INT32_MIN && r <= INT32_MAX) ? JS_NewInt32(ctx, (int32_t)r) : JS_NewFloat64(ctx, (double)r);
}
else if (strcmp(op, "div_int") == 0) {
int dest = (int)a1->valuedouble;
int32_t ia = JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]);
int32_t ib = JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]);
if (ib == 0) frame->slots[dest] = JS_NULL;
else if (ia % ib == 0) frame->slots[dest] = JS_NewInt32(ctx, ia / ib);
else frame->slots[dest] = JS_NewFloat64(ctx, (double)ia / (double)ib);
}
else if (strcmp(op, "mod_int") == 0) {
int dest = (int)a1->valuedouble;
int32_t ib = JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]);
if (ib == 0) frame->slots[dest] = JS_NULL;
else frame->slots[dest] = JS_NewInt32(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) % ib);
}
else if (strcmp(op, "neg_int") == 0) {
int dest = (int)a1->valuedouble;
int32_t i = JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]);
if (i == INT32_MIN) frame->slots[dest] = JS_NewFloat64(ctx, -(double)i);
else frame->slots[dest] = JS_NewInt32(ctx, -i);
}
/* ---- Specialized arithmetic (float) ---- */
else if (strcmp(op, "add_float") == 0) {
int dest = (int)a1->valuedouble;
double a, b;
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
frame->slots[dest] = JS_NewFloat64(ctx, a + b);
}
else if (strcmp(op, "sub_float") == 0) {
int dest = (int)a1->valuedouble;
double a, b;
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
frame->slots[dest] = JS_NewFloat64(ctx, a - b);
}
else if (strcmp(op, "mul_float") == 0) {
int dest = (int)a1->valuedouble;
double a, b;
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
frame->slots[dest] = JS_NewFloat64(ctx, a * b);
}
else if (strcmp(op, "div_float") == 0) {
int dest = (int)a1->valuedouble;
double a, b;
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
if (b == 0.0) frame->slots[dest] = JS_NULL;
else frame->slots[dest] = JS_NewFloat64(ctx, a / b);
}
else if (strcmp(op, "mod_float") == 0) {
int dest = (int)a1->valuedouble;
double a, b;
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
if (b == 0.0) frame->slots[dest] = JS_NULL;
else frame->slots[dest] = JS_NewFloat64(ctx, fmod(a, b));
}
else if (strcmp(op, "neg_float") == 0) {
int dest = (int)a1->valuedouble;
double d;
JS_ToFloat64(ctx, &d, frame->slots[(int)a2->valuedouble]);
frame->slots[dest] = JS_NewFloat64(ctx, -d);
}
/* ---- Specialized comparisons (int) ---- */
else if (strcmp(op, "eq_int") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) == JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]));
}
else if (strcmp(op, "ne_int") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) != JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]));
}
else if (strcmp(op, "lt_int") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) < JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]));
}
else if (strcmp(op, "le_int") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) <= JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]));
}
else if (strcmp(op, "gt_int") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) > JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]));
}
else if (strcmp(op, "ge_int") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_NewBool(ctx, JS_VALUE_GET_INT(frame->slots[(int)a2->valuedouble]) >= JS_VALUE_GET_INT(frame->slots[(int)a3->valuedouble]));
}
/* ---- Specialized comparisons (float) ---- */
else if (strcmp(op, "eq_float") == 0) {
int dest = (int)a1->valuedouble;
double a, b;
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
frame->slots[dest] = JS_NewBool(ctx, a == b);
}
else if (strcmp(op, "ne_float") == 0) {
int dest = (int)a1->valuedouble;
double a, b;
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
frame->slots[dest] = JS_NewBool(ctx, a != b);
}
else if (strcmp(op, "lt_float") == 0) {
int dest = (int)a1->valuedouble;
double a, b;
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
frame->slots[dest] = JS_NewBool(ctx, a < b);
}
else if (strcmp(op, "le_float") == 0) {
int dest = (int)a1->valuedouble;
double a, b;
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
frame->slots[dest] = JS_NewBool(ctx, a <= b);
}
else if (strcmp(op, "gt_float") == 0) {
int dest = (int)a1->valuedouble;
double a, b;
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
frame->slots[dest] = JS_NewBool(ctx, a > b);
}
else if (strcmp(op, "ge_float") == 0) {
int dest = (int)a1->valuedouble;
double a, b;
JS_ToFloat64(ctx, &a, frame->slots[(int)a2->valuedouble]);
JS_ToFloat64(ctx, &b, frame->slots[(int)a3->valuedouble]);
frame->slots[dest] = JS_NewBool(ctx, a >= b);
}
/* ---- Specialized comparisons (text) ---- */
else if (strcmp(op, "eq_text") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble], TRUE) == 0);
}
else if (strcmp(op, "ne_text") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble], TRUE) != 0);
}
else if (strcmp(op, "lt_text") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble], FALSE) < 0);
}
else if (strcmp(op, "le_text") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble], FALSE) <= 0);
}
else if (strcmp(op, "gt_text") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble], FALSE) > 0);
}
else if (strcmp(op, "ge_text") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = JS_NewBool(ctx, js_string_compare_value(ctx, frame->slots[(int)a2->valuedouble], frame->slots[(int)a3->valuedouble], FALSE) >= 0);
}
/* ---- Specialized comparisons (bool) ---- */
else if (strcmp(op, "eq_bool") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = (frame->slots[(int)a2->valuedouble] == frame->slots[(int)a3->valuedouble]) ? JS_TRUE : JS_FALSE;
}
else if (strcmp(op, "ne_bool") == 0) {
int dest = (int)a1->valuedouble;
frame->slots[dest] = (frame->slots[(int)a2->valuedouble] != frame->slots[(int)a3->valuedouble]) ? JS_TRUE : JS_FALSE;
}
else if (strcmp(op, "abs") == 0) {
int dest = (int)a1->valuedouble;
JSValue v = frame->slots[(int)a2->valuedouble];
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; }
JSValue tmp = v;
JSValue res = js_cell_text_lower(ctx, JS_NULL, 1, &tmp);
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; }
JSValue tmp = v;
JSValue res = js_cell_text_upper(ctx, JS_NULL, 1, &tmp);
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; }
JSValue tmp = v;
JSValue res = js_cell_character(ctx, JS_NULL, 1, &tmp);
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; }
JSValue tmp = v;
JSValue res = js_cell_text_codepoint(ctx, JS_NULL, 1, &tmp);
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_GetPropertyNumber(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)) {
JSValue r = JS_SetPropertyNumber(ctx, obj, JS_VALUE_GET_INT(idx), val);
ret = JS_IsException(r) ? -1 : 0;
} 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 C stack */
int nr_slots = (int)objhdr_cap56(new_frame->hdr);
int c_argc = (nr_slots >= 2) ? nr_slots - 2 : 0;
JSValue args[c_argc > 0 ? c_argc : 1];
for (int i = 0; i < c_argc; i++)
args[i] = new_frame->slots[i + 1];
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, args);
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...]) */
JSGCRef key_gc;
JS_PushGCRef(ctx, &key_gc);
key_gc.val = JS_NewString(ctx, method_name);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
JSValue arr = JS_NewArray(ctx);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(arr)) { JS_PopGCRef(ctx, &key_gc); 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_SetPropertyNumber(ctx, frame->slots[dest], i, frame->slots[areg]);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
}
JSValue call_args[2] = { key_gc.val, frame->slots[dest] };
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, call_args, 0);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
ctx->reg_current_frame = JS_NULL;
JS_PopGCRef(ctx, &key_gc);
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 */
JSValue args[nargs > 0 ? nargs : 1];
cJSON *p = a3->next;
for (int i = 0; i < nargs; i++, p = p->next) {
if (cJSON_IsString(p)) break;
args[i] = frame->slots[(int)p->valuedouble];
}
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, args);
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) &&
JS_VALUE_GET_FUNCTION(obj)->length == 2) {
/* Proxy call (arity-2 functions only): obj(key, [args...])
key lives in frame->slots[key_reg], which is GC-safe */
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_SetPropertyNumber(ctx, frame->slots[dest], i, frame->slots[areg]);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
}
JSValue call_args[2] = { frame->slots[key_reg], frame->slots[dest] };
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, call_args, 0);
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)) {
/* Non-proxy function: bracket access not allowed */
JS_ThrowTypeError(ctx, "cannot use bracket notation on non-proxy 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 {
JSValue args[nargs > 0 ? nargs : 1];
cJSON *p = a3->next;
for (int i = 0; i < nargs; i++, p = p->next) {
args[i] = frame->slots[(int)p->valuedouble];
}
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, args);
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;
JSGCRef arg_refs[len > 0 ? len : 1];
for (int i = 0; i < len; i++) {
JS_PushGCRef(ctx, &arg_refs[i]);
arg_refs[i].val = JS_GetPropertyNumber(ctx, frame->slots[arr_slot], i);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
}
JSValue args[len > 0 ? len : 1];
for (int i = 0; i < len; i++) args[i] = arg_refs[i].val;
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, args);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
for (int i = len - 1; i >= 0; i--) JS_PopGCRef(ctx, &arg_refs[i]);
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_SetPropertyNumber(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; }
JSText *s = JS_VALUE_GET_PTR(frame->slots[pt_slot]);
s = pretext_concat_value(ctx, s, frame->slots[right_slot]);
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 = "";
JSGCRef pat_gc, flags_gc;
JS_PushGCRef(ctx, &pat_gc);
pat_gc.val = JS_NewString(ctx, pattern);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
JS_PushGCRef(ctx, &flags_gc);
flags_gc.val = *flags_str ? JS_NewString(ctx, flags_str) : JS_NULL;
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
JSValue bc = js_compile_regexp(ctx, pat_gc.val, flags_gc.val);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
if (JS_IsException(bc)) {
JS_PopGCRef(ctx, &flags_gc);
JS_PopGCRef(ctx, &pat_gc);
goto disrupt;
}
JSValue re_obj = js_regexp_constructor_internal(ctx, pat_gc.val, bc);
frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val);
JS_PopGCRef(ctx, &flags_gc);
JS_PopGCRef(ctx, &pat_gc);
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)) {
fprintf(stderr, "unhandled disruption\n");
result = JS_Throw(ctx, JS_NULL);
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);
}