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