diff --git a/meson.build b/meson.build index ad0f422a..f515a62a 100644 --- a/meson.build +++ b/meson.build @@ -44,7 +44,12 @@ src += [ # core 'wildmatch.c', 'qjs_actor.c', 'miniz.c', - 'quickjs.c', + 'runtime.c', + 'cell_js.c', + 'tokenize.c', + 'parse.c', + 'mach.c', + 'mcode.c', 'libregexp.c', 'libunicode.c', 'cutils.c', 'dtoa.c' ] diff --git a/source/cell_js.c b/source/cell_js.c new file mode 100644 index 00000000..5d3b6c9b --- /dev/null +++ b/source/cell_js.c @@ -0,0 +1,12740 @@ +/* + * 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" + +/* argument of OP_special_object */ +typedef enum { + OP_SPECIAL_OBJECT_THIS_FUNC, + OP_SPECIAL_OBJECT_VAR_OBJECT, +} OPSpecialObjectEnum; + +JSValue JS_CallInternal (JSContext *caller_ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv, int flags) { + JSContext *ctx = caller_ctx; + JSFunction *f; + JSFunctionBytecode *b; + JSStackFrame sf_s, *sf = &sf_s; + const uint8_t *pc; + int opcode, arg_allocated_size, i; + JSValue *stack_buf, *var_buf, *arg_buf, *sp, ret_val; + size_t alloca_size; + +#if !DIRECT_DISPATCH +#define SWITCH(pc) switch (opcode = *pc++) +#define CASE(op) case op +#define DEFAULT default +#define BREAK break +#define SWITCH_OPCODE(new_op, target_label) \ + do { \ + const uint8_t *instr_ptr = pc - 1; \ + uint8_t *bc = (uint8_t *)b->byte_code_buf; \ + size_t instr_idx = instr_ptr - b->byte_code_buf; \ + bc[instr_idx] = new_op; \ + goto target_label; \ + } while (0) +#else + static const void *const dispatch_table[256] = { +#define DEF(id, size, n_pop, n_push, f) &&case_OP_##id, +#if SHORT_OPCODES +#define def(id, size, n_pop, n_push, f) +#else +#define def(id, size, n_pop, n_push, f) &&case_default, +#endif +#include "quickjs-opcode.h" + [OP_COUNT... 255] = &&case_default + }; +#define SWITCH(pc) goto *dispatch_table[opcode = *pc++]; +#define CASE(op) case_##op +#define DEFAULT case_default +#define BREAK SWITCH (pc) +#endif + + if (js_poll_interrupts (caller_ctx)) return JS_EXCEPTION; + if (unlikely (!JS_IsFunction (func_obj))) { + not_a_function: + return JS_ThrowTypeError (caller_ctx, "not a function"); + } + f = JS_VALUE_GET_FUNCTION (func_obj); + /* Strict arity enforcement: too many arguments throws (length < 0 means variadic) */ + if (unlikely (f->length >= 0 && argc > f->length)) { + char buf[KEY_GET_STR_BUF_SIZE]; + return JS_ThrowTypeError ( + caller_ctx, "too many arguments for %s: expected %d, got %d", JS_KeyGetStr (caller_ctx, buf, KEY_GET_STR_BUF_SIZE, f->name), f->length, argc); + } + switch (f->kind) { + case JS_FUNC_KIND_C: + return js_call_c_function (caller_ctx, func_obj, this_obj, argc, (JSValue *)argv); + case JS_FUNC_KIND_BYTECODE: + break; /* continue to bytecode execution below */ + case JS_FUNC_KIND_REGISTER: + return JS_CallRegisterVM(caller_ctx, f->u.reg.code, this_obj, argc, (JSValue *)argv, + f->u.reg.env_record, f->u.reg.outer_frame); + case JS_FUNC_KIND_MCODE: + return mcode_exec(caller_ctx, f->u.mcode.code, this_obj, argc, (JSValue *)argv, f->u.mcode.outer_frame); + default: + goto not_a_function; + } + b = f->u.func.function_bytecode; + + if (unlikely (caller_ctx->trace_hook) + && (caller_ctx->trace_type & JS_HOOK_CALL)) { + js_debug dbg; + js_debug_info (caller_ctx, func_obj, &dbg); + caller_ctx->trace_hook (caller_ctx, JS_HOOK_CALL, &dbg, caller_ctx->trace_data); + } + + if (unlikely (argc < b->arg_count || (flags & JS_CALL_FLAG_COPY_ARGV))) { + arg_allocated_size = b->arg_count; + } else { + arg_allocated_size = 0; + } + + alloca_size + = sizeof (JSValue) * (arg_allocated_size + b->var_count + b->stack_size); + if (js_check_stack_overflow (ctx, alloca_size)) + return JS_ThrowStackOverflow (caller_ctx); + + sf->js_mode = b->js_mode; + sf->arg_count = argc; + sf->cur_func = (JSValue)func_obj; + + /* Allocate JSFrame for args + vars on regular heap (stable pointers). + This enables the outer_frame closure model. */ + { + int frame_slot_count = b->arg_count + b->var_count; + size_t frame_size = sizeof(JSFrame) + frame_slot_count * sizeof(JSValue); + JSFrame *jf = pjs_mallocz(frame_size); + if (!jf) + return JS_ThrowOutOfMemory(caller_ctx); + jf->header = objhdr_make(frame_slot_count, OBJ_FRAME, false, false, false, false); + jf->function = JS_MKPTR(f); + jf->caller = JS_NULL; + jf->return_pc = 0; + sf->js_frame = JS_MKPTR(jf); + + /* Point arg_buf and var_buf into JSFrame slots */ + arg_buf = jf->slots; + var_buf = jf->slots + b->arg_count; + sf->arg_buf = arg_buf; + sf->var_buf = var_buf; + + /* Copy arguments into frame */ + int n = min_int(argc, b->arg_count); + for (i = 0; i < n; i++) + arg_buf[i] = argv[i]; + for (; i < b->arg_count; i++) + arg_buf[i] = JS_NULL; + if (argc < b->arg_count) + sf->arg_count = b->arg_count; + + /* Initialize local variables */ + for (i = 0; i < b->var_count; i++) + var_buf[i] = JS_NULL; + } + + /* Stack is still on C stack (transient) */ + stack_buf = alloca(sizeof(JSValue) * b->stack_size); + sp = stack_buf; + pc = b->byte_code_buf; + sf->stack_buf = stack_buf; + sf->p_sp = &sp; /* GC uses this to find current stack top */ + sf->prev_frame = ctx->current_stack_frame; + ctx->current_stack_frame = sf; + +restart: + for (;;) { + int call_argc; + JSValue *call_argv; + + SWITCH (pc) { + CASE (OP_push_i32) : *sp++ = JS_NewInt32 (ctx, get_u32 (pc)); + pc += 4; + BREAK; + CASE (OP_push_const) : *sp++ = b->cpool[get_u32 (pc)]; + pc += 4; + BREAK; +#if SHORT_OPCODES + CASE (OP_push_minus1) + : CASE (OP_push_0) + : CASE (OP_push_1) + : CASE (OP_push_2) + : CASE (OP_push_3) + : CASE (OP_push_4) + : CASE (OP_push_5) + : CASE (OP_push_6) + : CASE (OP_push_7) : *sp++ = JS_NewInt32 (ctx, opcode - OP_push_0); + BREAK; + CASE (OP_push_i8) : *sp++ = JS_NewInt32 (ctx, get_i8 (pc)); + pc += 1; + BREAK; + CASE (OP_push_i16) : *sp++ = JS_NewInt32 (ctx, get_i16 (pc)); + pc += 2; + BREAK; + CASE (OP_push_const8) : *sp++ = b->cpool[*pc++]; + BREAK; + CASE (OP_fclosure8) + : *sp++ = js_closure (ctx, b->cpool[*pc++], sf); + if (unlikely (JS_IsException (sp[-1]))) goto exception; + BREAK; + CASE (OP_push_empty_string) : *sp++ = JS_KEY_empty; + BREAK; +#endif + CASE (OP_null) : *sp++ = JS_NULL; + BREAK; + CASE (OP_push_this) + : /* OP_push_this is only called at the start of a function */ + { + JSValue val = this_obj; + *sp++ = val; + } + BREAK; + CASE (OP_push_false) : *sp++ = JS_FALSE; + BREAK; + CASE (OP_push_true) : *sp++ = JS_TRUE; + BREAK; + CASE (OP_object) : *sp++ = JS_NewObject (ctx); + if (unlikely (JS_IsException (sp[-1]))) goto exception; + BREAK; + CASE (OP_special_object) : { + int arg = *pc++; + switch (arg) { + case OP_SPECIAL_OBJECT_THIS_FUNC: + *sp++ = sf->cur_func; + break; + case OP_SPECIAL_OBJECT_VAR_OBJECT: + *sp++ = JS_NewObjectProto (ctx, JS_NULL); + if (unlikely (JS_IsException (sp[-1]))) goto exception; + break; + default: + abort (); + } + } + BREAK; + CASE (OP_drop) : ; + sp--; + BREAK; + CASE (OP_nip) : ; + sp[-2] = sp[-1]; + sp--; + BREAK; + CASE (OP_nip1) + : /* a b c -> b c */ + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp--; + BREAK; + CASE (OP_dup) : sp[0] = sp[-1]; + sp++; + BREAK; + CASE (OP_dup2) + : /* a b -> a b a b */ + sp[0] = sp[-2]; + sp[1] = sp[-1]; + sp += 2; + BREAK; + CASE (OP_dup3) + : /* a b c -> a b c a b c */ + sp[0] = sp[-3]; + sp[1] = sp[-2]; + sp[2] = sp[-1]; + sp += 3; + BREAK; + CASE (OP_dup1) + : /* a b -> a a b */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp++; + BREAK; + CASE (OP_insert2) + : /* obj a -> a obj a (dup_x1) */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[0]; + sp++; + BREAK; + CASE (OP_insert3) + : /* obj prop a -> a obj prop a (dup_x2) */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[0]; + sp++; + BREAK; + CASE (OP_insert4) + : /* this obj prop a -> a this obj prop a */ + sp[0] = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = sp[0]; + sp++; + BREAK; + CASE (OP_perm3) + : /* obj a b -> a obj b (213) */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = tmp; + } + BREAK; + CASE (OP_rot3l) + : /* x a b -> a b x (231) */ + { + JSValue tmp; + tmp = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE (OP_rot4l) + : /* x a b c -> a b c x */ + { + JSValue tmp; + tmp = sp[-4]; + sp[-4] = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE (OP_rot5l) + : /* x a b c d -> a b c d x */ + { + JSValue tmp; + tmp = sp[-5]; + sp[-5] = sp[-4]; + sp[-4] = sp[-3]; + sp[-3] = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE (OP_rot3r) + : /* a b x -> x a b (312) */ + { + JSValue tmp; + tmp = sp[-1]; + sp[-1] = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = tmp; + } + BREAK; + CASE (OP_perm4) + : /* obj prop a b -> a obj prop b */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = tmp; + } + BREAK; + CASE (OP_perm5) + : /* this obj prop a b -> a this obj prop b */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-3]; + sp[-3] = sp[-4]; + sp[-4] = sp[-5]; + sp[-5] = tmp; + } + BREAK; + CASE (OP_swap) + : /* a b -> b a */ + { + JSValue tmp; + tmp = sp[-2]; + sp[-2] = sp[-1]; + sp[-1] = tmp; + } + BREAK; + CASE (OP_swap2) + : /* a b c d -> c d a b */ + { + JSValue tmp1, tmp2; + tmp1 = sp[-4]; + tmp2 = sp[-3]; + sp[-4] = sp[-2]; + sp[-3] = sp[-1]; + sp[-2] = tmp1; + sp[-1] = tmp2; + } + BREAK; + + CASE (OP_fclosure) : { + JSValue bfunc = b->cpool[get_u32 (pc)]; + pc += 4; + *sp++ = js_closure (ctx, bfunc, sf); + if (unlikely (JS_IsException (sp[-1]))) goto exception; + } + BREAK; +#if SHORT_OPCODES + CASE (OP_call0) + : CASE (OP_call1) + : CASE (OP_call2) : CASE (OP_call3) : call_argc = opcode - OP_call0; + goto has_call_argc; +#endif + CASE (OP_call) : CASE (OP_tail_call) : { + call_argc = get_u16 (pc); + pc += 2; + goto has_call_argc; + has_call_argc: + call_argv = sp - call_argc; + sf->cur_pc = pc; + /* TODO: Use trampoline - for now keep recursive */ + ret_val = JS_CallInternal (ctx, call_argv[-1], JS_NULL, call_argc, call_argv, 0); + if (unlikely (JS_IsException (ret_val))) goto exception; + if (opcode == OP_tail_call) goto done; + sp -= call_argc + 1; + *sp++ = ret_val; + } + BREAK; + CASE (OP_call_method) : CASE (OP_tail_call_method) : { + call_argc = get_u16 (pc); + pc += 2; + call_argv = sp - call_argc; + sf->cur_pc = pc; + /* Proxy method-call: detect [func, "name", ...args] + and rewrite as func("name", [args]) */ + + if (JS_IsFunction (call_argv[-2])) { + JSValue name = call_argv[-1]; + if (!JS_IsText (name)) { + ret_val + = JS_ThrowTypeError (ctx, "second argument must be a string"); + goto exception; + } + + JSValue args = JS_NewArrayLen (ctx, call_argc); + if (unlikely (JS_IsException (args))) goto exception; + + /* Move args into the array, then null out stack slots. */ + JSArray *ar = JS_VALUE_GET_ARRAY (args); + for (i = 0; i < call_argc; i++) { + ar->values[i] = call_argv[i]; + call_argv[i] = JS_NULL; + } + + JSValue proxy_argv[2]; + proxy_argv[0] + = name; /* still owned by stack; freed by normal cleanup */ + proxy_argv[1] = args; + + ret_val + = JS_CallInternal (ctx, call_argv[-2], JS_NULL, 2, proxy_argv, 0); + } else { + ret_val = JS_CallInternal (ctx, call_argv[-1], call_argv[-2], call_argc, call_argv, 0); + } + if (unlikely (JS_IsException (ret_val))) goto exception; + if (opcode == OP_tail_call_method) goto done; + for (i = -2; i < call_argc; i++) + sp -= call_argc + 2; + *sp++ = ret_val; + } + BREAK; + CASE (OP_array_from) : { + int i; + + call_argc = get_u16 (pc); + pc += 2; + ret_val = JS_NewArrayLen (ctx, call_argc); + if (unlikely (JS_IsException (ret_val))) goto exception; + call_argv = sp - call_argc; + JSArray *ar = JS_VALUE_GET_ARRAY (ret_val); + for (i = 0; i < call_argc; i++) { + ar->values[i] = call_argv[i]; + call_argv[i] = JS_NULL; + } + sp -= call_argc; + *sp++ = ret_val; + } + BREAK; + CASE (OP_return) : ret_val = *--sp; + goto done; + CASE (OP_return_undef) : ret_val = JS_NULL; + goto done; + + CASE (OP_throw) : JS_Throw (ctx, *--sp); + goto exception; + + CASE (OP_throw_error) + : +#define JS_THROW_VAR_RO 0 +#define JS_THROW_VAR_REDECL 1 +#define JS_THROW_VAR_UNINITIALIZED 2 +#define JS_THROW_ERROR_ITERATOR_THROW 4 + { + JSValue key; + int type; + key = b->cpool[get_u32 (pc)]; + type = pc[4]; + pc += 5; + if (type == JS_THROW_VAR_REDECL) + JS_ThrowSyntaxErrorVarRedeclaration (ctx, key); + else if (type == JS_THROW_VAR_UNINITIALIZED) + JS_ThrowReferenceErrorUninitialized (ctx, key); + else if (type == JS_THROW_ERROR_ITERATOR_THROW) + JS_ThrowTypeError (ctx, "iterator does not have a throw method"); + else + JS_ThrowInternalError (ctx, "invalid throw var type %d", type); + } + goto exception; + + CASE (OP_regexp) : { + sp[-2] = js_regexp_constructor_internal (ctx, sp[-2], sp[-1]); + sp--; + } + BREAK; + + /* Global variable opcodes - resolved by linker to get/set_global_slot */ + CASE (OP_check_var) : + pc += 4; + JS_ThrowInternalError (ctx, "OP_check_var: linker should have resolved this"); + goto exception; + BREAK; + CASE (OP_get_var_undef) : + CASE (OP_get_var) : + pc += 4; + JS_ThrowInternalError (ctx, "OP_get_var: linker should have resolved to get_global_slot"); + goto exception; + BREAK; + CASE (OP_put_var) : + CASE (OP_put_var_init) : + pc += 4; + sp--; + JS_ThrowInternalError (ctx, "OP_put_var: global object is immutable, linker should resolve"); + goto exception; + BREAK; + CASE (OP_put_var_strict) : + pc += 4; + sp -= 2; + JS_ThrowInternalError (ctx, "OP_put_var_strict: global object is immutable, linker should resolve"); + goto exception; + BREAK; + + CASE (OP_define_var) : + CASE (OP_check_define_var) : + pc += 5; + JS_ThrowInternalError (ctx, "global object is immutable - cannot define variables at runtime"); + goto exception; + BREAK; + CASE (OP_define_func) : + pc += 5; + sp--; + JS_ThrowInternalError (ctx, "global object is immutable - cannot define functions at runtime"); + goto exception; + BREAK; + + CASE (OP_get_loc) : { + int idx; + idx = get_u16 (pc); + pc += 2; + sp[0] = var_buf[idx]; + sp++; + } + BREAK; + CASE (OP_put_loc) : { + int idx; + idx = get_u16 (pc); + pc += 2; + set_value (ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE (OP_set_loc) : { + int idx; + idx = get_u16 (pc); + pc += 2; + set_value (ctx, &var_buf[idx], sp[-1]); + } + BREAK; + CASE (OP_get_arg) : { + int idx; + idx = get_u16 (pc); + pc += 2; + sp[0] = arg_buf[idx]; + sp++; + } + BREAK; + CASE (OP_put_arg) : { + int idx; + idx = get_u16 (pc); + pc += 2; + set_value (ctx, &arg_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE (OP_set_arg) : { + int idx; + idx = get_u16 (pc); + pc += 2; + set_value (ctx, &arg_buf[idx], sp[-1]); + } + BREAK; + +#if SHORT_OPCODES + CASE (OP_get_loc8) : *sp++ = var_buf[*pc++]; + BREAK; + CASE (OP_put_loc8) : set_value (ctx, &var_buf[*pc++], *--sp); + BREAK; + CASE (OP_set_loc8) + : set_value (ctx, &var_buf[*pc++], sp[-1]); + BREAK; + + CASE (OP_get_loc0) : *sp++ = var_buf[0]; + BREAK; + CASE (OP_get_loc1) : *sp++ = var_buf[1]; + BREAK; + CASE (OP_get_loc2) : *sp++ = var_buf[2]; + BREAK; + CASE (OP_get_loc3) : *sp++ = var_buf[3]; + BREAK; + CASE (OP_put_loc0) : set_value (ctx, &var_buf[0], *--sp); + BREAK; + CASE (OP_put_loc1) : set_value (ctx, &var_buf[1], *--sp); + BREAK; + CASE (OP_put_loc2) : set_value (ctx, &var_buf[2], *--sp); + BREAK; + CASE (OP_put_loc3) : set_value (ctx, &var_buf[3], *--sp); + BREAK; + CASE (OP_set_loc0) + : set_value (ctx, &var_buf[0], sp[-1]); + BREAK; + CASE (OP_set_loc1) + : set_value (ctx, &var_buf[1], sp[-1]); + BREAK; + CASE (OP_set_loc2) + : set_value (ctx, &var_buf[2], sp[-1]); + BREAK; + CASE (OP_set_loc3) + : set_value (ctx, &var_buf[3], sp[-1]); + BREAK; + CASE (OP_get_arg0) : *sp++ = arg_buf[0]; + BREAK; + CASE (OP_get_arg1) : *sp++ = arg_buf[1]; + BREAK; + CASE (OP_get_arg2) : *sp++ = arg_buf[2]; + BREAK; + CASE (OP_get_arg3) : *sp++ = arg_buf[3]; + BREAK; + CASE (OP_put_arg0) : set_value (ctx, &arg_buf[0], *--sp); + BREAK; + CASE (OP_put_arg1) : set_value (ctx, &arg_buf[1], *--sp); + BREAK; + CASE (OP_put_arg2) : set_value (ctx, &arg_buf[2], *--sp); + BREAK; + CASE (OP_put_arg3) : set_value (ctx, &arg_buf[3], *--sp); + BREAK; + CASE (OP_set_arg0) + : set_value (ctx, &arg_buf[0], sp[-1]); + BREAK; + CASE (OP_set_arg1) + : set_value (ctx, &arg_buf[1], sp[-1]); + BREAK; + CASE (OP_set_arg2) + : set_value (ctx, &arg_buf[2], sp[-1]); + BREAK; + CASE (OP_set_arg3) + : set_value (ctx, &arg_buf[3], sp[-1]); + BREAK; +#endif + CASE (OP_set_loc_uninitialized) : { + int idx; + idx = get_u16 (pc); + pc += 2; + set_value (ctx, &var_buf[idx], JS_UNINITIALIZED); + } + BREAK; + CASE (OP_get_loc_check) : { + int idx; + idx = get_u16 (pc); + pc += 2; + if (unlikely (JS_IsUninitialized (var_buf[idx]))) { + JS_ThrowReferenceErrorUninitialized2 (ctx, b, idx, FALSE); + goto exception; + } + sp[0] = var_buf[idx]; + sp++; + } + BREAK; + CASE (OP_get_loc_checkthis) : { + int idx; + idx = get_u16 (pc); + pc += 2; + if (unlikely (JS_IsUninitialized (var_buf[idx]))) { + JS_ThrowReferenceErrorUninitialized2 (caller_ctx, b, idx, FALSE); + goto exception; + } + sp[0] = var_buf[idx]; + sp++; + } + BREAK; + CASE (OP_put_loc_check) : { + int idx; + idx = get_u16 (pc); + pc += 2; + if (unlikely (JS_IsUninitialized (var_buf[idx]))) { + JS_ThrowReferenceErrorUninitialized2 (ctx, b, idx, FALSE); + goto exception; + } + set_value (ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + CASE (OP_put_loc_check_init) : { + int idx; + idx = get_u16 (pc); + pc += 2; + if (unlikely (!JS_IsUninitialized (var_buf[idx]))) { + JS_ThrowReferenceError (ctx, "'this' can be initialized only once"); + goto exception; + } + set_value (ctx, &var_buf[idx], sp[-1]); + sp--; + } + BREAK; + + CASE (OP_goto) : pc += (int32_t)get_u32 (pc); + if (unlikely (js_poll_interrupts (ctx))) goto exception; + BREAK; +#if SHORT_OPCODES + CASE (OP_goto16) : pc += (int16_t)get_u16 (pc); + if (unlikely (js_poll_interrupts (ctx))) goto exception; + BREAK; + CASE (OP_goto8) : pc += (int8_t)pc[0]; + if (unlikely (js_poll_interrupts (ctx))) goto exception; + BREAK; +#endif + CASE (OP_if_true) : { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 4; + if ((uint32_t)JS_VALUE_GET_TAG (op1) <= JS_TAG_NULL) { + res = JS_VALUE_GET_INT (op1); + } else { + res = JS_ToBool (ctx, op1); + } + sp--; + if (res) { pc += (int32_t)get_u32 (pc - 4) - 4; } + if (unlikely (js_poll_interrupts (ctx))) goto exception; + } + BREAK; + CASE (OP_if_false) : { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 4; + /* quick and dirty test for JS_TAG_INT, JS_TAG_BOOL, JS_TAG_NULL and + * JS_TAG_UNDEFINED */ + if ((uint32_t)JS_VALUE_GET_TAG (op1) <= JS_TAG_NULL) { + res = JS_VALUE_GET_INT (op1); + } else { + res = JS_ToBool (ctx, op1); + } + sp--; + if (!res) { pc += (int32_t)get_u32 (pc - 4) - 4; } + if (unlikely (js_poll_interrupts (ctx))) goto exception; + } + BREAK; +#if SHORT_OPCODES + CASE (OP_if_true8) : { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 1; + if ((uint32_t)JS_VALUE_GET_TAG (op1) <= JS_TAG_NULL) { + res = JS_VALUE_GET_INT (op1); + } else { + res = JS_ToBool (ctx, op1); + } + sp--; + if (res) { pc += (int8_t)pc[-1] - 1; } + if (unlikely (js_poll_interrupts (ctx))) goto exception; + } + BREAK; + CASE (OP_if_false8) : { + int res; + JSValue op1; + + op1 = sp[-1]; + pc += 1; + if ((uint32_t)JS_VALUE_GET_TAG (op1) <= JS_TAG_NULL) { + res = JS_VALUE_GET_INT (op1); + } else { + res = JS_ToBool (ctx, op1); + } + sp--; + if (!res) { pc += (int8_t)pc[-1] - 1; } + if (unlikely (js_poll_interrupts (ctx))) goto exception; + } + BREAK; +#endif + CASE (OP_catch) : { + int32_t diff; + diff = get_u32 (pc); + sp[0] = JS_NewCatchOffset (ctx, pc + diff - b->byte_code_buf); + sp++; + pc += 4; + } + BREAK; + CASE (OP_gosub) : { + int32_t diff; + diff = get_u32 (pc); + /* XXX: should have a different tag to avoid security flaw */ + sp[0] = JS_NewInt32 (ctx, pc + 4 - b->byte_code_buf); + sp++; + pc += diff; + } + BREAK; + CASE (OP_ret) : { + JSValue op1; + uint32_t pos; + op1 = sp[-1]; + if (unlikely (JS_VALUE_GET_TAG (op1) != JS_TAG_INT)) goto ret_fail; + pos = JS_VALUE_GET_INT (op1); + if (unlikely (pos >= b->byte_code_len)) { + ret_fail: + JS_ThrowInternalError (ctx, "invalid ret value"); + goto exception; + } + sp--; + pc = b->byte_code_buf + pos; + } + BREAK; + + CASE (OP_nip_catch) : { + JSValue ret_val; + /* catch_offset ... ret_val -> ret_eval */ + ret_val = *--sp; + while (sp > stack_buf + && JS_VALUE_GET_TAG (sp[-1]) != JS_TAG_CATCH_OFFSET) { + } + if (unlikely (sp == stack_buf)) { + JS_ThrowInternalError (ctx, "nip_catch"); + goto exception; + } + sp[-1] = ret_val; + } + BREAK; + + CASE (OP_lnot) : { + int res; + JSValue op1; + + op1 = sp[-1]; + if ((uint32_t)JS_VALUE_GET_TAG (op1) <= JS_TAG_NULL) { + res = JS_VALUE_GET_INT (op1) != 0; + } else { + res = JS_ToBool (ctx, op1); + } + sp[-1] = JS_NewBool (ctx, !res); + } + BREAK; + + CASE (OP_get_field) : { + JSValue val; + uint32_t idx; + JSValue key; + JSValue obj; + + idx = get_u32 (pc); + pc += 4; + + sf->cur_pc = pc; + + /* Get JSValue key from cpool */ + key = b->cpool[idx]; + + obj = sp[-1]; + + /* Record property access - use JSValue key directly */ + if (JS_IsRecord (obj)) { + JSRecord *rec = JS_VALUE_GET_RECORD (obj); + val = rec_get (ctx, rec, key); + sp[-1] = val; + } else { + /* Non-record: return null */ + sp[-1] = JS_NULL; + } + } + BREAK; + + CASE (OP_get_field2) : { + JSValue val; + uint32_t idx; + JSValue key; + JSValue obj; + + idx = get_u32 (pc); + pc += 4; + + sf->cur_pc = pc; + + /* Get JSValue key from cpool */ + key = b->cpool[idx]; + + obj = sp[-1]; + + /* Proxy method-call sugar: func.name(...) -> func("name", [args...]) + OP_get_field2 is only emitted when a call immediately follows. */ + if (JS_IsFunction (obj)) { + val = key; /* "name" as JSValue string */ + *sp++ = val; /* stack becomes [func, "name"] */ + } else if (JS_IsRecord (obj)) { + /* Record property access - use JSValue key directly */ + JSRecord *rec = JS_VALUE_GET_RECORD (obj); + val = rec_get (ctx, rec, key); + *sp++ = val; + } else { + /* Non-record: push null */ + *sp++ = JS_NULL; + } + } + BREAK; + + CASE (OP_put_field) : { + int ret; + uint32_t idx; + JSValue key; + + idx = get_u32 (pc); + pc += 4; + sf->cur_pc = pc; + + /* Get JSValue key from cpool */ + key = b->cpool[idx]; + + /* Must be a record to set property */ + if (!JS_IsRecord (sp[-2])) { + sp -= 2; + JS_ThrowTypeError (ctx, "cannot set property of non-record"); + goto exception; + } + + /* Record property set - use JSValue key directly */ + ret = rec_set_own (ctx, &sp[-2], key, sp[-1]); + sp -= 2; + if (unlikely (ret < 0)) goto exception; + } + BREAK; + + CASE (OP_define_field) : { + int ret; + uint32_t idx; + JSValue key; + + idx = get_u32 (pc); + pc += 4; + + /* Get JSValue key from cpool */ + key = b->cpool[idx]; + + /* Must be a record */ + if (!JS_IsRecord (sp[-2])) { + sp--; + JS_ThrowTypeError (ctx, "cannot define field on non-record"); + goto exception; + } + + /* Record property set - use JSValue key directly */ + ret = rec_set_own (ctx, &sp[-2], key, sp[-1]); + sp--; + if (unlikely (ret < 0)) goto exception; + } + BREAK; + + CASE (OP_set_name) : { + int ret; + JSValue key; + key = b->cpool[get_u32 (pc)]; + pc += 4; + + ret = JS_DefineObjectName (ctx, sp[-1], key); + if (unlikely (ret < 0)) goto exception; + } + BREAK; + CASE (OP_set_name_computed) : { + int ret; + ret = JS_DefineObjectNameComputed (ctx, sp[-1], sp[-2]); + if (unlikely (ret < 0)) goto exception; + } + BREAK; + CASE (OP_define_method) : CASE (OP_define_method_computed) : { + JSValue value; + JSValue obj; + JSValue key; + int ret, op_flags; + BOOL is_computed; +#define OP_DEFINE_METHOD_METHOD 0 +#define OP_DEFINE_METHOD_ENUMERABLE 4 + + is_computed = (opcode == OP_define_method_computed); + if (is_computed) { + key = sp[-2]; + opcode += OP_define_method - OP_define_method_computed; + } else { + key = b->cpool[get_u32 (pc)]; + pc += 4; + } + op_flags = *pc++; + + obj = sp[-2 - is_computed]; + op_flags &= 3; + value = JS_NULL; + + if (op_flags == OP_DEFINE_METHOD_METHOD) { value = sp[-1]; } + ret = js_method_set_properties (ctx, sp[-1], key, 0, obj); + if (ret >= 0) { + /* JS_SetProperty consumes value, so don't free sp[-1] on success */ + ret = JS_SetProperty (ctx, obj, key, value); + } else { + } + if (is_computed) { ; } + sp -= 1 + is_computed; + if (unlikely (ret < 0)) goto exception; + } + BREAK; + + CASE (OP_define_class) + : CASE (OP_define_class_computed) + : JS_ThrowTypeError (ctx, "classes are not supported"); + goto exception; + + CASE (OP_get_array_el) : { + JSValue val; + sf->cur_pc = pc; + val = JS_GetPropertyValue (ctx, sp[-2], sp[-1]); + sp[-2] = val; + sp--; + if (unlikely (JS_IsException (val))) goto exception; + } + BREAK; + + CASE (OP_get_array_el2) : { + JSValue val; + sf->cur_pc = pc; + val = JS_GetPropertyValue (ctx, sp[-2], sp[-1]); + sp[-1] = val; + if (unlikely (JS_IsException (val))) goto exception; + } + BREAK; + + CASE (OP_get_array_el3) : { + JSValue val; + switch (JS_VALUE_GET_TAG (sp[-2])) { + case JS_TAG_INT: + case JS_TAG_STRING: + break; + default: + /* must be tested nefore JS_ToPropertyKey */ + if (unlikely (JS_IsNull (sp[-2]))) { + JS_ThrowTypeError (ctx, "value has no property"); + goto exception; + } + sf->cur_pc = pc; + ret_val = sp[-1]; // JS_ToPropertyKey (ctx, sp[-1]); // is this correct? No conversion can happen. + if (JS_IsException (ret_val)) goto exception; + sp[-1] = ret_val; + break; + } + sf->cur_pc = pc; + val = JS_GetPropertyValue (ctx, sp[-2], sp[-1]); + *sp++ = val; + if (unlikely (JS_IsException (val))) goto exception; + } + BREAK; + + CASE (OP_put_array_el) : { + int ret; + + /* Functions don't support property assignment in cell script */ + if (JS_IsFunction (sp[-3])) { + JS_ThrowTypeError (ctx, "cannot set property of function"); + goto exception; + } + + sf->cur_pc = pc; + ret = JS_SetPropertyValue (ctx, sp[-3], sp[-2], sp[-1]); + sp -= 3; + if (unlikely (ret < 0)) goto exception; + } + BREAK; + + CASE (OP_define_array_el) : { + int ret; + ret = JS_SetPropertyValue (ctx, sp[-3], sp[-2], sp[-1]); + sp -= 1; + if (unlikely (ret < 0)) goto exception; + } + BREAK; + + CASE (OP_copy_data_properties) + : /* target source excludeList */ + { + /* stack offsets (-1 based): + 2 bits for target, + 3 bits for source, + 2 bits for exclusionList */ + int mask; + + mask = *pc++; + sf->cur_pc = pc; + if (JS_CopyDataProperties (ctx, sp[-1 - (mask & 3)], sp[-1 - ((mask >> 2) & 7)], sp[-1 - ((mask >> 5) & 7)], 0)) + goto exception; + } + BREAK; + + CASE (OP_add) : CASE (OP_add_float) : { + JSValue op1 = sp[-2], op2 = sp[-1], res; + int tag1 = JS_VALUE_GET_NORM_TAG (op1); + int tag2 = JS_VALUE_GET_NORM_TAG (op2); + + /* 1) both ints? keep fast int path with overflow check */ + if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { + int64_t tmp + = (int64_t)JS_VALUE_GET_INT (op1) + JS_VALUE_GET_INT (op2); + if (likely ((int)tmp == tmp)) { + res = JS_NewInt32 (ctx, (int)tmp); + } else { + res = __JS_NewFloat64 (ctx, (double)JS_VALUE_GET_INT (op1) + (double)JS_VALUE_GET_INT (op2)); + } + } + /* 2) both floats? */ + else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2)) { + res = __JS_NewFloat64 (ctx, JS_VALUE_GET_FLOAT64 (op1) + JS_VALUE_GET_FLOAT64 (op2)); + } + /* 3) both strings? */ + else if (JS_IsText (op1) && JS_IsText (op2)) { + res = JS_ConcatString (ctx, op1, op2); + if (JS_IsException (res)) goto exception; + } + /* 4) mixed int/float? promote to float */ + // TODO: Seems slow + else if ((tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64) + || (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_INT)) { + double a, b; + if (tag1 == JS_TAG_INT) + a = (double)JS_VALUE_GET_INT (op1); + else + a = JS_VALUE_GET_FLOAT64 (op1); + if (tag2 == JS_TAG_INT) + b = (double)JS_VALUE_GET_INT (op2); + else + b = JS_VALUE_GET_FLOAT64 (op2); + res = __JS_NewFloat64 (ctx, a + b); + } else if (tag1 == JS_TAG_NULL || tag2 == JS_TAG_NULL) { + /* null + string or string + null should throw */ + if (JS_IsText (op1) || JS_IsText (op2)) { + JS_ThrowTypeError (ctx, "cannot concatenate null with string"); + goto exception; + } + res = JS_NULL; + } + /* 5) anything else → throw */ + else { + JS_ThrowTypeError (ctx, "cannot concatenate with string"); + goto exception; + } + + sp[-2] = res; + sp--; + } + BREAK; + CASE (OP_add_loc) : { + int idx = *pc++; + JSValue rhs = sp[-1]; + JSValue lhs = var_buf[idx]; + JSValue res; + int tag1 = JS_VALUE_GET_NORM_TAG (lhs); + int tag2 = JS_VALUE_GET_NORM_TAG (rhs); + + /* 1) both ints? fast path, overflow → float64 */ + if (likely (JS_VALUE_IS_BOTH_INT (lhs, rhs))) { + int a_i = JS_VALUE_GET_INT (lhs); + int b_i = JS_VALUE_GET_INT (rhs); + int64_t tmp = (int64_t)a_i + b_i; + if ((int)tmp == tmp) + res = JS_NewInt32 (ctx, (int)tmp); + else + res = __JS_NewFloat64 (ctx, (double)a_i + (double)b_i); + } + /* 2) both floats? */ + else if (JS_VALUE_IS_BOTH_FLOAT (lhs, rhs)) { + res = __JS_NewFloat64 (ctx, JS_VALUE_GET_FLOAT64 (lhs) + JS_VALUE_GET_FLOAT64 (rhs)); + } + /* 3) both strings? */ + else if (JS_IsText (lhs) && JS_IsText (rhs)) { + res = JS_ConcatString (ctx, lhs, rhs); + if (JS_IsException (res)) goto exception; + } + /* 4) mixed int/float? promote to float64 */ + else if ((tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64) + || (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_INT)) { + double a = tag1 == JS_TAG_INT ? (double)JS_VALUE_GET_INT (lhs) + : JS_VALUE_GET_FLOAT64 (lhs); + double b = tag2 == JS_TAG_INT ? (double)JS_VALUE_GET_INT (rhs) + : JS_VALUE_GET_FLOAT64 (rhs); + res = __JS_NewFloat64 (ctx, a + b); + } + /* 5) anything else → throw */ + else { + JS_ThrowTypeError (ctx, "cannot concatenate with string"); + goto exception; + } + + var_buf[idx] = res; + sp--; + } + BREAK; + CASE (OP_sub) : CASE (OP_sub_float) : { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { + int64_t r; + r = (int64_t)JS_VALUE_GET_INT (op1) - JS_VALUE_GET_INT (op2); + if (unlikely ((int)r != r)) goto binary_arith_slow; + sp[-2] = JS_NewInt32 (ctx, r); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2)) { + sp[-2] = __JS_NewFloat64 (ctx, JS_VALUE_GET_FLOAT64 (op1) - JS_VALUE_GET_FLOAT64 (op2)); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE (OP_mul) : CASE (OP_mul_float) : { + JSValue op1, op2; + double d; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { + int32_t v1, v2; + int64_t r; + v1 = JS_VALUE_GET_INT (op1); + v2 = JS_VALUE_GET_INT (op2); + r = (int64_t)v1 * v2; + if (unlikely ((int)r != r)) { + d = (double)r; + goto mul_fp_res; + } + /* -0 normalized to 0, no special case needed */ + sp[-2] = JS_NewInt32 (ctx, r); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2)) { + d = JS_VALUE_GET_FLOAT64 (op1) * JS_VALUE_GET_FLOAT64 (op2); + mul_fp_res: + sp[-2] = __JS_NewFloat64 (ctx, d); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE (OP_div) : CASE (OP_div_float) : { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { + int v1, v2; + v1 = JS_VALUE_GET_INT (op1); + v2 = JS_VALUE_GET_INT (op2); + sp[-2] = JS_NewFloat64 (ctx, (double)v1 / (double)v2); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE (OP_mod) : { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { + int v1, v2, r; + v1 = JS_VALUE_GET_INT (op1); + v2 = JS_VALUE_GET_INT (op2); + /* We must avoid v2 = 0, v1 = INT32_MIN and v2 = + -1 and the cases where the result is -0. */ + if (unlikely (v1 < 0 || v2 <= 0)) goto binary_arith_slow; + r = v1 % v2; + sp[-2] = JS_NewInt32 (ctx, r); + sp--; + } else { + goto binary_arith_slow; + } + } + BREAK; + CASE (OP_pow) : binary_arith_slow : sf->cur_pc = pc; + if (js_binary_arith_slow (ctx, sp, opcode)) goto exception; + sp--; + BREAK; + + CASE (OP_plus) : { + JSValue op1; + uint32_t tag; + op1 = sp[-1]; + tag = JS_VALUE_GET_TAG (op1); + if (tag == JS_TAG_INT || JS_TAG_IS_FLOAT64 (tag)) { + } else { + sf->cur_pc = pc; + if (js_unary_arith_slow (ctx, sp, opcode)) goto exception; + } + } + BREAK; + CASE (OP_neg) : { + JSValue op1; + uint32_t tag; + int val; + double d; + op1 = sp[-1]; + tag = JS_VALUE_GET_TAG (op1); + if (tag == JS_TAG_INT) { + val = JS_VALUE_GET_INT (op1); + /* -0 normalized to 0, val==0 just stays 0 */ + if (unlikely (val == INT32_MIN)) { + d = -(double)val; + goto neg_fp_res; + } + sp[-1] = JS_NewInt32 (ctx, -val); + } else if (JS_TAG_IS_FLOAT64 (tag)) { + d = -JS_VALUE_GET_FLOAT64 (op1); + neg_fp_res: + sp[-1] = __JS_NewFloat64 (ctx, d); + } else { + sf->cur_pc = pc; + if (js_unary_arith_slow (ctx, sp, opcode)) goto exception; + } + } + BREAK; + CASE (OP_inc) : { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG (op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT (op1); + if (unlikely (val == INT32_MAX)) goto inc_slow; + sp[-1] = JS_NewInt32 (ctx, val + 1); + } else { + inc_slow: + sf->cur_pc = pc; + if (js_unary_arith_slow (ctx, sp, opcode)) goto exception; + } + } + BREAK; + CASE (OP_dec) : { + JSValue op1; + int val; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG (op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT (op1); + if (unlikely (val == INT32_MIN)) goto dec_slow; + sp[-1] = JS_NewInt32 (ctx, val - 1); + } else { + dec_slow: + sf->cur_pc = pc; + if (js_unary_arith_slow (ctx, sp, opcode)) goto exception; + } + } + BREAK; + CASE (OP_post_inc) : CASE (OP_post_dec) : sf->cur_pc = pc; + if (js_post_inc_slow (ctx, sp, opcode)) goto exception; + sp++; + BREAK; + CASE (OP_inc_loc) : { + JSValue op1; + int val; + int idx; + idx = *pc; + pc += 1; + + op1 = var_buf[idx]; + if (JS_VALUE_GET_TAG (op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT (op1); + if (unlikely (val == INT32_MAX)) goto inc_loc_slow; + var_buf[idx] = JS_NewInt32 (ctx, val + 1); + } else { + inc_loc_slow: + sf->cur_pc = pc; + /* op1 is read from var_buf, passed by reference to js_unary_arith_slow */ + if (js_unary_arith_slow (ctx, &op1 + 1, OP_inc)) goto exception; + set_value (ctx, &var_buf[idx], op1); + } + } + BREAK; + CASE (OP_dec_loc) : { + JSValue op1; + int val; + int idx; + idx = *pc; + pc += 1; + + op1 = var_buf[idx]; + if (JS_VALUE_GET_TAG (op1) == JS_TAG_INT) { + val = JS_VALUE_GET_INT (op1); + if (unlikely (val == INT32_MIN)) goto dec_loc_slow; + var_buf[idx] = JS_NewInt32 (ctx, val - 1); + } else { + dec_loc_slow: + sf->cur_pc = pc; + /* op1 is read from var_buf, passed by reference to js_unary_arith_slow */ + if (js_unary_arith_slow (ctx, &op1 + 1, OP_dec)) goto exception; + set_value (ctx, &var_buf[idx], op1); + } + } + BREAK; + CASE (OP_not) : { + JSValue op1; + op1 = sp[-1]; + if (JS_VALUE_GET_TAG (op1) == JS_TAG_INT) { + sp[-1] = JS_NewInt32 (ctx, ~JS_VALUE_GET_INT (op1)); + } else { + sf->cur_pc = pc; + if (js_not_slow (ctx, sp)) goto exception; + } + } + BREAK; + + CASE (OP_shl) : { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { + uint32_t v1, v2; + v1 = JS_VALUE_GET_INT (op1); + v2 = JS_VALUE_GET_INT (op2); + v2 &= 0x1f; + sp[-2] = JS_NewInt32 (ctx, v1 << v2); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2) + || (JS_VALUE_GET_TAG (op1) == JS_TAG_INT + && JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op2))) + || (JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op1)) + && JS_VALUE_GET_TAG (op2) == JS_TAG_INT)) { + uint32_t v1, v2; + v1 = JS_VALUE_GET_TAG (op1) == JS_TAG_INT + ? JS_VALUE_GET_INT (op1) + : (int32_t)JS_VALUE_GET_FLOAT64 (op1); + v2 = JS_VALUE_GET_TAG (op2) == JS_TAG_INT + ? JS_VALUE_GET_INT (op2) + : (int32_t)JS_VALUE_GET_FLOAT64 (op2); + v2 &= 0x1f; + sp[-2] = JS_NewInt32 (ctx, v1 << v2); + sp--; + } else { + sp[-2] = JS_NULL; + sp--; + } + } + BREAK; + CASE (OP_shr) : { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { + uint32_t v2; + v2 = JS_VALUE_GET_INT (op2); + v2 &= 0x1f; + sp[-2] = JS_NewUint32 (ctx, (uint32_t)JS_VALUE_GET_INT (op1) >> v2); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2) + || (JS_VALUE_GET_TAG (op1) == JS_TAG_INT + && JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op2))) + || (JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op1)) + && JS_VALUE_GET_TAG (op2) == JS_TAG_INT)) { + uint32_t v1, v2; + v1 = JS_VALUE_GET_TAG (op1) == JS_TAG_INT + ? JS_VALUE_GET_INT (op1) + : (int32_t)JS_VALUE_GET_FLOAT64 (op1); + v2 = JS_VALUE_GET_TAG (op2) == JS_TAG_INT + ? JS_VALUE_GET_INT (op2) + : (int32_t)JS_VALUE_GET_FLOAT64 (op2); + v2 &= 0x1f; + sp[-2] = JS_NewUint32 (ctx, v1 >> v2); + sp--; + } else { + sp[-2] = JS_NULL; + sp--; + } + } + BREAK; + CASE (OP_sar) : { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { + uint32_t v2; + v2 = JS_VALUE_GET_INT (op2); + v2 &= 0x1f; + sp[-2] = JS_NewInt32 (ctx, (int)JS_VALUE_GET_INT (op1) >> v2); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2) + || (JS_VALUE_GET_TAG (op1) == JS_TAG_INT + && JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op2))) + || (JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op1)) + && JS_VALUE_GET_TAG (op2) == JS_TAG_INT)) { + int32_t v1; + uint32_t v2; + v1 = JS_VALUE_GET_TAG (op1) == JS_TAG_INT + ? JS_VALUE_GET_INT (op1) + : (int32_t)JS_VALUE_GET_FLOAT64 (op1); + v2 = JS_VALUE_GET_TAG (op2) == JS_TAG_INT + ? JS_VALUE_GET_INT (op2) + : (int32_t)JS_VALUE_GET_FLOAT64 (op2); + v2 &= 0x1f; + sp[-2] = JS_NewInt32 (ctx, v1 >> v2); + sp--; + } else { + sp[-2] = JS_NULL; + sp--; + } + } + BREAK; + CASE (OP_and) : { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { + sp[-2] = JS_NewInt32 (ctx, JS_VALUE_GET_INT (op1) & JS_VALUE_GET_INT (op2)); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2) + || (JS_VALUE_GET_TAG (op1) == JS_TAG_INT + && JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op2))) + || (JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op1)) + && JS_VALUE_GET_TAG (op2) == JS_TAG_INT)) { + int32_t v1, v2; + v1 = JS_VALUE_GET_TAG (op1) == JS_TAG_INT + ? JS_VALUE_GET_INT (op1) + : (int32_t)JS_VALUE_GET_FLOAT64 (op1); + v2 = JS_VALUE_GET_TAG (op2) == JS_TAG_INT + ? JS_VALUE_GET_INT (op2) + : (int32_t)JS_VALUE_GET_FLOAT64 (op2); + sp[-2] = JS_NewInt32 (ctx, v1 & v2); + sp--; + } else { + sp[-2] = JS_NULL; + sp--; + } + } + BREAK; + CASE (OP_or) : { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { + sp[-2] = JS_NewInt32 (ctx, JS_VALUE_GET_INT (op1) | JS_VALUE_GET_INT (op2)); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2) + || (JS_VALUE_GET_TAG (op1) == JS_TAG_INT + && JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op2))) + || (JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op1)) + && JS_VALUE_GET_TAG (op2) == JS_TAG_INT)) { + int32_t v1, v2; + v1 = JS_VALUE_GET_TAG (op1) == JS_TAG_INT + ? JS_VALUE_GET_INT (op1) + : (int32_t)JS_VALUE_GET_FLOAT64 (op1); + v2 = JS_VALUE_GET_TAG (op2) == JS_TAG_INT + ? JS_VALUE_GET_INT (op2) + : (int32_t)JS_VALUE_GET_FLOAT64 (op2); + sp[-2] = JS_NewInt32 (ctx, v1 | v2); + sp--; + } else { + sp[-2] = JS_NULL; + sp--; + } + } + BREAK; + CASE (OP_xor) : { + JSValue op1, op2; + op1 = sp[-2]; + op2 = sp[-1]; + if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { + sp[-2] = JS_NewInt32 (ctx, JS_VALUE_GET_INT (op1) ^ JS_VALUE_GET_INT (op2)); + sp--; + } else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2) + || (JS_VALUE_GET_TAG (op1) == JS_TAG_INT + && JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op2))) + || (JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op1)) + && JS_VALUE_GET_TAG (op2) == JS_TAG_INT)) { + int32_t v1, v2; + v1 = JS_VALUE_GET_TAG (op1) == JS_TAG_INT + ? JS_VALUE_GET_INT (op1) + : (int32_t)JS_VALUE_GET_FLOAT64 (op1); + v2 = JS_VALUE_GET_TAG (op2) == JS_TAG_INT + ? JS_VALUE_GET_INT (op2) + : (int32_t)JS_VALUE_GET_FLOAT64 (op2); + sp[-2] = JS_NewInt32 (ctx, v1 ^ v2); + sp--; + } else { + sp[-2] = JS_NULL; + sp--; + } + } + BREAK; + +#define OP_CMP(opcode, binary_op, slow_call) \ + CASE (opcode) : { \ + JSValue op1, op2; \ + op1 = sp[-2]; \ + op2 = sp[-1]; \ + if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { \ + sp[-2] = JS_NewBool (ctx, JS_VALUE_GET_INT (op1) binary_op JS_VALUE_GET_INT (op2)); \ + sp--; \ + } else { \ + sf->cur_pc = pc; \ + if (slow_call) goto exception; \ + sp--; \ + } \ + } \ + BREAK + + OP_CMP (OP_lt, <, js_relational_slow (ctx, sp, opcode)); + OP_CMP (OP_lte, <=, js_relational_slow (ctx, sp, opcode)); + OP_CMP (OP_gt, >, js_relational_slow (ctx, sp, opcode)); + OP_CMP (OP_gte, >=, js_relational_slow (ctx, sp, opcode)); + OP_CMP (OP_strict_eq, ==, js_strict_eq_slow (ctx, sp, 0)); + OP_CMP (OP_strict_neq, !=, js_strict_eq_slow (ctx, sp, 1)); + + CASE (OP_in) : sf->cur_pc = pc; + if (js_operator_in (ctx, sp)) goto exception; + sp--; + BREAK; + CASE (OP_delete) : sf->cur_pc = pc; + if (js_operator_delete (ctx, sp)) goto exception; + sp--; + BREAK; + + CASE (OP_delete_var) : + pc += 4; + JS_ThrowInternalError (ctx, "OP_delete_var: global object is immutable"); + goto exception; + BREAK; + + CASE (OP_to_propkey) : switch (JS_VALUE_GET_TAG (sp[-1])) { + case JS_TAG_INT: + case JS_TAG_STRING: + break; + default: + sf->cur_pc = pc; + ret_val = sp[-1]; // JS_ToPropertyKey (ctx, sp[-1]); // is this correct? No conversion can happen. + if (JS_IsException (ret_val)) goto exception; + sp[-1] = ret_val; + break; + } + BREAK; + + CASE (OP_format_template) : { + int expr_count = get_u16 (pc); pc += 2; + uint32_t cpool_idx = get_u32 (pc); pc += 4; + + /* Expression values are on the stack. We'll process them in place, + building the result string using pretext. */ + JSText *result = pretext_init (ctx, 64); + if (!result) goto exception; + + /* Re-read format_str after pretext_init (may have triggered GC) */ + JSValue format_str = b->cpool[cpool_idx]; + + /* Parse format string and substitute {N} with stringified stack values. + Format string is like "hello {0} world {1}" for `hello ${a} world ${b}`. + Each {N} refers to stack value at position N (0-indexed from base). */ + JSValue *expr_base = sp - expr_count; + int fmt_len = js_string_value_len (format_str); + int pos = 0; + + while (pos < fmt_len) { + /* Re-read format_str in case GC moved the cpool entry */ + format_str = b->cpool[cpool_idx]; + + /* Find next '{' */ + int brace_start = -1; + for (int i = pos; i < fmt_len; i++) { + if (js_string_value_get (format_str, i) == '{') { + brace_start = i; + break; + } + } + + if (brace_start < 0) { + /* No more braces, copy rest of string */ + format_str = b->cpool[cpool_idx]; /* Re-read */ + for (int i = pos; i < fmt_len; i++) { + result = pretext_putc (ctx, result, js_string_value_get (format_str, i)); + if (!result) goto exception; + } + break; + } + + /* Copy text before brace */ + format_str = b->cpool[cpool_idx]; /* Re-read */ + for (int i = pos; i < brace_start; i++) { + result = pretext_putc (ctx, result, js_string_value_get (format_str, i)); + if (!result) goto exception; + } + + /* Find closing '}' */ + format_str = b->cpool[cpool_idx]; /* Re-read */ + int brace_end = -1; + for (int i = brace_start + 1; i < fmt_len; i++) { + if (js_string_value_get (format_str, i) == '}') { + brace_end = i; + break; + } + } + + if (brace_end < 0) { + /* No closing brace, copy '{' and continue */ + result = pretext_putc (ctx, result, '{'); + if (!result) goto exception; + pos = brace_start + 1; + continue; + } + + /* Parse index from {N} */ + int idx = 0; + format_str = b->cpool[cpool_idx]; /* Re-read */ + for (int i = brace_start + 1; i < brace_end; i++) { + uint32_t c = js_string_value_get (format_str, i); + if (c >= '0' && c <= '9') { + idx = idx * 10 + (c - '0'); + } else { + idx = -1; /* Invalid */ + break; + } + } + + if (idx >= 0 && idx < expr_count) { + /* Valid index, stringify the stack value */ + JSValue val = expr_base[idx]; + JSValue str_val = JS_ToString (ctx, val); + if (JS_IsException (str_val)) { + goto exception; + } + /* Append stringified value to result */ + int str_len = js_string_value_len (str_val); + for (int i = 0; i < str_len; i++) { + result = pretext_putc (ctx, result, js_string_value_get (str_val, i)); + if (!result) goto exception; + } + } else { + /* Invalid index, keep original {N} */ + format_str = b->cpool[cpool_idx]; /* Re-read */ + for (int i = brace_start; i <= brace_end; i++) { + result = pretext_putc (ctx, result, js_string_value_get (format_str, i)); + if (!result) goto exception; + } + } + + pos = brace_end + 1; + } + + /* Finalize result string */ + JSValue result_str = pretext_end (ctx, result); + if (JS_IsException (result_str)) goto exception; + + /* Pop expr values, push result */ + sp -= expr_count; + *sp++ = result_str; + } + BREAK; + + /* Upvalue access via outer_frame chain (Phase 5) */ + CASE (OP_get_up) : { + int depth = *pc++; + int slot = get_u16 (pc); pc += 2; + JSValue *p = get_upvalue_ptr(sf->js_frame, depth, slot); + if (!p) { + JS_ThrowInternalError(ctx, "invalid upvalue: depth=%d slot=%d", depth, slot); + goto exception; + } + *sp++ = *p; + } + BREAK; + + CASE (OP_set_up) : { + int depth = *pc++; + int slot = get_u16 (pc); pc += 2; + JSValue *p = get_upvalue_ptr(sf->js_frame, depth, slot); + if (!p) { + JS_ThrowInternalError(ctx, "invalid upvalue: depth=%d slot=%d", depth, slot); + goto exception; + } + *p = *--sp; + } + BREAK; + + /* Name resolution with bytecode patching (Phase 6) */ + CASE (OP_get_name) : { + uint32_t idx = get_u32 (pc); pc += 4; + (void)idx; + JS_ThrowInternalError (ctx, "OP_get_name not yet implemented"); + goto exception; + } + BREAK; + + CASE (OP_get_env_slot) : { + int slot = get_u16 (pc); pc += 2; + /* Get env_record from current function, not global ctx->eval_env */ + JSFunction *fn = JS_VALUE_GET_FUNCTION (sf->cur_func); + JSValue env_val = fn->u.func.env_record; + if (JS_IsNull (env_val)) { + JS_ThrowReferenceError (ctx, "no environment record"); + goto exception; + } + JSRecord *env = (JSRecord *)JS_VALUE_GET_OBJ (env_val); + *sp++ = env->slots[slot].val; + } + BREAK; + + CASE (OP_set_env_slot) : { + int slot = get_u16 (pc); pc += 2; + /* Get env_record from current function */ + JSFunction *fn = JS_VALUE_GET_FUNCTION (sf->cur_func); + JSValue env_val = fn->u.func.env_record; + if (JS_IsNull (env_val)) { + JS_ThrowReferenceError (ctx, "no environment record"); + goto exception; + } + JSRecord *env = (JSRecord *)JS_VALUE_GET_OBJ (env_val); + env->slots[slot].val = *--sp; + } + BREAK; + + CASE (OP_get_global_slot) : { + int slot = get_u16 (pc); pc += 2; + JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); + *sp++ = global->slots[slot].val; + } + BREAK; + + CASE (OP_set_global_slot) : { + int slot = get_u16 (pc); pc += 2; + JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); + global->slots[slot].val = *--sp; + } + BREAK; + + CASE (OP_nop) : BREAK; + CASE (OP_is_null) : if (JS_VALUE_GET_TAG (sp[-1]) == JS_TAG_NULL) { + goto set_true; + } + else { goto free_and_set_false; } + set_true: + sp[-1] = JS_TRUE; + BREAK; + free_and_set_false: + sp[-1] = JS_FALSE; + BREAK; + CASE (OP_invalid) + : DEFAULT + : JS_ThrowInternalError (ctx, "invalid opcode: pc=%u opcode=0x%02x", (int)(pc - b->byte_code_buf - 1), opcode); + goto exception; + } + } +exception: + if (is_backtrace_needed (ctx, ctx->current_exception)) { + /* add the backtrace information now (it is not done + before if the exception happens in a bytecode + operation */ + sf->cur_pc = pc; + build_backtrace (ctx, ctx->current_exception, NULL, 0, 0, 0); + } + /* All exceptions are catchable in the simplified runtime */ + while (sp > stack_buf) { + JSValue val = *--sp; + if (JS_VALUE_GET_TAG (val) == JS_TAG_CATCH_OFFSET) { + int pos = JS_VALUE_GET_INT (val); + if (pos != 0) { + *sp++ = ctx->current_exception; + ctx->current_exception = JS_UNINITIALIZED; + pc = b->byte_code_buf + pos; + goto restart; + } + } + } + ret_val = JS_EXCEPTION; +done: + ctx->current_stack_frame = sf->prev_frame; + + if (unlikely (caller_ctx->trace_hook) + && (caller_ctx->trace_type & JS_HOOK_RET)) + caller_ctx->trace_hook (caller_ctx, JS_HOOK_RET, NULL, caller_ctx->trace_data); + + return ret_val; +} + +/* JS parser */ + + +/* Map token values to kind strings for tokenizer output */ + +typedef struct BlockEnv { + struct BlockEnv *prev; + JSValue label_name; /* JS_NULL if none */ + int label_break; /* -1 if none */ + int label_cont; /* -1 if none */ + int drop_count; /* number of stack elements to drop */ + int label_finally; /* -1 if none */ + int scope_level; + uint8_t is_regular_stmt : 1; /* i.e. not a loop statement */ +} BlockEnv; + +typedef struct JSGlobalVar { + int cpool_idx; /* if >= 0, index in the constant pool for hoisted + function defintion*/ + uint8_t force_init : 1; /* force initialization to undefined */ + uint8_t is_lexical : 1; /* global let/const definition */ + uint8_t is_const : 1; /* const definition */ + int scope_level; /* scope of definition */ + JSValue var_name; /* variable name as JSValue text */ +} JSGlobalVar; + +typedef struct RelocEntry { + struct RelocEntry *next; + uint32_t addr; /* address to patch */ + int size; /* address size: 1, 2 or 4 bytes */ +} RelocEntry; + +typedef struct JumpSlot { + int op; + int size; + int pos; + int label; +} JumpSlot; + +typedef struct LabelSlot { + int ref_count; + int pos; /* phase 1 address, -1 means not resolved yet */ + int pos2; /* phase 2 address, -1 means not resolved yet */ + int addr; /* phase 3 address, -1 means not resolved yet */ + RelocEntry *first_reloc; +} LabelSlot; + +typedef struct LineNumberSlot { + uint32_t pc; + uint32_t source_pos; +} LineNumberSlot; + + +typedef enum JSParseFunctionEnum { + JS_PARSE_FUNC_STATEMENT, + JS_PARSE_FUNC_VAR, + JS_PARSE_FUNC_EXPR, + JS_PARSE_FUNC_ARROW, + JS_PARSE_FUNC_GETTER, + JS_PARSE_FUNC_SETTER, + JS_PARSE_FUNC_METHOD, + JS_PARSE_FUNC_CLASS_STATIC_INIT, +} JSParseFunctionEnum; + +typedef struct JSFunctionDef { + JSContext *ctx; + struct JSFunctionDef *parent; + int parent_cpool_idx; /* index in the constant pool of the parent + or -1 if none */ + int parent_scope_level; /* scope level in parent at point of definition */ + struct list_head child_list; /* list of JSFunctionDef.link */ + struct list_head link; + + BOOL is_global_var; /* TRUE if variables are not defined locally */ + BOOL is_func_expr; /* TRUE if function expression */ + BOOL has_prototype; /* true if a prototype field is necessary */ + BOOL has_simple_parameter_list; + BOOL has_parameter_expressions; /* if true, an argument scope is created */ + BOOL has_use_strict; /* to reject directive in special cases */ + BOOL has_this_binding; /* true if the 'this' binding is available in the + function */ + BOOL in_function_body; + JSParseFunctionEnum func_type : 8; + uint8_t js_mode; /* bitmap of JS_MODE_x */ + JSValue func_name; /* JS_NULL if no name */ + + JSVarDef *vars; + int var_size; /* allocated size for vars[] */ + int var_count; + JSVarDef *args; + int arg_size; /* allocated size for args[] */ + int arg_count; /* number of arguments */ + int defined_arg_count; + int var_object_idx; /* -1 if none */ + int arg_var_object_idx; /* -1 if none (var object for the argument scope) */ + int func_var_idx; /* variable containing the current function (-1 + if none, only used if is_func_expr is true) */ + int this_var_idx; /* variable containg the 'this' value, -1 if none */ + int this_active_func_var_idx; /* variable containg the 'this.active_func' + value, -1 if none */ + + int scope_level; /* index into fd->scopes if the current lexical scope */ + int scope_first; /* index into vd->vars of first lexically scoped variable */ + int scope_size; /* allocated size of fd->scopes array */ + int scope_count; /* number of entries used in the fd->scopes array */ + JSVarScope *scopes; + JSVarScope def_scope_array[4]; + int body_scope; /* scope of the body of the function or eval */ + + int global_var_count; + int global_var_size; + JSGlobalVar *global_vars; + + DynBuf byte_code; + int last_opcode_pos; /* -1 if no last opcode */ + const uint8_t *last_opcode_source_ptr; + BOOL use_short_opcodes; /* true if short opcodes are used in byte_code */ + + LabelSlot *label_slots; + int label_size; /* allocated size for label_slots[] */ + int label_count; + BlockEnv *top_break; /* break/continue label stack */ + + /* constant pool (strings, functions, numbers) */ + JSValue *cpool; + int cpool_count; + int cpool_size; + + /* list of variables in the closure */ + int closure_var_count; + int closure_var_size; + JSClosureVar *closure_var; + + JumpSlot *jump_slots; + int jump_size; + int jump_count; + + LineNumberSlot *line_number_slots; + int line_number_size; + int line_number_count; + int line_number_last; + int line_number_last_pc; + + /* pc2line table */ + BOOL strip_debug + : 1; /* strip all debug info (implies strip_source = TRUE) */ + BOOL strip_source : 1; /* strip only source code */ + JSValue filename; + uint32_t source_pos; /* pointer in the eval() source */ + GetLineColCache *get_line_col_cache; /* XXX: could remove to save memory */ + DynBuf pc2line; + + char *source; /* raw source, utf-8 encoded */ + int source_len; +} JSFunctionDef; + +/* Scan a single JSFunctionDef's cpool and recursively scan its children */ +void gc_scan_parser_fd (JSContext *ctx, JSFunctionDef *fd, + uint8_t *from_base, uint8_t *from_end, + uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) { + if (!fd) return; + /* Scan constant pool */ + for (int i = 0; i < fd->cpool_count; i++) { + fd->cpool[i] = gc_copy_value (ctx, fd->cpool[i], from_base, from_end, to_base, to_free, to_end); + } + /* Scan func_name */ + fd->func_name = gc_copy_value (ctx, fd->func_name, from_base, from_end, to_base, to_free, to_end); + /* Scan filename */ + fd->filename = gc_copy_value (ctx, fd->filename, from_base, from_end, to_base, to_free, to_end); + /* Recursively scan child functions */ + struct list_head *el; + list_for_each (el, &fd->child_list) { + JSFunctionDef *child = list_entry (el, JSFunctionDef, link); + gc_scan_parser_fd (ctx, child, from_base, from_end, to_base, to_free, to_end); + } +} + +/* Scan parser's cpool values during GC - called when parsing is in progress */ +void gc_scan_parser_cpool (JSContext *ctx, uint8_t *from_base, uint8_t *from_end, + uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) { + gc_scan_parser_fd (ctx, ctx->current_parse_fd, from_base, from_end, to_base, to_free, to_end); +} + +typedef struct JSToken { + int val; + const uint8_t *ptr; /* position in the source */ + union { + struct { + JSValue str; + int sep; + } str; + struct { + JSValue val; + } num; + struct { + JSValue str; /* identifier as JSValue text */ + BOOL has_escape; + BOOL is_reserved; + } ident; + struct { + JSValue body; + JSValue flags; + } regexp; + } u; +} JSToken; + +typedef struct JSParseState { + JSContext *ctx; + const char *filename; + JSToken token; + BOOL got_lf; /* true if got line feed before the current token */ + const uint8_t *last_ptr; + const uint8_t *buf_start; + const uint8_t *buf_ptr; + const uint8_t *buf_end; + + /* current function code */ + JSFunctionDef *cur_func; + BOOL ext_json; /* true if accepting JSON superset */ + GetLineColCache get_line_col_cache; +} JSParseState; + + +/* Clone bytecode and resolve OP_get_var to OP_get_global_slot. + Returns new bytecode on success, NULL on link error. + The linked bytecode is a separate allocation that can be modified. + Note: closure_var is not copied - closures use outer_frame chain at runtime. */ +JSFunctionBytecode *js_link_bytecode (JSContext *ctx, + JSFunctionBytecode *tpl, + JSValue env) { + /* Calculate total size of bytecode allocation */ + int function_size; + int cpool_offset, vardefs_offset, byte_code_offset; + + if (tpl->has_debug) { + function_size = sizeof (JSFunctionBytecode); + } else { + function_size = offsetof (JSFunctionBytecode, debug); + } + + cpool_offset = function_size; + function_size += tpl->cpool_count * sizeof (JSValue); + + vardefs_offset = function_size; + if (tpl->vardefs) { + function_size += (tpl->arg_count + tpl->var_count) * sizeof (JSVarDef); + } + + /* closure_var not needed at runtime - closures use outer_frame chain */ + + byte_code_offset = function_size; + function_size += tpl->byte_code_len; + + /* Allocate and copy */ + JSFunctionBytecode *linked = pjs_malloc (function_size); + if (!linked) return NULL; + + /* Copy base structure */ + if (tpl->has_debug) { + memcpy (linked, tpl, sizeof (JSFunctionBytecode)); + } else { + memcpy (linked, tpl, offsetof (JSFunctionBytecode, debug)); + } + + /* Clear closure_var - not needed at runtime */ + linked->closure_var = NULL; + linked->closure_var_count = 0; + + /* Fix up self pointers */ + if (tpl->cpool_count > 0) { + linked->cpool = (JSValue *)((uint8_t *)linked + cpool_offset); + memcpy (linked->cpool, tpl->cpool, tpl->cpool_count * sizeof (JSValue)); + } + + if (tpl->vardefs) { + linked->vardefs = (JSVarDef *)((uint8_t *)linked + vardefs_offset); + memcpy (linked->vardefs, tpl->vardefs, + (tpl->arg_count + tpl->var_count) * sizeof (JSVarDef)); + } + + linked->byte_code_buf = (uint8_t *)linked + byte_code_offset; + memcpy (linked->byte_code_buf, tpl->byte_code_buf, tpl->byte_code_len); + + /* Mark as writable (for patching) */ + linked->read_only_bytecode = 0; + + /* Walk bytecode and patch global variable access opcodes */ + uint8_t *bc = linked->byte_code_buf; + int pos = 0; + + /* Get env record if provided */ + JSRecord *env_rec = NULL; + if (!JS_IsNull (env) && JS_IsRecord (env)) { + env_rec = (JSRecord *)JS_VALUE_GET_OBJ (env); + } + + while (pos < linked->byte_code_len) { + uint8_t op = bc[pos]; + int len = short_opcode_info (op).size; + + /* Patch OP_get_var -> OP_get_global_slot or OP_get_env_slot */ + if (op == OP_get_var || op == OP_get_var_undef) { + uint32_t cpool_idx = get_u32 (bc + pos + 1); + JSValue name = linked->cpool[cpool_idx]; + + /* Try env first (if provided) */ + if (env_rec) { + int slot = rec_find_slot (env_rec, name); + if (slot > 0) { + bc[pos] = OP_get_env_slot; + put_u16 (bc + pos + 1, (uint16_t)slot); + bc[pos + 3] = OP_nop; + bc[pos + 4] = OP_nop; + pos += len; + continue; + } + } + + /* Try global_obj (intrinsics like 'print') */ + JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); + int slot = rec_find_slot (global, name); + if (slot > 0) { + bc[pos] = OP_get_global_slot; + put_u16 (bc + pos + 1, (uint16_t)slot); + bc[pos + 3] = OP_nop; + bc[pos + 4] = OP_nop; + pos += len; + continue; + } + + /* Link error: variable not found */ + if (op == OP_get_var) { + char buf[64]; + JS_ThrowReferenceError (ctx, "'%s' is not defined", + JS_KeyGetStr (ctx, buf, sizeof (buf), name)); + pjs_free (linked); + return NULL; + } + /* OP_get_var_undef is ok - leaves as is for runtime check */ + } + + /* Patch OP_put_var family -> OP_set_env_slot or error */ + if (op == OP_put_var || op == OP_put_var_init || op == OP_put_var_strict) { + uint32_t cpool_idx = get_u32 (bc + pos + 1); + JSValue name = linked->cpool[cpool_idx]; + + /* Try env first (if provided) - env is writable */ + if (env_rec) { + int slot = rec_find_slot (env_rec, name); + if (slot > 0) { + bc[pos] = OP_set_env_slot; + put_u16 (bc + pos + 1, (uint16_t)slot); + bc[pos + 3] = OP_nop; + bc[pos + 4] = OP_nop; + pos += len; + continue; + } + } + + /* Try global for set_global_slot */ + JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); + int slot = rec_find_slot (global, name); + if (slot > 0) { + bc[pos] = OP_set_global_slot; + put_u16 (bc + pos + 1, (uint16_t)slot); + bc[pos + 3] = OP_nop; + bc[pos + 4] = OP_nop; + pos += len; + continue; + } + + /* Global object is immutable - can't write to intrinsics */ + char buf[64]; + JS_ThrowReferenceError (ctx, "cannot assign to '%s' - not found in environment", + JS_KeyGetStr (ctx, buf, sizeof (buf), name)); + pjs_free (linked); + return NULL; + } + + /* Patch OP_check_var -> OP_nop (if variable exists) */ + if (op == OP_check_var) { + uint32_t cpool_idx = get_u32 (bc + pos + 1); + JSValue name = linked->cpool[cpool_idx]; + + BOOL found = FALSE; + if (env_rec && rec_find_slot (env_rec, name) > 0) { + found = TRUE; + } + if (!found) { + JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); + if (rec_find_slot (global, name) > 0) { + found = TRUE; + } + } + + if (found) { + /* Variable exists, replace with NOPs */ + bc[pos] = OP_nop; + bc[pos + 1] = OP_nop; + bc[pos + 2] = OP_nop; + bc[pos + 3] = OP_nop; + bc[pos + 4] = OP_nop; + } + /* else leave check_var for runtime */ + } + + pos += len; + } + + return linked; +} + +/* New simplified linker producing JSCompiledUnit. + Converts JSFunctionBytecode template to JSCompiledUnit: + - Copies bytecode, cpool (no vardefs, no closure_var) + - Patches OP_get_var -> OP_get_env_slot or OP_get_global_slot + - Returns standalone unit ready for execution */ +static JSCompiledUnit *js_link_unit (JSContext *ctx, + JSFunctionBytecode *tpl, + JSValue env) { + int function_size; + int cpool_offset, byte_code_offset; + + /* Calculate size: base struct + cpool + bytecode */ + if (tpl->has_debug) { + function_size = sizeof (JSCompiledUnit); + } else { + function_size = offsetof (JSCompiledUnit, debug); + } + + cpool_offset = function_size; + function_size += tpl->cpool_count * sizeof (JSValue); + + byte_code_offset = function_size; + function_size += tpl->byte_code_len; + + /* Allocate */ + JSCompiledUnit *unit = pjs_malloc (function_size); + if (!unit) return NULL; + + /* Initialize header */ + unit->header = objhdr_make (0, OBJ_CODE, false, false, false, false); + unit->has_debug = tpl->has_debug; + unit->read_only_bytecode = 0; + + /* Copy stack requirements */ + unit->local_count = tpl->arg_count + tpl->var_count; + unit->stack_size = tpl->stack_size; + + /* Setup cpool */ + unit->cpool_count = tpl->cpool_count; + if (tpl->cpool_count > 0) { + unit->cpool = (JSValue *)((uint8_t *)unit + cpool_offset); + memcpy (unit->cpool, tpl->cpool, tpl->cpool_count * sizeof (JSValue)); + } else { + unit->cpool = NULL; + } + + /* Copy bytecode */ + unit->byte_code_buf = (uint8_t *)unit + byte_code_offset; + unit->byte_code_len = tpl->byte_code_len; + memcpy (unit->byte_code_buf, tpl->byte_code_buf, tpl->byte_code_len); + + /* Copy debug info if present */ + if (tpl->has_debug) { + unit->debug.filename = tpl->debug.filename; + unit->debug.source_len = tpl->debug.source_len; + unit->debug.pc2line_len = tpl->debug.pc2line_len; + unit->debug.pc2line_buf = tpl->debug.pc2line_buf; + unit->debug.source = tpl->debug.source; + } + + /* Walk bytecode and patch global variable access opcodes */ + uint8_t *bc = unit->byte_code_buf; + int pos = 0; + + /* Get env record if provided */ + JSRecord *env_rec = NULL; + if (!JS_IsNull (env) && JS_IsRecord (env)) { + env_rec = (JSRecord *)JS_VALUE_GET_OBJ (env); + } + + while (pos < unit->byte_code_len) { + uint8_t op = bc[pos]; + int len = short_opcode_info (op).size; + + /* Patch OP_get_var -> OP_get_global_slot or OP_get_env_slot */ + if (op == OP_get_var || op == OP_get_var_undef) { + uint32_t cpool_idx = get_u32 (bc + pos + 1); + JSValue name = unit->cpool[cpool_idx]; + + /* Try env first (if provided) */ + if (env_rec) { + int slot = rec_find_slot (env_rec, name); + if (slot > 0) { + bc[pos] = OP_get_env_slot; + put_u16 (bc + pos + 1, (uint16_t)slot); + bc[pos + 3] = OP_nop; + bc[pos + 4] = OP_nop; + pos += len; + continue; + } + } + + /* Try global_obj (intrinsics like 'print') */ + JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); + int slot = rec_find_slot (global, name); + if (slot > 0) { + bc[pos] = OP_get_global_slot; + put_u16 (bc + pos + 1, (uint16_t)slot); + bc[pos + 3] = OP_nop; + bc[pos + 4] = OP_nop; + pos += len; + continue; + } + + /* Link error: variable not found */ + if (op == OP_get_var) { + char buf[64]; + JS_ThrowReferenceError (ctx, "'%s' is not defined", + JS_KeyGetStr (ctx, buf, sizeof (buf), name)); + pjs_free (unit); + return NULL; + } + } + + /* Patch OP_put_var family -> error (global is immutable) */ + if (op == OP_put_var || op == OP_put_var_init || op == OP_put_var_strict) { + uint32_t cpool_idx = get_u32 (bc + pos + 1); + JSValue name = unit->cpool[cpool_idx]; + char buf[64]; + JS_ThrowReferenceError (ctx, "cannot assign to '%s' - global object is immutable", + JS_KeyGetStr (ctx, buf, sizeof (buf), name)); + pjs_free (unit); + return NULL; + } + + /* Patch OP_check_var -> OP_nop (if variable exists) */ + if (op == OP_check_var) { + uint32_t cpool_idx = get_u32 (bc + pos + 1); + JSValue name = unit->cpool[cpool_idx]; + + BOOL found = FALSE; + if (env_rec && rec_find_slot (env_rec, name) > 0) { + found = TRUE; + } + if (!found) { + JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); + if (rec_find_slot (global, name) > 0) { + found = TRUE; + } + } + + if (found) { + bc[pos] = OP_nop; + bc[pos + 1] = OP_nop; + bc[pos + 2] = OP_nop; + bc[pos + 3] = OP_nop; + bc[pos + 4] = OP_nop; + } + } + + pos += len; + } + + return unit; +} + +static __exception int next_token (JSParseState *s); + +static void free_token (JSParseState *s, JSToken *token) { + switch (token->val) { + case TOK_NUMBER: + break; + case TOK_STRING: + case TOK_TEMPLATE: + break; + case TOK_REGEXP: + break; + case TOK_IDENT: + break; + default: + if (token->val >= TOK_FIRST_KEYWORD && token->val <= TOK_LAST_KEYWORD) { + } + break; + } +} + +static void __attribute ((unused)) dump_token (JSParseState *s, + const JSToken *token) { + switch (token->val) { + case TOK_NUMBER: { + double d; + JS_ToFloat64 (s->ctx, &d, token->u.num.val); /* no exception possible */ + printf ("number: %.14g\n", d); + } break; + case TOK_IDENT: + dump_ident: { + const char *str = JS_ToCString (s->ctx, token->u.ident.str); + printf ("ident: '%s'\n", str ? str : ""); + JS_FreeCString (s->ctx, str); + } break; + case TOK_STRING: { + const char *str; + /* XXX: quote the string */ + str = JS_ToCString (s->ctx, token->u.str.str); + printf ("string: '%s'\n", str); + JS_FreeCString (s->ctx, str); + } break; + case TOK_TEMPLATE: { + const char *str; + str = JS_ToCString (s->ctx, token->u.str.str); + printf ("template: `%s`\n", str); + JS_FreeCString (s->ctx, str); + } break; + case TOK_REGEXP: { + const char *str, *str2; + str = JS_ToCString (s->ctx, token->u.regexp.body); + str2 = JS_ToCString (s->ctx, token->u.regexp.flags); + printf ("regexp: '%s' '%s'\n", str, str2); + JS_FreeCString (s->ctx, str); + JS_FreeCString (s->ctx, str2); + } break; + case TOK_EOF: + printf ("eof\n"); + break; + default: + if (s->token.val >= TOK_NULL && s->token.val <= TOK_LAST_KEYWORD) { + goto dump_ident; + } else if (s->token.val >= 256) { + printf ("token: %d\n", token->val); + } else { + printf ("token: '%c'\n", token->val); + } + break; + } +} + +/* return the zero based line and column number in the source. */ +/* Note: we no longer support '\r' as line terminator */ +int get_line_col (int *pcol_num, const uint8_t *buf, size_t len) { + int line_num, col_num, c; + size_t i; + + line_num = 0; + col_num = 0; + for (i = 0; i < len; i++) { + c = buf[i]; + if (c == '\n') { + line_num++; + col_num = 0; + } else if (c < 0x80 || c >= 0xc0) { + col_num++; + } + } + *pcol_num = col_num; + return line_num; +} + +int get_line_col_cached (GetLineColCache *s, int *pcol_num, const uint8_t *ptr) { + int line_num, col_num; + if (ptr >= s->ptr) { + line_num = get_line_col (&col_num, s->ptr, ptr - s->ptr); + if (line_num == 0) { + s->col_num += col_num; + } else { + s->line_num += line_num; + s->col_num = col_num; + } + } else { + line_num = get_line_col (&col_num, ptr, s->ptr - ptr); + if (line_num == 0) { + s->col_num -= col_num; + } else { + const uint8_t *p; + s->line_num -= line_num; + /* find the absolute column position */ + col_num = 0; + for (p = ptr - 1; p >= s->buf_start; p--) { + if (*p == '\n') { + break; + } else if (*p < 0x80 || *p >= 0xc0) { + col_num++; + } + } + s->col_num = col_num; + } + } + s->ptr = ptr; + *pcol_num = s->col_num; + return s->line_num; +} + +/* 'ptr' is the position of the error in the source */ +static int js_parse_error_v (JSParseState *s, const uint8_t *ptr, const char *fmt, va_list ap) { + JSContext *ctx = s->ctx; + int line_num, col_num; + line_num = get_line_col (&col_num, s->buf_start, ptr - s->buf_start); + JS_ThrowError2 (ctx, JS_SYNTAX_ERROR, fmt, ap, FALSE); + build_backtrace (ctx, ctx->current_exception, s->filename, line_num + 1, col_num + 1, 0); + return -1; +} + +static __attribute__ ((format (printf, 3, 4))) int +js_parse_error_pos (JSParseState *s, const uint8_t *ptr, const char *fmt, ...) { + va_list ap; + int ret; + + va_start (ap, fmt); + ret = js_parse_error_v (s, ptr, fmt, ap); + va_end (ap); + return ret; +} + +static __attribute__ ((format (printf, 2, 3))) int +js_parse_error (JSParseState *s, const char *fmt, ...) { + va_list ap; + int ret; + + va_start (ap, fmt); + ret = js_parse_error_v (s, s->token.ptr, fmt, ap); + va_end (ap); + return ret; +} + +static int js_parse_expect (JSParseState *s, int tok) { + if (s->token.val != tok) { + /* XXX: dump token correctly in all cases */ + return js_parse_error (s, "expecting '%c'", tok); + } + return next_token (s); +} + +static int js_parse_expect_semi (JSParseState *s) { + if (s->token.val != ';') { + /* automatic insertion of ';' */ + if (s->token.val == TOK_EOF || s->token.val == '}' || s->got_lf + || s->token.val == TOK_ELSE) { + return 0; + } + return js_parse_error (s, "expecting '%c'", ';'); + } + return next_token (s); +} + +static int js_parse_error_reserved_identifier (JSParseState *s) { + char buf1[KEY_GET_STR_BUF_SIZE]; + return js_parse_error ( + s, "'%s' is a reserved identifier", JS_KeyGetStr (s->ctx, buf1, sizeof (buf1), s->token.u.ident.str)); +} + +static __exception int js_parse_template_part (JSParseState *s, + const uint8_t *p) { + uint32_t c; + PPretext *b; + JSValue str; + + /* p points to the first byte of the template part */ + b = ppretext_init (32); + if (!b) goto fail; + for (;;) { + if (p >= s->buf_end) goto unexpected_eof; + c = *p++; + if (c == '`') { + /* template end part */ + break; + } + if (c == '$' && *p == '{') { + /* template start or middle part */ + p++; + break; + } + if (c == '\\') { + b = ppretext_putc (b, c); + if (!b) goto fail; + if (p >= s->buf_end) goto unexpected_eof; + c = *p++; + } + /* newline sequences are normalized as single '\n' bytes */ + if (c == '\r') { + if (*p == '\n') p++; + c = '\n'; + } + if (c >= 0x80) { + const uint8_t *p_next; + c = unicode_from_utf8 (p - 1, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) { + js_parse_error_pos (s, p - 1, "invalid UTF-8 sequence"); + goto fail; + } + p = p_next; + } + b = ppretext_putc (b, c); + if (!b) goto fail; + } + str = ppretext_end (s->ctx, b); + if (JS_IsException (str)) return -1; + s->token.val = TOK_TEMPLATE; + s->token.u.str.sep = c; + s->token.u.str.str = str; + s->buf_ptr = p; + return 0; + +unexpected_eof: + js_parse_error (s, "unexpected end of string"); +fail: + ppretext_free (b); + return -1; +} + +static __exception int js_parse_string (JSParseState *s, int sep, BOOL do_throw, const uint8_t *p, JSToken *token, const uint8_t **pp) { + int ret; + uint32_t c; + PPretext *b; + const uint8_t *p_escape; + JSValue str; + + /* string */ + b = ppretext_init (32); + if (!b) goto fail; + for (;;) { + if (p >= s->buf_end) goto invalid_char; + c = *p; + if (c < 0x20) { + if (sep == '`') { + if (c == '\r') { + if (p[1] == '\n') p++; + c = '\n'; + } + /* do not update s->line_num */ + } else if (c == '\n' || c == '\r') + goto invalid_char; + } + p++; + if (c == sep) break; + if (c == '$' && *p == '{' && sep == '`') { + /* template start or middle part */ + p++; + break; + } + if (c == '\\') { + p_escape = p - 1; + c = *p; + /* XXX: need a specific JSON case to avoid + accepting invalid escapes */ + switch (c) { + case '\0': + if (p >= s->buf_end) goto invalid_char; + p++; + break; + case '\'': + case '\"': + case '\\': + p++; + break; + case '\r': /* accept DOS and MAC newline sequences */ + if (p[1] == '\n') { p++; } + /* fall thru */ + case '\n': + /* ignore escaped newline sequence */ + p++; + continue; + default: + if (c >= '0' && c <= '9') { + if (c == '0' && !(p[1] >= '0' && p[1] <= '9')) { + p++; + c = '\0'; + } else { + if (c >= '8' || sep == '`') { + /* Note: according to ES2021, \8 and \9 are not + accepted in strict mode or in templates. */ + goto invalid_escape; + } else { + if (do_throw) + js_parse_error_pos ( + s, p_escape, "octal escape sequences are not allowed in strict mode"); + } + goto fail; + } + } else if (c >= 0x80) { + const uint8_t *p_next; + c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) { goto invalid_utf8; } + p = p_next; + /* LS or PS are skipped */ + if (c == CP_LS || c == CP_PS) continue; + } else { + ret = lre_parse_escape (&p, TRUE); + if (ret == -1) { + invalid_escape: + if (do_throw) + js_parse_error_pos ( + s, p_escape, "malformed escape sequence in string literal"); + goto fail; + } else if (ret < 0) { + /* ignore the '\' (could output a warning) */ + p++; + } else { + c = ret; + } + } + break; + } + } else if (c >= 0x80) { + const uint8_t *p_next; + c = unicode_from_utf8 (p - 1, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) goto invalid_utf8; + p = p_next; + } + b = ppretext_putc (b, c); + if (!b) goto fail; + } + str = ppretext_end (s->ctx, b); + if (JS_IsException (str)) return -1; + token->val = TOK_STRING; + token->u.str.sep = c; + token->u.str.str = str; + *pp = p; + return 0; + +invalid_utf8: + if (do_throw) js_parse_error (s, "invalid UTF-8 sequence"); + goto fail; +invalid_char: + if (do_throw) js_parse_error (s, "unexpected end of string"); +fail: + ppretext_free (b); + return -1; +} + +static inline BOOL token_is_pseudo_keyword (JSParseState *s, JSValue key) { + return s->token.val == TOK_IDENT && js_key_equal (s->token.u.ident.str, key) + && !s->token.u.ident.has_escape; +} + +static __exception int js_parse_regexp (JSParseState *s) { + const uint8_t *p; + BOOL in_class; + PPretext *b = NULL; + PPretext *b2 = NULL; + uint32_t c; + JSValue body_str, flags_str; + + p = s->buf_ptr; + p++; + in_class = FALSE; + b = ppretext_init (32); + if (!b) return -1; + b2 = ppretext_init (1); + if (!b2) goto fail; + for (;;) { + if (p >= s->buf_end) { + eof_error: + js_parse_error (s, "unexpected end of regexp"); + goto fail; + } + c = *p++; + if (c == '\n' || c == '\r') { + goto eol_error; + } else if (c == '/') { + if (!in_class) break; + } else if (c == '[') { + in_class = TRUE; + } else if (c == ']') { + /* XXX: incorrect as the first character in a class */ + in_class = FALSE; + } else if (c == '\\') { + b = ppretext_putc (b, c); + if (!b) goto fail; + c = *p++; + if (c == '\n' || c == '\r') + goto eol_error; + else if (c == '\0' && p >= s->buf_end) + goto eof_error; + else if (c >= 0x80) { + const uint8_t *p_next; + c = unicode_from_utf8 (p - 1, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) { goto invalid_utf8; } + p = p_next; + if (c == CP_LS || c == CP_PS) goto eol_error; + } + } else if (c >= 0x80) { + const uint8_t *p_next; + c = unicode_from_utf8 (p - 1, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) { + invalid_utf8: + js_parse_error_pos (s, p - 1, "invalid UTF-8 sequence"); + goto fail; + } + /* LS or PS are considered as line terminator */ + if (c == CP_LS || c == CP_PS) { + eol_error: + js_parse_error_pos (s, p - 1, "unexpected line terminator in regexp"); + goto fail; + } + p = p_next; + } + b = ppretext_putc (b, c); + if (!b) goto fail; + } + + /* flags */ + for (;;) { + const uint8_t *p_next = p; + c = *p_next++; + if (c >= 0x80) { + c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) { + p++; + goto invalid_utf8; + } + } + if (!lre_js_is_ident_next (c)) break; + b2 = ppretext_putc (b2, c); + if (!b2) goto fail; + p = p_next; + } + + body_str = ppretext_end (s->ctx, b); + flags_str = ppretext_end (s->ctx, b2); + if (JS_IsException (body_str) || JS_IsException (flags_str)) { + return -1; + } + s->token.val = TOK_REGEXP; + s->token.u.regexp.body = body_str; + s->token.u.regexp.flags = flags_str; + s->buf_ptr = p; + return 0; +fail: + ppretext_free (b); + ppretext_free (b2); + return -1; +} + +static __exception int ident_realloc (JSContext *ctx, char **pbuf, size_t *psize, char *static_buf) { + (void)ctx; /* unused - uses system allocator */ + char *buf, *new_buf; + size_t size, new_size; + + buf = *pbuf; + size = *psize; + if (size >= (SIZE_MAX / 3) * 2) + new_size = SIZE_MAX; + else + new_size = size + (size >> 1); + if (buf == static_buf) { + new_buf = pjs_malloc (new_size); + if (!new_buf) return -1; + memcpy (new_buf, buf, size); + } else { + new_buf = pjs_realloc (buf, new_size); + if (!new_buf) return -1; + } + *pbuf = new_buf; + *psize = new_size; + return 0; +} + +/* keyword lookup table */ +typedef struct { + const char *name; + int token; + BOOL is_strict_reserved; /* TRUE if reserved only in strict mode */ +} JSKeywordEntry; + +static const JSKeywordEntry js_keywords[] = { + { "null", TOK_NULL, FALSE }, + { "false", TOK_FALSE, FALSE }, + { "true", TOK_TRUE, FALSE }, + { "if", TOK_IF, FALSE }, + { "else", TOK_ELSE, FALSE }, + { "return", TOK_RETURN, FALSE }, + { "go", TOK_GO, FALSE }, + { "var", TOK_VAR, FALSE }, + { "def", TOK_DEF, FALSE }, + { "this", TOK_THIS, FALSE }, + { "delete", TOK_DELETE, FALSE }, + { "in", TOK_IN, FALSE }, + { "do", TOK_DO, FALSE }, + { "while", TOK_WHILE, FALSE }, + { "for", TOK_FOR, FALSE }, + { "break", TOK_BREAK, FALSE }, + { "continue", TOK_CONTINUE, FALSE }, + { "disrupt", TOK_DISRUPT, FALSE }, + { "disruption", TOK_DISRUPTION, FALSE }, + { "function", TOK_FUNCTION, FALSE }, + { "debugger", TOK_DEBUGGER, FALSE }, + { "with", TOK_WITH, FALSE }, + { "class", TOK_CLASS, FALSE }, + { "const", TOK_CONST, FALSE }, + { "enum", TOK_ENUM, FALSE }, + { "export", TOK_EXPORT, FALSE }, + { "extends", TOK_EXTENDS, FALSE }, + { "import", TOK_IMPORT, FALSE }, + { "super", TOK_SUPER, FALSE }, + /* strict mode future reserved words */ + { "implements", TOK_IMPLEMENTS, TRUE }, + { "interface", TOK_INTERFACE, TRUE }, + { "let", TOK_LET, TRUE }, + { "private", TOK_PRIVATE, TRUE }, + { "protected", TOK_PROTECTED, TRUE }, + { "public", TOK_PUBLIC, TRUE }, + { "static", TOK_STATIC, TRUE }, + { "yield", TOK_YIELD, TRUE }, + { "await", TOK_AWAIT, TRUE }, +}; + +#define JS_KEYWORD_COUNT (sizeof (js_keywords) / sizeof (js_keywords[0])) + +/* lookup keyword by JSValue string, return token or -1 if not found */ +static int js_keyword_lookup (JSValue str, BOOL *is_strict_reserved) { + char buf[16]; + const char *cstr; + size_t len; + + if (MIST_IsImmediateASCII (str)) { + len = MIST_GetImmediateASCIILen (str); + if (len >= sizeof (buf)) return -1; + for (size_t i = 0; i < len; i++) { + buf[i] = MIST_GetImmediateASCIIChar (str, i); + } + buf[len] = '\0'; + cstr = buf; + } else if (JS_IsPtr (str)) { + /* Handle heap strings (JSText) - keywords like "function" are 8 chars */ + JSText *text = (JSText *)JS_VALUE_GET_PTR (str); +#ifdef PRINT_LEXER + fprintf(stderr, "DEBUG: JS_IsPtr branch, ptr=%p, hdr_type=%d\n", (void*)text, objhdr_type(text->hdr)); +#endif + if (objhdr_type (text->hdr) != OBJ_TEXT) return -1; + len = JSText_len (text); +#ifdef PRINT_LEXER + fprintf(stderr, "DEBUG: text len=%zu\n", len); +#endif + if (len >= sizeof (buf)) return -1; + for (size_t i = 0; i < len; i++) { + uint32_t c = string_get (text, i); + if (c >= 128) return -1; /* Keywords are ASCII only */ + buf[i] = (char)c; + } + buf[len] = '\0'; + cstr = buf; +#ifdef PRINT_LEXER + fprintf(stderr, "DEBUG: extracted string='%s' len=%zu\n", cstr, len); +#endif + } else { +#ifdef PRINT_LEXER + fprintf(stderr, "DEBUG: unknown str type, tag=%llx\n", (unsigned long long)(str & 0xFF)); +#endif + return -1; + } + +#ifdef PRINT_LEXER + fprintf(stderr, "DEBUG: looking up '%s' in %zu keywords\n", cstr, (size_t)JS_KEYWORD_COUNT); +#endif + for (size_t i = 0; i < JS_KEYWORD_COUNT; i++) { + if (strcmp (cstr, js_keywords[i].name) == 0) { +#ifdef PRINT_LEXER + fprintf(stderr, "DEBUG: matched keyword '%s' -> token %d\n", js_keywords[i].name, js_keywords[i].token); +#endif + if (is_strict_reserved) + *is_strict_reserved = js_keywords[i].is_strict_reserved; + return js_keywords[i].token; + } + } +#ifdef PRINT_LEXER + fprintf(stderr, "DEBUG: no keyword match for '%s'\n", cstr); +#endif + return -1; +} + +/* convert a TOK_IDENT to a keyword when needed */ +static void update_token_ident (JSParseState *s) { + BOOL is_strict_reserved = FALSE; + int token = js_keyword_lookup (s->token.u.ident.str, &is_strict_reserved); + + if (token != -1) { + if (s->token.u.ident.has_escape) { + /* identifiers with escape sequences stay identifiers */ + s->token.u.ident.is_reserved = TRUE; + s->token.val = TOK_IDENT; + } else { + s->token.val = token; + } + } +} + +/* if the current token is an identifier or keyword, reparse it + according to the current function type */ +static void reparse_ident_token (JSParseState *s) { + if (s->token.val == TOK_IDENT + || (s->token.val >= TOK_FIRST_KEYWORD + && s->token.val <= TOK_LAST_KEYWORD)) { + s->token.val = TOK_IDENT; + s->token.u.ident.is_reserved = FALSE; + update_token_ident (s); + } +} + +/* 'c' is the first character. Return JS_NULL in case of error */ +static JSValue parse_ident (JSParseState *s, const uint8_t **pp, BOOL *pident_has_escape, int c) { + const uint8_t *p, *p1; + char ident_buf[128], *buf; + size_t ident_size, ident_pos; + JSValue str; + + p = *pp; + buf = ident_buf; + ident_size = sizeof (ident_buf); + ident_pos = 0; + for (;;) { + p1 = p; + + if (c < 128) { + buf[ident_pos++] = c; + } else { + ident_pos += unicode_to_utf8 ((uint8_t *)buf + ident_pos, c); + } + c = *p1++; + if (c == '\\' && *p1 == 'u') { + c = lre_parse_escape (&p1, TRUE); + *pident_has_escape = TRUE; + } else if (c >= 128) { + c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p1); + } + if (!lre_js_is_ident_next (c) && c != '?' && c != '!') break; + p = p1; + if (unlikely (ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { + if (ident_realloc (s->ctx, &buf, &ident_size, ident_buf)) { + str = JS_NULL; + goto done; + } + } + } + /* Create interned JSValue key */ + str = js_key_new_len (s->ctx, buf, ident_pos); +done: + if (unlikely (buf != ident_buf)) pjs_free (buf); + *pp = p; + return str; +} + +static __exception int next_token (JSParseState *s) { + const uint8_t *p; + int c; + BOOL ident_has_escape; + JSValue ident_str; + + if (js_check_stack_overflow (s->ctx, 0)) { + return js_parse_error (s, "stack overflow"); + } + + free_token (s, &s->token); + + p = s->last_ptr = s->buf_ptr; + s->got_lf = FALSE; +redo: + s->token.ptr = p; + c = *p; + switch (c) { + case 0: + if (p >= s->buf_end) { + s->token.val = TOK_EOF; + } else { + goto def_token; + } + break; + case '`': + if (js_parse_template_part (s, p + 1)) goto fail; + p = s->buf_ptr; + break; + case '\'': + case '\"': + if (js_parse_string (s, c, TRUE, p + 1, &s->token, &p)) goto fail; + break; + case '\r': /* accept DOS and MAC newline sequences */ + if (p[1] == '\n') { p++; } + /* fall thru */ + case '\n': + p++; + line_terminator: + s->got_lf = TRUE; + goto redo; + case '\f': + case '\v': + case ' ': + case '\t': + p++; + goto redo; + case '/': + if (p[1] == '*') { + /* comment */ + p += 2; + for (;;) { + if (*p == '\0' && p >= s->buf_end) { + js_parse_error (s, "unexpected end of comment"); + goto fail; + } + if (p[0] == '*' && p[1] == '/') { + p += 2; + break; + } + if (*p == '\n' || *p == '\r') { + s->got_lf = TRUE; /* considered as LF for ASI */ + p++; + } else if (*p >= 0x80) { + c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p); + if (c == CP_LS || c == CP_PS) { + s->got_lf = TRUE; /* considered as LF for ASI */ + } else if (c == -1) { + p++; /* skip invalid UTF-8 */ + } + } else { + p++; + } + } + goto redo; + } else if (p[1] == '/') { + /* line comment */ + p += 2; + for (;;) { + if (*p == '\0' && p >= s->buf_end) break; + if (*p == '\r' || *p == '\n') break; + if (*p >= 0x80) { + c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p); + /* LS or PS are considered as line terminator */ + if (c == CP_LS || c == CP_PS) { + break; + } else if (c == -1) { + p++; /* skip invalid UTF-8 */ + } + } else { + p++; + } + } + goto redo; + } else if (p[1] == '=') { + p += 2; + s->token.val = TOK_DIV_ASSIGN; + } else { + p++; + s->token.val = c; + } + break; + case '\\': + if (p[1] == 'u') { + const uint8_t *p1 = p + 1; + int c1 = lre_parse_escape (&p1, TRUE); + if (c1 >= 0 && lre_js_is_ident_first (c1)) { + c = c1; + p = p1; + ident_has_escape = TRUE; + goto has_ident; + } else { + /* XXX: syntax error? */ + } + } + goto def_token; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '_': + case '$': + /* identifier */ + p++; + ident_has_escape = FALSE; + has_ident: + ident_str = parse_ident (s, &p, &ident_has_escape, c); + if (JS_IsNull (ident_str)) goto fail; + s->token.u.ident.str = ident_str; + s->token.u.ident.has_escape = ident_has_escape; + s->token.u.ident.is_reserved = FALSE; + s->token.val = TOK_IDENT; + update_token_ident (s); + break; + case '.': + if (p[1] == '.' && p[2] == '.') { + js_parse_error (s, "ellipsis (...) is not supported"); + goto fail; + } + if (p[1] >= '0' && p[1] <= '9') { + goto parse_number; + } else { + goto def_token; + } + break; + case '0': + /* in strict mode, octal literals are not accepted */ + if (is_digit (p[1])) { + js_parse_error (s, "octal literals are not allowed"); + goto fail; + } + goto parse_number; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + /* number */ + parse_number: { + JSValue ret; + const uint8_t *p1; + int flags; + flags = ATOD_ACCEPT_BIN_OCT | ATOD_ACCEPT_LEGACY_OCTAL + | ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_SUFFIX; + ret = js_atof (s->ctx, (const char *)p, (const char **)&p, 0, flags); + if (JS_IsException (ret)) goto fail; + /* reject number immediately followed by identifier */ + if (JS_IsNull (ret) + || lre_js_is_ident_next ( + unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p1))) { + js_parse_error (s, "invalid number literal"); + goto fail; + } + s->token.val = TOK_NUMBER; + s->token.u.num.val = ret; + } break; + case '*': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MUL_ASSIGN; + } else if (p[1] == '*') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_POW_ASSIGN; + } else { + p += 2; + s->token.val = TOK_POW; + } + } else { + goto def_token; + } + break; + case '%': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MOD_ASSIGN; + } else { + goto def_token; + } + break; + case '+': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_PLUS_ASSIGN; + } else if (p[1] == '+') { + p += 2; + s->token.val = TOK_INC; + } else { + goto def_token; + } + break; + case '-': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_MINUS_ASSIGN; + } else if (p[1] == '-') { + p += 2; + s->token.val = TOK_DEC; + } else { + goto def_token; + } + break; + case '<': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_LTE; + } else if (p[1] == '<') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_SHL_ASSIGN; + } else { + p += 2; + s->token.val = TOK_SHL; + } + } else { + goto def_token; + } + break; + case '>': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_GTE; + } else if (p[1] == '>') { + if (p[2] == '>') { + if (p[3] == '=') { + p += 4; + s->token.val = TOK_SHR_ASSIGN; + } else { + p += 3; + s->token.val = TOK_SHR; + } + } else if (p[2] == '=') { + p += 3; + s->token.val = TOK_SAR_ASSIGN; + } else { + p += 2; + s->token.val = TOK_SAR; + } + } else { + goto def_token; + } + break; + case '=': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_STRICT_EQ; + } else if (p[1] == '>') { + p += 2; + s->token.val = TOK_ARROW; + } else { + goto def_token; + } + break; + case '!': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_STRICT_NEQ; + } else { + goto def_token; + } + break; + case '&': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_AND_ASSIGN; + } else if (p[1] == '&') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_LAND_ASSIGN; + } else { + p += 2; + s->token.val = TOK_LAND; + } + } else { + goto def_token; + } + break; + case '^': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_XOR_ASSIGN; + } else { + goto def_token; + } + break; + case '|': + if (p[1] == '=') { + p += 2; + s->token.val = TOK_OR_ASSIGN; + } else if (p[1] == '|') { + if (p[2] == '=') { + p += 3; + s->token.val = TOK_LOR_ASSIGN; + } else { + p += 2; + s->token.val = TOK_LOR; + } + } else { + goto def_token; + } + break; + case '?': + goto def_token; + default: + if (c >= 128) { + /* unicode value */ + c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p); + switch (c) { + case CP_PS: + case CP_LS: + /* XXX: should avoid incrementing line_number, but + needed to handle HTML comments */ + goto line_terminator; + default: + if (lre_is_space (c)) { + goto redo; + } else if (lre_js_is_ident_first (c)) { + ident_has_escape = FALSE; + goto has_ident; + } else { + js_parse_error (s, "unexpected character"); + goto fail; + } + } + } + def_token: + s->token.val = c; + p++; + break; + } + s->buf_ptr = p; + + // dump_token(s, &s->token); + return 0; + +fail: + s->token.val = TOK_ERROR; + return -1; +} + +/* 'c' is the first character. Return JS_NULL in case of error */ +/* XXX: accept unicode identifiers as JSON5 ? */ +static JSValue json_parse_ident (JSParseState *s, const uint8_t **pp, int c) { + const uint8_t *p; + char ident_buf[128], *buf; + size_t ident_size, ident_pos; + JSValue str; + + p = *pp; + buf = ident_buf; + ident_size = sizeof (ident_buf); + ident_pos = 0; + for (;;) { + buf[ident_pos++] = c; + c = *p; + if (c >= 128 || !lre_is_id_continue_byte (c)) break; + p++; + if (unlikely (ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { + if (ident_realloc (s->ctx, &buf, &ident_size, ident_buf)) { + str = JS_NULL; + goto done; + } + } + } + /* Create interned JSValue key */ + str = js_key_new_len (s->ctx, buf, ident_pos); +done: + if (unlikely (buf != ident_buf)) pjs_free (buf); + *pp = p; + return str; +} + +static int json_parse_string (JSParseState *s, const uint8_t **pp, int sep) { + const uint8_t *p, *p_next; + int i; + uint32_t c; + PPretext *b; + + b = ppretext_init (32); + if (!b) goto fail; + + p = *pp; + for (;;) { + if (p >= s->buf_end) { goto end_of_input; } + c = *p++; + if (c == sep) break; + if (c < 0x20) { + js_parse_error_pos (s, p - 1, "Bad control character in string literal"); + goto fail; + } + if (c == '\\') { + c = *p++; + switch (c) { + case 'b': + c = '\b'; + break; + case 'f': + c = '\f'; + break; + case 'n': + c = '\n'; + break; + case 'r': + c = '\r'; + break; + case 't': + c = '\t'; + break; + case '\\': + break; + case '/': + break; + case 'u': + c = 0; + for (i = 0; i < 4; i++) { + int h = from_hex (*p++); + if (h < 0) { + js_parse_error_pos (s, p - 1, "Bad Unicode escape"); + goto fail; + } + c = (c << 4) | h; + } + break; + case '\n': + if (s->ext_json) continue; + goto bad_escape; + case 'v': + if (s->ext_json) { + c = '\v'; + break; + } + goto bad_escape; + default: + if (c == sep) break; + if (p > s->buf_end) goto end_of_input; + bad_escape: + js_parse_error_pos (s, p - 1, "Bad escaped character"); + goto fail; + } + } else if (c >= 0x80) { + c = unicode_from_utf8 (p - 1, UTF8_CHAR_LEN_MAX, &p_next); + if (c > 0x10FFFF) { + js_parse_error_pos (s, p - 1, "Bad UTF-8 sequence"); + goto fail; + } + p = p_next; + } + b = ppretext_putc (b, c); + if (!b) goto fail; + } + s->token.val = TOK_STRING; + s->token.u.str.sep = sep; + s->token.u.str.str = ppretext_end (s->ctx, b); + *pp = p; + return 0; + +end_of_input: + js_parse_error (s, "Unexpected end of JSON input"); +fail: + ppretext_free (b); + return -1; +} + +static int json_parse_number (JSParseState *s, const uint8_t **pp) { + const uint8_t *p = *pp; + const uint8_t *p_start = p; + int radix; + double d; + JSATODTempMem atod_mem; + + if (*p == '+' || *p == '-') p++; + + if (!is_digit (*p)) { + if (s->ext_json) { + if (strstart ((const char *)p, "Infinity", (const char **)&p)) { + d = 1.0 / 0.0; + if (*p_start == '-') d = -d; + goto done; + } else if (strstart ((const char *)p, "NaN", (const char **)&p)) { + d = NAN; + goto done; + } else if (*p != '.') { + goto unexpected_token; + } + } else { + goto unexpected_token; + } + } + + if (p[0] == '0') { + if (s->ext_json) { + /* also accepts base 16, 8 and 2 prefix for integers */ + radix = 10; + if (p[1] == 'x' || p[1] == 'X') { + p += 2; + radix = 16; + } else if ((p[1] == 'o' || p[1] == 'O')) { + p += 2; + radix = 8; + } else if ((p[1] == 'b' || p[1] == 'B')) { + p += 2; + radix = 2; + } + if (radix != 10) { + /* prefix is present */ + if (to_digit (*p) >= radix) { + unexpected_token: + return js_parse_error_pos (s, p, "Unexpected token '%c'", *p); + } + d = js_atod ((const char *)p_start, (const char **)&p, 0, JS_ATOD_INT_ONLY | JS_ATOD_ACCEPT_BIN_OCT, &atod_mem); + goto done; + } + } + if (is_digit (p[1])) return js_parse_error_pos (s, p, "Unexpected number"); + } + + while (is_digit (*p)) + p++; + + if (*p == '.') { + p++; + if (!is_digit (*p)) + return js_parse_error_pos (s, p, "Unterminated fractional number"); + while (is_digit (*p)) + p++; + } + if (*p == 'e' || *p == 'E') { + p++; + if (*p == '+' || *p == '-') p++; + if (!is_digit (*p)) + return js_parse_error_pos (s, p, "Exponent part is missing a number"); + while (is_digit (*p)) + p++; + } + d = js_atod ((const char *)p_start, NULL, 10, 0, &atod_mem); +done: + s->token.val = TOK_NUMBER; + s->token.u.num.val = JS_NewFloat64 (s->ctx, d); + *pp = p; + return 0; +} + +static __exception int json_next_token (JSParseState *s) { + const uint8_t *p; + int c; + JSValue ident_str; + + if (js_check_stack_overflow (s->ctx, 0)) { + return js_parse_error (s, "stack overflow"); + } + + free_token (s, &s->token); + + p = s->last_ptr = s->buf_ptr; +redo: + s->token.ptr = p; + c = *p; + switch (c) { + case 0: + if (p >= s->buf_end) { + s->token.val = TOK_EOF; + } else { + goto def_token; + } + break; + case '\'': + if (!s->ext_json) { + /* JSON does not accept single quoted strings */ + goto def_token; + } + /* fall through */ + case '\"': + p++; + if (json_parse_string (s, &p, c)) goto fail; + break; + case '\r': /* accept DOS and MAC newline sequences */ + if (p[1] == '\n') { p++; } + /* fall thru */ + case '\n': + p++; + goto redo; + case '\f': + case '\v': + if (!s->ext_json) { + /* JSONWhitespace does not match , nor */ + goto def_token; + } + /* fall through */ + case ' ': + case '\t': + p++; + goto redo; + case '/': + if (!s->ext_json) { + /* JSON does not accept comments */ + goto def_token; + } + if (p[1] == '*') { + /* comment */ + p += 2; + for (;;) { + if (*p == '\0' && p >= s->buf_end) { + js_parse_error (s, "unexpected end of comment"); + goto fail; + } + if (p[0] == '*' && p[1] == '/') { + p += 2; + break; + } + if (*p >= 0x80) { + c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p); + if (c == -1) { p++; /* skip invalid UTF-8 */ } + } else { + p++; + } + } + goto redo; + } else if (p[1] == '/') { + /* line comment */ + p += 2; + for (;;) { + if (*p == '\0' && p >= s->buf_end) break; + if (*p == '\r' || *p == '\n') break; + if (*p >= 0x80) { + c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p); + /* LS or PS are considered as line terminator */ + if (c == CP_LS || c == CP_PS) { + break; + } else if (c == -1) { + p++; /* skip invalid UTF-8 */ + } + } else { + p++; + } + } + goto redo; + } else { + goto def_token; + } + break; + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case '_': + case '$': + p++; + ident_str = json_parse_ident (s, &p, c); + if (JS_IsNull (ident_str)) goto fail; + s->token.u.ident.str = ident_str; + s->token.u.ident.has_escape = FALSE; + s->token.u.ident.is_reserved = FALSE; + s->token.val = TOK_IDENT; + break; + case '+': + if (!s->ext_json) goto def_token; + goto parse_number; + case '.': + if (s->ext_json && is_digit (p[1])) + goto parse_number; + else + goto def_token; + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + /* number */ + parse_number: + if (json_parse_number (s, &p)) goto fail; + break; + default: + if (c >= 128) { + js_parse_error (s, "unexpected character"); + goto fail; + } + def_token: + s->token.val = c; + p++; + break; + } + s->buf_ptr = p; + + // dump_token(s, &s->token); + return 0; + +fail: + s->token.val = TOK_ERROR; + return -1; +} + +static int match_identifier (const uint8_t *p, const char *s) { + uint32_t c; + while (*s) { + if ((uint8_t)*s++ != *p++) return 0; + } + c = *p; + if (c >= 128) c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p); + return !lre_js_is_ident_next (c); +} + +/* simple_next_token() is used to check for the next token in simple cases. + It is only used for ':' and '=>', 'let' or 'function' look-ahead. + (*pp) is only set if TOK_IMPORT is returned for JS_DetectModule() + Whitespace and comments are skipped correctly. + Then the next token is analyzed, only for specific words. + Return values: + - '\n' if !no_line_terminator + - TOK_ARROW, TOK_IN, TOK_IMPORT, TOK_OF, TOK_EXPORT, TOK_FUNCTION + - TOK_IDENT is returned for other identifiers and keywords + - otherwise the next character or unicode codepoint is returned. + */ +static int simple_next_token (const uint8_t **pp, BOOL no_line_terminator) { + const uint8_t *p; + uint32_t c; + + /* skip spaces and comments */ + p = *pp; + for (;;) { + switch (c = *p++) { + case '\r': + case '\n': + if (no_line_terminator) return '\n'; + continue; + case ' ': + case '\t': + case '\v': + case '\f': + continue; + case '/': + if (*p == '/') { + if (no_line_terminator) return '\n'; + while (*p && *p != '\r' && *p != '\n') + p++; + continue; + } + if (*p == '*') { + while (*++p) { + if ((*p == '\r' || *p == '\n') && no_line_terminator) return '\n'; + if (*p == '*' && p[1] == '/') { + p += 2; + break; + } + } + continue; + } + break; + case '=': + if (*p == '>') return TOK_ARROW; + break; + case 'i': + if (match_identifier (p, "n")) return TOK_IN; + if (match_identifier (p, "mport")) { + *pp = p + 5; + return TOK_IMPORT; + } + return TOK_IDENT; + case 'o': + if (match_identifier (p, "f")) return TOK_OF; + return TOK_IDENT; + case 'e': + if (match_identifier (p, "xport")) return TOK_EXPORT; + return TOK_IDENT; + case 'f': + if (match_identifier (p, "unction")) return TOK_FUNCTION; + return TOK_IDENT; + case '\\': + if (*p == 'u') { + if (lre_js_is_ident_first (lre_parse_escape (&p, TRUE))) + return TOK_IDENT; + } + break; + default: + if (c >= 128) { + c = unicode_from_utf8 (p - 1, UTF8_CHAR_LEN_MAX, &p); + if (no_line_terminator && (c == CP_PS || c == CP_LS)) return '\n'; + } + if (lre_is_space (c)) continue; + if (lre_js_is_ident_first (c)) return TOK_IDENT; + break; + } + return c; + } +} + +static int peek_token (JSParseState *s, BOOL no_line_terminator) { + const uint8_t *p = s->buf_ptr; + return simple_next_token (&p, no_line_terminator); +} + +static inline int get_prev_opcode (JSFunctionDef *fd) { + if (fd->last_opcode_pos < 0 || dbuf_error (&fd->byte_code)) + return OP_invalid; + else + return fd->byte_code.buf[fd->last_opcode_pos]; +} + +static BOOL js_is_live_code (JSParseState *s) { + switch (get_prev_opcode (s->cur_func)) { + case OP_tail_call: + case OP_tail_call_method: + case OP_return: + case OP_return_undef: + case OP_throw: + case OP_throw_error: + case OP_goto: +#if SHORT_OPCODES + case OP_goto8: + case OP_goto16: +#endif + case OP_ret: + return FALSE; + default: + return TRUE; + } +} + +static void emit_u8 (JSParseState *s, uint8_t val) { + dbuf_putc (&s->cur_func->byte_code, val); +} + +static void emit_u16 (JSParseState *s, uint16_t val) { + dbuf_put_u16 (&s->cur_func->byte_code, val); +} + +static void emit_u32 (JSParseState *s, uint32_t val) { + dbuf_put_u32 (&s->cur_func->byte_code, val); +} + +static void emit_source_pos (JSParseState *s, const uint8_t *source_ptr) { + JSFunctionDef *fd = s->cur_func; + DynBuf *bc = &fd->byte_code; + + if (unlikely (fd->last_opcode_source_ptr != source_ptr)) { + dbuf_putc (bc, OP_line_num); + dbuf_put_u32 (bc, source_ptr - s->buf_start); + fd->last_opcode_source_ptr = source_ptr; + } +} + +static void emit_op (JSParseState *s, uint8_t val) { + JSFunctionDef *fd = s->cur_func; + DynBuf *bc = &fd->byte_code; + + fd->last_opcode_pos = bc->size; + dbuf_putc (bc, val); +} + +/* Forward declarations for cpool */ +static int fd_cpool_add (JSContext *ctx, JSFunctionDef *fd, JSValue val); +static int cpool_add (JSParseState *s, JSValue val); + +/* Emit a key (JSValue) by adding to cpool and emitting the index. + Used for variable opcodes (get_var, put_var, etc.) and property ops. */ +static int emit_key (JSParseState *s, JSValue name) { + int idx = cpool_add (s, name); + if (idx < 0) return -1; + emit_u32 (s, idx); + return 0; +} + +/* Emit a property key for field access opcodes. */ +static int emit_prop_key (JSParseState *s, JSValue key) { + return emit_key (s, key); +} + +static int update_label (JSFunctionDef *s, int label, int delta) { + LabelSlot *ls; + + assert (label >= 0 && label < s->label_count); + ls = &s->label_slots[label]; + ls->ref_count += delta; + assert (ls->ref_count >= 0); + return ls->ref_count; +} + +static int new_label_fd (JSFunctionDef *fd) { + int label; + LabelSlot *ls; + + if (pjs_resize_array ((void **)&fd->label_slots, sizeof (fd->label_slots[0]), &fd->label_size, fd->label_count + 1)) + return -1; + label = fd->label_count++; + ls = &fd->label_slots[label]; + ls->ref_count = 0; + ls->pos = -1; + ls->pos2 = -1; + ls->addr = -1; + ls->first_reloc = NULL; + return label; +} + +static int new_label (JSParseState *s) { + int label; + label = new_label_fd (s->cur_func); + if (unlikely (label < 0)) { dbuf_set_error (&s->cur_func->byte_code); } + return label; +} + +/* don't update the last opcode and don't emit line number info */ +static void emit_label_raw (JSParseState *s, int label) { + emit_u8 (s, OP_label); + emit_u32 (s, label); + s->cur_func->label_slots[label].pos = s->cur_func->byte_code.size; +} + +/* return the label ID offset */ +static int emit_label (JSParseState *s, int label) { + if (label >= 0) { + emit_op (s, OP_label); + emit_u32 (s, label); + s->cur_func->label_slots[label].pos = s->cur_func->byte_code.size; + return s->cur_func->byte_code.size - 4; + } else { + return -1; + } +} + +/* return label or -1 if dead code */ +static int emit_goto (JSParseState *s, int opcode, int label) { + if (js_is_live_code (s)) { + if (label < 0) { + label = new_label (s); + if (label < 0) return -1; + } + emit_op (s, opcode); + emit_u32 (s, label); + s->cur_func->label_slots[label].ref_count++; + return label; + } + return -1; +} + +/* return the constant pool index. 'val' is not duplicated. */ +static int fd_cpool_add (JSContext *ctx, JSFunctionDef *fd, JSValue val) { + (void)ctx; + if (pjs_resize_array ((void **)&fd->cpool, sizeof (fd->cpool[0]), &fd->cpool_size, fd->cpool_count + 1)) + return -1; + fd->cpool[fd->cpool_count++] = val; + return fd->cpool_count - 1; +} + +/* return the constant pool index. 'val' is not duplicated. */ +static int cpool_add (JSParseState *s, JSValue val) { + return fd_cpool_add (s->ctx, s->cur_func, val); +} + +static __exception int emit_push_const (JSParseState *s, JSValue val) { + int idx; + idx = cpool_add (s, val); + if (idx < 0) return -1; + emit_op (s, OP_push_const); + emit_u32 (s, idx); + return 0; +} + +/* return the variable index or -1 if not found, + add ARGUMENT_VAR_OFFSET for argument variables */ +static int find_arg (JSContext *ctx, JSFunctionDef *fd, JSValue name) { + int i; + for (i = fd->arg_count; i-- > 0;) { + if (js_key_equal (fd->args[i].var_name, name)) + return i | ARGUMENT_VAR_OFFSET; + } + return -1; +} + +static int find_var (JSContext *ctx, JSFunctionDef *fd, JSValue name) { + int i; + for (i = fd->var_count; i-- > 0;) { + if (js_key_equal (fd->vars[i].var_name, name) && fd->vars[i].scope_level == 0) + return i; + } + return find_arg (ctx, fd, name); +} + +/* return true if scope == parent_scope or if scope is a child of + parent_scope */ +static BOOL is_child_scope (JSContext *ctx, JSFunctionDef *fd, int scope, int parent_scope) { + while (scope >= 0) { + if (scope == parent_scope) return TRUE; + scope = fd->scopes[scope].parent; + } + return FALSE; +} + +/* find a 'var' declaration in the same scope or a child scope */ +static int find_var_in_child_scope (JSContext *ctx, JSFunctionDef *fd, JSValue name, int scope_level) { + int i; + for (i = 0; i < fd->var_count; i++) { + JSVarDef *vd = &fd->vars[i]; + if (js_key_equal (vd->var_name, name) && vd->scope_level == 0) { + if (is_child_scope (ctx, fd, vd->scope_next, scope_level)) return i; + } + } + return -1; +} + +static JSGlobalVar *find_global_var (JSFunctionDef *fd, JSValue name) { + int i; + for (i = 0; i < fd->global_var_count; i++) { + JSGlobalVar *hf = &fd->global_vars[i]; + if (js_key_equal (hf->var_name, name)) return hf; + } + return NULL; +} + +static JSGlobalVar *find_lexical_global_var (JSFunctionDef *fd, JSValue name) { + JSGlobalVar *hf = find_global_var (fd, name); + if (hf && hf->is_lexical) + return hf; + else + return NULL; +} + +static int find_lexical_decl (JSContext *ctx, JSFunctionDef *fd, JSValue name, int scope_idx, BOOL check_catch_var) { + while (scope_idx >= 0) { + JSVarDef *vd = &fd->vars[scope_idx]; + if (js_key_equal (vd->var_name, name) + && (vd->is_lexical + || (vd->var_kind == JS_VAR_CATCH && check_catch_var))) + return scope_idx; + scope_idx = vd->scope_next; + } + + return -1; +} + +static int push_scope (JSParseState *s) { + if (s->cur_func) { + JSFunctionDef *fd = s->cur_func; + int scope = fd->scope_count; + /* XXX: should check for scope overflow */ + if ((fd->scope_count + 1) > fd->scope_size) { + int new_size; + JSVarScope *new_buf; + /* XXX: potential arithmetic overflow */ + new_size = max_int (fd->scope_count + 1, fd->scope_size * 3 / 2); + if (fd->scopes == fd->def_scope_array) { + new_buf = pjs_malloc (new_size * sizeof (*fd->scopes)); + if (!new_buf) return -1; + memcpy (new_buf, fd->scopes, fd->scope_count * sizeof (*fd->scopes)); + } else { + new_buf = pjs_realloc (fd->scopes, new_size * sizeof (*fd->scopes)); + if (!new_buf) return -1; + } + fd->scopes = new_buf; + fd->scope_size = new_size; + } + fd->scope_count++; + fd->scopes[scope].parent = fd->scope_level; + fd->scopes[scope].first = fd->scope_first; + emit_op (s, OP_enter_scope); + emit_u16 (s, scope); + return fd->scope_level = scope; + } + return 0; +} + +static int get_first_lexical_var (JSFunctionDef *fd, int scope) { + while (scope >= 0) { + int scope_idx = fd->scopes[scope].first; + if (scope_idx >= 0) return scope_idx; + scope = fd->scopes[scope].parent; + } + return -1; +} + +static void pop_scope (JSParseState *s) { + if (s->cur_func) { + /* disable scoped variables */ + JSFunctionDef *fd = s->cur_func; + int scope = fd->scope_level; + emit_op (s, OP_leave_scope); + emit_u16 (s, scope); + fd->scope_level = fd->scopes[scope].parent; + fd->scope_first = get_first_lexical_var (fd, fd->scope_level); + } +} + +static void close_scopes (JSParseState *s, int scope, int scope_stop) { + while (scope > scope_stop) { + emit_op (s, OP_leave_scope); + emit_u16 (s, scope); + scope = s->cur_func->scopes[scope].parent; + } +} + +/* return the variable index or -1 if error */ +static int add_var (JSContext *ctx, JSFunctionDef *fd, JSValue name) { + JSVarDef *vd; + + /* the local variable indexes are currently stored on 16 bits */ + if (fd->var_count >= JS_MAX_LOCAL_VARS) { + JS_ThrowInternalError (ctx, "too many local variables"); + return -1; + } + if (pjs_resize_array ((void **)&fd->vars, sizeof (fd->vars[0]), &fd->var_size, fd->var_count + 1)) + return -1; + vd = &fd->vars[fd->var_count++]; + memset (vd, 0, sizeof (*vd)); + vd->var_name = name; + vd->func_pool_idx = -1; + return fd->var_count - 1; +} + +static int add_scope_var (JSContext *ctx, JSFunctionDef *fd, JSValue name, JSVarKindEnum var_kind) { + int idx = add_var (ctx, fd, name); + if (idx >= 0) { + JSVarDef *vd = &fd->vars[idx]; + vd->var_kind = var_kind; + vd->scope_level = fd->scope_level; + vd->scope_next = fd->scope_first; + fd->scopes[fd->scope_level].first = idx; + fd->scope_first = idx; + } + return idx; +} + +static int add_func_var (JSContext *ctx, JSFunctionDef *fd, JSValue name) { + int idx = fd->func_var_idx; + if (idx < 0 && (idx = add_var (ctx, fd, name)) >= 0) { + fd->func_var_idx = idx; + fd->vars[idx].var_kind = JS_VAR_FUNCTION_NAME; + fd->vars[idx].is_const = TRUE; + } + return idx; +} + +static int add_arg (JSContext *ctx, JSFunctionDef *fd, JSValue name) { + JSVarDef *vd; + + /* the local variable indexes are currently stored on 16 bits */ + if (fd->arg_count >= JS_MAX_LOCAL_VARS) { + JS_ThrowInternalError (ctx, "too many arguments"); + return -1; + } + if (pjs_resize_array ((void **)&fd->args, sizeof (fd->args[0]), &fd->arg_size, fd->arg_count + 1)) + return -1; + vd = &fd->args[fd->arg_count++]; + memset (vd, 0, sizeof (*vd)); + vd->var_name = name; + vd->func_pool_idx = -1; + return fd->arg_count - 1; +} + +/* add a global variable definition */ +static JSGlobalVar *add_global_var (JSContext *ctx, JSFunctionDef *s, JSValue name) { + JSGlobalVar *hf; + (void)ctx; + + if (pjs_resize_array ((void **)&s->global_vars, sizeof (s->global_vars[0]), &s->global_var_size, s->global_var_count + 1)) + return NULL; + hf = &s->global_vars[s->global_var_count++]; + hf->cpool_idx = -1; + hf->force_init = FALSE; + hf->is_lexical = FALSE; + hf->is_const = FALSE; + hf->scope_level = s->scope_level; + hf->var_name = name; + return hf; +} + +typedef enum { + JS_VAR_DEF_WITH, + JS_VAR_DEF_LET, + JS_VAR_DEF_CONST, + JS_VAR_DEF_FUNCTION_DECL, /* function declaration */ + JS_VAR_DEF_NEW_FUNCTION_DECL, /* async/generator function declaration */ + JS_VAR_DEF_CATCH, + JS_VAR_DEF_VAR, +} JSVarDefEnum; + +static int define_var (JSParseState *s, JSFunctionDef *fd, JSValue name, JSVarDefEnum var_def_type) { + JSContext *ctx = s->ctx; + JSVarDef *vd; + int idx; + + switch (var_def_type) { + case JS_VAR_DEF_WITH: + idx = add_scope_var (ctx, fd, name, JS_VAR_NORMAL); + break; + + case JS_VAR_DEF_LET: + case JS_VAR_DEF_CONST: + case JS_VAR_DEF_FUNCTION_DECL: + case JS_VAR_DEF_NEW_FUNCTION_DECL: + idx = find_lexical_decl (ctx, fd, name, fd->scope_first, TRUE); + if (idx >= 0) { + if (idx < GLOBAL_VAR_OFFSET) { + if (fd->vars[idx].scope_level == fd->scope_level) { + goto redef_lex_error; + } else if (fd->vars[idx].var_kind == JS_VAR_CATCH + && (fd->vars[idx].scope_level + 2) == fd->scope_level) { + goto redef_lex_error; + } + } else { + if (fd->scope_level == fd->body_scope) { + redef_lex_error: + return js_parse_error (s, + "invalid redefinition of lexical identifier"); + } + } + } + if (var_def_type != JS_VAR_DEF_FUNCTION_DECL + && var_def_type != JS_VAR_DEF_NEW_FUNCTION_DECL + && fd->scope_level == fd->body_scope + && find_arg (ctx, fd, name) >= 0) { + /* lexical variable redefines a parameter name */ + return js_parse_error (s, "invalid redefinition of parameter name"); + } + + if (find_var_in_child_scope (ctx, fd, name, fd->scope_level) >= 0) { + return js_parse_error (s, "invalid redefinition of a variable"); + } + + if (fd->is_global_var) { + JSGlobalVar *hf; + hf = find_global_var (fd, name); + if (hf && is_child_scope (ctx, fd, hf->scope_level, fd->scope_level)) { + return js_parse_error (s, "invalid redefinition of global identifier"); + } + } + + /* Global object is immutable - always use local variables */ + { + JSVarKindEnum var_kind; + if (var_def_type == JS_VAR_DEF_FUNCTION_DECL) + var_kind = JS_VAR_FUNCTION_DECL; + else if (var_def_type == JS_VAR_DEF_NEW_FUNCTION_DECL) + var_kind = JS_VAR_NEW_FUNCTION_DECL; + else + var_kind = JS_VAR_NORMAL; + idx = add_scope_var (ctx, fd, name, var_kind); + if (idx >= 0) { + vd = &fd->vars[idx]; + vd->is_lexical = 1; + vd->is_const = (var_def_type == JS_VAR_DEF_CONST); + } + } + break; + + case JS_VAR_DEF_CATCH: + idx = add_scope_var (ctx, fd, name, JS_VAR_CATCH); + break; + + case JS_VAR_DEF_VAR: + if (find_lexical_decl (ctx, fd, name, fd->scope_first, FALSE) >= 0) { + /* error to redefine a var that inside a lexical scope */ + return js_parse_error (s, "invalid redefinition of lexical identifier"); + } + if (fd->is_global_var) { + JSGlobalVar *hf; + hf = find_global_var (fd, name); + hf = add_global_var (s->ctx, fd, name); + if (!hf) return -1; + idx = GLOBAL_VAR_OFFSET; + } else { + /* if the variable already exists, don't add it again */ + idx = find_var (ctx, fd, name); + if (idx >= 0) break; + idx = add_var (ctx, fd, name); + if (idx >= 0) { fd->vars[idx].scope_next = fd->scope_level; } + } + break; + default: + abort (); + } + return idx; +} + +static __exception int js_parse_expr (JSParseState *s); +static __exception int js_parse_function_decl (JSParseState *s, + JSParseFunctionEnum func_type, + JSValue func_name, + const uint8_t *ptr); +static __exception int js_parse_function_decl2 (JSParseState *s, + JSParseFunctionEnum func_type, + JSValue func_name, + const uint8_t *ptr, + JSFunctionDef **pfd); +static __exception int js_parse_assign_expr2 (JSParseState *s, + int parse_flags); +static __exception int js_parse_assign_expr (JSParseState *s); +static __exception int js_parse_unary (JSParseState *s, int parse_flags); +static void push_break_entry (JSFunctionDef *fd, BlockEnv *be, JSValue label_name, int label_break, int label_cont, int drop_count); +static void pop_break_entry (JSFunctionDef *fd); + +static __exception int js_parse_template (JSParseState *s) { + JSContext *ctx = s->ctx; + PPretext *fmt = ppretext_init (64); + JSToken cooked; + int expr_count = 0; + + if (!fmt) return -1; + + while (s->token.val == TOK_TEMPLATE) { + const uint8_t *p = s->token.ptr + 1; + + /* re-parse the string with escape sequences and throw a + syntax error if it contains invalid sequences */ + s->token.u.str.str = JS_NULL; + if (js_parse_string (s, '`', TRUE, p, &cooked, &p)) { + ppretext_free (fmt); + return -1; + } + + /* Append cooked string to format string */ + fmt = ppretext_append_jsvalue (fmt, cooked.u.str.str); + if (!fmt) return -1; + + /* Check for end of template */ + if (s->token.u.str.sep == '`') break; + + /* Append placeholder {N} */ + fmt = ppretext_putc (fmt, '{'); + if (!fmt) return -1; + fmt = ppretext_append_int (fmt, expr_count); + if (!fmt) return -1; + fmt = ppretext_putc (fmt, '}'); + if (!fmt) return -1; + + /* Parse expression */ + if (next_token (s)) { ppretext_free (fmt); return -1; } + if (js_parse_expr (s)) { ppretext_free (fmt); return -1; } + expr_count++; + + if (s->token.val != '}') { + ppretext_free (fmt); + return js_parse_error (s, "expected '}' after template expression"); + } + + free_token (s, &s->token); + s->got_lf = FALSE; + if (js_parse_template_part (s, s->buf_ptr)) { + ppretext_free (fmt); + return -1; + } + } + + /* Finalize format string */ + JSValue format_str = ppretext_end (ctx, fmt); + if (JS_IsException (format_str)) return -1; + + int idx = cpool_add (s, format_str); + if (idx < 0) return -1; + + if (expr_count == 0) { + /* Simple template - just push the string */ + emit_op (s, OP_push_const); + emit_u32 (s, idx); + } else { + /* Template with expressions - emit u16 first for stack size computation */ + emit_op (s, OP_format_template); + emit_u16 (s, expr_count); + emit_u32 (s, idx); + } + + return next_token (s); +} + +#define PROP_TYPE_IDENT 0 +#define PROP_TYPE_VAR 1 + +static BOOL token_is_ident (int tok) { + /* Accept keywords and reserved words as property names */ + return (tok == TOK_IDENT + || (tok >= TOK_FIRST_KEYWORD && tok <= TOK_LAST_KEYWORD)); +} + +/* if the property is an expression, name = JS_NULL */ +static int __exception js_parse_property_name (JSParseState *s, JSValue *pname, BOOL allow_method, BOOL allow_var) { + BOOL is_non_reserved_ident; + JSValue name; + int prop_type; + + prop_type = PROP_TYPE_IDENT; + if (token_is_ident (s->token.val)) { + /* variable can only be a non-reserved identifier */ + is_non_reserved_ident + = (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved); + /* keywords and reserved words have a valid JSValue key */ + name = s->token.u.ident.str; + if (next_token (s)) goto fail1; + if (is_non_reserved_ident && prop_type == PROP_TYPE_IDENT && allow_var) { + if (!(s->token.val == ':' || (s->token.val == '(' && allow_method))) { + prop_type = PROP_TYPE_VAR; + } + } + } else if (s->token.val == TOK_STRING) { + /* String literal as property name - already interned from parsing */ + name = s->token.u.str.str; + if (next_token (s)) goto fail1; + } else if (s->token.val == TOK_NUMBER) { + /* Numeric property name - convert to string key */ + JSValue val = s->token.u.num.val; + JSValue str = JS_ToString (s->ctx, val); + if (JS_IsException (str)) goto fail; + name = str; + if (next_token (s)) goto fail1; + } else if (s->token.val == '[') { + if (next_token (s)) goto fail; + if (js_parse_expr (s)) goto fail; + if (js_parse_expect (s, ']')) goto fail; + name = JS_NULL; + } else { + goto invalid_prop; + } + if (prop_type != PROP_TYPE_IDENT && prop_type != PROP_TYPE_VAR + && s->token.val != '(') { + invalid_prop: + js_parse_error (s, "invalid property name"); + goto fail; + } + *pname = name; + return prop_type; +fail1: +fail: + *pname = JS_NULL; + return -1; +} + +typedef struct JSParsePos { + BOOL got_lf; + const uint8_t *ptr; +} JSParsePos; + +static int js_parse_get_pos (JSParseState *s, JSParsePos *sp) { + sp->ptr = s->token.ptr; + sp->got_lf = s->got_lf; + return 0; +} + +static __exception int js_parse_seek_token (JSParseState *s, + const JSParsePos *sp) { + s->buf_ptr = sp->ptr; + s->got_lf = sp->got_lf; + return next_token (s); +} + +/* return TRUE if a regexp literal is allowed after this token */ +static BOOL is_regexp_allowed (int tok) { + switch (tok) { + case TOK_NUMBER: + case TOK_STRING: + case TOK_REGEXP: + case TOK_DEC: + case TOK_INC: + case TOK_NULL: + case TOK_FALSE: + case TOK_TRUE: + case TOK_THIS: + case ')': + case ']': + case '}': /* XXX: regexp may occur after */ + case TOK_IDENT: + return FALSE; + default: + return TRUE; + } +} + +#define SKIP_HAS_SEMI (1 << 0) +#define SKIP_HAS_ASSIGNMENT (1 << 1) + +static BOOL has_lf_in_range (const uint8_t *p1, const uint8_t *p2) { + const uint8_t *tmp; + if (p1 > p2) { + tmp = p1; + p1 = p2; + p2 = tmp; + } + return (memchr (p1, '\n', p2 - p1) != NULL); +} + +/* XXX: improve speed with early bailout */ +/* XXX: no longer works if regexps are present. Could use previous + regexp parsing heuristics to handle most cases */ +static int js_parse_skip_parens_token (JSParseState *s, int *pbits, BOOL no_line_terminator) { + char state[256]; + size_t level = 0; + JSParsePos pos; + int last_tok, tok = TOK_EOF; + int c, tok_len, bits = 0; + const uint8_t *last_token_ptr; + + /* protect from underflow */ + state[level++] = 0; + + js_parse_get_pos (s, &pos); + last_tok = 0; + for (;;) { + switch (s->token.val) { + case '(': + case '[': + case '{': + if (level >= sizeof (state)) goto done; + state[level++] = s->token.val; + break; + case ')': + if (state[--level] != '(') goto done; + break; + case ']': + if (state[--level] != '[') goto done; + break; + case '}': + c = state[--level]; + if (c == '`') { + /* continue the parsing of the template */ + free_token (s, &s->token); + /* Resume TOK_TEMPLATE parsing (s->token.line_num and + * s->token.ptr are OK) */ + s->got_lf = FALSE; + if (js_parse_template_part (s, s->buf_ptr)) goto done; + goto handle_template; + } else if (c != '{') { + goto done; + } + break; + case TOK_TEMPLATE: + handle_template: + if (s->token.u.str.sep != '`') { + /* '${' inside the template : closing '}' and continue + parsing the template */ + if (level >= sizeof (state)) goto done; + state[level++] = '`'; + } + break; + case TOK_EOF: + goto done; + case ';': + if (level == 2) { bits |= SKIP_HAS_SEMI; } + break; + case '=': + bits |= SKIP_HAS_ASSIGNMENT; + break; + + case TOK_DIV_ASSIGN: + tok_len = 2; + goto parse_regexp; + case '/': + tok_len = 1; + parse_regexp: + if (is_regexp_allowed (last_tok)) { + s->buf_ptr -= tok_len; + if (js_parse_regexp (s)) { + /* XXX: should clear the exception */ + goto done; + } + } + break; + } + /* last_tok is only used to recognize regexps */ + if (s->token.val == TOK_IDENT + && (token_is_pseudo_keyword (s, JS_KEY_of) + || token_is_pseudo_keyword (s, JS_KEY_yield))) { + last_tok = TOK_OF; + } else { + last_tok = s->token.val; + } + last_token_ptr = s->token.ptr; + if (next_token (s)) { + /* XXX: should clear the exception generated by next_token() */ + break; + } + if (level <= 1) { + tok = s->token.val; + if (token_is_pseudo_keyword (s, JS_KEY_of)) tok = TOK_OF; + if (no_line_terminator && has_lf_in_range (last_token_ptr, s->token.ptr)) + tok = '\n'; + break; + } + } +done: + if (pbits) { *pbits = bits; } + if (js_parse_seek_token (s, &pos)) return -1; + return tok; +} + +static void set_object_name (JSParseState *s, JSValue name) { + JSFunctionDef *fd = s->cur_func; + int opcode; + + opcode = get_prev_opcode (fd); + if (opcode == OP_set_name) { + /* XXX: should free key after OP_set_name? */ + fd->byte_code.size = fd->last_opcode_pos; + fd->last_opcode_pos = -1; + emit_op (s, OP_set_name); + emit_key (s, name); + } else if (opcode == OP_set_class_name) { + int define_class_pos; + uint32_t cpool_idx; + define_class_pos = fd->last_opcode_pos + 1 + - get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1); + assert (fd->byte_code.buf[define_class_pos] == OP_define_class); + /* Update the cpool index in the bytecode to point to the new name */ + cpool_idx = cpool_add (s, name); + put_u32 (fd->byte_code.buf + define_class_pos + 1, cpool_idx); + fd->last_opcode_pos = -1; + } +} + +static void set_object_name_computed (JSParseState *s) { + JSFunctionDef *fd = s->cur_func; + int opcode; + + opcode = get_prev_opcode (fd); + if (opcode == OP_set_name) { + /* XXX: should free atom after OP_set_name? */ + fd->byte_code.size = fd->last_opcode_pos; + fd->last_opcode_pos = -1; + emit_op (s, OP_set_name_computed); + } else if (opcode == OP_set_class_name) { + int define_class_pos; + define_class_pos = fd->last_opcode_pos + 1 + - get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1); + assert (fd->byte_code.buf[define_class_pos] == OP_define_class); + fd->byte_code.buf[define_class_pos] = OP_define_class_computed; + fd->last_opcode_pos = -1; + } +} + +static __exception int js_parse_object_literal (JSParseState *s) { + JSValue name = JS_NULL; + const uint8_t *start_ptr; + int prop_type; + + if (next_token (s)) goto fail; + /* XXX: add an initial length that will be patched back */ + emit_op (s, OP_object); + + while (s->token.val != '}') { + /* specific case for getter/setter */ + start_ptr = s->token.ptr; + + prop_type = js_parse_property_name (s, &name, TRUE, TRUE); + if (prop_type < 0) goto fail; + + if (prop_type == PROP_TYPE_VAR) { + /* shortcut for x: x */ + emit_op (s, OP_scope_get_var); + emit_key (s, name); + emit_u16 (s, s->cur_func->scope_level); + emit_op (s, OP_define_field); + emit_prop_key (s, name); + } else if (s->token.val == '(') { + JSParseFunctionEnum func_type; + int op_flags; + + func_type = JS_PARSE_FUNC_METHOD; + if (js_parse_function_decl (s, func_type, JS_NULL, start_ptr)) goto fail; + if (JS_IsNull (name)) { + emit_op (s, OP_define_method_computed); + } else { + emit_op (s, OP_define_method); + emit_key (s, name); + } + + op_flags = OP_DEFINE_METHOD_METHOD; + emit_u8 (s, op_flags | OP_DEFINE_METHOD_ENUMERABLE); + } else { + if (JS_IsNull (name)) { + /* must be done before evaluating expr */ + emit_op (s, OP_to_propkey); + } + if (js_parse_expect (s, ':')) goto fail; + if (js_parse_assign_expr (s)) goto fail; + if (JS_IsNull (name)) { + set_object_name_computed (s); + emit_op (s, OP_define_array_el); + emit_op (s, OP_drop); + } else { + set_object_name (s, name); + emit_op (s, OP_define_field); + emit_prop_key (s, name); + } + } + name = JS_NULL; + if (s->token.val != ',') break; + if (next_token (s)) goto fail; + } + if (js_parse_expect (s, '}')) goto fail; + return 0; +fail: + return -1; +} + +/* allow the 'in' binary operator */ +#define PF_IN_ACCEPTED (1 << 0) +/* allow function calls parsing in js_parse_postfix_expr() */ +#define PF_POSTFIX_CALL (1 << 1) +/* allow the exponentiation operator in js_parse_unary() */ +#define PF_POW_ALLOWED (1 << 2) +/* forbid the exponentiation operator in js_parse_unary() */ +#define PF_POW_FORBIDDEN (1 << 3) + +static __exception int js_parse_postfix_expr (JSParseState *s, + int parse_flags); + +static JSFunctionDef * +js_new_function_def (JSContext *ctx, JSFunctionDef *parent, BOOL is_func_expr, const char *filename, const uint8_t *source_ptr, GetLineColCache *get_line_col_cache); +static void emit_return (JSParseState *s, BOOL hasval); + +static __exception int js_parse_left_hand_side_expr (JSParseState *s) { + return js_parse_postfix_expr (s, PF_POSTFIX_CALL); +} + +#define ARRAY_LITERAL_MAX 1024 + +static __exception int js_parse_array_literal (JSParseState *s) { + uint32_t idx = 0; + + if (next_token (s)) return -1; + + while (s->token.val != ']' && idx < ARRAY_LITERAL_MAX) { + if (s->token.val == ',') + return js_parse_error (s, "array holes not allowed"); + + if (js_parse_assign_expr (s)) return -1; + idx++; + + if (s->token.val == ',') { + if (next_token (s)) return -1; + } else if (s->token.val != ']') { + return js_parse_error (s, "expected ',' or ']'"); + } + } + + if (s->token.val != ']') return js_parse_error (s, "array literal too long"); + + emit_op (s, OP_array_from); + emit_u16 (s, idx); + return js_parse_expect (s, ']'); +} + +static __exception int get_lvalue (JSParseState *s, int *popcode, int *pscope, JSValue *pname, int *plabel, int *pdepth, BOOL keep, int tok) { + JSFunctionDef *fd; + int opcode, scope, label, depth; + JSValue name; + + /* we check the last opcode to get the lvalue type */ + fd = s->cur_func; + scope = 0; + name = JS_NULL; + label = -1; + depth = 0; + switch (opcode = get_prev_opcode (fd)) { + case OP_scope_get_var: { + /* Read cpool index and get key from cpool */ + uint32_t idx = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1); + scope = get_u16 (fd->byte_code.buf + fd->last_opcode_pos + 5); + name = fd->cpool[idx]; + if (js_key_equal_str (name, "eval")) { + return js_parse_error (s, "invalid lvalue in strict mode"); + } + if (js_key_equal_str (name, "this") + || js_key_equal_str (name, "new.target")) { + goto invalid_lvalue; + } + /* Variable lvalue: depth=0, no reference objects on stack */ + depth = 0; + break; + } + case OP_get_field: { + /* Read cpool index and get property name from cpool */ + uint32_t idx = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1); + name = fd->cpool[idx]; + depth = 1; + break; + } + case OP_get_array_el: + depth = 2; + break; + default: + invalid_lvalue: + if (tok == TOK_FOR) { + return js_parse_error (s, "invalid for in/of left hand-side"); + } else if (tok == TOK_INC || tok == TOK_DEC) { + return js_parse_error (s, "invalid increment/decrement operand"); + } else if (tok == '[' || tok == '{') { + return js_parse_error (s, "invalid destructuring target"); + } else { + return js_parse_error (s, "invalid assignment left-hand side"); + } + } + /* remove the last opcode */ + fd->byte_code.size = fd->last_opcode_pos; + fd->last_opcode_pos = -1; + + if (keep) { + /* get the value but keep the object/fields on the stack */ + switch (opcode) { + case OP_scope_get_var: + /* For variable lvalues, just re-emit the get to have the value on stack. + No reference objects needed - put_lvalue will emit scope_put_var directly. */ + emit_op (s, OP_scope_get_var); + emit_key (s, name); + emit_u16 (s, scope); + break; + case OP_get_field: + emit_op (s, OP_get_field2); + emit_prop_key (s, name); + break; + case OP_get_array_el: + emit_op (s, OP_get_array_el3); + break; + default: + abort (); + } + } + /* For variable lvalues without keep, nothing needs to be emitted. + put_lvalue will handle the store with just the value on stack. */ + + *popcode = opcode; + *pscope = scope; + /* name is set for OP_get_field, OP_scope_get_var, and JS_NULL for array_el */ + *pname = name; + *plabel = label; + if (pdepth) *pdepth = depth; + return 0; +} + +typedef enum { + PUT_LVALUE_NOKEEP, /* [depth] v -> */ + PUT_LVALUE_NOKEEP_DEPTH, /* [depth] v -> , keep depth (currently + just disable optimizations) */ + PUT_LVALUE_KEEP_TOP, /* [depth] v -> v */ + PUT_LVALUE_KEEP_SECOND, /* [depth] v0 v -> v0 */ + PUT_LVALUE_NOKEEP_BOTTOM, /* v [depth] -> */ +} PutLValueEnum; + +/* name has a live reference. 'is_let' is only used with opcode = + OP_scope_get_var for variable lvalues (no reference objects on stack). */ +static void put_lvalue (JSParseState *s, int opcode, int scope, JSValue name, int label, PutLValueEnum special, BOOL is_let) { + switch (opcode) { + case OP_scope_get_var: + /* depth = 0 for variable lvalues - no reference objects on stack */ + switch (special) { + case PUT_LVALUE_NOKEEP: + case PUT_LVALUE_NOKEEP_DEPTH: + /* val -> (store, nothing left) */ + break; + case PUT_LVALUE_KEEP_TOP: + /* val -> val (dup before store) */ + emit_op (s, OP_dup); + break; + case PUT_LVALUE_KEEP_SECOND: + /* v0 v -> v0 (store v, keep v0) - for postfix ++/-- */ + /* stack: old_val new_val, store new_val, result is old_val */ + break; + case PUT_LVALUE_NOKEEP_BOTTOM: + /* val -> (same as NOKEEP for depth=0) */ + break; + default: + abort (); + } + break; + case OP_get_field: + /* depth = 1 */ + switch (special) { + case PUT_LVALUE_NOKEEP: + case PUT_LVALUE_NOKEEP_DEPTH: + break; + case PUT_LVALUE_KEEP_TOP: + emit_op (s, OP_insert2); /* obj v -> v obj v */ + break; + case PUT_LVALUE_KEEP_SECOND: + emit_op (s, OP_perm3); /* obj v0 v -> v0 obj v */ + break; + case PUT_LVALUE_NOKEEP_BOTTOM: + emit_op (s, OP_swap); + break; + default: + abort (); + } + break; + case OP_get_array_el: + /* depth = 2 */ + switch (special) { + case PUT_LVALUE_NOKEEP: + emit_op (s, OP_nop); /* will trigger optimization */ + break; + case PUT_LVALUE_NOKEEP_DEPTH: + break; + case PUT_LVALUE_KEEP_TOP: + emit_op (s, OP_insert3); /* obj prop v -> v obj prop v */ + break; + case PUT_LVALUE_KEEP_SECOND: + emit_op (s, OP_perm4); /* obj prop v0 v -> v0 obj prop v */ + break; + case PUT_LVALUE_NOKEEP_BOTTOM: + emit_op (s, OP_rot3l); + break; + default: + abort (); + } + break; + default: + break; + } + + switch (opcode) { + case OP_scope_get_var: /* val -> */ + emit_op (s, is_let ? OP_scope_put_var_init : OP_scope_put_var); + emit_key (s, name); /* emit cpool index */ + emit_u16 (s, scope); + break; + case OP_get_field: + emit_op (s, OP_put_field); + emit_prop_key (s, name); /* name has refcount */ + break; + case OP_get_array_el: + emit_op (s, OP_put_array_el); + break; + default: + abort (); + } +} + +static __exception int js_parse_expr_paren (JSParseState *s) { + if (js_parse_expect (s, '(')) return -1; + if (js_parse_expr (s)) return -1; + if (js_parse_expect (s, ')')) return -1; + return 0; +} + +static int js_unsupported_keyword (JSParseState *s, JSValue key) { + char buf[KEY_GET_STR_BUF_SIZE]; + return js_parse_error (s, "unsupported keyword: %s", JS_KeyGetStr (s->ctx, buf, sizeof (buf), key)); +} + +static __exception int js_define_var (JSParseState *s, JSValue name, int tok) { + JSFunctionDef *fd = s->cur_func; + JSVarDefEnum var_def_type; + + if (js_key_equal (name, JS_KEY_eval)) { + return js_parse_error (s, "invalid variable name in strict mode"); + } + if (js_key_equal (name, JS_KEY_let) && tok == TOK_DEF) { + return js_parse_error (s, "invalid lexical variable name"); + } + switch (tok) { + case TOK_DEF: + var_def_type = JS_VAR_DEF_CONST; + break; + case TOK_VAR: + var_def_type = JS_VAR_DEF_LET; + break; + case TOK_DISRUPTION: + var_def_type = JS_VAR_DEF_CATCH; + break; + default: + abort (); + } + if (define_var (s, fd, name, var_def_type) < 0) return -1; + return 0; +} + +static int js_parse_check_duplicate_parameter (JSParseState *s, JSValue name) { + /* Check for duplicate parameter names */ + JSFunctionDef *fd = s->cur_func; + int i; + for (i = 0; i < fd->arg_count; i++) { + if (js_key_equal (fd->args[i].var_name, name)) goto duplicate; + } + for (i = 0; i < fd->var_count; i++) { + if (js_key_equal (fd->vars[i].var_name, name)) goto duplicate; + } + return 0; + +duplicate: + return js_parse_error ( + s, "duplicate parameter names not allowed in this context"); +} + +static JSValue js_parse_destructuring_var (JSParseState *s, int tok, int is_arg) { + JSValue name; + + if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved) + || js_key_equal (s->token.u.ident.str, JS_KEY_eval)) { + js_parse_error (s, "invalid destructuring target"); + return JS_NULL; + } + name = s->token.u.ident.str; + if (is_arg && js_parse_check_duplicate_parameter (s, name)) goto fail; + if (next_token (s)) goto fail; + + return name; +fail: + return JS_NULL; +} + +/* Return -1 if error, 0 if no initializer, 1 if an initializer is + present at the top level. */ +static int js_parse_destructuring_element (JSParseState *s, int tok, int is_arg, int hasval, BOOL allow_initializer, BOOL export_flag) { + int label_parse, label_assign, label_done, label_lvalue, depth_lvalue; + int start_addr, assign_addr; + JSValue prop_name, var_name; + int opcode, scope, tok1, skip_bits; + BOOL has_initializer; + + label_parse = new_label (s); + label_assign = new_label (s); + + start_addr = s->cur_func->byte_code.size; + if (hasval) { + /* consume value from the stack */ + emit_op (s, OP_dup); + emit_op (s, OP_null); + emit_op (s, OP_strict_eq); + emit_goto (s, OP_if_true, label_parse); + emit_label (s, label_assign); + } else { + emit_goto (s, OP_goto, label_parse); + emit_label (s, label_assign); + /* leave value on the stack */ + emit_op (s, OP_dup); + } + assign_addr = s->cur_func->byte_code.size; + if (s->token.val == '{') { + if (next_token (s)) return -1; + /* XXX: throw an exception if the value cannot be converted to an object */ + while (s->token.val != '}') { + int prop_type; + prop_type = js_parse_property_name (s, &prop_name, FALSE, TRUE); + if (prop_type < 0) return -1; + var_name = JS_NULL; + if (prop_type == PROP_TYPE_IDENT) { + if (next_token (s)) goto prop_error; + if ((s->token.val == '[' || s->token.val == '{') + && ((tok1 = js_parse_skip_parens_token (s, &skip_bits, FALSE)) + == ',' + || tok1 == '=' || tok1 == '}')) { + if (JS_IsNull (prop_name)) { + /* computed property name on stack */ + /* get the computed property from the source object */ + emit_op (s, OP_get_array_el2); + } else { + /* named property */ + /* get the named property from the source object */ + emit_op (s, OP_get_field2); + emit_prop_key (s, prop_name); + } + if (js_parse_destructuring_element (s, tok, is_arg, TRUE, TRUE, export_flag) + < 0) + return -1; + if (s->token.val == '}') break; + /* accept a trailing comma before the '}' */ + if (js_parse_expect (s, ',')) return -1; + continue; + } + if (JS_IsNull (prop_name)) { + emit_op (s, OP_to_propkey); + /* source prop -- source source prop */ + emit_op (s, OP_dup1); + } else { + /* source -- source source */ + emit_op (s, OP_dup); + } + if (tok) { + var_name = js_parse_destructuring_var (s, tok, is_arg); + if (JS_IsNull (var_name)) goto prop_error; + /* no need to make a reference for let/const */ + opcode = OP_scope_get_var; + scope = s->cur_func->scope_level; + label_lvalue = -1; + depth_lvalue = 0; + } else { + if (js_parse_left_hand_side_expr (s)) goto prop_error; + lvalue1: + if (get_lvalue (s, &opcode, &scope, &var_name, &label_lvalue, &depth_lvalue, FALSE, '{')) + goto prop_error; + /* swap ref and lvalue object if any */ + if (JS_IsNull (prop_name)) { + switch (depth_lvalue) { + case 1: + /* source prop x -> x source prop */ + emit_op (s, OP_rot3r); + break; + case 2: + /* source prop x y -> x y source prop */ + emit_op (s, OP_swap2); /* t p2 s p1 */ + break; + case 3: + /* source prop x y z -> x y z source prop */ + emit_op (s, OP_rot5l); + emit_op (s, OP_rot5l); + break; + } + } else { + switch (depth_lvalue) { + case 1: + /* source x -> x source */ + emit_op (s, OP_swap); + break; + case 2: + /* source x y -> x y source */ + emit_op (s, OP_rot3l); + break; + case 3: + /* source x y z -> x y z source */ + emit_op (s, OP_rot4l); + break; + } + } + } + if (JS_IsNull (prop_name)) { + /* computed property name on stack */ + /* XXX: should have OP_get_array_el2x with depth */ + /* source prop -- val */ + emit_op (s, OP_get_array_el); + } else { + /* named property */ + /* XXX: should have OP_get_field2x with depth */ + /* source -- val */ + emit_op (s, OP_get_field); + emit_prop_key (s, prop_name); + } + } else { + /* prop_type = PROP_TYPE_VAR, cannot be a computed property */ + if (is_arg && js_parse_check_duplicate_parameter (s, prop_name)) + goto prop_error; + if (js_key_equal_str (prop_name, "eval")) { + js_parse_error (s, "invalid destructuring target"); + goto prop_error; + } + if (!tok) { + /* generate reference */ + /* source -- source source */ + emit_op (s, OP_dup); + emit_op (s, OP_scope_get_var); + emit_key (s, prop_name); + emit_u16 (s, s->cur_func->scope_level); + goto lvalue1; + } else { + /* no need to make a reference for let/const */ + var_name = prop_name; + opcode = OP_scope_get_var; + scope = s->cur_func->scope_level; + label_lvalue = -1; + depth_lvalue = 0; + + /* source -- source val */ + emit_op (s, OP_get_field2); + emit_prop_key (s, prop_name); + } + } + if (tok) { + if (js_define_var (s, var_name, tok)) goto var_error; + scope = s->cur_func->scope_level; /* XXX: check */ + } + if (s->token.val == '=') { /* handle optional default value */ + int label_hasval; + emit_op (s, OP_dup); + emit_op (s, OP_null); + emit_op (s, OP_strict_eq); + label_hasval = emit_goto (s, OP_if_false, -1); + if (next_token (s)) goto var_error; + emit_op (s, OP_drop); + if (js_parse_assign_expr (s)) goto var_error; + if (opcode == OP_scope_get_var) + set_object_name (s, var_name); + emit_label (s, label_hasval); + } + /* store value into lvalue object */ + put_lvalue (s, opcode, scope, var_name, label_lvalue, PUT_LVALUE_NOKEEP_DEPTH, (tok == TOK_DEF || tok == TOK_VAR)); + if (s->token.val == '}') break; + /* accept a trailing comma before the '}' */ + if (js_parse_expect (s, ',')) return -1; + } + /* drop the source object */ + emit_op (s, OP_drop); + if (next_token (s)) return -1; + } else if (s->token.val == '[') { + return js_parse_error (s, "array destructuring is not supported"); + } else { + return js_parse_error (s, "invalid assignment syntax"); + } + if (s->token.val == '=' && allow_initializer) { + label_done = emit_goto (s, OP_goto, -1); + if (next_token (s)) return -1; + emit_label (s, label_parse); + if (hasval) emit_op (s, OP_drop); + if (js_parse_assign_expr (s)) return -1; + emit_goto (s, OP_goto, label_assign); + emit_label (s, label_done); + has_initializer = TRUE; + } else { + /* normally hasval is true except if + js_parse_skip_parens_token() was wrong in the parsing */ + // assert(hasval); + if (!hasval) { + js_parse_error (s, "too complicated destructuring expression"); + return -1; + } + /* remove test and decrement label ref count */ + memset (s->cur_func->byte_code.buf + start_addr, OP_nop, assign_addr - start_addr); + s->cur_func->label_slots[label_parse].ref_count--; + has_initializer = FALSE; + } + return has_initializer; + +prop_error: +var_error: + return -1; +} + +static void optional_chain_test (JSParseState *s, + int *poptional_chaining_label, + int drop_count) { + int label_next, i; + if (*poptional_chaining_label < 0) *poptional_chaining_label = new_label (s); + /* XXX: could be more efficient with a specific opcode */ + emit_op (s, OP_dup); + emit_op (s, OP_is_null); + label_next = emit_goto (s, OP_if_false, -1); + for (i = 0; i < drop_count; i++) + emit_op (s, OP_drop); + emit_op (s, OP_null); + emit_goto (s, OP_goto, *poptional_chaining_label); + emit_label (s, label_next); +} + +/* allowed parse_flags: PF_POSTFIX_CALL */ +static __exception int js_parse_postfix_expr (JSParseState *s, + int parse_flags) { + int optional_chaining_label; + BOOL accept_lparen = (parse_flags & PF_POSTFIX_CALL) != 0; + const uint8_t *op_token_ptr; + + switch (s->token.val) { + case TOK_NUMBER: { + JSValue val; + val = s->token.u.num.val; + + if (JS_VALUE_GET_TAG (val) == JS_TAG_INT) { + emit_op (s, OP_push_i32); + emit_u32 (s, JS_VALUE_GET_INT (val)); + } else { + if (emit_push_const (s, val) < 0) return -1; + } + } + if (next_token (s)) return -1; + break; + case TOK_TEMPLATE: + if (js_parse_template (s)) return -1; + break; + case TOK_STRING: + if (emit_push_const (s, s->token.u.str.str)) return -1; + if (next_token (s)) return -1; + break; + + case TOK_DIV_ASSIGN: + s->buf_ptr -= 2; + goto parse_regexp; + case '/': + s->buf_ptr--; + parse_regexp: { + JSValue str; + int ret; + if (!s->ctx->compile_regexp) + return js_parse_error (s, "RegExp are not supported"); + /* the previous token is '/' or '/=', so no need to free */ + if (js_parse_regexp (s)) return -1; + ret = emit_push_const (s, s->token.u.regexp.body); + str = s->ctx->compile_regexp (s->ctx, s->token.u.regexp.body, s->token.u.regexp.flags); + if (JS_IsException (str)) { + /* add the line number info */ + int line_num, col_num; + line_num + = get_line_col (&col_num, s->buf_start, s->token.ptr - s->buf_start); + build_backtrace (s->ctx, s->ctx->current_exception, s->filename, line_num + 1, col_num + 1, 0); + return -1; + } + ret = emit_push_const (s, str); + if (ret) return -1; + /* we use a specific opcode to be sure the correct + function is called (otherwise the bytecode would have + to be verified by the RegExp constructor) */ + emit_op (s, OP_regexp); + if (next_token (s)) return -1; + } break; + case '(': + if (js_parse_expr_paren (s)) return -1; + break; + case TOK_FUNCTION: + if (js_parse_function_decl (s, JS_PARSE_FUNC_EXPR, JS_NULL, s->token.ptr)) + return -1; + break; + case TOK_NULL: + if (next_token (s)) return -1; + emit_op (s, OP_null); + break; + case TOK_THIS: + if (next_token (s)) return -1; + emit_op (s, OP_scope_get_var); + emit_key (s, JS_KEY_this); + emit_u16 (s, 0); + break; + case TOK_FALSE: + if (next_token (s)) return -1; + emit_op (s, OP_push_false); + break; + case TOK_TRUE: + if (next_token (s)) return -1; + emit_op (s, OP_push_true); + break; + case TOK_IDENT: { + JSValue name; + const uint8_t *source_ptr; + if (s->token.u.ident.is_reserved) { + return js_parse_error_reserved_identifier (s); + } + source_ptr = s->token.ptr; + name = s->token.u.ident.str; + if (next_token (s)) { + return -1; + } + emit_source_pos (s, source_ptr); + emit_op (s, OP_scope_get_var); + emit_key (s, name); + emit_u16 (s, s->cur_func->scope_level); + } break; + case '{': + case '[': + if (s->token.val == '{') { + if (js_parse_object_literal (s)) return -1; + } else { + if (js_parse_array_literal (s)) return -1; + } + break; + default: + return js_parse_error (s, "unexpected token in expression: '%.*s'", (int)(s->buf_ptr - s->token.ptr), s->token.ptr); + } + + optional_chaining_label = -1; + for (;;) { + JSFunctionDef *fd = s->cur_func; + BOOL has_optional_chain = FALSE; + + if (s->token.val == '(' && accept_lparen) { + int opcode, arg_count, drop_count; + + /* function call */ + parse_func_call: + op_token_ptr = s->token.ptr; + if (next_token (s)) return -1; + + switch (opcode = get_prev_opcode (fd)) { + case OP_get_field: + /* keep the object on the stack */ + fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2; + drop_count = 2; + break; + case OP_get_field_opt_chain: { + int opt_chain_label, next_label; + opt_chain_label + = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1 + 4 + 1); + /* keep the object on the stack */ + fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2; + fd->byte_code.size = fd->last_opcode_pos + 1 + 4; + next_label = emit_goto (s, OP_goto, -1); + emit_label (s, opt_chain_label); + /* need an additional undefined value for the + case where the optional field does not + exists */ + emit_op (s, OP_null); + emit_label (s, next_label); + drop_count = 2; + opcode = OP_get_field; + } break; + case OP_get_array_el: + /* keep the object on the stack */ + fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2; + drop_count = 2; + break; + case OP_get_array_el_opt_chain: { + int opt_chain_label, next_label; + opt_chain_label + = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1 + 1); + /* keep the object on the stack */ + fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2; + fd->byte_code.size = fd->last_opcode_pos + 1; + next_label = emit_goto (s, OP_goto, -1); + emit_label (s, opt_chain_label); + /* need an additional undefined value for the + case where the optional field does not + exists */ + emit_op (s, OP_null); + emit_label (s, next_label); + drop_count = 2; + opcode = OP_get_array_el; + } break; + case OP_scope_get_var: { + /* verify if function name resolves to a simple + get_loc/get_arg: a function call inside a `with` + statement can resolve to a method call of the + `with` context object + */ + drop_count = 1; + } break; + default: + opcode = OP_invalid; + drop_count = 1; + break; + } + if (has_optional_chain) { + optional_chain_test (s, &optional_chaining_label, drop_count); + } + + /* parse arguments */ + arg_count = 0; + while (s->token.val != ')') { + if (arg_count >= 65535) { + return js_parse_error (s, "Too many call arguments"); + } + if (js_parse_assign_expr (s)) return -1; + arg_count++; + if (s->token.val == ')') break; + /* accept a trailing comma before the ')' */ + if (js_parse_expect (s, ',')) return -1; + } + if (next_token (s)) return -1; + emit_func_call: { + emit_source_pos (s, op_token_ptr); + switch (opcode) { + case OP_get_field: + case OP_get_array_el: + emit_op (s, OP_call_method); + emit_u16 (s, arg_count); + break; + default: + emit_op (s, OP_call); + emit_u16 (s, arg_count); + break; + } + } + } else if (s->token.val == '.') { + op_token_ptr = s->token.ptr; + if (next_token (s)) return -1; + parse_property: + emit_source_pos (s, op_token_ptr); + if (!token_is_ident (s->token.val)) { + return js_parse_error (s, "expecting field name"); + } + if (has_optional_chain) { + optional_chain_test (s, &optional_chaining_label, 1); + } + emit_op (s, OP_get_field); + emit_prop_key (s, s->token.u.ident.str); + if (next_token (s)) return -1; + } else if (s->token.val == '[') { + op_token_ptr = s->token.ptr; + parse_array_access: + if (has_optional_chain) { + optional_chain_test (s, &optional_chaining_label, 1); + } + if (next_token (s)) return -1; + if (js_parse_expr (s)) return -1; + if (js_parse_expect (s, ']')) return -1; + emit_source_pos (s, op_token_ptr); + emit_op (s, OP_get_array_el); + } else { + break; + } + } + if (optional_chaining_label >= 0) { + JSFunctionDef *fd = s->cur_func; + int opcode; + emit_label_raw (s, optional_chaining_label); + /* modify the last opcode so that it is an indicator of an + optional chain */ + opcode = get_prev_opcode (fd); + if (opcode == OP_get_field || opcode == OP_get_array_el) { + if (opcode == OP_get_field) + opcode = OP_get_field_opt_chain; + else + opcode = OP_get_array_el_opt_chain; + fd->byte_code.buf[fd->last_opcode_pos] = opcode; + } else { + fd->last_opcode_pos = -1; + } + } + return 0; +} + +static __exception int js_parse_delete (JSParseState *s) { + JSFunctionDef *fd = s->cur_func; + int opcode; + + if (next_token (s)) return -1; + if (js_parse_unary (s, PF_POW_FORBIDDEN)) return -1; + switch (opcode = get_prev_opcode (fd)) { + case OP_get_field: + case OP_get_field_opt_chain: { + JSValue val; + int ret, opt_chain_label, next_label; + if (opcode == OP_get_field_opt_chain) { + opt_chain_label + = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1 + 4 + 1); + } else { + opt_chain_label = -1; + } + { + /* Read cpool index and get property name from cpool */ + uint32_t idx = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1); + val = fd->cpool[idx]; + } + fd->byte_code.size = fd->last_opcode_pos; + ret = emit_push_const (s, val); + if (ret) return ret; + emit_op (s, OP_delete); + if (opt_chain_label >= 0) { + next_label = emit_goto (s, OP_goto, -1); + emit_label (s, opt_chain_label); + /* if the optional chain is not taken, return 'true' */ + emit_op (s, OP_drop); + emit_op (s, OP_push_true); + emit_label (s, next_label); + } + fd->last_opcode_pos = -1; + } break; + case OP_get_array_el: + fd->byte_code.size = fd->last_opcode_pos; + fd->last_opcode_pos = -1; + emit_op (s, OP_delete); + break; + case OP_get_array_el_opt_chain: { + int opt_chain_label, next_label; + opt_chain_label + = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1 + 1); + fd->byte_code.size = fd->last_opcode_pos; + emit_op (s, OP_delete); + next_label = emit_goto (s, OP_goto, -1); + emit_label (s, opt_chain_label); + /* if the optional chain is not taken, return 'true' */ + emit_op (s, OP_drop); + emit_op (s, OP_push_true); + emit_label (s, next_label); + fd->last_opcode_pos = -1; + } break; + case OP_scope_get_var: { + /* 'delete this': this is not a reference */ + uint32_t idx = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1); + JSValue name = fd->cpool[idx]; + if (js_key_equal (name, JS_KEY_this) + || js_key_equal_str (name, "new.target")) + goto ret_true; + + return js_parse_error (s, + "cannot delete a direct reference in strict mode"); + } break; + default: + ret_true: + emit_op (s, OP_drop); + emit_op (s, OP_push_true); + break; + } + return 0; +} + +/* allowed parse_flags: PF_POW_ALLOWED, PF_POW_FORBIDDEN */ +static __exception int js_parse_unary (JSParseState *s, int parse_flags) { + int op; + const uint8_t *op_token_ptr; + + switch (s->token.val) { + case '+': + case '-': + case '!': + case '~': + op_token_ptr = s->token.ptr; + op = s->token.val; + if (next_token (s)) return -1; + if (js_parse_unary (s, PF_POW_FORBIDDEN)) return -1; + switch (op) { + case '-': + emit_source_pos (s, op_token_ptr); + emit_op (s, OP_neg); + break; + case '+': + emit_source_pos (s, op_token_ptr); + emit_op (s, OP_plus); + break; + case '!': + emit_op (s, OP_lnot); + break; + case '~': + emit_source_pos (s, op_token_ptr); + emit_op (s, OP_not); + break; + default: + abort (); + } + parse_flags = 0; + break; + case TOK_DEC: + case TOK_INC: { + int opcode, op, scope, label; + JSValue name; + op = s->token.val; + op_token_ptr = s->token.ptr; + if (next_token (s)) return -1; + if (js_parse_unary (s, 0)) return -1; + if (get_lvalue (s, &opcode, &scope, &name, &label, NULL, TRUE, op)) + return -1; + emit_source_pos (s, op_token_ptr); + emit_op (s, OP_dec + op - TOK_DEC); + put_lvalue (s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, FALSE); + } break; + case TOK_DELETE: + if (js_parse_delete (s)) return -1; + parse_flags = 0; + break; + default: + if (js_parse_postfix_expr (s, PF_POSTFIX_CALL)) return -1; + if (!s->got_lf && (s->token.val == TOK_DEC || s->token.val == TOK_INC)) { + int opcode, op, scope, label; + JSValue name; + op = s->token.val; + op_token_ptr = s->token.ptr; + if (get_lvalue (s, &opcode, &scope, &name, &label, NULL, TRUE, op)) + return -1; + emit_source_pos (s, op_token_ptr); + emit_op (s, OP_post_dec + op - TOK_DEC); + put_lvalue (s, opcode, scope, name, label, PUT_LVALUE_KEEP_SECOND, FALSE); + if (next_token (s)) return -1; + } + break; + } + if (parse_flags & (PF_POW_ALLOWED | PF_POW_FORBIDDEN)) { + if (s->token.val == TOK_POW) { + /* Strict ES7 exponentiation syntax rules: To solve + conficting semantics between different implementations + regarding the precedence of prefix operators and the + postifx exponential, ES7 specifies that -2**2 is a + syntax error. */ + if (parse_flags & PF_POW_FORBIDDEN) { + JS_ThrowSyntaxError (s->ctx, "unparenthesized unary expression can't " + "appear on the left-hand side of '**'"); + return -1; + } + op_token_ptr = s->token.ptr; + if (next_token (s)) return -1; + if (js_parse_unary (s, PF_POW_ALLOWED)) return -1; + emit_source_pos (s, op_token_ptr); + emit_op (s, OP_pow); + } + } + return 0; +} + +/* allowed parse_flags: PF_IN_ACCEPTED */ +static __exception int js_parse_expr_binary (JSParseState *s, int level, int parse_flags) { + int op, opcode; + const uint8_t *op_token_ptr; + + if (level == 0) { + return js_parse_unary (s, PF_POW_ALLOWED); + } else { + if (js_parse_expr_binary (s, level - 1, parse_flags)) return -1; + } + for (;;) { + op = s->token.val; + op_token_ptr = s->token.ptr; + switch (level) { + case 1: + switch (op) { + case '*': + opcode = OP_mul; + break; + case '/': + opcode = OP_div; + break; + case '%': + opcode = OP_mod; + break; + default: + return 0; + } + break; + case 2: + switch (op) { + case '+': + opcode = OP_add; + break; + case '-': + opcode = OP_sub; + break; + default: + return 0; + } + break; + case 3: + switch (op) { + case TOK_SHL: + opcode = OP_shl; + break; + case TOK_SAR: + opcode = OP_sar; + break; + case TOK_SHR: + opcode = OP_shr; + break; + default: + return 0; + } + break; + case 4: + switch (op) { + case '<': + opcode = OP_lt; + break; + case '>': + opcode = OP_gt; + break; + case TOK_LTE: + opcode = OP_lte; + break; + case TOK_GTE: + opcode = OP_gte; + break; + case TOK_IN: + if (parse_flags & PF_IN_ACCEPTED) { + opcode = OP_in; + } else { + return 0; + } + break; + default: + return 0; + } + break; + case 5: + switch (op) { + case TOK_STRICT_EQ: + opcode = OP_strict_eq; + break; + case TOK_STRICT_NEQ: + opcode = OP_strict_neq; + break; + default: + return 0; + } + break; + case 6: + switch (op) { + case '&': + opcode = OP_and; + break; + default: + return 0; + } + break; + case 7: + switch (op) { + case '^': + opcode = OP_xor; + break; + default: + return 0; + } + break; + case 8: + switch (op) { + case '|': + opcode = OP_or; + break; + default: + return 0; + } + break; + default: + abort (); + } + if (next_token (s)) return -1; + if (js_parse_expr_binary (s, level - 1, parse_flags)) return -1; + emit_source_pos (s, op_token_ptr); + emit_op (s, opcode); + } + return 0; +} + +/* allowed parse_flags: PF_IN_ACCEPTED */ +static __exception int js_parse_logical_and_or (JSParseState *s, int op, int parse_flags) { + int label1; + + if (op == TOK_LAND) { + if (js_parse_expr_binary (s, 8, parse_flags)) return -1; + } else { + if (js_parse_logical_and_or (s, TOK_LAND, parse_flags)) return -1; + } + if (s->token.val == op) { + label1 = new_label (s); + + for (;;) { + if (next_token (s)) return -1; + emit_op (s, OP_dup); + emit_goto (s, op == TOK_LAND ? OP_if_false : OP_if_true, label1); + emit_op (s, OP_drop); + + if (op == TOK_LAND) { + if (js_parse_expr_binary (s, 8, parse_flags)) return -1; + } else { + if (js_parse_logical_and_or (s, TOK_LAND, parse_flags)) return -1; + } + if (s->token.val != op) { + break; + } + } + + emit_label (s, label1); + } + return 0; +} + +static __exception int js_parse_coalesce_expr (JSParseState *s, + int parse_flags) { + return js_parse_logical_and_or (s, TOK_LOR, parse_flags); +} + +/* allowed parse_flags: PF_IN_ACCEPTED */ +static __exception int js_parse_cond_expr (JSParseState *s, int parse_flags) { + int label1, label2; + + if (js_parse_coalesce_expr (s, parse_flags)) return -1; + if (s->token.val == '?') { + if (next_token (s)) return -1; + label1 = emit_goto (s, OP_if_false, -1); + + if (js_parse_assign_expr (s)) return -1; + if (js_parse_expect (s, ':')) return -1; + + label2 = emit_goto (s, OP_goto, -1); + + emit_label (s, label1); + + if (js_parse_assign_expr2 (s, parse_flags & PF_IN_ACCEPTED)) return -1; + + emit_label (s, label2); + } + return 0; +} + +/* allowed parse_flags: PF_IN_ACCEPTED */ +static __exception int js_parse_assign_expr2 (JSParseState *s, + int parse_flags) { + int opcode, op, scope, skip_bits; + JSValue name0 = JS_NULL; + JSValue name; + + if (s->token.val == '(' + && js_parse_skip_parens_token (s, NULL, TRUE) == TOK_ARROW) { + return js_parse_function_decl (s, JS_PARSE_FUNC_ARROW, JS_NULL, s->token.ptr); + } else if (s->token.val == TOK_IDENT && peek_token (s, TRUE) == TOK_ARROW) { + return js_parse_function_decl (s, JS_PARSE_FUNC_ARROW, JS_NULL, s->token.ptr); + } else if ((s->token.val == '{' || s->token.val == '[') + && js_parse_skip_parens_token (s, &skip_bits, FALSE) == '=') { + if (js_parse_destructuring_element (s, 0, 0, FALSE, TRUE, FALSE) < 0) + return -1; + return 0; + } + if (s->token.val == TOK_IDENT) { + /* name0 is used to check for OP_set_name pattern, not duplicated */ + name0 = s->token.u.ident.str; + } + if (js_parse_cond_expr (s, parse_flags)) return -1; + + op = s->token.val; + if (op == '=' || (op >= TOK_MUL_ASSIGN && op <= TOK_POW_ASSIGN)) { + int label; + const uint8_t *op_token_ptr; + op_token_ptr = s->token.ptr; + if (next_token (s)) return -1; + if (get_lvalue (s, &opcode, &scope, &name, &label, NULL, (op != '='), op) + < 0) + return -1; + + if (js_parse_assign_expr2 (s, parse_flags)) { + return -1; + } + + if (op == '=') { + if (opcode == OP_scope_get_var && js_key_equal (name, name0)) { + set_object_name (s, name); + } + } else { + static const uint8_t assign_opcodes[] = { + OP_mul, + OP_div, + OP_mod, + OP_add, + OP_sub, + OP_shl, + OP_sar, + OP_shr, + OP_and, + OP_xor, + OP_or, + OP_pow, + }; + op = assign_opcodes[op - TOK_MUL_ASSIGN]; + emit_source_pos (s, op_token_ptr); + emit_op (s, op); + } + put_lvalue (s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, FALSE); + } else if (op >= TOK_LAND_ASSIGN && op <= TOK_LOR_ASSIGN) { + int label, label1, depth_lvalue, label2; + + if (next_token (s)) return -1; + if (get_lvalue (s, &opcode, &scope, &name, &label, &depth_lvalue, TRUE, op) + < 0) + return -1; + + emit_op (s, OP_dup); + label1 + = emit_goto (s, op == TOK_LOR_ASSIGN ? OP_if_true : OP_if_false, -1); + emit_op (s, OP_drop); + + if (js_parse_assign_expr2 (s, parse_flags)) { + return -1; + } + + if (opcode == OP_scope_get_var && js_key_equal (name, name0)) { + set_object_name (s, name); + } + + /* For depth=0 (variable lvalues), dup to keep result on stack after put. + For depth>=1, insert to put value below reference objects. */ + switch (depth_lvalue) { + case 0: + emit_op (s, OP_dup); + break; + case 1: + emit_op (s, OP_insert2); + break; + case 2: + emit_op (s, OP_insert3); + break; + case 3: + emit_op (s, OP_insert4); + break; + default: + abort (); + } + + put_lvalue (s, opcode, scope, name, label, PUT_LVALUE_NOKEEP_DEPTH, FALSE); + label2 = emit_goto (s, OP_goto, -1); + + emit_label (s, label1); + + /* remove the lvalue stack entries (none for depth=0 variables) */ + while (depth_lvalue != 0) { + emit_op (s, OP_nip); + depth_lvalue--; + } + + emit_label (s, label2); + } + return 0; +} + +static __exception int js_parse_assign_expr (JSParseState *s) { + return js_parse_assign_expr2 (s, PF_IN_ACCEPTED); +} + +/* allowed parse_flags: PF_IN_ACCEPTED */ +static __exception int js_parse_expr2 (JSParseState *s, int parse_flags) { + BOOL comma = FALSE; + for (;;) { + if (js_parse_assign_expr2 (s, parse_flags)) return -1; + if (comma) { + /* prevent get_lvalue from using the last expression + as an lvalue. This also prevents the conversion of + of get_var to get_ref for method lookup in function + call inside `with` statement. + */ + s->cur_func->last_opcode_pos = -1; + } + if (s->token.val != ',') break; + comma = TRUE; + if (next_token (s)) return -1; + emit_op (s, OP_drop); + } + return 0; +} + +static __exception int js_parse_expr (JSParseState *s) { + return js_parse_expr2 (s, PF_IN_ACCEPTED); +} + +static void push_break_entry (JSFunctionDef *fd, BlockEnv *be, JSValue label_name, int label_break, int label_cont, int drop_count) { + be->prev = fd->top_break; + fd->top_break = be; + be->label_name = label_name; + be->label_break = label_break; + be->label_cont = label_cont; + be->drop_count = drop_count; + be->label_finally = -1; + be->scope_level = fd->scope_level; + be->is_regular_stmt = FALSE; +} + +static void pop_break_entry (JSFunctionDef *fd) { + BlockEnv *be; + be = fd->top_break; + fd->top_break = be->prev; +} + +static __exception int emit_break (JSParseState *s, JSValue name, int is_cont) { + BlockEnv *top; + int i, scope_level; + + scope_level = s->cur_func->scope_level; + top = s->cur_func->top_break; + while (top != NULL) { + close_scopes (s, scope_level, top->scope_level); + scope_level = top->scope_level; + if (is_cont && top->label_cont != -1 + && (JS_IsNull (name) || js_key_equal (top->label_name, name))) { + /* continue stays inside the same block */ + emit_goto (s, OP_goto, top->label_cont); + return 0; + } + if (!is_cont && top->label_break != -1 + && ((JS_IsNull (name) && !top->is_regular_stmt) + || js_key_equal (top->label_name, name))) { + emit_goto (s, OP_goto, top->label_break); + return 0; + } + for (i = 0; i < top->drop_count; i++) + emit_op (s, OP_drop); + if (top->label_finally != -1) { + /* must push dummy value to keep same stack depth */ + emit_op (s, OP_null); + emit_goto (s, OP_gosub, top->label_finally); + emit_op (s, OP_drop); + } + top = top->prev; + } + if (JS_IsNull (name)) { + if (is_cont) + return js_parse_error (s, "continue must be inside loop"); + else + return js_parse_error (s, "break must be inside loop or switch"); + } else { + return js_parse_error (s, "break/continue label not found"); + } +} + +/* execute the finally blocks before return */ +static void emit_return (JSParseState *s, BOOL hasval) { + BlockEnv *top; + + top = s->cur_func->top_break; + while (top != NULL) { + if (top->label_finally != -1) { + if (!hasval) { + emit_op (s, OP_null); + hasval = TRUE; + } + /* Remove the stack elements up to and including the catch + offset. When 'yield' is used in an expression we have + no easy way to count them, so we use this specific + instruction instead. */ + emit_op (s, OP_nip_catch); + /* execute the "finally" block */ + emit_goto (s, OP_gosub, top->label_finally); + } + top = top->prev; + } + + emit_op (s, hasval ? OP_return : OP_return_undef); +} + +#define DECL_MASK_FUNC (1 << 0) /* allow normal function declaration */ +/* ored with DECL_MASK_FUNC if function declarations are allowed with a label + */ +#define DECL_MASK_FUNC_WITH_LABEL (1 << 1) +#define DECL_MASK_OTHER (1 << 2) /* all other declarations */ +#define DECL_MASK_ALL \ + (DECL_MASK_FUNC | DECL_MASK_FUNC_WITH_LABEL | DECL_MASK_OTHER) + +static __exception int js_parse_statement_or_decl (JSParseState *s, + int decl_mask); + +static __exception int js_parse_statement (JSParseState *s) { + return js_parse_statement_or_decl (s, 0); +} + +static __exception int js_parse_block (JSParseState *s) { + if (js_parse_expect (s, '{')) return -1; + if (s->token.val != '}') { + push_scope (s); + for (;;) { + if (js_parse_statement_or_decl (s, DECL_MASK_ALL)) return -1; + if (s->token.val == '}') break; + } + pop_scope (s); + } + if (next_token (s)) return -1; + return 0; +} + +/* allowed parse_flags: PF_IN_ACCEPTED */ +static __exception int js_parse_var (JSParseState *s, int parse_flags, int tok, BOOL export_flag) { + JSFunctionDef *fd = s->cur_func; + JSValue name = JS_NULL; + + for (;;) { + if (s->token.val == TOK_IDENT) { + if (s->token.u.ident.is_reserved) { + return js_parse_error_reserved_identifier (s); + } + name = s->token.u.ident.str; + if (js_key_equal (name, JS_KEY_let) && tok == TOK_DEF) { + js_parse_error (s, "'let' is not a valid lexical identifier"); + goto var_error; + } + if (next_token (s)) goto var_error; + if (js_define_var (s, name, tok)) goto var_error; + + if (s->token.val == '=') { + if (next_token (s)) goto var_error; + + if (js_parse_assign_expr2 (s, parse_flags)) goto var_error; + set_object_name (s, name); + emit_op (s, (tok == TOK_DEF || tok == TOK_VAR) ? OP_scope_put_var_init : OP_scope_put_var); + emit_key (s, name); + emit_u16 (s, fd->scope_level); + } else { + if (tok == TOK_DEF) { + js_parse_error (s, "missing initializer for const variable"); + goto var_error; + } + if (tok == TOK_VAR) { + /* initialize lexical variable upon entering its scope */ + emit_op (s, OP_null); + emit_op (s, OP_scope_put_var_init); + emit_key (s, name); + emit_u16 (s, fd->scope_level); + } + } + } else { + int skip_bits; + if ((s->token.val == '[' || s->token.val == '{') + && js_parse_skip_parens_token (s, &skip_bits, FALSE) == '=') { + emit_op (s, OP_null); + if (js_parse_destructuring_element (s, tok, 0, TRUE, TRUE, export_flag) + < 0) + return -1; + } else { + return js_parse_error (s, "variable name expected"); + } + } + if (s->token.val != ',') break; + if (next_token (s)) return -1; + } + return 0; + +var_error: + return -1; +} + +/* test if the current token is a label. Use simplistic look-ahead scanner */ +static BOOL is_label (JSParseState *s) { + return (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved + && peek_token (s, FALSE) == ':'); +} + +/* for-in and for-of loops are not supported */ +static __exception int js_parse_for_in_of (JSParseState *s, int label_name) { + return js_parse_error (s, "'for in' and 'for of' loops are not supported"); +} + +static __exception int js_parse_statement_or_decl (JSParseState *s, + int decl_mask) { + JSValue label_name; + int tok; + + /* specific label handling */ + /* XXX: support multiple labels on loop statements */ + label_name = JS_NULL; + if (is_label (s)) { + BlockEnv *be; + + label_name = s->token.u.ident.str; + + for (be = s->cur_func->top_break; be; be = be->prev) { + if (js_key_equal (be->label_name, label_name)) { + js_parse_error (s, "duplicate label name"); + goto fail; + } + } + + if (next_token (s)) goto fail; + if (js_parse_expect (s, ':')) goto fail; + if (s->token.val != TOK_FOR && s->token.val != TOK_DO + && s->token.val != TOK_WHILE) { + /* labelled regular statement */ + int label_break, mask; + BlockEnv break_entry; + + label_break = new_label (s); + push_break_entry (s->cur_func, &break_entry, label_name, label_break, -1, 0); + break_entry.is_regular_stmt = TRUE; + mask = 0; + if (js_parse_statement_or_decl (s, mask)) goto fail; + emit_label (s, label_break); + pop_break_entry (s->cur_func); + goto done; + } + } + + switch (tok = s->token.val) { + case '{': + if (js_parse_block (s)) goto fail; + break; + case TOK_RETURN: { + const uint8_t *op_token_ptr; + op_token_ptr = s->token.ptr; + if (next_token (s)) goto fail; + if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) { + if (js_parse_expr (s)) goto fail; + emit_source_pos (s, op_token_ptr); + emit_return (s, TRUE); + } else { + emit_source_pos (s, op_token_ptr); + emit_return (s, FALSE); + } + if (js_parse_expect_semi (s)) goto fail; + } break; + case TOK_DISRUPT: { + if (next_token (s)) goto fail; + emit_op (s, OP_null); + emit_op (s, OP_throw); + if (js_parse_expect_semi (s)) goto fail; + } break; + case TOK_DEF: + if (!(decl_mask & DECL_MASK_OTHER)) { + js_parse_error ( + s, "lexical declarations can't appear in single-statement context"); + goto fail; + } + /* fall thru */ + case TOK_VAR: + if (!(decl_mask & DECL_MASK_OTHER)) { + js_parse_error ( + s, "lexical declarations can't appear in single-statement context"); + goto fail; + } + if (next_token (s)) goto fail; + if (js_parse_var (s, TRUE, tok, FALSE)) goto fail; + if (js_parse_expect_semi (s)) goto fail; + break; + case TOK_IF: { + int label1, label2, mask; + if (next_token (s)) goto fail; + /* create a new scope for `let f;if(1) function f(){}` */ + push_scope (s); + if (js_parse_expr_paren (s)) goto fail; + label1 = emit_goto (s, OP_if_false, -1); + mask = 0; + + if (js_parse_statement_or_decl (s, mask)) goto fail; + + if (s->token.val == TOK_ELSE) { + label2 = emit_goto (s, OP_goto, -1); + if (next_token (s)) goto fail; + + emit_label (s, label1); + if (js_parse_statement_or_decl (s, mask)) goto fail; + + label1 = label2; + } + emit_label (s, label1); + pop_scope (s); + } break; + case TOK_WHILE: { + int label_cont, label_break; + BlockEnv break_entry; + + label_cont = new_label (s); + label_break = new_label (s); + + push_break_entry (s->cur_func, &break_entry, label_name, label_break, label_cont, 0); + + if (next_token (s)) goto fail; + + emit_label (s, label_cont); + if (js_parse_expr_paren (s)) goto fail; + emit_goto (s, OP_if_false, label_break); + + if (js_parse_statement (s)) goto fail; + emit_goto (s, OP_goto, label_cont); + + emit_label (s, label_break); + + pop_break_entry (s->cur_func); + } break; + case TOK_DO: { + int label_cont, label_break, label1; + BlockEnv break_entry; + + label_cont = new_label (s); + label_break = new_label (s); + label1 = new_label (s); + + push_break_entry (s->cur_func, &break_entry, label_name, label_break, label_cont, 0); + + if (next_token (s)) goto fail; + + emit_label (s, label1); + + if (js_parse_statement (s)) goto fail; + + emit_label (s, label_cont); + if (js_parse_expect (s, TOK_WHILE)) goto fail; + if (js_parse_expr_paren (s)) goto fail; + /* Insert semicolon if missing */ + if (s->token.val == ';') { + if (next_token (s)) goto fail; + } + emit_goto (s, OP_if_true, label1); + + emit_label (s, label_break); + + pop_break_entry (s->cur_func); + } break; + case TOK_FOR: { + int label_cont, label_break, label_body, label_test; + int pos_cont, pos_body, block_scope_level; + BlockEnv break_entry; + int tok, bits; + + if (next_token (s)) goto fail; + + bits = 0; + if (s->token.val == '(') { js_parse_skip_parens_token (s, &bits, FALSE); } + if (js_parse_expect (s, '(')) goto fail; + + if (!(bits & SKIP_HAS_SEMI)) { + /* parse for/in or for/of */ + if (js_parse_for_in_of (s, label_name)) goto fail; + break; + } + block_scope_level = s->cur_func->scope_level; + + /* create scope for the lexical variables declared in the initial, + test and increment expressions */ + push_scope (s); + /* initial expression */ + tok = s->token.val; + if (tok != ';') { + if (tok == TOK_VAR || tok == TOK_DEF) { + if (next_token (s)) goto fail; + if (js_parse_var (s, FALSE, tok, FALSE)) goto fail; + } else { + if (js_parse_expr2 (s, FALSE)) goto fail; + emit_op (s, OP_drop); + } + + /* close the closures before the first iteration */ + close_scopes (s, s->cur_func->scope_level, block_scope_level); + } + if (js_parse_expect (s, ';')) goto fail; + + label_test = new_label (s); + label_cont = new_label (s); + label_body = new_label (s); + label_break = new_label (s); + + push_break_entry (s->cur_func, &break_entry, label_name, label_break, label_cont, 0); + + /* test expression */ + if (s->token.val == ';') { + /* no test expression */ + label_test = label_body; + } else { + emit_label (s, label_test); + if (js_parse_expr (s)) goto fail; + emit_goto (s, OP_if_false, label_break); + } + if (js_parse_expect (s, ';')) goto fail; + + if (s->token.val == ')') { + /* no end expression */ + break_entry.label_cont = label_cont = label_test; + pos_cont = 0; /* avoid warning */ + } else { + /* skip the end expression */ + emit_goto (s, OP_goto, label_body); + + pos_cont = s->cur_func->byte_code.size; + emit_label (s, label_cont); + if (js_parse_expr (s)) goto fail; + emit_op (s, OP_drop); + if (label_test != label_body) emit_goto (s, OP_goto, label_test); + } + if (js_parse_expect (s, ')')) goto fail; + + pos_body = s->cur_func->byte_code.size; + emit_label (s, label_body); + if (js_parse_statement (s)) goto fail; + + /* close the closures before the next iteration */ + /* XXX: check continue case */ + close_scopes (s, s->cur_func->scope_level, block_scope_level); + + if (OPTIMIZE && label_test != label_body && label_cont != label_test) { + /* move the increment code here */ + DynBuf *bc = &s->cur_func->byte_code; + int chunk_size = pos_body - pos_cont; + int offset = bc->size - pos_cont; + int i; + dbuf_realloc (bc, bc->size + chunk_size); + dbuf_put (bc, bc->buf + pos_cont, chunk_size); + memset (bc->buf + pos_cont, OP_nop, chunk_size); + /* increment part ends with a goto */ + s->cur_func->last_opcode_pos = bc->size - 5; + /* relocate labels */ + for (i = label_cont; i < s->cur_func->label_count; i++) { + LabelSlot *ls = &s->cur_func->label_slots[i]; + if (ls->pos >= pos_cont && ls->pos < pos_body) ls->pos += offset; + } + } else { + emit_goto (s, OP_goto, label_cont); + } + + emit_label (s, label_break); + + pop_break_entry (s->cur_func); + pop_scope (s); + } break; + case TOK_BREAK: + case TOK_CONTINUE: { + int is_cont = s->token.val - TOK_BREAK; + JSValue label; + + if (next_token (s)) goto fail; + if (!s->got_lf && s->token.val == TOK_IDENT + && !s->token.u.ident.is_reserved) + label = s->token.u.ident.str; + else + label = JS_NULL; + if (emit_break (s, label, is_cont)) goto fail; + if (!JS_IsNull (label)) { + if (next_token (s)) goto fail; + } + if (js_parse_expect_semi (s)) goto fail; + } break; + case ';': + /* empty statement */ + if (next_token (s)) goto fail; + break; + case TOK_FUNCTION: + /* ES6 Annex B.3.2 and B.3.3 semantics */ + if (!(decl_mask & DECL_MASK_FUNC)) goto func_decl_error; + if (!(decl_mask & DECL_MASK_OTHER) && peek_token (s, FALSE) == '*') + goto func_decl_error; + goto parse_func_var; + case TOK_IDENT: + if (s->token.u.ident.is_reserved) { + js_parse_error_reserved_identifier (s); + goto fail; + } + /* let keyword is no longer supported */ + if (token_is_pseudo_keyword (s, JS_KEY_async) + && peek_token (s, TRUE) == TOK_FUNCTION) { + if (!(decl_mask & DECL_MASK_OTHER)) { + func_decl_error: + js_parse_error ( + s, "function declarations can't appear in single-statement context"); + goto fail; + } + parse_func_var: + if (js_parse_function_decl (s, JS_PARSE_FUNC_VAR, JS_NULL, s->token.ptr)) + goto fail; + break; + } + goto hasexpr; + + case TOK_DEBUGGER: + /* currently no debugger, so just skip the keyword */ + if (next_token (s)) goto fail; + if (js_parse_expect_semi (s)) goto fail; + break; + + case TOK_ENUM: + case TOK_EXPORT: + js_unsupported_keyword (s, s->token.u.ident.str); + goto fail; + + default: + hasexpr: + emit_source_pos (s, s->token.ptr); + if (js_parse_expr (s)) goto fail; + emit_op (s, OP_drop); /* drop the result */ + if (js_parse_expect_semi (s)) goto fail; + break; + } +done: + return 0; +fail: + return -1; +} + +static int add_closure_var (JSContext *ctx, JSFunctionDef *s, BOOL is_local, BOOL is_arg, int var_idx, JSValue var_name, BOOL is_const, BOOL is_lexical, JSVarKindEnum var_kind); + +static __exception int js_parse_source_element (JSParseState *s) { + if (s->token.val == TOK_FUNCTION + || (token_is_pseudo_keyword (s, JS_KEY_async) + && peek_token (s, TRUE) == TOK_FUNCTION)) { + if (js_parse_function_decl (s, JS_PARSE_FUNC_STATEMENT, JS_NULL, s->token.ptr)) + return -1; + } else { + if (js_parse_statement_or_decl (s, DECL_MASK_ALL)) return -1; + } + return 0; +} + +static JSFunctionDef * +js_new_function_def (JSContext *ctx, JSFunctionDef *parent, BOOL is_func_expr, const char *filename, const uint8_t *source_ptr, GetLineColCache *get_line_col_cache) { + JSFunctionDef *fd; + + fd = pjs_mallocz (sizeof (*fd)); + if (!fd) return NULL; + + fd->ctx = ctx; + init_list_head (&fd->child_list); + + /* insert in parent list */ + fd->parent = parent; + fd->parent_cpool_idx = -1; + if (parent) { + list_add_tail (&fd->link, &parent->child_list); + fd->js_mode = parent->js_mode; + fd->parent_scope_level = parent->scope_level; + } + fd->strip_debug = ((ctx->rt->strip_flags & JS_STRIP_DEBUG) != 0); + fd->strip_source + = ((ctx->rt->strip_flags & (JS_STRIP_DEBUG | JS_STRIP_SOURCE)) != 0); + + fd->is_func_expr = is_func_expr; + js_dbuf_init (ctx, &fd->byte_code); + fd->last_opcode_pos = -1; + fd->func_name = JS_NULL; + fd->var_object_idx = -1; + fd->arg_var_object_idx = -1; + fd->func_var_idx = -1; + fd->this_var_idx = -1; + fd->this_active_func_var_idx = -1; + + /* XXX: should distinguish arg, var and var object and body scopes */ + fd->scopes = fd->def_scope_array; + fd->scope_size = countof (fd->def_scope_array); + fd->scope_count = 1; + fd->scopes[0].first = -1; + fd->scopes[0].parent = -1; + fd->scope_level = 0; /* 0: var/arg scope */ + fd->scope_first = -1; + fd->body_scope = -1; + + fd->filename = js_key_new (ctx, filename); + fd->source_pos = source_ptr - get_line_col_cache->buf_start; + fd->get_line_col_cache = get_line_col_cache; + + js_dbuf_init (ctx, &fd->pc2line); + // fd->pc2line_last_line_num = line_num; + // fd->pc2line_last_pc = 0; + fd->last_opcode_source_ptr = source_ptr; + return fd; +} + +static void free_bytecode_atoms (JSRuntime *rt, const uint8_t *bc_buf, int bc_len, BOOL use_short_opcodes) { + int pos, len, op; + const JSOpCode *oi; + + pos = 0; + while (pos < bc_len) { + op = bc_buf[pos]; + if (use_short_opcodes) + oi = &short_opcode_info (op); + else + oi = &opcode_info[op]; + + len = oi->size; + switch (oi->fmt) { + case OP_FMT_key: + case OP_FMT_key_u8: + case OP_FMT_key_u16: + case OP_FMT_key_label_u16: + /* Key operand is now a cpool index; cpool values freed separately */ + break; + default: + break; + } + pos += len; + } +} + +static void js_free_function_def (JSContext *ctx, JSFunctionDef *fd) { + int i; + struct list_head *el, *el1; + + /* free the child functions */ + list_for_each_safe (el, el1, &fd->child_list) { + JSFunctionDef *fd1; + fd1 = list_entry (el, JSFunctionDef, link); + js_free_function_def (ctx, fd1); + } + + free_bytecode_atoms (ctx->rt, fd->byte_code.buf, fd->byte_code.size, fd->use_short_opcodes); + dbuf_free (&fd->byte_code); + pjs_free (fd->jump_slots); + pjs_free (fd->label_slots); + pjs_free (fd->line_number_slots); + + for (i = 0; i < fd->cpool_count; i++) { + } + pjs_free (fd->cpool); + + + for (i = 0; i < fd->var_count; i++) { + } + pjs_free (fd->vars); + for (i = 0; i < fd->arg_count; i++) { + } + pjs_free (fd->args); + + for (i = 0; i < fd->global_var_count; i++) { + } + pjs_free (fd->global_vars); + + for (i = 0; i < fd->closure_var_count; i++) { + JSClosureVar *cv = &fd->closure_var[i]; + (void)cv; + } + pjs_free (fd->closure_var); + + if (fd->scopes != fd->def_scope_array) pjs_free (fd->scopes); + + dbuf_free (&fd->pc2line); + + pjs_free (fd->source); + + if (fd->parent) { + /* remove in parent list */ + list_del (&fd->link); + } + pjs_free (fd); +} + +#ifdef DUMP_BYTECODE +static const char *skip_lines (const char *p, int n) { + while (n-- > 0 && *p) { + while (*p && *p++ != '\n') + continue; + } + return p; +} + +static void print_lines (const char *source, int line, int line1) { + const char *s = source; + const char *p = skip_lines (s, line); + if (*p) { + while (line++ < line1) { + p = skip_lines (s = p, 1); + printf (";; %.*s", (int)(p - s), s); + if (!*p) { + if (p[-1] != '\n') printf ("\n"); + break; + } + } + } +} + +static void dump_byte_code (JSContext *ctx, int pass, const uint8_t *tab, int len, const JSVarDef *args, int arg_count, const JSVarDef *vars, int var_count, const JSClosureVar *closure_var, int closure_var_count, const JSValue *cpool, uint32_t cpool_count, const char *source, const LabelSlot *label_slots, JSFunctionBytecode *b) { + const JSOpCode *oi; + int pos, pos_next, op, size, idx, addr, line, line1, in_source, line_num; + uint8_t *bits = js_mallocz (ctx, len * sizeof (*bits)); + BOOL use_short_opcodes = (b != NULL); + + if (b) { + int col_num; + line_num = find_line_num (ctx, b, -1, &col_num); + } + + /* scan for jump targets */ + for (pos = 0; pos < len; pos = pos_next) { + op = tab[pos]; + if (use_short_opcodes) + oi = &short_opcode_info (op); + else + oi = &opcode_info[op]; + pos_next = pos + oi->size; + if (op < OP_COUNT) { + switch (oi->fmt) { +#if SHORT_OPCODES + case OP_FMT_label8: + pos++; + addr = (int8_t)tab[pos]; + goto has_addr; + case OP_FMT_label16: + pos++; + addr = (int16_t)get_u16 (tab + pos); + goto has_addr; +#endif + case OP_FMT_key_label_u16: + pos += 4; + /* fall thru */ + case OP_FMT_label: + case OP_FMT_label_u16: + pos++; + addr = get_u32 (tab + pos); + goto has_addr; + has_addr: + if (pass == 1) addr = label_slots[addr].pos; + if (pass == 2) addr = label_slots[addr].pos2; + if (pass == 3) addr += pos; + if (addr >= 0 && addr < len) bits[addr] |= 1; + break; + } + } + } + in_source = 0; + if (source) { + /* Always print first line: needed if single line */ + print_lines (source, 0, 1); + in_source = 1; + } + line1 = line = 1; + pos = 0; + while (pos < len) { + op = tab[pos]; + if (source && b) { + int col_num; + if (b) { + line1 = find_line_num (ctx, b, pos, &col_num) - line_num + 1; + } else if (op == OP_line_num) { + /* XXX: no longer works */ + line1 = get_u32 (tab + pos + 1) - line_num + 1; + } + if (line1 > line) { + if (!in_source) printf ("\n"); + in_source = 1; + print_lines (source, line, line1); + line = line1; + // bits[pos] |= 2; + } + } + if (in_source) printf ("\n"); + in_source = 0; + if (op >= OP_COUNT) { + printf ("invalid opcode (0x%02x)\n", op); + pos++; + continue; + } + if (use_short_opcodes) + oi = &short_opcode_info (op); + else + oi = &opcode_info[op]; + size = oi->size; + if (pos + size > len) { + printf ("truncated opcode (0x%02x)\n", op); + break; + } +#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 16) + { + int i, x, x0; + x = x0 = printf ("%5d ", pos); + for (i = 0; i < size; i++) { + if (i == 6) { printf ("\n%*s", x = x0, ""); } + x += printf (" %02X", tab[pos + i]); + } + printf ("%*s", x0 + 20 - x, ""); + } +#endif + if (bits[pos]) { + printf ("%5d: ", pos); + } else { + printf (" "); + } + printf ("%s", oi->name); + pos++; + switch (oi->fmt) { + case OP_FMT_none_int: + printf (" %d", op - OP_push_0); + break; + case OP_FMT_npopx: + printf (" %d", op - OP_call0); + break; + case OP_FMT_u8: + printf (" %u", get_u8 (tab + pos)); + break; + case OP_FMT_i8: + printf (" %d", get_i8 (tab + pos)); + break; + case OP_FMT_u16: + case OP_FMT_npop: + printf (" %u", get_u16 (tab + pos)); + break; + case OP_FMT_npop_u16: + printf (" %u,%u", get_u16 (tab + pos), get_u16 (tab + pos + 2)); + break; + case OP_FMT_i16: + printf (" %d", get_i16 (tab + pos)); + break; + case OP_FMT_i32: + printf (" %d", get_i32 (tab + pos)); + break; + case OP_FMT_u32: + printf (" %u", get_u32 (tab + pos)); + break; +#if SHORT_OPCODES + case OP_FMT_label8: + addr = get_i8 (tab + pos); + goto has_addr1; + case OP_FMT_label16: + addr = get_i16 (tab + pos); + goto has_addr1; +#endif + case OP_FMT_label: + addr = get_u32 (tab + pos); + goto has_addr1; + has_addr1: + if (pass == 1) printf (" %u:%u", addr, label_slots[addr].pos); + if (pass == 2) printf (" %u:%u", addr, label_slots[addr].pos2); + if (pass == 3) printf (" %u", addr + pos); + break; + case OP_FMT_label_u16: + addr = get_u32 (tab + pos); + if (pass == 1) printf (" %u:%u", addr, label_slots[addr].pos); + if (pass == 2) printf (" %u:%u", addr, label_slots[addr].pos2); + if (pass == 3) printf (" %u", addr + pos); + printf (",%u", get_u16 (tab + pos + 4)); + break; +#if SHORT_OPCODES + case OP_FMT_const8: + idx = get_u8 (tab + pos); + goto has_pool_idx; +#endif + case OP_FMT_const: + idx = get_u32 (tab + pos); + goto has_pool_idx; + has_pool_idx: + printf (" %u: ", idx); + if (idx < cpool_count) { + JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[idx], NULL); + } + break; + case OP_FMT_key: + printf (" "); + print_atom (ctx, get_u32 (tab + pos)); + break; + case OP_FMT_key: { + /* Key operand is a cpool index; print the cpool value */ + uint32_t key_idx = get_u32 (tab + pos); + printf (" %u: ", key_idx); + if (key_idx < cpool_count) { + JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[key_idx], NULL); + } + } break; + case OP_FMT_key_u8: + printf (" "); + print_atom (ctx, get_u32 (tab + pos)); + printf (",%d", get_u8 (tab + pos + 4)); + break; + case OP_FMT_key_u16: + printf (" "); + print_atom (ctx, get_u32 (tab + pos)); + printf (",%d", get_u16 (tab + pos + 4)); + break; + case OP_FMT_key_label_u16: + printf (" "); + print_atom (ctx, get_u32 (tab + pos)); + addr = get_u32 (tab + pos + 4); + if (pass == 1) printf (",%u:%u", addr, label_slots[addr].pos); + if (pass == 2) printf (",%u:%u", addr, label_slots[addr].pos2); + if (pass == 3) printf (",%u", addr + pos + 4); + printf (",%u", get_u16 (tab + pos + 8)); + break; + case OP_FMT_none_loc: + idx = (op - OP_get_loc0) % 4; + goto has_loc; + case OP_FMT_loc8: + idx = get_u8 (tab + pos); + goto has_loc; + case OP_FMT_loc: + idx = get_u16 (tab + pos); + has_loc: + printf (" %d: ", idx); + if (idx < var_count) { print_atom (ctx, vars[idx].var_name); } + break; + case OP_FMT_none_arg: + idx = (op - OP_get_arg0) % 4; + goto has_arg; + case OP_FMT_arg: + idx = get_u16 (tab + pos); + has_arg: + printf (" %d: ", idx); + if (idx < arg_count) { print_atom (ctx, args[idx].var_name); } + break; + default: + break; + } + printf ("\n"); + pos += oi->size - 1; + } + if (source) { + if (!in_source) printf ("\n"); + print_lines (source, line, INT32_MAX); + } + js_free (ctx, bits); +} + +static __maybe_unused void dump_pc2line (JSContext *ctx, const uint8_t *buf, int len) { + const uint8_t *p_end, *p; + int pc, v, line_num, col_num, ret; + unsigned int op; + uint32_t val; + + if (len <= 0) return; + + printf ("%5s %5s %5s\n", "PC", "LINE", "COL"); + + p = buf; + p_end = buf + len; + + /* get the function line and column numbers */ + ret = get_leb128 (&val, p, p_end); + if (ret < 0) goto fail; + p += ret; + line_num = val + 1; + + ret = get_leb128 (&val, p, p_end); + if (ret < 0) goto fail; + p += ret; + col_num = val + 1; + + printf ("%5s %5d %5d\n", "-", line_num, col_num); + + pc = 0; + while (p < p_end) { + op = *p++; + if (op == 0) { + ret = get_leb128 (&val, p, p_end); + if (ret < 0) goto fail; + pc += val; + p += ret; + ret = get_sleb128 (&v, p, p_end); + if (ret < 0) goto fail; + p += ret; + line_num += v; + } else { + op -= PC2LINE_OP_FIRST; + pc += (op / PC2LINE_RANGE); + line_num += (op % PC2LINE_RANGE) + PC2LINE_BASE; + } + ret = get_sleb128 (&v, p, p_end); + if (ret < 0) goto fail; + p += ret; + col_num += v; + + printf ("%5d %5d %5d\n", pc, line_num, col_num); + } +fail:; +} + +static __maybe_unused void js_dump_function_bytecode (JSContext *ctx, + JSFunctionBytecode *b) { + int i; + char atom_buf[KEY_GET_STR_BUF_SIZE]; + const char *str; + + if (b->has_debug && !JS_IsNull (b->debug.filename)) { + int line_num, col_num; + str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), b->debug.filename); + line_num = find_line_num (ctx, b, -1, &col_num); + printf ("%s:%d:%d: ", str, line_num, col_num); + } + + str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), b->func_name); + printf ("function: %s%s\n", "", str); + if (b->js_mode) { + printf (" mode:"); + printf (" strict"); + printf ("\n"); + } + if (b->arg_count && b->vardefs) { + printf (" args:"); + for (i = 0; i < b->arg_count; i++) { + printf (" %s", JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), b->vardefs[i].var_name)); + } + printf ("\n"); + } + if (b->var_count && b->vardefs) { + printf (" locals:\n"); + for (i = 0; i < b->var_count; i++) { + JSVarDef *vd = &b->vardefs[b->arg_count + i]; + printf ("%5d: %s %s", i, vd->var_kind == JS_VAR_CATCH ? "catch" : (vd->var_kind == JS_VAR_FUNCTION_DECL || vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) ? "function" + : vd->is_const ? "const" + : vd->is_lexical ? "let" + : "var", + JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), vd->var_name)); + if (vd->scope_level) + printf (" [level:%d next:%d]", vd->scope_level, vd->scope_next); + printf ("\n"); + } + } + if (b->closure_var_count) { + printf (" closure vars:\n"); + for (i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cv = &b->closure_var[i]; + printf ("%5d: %s %s:%s%d %s\n", i, JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), cv->var_name), cv->is_local ? "local" : "parent", cv->is_arg ? "arg" : "loc", cv->var_idx, cv->is_const ? "const" : cv->is_lexical ? "let" + : "var"); + } + } + printf (" stack_size: %d\n", b->stack_size); + printf (" opcodes:\n"); + dump_byte_code (ctx, 3, b->byte_code_buf, b->byte_code_len, b->vardefs, b->arg_count, b->vardefs ? b->vardefs + b->arg_count : NULL, b->var_count, b->closure_var, b->closure_var_count, b->cpool, b->cpool_count, b->has_debug ? b->debug.source : NULL, NULL, b); +#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 32) + if (b->has_debug) + dump_pc2line (ctx, b->debug.pc2line_buf, b->debug.pc2line_len); +#endif + printf ("\n"); +} +#endif + +static int add_closure_var (JSContext *ctx, JSFunctionDef *s, BOOL is_local, BOOL is_arg, int var_idx, JSValue var_name, BOOL is_const, BOOL is_lexical, JSVarKindEnum var_kind) { + JSClosureVar *cv; + + /* the closure variable indexes are currently stored on 16 bits */ + if (s->closure_var_count >= JS_MAX_LOCAL_VARS) { + JS_ThrowInternalError (ctx, "too many closure variables"); + return -1; + } + + if (pjs_resize_array ((void **)&s->closure_var, sizeof (s->closure_var[0]), &s->closure_var_size, s->closure_var_count + 1)) + return -1; + cv = &s->closure_var[s->closure_var_count++]; + cv->is_local = is_local; + cv->is_arg = is_arg; + cv->is_const = is_const; + cv->is_lexical = is_lexical; + cv->var_kind = var_kind; + cv->var_idx = var_idx; + cv->var_name = var_name; + return s->closure_var_count - 1; +} + +/* 'fd' must be a parent of 's'. Create in 's' a closure referencing a + local variable (is_local = TRUE) or a closure (is_local = FALSE) in + 'fd' */ +static int get_closure_var2 (JSContext *ctx, JSFunctionDef *s, JSFunctionDef *fd, BOOL is_local, BOOL is_arg, int var_idx, JSValue var_name, BOOL is_const, BOOL is_lexical, JSVarKindEnum var_kind) { + int i; + + if (fd != s->parent) { + var_idx = get_closure_var2 (ctx, s->parent, fd, is_local, is_arg, var_idx, var_name, is_const, is_lexical, var_kind); + if (var_idx < 0) return -1; + is_local = FALSE; + } + for (i = 0; i < s->closure_var_count; i++) { + JSClosureVar *cv = &s->closure_var[i]; + if (cv->var_idx == var_idx && cv->is_arg == is_arg + && cv->is_local == is_local) + return i; + } + return add_closure_var (ctx, s, is_local, is_arg, var_idx, var_name, is_const, is_lexical, var_kind); +} + +static int get_closure_var (JSContext *ctx, JSFunctionDef *s, JSFunctionDef *fd, BOOL is_arg, int var_idx, JSValue var_name, BOOL is_const, BOOL is_lexical, JSVarKindEnum var_kind) { + return get_closure_var2 (ctx, s, fd, TRUE, is_arg, var_idx, var_name, is_const, is_lexical, var_kind); +} + +/* Compute closure depth and slot for OP_get_up/OP_set_up opcodes. + Follows the closure_var chain to find the actual variable location. + Returns depth (1 = immediate parent) and slot (index in JSFrame.slots). + Returns -1 on error. */ +static int compute_closure_depth_slot(JSFunctionDef *s, int closure_idx, int *out_slot) { + int depth = 1; + JSClosureVar *cv = &s->closure_var[closure_idx]; + JSFunctionDef *fd = s->parent; + + /* Follow chain until we find the actual local variable */ + while (!cv->is_local) { + if (!fd || !fd->parent) return -1; /* Invalid chain */ + cv = &fd->closure_var[cv->var_idx]; + fd = fd->parent; + depth++; + } + + /* Now cv->var_idx is the index in fd's variables/arguments. + Compute slot in JSFrame.slots layout: [args..., vars...] */ + if (cv->is_arg) { + *out_slot = cv->var_idx; + } else { + *out_slot = fd->arg_count + cv->var_idx; + } + return depth; +} + +static int add_var_this (JSContext *ctx, JSFunctionDef *fd) { + return add_var (ctx, fd, JS_KEY_this); +} + +static int resolve_pseudo_var (JSContext *ctx, JSFunctionDef *s, JSValue var_name) { + int var_idx; + + if (!s->has_this_binding) return -1; + + if (js_key_equal (var_name, JS_KEY_this)) { + /* 'this' pseudo variable */ + if (s->this_var_idx < 0) s->this_var_idx = add_var_this (ctx, s); + var_idx = s->this_var_idx; + } else if (js_key_equal (var_name, js_key_new (ctx, "this_active_func"))) { + /* 'this.active_func' pseudo variable */ + if (s->this_active_func_var_idx < 0) + s->this_active_func_var_idx = add_var (ctx, s, var_name); + var_idx = s->this_active_func_var_idx; + } else if (js_key_equal (var_name, js_key_new (ctx, "new_target"))) { + /* 'new.target' not supported - constructors removed */ + var_idx = -1; + } else { + var_idx = -1; + } + return var_idx; +} + +/* test if 'var_name' is in the variable object on the stack. If is it + the case, handle it and jump to 'label_done' */ +static void var_object_test (JSContext *ctx, JSFunctionDef *s, JSValue var_name, int op, DynBuf *bc, int *plabel_done, BOOL is_with) { + int cpool_idx = fd_cpool_add (ctx, s, var_name); + dbuf_putc (bc, op); + dbuf_put_u32 (bc, cpool_idx); +} + +/* return the position of the next opcode */ +static int resolve_scope_var (JSContext *ctx, JSFunctionDef *s, JSValue var_name, int scope_level, int op, DynBuf *bc, uint8_t *bc_buf, LabelSlot *ls, int pos_next) { + int idx, var_idx, is_put; + int label_done; + JSFunctionDef *fd; + JSVarDef *vd; + BOOL is_pseudo_var, is_arg_scope; + + label_done = -1; + + /* XXX: could be simpler to use a specific function to + resolve the pseudo variables */ + is_pseudo_var = (js_key_equal_str (var_name, "home_object") + || js_key_equal_str (var_name, "this_active_func") + || js_key_equal_str (var_name, "new_target") + || js_key_equal (var_name, JS_KEY_this)); + + /* resolve local scoped variables */ + var_idx = -1; + for (idx = s->scopes[scope_level].first; idx >= 0;) { + vd = &s->vars[idx]; + if (js_key_equal (vd->var_name, var_name)) { + if (op == OP_scope_put_var) { + if (vd->is_const) { + int cpool_idx = fd_cpool_add (ctx, s, var_name); + dbuf_putc (bc, OP_throw_error); + dbuf_put_u32 (bc, cpool_idx); + dbuf_putc (bc, JS_THROW_VAR_RO); + goto done; + } + } + var_idx = idx; + break; + } + idx = vd->scope_next; + } + is_arg_scope = (idx == ARG_SCOPE_END); + if (var_idx < 0) { + /* argument scope: variables are not visible but pseudo + variables are visible */ + if (!is_arg_scope) { var_idx = find_var (ctx, s, var_name); } + + if (var_idx < 0 && is_pseudo_var) + var_idx = resolve_pseudo_var (ctx, s, var_name); + + if (var_idx < 0 && s->is_func_expr + && js_key_equal (var_name, s->func_name)) { + /* add a new variable with the function name */ + var_idx = add_func_var (ctx, s, var_name); + } + } + if (var_idx >= 0) { + if (op == OP_scope_put_var + && !(var_idx & ARGUMENT_VAR_OFFSET) && s->vars[var_idx].is_const) { + /* only happens when assigning a function expression name + in strict mode */ + int cpool_idx = fd_cpool_add (ctx, s, var_name); + dbuf_putc (bc, OP_throw_error); + dbuf_put_u32 (bc, cpool_idx); + dbuf_putc (bc, JS_THROW_VAR_RO); + goto done; + } + /* OP_scope_put_var_init is only used to initialize a + lexical variable, so it is never used in a with or var object. It + can be used with a closure (module global variable case). */ + switch (op) { + case OP_scope_get_var_checkthis: + case OP_scope_get_var_undef: + case OP_scope_get_var: + case OP_scope_put_var: + case OP_scope_put_var_init: + is_put = (op == OP_scope_put_var || op == OP_scope_put_var_init); + if (var_idx & ARGUMENT_VAR_OFFSET) { + dbuf_putc (bc, OP_get_arg + is_put); + dbuf_put_u16 (bc, var_idx - ARGUMENT_VAR_OFFSET); + } else { + if (is_put) { + if (s->vars[var_idx].is_lexical) { + if (op == OP_scope_put_var_init) { + /* 'this' can only be initialized once */ + if (js_key_equal (var_name, JS_KEY_this)) + dbuf_putc (bc, OP_put_loc_check_init); + else + dbuf_putc (bc, OP_put_loc); + } else { + dbuf_putc (bc, OP_put_loc_check); + } + } else { + dbuf_putc (bc, OP_put_loc); + } + } else { + if (s->vars[var_idx].is_lexical) { + if (op == OP_scope_get_var_checkthis) { + /* only used for 'this' return in derived class constructors */ + dbuf_putc (bc, OP_get_loc_checkthis); + } else { + dbuf_putc (bc, OP_get_loc_check); + } + } else { + dbuf_putc (bc, OP_get_loc); + } + } + dbuf_put_u16 (bc, var_idx); + } + break; + case OP_scope_delete_var: + dbuf_putc (bc, OP_push_false); + break; + } + goto done; + } + /* check eval object */ + if (!is_arg_scope && s->var_object_idx >= 0 && !is_pseudo_var) { + dbuf_putc (bc, OP_get_loc); + dbuf_put_u16 (bc, s->var_object_idx); + var_object_test (ctx, s, var_name, op, bc, &label_done, 0); + } + /* check eval object in argument scope */ + if (s->arg_var_object_idx >= 0 && !is_pseudo_var) { + dbuf_putc (bc, OP_get_loc); + dbuf_put_u16 (bc, s->arg_var_object_idx); + var_object_test (ctx, s, var_name, op, bc, &label_done, 0); + } + + /* check parent scopes */ + for (fd = s; fd->parent;) { + scope_level = fd->parent_scope_level; + fd = fd->parent; + for (idx = fd->scopes[scope_level].first; idx >= 0;) { + vd = &fd->vars[idx]; + if (js_key_equal (vd->var_name, var_name)) { + if (op == OP_scope_put_var) { + if (vd->is_const) { + int cpool_idx = fd_cpool_add (ctx, s, var_name); + dbuf_putc (bc, OP_throw_error); + dbuf_put_u32 (bc, cpool_idx); + dbuf_putc (bc, JS_THROW_VAR_RO); + goto done; + } + } + var_idx = idx; + break; + } + idx = vd->scope_next; + } + is_arg_scope = (idx == ARG_SCOPE_END); + if (var_idx >= 0) break; + + if (!is_arg_scope) { + var_idx = find_var (ctx, fd, var_name); + if (var_idx >= 0) break; + } + if (is_pseudo_var) { + var_idx = resolve_pseudo_var (ctx, fd, var_name); + if (var_idx >= 0) break; + } + if (fd->is_func_expr && js_key_equal (fd->func_name, var_name)) { + /* add a new variable with the function name */ + var_idx = add_func_var (ctx, fd, var_name); + break; + } + + /* check eval object */ + if (!is_arg_scope && fd->var_object_idx >= 0 && !is_pseudo_var) { + int slot, depth; + vd = &fd->vars[fd->var_object_idx]; + vd->is_captured = 1; + idx = get_closure_var (ctx, s, fd, FALSE, fd->var_object_idx, vd->var_name, FALSE, FALSE, JS_VAR_NORMAL); + depth = compute_closure_depth_slot(s, idx, &slot); + dbuf_putc (bc, OP_get_up); + dbuf_putc (bc, (uint8_t)depth); + dbuf_put_u16 (bc, (uint16_t)slot); + var_object_test (ctx, s, var_name, op, bc, &label_done, 0); + } + + /* check eval object in argument scope */ + if (fd->arg_var_object_idx >= 0 && !is_pseudo_var) { + int slot, depth; + vd = &fd->vars[fd->arg_var_object_idx]; + vd->is_captured = 1; + idx = get_closure_var (ctx, s, fd, FALSE, fd->arg_var_object_idx, vd->var_name, FALSE, FALSE, JS_VAR_NORMAL); + depth = compute_closure_depth_slot(s, idx, &slot); + dbuf_putc (bc, OP_get_up); + dbuf_putc (bc, (uint8_t)depth); + dbuf_put_u16 (bc, (uint16_t)slot); + var_object_test (ctx, s, var_name, op, bc, &label_done, 0); + } + } + + if (var_idx >= 0) { + /* find the corresponding closure variable */ + if (var_idx & ARGUMENT_VAR_OFFSET) { + fd->args[var_idx - ARGUMENT_VAR_OFFSET].is_captured = 1; + idx = get_closure_var (ctx, s, fd, TRUE, var_idx - ARGUMENT_VAR_OFFSET, var_name, FALSE, FALSE, JS_VAR_NORMAL); + } else { + fd->vars[var_idx].is_captured = 1; + idx = get_closure_var ( + ctx, s, fd, FALSE, var_idx, var_name, fd->vars[var_idx].is_const, fd->vars[var_idx].is_lexical, fd->vars[var_idx].var_kind); + } + if (idx >= 0) { + has_idx: + if (op == OP_scope_put_var && s->closure_var[idx].is_const) { + int cpool_idx = fd_cpool_add (ctx, s, var_name); + dbuf_putc (bc, OP_throw_error); + dbuf_put_u32 (bc, cpool_idx); + dbuf_putc (bc, JS_THROW_VAR_RO); + goto done; + } + switch (op) { + case OP_scope_get_var_undef: + case OP_scope_get_var: + case OP_scope_put_var: + case OP_scope_put_var_init: + is_put = (op == OP_scope_put_var || op == OP_scope_put_var_init); + { + /* Use OP_get_up/OP_set_up with (depth, slot) encoding */ + int slot; + int depth = compute_closure_depth_slot(s, idx, &slot); + assert(depth > 0 && depth <= 255 && slot <= 65535); + if (is_put) { + dbuf_putc(bc, OP_set_up); + } else { + dbuf_putc(bc, OP_get_up); + } + dbuf_putc(bc, (uint8_t)depth); + dbuf_put_u16(bc, (uint16_t)slot); + } + break; + case OP_scope_delete_var: + dbuf_putc (bc, OP_push_false); + break; + } + goto done; + } + } + + /* global variable access */ + { + int cpool_idx = fd_cpool_add (ctx, s, var_name); + + switch (op) { + case OP_scope_get_var_undef: + case OP_scope_get_var: + case OP_scope_put_var: + dbuf_putc (bc, OP_get_var_undef + (op - OP_scope_get_var_undef)); + dbuf_put_u32 (bc, cpool_idx); + break; + case OP_scope_put_var_init: + dbuf_putc (bc, OP_put_var_init); + dbuf_put_u32 (bc, cpool_idx); + break; + case OP_scope_delete_var: + dbuf_putc (bc, OP_delete_var); + dbuf_put_u32 (bc, cpool_idx); + break; + } + } +done: + if (label_done >= 0) { + dbuf_putc (bc, OP_label); + dbuf_put_u32 (bc, label_done); + s->label_slots[label_done].pos2 = bc->size; + } + return pos_next; +} + +typedef struct CodeContext { + const uint8_t *bc_buf; /* code buffer */ + int bc_len; /* length of the code buffer */ + int pos; /* position past the matched code pattern */ + int line_num; /* last visited OP_line_num parameter or -1 */ + int op; + int idx; + int label; + int val; +} CodeContext; + +#define M2(op1, op2) ((op1) | ((op2) << 8)) +#define M3(op1, op2, op3) ((op1) | ((op2) << 8) | ((op3) << 16)) +#define M4(op1, op2, op3, op4) \ + ((op1) | ((op2) << 8) | ((op3) << 16) | ((op4) << 24)) + +static BOOL code_match (CodeContext *s, int pos, ...) { + const uint8_t *tab = s->bc_buf; + int op, len, op1, line_num, pos_next; + va_list ap; + BOOL ret = FALSE; + + line_num = -1; + va_start (ap, pos); + + for (;;) { + op1 = va_arg (ap, int); + if (op1 == -1) { + s->pos = pos; + s->line_num = line_num; + ret = TRUE; + break; + } + for (;;) { + if (pos >= s->bc_len) goto done; + op = tab[pos]; + len = opcode_info[op].size; + pos_next = pos + len; + if (pos_next > s->bc_len) goto done; + if (op == OP_line_num) { + line_num = get_u32 (tab + pos + 1); + pos = pos_next; + } else { + break; + } + } + if (op != op1) { + if (op1 == (uint8_t)op1 || !op) break; + if (op != (uint8_t)op1 && op != (uint8_t)(op1 >> 8) + && op != (uint8_t)(op1 >> 16) && op != (uint8_t)(op1 >> 24)) { + break; + } + s->op = op; + } + + pos++; + switch (opcode_info[op].fmt) { + case OP_FMT_loc8: + case OP_FMT_u8: { + int idx = tab[pos]; + int arg = va_arg (ap, int); + if (arg == -1) { + s->idx = idx; + } else { + if (arg != idx) goto done; + } + break; + } + case OP_FMT_u16: + case OP_FMT_npop: + case OP_FMT_loc: + case OP_FMT_arg: { + int idx = get_u16 (tab + pos); + int arg = va_arg (ap, int); + if (arg == -1) { + s->idx = idx; + } else { + if (arg != idx) goto done; + } + break; + } + case OP_FMT_i32: + case OP_FMT_u32: + case OP_FMT_label: + case OP_FMT_const: { + s->label = get_u32 (tab + pos); + break; + } + case OP_FMT_label_u16: { + s->label = get_u32 (tab + pos); + s->val = get_u16 (tab + pos + 4); + break; + } + case OP_FMT_key: { + /* Store cpool index in the label field */ + s->label = get_u32 (tab + pos); + break; + } + case OP_FMT_key_u8: { + s->label = get_u32 (tab + pos); + s->val = get_u8 (tab + pos + 4); + break; + } + case OP_FMT_key_u16: { + s->label = get_u32 (tab + pos); + s->val = get_u16 (tab + pos + 4); + break; + } + case OP_FMT_key_label_u16: { + s->label = get_u32 (tab + pos); + s->label = get_u32 (tab + pos + 4); + s->val = get_u16 (tab + pos + 8); + break; + } + default: + break; + } + pos = pos_next; + } +done: + va_end (ap); + return ret; +} + +static void instantiate_hoisted_definitions (JSContext *ctx, JSFunctionDef *s, DynBuf *bc) { + int i, idx; + + /* add the hoisted functions in arguments and local variables */ + for (i = 0; i < s->arg_count; i++) { + JSVarDef *vd = &s->args[i]; + if (vd->func_pool_idx >= 0) { + dbuf_putc (bc, OP_fclosure); + dbuf_put_u32 (bc, vd->func_pool_idx); + dbuf_putc (bc, OP_put_arg); + dbuf_put_u16 (bc, i); + } + } + for (i = 0; i < s->var_count; i++) { + JSVarDef *vd = &s->vars[i]; + if (vd->scope_level == 0 && vd->func_pool_idx >= 0) { + dbuf_putc (bc, OP_fclosure); + dbuf_put_u32 (bc, vd->func_pool_idx); + dbuf_putc (bc, OP_put_loc); + dbuf_put_u16 (bc, i); + } + } + + /* add the global variables (only happens if s->is_global_var is + true) */ + for (i = 0; i < s->global_var_count; i++) { + JSGlobalVar *hf = &s->global_vars[i]; + int has_closure = 0; + BOOL force_init = hf->force_init; + /* we are in an eval, so the closure contains all the + enclosing variables */ + /* If the outer function has a variable environment, + create a property for the variable there */ + for (idx = 0; idx < s->closure_var_count; idx++) { + JSClosureVar *cv = &s->closure_var[idx]; + if (js_key_equal (cv->var_name, hf->var_name)) { + has_closure = 2; + force_init = FALSE; + break; + } + if (js_key_equal_str (cv->var_name, "_var_") + || js_key_equal_str (cv->var_name, "_arg_var_")) { + int slot, depth = compute_closure_depth_slot(s, idx, &slot); + dbuf_putc (bc, OP_get_up); + dbuf_putc (bc, (uint8_t)depth); + dbuf_put_u16 (bc, (uint16_t)slot); + has_closure = 1; + force_init = TRUE; + break; + } + } + if (!has_closure) { + int flags; + + flags = 0; + if (hf->cpool_idx >= 0 && !hf->is_lexical) { + /* global function definitions need a specific handling */ + dbuf_putc (bc, OP_fclosure); + dbuf_put_u32 (bc, hf->cpool_idx); + + { + int key_idx = fd_cpool_add (ctx, s, hf->var_name); + dbuf_putc (bc, OP_define_func); + dbuf_put_u32 (bc, key_idx); + dbuf_putc (bc, flags); + } + + goto done_global_var; + } else { + if (hf->is_lexical) { flags |= DEFINE_GLOBAL_LEX_VAR; } + int key_idx = fd_cpool_add (ctx, s, hf->var_name); + dbuf_putc (bc, OP_define_var); + dbuf_put_u32 (bc, key_idx); + dbuf_putc (bc, flags); + } + } + if (hf->cpool_idx >= 0 || force_init) { + if (hf->cpool_idx >= 0) { + dbuf_putc (bc, OP_fclosure); + dbuf_put_u32 (bc, hf->cpool_idx); + if (js_key_equal_str (hf->var_name, "_default_")) { + /* set default export function name */ + int name_idx = fd_cpool_add (ctx, s, JS_KEY_default); + dbuf_putc (bc, OP_set_name); + dbuf_put_u32 (bc, name_idx); + } + } else { + dbuf_putc (bc, OP_null); + } + if (has_closure == 2) { + int slot, depth = compute_closure_depth_slot(s, idx, &slot); + dbuf_putc (bc, OP_set_up); + dbuf_putc (bc, (uint8_t)depth); + dbuf_put_u16 (bc, (uint16_t)slot); + } else if (has_closure == 1) { + int key_idx = fd_cpool_add (ctx, s, hf->var_name); + dbuf_putc (bc, OP_define_field); + dbuf_put_u32 (bc, key_idx); + dbuf_putc (bc, OP_drop); + } else { + /* XXX: Check if variable is writable and enumerable */ + int key_idx = fd_cpool_add (ctx, s, hf->var_name); + dbuf_putc (bc, OP_put_var); + dbuf_put_u32 (bc, key_idx); + } + } + done_global_var:; + } + + js_free (ctx, s->global_vars); + s->global_vars = NULL; + s->global_var_count = 0; + s->global_var_size = 0; +} + +static int skip_dead_code (JSFunctionDef *s, const uint8_t *bc_buf, int bc_len, int pos, int *linep) { + int op, len, label; + + for (; pos < bc_len; pos += len) { + op = bc_buf[pos]; + len = opcode_info[op].size; + if (op == OP_line_num) { + *linep = get_u32 (bc_buf + pos + 1); + } else if (op == OP_label) { + label = get_u32 (bc_buf + pos + 1); + if (update_label (s, label, 0) > 0) break; + assert (s->label_slots[label].first_reloc == NULL); + } else { + /* XXX: output a warning for unreachable code? */ + switch (opcode_info[op].fmt) { + case OP_FMT_label: + case OP_FMT_label_u16: + label = get_u32 (bc_buf + pos + 1); + update_label (s, label, -1); + break; + case OP_FMT_key_label_u16: + label = get_u32 (bc_buf + pos + 5); + update_label (s, label, -1); + /* fall thru */ + case OP_FMT_key: + case OP_FMT_key_u8: + case OP_FMT_key_u16: + /* Key operand is a cpool index; cpool values freed separately */ + break; + default: + break; + } + } + } + return pos; +} + +static int get_label_pos (JSFunctionDef *s, int label) { + int i, pos; + for (i = 0; i < 20; i++) { + pos = s->label_slots[label].pos; + for (;;) { + switch (s->byte_code.buf[pos]) { + case OP_line_num: + case OP_label: + pos += 5; + continue; + case OP_goto: + label = get_u32 (s->byte_code.buf + pos + 1); + break; + default: + return pos; + } + break; + } + } + return pos; +} + +/* convert global variable accesses to local variables or closure + variables when necessary */ +static __exception int resolve_variables (JSContext *ctx, JSFunctionDef *s) { + int pos, pos_next, bc_len, op, len, i, idx, line_num; + uint8_t *bc_buf; + JSValue var_name; + int cpool_idx; + DynBuf bc_out; + CodeContext cc; + int scope; + + cc.bc_buf = bc_buf = s->byte_code.buf; + cc.bc_len = bc_len = s->byte_code.size; + js_dbuf_init (ctx, &bc_out); + + /* first pass for runtime checks (must be done before the + variables are created) */ + for (i = 0; i < s->global_var_count; i++) { + JSGlobalVar *hf = &s->global_vars[i]; + int flags; + + /* check if global variable (XXX: simplify) */ + for (idx = 0; idx < s->closure_var_count; idx++) { + JSClosureVar *cv = &s->closure_var[idx]; + if (js_key_equal (cv->var_name, hf->var_name)) { + goto next; + } + if (js_key_equal_str (cv->var_name, "") + || js_key_equal_str (cv->var_name, "")) + goto next; + } + + { + int key_idx = fd_cpool_add (ctx, s, hf->var_name); + dbuf_putc (&bc_out, OP_check_define_var); + dbuf_put_u32 (&bc_out, key_idx); + } + flags = 0; + if (hf->is_lexical) flags |= DEFINE_GLOBAL_LEX_VAR; + if (hf->cpool_idx >= 0) flags |= DEFINE_GLOBAL_FUNC_VAR; + dbuf_putc (&bc_out, flags); + next:; + } + + line_num = 0; /* avoid warning */ + for (pos = 0; pos < bc_len; pos = pos_next) { + op = bc_buf[pos]; + len = opcode_info[op].size; + pos_next = pos + len; + switch (op) { + case OP_line_num: + line_num = get_u32 (bc_buf + pos + 1); + s->line_number_size++; + goto no_change; + + case OP_scope_get_var_checkthis: + case OP_scope_get_var_undef: + case OP_scope_get_var: + case OP_scope_put_var: + case OP_scope_delete_var: + case OP_scope_put_var_init: + cpool_idx = get_u32 (bc_buf + pos + 1); + var_name = s->cpool[cpool_idx]; + scope = get_u16 (bc_buf + pos + 5); + pos_next = resolve_scope_var (ctx, s, var_name, scope, op, &bc_out, NULL, NULL, pos_next); + break; + case OP_gosub: + s->jump_size++; + if (OPTIMIZE) { + /* remove calls to empty finalizers */ + int label; + LabelSlot *ls; + + label = get_u32 (bc_buf + pos + 1); + assert (label >= 0 && label < s->label_count); + ls = &s->label_slots[label]; + if (code_match (&cc, ls->pos, OP_ret, -1)) { + ls->ref_count--; + break; + } + } + goto no_change; + case OP_drop: + if (0) { + /* remove drops before return_undef */ + /* do not perform this optimization in pass2 because + it breaks patterns recognised in resolve_labels */ + int pos1 = pos_next; + int line1 = line_num; + while (code_match (&cc, pos1, OP_drop, -1)) { + if (cc.line_num >= 0) line1 = cc.line_num; + pos1 = cc.pos; + } + if (code_match (&cc, pos1, OP_return_undef, -1)) { + pos_next = pos1; + if (line1 != -1 && line1 != line_num) { + line_num = line1; + s->line_number_size++; + dbuf_putc (&bc_out, OP_line_num); + dbuf_put_u32 (&bc_out, line_num); + } + break; + } + } + goto no_change; + case OP_insert3: + if (OPTIMIZE) { + /* Transformation: insert3 put_array_el drop -> put_array_el */ + if (code_match (&cc, pos_next, OP_put_array_el, OP_drop, -1)) { + dbuf_putc (&bc_out, cc.op); + pos_next = cc.pos; + if (cc.line_num != -1 && cc.line_num != line_num) { + line_num = cc.line_num; + s->line_number_size++; + dbuf_putc (&bc_out, OP_line_num); + dbuf_put_u32 (&bc_out, line_num); + } + break; + } + } + goto no_change; + + case OP_goto: + s->jump_size++; + /* fall thru */ + case OP_tail_call: + case OP_tail_call_method: + case OP_return: + case OP_return_undef: + case OP_throw: + case OP_throw_error: + case OP_ret: + if (OPTIMIZE) { + /* remove dead code */ + int line = -1; + dbuf_put (&bc_out, bc_buf + pos, len); + pos = skip_dead_code (s, bc_buf, bc_len, pos + len, &line); + pos_next = pos; + if (pos < bc_len && line >= 0 && line_num != line) { + line_num = line; + s->line_number_size++; + dbuf_putc (&bc_out, OP_line_num); + dbuf_put_u32 (&bc_out, line_num); + } + break; + } + goto no_change; + + case OP_label: { + int label; + LabelSlot *ls; + + label = get_u32 (bc_buf + pos + 1); + assert (label >= 0 && label < s->label_count); + ls = &s->label_slots[label]; + ls->pos2 = bc_out.size + opcode_info[op].size; + } + goto no_change; + + case OP_enter_scope: { + int scope_idx, scope = get_u16 (bc_buf + pos + 1); + + if (scope == s->body_scope) { + instantiate_hoisted_definitions (ctx, s, &bc_out); + } + + for (scope_idx = s->scopes[scope].first; scope_idx >= 0;) { + JSVarDef *vd = &s->vars[scope_idx]; + if (vd->scope_level != scope) break; + + if (vd->var_kind == JS_VAR_FUNCTION_DECL + || vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) { + /* Initialize lexical variable upon entering scope */ + dbuf_putc (&bc_out, OP_fclosure); + dbuf_put_u32 (&bc_out, vd->func_pool_idx); + dbuf_putc (&bc_out, OP_put_loc); + dbuf_put_u16 (&bc_out, scope_idx); + } else { + /* XXX: should check if variable can be used + before initialization */ + dbuf_putc (&bc_out, OP_set_loc_uninitialized); + dbuf_put_u16 (&bc_out, scope_idx); + } + + scope_idx = vd->scope_next; + } + } break; + + case OP_leave_scope: + /* With outer_frame model, captured variables don't need closing */ + break; + + case OP_set_name: { + /* remove dummy set_name opcodes */ + int name_idx = get_u32 (bc_buf + pos + 1); + if (JS_IsNull (s->cpool[name_idx])) break; + } + goto no_change; + + case OP_if_false: + case OP_if_true: + case OP_catch: + s->jump_size++; + goto no_change; + + case OP_dup: + if (OPTIMIZE) { + /* Transformation: dup if_false(l1) drop, l1: if_false(l2) -> + * if_false(l2) */ + /* Transformation: dup if_true(l1) drop, l1: if_true(l2) -> if_true(l2) + */ + if (code_match (&cc, pos_next, M2 (OP_if_false, OP_if_true), OP_drop, -1)) { + int lab0, lab1, op1, pos1, line1, pos2; + lab0 = lab1 = cc.label; + assert (lab1 >= 0 && lab1 < s->label_count); + op1 = cc.op; + pos1 = cc.pos; + line1 = cc.line_num; + while (code_match (&cc, (pos2 = get_label_pos (s, lab1)), OP_dup, op1, OP_drop, -1)) { + lab1 = cc.label; + } + if (code_match (&cc, pos2, op1, -1)) { + s->jump_size++; + update_label (s, lab0, -1); + update_label (s, cc.label, +1); + dbuf_putc (&bc_out, op1); + dbuf_put_u32 (&bc_out, cc.label); + pos_next = pos1; + if (line1 != -1 && line1 != line_num) { + line_num = line1; + s->line_number_size++; + dbuf_putc (&bc_out, OP_line_num); + dbuf_put_u32 (&bc_out, line_num); + } + break; + } + } + } + goto no_change; + + case OP_nop: + /* remove erased code */ + break; + case OP_set_class_name: + /* only used during parsing */ + break; + + case OP_get_field_opt_chain: /* equivalent to OP_get_field */ + { + int key_idx = get_u32 (bc_buf + pos + 1); + dbuf_putc (&bc_out, OP_get_field); + dbuf_put_u32 (&bc_out, key_idx); + } break; + case OP_get_array_el_opt_chain: /* equivalent to OP_get_array_el */ + dbuf_putc (&bc_out, OP_get_array_el); + break; + + default: + no_change: + dbuf_put (&bc_out, bc_buf + pos, len); + break; + } + } + + /* set the new byte code */ + dbuf_free (&s->byte_code); + s->byte_code = bc_out; + if (dbuf_error (&s->byte_code)) { + JS_ThrowOutOfMemory (ctx); + return -1; + } + return 0; +} + +/* the pc2line table gives a source position for each PC value */ +static void add_pc2line_info (JSFunctionDef *s, uint32_t pc, uint32_t source_pos) { + if (s->line_number_slots != NULL + && s->line_number_count < s->line_number_size + && pc >= s->line_number_last_pc && source_pos != s->line_number_last) { + s->line_number_slots[s->line_number_count].pc = pc; + s->line_number_slots[s->line_number_count].source_pos = source_pos; + s->line_number_count++; + s->line_number_last_pc = pc; + s->line_number_last = source_pos; + } +} + +/* XXX: could use a more compact storage */ +/* XXX: get_line_col_cached() is slow. For more predictable + performance, line/cols could be stored every N source + bytes. Alternatively, get_line_col_cached() could be issued in + emit_source_pos() so that the deltas are more likely to be + small. */ +static void compute_pc2line_info (JSFunctionDef *s) { + if (!s->strip_debug) { + int last_line_num, last_col_num; + uint32_t last_pc = 0; + int i, line_num, col_num; + const uint8_t *buf_start = s->get_line_col_cache->buf_start; + js_dbuf_init (s->ctx, &s->pc2line); + + last_line_num = get_line_col_cached (s->get_line_col_cache, &last_col_num, buf_start + s->source_pos); + dbuf_put_leb128 (&s->pc2line, last_line_num); /* line number minus 1 */ + dbuf_put_leb128 (&s->pc2line, last_col_num); /* column number minus 1 */ + + for (i = 0; i < s->line_number_count; i++) { + uint32_t pc = s->line_number_slots[i].pc; + uint32_t source_pos = s->line_number_slots[i].source_pos; + int diff_pc, diff_line, diff_col; + + if (source_pos == -1) continue; + diff_pc = pc - last_pc; + if (diff_pc < 0) continue; + + line_num = get_line_col_cached (s->get_line_col_cache, &col_num, buf_start + source_pos); + diff_line = line_num - last_line_num; + diff_col = col_num - last_col_num; + if (diff_line == 0 && diff_col == 0) continue; + + if (diff_line >= PC2LINE_BASE && diff_line < PC2LINE_BASE + PC2LINE_RANGE + && diff_pc <= PC2LINE_DIFF_PC_MAX) { + dbuf_putc (&s->pc2line, (diff_line - PC2LINE_BASE) + diff_pc * PC2LINE_RANGE + PC2LINE_OP_FIRST); + } else { + /* longer encoding */ + dbuf_putc (&s->pc2line, 0); + dbuf_put_leb128 (&s->pc2line, diff_pc); + dbuf_put_sleb128 (&s->pc2line, diff_line); + } + dbuf_put_sleb128 (&s->pc2line, diff_col); + + last_pc = pc; + last_line_num = line_num; + last_col_num = col_num; + } + } +} + +static RelocEntry *add_reloc (JSContext *ctx, LabelSlot *ls, uint32_t addr, int size) { + RelocEntry *re; + (void)ctx; + re = pjs_malloc (sizeof (*re)); + if (!re) return NULL; + re->addr = addr; + re->size = size; + re->next = ls->first_reloc; + ls->first_reloc = re; + return re; +} + +static BOOL code_has_label (CodeContext *s, int pos, int label) { + while (pos < s->bc_len) { + int op = s->bc_buf[pos]; + if (op == OP_line_num) { + pos += 5; + continue; + } + if (op == OP_label) { + int lab = get_u32 (s->bc_buf + pos + 1); + if (lab == label) return TRUE; + pos += 5; + continue; + } + if (op == OP_goto) { + int lab = get_u32 (s->bc_buf + pos + 1); + if (lab == label) return TRUE; + } + break; + } + return FALSE; +} + +/* return the target label, following the OP_goto jumps + the first opcode at destination is stored in *pop + */ +static int find_jump_target (JSFunctionDef *s, int label0, int *pop, int *pline) { + int i, pos, op, label; + + label = label0; + update_label (s, label, -1); + for (i = 0; i < 10; i++) { + assert (label >= 0 && label < s->label_count); + pos = s->label_slots[label].pos2; + for (;;) { + switch (op = s->byte_code.buf[pos]) { + case OP_line_num: + if (pline) *pline = get_u32 (s->byte_code.buf + pos + 1); + /* fall thru */ + case OP_label: + pos += opcode_info[op].size; + continue; + case OP_goto: + label = get_u32 (s->byte_code.buf + pos + 1); + break; + case OP_drop: + /* ignore drop opcodes if followed by OP_return_undef */ + while (s->byte_code.buf[++pos] == OP_drop) + continue; + if (s->byte_code.buf[pos] == OP_return_undef) op = OP_return_undef; + /* fall thru */ + default: + goto done; + } + break; + } + } + /* cycle detected, could issue a warning */ + /* XXX: the combination of find_jump_target() and skip_dead_code() + seems incorrect with cyclic labels. See for exemple: + + for (;;) { + l:break l; + l:break l; + l:break l; + l:break l; + } + + Avoiding changing the target is just a workaround and might not + suffice to completely fix the problem. */ + label = label0; +done: + *pop = op; + update_label (s, label, +1); + return label; +} + +static void push_short_int (DynBuf *bc_out, int val) { +#if SHORT_OPCODES + if (val >= -1 && val <= 7) { + dbuf_putc (bc_out, OP_push_0 + val); + return; + } + if (val == (int8_t)val) { + dbuf_putc (bc_out, OP_push_i8); + dbuf_putc (bc_out, val); + return; + } + if (val == (int16_t)val) { + dbuf_putc (bc_out, OP_push_i16); + dbuf_put_u16 (bc_out, val); + return; + } +#endif + dbuf_putc (bc_out, OP_push_i32); + dbuf_put_u32 (bc_out, val); +} + +static void put_short_code (DynBuf *bc_out, int op, int idx) { +#if SHORT_OPCODES + if (idx < 4) { + switch (op) { + case OP_get_loc: + dbuf_putc (bc_out, OP_get_loc0 + idx); + return; + case OP_put_loc: + dbuf_putc (bc_out, OP_put_loc0 + idx); + return; + case OP_set_loc: + dbuf_putc (bc_out, OP_set_loc0 + idx); + return; + case OP_get_arg: + dbuf_putc (bc_out, OP_get_arg0 + idx); + return; + case OP_put_arg: + dbuf_putc (bc_out, OP_put_arg0 + idx); + return; + case OP_set_arg: + dbuf_putc (bc_out, OP_set_arg0 + idx); + return; + case OP_call: + dbuf_putc (bc_out, OP_call0 + idx); + return; + } + } + if (idx < 256) { + switch (op) { + case OP_get_loc: + dbuf_putc (bc_out, OP_get_loc8); + dbuf_putc (bc_out, idx); + return; + case OP_put_loc: + dbuf_putc (bc_out, OP_put_loc8); + dbuf_putc (bc_out, idx); + return; + case OP_set_loc: + dbuf_putc (bc_out, OP_set_loc8); + dbuf_putc (bc_out, idx); + return; + } + } +#endif + dbuf_putc (bc_out, op); + dbuf_put_u16 (bc_out, idx); +} + +/* peephole optimizations and resolve goto/labels */ +static __exception int resolve_labels (JSContext *ctx, JSFunctionDef *s) { + int pos, pos_next, bc_len, op, op1, len, i, line_num; + const uint8_t *bc_buf; + DynBuf bc_out; + LabelSlot *label_slots, *ls; + RelocEntry *re, *re_next; + CodeContext cc; + int label; +#if SHORT_OPCODES + JumpSlot *jp; +#endif + + label_slots = s->label_slots; + + line_num = s->source_pos; + + cc.bc_buf = bc_buf = s->byte_code.buf; + cc.bc_len = bc_len = s->byte_code.size; + js_dbuf_init (ctx, &bc_out); + +#if SHORT_OPCODES + if (s->jump_size) { + s->jump_slots + = pjs_mallocz (sizeof (*s->jump_slots) * s->jump_size); + if (s->jump_slots == NULL) return -1; + } +#endif + /* XXX: Should skip this phase if not generating SHORT_OPCODES */ + if (s->line_number_size && !s->strip_debug) { + s->line_number_slots = pjs_mallocz (sizeof (*s->line_number_slots) * s->line_number_size); + if (s->line_number_slots == NULL) return -1; + s->line_number_last = s->source_pos; + s->line_number_last_pc = 0; + } + + /* initialize the 'this.active_func' variable if needed */ + if (s->this_active_func_var_idx >= 0) { + dbuf_putc (&bc_out, OP_special_object); + dbuf_putc (&bc_out, OP_SPECIAL_OBJECT_THIS_FUNC); + put_short_code (&bc_out, OP_put_loc, s->this_active_func_var_idx); + } + /* initialize the 'this' variable if needed */ + if (s->this_var_idx >= 0) { + dbuf_putc (&bc_out, OP_push_this); + put_short_code (&bc_out, OP_put_loc, s->this_var_idx); + } + /* initialize a reference to the current function if needed */ + if (s->func_var_idx >= 0) { + dbuf_putc (&bc_out, OP_special_object); + dbuf_putc (&bc_out, OP_SPECIAL_OBJECT_THIS_FUNC); + put_short_code (&bc_out, OP_put_loc, s->func_var_idx); + } + /* initialize the variable environment object if needed */ + if (s->var_object_idx >= 0) { + dbuf_putc (&bc_out, OP_special_object); + dbuf_putc (&bc_out, OP_SPECIAL_OBJECT_VAR_OBJECT); + put_short_code (&bc_out, OP_put_loc, s->var_object_idx); + } + if (s->arg_var_object_idx >= 0) { + dbuf_putc (&bc_out, OP_special_object); + dbuf_putc (&bc_out, OP_SPECIAL_OBJECT_VAR_OBJECT); + put_short_code (&bc_out, OP_put_loc, s->arg_var_object_idx); + } + + for (pos = 0; pos < bc_len; pos = pos_next) { + int val; + op = bc_buf[pos]; + len = opcode_info[op].size; + pos_next = pos + len; + switch (op) { + case OP_line_num: + /* line number info (for debug). We put it in a separate + compressed table to reduce memory usage and get better + performance */ + line_num = get_u32 (bc_buf + pos + 1); + break; + + case OP_label: { + label = get_u32 (bc_buf + pos + 1); + assert (label >= 0 && label < s->label_count); + ls = &label_slots[label]; + assert (ls->addr == -1); + ls->addr = bc_out.size; + /* resolve the relocation entries */ + for (re = ls->first_reloc; re != NULL; re = re_next) { + int diff = ls->addr - re->addr; + re_next = re->next; + switch (re->size) { + case 4: + put_u32 (bc_out.buf + re->addr, diff); + break; + case 2: + assert (diff == (int16_t)diff); + put_u16 (bc_out.buf + re->addr, diff); + break; + case 1: + assert (diff == (int8_t)diff); + put_u8 (bc_out.buf + re->addr, diff); + break; + } + pjs_free (re); + } + ls->first_reloc = NULL; + } break; + + case OP_call: + case OP_call_method: { + /* detect and transform tail calls */ + int argc; + argc = get_u16 (bc_buf + pos + 1); + if (code_match (&cc, pos_next, OP_return, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + add_pc2line_info (s, bc_out.size, line_num); + put_short_code (&bc_out, op + 1, argc); + pos_next = skip_dead_code (s, bc_buf, bc_len, cc.pos, &line_num); + break; + } + add_pc2line_info (s, bc_out.size, line_num); + put_short_code (&bc_out, op, argc); + break; + } + goto no_change; + + case OP_return: + case OP_return_undef: + case OP_throw: + case OP_throw_error: + pos_next = skip_dead_code (s, bc_buf, bc_len, pos_next, &line_num); + goto no_change; + + case OP_goto: + label = get_u32 (bc_buf + pos + 1); + has_goto: + if (OPTIMIZE) { + int line1 = -1; + /* Use custom matcher because multiple labels can follow */ + label = find_jump_target (s, label, &op1, &line1); + if (code_has_label (&cc, pos_next, label)) { + /* jump to next instruction: remove jump */ + update_label (s, label, -1); + break; + } + if (op1 == OP_return || op1 == OP_return_undef || op1 == OP_throw) { + /* jump to return/throw: remove jump, append return/throw */ + /* updating the line number obfuscates assembly listing */ + // if (line1 != -1) line_num = line1; + update_label (s, label, -1); + add_pc2line_info (s, bc_out.size, line_num); + dbuf_putc (&bc_out, op1); + pos_next = skip_dead_code (s, bc_buf, bc_len, pos_next, &line_num); + break; + } + /* XXX: should duplicate single instructions followed by goto or return + */ + /* For example, can match one of these followed by return: + push_i32 / push_const / push_atom_value / get_var / + undefined / null / push_false / push_true / get_ref_value / + get_loc / get_arg / get_var_ref + */ + } + goto has_label; + + case OP_gosub: + label = get_u32 (bc_buf + pos + 1); + if (0 && OPTIMIZE) { + label = find_jump_target (s, label, &op1, NULL); + if (op1 == OP_ret) { + update_label (s, label, -1); + /* empty finally clause: remove gosub */ + break; + } + } + goto has_label; + + case OP_catch: + label = get_u32 (bc_buf + pos + 1); + goto has_label; + + case OP_if_true: + case OP_if_false: + label = get_u32 (bc_buf + pos + 1); + if (OPTIMIZE) { + label = find_jump_target (s, label, &op1, NULL); + /* transform if_false/if_true(l1) label(l1) -> drop label(l1) */ + if (code_has_label (&cc, pos_next, label)) { + update_label (s, label, -1); + dbuf_putc (&bc_out, OP_drop); + break; + } + /* transform if_false(l1) goto(l2) label(l1) -> if_false(l2) label(l1) + */ + if (code_match (&cc, pos_next, OP_goto, -1)) { + int pos1 = cc.pos; + int line1 = cc.line_num; + if (code_has_label (&cc, pos1, label)) { + if (line1 != -1) line_num = line1; + pos_next = pos1; + update_label (s, label, -1); + label = cc.label; + op ^= OP_if_true ^ OP_if_false; + } + } + } + has_label: + add_pc2line_info (s, bc_out.size, line_num); + if (op == OP_goto) { + pos_next = skip_dead_code (s, bc_buf, bc_len, pos_next, &line_num); + } + assert (label >= 0 && label < s->label_count); + ls = &label_slots[label]; +#if SHORT_OPCODES + jp = &s->jump_slots[s->jump_count++]; + jp->op = op; + jp->size = 4; + jp->pos = bc_out.size + 1; + jp->label = label; + + if (ls->addr == -1) { + int diff = ls->pos2 - pos - 1; + if (diff < 128 + && (op == OP_if_false || op == OP_if_true || op == OP_goto)) { + jp->size = 1; + jp->op = OP_if_false8 + (op - OP_if_false); + dbuf_putc (&bc_out, OP_if_false8 + (op - OP_if_false)); + dbuf_putc (&bc_out, 0); + if (!add_reloc (ctx, ls, bc_out.size - 1, 1)) goto fail; + break; + } + if (diff < 32768 && op == OP_goto) { + jp->size = 2; + jp->op = OP_goto16; + dbuf_putc (&bc_out, OP_goto16); + dbuf_put_u16 (&bc_out, 0); + if (!add_reloc (ctx, ls, bc_out.size - 2, 2)) goto fail; + break; + } + } else { + int diff = ls->addr - bc_out.size - 1; + if (diff == (int8_t)diff + && (op == OP_if_false || op == OP_if_true || op == OP_goto)) { + jp->size = 1; + jp->op = OP_if_false8 + (op - OP_if_false); + dbuf_putc (&bc_out, OP_if_false8 + (op - OP_if_false)); + dbuf_putc (&bc_out, diff); + break; + } + if (diff == (int16_t)diff && op == OP_goto) { + jp->size = 2; + jp->op = OP_goto16; + dbuf_putc (&bc_out, OP_goto16); + dbuf_put_u16 (&bc_out, diff); + break; + } + } +#endif + dbuf_putc (&bc_out, op); + dbuf_put_u32 (&bc_out, ls->addr - bc_out.size); + if (ls->addr == -1) { + /* unresolved yet: create a new relocation entry */ + if (!add_reloc (ctx, ls, bc_out.size - 4, 4)) goto fail; + } + break; + case OP_drop: + if (OPTIMIZE) { + /* remove useless drops before return */ + if (code_match (&cc, pos_next, OP_return_undef, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + break; + } + } + goto no_change; + + case OP_null: + if (OPTIMIZE) { + /* 1) remove null; drop → (nothing) */ + if (code_match (&cc, pos_next, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + pos_next = cc.pos; + break; + } + /* 2) null; return → return_undef */ + if (code_match (&cc, pos_next, OP_return, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + add_pc2line_info (s, bc_out.size, line_num); + dbuf_putc (&bc_out, OP_return_undef); + pos_next = cc.pos; + break; + } + /* 3) null; if_false/if_true → goto or nop */ + if (code_match (&cc, pos_next, M2 (OP_if_false, OP_if_true), -1)) { + val = 0; + goto has_constant_test; + } +#if SHORT_OPCODES + /* 4a) null strict_eq → is_null */ + if (code_match (&cc, pos_next, OP_strict_eq, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + add_pc2line_info (s, bc_out.size, line_num); + dbuf_putc (&bc_out, OP_is_null); + pos_next = cc.pos; + break; + } + /* 4b) null strict_neq; if_false/if_true → is_null + flipped branch */ + if (code_match (&cc, pos_next, OP_strict_neq, M2 (OP_if_false, OP_if_true), -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + add_pc2line_info (s, bc_out.size, line_num); + dbuf_putc (&bc_out, OP_is_null); + pos_next = cc.pos; + label = cc.label; + op = cc.op ^ OP_if_false ^ OP_if_true; + goto has_label; + } +#endif + } + /* didn’t match any of the above? fall straight into the + OP_push_false / OP_push_true constant‐test code: */ + case OP_push_false: + case OP_push_true: + if (OPTIMIZE) { + val = (op == OP_push_true); + if (code_match (&cc, pos_next, M2 (OP_if_false, OP_if_true), -1)) { + has_constant_test: + if (cc.line_num >= 0) line_num = cc.line_num; + if (val == (cc.op - OP_if_false)) { + /* e.g. null if_false(L) → goto L */ + pos_next = cc.pos; + op = OP_goto; + label = cc.label; + goto has_goto; + } else { + /* e.g. null if_true(L) → nop */ + pos_next = cc.pos; + update_label (s, cc.label, -1); + break; + } + } + } + goto no_change; + + case OP_push_i32: + if (OPTIMIZE) { + /* transform i32(val) neg -> i32(-val) */ + val = get_i32 (bc_buf + pos + 1); + if ((val != INT32_MIN && val != 0) + && code_match (&cc, pos_next, OP_neg, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + if (code_match (&cc, cc.pos, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + } else { + add_pc2line_info (s, bc_out.size, line_num); + push_short_int (&bc_out, -val); + } + pos_next = cc.pos; + break; + } + /* remove push/drop pairs generated by the parser */ + if (code_match (&cc, pos_next, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + pos_next = cc.pos; + break; + } + /* Optimize constant tests: `if (0)`, `if (1)`, `if (!0)`... */ + if (code_match (&cc, pos_next, M2 (OP_if_false, OP_if_true), -1)) { + val = (val != 0); + goto has_constant_test; + } + add_pc2line_info (s, bc_out.size, line_num); + push_short_int (&bc_out, val); + break; + } + goto no_change; + +#if SHORT_OPCODES + case OP_push_const: + case OP_fclosure: + if (OPTIMIZE) { + int idx = get_u32 (bc_buf + pos + 1); + if (idx < 256) { + add_pc2line_info (s, bc_out.size, line_num); + dbuf_putc (&bc_out, OP_push_const8 + op - OP_push_const); + dbuf_putc (&bc_out, idx); + break; + } + } + goto no_change; +#endif + case OP_to_propkey: + if (OPTIMIZE) { + /* remove redundant to_propkey opcodes when storing simple data */ + if (code_match (&cc, pos_next, M2 (OP_get_loc, OP_get_arg), -1, OP_put_array_el, -1) + || code_match (&cc, pos_next, M2 (OP_push_i32, OP_push_const), OP_put_array_el, -1) + || code_match (&cc, pos_next, M4 (OP_null, OP_null, OP_push_true, OP_push_false), OP_put_array_el, -1)) { + break; + } + } + goto no_change; + case OP_insert2: + if (OPTIMIZE) { + /* Transformation: + insert2 put_field(a) drop -> put_field(a) + insert2 put_var_strict(a) drop -> put_var_strict(a) + */ + if (code_match (&cc, pos_next, M2 (OP_put_field, OP_put_var_strict), OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + add_pc2line_info (s, bc_out.size, line_num); + dbuf_putc (&bc_out, cc.op); + dbuf_put_u32 (&bc_out, cc.label); + pos_next = cc.pos; + break; + } + } + goto no_change; + + case OP_dup: + if (OPTIMIZE) { + /* Transformation: dup put_x(n) drop -> put_x(n) */ + int op1, line2 = -1; + /* Transformation: dup put_x(n) -> set_x(n) */ + if (code_match (&cc, pos_next, M2 (OP_put_loc, OP_put_arg), -1, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + op1 = cc.op + 1; /* put_x -> set_x */ + pos_next = cc.pos; + if (code_match (&cc, cc.pos, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + op1 -= 1; /* set_x drop -> put_x */ + pos_next = cc.pos; + if (code_match (&cc, cc.pos, op1 - 1, cc.idx, -1)) { + line2 = cc.line_num; /* delay line number update */ + op1 += 1; /* put_x(n) get_x(n) -> set_x(n) */ + pos_next = cc.pos; + } + } + add_pc2line_info (s, bc_out.size, line_num); + put_short_code (&bc_out, op1, cc.idx); + if (line2 >= 0) line_num = line2; + break; + } + } + goto no_change; + + case OP_get_loc: + if (OPTIMIZE) { + /* transformation: + get_loc(n) post_dec put_loc(n) drop -> dec_loc(n) + get_loc(n) post_inc put_loc(n) drop -> inc_loc(n) + get_loc(n) dec dup put_loc(n) drop -> dec_loc(n) + get_loc(n) inc dup put_loc(n) drop -> inc_loc(n) + */ + int idx; + idx = get_u16 (bc_buf + pos + 1); + if (idx >= 256) goto no_change; + if (code_match (&cc, pos_next, M2 (OP_post_dec, OP_post_inc), OP_put_loc, idx, OP_drop, -1) + || code_match (&cc, pos_next, M2 (OP_dec, OP_inc), OP_dup, OP_put_loc, idx, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + add_pc2line_info (s, bc_out.size, line_num); + dbuf_putc (&bc_out, (cc.op == OP_inc || cc.op == OP_post_inc) ? OP_inc_loc : OP_dec_loc); + dbuf_putc (&bc_out, idx); + pos_next = cc.pos; + break; + } + /* transformation: + get_loc(n) push_i32(x) add dup put_loc(n) drop -> push_i32(x) + add_loc(n) + */ + if (code_match (&cc, pos_next, OP_push_i32, OP_add, OP_dup, OP_put_loc, idx, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + add_pc2line_info (s, bc_out.size, line_num); + push_short_int (&bc_out, cc.label); + dbuf_putc (&bc_out, OP_add_loc); + dbuf_putc (&bc_out, idx); + pos_next = cc.pos; + break; + } + /* transformation: + get_loc(n) get_loc(x) add dup put_loc(n) drop -> get_loc(x) add_loc(n) + get_loc(n) get_arg(x) add dup put_loc(n) drop -> get_arg(x) add_loc(n) + */ + if (code_match (&cc, pos_next, M2 (OP_get_loc, OP_get_arg), -1, OP_add, OP_dup, OP_put_loc, idx, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + add_pc2line_info (s, bc_out.size, line_num); + put_short_code (&bc_out, cc.op, cc.idx); + dbuf_putc (&bc_out, OP_add_loc); + dbuf_putc (&bc_out, idx); + pos_next = cc.pos; + break; + } + add_pc2line_info (s, bc_out.size, line_num); + put_short_code (&bc_out, op, idx); + break; + } + goto no_change; +#if SHORT_OPCODES + case OP_get_arg: + if (OPTIMIZE) { + int idx; + idx = get_u16 (bc_buf + pos + 1); + add_pc2line_info (s, bc_out.size, line_num); + put_short_code (&bc_out, op, idx); + break; + } + goto no_change; +#endif + case OP_put_loc: + case OP_put_arg: + if (OPTIMIZE) { + /* transformation: put_x(n) get_x(n) -> set_x(n) */ + int idx; + idx = get_u16 (bc_buf + pos + 1); + if (code_match (&cc, pos_next, op - 1, idx, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + add_pc2line_info (s, bc_out.size, line_num); + put_short_code (&bc_out, op + 1, idx); + pos_next = cc.pos; + break; + } + add_pc2line_info (s, bc_out.size, line_num); + put_short_code (&bc_out, op, idx); + break; + } + goto no_change; + + case OP_post_inc: + case OP_post_dec: + if (OPTIMIZE) { + /* transformation: + post_inc put_x drop -> inc put_x + post_inc perm3 put_field drop -> inc put_field + post_inc perm3 put_var_strict drop -> inc put_var_strict + post_inc perm4 put_array_el drop -> inc put_array_el + */ + int op1, idx; + if (code_match (&cc, pos_next, M2 (OP_put_loc, OP_put_arg), -1, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + op1 = cc.op; + idx = cc.idx; + pos_next = cc.pos; + if (code_match (&cc, cc.pos, op1 - 1, idx, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + op1 += 1; /* put_x(n) get_x(n) -> set_x(n) */ + pos_next = cc.pos; + } + add_pc2line_info (s, bc_out.size, line_num); + dbuf_putc (&bc_out, OP_dec + (op - OP_post_dec)); + put_short_code (&bc_out, op1, idx); + break; + } + if (code_match (&cc, pos_next, OP_perm3, M2 (OP_put_field, OP_put_var_strict), OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + add_pc2line_info (s, bc_out.size, line_num); + dbuf_putc (&bc_out, OP_dec + (op - OP_post_dec)); + dbuf_putc (&bc_out, cc.op); + dbuf_put_u32 (&bc_out, cc.label); + pos_next = cc.pos; + break; + } + if (code_match (&cc, pos_next, OP_perm4, OP_put_array_el, OP_drop, -1)) { + if (cc.line_num >= 0) line_num = cc.line_num; + add_pc2line_info (s, bc_out.size, line_num); + dbuf_putc (&bc_out, OP_dec + (op - OP_post_dec)); + dbuf_putc (&bc_out, OP_put_array_el); + pos_next = cc.pos; + break; + } + } + goto no_change; + + default: + no_change: + add_pc2line_info (s, bc_out.size, line_num); + dbuf_put (&bc_out, bc_buf + pos, len); + break; + } + } + + /* check that there were no missing labels */ + for (i = 0; i < s->label_count; i++) { + assert (label_slots[i].first_reloc == NULL); + } +#if SHORT_OPCODES + if (OPTIMIZE) { + /* more jump optimizations */ + int patch_offsets = 0; + for (i = 0, jp = s->jump_slots; i < s->jump_count; i++, jp++) { + LabelSlot *ls; + JumpSlot *jp1; + int j, pos, diff, delta; + + delta = 3; + switch (op = jp->op) { + case OP_goto16: + delta = 1; + /* fall thru */ + case OP_if_false: + case OP_if_true: + case OP_goto: + pos = jp->pos; + diff = s->label_slots[jp->label].addr - pos; + if (diff >= -128 && diff <= 127 + delta) { + // put_u8(bc_out.buf + pos, diff); + jp->size = 1; + if (op == OP_goto16) { + bc_out.buf[pos - 1] = jp->op = OP_goto8; + } else { + bc_out.buf[pos - 1] = jp->op = OP_if_false8 + (op - OP_if_false); + } + goto shrink; + } else if (diff == (int16_t)diff && op == OP_goto) { + // put_u16(bc_out.buf + pos, diff); + jp->size = 2; + delta = 2; + bc_out.buf[pos - 1] = jp->op = OP_goto16; + shrink: + /* XXX: should reduce complexity, using 2 finger copy scheme */ + memmove (bc_out.buf + pos + jp->size, + bc_out.buf + pos + jp->size + delta, + bc_out.size - pos - jp->size - delta); + bc_out.size -= delta; + patch_offsets++; + for (j = 0, ls = s->label_slots; j < s->label_count; j++, ls++) { + if (ls->addr > pos) ls->addr -= delta; + } + for (j = i + 1, jp1 = jp + 1; j < s->jump_count; j++, jp1++) { + if (jp1->pos > pos) jp1->pos -= delta; + } + for (j = 0; j < s->line_number_count; j++) { + if (s->line_number_slots[j].pc > pos) + s->line_number_slots[j].pc -= delta; + } + continue; + } + break; + } + } + if (patch_offsets) { + JumpSlot *jp1; + int j; + for (j = 0, jp1 = s->jump_slots; j < s->jump_count; j++, jp1++) { + int diff1 = s->label_slots[jp1->label].addr - jp1->pos; + switch (jp1->size) { + case 1: + put_u8 (bc_out.buf + jp1->pos, diff1); + break; + case 2: + put_u16 (bc_out.buf + jp1->pos, diff1); + break; + case 4: + put_u32 (bc_out.buf + jp1->pos, diff1); + break; + } + } + } + } + pjs_free (s->jump_slots); + s->jump_slots = NULL; +#endif + pjs_free (s->label_slots); + s->label_slots = NULL; + /* XXX: should delay until copying to runtime bytecode function */ + compute_pc2line_info (s); + pjs_free (s->line_number_slots); + s->line_number_slots = NULL; + /* set the new byte code */ + dbuf_free (&s->byte_code); + s->byte_code = bc_out; + s->use_short_opcodes = TRUE; + if (dbuf_error (&s->byte_code)) { + JS_ThrowOutOfMemory (ctx); + return -1; + } + return 0; +fail: + /* XXX: not safe */ + dbuf_free (&bc_out); + return -1; +} + +/* compute the maximum stack size needed by the function */ + +typedef struct StackSizeState { + int bc_len; + int stack_len_max; + uint16_t *stack_level_tab; + int32_t *catch_pos_tab; + int *pc_stack; + int pc_stack_len; + int pc_stack_size; +} StackSizeState; + +/* 'op' is only used for error indication */ +static __exception int ss_check (JSContext *ctx, StackSizeState *s, int pos, int op, int stack_len, int catch_pos) { + if ((unsigned)pos >= s->bc_len) { + JS_ThrowInternalError (ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos); + return -1; + } + if (stack_len > s->stack_len_max) { + s->stack_len_max = stack_len; + if (s->stack_len_max > JS_STACK_SIZE_MAX) { + JS_ThrowInternalError (ctx, "stack overflow (op=%d, pc=%d)", op, pos); + return -1; + } + } + if (s->stack_level_tab[pos] != 0xffff) { + /* already explored: check that the stack size is consistent */ + if (s->stack_level_tab[pos] != stack_len) { + JS_ThrowInternalError (ctx, "inconsistent stack size: %d %d (pc=%d)", s->stack_level_tab[pos], stack_len, pos); + return -1; + } else if (s->catch_pos_tab[pos] != catch_pos) { + JS_ThrowInternalError (ctx, "inconsistent catch position: %d %d (pc=%d)", s->catch_pos_tab[pos], catch_pos, pos); + return -1; + } else { + return 0; + } + } + + /* mark as explored and store the stack size */ + s->stack_level_tab[pos] = stack_len; + s->catch_pos_tab[pos] = catch_pos; + + /* queue the new PC to explore */ + if (pjs_resize_array ((void **)&s->pc_stack, sizeof (s->pc_stack[0]), &s->pc_stack_size, s->pc_stack_len + 1)) + return -1; + s->pc_stack[s->pc_stack_len++] = pos; + return 0; +} + +static __exception int compute_stack_size (JSContext *ctx, JSFunctionDef *fd, int *pstack_size) { + StackSizeState s_s, *s = &s_s; + int i, diff, n_pop, pos_next, stack_len, pos, op, catch_pos, catch_level; + const JSOpCode *oi; + const uint8_t *bc_buf; + + bc_buf = fd->byte_code.buf; + s->bc_len = fd->byte_code.size; + /* bc_len > 0 */ + s->stack_level_tab + = pjs_malloc (sizeof (s->stack_level_tab[0]) * s->bc_len); + if (!s->stack_level_tab) return -1; + for (i = 0; i < s->bc_len; i++) + s->stack_level_tab[i] = 0xffff; + s->pc_stack = NULL; + s->catch_pos_tab = pjs_malloc (sizeof (s->catch_pos_tab[0]) * s->bc_len); + if (!s->catch_pos_tab) goto fail; + + s->stack_len_max = 0; + s->pc_stack_len = 0; + s->pc_stack_size = 0; + + /* breadth-first graph exploration */ + if (ss_check (ctx, s, 0, OP_invalid, 0, -1)) goto fail; + + while (s->pc_stack_len > 0) { + pos = s->pc_stack[--s->pc_stack_len]; + stack_len = s->stack_level_tab[pos]; + catch_pos = s->catch_pos_tab[pos]; + op = bc_buf[pos]; + if (op == 0 || op >= OP_COUNT) { + JS_ThrowInternalError (ctx, "invalid opcode (op=%d, pc=%d)", op, pos); + goto fail; + } + oi = &short_opcode_info (op); +#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 64) + printf ("%5d: %10s %5d %5d\n", pos, oi->name, stack_len, catch_pos); +#endif + pos_next = pos + oi->size; + if (pos_next > s->bc_len) { + JS_ThrowInternalError (ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos); + goto fail; + } + n_pop = oi->n_pop; + /* call pops a variable number of arguments */ + if (oi->fmt == OP_FMT_npop || oi->fmt == OP_FMT_npop_u16) { + n_pop += get_u16 (bc_buf + pos + 1); + } else { +#if SHORT_OPCODES + if (oi->fmt == OP_FMT_npopx) { n_pop += op - OP_call0; } +#endif + } + + if (stack_len < n_pop) { + JS_ThrowInternalError (ctx, "stack underflow (op=%d, pc=%d)", op, pos); + goto fail; + } + stack_len += oi->n_push - n_pop; + if (stack_len > s->stack_len_max) { + s->stack_len_max = stack_len; + if (s->stack_len_max > JS_STACK_SIZE_MAX) { + JS_ThrowInternalError (ctx, "stack overflow (op=%d, pc=%d)", op, pos); + goto fail; + } + } + switch (op) { + case OP_tail_call: + case OP_tail_call_method: + case OP_return: + case OP_return_undef: + case OP_throw: + case OP_throw_error: + case OP_ret: + goto done_insn; + case OP_goto: + diff = get_u32 (bc_buf + pos + 1); + pos_next = pos + 1 + diff; + break; +#if SHORT_OPCODES + case OP_goto16: + diff = (int16_t)get_u16 (bc_buf + pos + 1); + pos_next = pos + 1 + diff; + break; + case OP_goto8: + diff = (int8_t)bc_buf[pos + 1]; + pos_next = pos + 1 + diff; + break; + case OP_if_true8: + case OP_if_false8: + diff = (int8_t)bc_buf[pos + 1]; + if (ss_check (ctx, s, pos + 1 + diff, op, stack_len, catch_pos)) + goto fail; + break; +#endif + case OP_if_true: + case OP_if_false: + diff = get_u32 (bc_buf + pos + 1); + if (ss_check (ctx, s, pos + 1 + diff, op, stack_len, catch_pos)) + goto fail; + break; + case OP_gosub: + diff = get_u32 (bc_buf + pos + 1); + if (ss_check (ctx, s, pos + 1 + diff, op, stack_len + 1, catch_pos)) + goto fail; + break; + case OP_catch: + diff = get_u32 (bc_buf + pos + 1); + if (ss_check (ctx, s, pos + 1 + diff, op, stack_len, catch_pos)) + goto fail; + catch_pos = pos; + break; + /* we assume the catch offset entry is only removed with + some op codes */ + case OP_drop: + catch_level = stack_len; + goto check_catch; + case OP_nip: + catch_level = stack_len - 1; + goto check_catch; + case OP_nip1: + catch_level = stack_len - 1; + check_catch: + if (catch_pos >= 0) { + int level; + level = s->stack_level_tab[catch_pos]; + /* catch_level = stack_level before op_catch is executed ? */ + if (catch_level == level) { catch_pos = s->catch_pos_tab[catch_pos]; } + } + break; + case OP_nip_catch: + if (catch_pos < 0) { + JS_ThrowInternalError (ctx, "nip_catch: no catch op (pc=%d)", pos); + goto fail; + } + stack_len = s->stack_level_tab[catch_pos]; + stack_len++; /* no stack overflow is possible by construction */ + catch_pos = s->catch_pos_tab[catch_pos]; + break; + default: + break; + } + if (ss_check (ctx, s, pos_next, op, stack_len, catch_pos)) goto fail; + done_insn:; + } + pjs_free (s->pc_stack); + pjs_free (s->catch_pos_tab); + pjs_free (s->stack_level_tab); + *pstack_size = s->stack_len_max; + return 0; +fail: + pjs_free (s->pc_stack); + pjs_free (s->catch_pos_tab); + pjs_free (s->stack_level_tab); + *pstack_size = 0; + return -1; +} + +/* create a function object from a function definition. The function + definition is freed. All the child functions are also created. It + must be done this way to resolve all the variables. */ +JSValue js_create_function (JSContext *ctx, JSFunctionDef *fd) { + JSValue func_obj; + JSFunctionBytecode *b; + struct list_head *el, *el1; + int stack_size, scope, idx; + int function_size, byte_code_offset, cpool_offset; + int closure_var_offset, vardefs_offset; + + /* recompute scope linkage */ + for (scope = 0; scope < fd->scope_count; scope++) { + fd->scopes[scope].first = -1; + } + if (fd->has_parameter_expressions) { + /* special end of variable list marker for the argument scope */ + fd->scopes[ARG_SCOPE_INDEX].first = ARG_SCOPE_END; + } + for (idx = 0; idx < fd->var_count; idx++) { + JSVarDef *vd = &fd->vars[idx]; + vd->scope_next = fd->scopes[vd->scope_level].first; + fd->scopes[vd->scope_level].first = idx; + } + for (scope = 2; scope < fd->scope_count; scope++) { + JSVarScope *sd = &fd->scopes[scope]; + if (sd->first < 0) sd->first = fd->scopes[sd->parent].first; + } + for (idx = 0; idx < fd->var_count; idx++) { + JSVarDef *vd = &fd->vars[idx]; + if (vd->scope_next < 0 && vd->scope_level > 1) { + scope = fd->scopes[vd->scope_level].parent; + vd->scope_next = fd->scopes[scope].first; + } + } + + /* first create all the child functions */ + list_for_each_safe (el, el1, &fd->child_list) { + JSFunctionDef *fd1; + int cpool_idx; + + fd1 = list_entry (el, JSFunctionDef, link); + cpool_idx = fd1->parent_cpool_idx; + func_obj = js_create_function (ctx, fd1); + if (JS_IsException (func_obj)) goto fail; + /* save it in the constant pool */ + assert (cpool_idx >= 0); + fd->cpool[cpool_idx] = func_obj; + } + +#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 4) + if (!fd->strip_debug) { + printf ("pass 1\n"); + dump_byte_code (ctx, 1, fd->byte_code.buf, fd->byte_code.size, fd->args, fd->arg_count, fd->vars, fd->var_count, fd->closure_var, fd->closure_var_count, fd->cpool, fd->cpool_count, fd->source, fd->label_slots, NULL); + printf ("\n"); + } +#endif + + if (resolve_variables (ctx, fd)) goto fail; + +#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 2) + if (!fd->strip_debug) { + printf ("pass 2\n"); + dump_byte_code (ctx, 2, fd->byte_code.buf, fd->byte_code.size, fd->args, fd->arg_count, fd->vars, fd->var_count, fd->closure_var, fd->closure_var_count, fd->cpool, fd->cpool_count, fd->source, fd->label_slots, NULL); + printf ("\n"); + } +#endif + + if (resolve_labels (ctx, fd)) goto fail; + + if (compute_stack_size (ctx, fd, &stack_size) < 0) goto fail; + + if (fd->strip_debug) { + function_size = offsetof (JSFunctionBytecode, debug); + } else { + function_size = sizeof (*b); + } + cpool_offset = function_size; + function_size += fd->cpool_count * sizeof (*fd->cpool); + vardefs_offset = function_size; + if (!fd->strip_debug) { + function_size += (fd->arg_count + fd->var_count) * sizeof (*b->vardefs); + } + closure_var_offset = function_size; + function_size += fd->closure_var_count * sizeof (*fd->closure_var); + byte_code_offset = function_size; + function_size += fd->byte_code.size; + + /* Bytecode is immutable - allocate outside GC heap */ + b = pjs_mallocz (function_size); + if (!b) goto fail; + b->header = objhdr_make (0, OBJ_CODE, false, false, false, false); + + b->byte_code_buf = (void *)((uint8_t *)b + byte_code_offset); + b->byte_code_len = fd->byte_code.size; + memcpy (b->byte_code_buf, fd->byte_code.buf, fd->byte_code.size); + js_free (ctx, fd->byte_code.buf); + fd->byte_code.buf = NULL; + + b->func_name = fd->func_name; + if (fd->arg_count + fd->var_count > 0) { + if (fd->strip_debug) { + /* Strip variable definitions not needed at runtime */ + int i; + for (i = 0; i < fd->var_count; i++) { + } + for (i = 0; i < fd->arg_count; i++) { + } + for (i = 0; i < fd->closure_var_count; i++) { + fd->closure_var[i].var_name = JS_NULL; + } + } else { + b->vardefs = (void *)((uint8_t *)b + vardefs_offset); + memcpy_no_ub (b->vardefs, fd->args, fd->arg_count * sizeof (fd->args[0])); + memcpy_no_ub (b->vardefs + fd->arg_count, fd->vars, fd->var_count * sizeof (fd->vars[0])); + } + b->var_count = fd->var_count; + b->arg_count = fd->arg_count; + b->defined_arg_count = fd->defined_arg_count; + js_free (ctx, fd->args); + js_free (ctx, fd->vars); + } + b->cpool_count = fd->cpool_count; + if (b->cpool_count) { + b->cpool = (void *)((uint8_t *)b + cpool_offset); + memcpy (b->cpool, fd->cpool, b->cpool_count * sizeof (*b->cpool)); + } + js_free (ctx, fd->cpool); + fd->cpool = NULL; + + b->stack_size = stack_size; + + if (fd->strip_debug) { + dbuf_free (&fd->pc2line); // probably useless + } else { + /* XXX: source and pc2line info should be packed at the end of the + JSFunctionBytecode structure, avoiding allocation overhead + */ + b->has_debug = 1; + b->debug.filename = fd->filename; + + // DynBuf pc2line; + // compute_pc2line_info(fd, &pc2line); + // js_free(ctx, fd->line_number_slots) + /* pc2line.buf is allocated by dbuf (system realloc), so just use it directly */ + b->debug.pc2line_buf = fd->pc2line.buf; + fd->pc2line.buf = NULL; /* Transfer ownership */ + b->debug.pc2line_len = fd->pc2line.size; + b->debug.source = fd->source; + b->debug.source_len = fd->source_len; + } + if (fd->scopes != fd->def_scope_array) pjs_free (fd->scopes); + + b->closure_var_count = fd->closure_var_count; + if (b->closure_var_count) { + b->closure_var = (void *)((uint8_t *)b + closure_var_offset); + memcpy (b->closure_var, fd->closure_var, b->closure_var_count * sizeof (*b->closure_var)); + } + pjs_free (fd->closure_var); + fd->closure_var = NULL; + + b->has_prototype = fd->has_prototype; + b->has_simple_parameter_list = fd->has_simple_parameter_list; + b->js_mode = fd->js_mode; + + /* IC removed - shapes no longer used */ + + b->is_direct_or_indirect_eval = FALSE; + +#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 1) + if (!fd->strip_debug) { js_dump_function_bytecode (ctx, b); } +#endif + + if (fd->parent) { + /* remove from parent list */ + list_del (&fd->link); + } + + js_free (ctx, fd); + return JS_MKPTR (b); +fail: + js_free_function_def (ctx, fd); + return JS_EXCEPTION; +} + +static __exception int js_parse_directives (JSParseState *s) { + char str[20]; + JSParsePos pos; + BOOL has_semi; + + if (s->token.val != TOK_STRING) return 0; + + js_parse_get_pos (s, &pos); + + while (s->token.val == TOK_STRING) { + /* Copy actual source string representation */ + snprintf (str, sizeof str, "%.*s", (int)(s->buf_ptr - s->token.ptr - 2), s->token.ptr + 1); + + if (next_token (s)) return -1; + + has_semi = FALSE; + switch (s->token.val) { + case ';': + if (next_token (s)) return -1; + has_semi = TRUE; + break; + case '}': + case TOK_EOF: + has_semi = TRUE; + break; + case TOK_NUMBER: + case TOK_STRING: + case TOK_TEMPLATE: + case TOK_IDENT: + case TOK_REGEXP: + case TOK_DEC: + case TOK_INC: + case TOK_NULL: + case TOK_FALSE: + case TOK_TRUE: + case TOK_IF: + case TOK_RETURN: + case TOK_VAR: + case TOK_THIS: + case TOK_DELETE: + case TOK_DO: + case TOK_WHILE: + case TOK_FOR: + case TOK_DISRUPT: + case TOK_FUNCTION: + case TOK_DEBUGGER: + case TOK_DEF: + case TOK_ENUM: + case TOK_EXPORT: + case TOK_IMPORT: + case TOK_INTERFACE: + /* automatic insertion of ';' */ + if (s->got_lf) has_semi = TRUE; + break; + default: + break; + } + if (!has_semi) break; + } + return js_parse_seek_token (s, &pos); +} + +/* return TRUE if the keyword is forbidden only in strict mode */ +static BOOL is_strict_future_keyword (JSValue name) { + BOOL is_strict_reserved = FALSE; + int token = js_keyword_lookup (name, &is_strict_reserved); + return (token >= 0 && is_strict_reserved); +} + +static int js_parse_function_check_names (JSParseState *s, JSFunctionDef *fd, JSValue func_name) { + JSValue name; + int i, idx; + + if (!fd->has_simple_parameter_list && fd->has_use_strict) { + return js_parse_error (s, "\"use strict\" not allowed in function with " + "default or destructuring parameter"); + } + if (js_key_equal (func_name, JS_KEY_eval) + || is_strict_future_keyword (func_name)) { + return js_parse_error (s, "invalid function name in strict code"); + } + for (idx = 0; idx < fd->arg_count; idx++) { + name = fd->args[idx].var_name; + + if (js_key_equal (name, JS_KEY_eval) || is_strict_future_keyword (name)) { + return js_parse_error (s, "invalid argument name in strict code"); + } + } + + for (idx = 0; idx < fd->arg_count; idx++) { + name = fd->args[idx].var_name; + if (!JS_IsNull (name)) { + for (i = 0; i < idx; i++) { + if (js_key_equal (fd->args[i].var_name, name)) goto duplicate; + } + /* Check if argument name duplicates a destructuring parameter */ + /* XXX: should have a flag for such variables */ + for (i = 0; i < fd->var_count; i++) { + if (js_key_equal (fd->vars[i].var_name, name) + && fd->vars[i].scope_level == 0) + goto duplicate; + } + } + } + return 0; + +duplicate: + return js_parse_error ( + s, "duplicate argument names not allowed in this context"); +} + +/* func_name must be JS_NULL for JS_PARSE_FUNC_STATEMENT and + JS_PARSE_FUNC_EXPR, JS_PARSE_FUNC_ARROW and JS_PARSE_FUNC_VAR */ +static __exception int js_parse_function_decl2 (JSParseState *s, + JSParseFunctionEnum func_type, + JSValue func_name, + const uint8_t *ptr, + JSFunctionDef **pfd) { + JSContext *ctx = s->ctx; + JSFunctionDef *fd = s->cur_func; + BOOL is_expr; + int func_idx, lexical_func_idx = -1; + BOOL create_func_var = FALSE; + + is_expr + = (func_type != JS_PARSE_FUNC_STATEMENT && func_type != JS_PARSE_FUNC_VAR); + + if (func_type == JS_PARSE_FUNC_STATEMENT || func_type == JS_PARSE_FUNC_VAR + || func_type == JS_PARSE_FUNC_EXPR) { + if (next_token (s)) return -1; + + if (s->token.val == TOK_IDENT) { + if (s->token.u.ident.is_reserved) + return js_parse_error_reserved_identifier (s); + func_name = s->token.u.ident.str; + if (next_token (s)) { + return -1; + } + } else { + if (func_type != JS_PARSE_FUNC_EXPR) + return js_parse_error (s, "function name expected"); + } + + } else if (func_type != JS_PARSE_FUNC_ARROW) { + /* func_name is already set, nothing to do */ + } + + if (func_type == JS_PARSE_FUNC_VAR) { + /* Create the lexical name here so that the function closure + contains it */ + /* Always create a lexical name, fail if at the same scope as + existing name */ + /* Lexical variable will be initialized upon entering scope */ + lexical_func_idx + = define_var (s, fd, func_name, JS_VAR_DEF_FUNCTION_DECL); + if (lexical_func_idx < 0) { + return -1; + } + } + + fd = js_new_function_def (ctx, fd, is_expr, s->filename, ptr, &s->get_line_col_cache); + if (!fd) { + return -1; + } + if (pfd) *pfd = fd; + s->cur_func = fd; + fd->func_name = func_name; + /* XXX: test !fd->is_generator is always false */ + fd->has_prototype + = (func_type == JS_PARSE_FUNC_STATEMENT || func_type == JS_PARSE_FUNC_VAR + || func_type == JS_PARSE_FUNC_EXPR); + + fd->has_this_binding = (func_type != JS_PARSE_FUNC_ARROW + && func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT); + + /* fd->in_function_body == FALSE prevents yield/await during the parsing + of the arguments in generator/async functions. They are parsed as + regular identifiers for other function kinds. */ + fd->func_type = func_type; + + /* parse arguments */ + fd->has_simple_parameter_list = TRUE; + fd->has_parameter_expressions = FALSE; + if (func_type == JS_PARSE_FUNC_ARROW && s->token.val == TOK_IDENT) { + JSValue name; + if (s->token.u.ident.is_reserved) { + js_parse_error_reserved_identifier (s); + goto fail; + } + name = s->token.u.ident.str; + if (add_arg (ctx, fd, name) < 0) goto fail; + } else if (func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT) { + if (s->token.val == '(') { + int skip_bits; + /* if there is an '=' inside the parameter list, we + consider there is a parameter expression inside */ + js_parse_skip_parens_token (s, &skip_bits, FALSE); + if (skip_bits & SKIP_HAS_ASSIGNMENT) + fd->has_parameter_expressions = TRUE; + if (next_token (s)) goto fail; + } else { + if (js_parse_expect (s, '(')) goto fail; + } + + if (fd->has_parameter_expressions) { + fd->scope_level = -1; /* force no parent scope */ + if (push_scope (s) < 0) return -1; + } + + while (s->token.val != ')') { + JSValue name; + int idx, has_initializer; + + if (s->token.val == '[' || s->token.val == '{') { + fd->has_simple_parameter_list = FALSE; + /* unnamed arg for destructuring */ + idx = add_arg (ctx, fd, JS_NULL); + emit_op (s, OP_get_arg); + emit_u16 (s, idx); + has_initializer + = js_parse_destructuring_element (s, TOK_VAR, 1, TRUE, TRUE, FALSE); + if (has_initializer < 0) goto fail; + } else if (s->token.val == TOK_IDENT) { + if (s->token.u.ident.is_reserved) { + js_parse_error_reserved_identifier (s); + goto fail; + } + name = s->token.u.ident.str; + if (fd->has_parameter_expressions) { + if (js_parse_check_duplicate_parameter (s, name)) goto fail; + if (define_var (s, fd, name, JS_VAR_DEF_LET) < 0) goto fail; + } + idx = add_arg (ctx, fd, name); + if (idx < 0) goto fail; + if (next_token (s)) goto fail; + if (s->token.val == '=') { + int label; + + fd->has_simple_parameter_list = FALSE; + + if (next_token (s)) goto fail; + + label = new_label (s); + emit_op (s, OP_get_arg); + emit_u16 (s, idx); + emit_op (s, OP_dup); + emit_op (s, OP_null); + emit_op (s, OP_strict_eq); + emit_goto (s, OP_if_false, label); + emit_op (s, OP_drop); + if (js_parse_assign_expr (s)) goto fail; + set_object_name (s, name); + emit_op (s, OP_dup); + emit_op (s, OP_put_arg); + emit_u16 (s, idx); + emit_label (s, label); + emit_op (s, OP_scope_put_var_init); + emit_key (s, name); + emit_u16 (s, fd->scope_level); + } else { + if (fd->has_parameter_expressions) { + /* copy the argument to the argument scope */ + emit_op (s, OP_get_arg); + emit_u16 (s, idx); + emit_op (s, OP_scope_put_var_init); + emit_key (s, name); + emit_u16 (s, fd->scope_level); + } + } + } else { + js_parse_error (s, "missing formal parameter"); + goto fail; + } + if (s->token.val == ')') break; + if (js_parse_expect (s, ',')) goto fail; + } + if ((func_type == JS_PARSE_FUNC_GETTER && fd->arg_count != 0) + || (func_type == JS_PARSE_FUNC_SETTER && fd->arg_count != 1)) { + js_parse_error (s, "invalid number of arguments for getter or setter"); + goto fail; + } + if (fd->arg_count > 4) { + js_parse_error (s, "functions cannot have more than 4 parameters"); + goto fail; + } + } + /* Explicit arity: defined_arg_count == arg_count always */ + fd->defined_arg_count = fd->arg_count; + + if (fd->has_parameter_expressions) { + int idx; + + /* Copy the variables in the argument scope to the variable + scope (see FunctionDeclarationInstantiation() in spec). The + normal arguments are already present, so no need to copy + them. */ + idx = fd->scopes[fd->scope_level].first; + while (idx >= 0) { + JSVarDef *vd = &fd->vars[idx]; + if (vd->scope_level != fd->scope_level) break; + if (find_var (ctx, fd, vd->var_name) < 0) { + if (add_var (ctx, fd, vd->var_name) < 0) goto fail; + vd = &fd->vars[idx]; /* fd->vars may have been reallocated */ + emit_op (s, OP_scope_get_var); + emit_key (s, vd->var_name); + emit_u16 (s, fd->scope_level); + emit_op (s, OP_scope_put_var); + emit_key (s, vd->var_name); + emit_u16 (s, 0); + } + idx = vd->scope_next; + } + + /* the argument scope has no parent, hence we don't use pop_scope(s) */ + emit_op (s, OP_leave_scope); + emit_u16 (s, fd->scope_level); + + /* set the variable scope as the current scope */ + fd->scope_level = 0; + fd->scope_first = fd->scopes[fd->scope_level].first; + } + + if (next_token (s)) goto fail; + + /* in generators, yield expression is forbidden during the parsing + of the arguments */ + fd->in_function_body = TRUE; + push_scope (s); /* enter body scope */ + fd->body_scope = fd->scope_level; + + if (s->token.val == TOK_ARROW && func_type == JS_PARSE_FUNC_ARROW) { + if (next_token (s)) goto fail; + + if (s->token.val != '{') { + if (js_parse_function_check_names (s, fd, func_name)) goto fail; + + if (js_parse_assign_expr (s)) goto fail; + + emit_op (s, OP_return); + + if (!fd->strip_source) { + /* save the function source code */ + /* the end of the function source code is after the last + token of the function source stored into s->last_ptr */ + fd->source_len = s->last_ptr - ptr; + fd->source = strndup ((const char *)ptr, fd->source_len); + if (!fd->source) goto fail; + } + goto done; + } + } + + if (js_parse_expect (s, '{')) goto fail; + + if (js_parse_directives (s)) goto fail; + + /* in strict_mode, check function and argument names */ + if (js_parse_function_check_names (s, fd, func_name)) goto fail; + + while (s->token.val != '}') { + if (js_parse_source_element (s)) goto fail; + } + if (!fd->strip_source) { + /* save the function source code */ + fd->source_len = s->buf_ptr - ptr; + fd->source = strndup ((const char *)ptr, fd->source_len); + if (!fd->source) goto fail; + } + + if (next_token (s)) { + /* consume the '}' */ + goto fail; + } + + /* in case there is no return, add one */ + if (js_is_live_code (s)) { emit_return (s, FALSE); } +done: + s->cur_func = fd->parent; + + /* Reparse identifiers after the function is terminated so that + the token is parsed in the englobing function. It could be done + by just using next_token() here for normal functions, but it is + necessary for arrow functions with an expression body. */ + reparse_ident_token (s); + + /* create the function object */ + { + int idx; + JSValue func_name_val = fd->func_name; + + /* the real object will be set at the end of the compilation */ + idx = cpool_add (s, JS_NULL); + fd->parent_cpool_idx = idx; + + if (is_expr) { + /* OP_fclosure creates the function object from the bytecode + and adds the scope information */ + emit_op (s, OP_fclosure); + emit_u32 (s, idx); + if (JS_IsNull (func_name_val)) { + emit_op (s, OP_set_name); + emit_key (s, JS_NULL); + } + } else if (func_type == JS_PARSE_FUNC_VAR) { + emit_op (s, OP_fclosure); + emit_u32 (s, idx); + if (create_func_var) { + if (s->cur_func->is_global_var) { + JSGlobalVar *hf; + /* the global variable must be defined at the start of the + function */ + hf = add_global_var (ctx, s->cur_func, func_name_val); + if (!hf) goto fail; + /* it is considered as defined at the top level + (needed for annex B.3.3.4 and B.3.3.5 + checks) */ + hf->scope_level = 0; + hf->force_init = 1; + /* store directly into global var, bypass lexical scope */ + emit_op (s, OP_dup); + emit_op (s, OP_scope_put_var); + emit_key (s, func_name_val); + emit_u16 (s, 0); + } else { + /* do not call define_var to bypass lexical scope check */ + func_idx = find_var (ctx, s->cur_func, func_name_val); + if (func_idx < 0) { + func_idx = add_var (ctx, s->cur_func, func_name_val); + if (func_idx < 0) goto fail; + } + /* store directly into local var, bypass lexical catch scope */ + emit_op (s, OP_dup); + emit_op (s, OP_scope_put_var); + emit_key (s, func_name_val); + emit_u16 (s, 0); + } + } + if (lexical_func_idx >= 0) { + /* lexical variable will be initialized upon entering scope */ + s->cur_func->vars[lexical_func_idx].func_pool_idx = idx; + emit_op (s, OP_drop); + } else { + /* store function object into its lexical name */ + /* XXX: could use OP_put_loc directly */ + emit_op (s, OP_scope_put_var_init); + emit_key (s, func_name_val); + emit_u16 (s, s->cur_func->scope_level); + } + } else { + if (!s->cur_func->is_global_var) { + int var_idx + = define_var (s, s->cur_func, func_name_val, JS_VAR_DEF_VAR); + + if (var_idx < 0) goto fail; + /* the variable will be assigned at the top of the function */ + if (var_idx & ARGUMENT_VAR_OFFSET) { + s->cur_func->args[var_idx - ARGUMENT_VAR_OFFSET].func_pool_idx = idx; + } else { + s->cur_func->vars[var_idx].func_pool_idx = idx; + } + } else { + JSValue func_var_name; + JSGlobalVar *hf; + if (JS_IsNull (func_name_val)) + func_var_name = JS_KEY_STR (ctx, "default"); /* export default */ + else + func_var_name = func_name_val; + /* the variable will be assigned at the top of the function */ + hf = add_global_var (ctx, s->cur_func, func_var_name); + if (!hf) goto fail; + hf->cpool_idx = idx; + } + } + } + return 0; +fail: + s->cur_func = fd->parent; + js_free_function_def (ctx, fd); + if (pfd) *pfd = NULL; + return -1; +} + +static __exception int js_parse_function_decl (JSParseState *s, + JSParseFunctionEnum func_type, + JSValue func_name, + const uint8_t *ptr) { + return js_parse_function_decl2 (s, func_type, func_name, ptr, NULL); +} + +static __exception int js_parse_program (JSParseState *s) { + JSFunctionDef *fd = s->cur_func; + if (next_token (s)) return -1; + + if (js_parse_directives (s)) return -1; + + /* Global object is immutable - all variables are locals of the eval function */ + fd->is_global_var = FALSE; + + while (s->token.val != TOK_EOF) { + if (js_parse_source_element (s)) return -1; + } + + /* For eval-like semantics: if the last statement was an expression, + return its value instead of null. Expression statements emit OP_drop + to discard the value - remove that and emit OP_return instead. */ + if (get_prev_opcode (fd) == OP_drop) { + fd->byte_code.size = fd->last_opcode_pos; + fd->last_opcode_pos = -1; + emit_return (s, TRUE); + } else { + emit_return (s, FALSE); + } + + return 0; +} + +static void js_parse_init (JSContext *ctx, JSParseState *s, const char *input, size_t input_len, const char *filename) { + memset (s, 0, sizeof (*s)); + s->ctx = ctx; + s->filename = filename; + s->buf_start = s->buf_ptr = (const uint8_t *)input; + s->buf_end = s->buf_ptr + input_len; + s->token.val = ' '; + s->token.ptr = s->buf_ptr; + + s->get_line_col_cache.ptr = s->buf_start; + s->get_line_col_cache.buf_start = s->buf_start; + s->get_line_col_cache.line_num = 0; + s->get_line_col_cache.col_num = 0; +} + +/* Forward declaration */ +JSValue __JS_CompileInternal (JSContext *ctx, const char *input, size_t input_len, const char *filename); + +/* Compile source code to bytecode without executing. + Returns unlinked bytecode on success, JS_EXCEPTION on error. + 'input' must be zero terminated i.e. input[input_len] = '\0'. */ +JSValue JS_Compile (JSContext *ctx, const char *input, size_t input_len, + const char *filename) { + return __JS_CompileInternal (ctx, input, input_len, filename); +} + +/* Link compiled bytecode with environment and execute. + The env should be a stoned record (or null for no env). + Variables resolve: env first, then global intrinsics. */ +JSValue JS_Integrate (JSContext *ctx, JSValue fun_obj, JSValue env) { + JSValue ret_val; + uint32_t tag; + JSGCRef env_ref, fun_ref; + JSFunctionBytecode *tpl, *linked_bc; + + tag = JS_VALUE_GET_TAG (fun_obj); + /* JSFunctionBytecode uses OBJ_CODE type with JS_TAG_PTR */ + if (tag != JS_TAG_PTR || objhdr_type (*(objhdr_t *)JS_VALUE_GET_PTR (fun_obj)) != OBJ_CODE) { + return JS_ThrowTypeError (ctx, "bytecode function expected"); + } + + /* Protect env and fun_obj from GC during linking */ + JS_AddGCRef (ctx, &env_ref); + env_ref.val = env; + JS_AddGCRef (ctx, &fun_ref); + fun_ref.val = fun_obj; + + /* Link bytecode with environment */ + tpl = (JSFunctionBytecode *)JS_VALUE_GET_PTR (fun_ref.val); + linked_bc = js_link_bytecode (ctx, tpl, env_ref.val); + if (!linked_bc) { + JS_DeleteGCRef (ctx, &fun_ref); + JS_DeleteGCRef (ctx, &env_ref); + return JS_EXCEPTION; + } + JSValue linked = JS_MKPTR (linked_bc); + + /* Update env from GC ref (may have moved) */ + env = env_ref.val; + JS_DeleteGCRef (ctx, &fun_ref); + JS_DeleteGCRef (ctx, &env_ref); + + /* Create closure and set env_record on the function object */ + linked = js_closure (ctx, linked, NULL); + if (JS_IsException (linked)) { + return JS_EXCEPTION; + } + + /* Store env_record on the function for OP_get_env_slot access */ + JSFunction *f = JS_VALUE_GET_FUNCTION (linked); + f->u.func.env_record = env; + + ret_val = JS_Call (ctx, linked, ctx->global_obj, 0, NULL); + + return ret_val; +} + +/* Compile source to bytecode. + 'input' must be zero terminated i.e. input[input_len] = '\0'. */ +JSValue __JS_CompileInternal (JSContext *ctx, const char *input, size_t input_len, const char *filename) { + JSParseState s1, *s = &s1; + int err; + JSValue fun_obj; + JSFunctionDef *fd; + + js_parse_init (ctx, s, input, input_len, filename); + + fd = js_new_function_def (ctx, NULL, FALSE, filename, s->buf_start, &s->get_line_col_cache); + if (!fd) goto fail1; + s->cur_func = fd; + ctx->current_parse_fd = fd; /* Set GC root for parser's cpool */ + fd->has_this_binding = TRUE; + fd->js_mode = 0; + fd->func_name = JS_KEY__eval_; + + push_scope (s); /* body scope */ + fd->body_scope = fd->scope_level; + + err = js_parse_program (s); + if (err) { + free_token (s, &s->token); + ctx->current_parse_fd = NULL; /* Clear GC root before freeing */ + js_free_function_def (ctx, fd); + goto fail1; + } + + /* create the function object and all the enclosed functions */ + ctx->current_parse_fd = NULL; /* Clear GC root - fd ownership transfers to js_create_function */ + fun_obj = js_create_function (ctx, fd); + if (JS_IsException (fun_obj)) goto fail1; + return fun_obj; +fail1: + return JS_EXCEPTION; +} + +/*******************************************************************/ +/* object list */ + +typedef struct { + JSRecord *obj; + uint32_t hash_next; /* -1 if no next entry */ +} JSRecordListEntry; + +/* XXX: reuse it to optimize weak references */ +typedef struct { + JSRecordListEntry *object_tab; + int object_count; + int object_size; + uint32_t *hash_table; + uint32_t hash_size; +} JSRecordList; + +static void js_object_list_init (JSRecordList *s) { + memset (s, 0, sizeof (*s)); +} + +static uint32_t js_object_list_get_hash (JSRecord *p, uint32_t hash_size) { + return ((uintptr_t)p * 3163) & (hash_size - 1); +} + +static int js_object_list_resize_hash (JSContext *ctx, JSRecordList *s, uint32_t new_hash_size) { + JSRecordListEntry *e; + uint32_t i, h, *new_hash_table; + + new_hash_table = js_malloc (ctx, sizeof (new_hash_table[0]) * new_hash_size); + if (!new_hash_table) return -1; + js_free (ctx, s->hash_table); + s->hash_table = new_hash_table; + s->hash_size = new_hash_size; + + for (i = 0; i < s->hash_size; i++) { + s->hash_table[i] = -1; + } + for (i = 0; i < s->object_count; i++) { + e = &s->object_tab[i]; + h = js_object_list_get_hash (e->obj, s->hash_size); + e->hash_next = s->hash_table[h]; + s->hash_table[h] = i; + } + return 0; +} + +/* the reference count of 'obj' is not modified. Return 0 if OK, -1 if + memory error */ +static int js_object_list_add (JSContext *ctx, JSRecordList *s, JSRecord *obj) { + JSRecordListEntry *e; + uint32_t h, new_hash_size; + + if (js_resize_array (ctx, (void *)&s->object_tab, sizeof (s->object_tab[0]), &s->object_size, s->object_count + 1)) + return -1; + if (unlikely ((s->object_count + 1) >= s->hash_size)) { + new_hash_size = max_uint32 (s->hash_size, 4); + while (new_hash_size <= s->object_count) + new_hash_size *= 2; + if (js_object_list_resize_hash (ctx, s, new_hash_size)) return -1; + } + e = &s->object_tab[s->object_count++]; + h = js_object_list_get_hash (obj, s->hash_size); + e->obj = obj; + e->hash_next = s->hash_table[h]; + s->hash_table[h] = s->object_count - 1; + return 0; +} + +/* return -1 if not present or the object index */ +static int js_object_list_find (JSContext *ctx, JSRecordList *s, JSRecord *obj) { + JSRecordListEntry *e; + uint32_t h, p; + + /* must test empty size because there is no hash table */ + if (s->object_count == 0) return -1; + h = js_object_list_get_hash (obj, s->hash_size); + p = s->hash_table[h]; + while (p != -1) { + e = &s->object_tab[p]; + if (e->obj == obj) return p; + p = e->hash_next; + } + return -1; +} + +static void js_object_list_end (JSContext *ctx, JSRecordList *s) { + js_free (ctx, s->object_tab); + js_free (ctx, s->hash_table); +} + +/*******************************************************************/ +/* binary object writer & reader */ + +typedef enum BCTagEnum { + BC_TAG_NULL = 1, + BC_TAG_BOOL_FALSE, + BC_TAG_BOOL_TRUE, + BC_TAG_INT32, + BC_TAG_FLOAT64, + BC_TAG_STRING, + BC_TAG_OBJECT, + BC_TAG_ARRAY, + BC_TAG_TEMPLATE_OBJECT, + BC_TAG_FUNCTION_BYTECODE, + BC_TAG_MODULE, + BC_TAG_OBJECT_VALUE, + BC_TAG_OBJECT_REFERENCE, +} BCTagEnum; + +#define BC_VERSION 6 + +typedef struct BCWriterState { + JSContext *ctx; + DynBuf dbuf; + BOOL allow_bytecode : 8; + BOOL allow_sab : 8; + BOOL allow_reference : 8; + uint8_t **sab_tab; + int sab_tab_len; + int sab_tab_size; + /* list of referenced objects (used if allow_reference = TRUE) */ + JSRecordList object_list; +} BCWriterState; + +#ifdef DUMP_READ_OBJECT +static const char *const bc_tag_str[] = { + "invalid", + "null", + "false", + "true", + "int32", + "float64", + "string", + "object", + "array", + "template", + "function", + "module", + "ObjectReference", +}; +#endif + +static inline BOOL is_be (void) { + union { + uint16_t a; + uint8_t b; + } u = { 0x100 }; + return u.b; +} + +static void bc_put_u8 (BCWriterState *s, uint8_t v) { + dbuf_putc (&s->dbuf, v); +} + +static void bc_put_u16 (BCWriterState *s, uint16_t v) { + if (is_be ()) v = bswap16 (v); + dbuf_put_u16 (&s->dbuf, v); +} + +static __maybe_unused void bc_put_u32 (BCWriterState *s, uint32_t v) { + if (is_be ()) v = bswap32 (v); + dbuf_put_u32 (&s->dbuf, v); +} + +static void bc_put_u64 (BCWriterState *s, uint64_t v) { + if (is_be ()) v = bswap64 (v); + dbuf_put (&s->dbuf, (uint8_t *)&v, sizeof (v)); +} + +static void bc_put_leb128 (BCWriterState *s, uint32_t v) { + dbuf_put_leb128 (&s->dbuf, v); +} + +static void bc_put_sleb128 (BCWriterState *s, int32_t v) { + dbuf_put_sleb128 (&s->dbuf, v); +} + +static void bc_set_flags (uint32_t *pflags, int *pidx, uint32_t val, int n) { + *pflags = *pflags | (val << *pidx); + *pidx += n; +} + +/* Write a JSValue key (text) to bytecode stream */ +static int bc_put_key (BCWriterState *s, JSValue key) { + /* Handle immediate ASCII strings */ + if (MIST_IsImmediateASCII (key)) { + int len = MIST_GetImmediateASCIILen (key); + bc_put_leb128 (s, (uint32_t)len); + for (int i = 0; i < len; i++) { + bc_put_u8 (s, (uint8_t)MIST_GetImmediateASCIIChar (key, i)); + } + return 0; + } + + /* Handle heap strings */ + if (!JS_IsText (key)) { + /* Not a string - write empty */ + bc_put_leb128 (s, 0); + return 0; + } + + JSText *p = JS_VALUE_GET_STRING (key); + /* Write as UTF-8 */ + uint32_t len = (uint32_t)JSText_len (p); + /* Calculate UTF-8 size */ + size_t utf8_size = 0; + for (uint32_t i = 0; i < len; i++) { + uint32_t c = string_get (p, i); + if (c < 0x80) + utf8_size += 1; + else if (c < 0x800) + utf8_size += 2; + else if (c < 0x10000) + utf8_size += 3; + else + utf8_size += 4; + } + bc_put_leb128 (s, (uint32_t)utf8_size); + for (uint32_t i = 0; i < len; i++) { + uint32_t c = string_get (p, i); + if (c < 0x80) { + bc_put_u8 (s, c); + } else if (c < 0x800) { + bc_put_u8 (s, 0xC0 | (c >> 6)); + bc_put_u8 (s, 0x80 | (c & 0x3F)); + } else if (c < 0x10000) { + bc_put_u8 (s, 0xE0 | (c >> 12)); + bc_put_u8 (s, 0x80 | ((c >> 6) & 0x3F)); + bc_put_u8 (s, 0x80 | (c & 0x3F)); + } else { + bc_put_u8 (s, 0xF0 | (c >> 18)); + bc_put_u8 (s, 0x80 | ((c >> 12) & 0x3F)); + bc_put_u8 (s, 0x80 | ((c >> 6) & 0x3F)); + bc_put_u8 (s, 0x80 | (c & 0x3F)); + } + } + return 0; +} + +static void bc_byte_swap (uint8_t *bc_buf, int bc_len) { + int pos, len, op, fmt; + + pos = 0; + while (pos < bc_len) { + op = bc_buf[pos]; + len = short_opcode_info (op).size; + fmt = short_opcode_info (op).fmt; + switch (fmt) { + case OP_FMT_u16: + case OP_FMT_i16: + case OP_FMT_label16: + case OP_FMT_npop: + case OP_FMT_loc: + case OP_FMT_arg: + put_u16 (bc_buf + pos + 1, bswap16 (get_u16 (bc_buf + pos + 1))); + break; + case OP_FMT_i32: + case OP_FMT_u32: + case OP_FMT_const: + case OP_FMT_label: + case OP_FMT_key: + case OP_FMT_key_u8: + put_u32 (bc_buf + pos + 1, bswap32 (get_u32 (bc_buf + pos + 1))); + break; + case OP_FMT_key_u16: + case OP_FMT_label_u16: + put_u32 (bc_buf + pos + 1, bswap32 (get_u32 (bc_buf + pos + 1))); + put_u16 (bc_buf + pos + 1 + 4, bswap16 (get_u16 (bc_buf + pos + 1 + 4))); + break; + case OP_FMT_key_label_u16: + put_u32 (bc_buf + pos + 1, bswap32 (get_u32 (bc_buf + pos + 1))); + put_u32 (bc_buf + pos + 1 + 4, bswap32 (get_u32 (bc_buf + pos + 1 + 4))); + put_u16 (bc_buf + pos + 1 + 4 + 4, + bswap16 (get_u16 (bc_buf + pos + 1 + 4 + 4))); + break; + case OP_FMT_npop_u16: + put_u16 (bc_buf + pos + 1, bswap16 (get_u16 (bc_buf + pos + 1))); + put_u16 (bc_buf + pos + 1 + 2, bswap16 (get_u16 (bc_buf + pos + 1 + 2))); + break; + default: + break; + } + pos += len; + } +} + +static int JS_WriteFunctionBytecode (BCWriterState *s, const uint8_t *bc_buf1, int bc_len) { + int pos, len, op; + uint8_t *bc_buf; + + bc_buf = js_malloc (s->ctx, bc_len); + if (!bc_buf) return -1; + memcpy (bc_buf, bc_buf1, bc_len); + + pos = 0; + while (pos < bc_len) { + op = bc_buf[pos]; + len = short_opcode_info (op).size; + switch (short_opcode_info (op).fmt) { + case OP_FMT_key: + case OP_FMT_key_u8: + case OP_FMT_key_u16: + case OP_FMT_key_label_u16: + /* Key operand is a cpool index; cpool values serialized separately */ + break; + default: + break; + } + pos += len; + } + + if (is_be ()) bc_byte_swap (bc_buf, bc_len); + + dbuf_put (&s->dbuf, bc_buf, bc_len); + + js_free (s->ctx, bc_buf); + return 0; +} + +static void JS_WriteString (BCWriterState *s, JSText *p) { + int i; + int len = (int)JSText_len (p); + /* UTF-32: write length, then each character as 32-bit value */ + bc_put_leb128 (s, (uint32_t)len); + for (i = 0; i < len; i++) { + uint32_t c = string_get (p, i); + bc_put_u32 (s, c); + } +} + +static int JS_WriteObjectRec (BCWriterState *s, JSValue obj); + +static int JS_WriteObjectTag (BCWriterState *s, JSValue obj) { + JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (obj); + uint32_t mask = (uint32_t)objhdr_cap56 (rec->mist_hdr); + uint32_t i, prop_count; + int pass; + + bc_put_u8 (s, BC_TAG_OBJECT); + prop_count = 0; + + /* Two-pass: count then write */ + for (pass = 0; pass < 2; pass++) { + if (pass == 1) bc_put_leb128 (s, prop_count); + for (i = 1; i <= mask; i++) { + JSValue k = rec->slots[i].key; + if (!rec_key_is_empty (k) && !rec_key_is_tomb (k) && JS_IsText (k)) { + if (pass == 0) { + prop_count++; + } else { + /* Write key as JSValue text directly */ + bc_put_key (s, k); + if (JS_WriteObjectRec (s, rec->slots[i].val)) goto fail; + } + } + } + } + return 0; +fail: + return -1; +} + +static int JS_WriteObjectRec (BCWriterState *s, JSValue obj) { + uint32_t tag; + + if (js_check_stack_overflow (s->ctx, 0)) { + JS_ThrowStackOverflow (s->ctx); + return -1; + } + + tag = JS_VALUE_GET_NORM_TAG (obj); + switch (tag) { + case JS_TAG_NULL: + bc_put_u8 (s, BC_TAG_NULL); + break; + case JS_TAG_BOOL: + bc_put_u8 (s, BC_TAG_BOOL_FALSE + JS_VALUE_GET_INT (obj)); + break; + case JS_TAG_INT: + bc_put_u8 (s, BC_TAG_INT32); + bc_put_sleb128 (s, JS_VALUE_GET_INT (obj)); + break; + case JS_TAG_FLOAT64: { + JSFloat64Union u; + bc_put_u8 (s, BC_TAG_FLOAT64); + u.d = JS_VALUE_GET_FLOAT64 (obj); + bc_put_u64 (s, u.u64); + } break; + case JS_TAG_STRING_IMM: { + /* Immediate ASCII string */ + int len = MIST_GetImmediateASCIILen (obj); + char buf[8]; + for (int i = 0; i < len; i++) + buf[i] = MIST_GetImmediateASCIIChar (obj, i); + JSValue tmp = js_new_string8_len (s->ctx, buf, len); + if (JS_IsException (tmp)) goto fail; + JSText *p = JS_VALUE_GET_STRING (tmp); + bc_put_u8 (s, BC_TAG_STRING); + JS_WriteString (s, p); + } break; + case JS_TAG_PTR: + /* Check if this is a heap string */ + if (JS_IsText (obj)) { + JSText *p = JS_VALUE_GET_STRING (obj); + bc_put_u8 (s, BC_TAG_STRING); + JS_WriteString (s, p); + break; + } + /* Check if this is an intrinsic array */ + if (JS_IsArray (obj)) { + JSArray *arr = JS_VALUE_GET_ARRAY (obj); + uint32_t i; + bc_put_u8 (s, BC_TAG_ARRAY); + bc_put_leb128 (s, arr->len); + for (i = 0; i < arr->len; i++) { + if (JS_WriteObjectRec (s, arr->values[i])) goto fail; + } + } else { + JSRecord *p = JS_VALUE_GET_OBJ (obj); + int ret, idx; + + /* Always use object_list for cycle detection */ + idx = js_object_list_find (s->ctx, &s->object_list, p); + if (idx >= 0) { + if (s->allow_reference) { + bc_put_u8 (s, BC_TAG_OBJECT_REFERENCE); + bc_put_leb128 (s, idx); + break; + } else { + JS_ThrowTypeError (s->ctx, "circular reference"); + goto fail; + } + } + if (js_object_list_add (s->ctx, &s->object_list, p)) goto fail; + switch (REC_GET_CLASS_ID(p)) { + case JS_CLASS_OBJECT: + ret = JS_WriteObjectTag (s, obj); + break; + default: + JS_ThrowTypeError (s->ctx, "unsupported object class"); + ret = -1; + break; + } + if (ret) goto fail; + } + break; + default: + JS_ThrowInternalError (s->ctx, "unsupported tag (%d)", tag); + goto fail; + } + return 0; + +fail: + return -1; +} + +/* create the atom table */ +static int JS_WriteObjectAtoms (BCWriterState *s) { + DynBuf dbuf1; + int atoms_size; + + dbuf1 = s->dbuf; + js_dbuf_init (s->ctx, &s->dbuf); + bc_put_u8 (s, BC_VERSION); + + /* Atoms removed - write empty atom table */ + bc_put_leb128 (s, 0); + /* XXX: should check for OOM in above phase */ + + /* move the atoms at the start */ + /* XXX: could just append dbuf1 data, but it uses more memory if + dbuf1 is larger than dbuf */ + atoms_size = s->dbuf.size; + if (dbuf_realloc (&dbuf1, dbuf1.size + atoms_size)) goto fail; + memmove (dbuf1.buf + atoms_size, dbuf1.buf, dbuf1.size); + memcpy (dbuf1.buf, s->dbuf.buf, atoms_size); + dbuf1.size += atoms_size; + dbuf_free (&s->dbuf); + s->dbuf = dbuf1; + return 0; +fail: + dbuf_free (&dbuf1); + return -1; +} + +uint8_t *JS_WriteObject2 (JSContext *ctx, size_t *psize, JSValue obj, int flags, uint8_t ***psab_tab, size_t *psab_tab_len) { + BCWriterState ss, *s = &ss; + + memset (s, 0, sizeof (*s)); + s->ctx = ctx; + s->allow_bytecode = ((flags & JS_WRITE_OBJ_BYTECODE) != 0); + s->allow_sab = ((flags & JS_WRITE_OBJ_SAB) != 0); + s->allow_reference = ((flags & JS_WRITE_OBJ_REFERENCE) != 0); + js_dbuf_init (ctx, &s->dbuf); + js_object_list_init (&s->object_list); + + if (JS_WriteObjectRec (s, obj)) goto fail; + if (JS_WriteObjectAtoms (s)) goto fail; + js_object_list_end (ctx, &s->object_list); + *psize = s->dbuf.size; + if (psab_tab) *psab_tab = s->sab_tab; + if (psab_tab_len) *psab_tab_len = s->sab_tab_len; + return s->dbuf.buf; +fail: + js_object_list_end (ctx, &s->object_list); + dbuf_free (&s->dbuf); + *psize = 0; + if (psab_tab) *psab_tab = NULL; + if (psab_tab_len) *psab_tab_len = 0; + return NULL; +} + +uint8_t *JS_WriteObject (JSContext *ctx, size_t *psize, JSValue obj, int flags) { + return JS_WriteObject2 (ctx, psize, obj, flags, NULL, NULL); +} + +typedef struct BCReaderState { + JSContext *ctx; + const uint8_t *buf_start, *ptr, *buf_end; + int error_state; + BOOL allow_sab : 8; + BOOL allow_bytecode : 8; + BOOL is_rom_data : 8; + BOOL allow_reference : 8; + /* object references */ + JSRecord **objects; + int objects_count; + int objects_size; + +#ifdef DUMP_READ_OBJECT + const uint8_t *ptr_last; + int level; +#endif +} BCReaderState; + +#ifdef DUMP_READ_OBJECT +static void __attribute__ ((format (printf, 2, 3))) +bc_read_trace (BCReaderState *s, const char *fmt, ...) { + va_list ap; + int i, n, n0; + + if (!s->ptr_last) s->ptr_last = s->buf_start; + + n = n0 = 0; + if (s->ptr > s->ptr_last || s->ptr == s->buf_start) { + n0 = printf ("%04x: ", (int)(s->ptr_last - s->buf_start)); + n += n0; + } + for (i = 0; s->ptr_last < s->ptr; i++) { + if ((i & 7) == 0 && i > 0) { + printf ("\n%*s", n0, ""); + n = n0; + } + n += printf (" %02x", *s->ptr_last++); + } + if (*fmt == '}') s->level--; + if (n < 32 + s->level * 2) { printf ("%*s", 32 + s->level * 2 - n, ""); } + va_start (ap, fmt); + vfprintf (stdout, fmt, ap); + va_end (ap); + if (strchr (fmt, '{')) s->level++; +} +#else +#define bc_read_trace(...) +#endif + +static int bc_read_error_end (BCReaderState *s) { + if (!s->error_state) { + JS_ThrowSyntaxError (s->ctx, "read after the end of the buffer"); + } + return s->error_state = -1; +} + +static int bc_get_u8 (BCReaderState *s, uint8_t *pval) { + if (unlikely (s->buf_end - s->ptr < 1)) { + *pval = 0; /* avoid warning */ + return bc_read_error_end (s); + } + *pval = *s->ptr++; + return 0; +} + +static int bc_get_u16 (BCReaderState *s, uint16_t *pval) { + uint16_t v; + if (unlikely (s->buf_end - s->ptr < 2)) { + *pval = 0; /* avoid warning */ + return bc_read_error_end (s); + } + v = get_u16 (s->ptr); + if (is_be ()) v = bswap16 (v); + *pval = v; + s->ptr += 2; + return 0; +} + +static __maybe_unused int bc_get_u32 (BCReaderState *s, uint32_t *pval) { + uint32_t v; + if (unlikely (s->buf_end - s->ptr < 4)) { + *pval = 0; /* avoid warning */ + return bc_read_error_end (s); + } + v = get_u32 (s->ptr); + if (is_be ()) v = bswap32 (v); + *pval = v; + s->ptr += 4; + return 0; +} + +static int bc_get_u64 (BCReaderState *s, uint64_t *pval) { + uint64_t v; + if (unlikely (s->buf_end - s->ptr < 8)) { + *pval = 0; /* avoid warning */ + return bc_read_error_end (s); + } + v = get_u64 (s->ptr); + if (is_be ()) v = bswap64 (v); + *pval = v; + s->ptr += 8; + return 0; +} + +static int bc_get_leb128 (BCReaderState *s, uint32_t *pval) { + int ret; + ret = get_leb128 (pval, s->ptr, s->buf_end); + if (unlikely (ret < 0)) return bc_read_error_end (s); + s->ptr += ret; + return 0; +} + +static int bc_get_sleb128 (BCReaderState *s, int32_t *pval) { + int ret; + ret = get_sleb128 (pval, s->ptr, s->buf_end); + if (unlikely (ret < 0)) return bc_read_error_end (s); + s->ptr += ret; + return 0; +} + +/* XXX: used to read an `int` with a positive value */ +static int bc_get_leb128_int (BCReaderState *s, int *pval) { + return bc_get_leb128 (s, (uint32_t *)pval); +} + +static int bc_get_leb128_u16 (BCReaderState *s, uint16_t *pval) { + uint32_t val; + if (bc_get_leb128 (s, &val)) { + *pval = 0; + return -1; + } + *pval = val; + return 0; +} + +static int bc_get_buf (BCReaderState *s, uint8_t *buf, uint32_t buf_len) { + if (buf_len != 0) { + if (unlikely (!buf || s->buf_end - s->ptr < buf_len)) + return bc_read_error_end (s); + memcpy (buf, s->ptr, buf_len); + s->ptr += buf_len; + } + return 0; +} + +/* Read a JSValue key (text) from bytecode stream */ +static int bc_get_key (BCReaderState *s, JSValue *pkey) { + uint32_t len; + if (bc_get_leb128 (s, &len)) return -1; + if (len == 0) { + *pkey = JS_KEY_empty; + return 0; + } + /* Read UTF-8 bytes */ + char *buf = alloca (len + 1); + for (uint32_t i = 0; i < len; i++) { + uint8_t byte; + if (bc_get_u8 (s, &byte)) return -1; + buf[i] = (char)byte; + } + buf[len] = '\0'; + /* Create interned key from UTF-8 */ + *pkey = js_key_new_len (s->ctx, buf, len); + return JS_IsNull (*pkey) ? -1 : 0; +} + +static JSText *JS_ReadString (BCReaderState *s) { + uint32_t len; + size_t size; + JSText *p; + uint32_t i; + + if (bc_get_leb128 (s, &len)) return NULL; + /* len is uint32_t, JS_STRING_LEN_MAX is 56 bits, so this always fits */ + p = js_alloc_string (s->ctx, len); + if (!p) { + s->error_state = -1; + return NULL; + } + /* UTF-32: each character is 32-bit */ + size = (size_t)len * sizeof (uint32_t); + if ((s->buf_end - s->ptr) < size) { + bc_read_error_end (s); + /* GC handles cleanup - partial string will be collected */ + return NULL; + } + for (i = 0; i < len; i++) { + uint32_t c = get_u32 (s->ptr); + if (is_be ()) c = bswap32 (c); + string_put (p, i, c); + s->ptr += 4; + } +#ifdef DUMP_READ_OBJECT + JS_DumpString (s->ctx->rt, p); + printf ("\n"); +#endif + return p; +} + +static uint32_t bc_get_flags (uint32_t flags, int *pidx, int n) { + uint32_t val; + /* XXX: this does not work for n == 32 */ + val = (flags >> *pidx) & ((1U << n) - 1); + *pidx += n; + return val; +} + +static int JS_ReadFunctionBytecode (BCReaderState *s, JSFunctionBytecode *b, int byte_code_offset, uint32_t bc_len) { + uint8_t *bc_buf; + int pos, len, op; + + if (s->is_rom_data) { + /* directly use the input buffer */ + if (unlikely (s->buf_end - s->ptr < bc_len)) return bc_read_error_end (s); + bc_buf = (uint8_t *)s->ptr; + s->ptr += bc_len; + } else { + bc_buf = (void *)((uint8_t *)b + byte_code_offset); + if (bc_get_buf (s, bc_buf, bc_len)) return -1; + } + b->byte_code_buf = bc_buf; + + if (is_be ()) bc_byte_swap (bc_buf, bc_len); + + pos = 0; + while (pos < bc_len) { + op = bc_buf[pos]; + len = short_opcode_info (op).size; + switch (short_opcode_info (op).fmt) { + case OP_FMT_key: + case OP_FMT_key_u8: + case OP_FMT_key_u16: + case OP_FMT_key_label_u16: + /* Key operand is a cpool index; cpool values deserialized separately */ + break; + default: + break; + } + pos += len; + } + return 0; +} + +static JSValue JS_ReadObjectRec (BCReaderState *s); + +static int BC_add_object_ref1 (BCReaderState *s, JSRecord *p) { + if (s->allow_reference) { + if (js_resize_array (s->ctx, (void *)&s->objects, sizeof (s->objects[0]), &s->objects_size, s->objects_count + 1)) + return -1; + s->objects[s->objects_count++] = p; + } + return 0; +} + +static int BC_add_object_ref (BCReaderState *s, JSValue obj) { + return BC_add_object_ref1 (s, JS_VALUE_GET_OBJ (obj)); +} + +static JSValue JS_ReadFunctionTag (BCReaderState *s) { + JSContext *ctx = s->ctx; + JSFunctionBytecode bc, *b; + JSValue obj = JS_NULL; + uint16_t v16; + uint8_t v8; + int idx, i, local_count; + int function_size, cpool_offset, byte_code_offset; + int closure_var_offset, vardefs_offset; + + memset (&bc, 0, sizeof (bc)); + bc.header = objhdr_make (0, OBJ_CODE, false, false, false, false); + + if (bc_get_u16 (s, &v16)) goto fail; + idx = 0; + bc.has_prototype = bc_get_flags (v16, &idx, 1); + bc.has_simple_parameter_list = bc_get_flags (v16, &idx, 1); + bc.func_kind = bc_get_flags (v16, &idx, 2); + bc.has_debug = bc_get_flags (v16, &idx, 1); + bc.is_direct_or_indirect_eval = bc_get_flags (v16, &idx, 1); + bc.read_only_bytecode = s->is_rom_data; + if (bc_get_u8 (s, &v8)) goto fail; + bc.js_mode = v8; + if (bc_get_key (s, &bc.func_name)) goto fail; + if (bc_get_leb128_u16 (s, &bc.arg_count)) goto fail; + if (bc_get_leb128_u16 (s, &bc.var_count)) goto fail; + if (bc_get_leb128_u16 (s, &bc.defined_arg_count)) goto fail; + if (bc_get_leb128_u16 (s, &bc.stack_size)) goto fail; + if (bc_get_leb128_int (s, &bc.closure_var_count)) goto fail; + if (bc_get_leb128_int (s, &bc.cpool_count)) goto fail; + if (bc_get_leb128_int (s, &bc.byte_code_len)) goto fail; + if (bc_get_leb128_int (s, &local_count)) goto fail; + + if (bc.has_debug) { + function_size = sizeof (*b); + } else { + function_size = offsetof (JSFunctionBytecode, debug); + } + cpool_offset = function_size; + function_size += bc.cpool_count * sizeof (*bc.cpool); + vardefs_offset = function_size; + function_size += local_count * sizeof (*bc.vardefs); + closure_var_offset = function_size; + function_size += bc.closure_var_count * sizeof (*bc.closure_var); + byte_code_offset = function_size; + if (!bc.read_only_bytecode) { function_size += bc.byte_code_len; } + + b = js_mallocz (ctx, function_size); + if (!b) return JS_EXCEPTION; + + memcpy (b, &bc, offsetof (JSFunctionBytecode, debug)); + b->header = objhdr_make (0, OBJ_CODE, false, false, false, false); + if (local_count != 0) { + b->vardefs = (void *)((uint8_t *)b + vardefs_offset); + } + if (b->closure_var_count != 0) { + b->closure_var = (void *)((uint8_t *)b + closure_var_offset); + } + if (b->cpool_count != 0) { + b->cpool = (void *)((uint8_t *)b + cpool_offset); + } + + obj = JS_MKPTR (b); + +#ifdef DUMP_READ_OBJECT + bc_read_trace (s, "name: "); + print_atom (s->ctx, b->func_name); + printf ("\n"); +#endif + bc_read_trace (s, "args=%d vars=%d defargs=%d closures=%d cpool=%d\n", b->arg_count, b->var_count, b->defined_arg_count, b->closure_var_count, b->cpool_count); + bc_read_trace (s, "stack=%d bclen=%d locals=%d\n", b->stack_size, b->byte_code_len, local_count); + + if (local_count != 0) { + bc_read_trace (s, "vars {\n"); + for (i = 0; i < local_count; i++) { + JSVarDef *vd = &b->vardefs[i]; + if (bc_get_key (s, &vd->var_name)) goto fail; + if (bc_get_leb128_int (s, &vd->scope_level)) goto fail; + if (bc_get_leb128_int (s, &vd->scope_next)) goto fail; + vd->scope_next--; + if (bc_get_u8 (s, &v8)) goto fail; + idx = 0; + vd->var_kind = bc_get_flags (v8, &idx, 4); + vd->is_const = bc_get_flags (v8, &idx, 1); + vd->is_lexical = bc_get_flags (v8, &idx, 1); + vd->is_captured = bc_get_flags (v8, &idx, 1); +#ifdef DUMP_READ_OBJECT + bc_read_trace (s, "name: "); + print_atom (s->ctx, vd->var_name); + printf ("\n"); +#endif + } + bc_read_trace (s, "}\n"); + } + if (b->closure_var_count != 0) { + bc_read_trace (s, "closure vars {\n"); + for (i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cv = &b->closure_var[i]; + int var_idx; + if (bc_get_key (s, &cv->var_name)) goto fail; + if (bc_get_leb128_int (s, &var_idx)) goto fail; + cv->var_idx = var_idx; + if (bc_get_u8 (s, &v8)) goto fail; + idx = 0; + cv->is_local = bc_get_flags (v8, &idx, 1); + cv->is_arg = bc_get_flags (v8, &idx, 1); + cv->is_const = bc_get_flags (v8, &idx, 1); + cv->is_lexical = bc_get_flags (v8, &idx, 1); + cv->var_kind = bc_get_flags (v8, &idx, 4); +#ifdef DUMP_READ_OBJECT + bc_read_trace (s, "name: "); + print_atom (s->ctx, cv->var_name); + printf ("\n"); +#endif + } + bc_read_trace (s, "}\n"); + } + { + bc_read_trace (s, "bytecode {\n"); + if (JS_ReadFunctionBytecode (s, b, byte_code_offset, b->byte_code_len)) + goto fail; + bc_read_trace (s, "}\n"); + } + if (b->has_debug) { + /* read optional debug information */ + bc_read_trace (s, "debug {\n"); + if (bc_get_key (s, &b->debug.filename)) goto fail; +#ifdef DUMP_READ_OBJECT + bc_read_trace (s, "filename: "); + print_atom (s->ctx, b->debug.filename); + printf ("\n"); +#endif + if (bc_get_leb128_int (s, &b->debug.pc2line_len)) goto fail; + if (b->debug.pc2line_len) { + b->debug.pc2line_buf = js_mallocz (ctx, b->debug.pc2line_len); + if (!b->debug.pc2line_buf) goto fail; + if (bc_get_buf (s, b->debug.pc2line_buf, b->debug.pc2line_len)) + goto fail; + } + if (bc_get_leb128_int (s, &b->debug.source_len)) goto fail; + if (b->debug.source_len) { + bc_read_trace (s, "source: %d bytes\n", b->source_len); + b->debug.source = js_mallocz (ctx, b->debug.source_len); + if (!b->debug.source) goto fail; + if (bc_get_buf (s, (uint8_t *)b->debug.source, b->debug.source_len)) + goto fail; + } + bc_read_trace (s, "}\n"); + } + if (b->cpool_count != 0) { + bc_read_trace (s, "cpool {\n"); + for (i = 0; i < b->cpool_count; i++) { + JSValue val; + val = JS_ReadObjectRec (s); + if (JS_IsException (val)) goto fail; + b->cpool[i] = val; + } + bc_read_trace (s, "}\n"); + } + return obj; +fail: + return JS_EXCEPTION; +} + +static JSValue JS_ReadObjectTag (BCReaderState *s) { + JSContext *ctx = s->ctx; + JSValue obj; + uint32_t prop_count, i; + JSValue key; + JSValue val; + int ret; + + obj = JS_NewObject (ctx); + if (BC_add_object_ref (s, obj)) goto fail; + if (bc_get_leb128 (s, &prop_count)) goto fail; + for (i = 0; i < prop_count; i++) { + if (bc_get_key (s, &key)) goto fail; +#ifdef DUMP_READ_OBJECT + bc_read_trace (s, "propname: "); + JS_DumpValue (key); + printf ("\n"); +#endif + val = JS_ReadObjectRec (s); + if (JS_IsException (val)) { + goto fail; + } + ret = JS_SetPropertyInternal (ctx, obj, key, val); + if (ret < 0) goto fail; + } + return obj; +fail: + return JS_EXCEPTION; +} + +static JSValue JS_ReadArray (BCReaderState *s, int tag) { + JSContext *ctx = s->ctx; + JSValue obj; + uint32_t len, i; + JSValue val; + BOOL is_template; + + is_template = (tag == BC_TAG_TEMPLATE_OBJECT); + + if (bc_get_leb128 (s, &len)) return JS_EXCEPTION; + + /* Create intrinsic array with preallocated capacity */ + obj = JS_NewArrayLen (ctx, len); + if (JS_IsException (obj)) return JS_EXCEPTION; + + /* Note: intrinsic arrays don't support object reference tracking + for circular references - they're simpler value containers */ + + /* Read and set each element directly */ + JSArray *arr = JS_VALUE_GET_ARRAY (obj); + for (i = 0; i < len; i++) { + val = JS_ReadObjectRec (s); + if (JS_IsException (val)) goto fail; + /* Replace the null placeholder with the read value */ + arr->values[i] = val; + } + + /* Template objects have additional 'raw' property - not supported for + * intrinsic arrays */ + if (is_template) { + val = JS_ReadObjectRec (s); + if (JS_IsException (val)) goto fail; + /* Skip the raw property for intrinsic arrays */ + } + return obj; +fail: + return JS_EXCEPTION; +} + +static JSValue JS_ReadObjectRec (BCReaderState *s) { + JSContext *ctx = s->ctx; + uint8_t tag; + JSValue obj = JS_NULL; + + if (js_check_stack_overflow (ctx, 0)) return JS_ThrowStackOverflow (ctx); + + if (bc_get_u8 (s, &tag)) return JS_EXCEPTION; + + bc_read_trace (s, "%s {\n", bc_tag_str[tag]); + + switch (tag) { + case BC_TAG_NULL: + obj = JS_NULL; + break; + case BC_TAG_BOOL_FALSE: + case BC_TAG_BOOL_TRUE: + obj = JS_NewBool (ctx, tag - BC_TAG_BOOL_FALSE); + break; + case BC_TAG_INT32: { + int32_t val; + if (bc_get_sleb128 (s, &val)) return JS_EXCEPTION; + bc_read_trace (s, "%d\n", val); + obj = JS_NewInt32 (ctx, val); + } break; + case BC_TAG_FLOAT64: { + JSFloat64Union u; + if (bc_get_u64 (s, &u.u64)) return JS_EXCEPTION; + bc_read_trace (s, "%g\n", u.d); + obj = __JS_NewFloat64 (ctx, u.d); + } break; + case BC_TAG_STRING: { + JSText *p; + p = JS_ReadString (s); + if (!p) return JS_EXCEPTION; + obj = JS_MKPTR (p); + } break; + case BC_TAG_FUNCTION_BYTECODE: + if (!s->allow_bytecode) goto invalid_tag; + obj = JS_ReadFunctionTag (s); + break; + case BC_TAG_OBJECT: + obj = JS_ReadObjectTag (s); + break; + case BC_TAG_ARRAY: + case BC_TAG_TEMPLATE_OBJECT: + obj = JS_ReadArray (s, tag); + break; + case BC_TAG_OBJECT_REFERENCE: { + uint32_t val; + if (!s->allow_reference) + return JS_ThrowSyntaxError (ctx, "object references are not allowed"); + if (bc_get_leb128 (s, &val)) return JS_EXCEPTION; + bc_read_trace (s, "%u\n", val); + if (val >= s->objects_count) { + return JS_ThrowSyntaxError (ctx, "invalid object reference (%u >= %u)", val, s->objects_count); + } + obj = JS_MKPTR (s->objects[val]); + } break; + default: + invalid_tag: + return JS_ThrowSyntaxError (ctx, "invalid tag (tag=%d pos=%u)", tag, (unsigned int)(s->ptr - s->buf_start)); + } + bc_read_trace (s, "}\n"); + return obj; +} + +static int JS_ReadObjectAtoms (BCReaderState *s) { + uint8_t v8; + uint32_t atom_count; + + if (bc_get_u8 (s, &v8)) return -1; + if (v8 != BC_VERSION) { + JS_ThrowSyntaxError (s->ctx, "invalid version (%d expected=%d)", v8, BC_VERSION); + return -1; + } + /* Read atom count - should always be 0 for new bytecode */ + if (bc_get_leb128 (s, &atom_count)) return -1; + bc_read_trace (s, "%d atom indexes\n", atom_count); + if (atom_count != 0) { + JS_ThrowSyntaxError (s->ctx, "legacy atom format not supported"); + return -1; + } + return 0; +} + +static void bc_reader_free (BCReaderState *s) { + js_free (s->ctx, s->objects); +} + +JSValue JS_ReadObject (JSContext *ctx, const uint8_t *buf, size_t buf_len, int flags) { + BCReaderState ss, *s = &ss; + JSValue obj; + + ctx->binary_object_count += 1; + ctx->binary_object_size += buf_len; + + memset (s, 0, sizeof (*s)); + s->ctx = ctx; + s->buf_start = buf; + s->buf_end = buf + buf_len; + s->ptr = buf; + s->allow_bytecode = ((flags & JS_READ_OBJ_BYTECODE) != 0); + s->is_rom_data = ((flags & JS_READ_OBJ_ROM_DATA) != 0); + s->allow_sab = ((flags & JS_READ_OBJ_SAB) != 0); + s->allow_reference = ((flags & JS_READ_OBJ_REFERENCE) != 0); + if (JS_ReadObjectAtoms (s)) { + obj = JS_EXCEPTION; + } else { + obj = JS_ReadObjectRec (s); + } + bc_reader_free (s); + return obj; +} + +/*******************************************************************/ +/* JSCompiledUnit Serialization */ + +/* Magic number for compiled unit files */ +#define COMPILED_UNIT_MAGIC 0x43454C4C /* "CELL" */ +#define COMPILED_UNIT_VERSION 1 + +/* Write a JSCompiledUnit to a byte buffer. + Returns allocated buffer (caller must free), or NULL on error. + The format is: + - magic (4 bytes): "CELL" + - version (1 byte) + - flags (1 byte): has_debug + - local_count (2 bytes) + - stack_size (2 bytes) + - cpool_count (4 bytes) + - byte_code_len (4 bytes) + - cpool values (variable) + - bytecode (byte_code_len bytes) + - debug section (if has_debug) +*/ +uint8_t *JS_WriteCompiledUnit (JSContext *ctx, JSCompiledUnit *unit, size_t *out_len) { + DynBuf dbuf; + dbuf_init (&dbuf); + + /* Magic */ + dbuf_put_u32 (&dbuf, COMPILED_UNIT_MAGIC); + + /* Version */ + dbuf_putc (&dbuf, COMPILED_UNIT_VERSION); + + /* Flags */ + uint8_t flags = 0; + if (unit->has_debug) flags |= 1; + dbuf_putc (&dbuf, flags); + + /* Stack requirements */ + dbuf_put_u16 (&dbuf, unit->local_count); + dbuf_put_u16 (&dbuf, unit->stack_size); + + /* Counts */ + dbuf_put_u32 (&dbuf, unit->cpool_count); + dbuf_put_u32 (&dbuf, unit->byte_code_len); + + /* Write constant pool (simplified - just strings for now) */ + for (int i = 0; i < unit->cpool_count; i++) { + JSValue val = unit->cpool[i]; + uint32_t tag = JS_VALUE_GET_TAG (val); + + if (tag == JS_TAG_INT) { + dbuf_putc (&dbuf, 1); /* type: int */ + int32_t v = JS_VALUE_GET_INT (val); + dbuf_put_u32 (&dbuf, (uint32_t)v); + } else if (tag == JS_TAG_FLOAT64) { + dbuf_putc (&dbuf, 2); /* type: float */ + double d = JS_VALUE_GET_FLOAT64 (val); + dbuf_put (&dbuf, (uint8_t *)&d, sizeof (d)); + } else if (JS_IsText (val)) { + dbuf_putc (&dbuf, 3); /* type: string */ + const char *str = JS_ToCString (ctx, val); + if (str) { + size_t len = strlen (str); + dbuf_put_u32 (&dbuf, (uint32_t)len); + dbuf_put (&dbuf, (uint8_t *)str, len); + JS_FreeCString (ctx, str); + } else { + dbuf_put_u32 (&dbuf, 0); + } + } else { + dbuf_putc (&dbuf, 0); /* type: null/unsupported */ + } + } + + /* Write bytecode */ + dbuf_put (&dbuf, unit->byte_code_buf, unit->byte_code_len); + + /* Write debug section if present */ + if (unit->has_debug) { + /* Filename */ + const char *fname = JS_ToCString (ctx, unit->debug.filename); + if (fname) { + size_t len = strlen (fname); + dbuf_put_u32 (&dbuf, (uint32_t)len); + dbuf_put (&dbuf, (uint8_t *)fname, len); + JS_FreeCString (ctx, fname); + } else { + dbuf_put_u32 (&dbuf, 0); + } + + /* source_len, pc2line_len */ + dbuf_put_u32 (&dbuf, unit->debug.source_len); + dbuf_put_u32 (&dbuf, unit->debug.pc2line_len); + + /* pc2line_buf */ + if (unit->debug.pc2line_len > 0 && unit->debug.pc2line_buf) { + dbuf_put (&dbuf, unit->debug.pc2line_buf, unit->debug.pc2line_len); + } + + /* source */ + if (unit->debug.source_len > 0 && unit->debug.source) { + dbuf_put (&dbuf, (uint8_t *)unit->debug.source, unit->debug.source_len); + } + } + + *out_len = dbuf.size; + return dbuf.buf; +} + +/* Read a JSCompiledUnit from a byte buffer. + Returns unit on success, NULL on error. */ +JSCompiledUnit *JS_ReadCompiledUnit (JSContext *ctx, const uint8_t *buf, size_t buf_len) { + const uint8_t *p = buf; + const uint8_t *end = buf + buf_len; + + if (buf_len < 18) return NULL; /* Minimum header size */ + + /* Check magic */ + uint32_t magic = get_u32 (p); p += 4; + if (magic != COMPILED_UNIT_MAGIC) return NULL; + + /* Version */ + uint8_t version = *p++; + if (version != COMPILED_UNIT_VERSION) return NULL; + + /* Flags */ + uint8_t flags = *p++; + BOOL has_debug = (flags & 1) != 0; + + /* Stack requirements */ + uint16_t local_count = get_u16 (p); p += 2; + uint16_t stack_size = get_u16 (p); p += 2; + + /* Counts */ + uint32_t cpool_count = get_u32 (p); p += 4; + uint32_t byte_code_len = get_u32 (p); p += 4; + + /* Calculate allocation size */ + size_t unit_size; + if (has_debug) { + unit_size = sizeof (JSCompiledUnit); + } else { + unit_size = offsetof (JSCompiledUnit, debug); + } + size_t cpool_offset = unit_size; + unit_size += cpool_count * sizeof (JSValue); + size_t bc_offset = unit_size; + unit_size += byte_code_len; + + /* Allocate unit */ + JSCompiledUnit *unit = pjs_mallocz (unit_size); + if (!unit) return NULL; + + /* Initialize header */ + unit->header = objhdr_make (0, OBJ_CODE, false, false, false, false); + unit->has_debug = has_debug; + unit->read_only_bytecode = 0; + unit->local_count = local_count; + unit->stack_size = stack_size; + unit->cpool_count = cpool_count; + unit->byte_code_len = byte_code_len; + + /* Setup pointers */ + if (cpool_count > 0) { + unit->cpool = (JSValue *)((uint8_t *)unit + cpool_offset); + } else { + unit->cpool = NULL; + } + unit->byte_code_buf = (uint8_t *)unit + bc_offset; + + /* Read constant pool */ + for (uint32_t i = 0; i < cpool_count; i++) { + if (p >= end) goto fail; + uint8_t type = *p++; + + switch (type) { + case 0: /* null */ + unit->cpool[i] = JS_NULL; + break; + case 1: /* int */ + if (p + 4 > end) goto fail; + unit->cpool[i] = JS_NewInt32 (ctx, (int32_t)get_u32 (p)); + p += 4; + break; + case 2: /* float */ + if (p + 8 > end) goto fail; + { + double d; + memcpy (&d, p, sizeof (d)); + unit->cpool[i] = JS_NewFloat64 (ctx, d); + p += 8; + } + break; + case 3: /* string */ + if (p + 4 > end) goto fail; + { + uint32_t len = get_u32 (p); p += 4; + if (p + len > end) goto fail; + unit->cpool[i] = JS_NewStringLen (ctx, (const char *)p, len); + p += len; + } + break; + default: + unit->cpool[i] = JS_NULL; + break; + } + } + + /* Read bytecode */ + if (p + byte_code_len > end) goto fail; + memcpy (unit->byte_code_buf, p, byte_code_len); + p += byte_code_len; + + /* Read debug section if present */ + if (has_debug) { + /* Filename */ + if (p + 4 > end) goto fail; + uint32_t fname_len = get_u32 (p); p += 4; + if (p + fname_len > end) goto fail; + if (fname_len > 0) { + unit->debug.filename = JS_NewStringLen (ctx, (const char *)p, fname_len); + } else { + unit->debug.filename = JS_NULL; + } + p += fname_len; + + /* source_len, pc2line_len */ + if (p + 8 > end) goto fail; + unit->debug.source_len = get_u32 (p); p += 4; + unit->debug.pc2line_len = get_u32 (p); p += 4; + + /* pc2line_buf */ + if (unit->debug.pc2line_len > 0) { + if (p + unit->debug.pc2line_len > end) goto fail; + unit->debug.pc2line_buf = js_malloc (ctx, unit->debug.pc2line_len); + if (!unit->debug.pc2line_buf) goto fail; + memcpy (unit->debug.pc2line_buf, p, unit->debug.pc2line_len); + p += unit->debug.pc2line_len; + } else { + unit->debug.pc2line_buf = NULL; + } + + /* source */ + if (unit->debug.source_len > 0) { + if (p + unit->debug.source_len > end) goto fail; + unit->debug.source = js_malloc (ctx, unit->debug.source_len + 1); + if (!unit->debug.source) goto fail; + memcpy (unit->debug.source, p, unit->debug.source_len); + unit->debug.source[unit->debug.source_len] = '\0'; + p += unit->debug.source_len; + } else { + unit->debug.source = NULL; + } + } + + return unit; + +fail: + pjs_free (unit); + return NULL; +} + +/*******************************************************************/ +/* CellModule Serialization (context-neutral) */ + +/* Free a CellModule and all its contents */ +void cell_module_free (CellModule *mod) { + if (!mod) return; + + /* Free string table */ + if (mod->string_data) pjs_free (mod->string_data); + if (mod->string_offsets) pjs_free (mod->string_offsets); + + /* Free units */ + if (mod->units) { + for (uint32_t i = 0; i < mod->unit_count; i++) { + CellUnit *u = &mod->units[i]; + if (u->constants) pjs_free (u->constants); + if (u->bytecode) pjs_free (u->bytecode); + if (u->upvalues) pjs_free (u->upvalues); + if (u->externals) pjs_free (u->externals); + if (u->pc2line) pjs_free (u->pc2line); + } + pjs_free (mod->units); + } + + /* Free source */ + if (mod->source) pjs_free (mod->source); + + pjs_free (mod); +} + +/* Write a CellModule to a byte buffer (context-neutral format). + Returns allocated buffer (caller must free with pjs_free), or NULL on error. + Format: + - magic (4 bytes): 0x4C4C4543 "CELL" + - version (1 byte) + - flags (1 byte) + - string_count (4 bytes) + - string_data_size (4 bytes) + - string_data (string_data_size bytes) + - string_offsets (string_count * 4 bytes) + - unit_count (4 bytes) + - for each unit: + - const_count (4 bytes) + - for each const: type (1 byte), value (4-8 bytes) + - bytecode_len (4 bytes) + - bytecode (bytecode_len bytes) + - arg_count (2 bytes) + - var_count (2 bytes) + - stack_size (2 bytes) + - upvalue_count (2 bytes) + - for each upvalue: kind (1 byte), index (2 bytes) + - external_count (4 bytes) + - for each external: pc_offset (4), name_sid (4), kind (1) + - pc2line_len (4 bytes) + - pc2line (pc2line_len bytes) + - name_sid (4 bytes) + - source_len (4 bytes) + - source (source_len bytes) +*/ +uint8_t *cell_module_write (CellModule *mod, size_t *out_len) { + DynBuf buf; + dbuf_init (&buf); + + /* Header */ + dbuf_put_u32 (&buf, mod->magic); + dbuf_putc (&buf, mod->version); + dbuf_putc (&buf, mod->flags); + + /* String table */ + dbuf_put_u32 (&buf, mod->string_count); + dbuf_put_u32 (&buf, mod->string_data_size); + if (mod->string_data_size > 0 && mod->string_data) { + dbuf_put (&buf, mod->string_data, mod->string_data_size); + } + if (mod->string_count > 0 && mod->string_offsets) { + for (uint32_t i = 0; i < mod->string_count; i++) { + dbuf_put_u32 (&buf, mod->string_offsets[i]); + } + } + + /* Units */ + dbuf_put_u32 (&buf, mod->unit_count); + for (uint32_t u = 0; u < mod->unit_count; u++) { + CellUnit *unit = &mod->units[u]; + + /* Constants */ + dbuf_put_u32 (&buf, unit->const_count); + for (uint32_t c = 0; c < unit->const_count; c++) { + CellConst *cc = &unit->constants[c]; + dbuf_putc (&buf, cc->type); + switch (cc->type) { + case CELL_CONST_NULL: + break; + case CELL_CONST_INT: + dbuf_put_u32 (&buf, (uint32_t)cc->i32); + break; + case CELL_CONST_FLOAT: { + JSFloat64Union fu; + fu.d = cc->f64; + dbuf_put_u32 (&buf, (uint32_t)(fu.u64 & 0xFFFFFFFF)); + dbuf_put_u32 (&buf, (uint32_t)(fu.u64 >> 32)); + break; + } + case CELL_CONST_STRING: + case CELL_CONST_UNIT: + dbuf_put_u32 (&buf, cc->string_sid); + break; + } + } + + /* Bytecode */ + dbuf_put_u32 (&buf, unit->bytecode_len); + if (unit->bytecode_len > 0 && unit->bytecode) { + dbuf_put (&buf, unit->bytecode, unit->bytecode_len); + } + + /* Stack requirements */ + dbuf_put_u16 (&buf, unit->arg_count); + dbuf_put_u16 (&buf, unit->var_count); + dbuf_put_u16 (&buf, unit->stack_size); + + /* Upvalues */ + dbuf_put_u16 (&buf, unit->upvalue_count); + for (uint16_t i = 0; i < unit->upvalue_count; i++) { + dbuf_putc (&buf, unit->upvalues[i].kind); + dbuf_put_u16 (&buf, unit->upvalues[i].index); + } + + /* Externals */ + dbuf_put_u32 (&buf, unit->external_count); + for (uint32_t i = 0; i < unit->external_count; i++) { + dbuf_put_u32 (&buf, unit->externals[i].pc_offset); + dbuf_put_u32 (&buf, unit->externals[i].name_sid); + dbuf_putc (&buf, unit->externals[i].kind); + } + + /* Debug */ + dbuf_put_u32 (&buf, unit->pc2line_len); + if (unit->pc2line_len > 0 && unit->pc2line) { + dbuf_put (&buf, unit->pc2line, unit->pc2line_len); + } + dbuf_put_u32 (&buf, unit->name_sid); + } + + /* Source */ + dbuf_put_u32 (&buf, mod->source_len); + if (mod->source_len > 0 && mod->source) { + dbuf_put (&buf, (uint8_t *)mod->source, mod->source_len); + } + + if (buf.error) { + dbuf_free (&buf); + *out_len = 0; + return NULL; + } + + *out_len = buf.size; + return buf.buf; +} + +/* Read a CellModule from a byte buffer. + Returns allocated CellModule (caller must free with cell_module_free), or NULL on error. */ +CellModule *cell_module_read (const uint8_t *buf, size_t buf_len) { + const uint8_t *p = buf; + const uint8_t *end = buf + buf_len; + + if (buf_len < 14) return NULL; /* minimum header size */ + + CellModule *mod = pjs_mallocz (sizeof (CellModule)); + if (!mod) return NULL; + + /* Header */ + mod->magic = get_u32 (p); p += 4; + if (mod->magic != CELL_MODULE_MAGIC) goto fail; + mod->version = *p++; + if (mod->version != CELL_MODULE_VERSION) goto fail; + mod->flags = *p++; + + /* String table */ + if (p + 8 > end) goto fail; + mod->string_count = get_u32 (p); p += 4; + mod->string_data_size = get_u32 (p); p += 4; + + if (mod->string_data_size > 0) { + if (p + mod->string_data_size > end) goto fail; + mod->string_data = pjs_malloc (mod->string_data_size); + if (!mod->string_data) goto fail; + memcpy (mod->string_data, p, mod->string_data_size); + p += mod->string_data_size; + } + + if (mod->string_count > 0) { + if (p + mod->string_count * 4 > end) goto fail; + mod->string_offsets = pjs_malloc (mod->string_count * sizeof (uint32_t)); + if (!mod->string_offsets) goto fail; + for (uint32_t i = 0; i < mod->string_count; i++) { + mod->string_offsets[i] = get_u32 (p); p += 4; + } + } + + /* Units */ + if (p + 4 > end) goto fail; + mod->unit_count = get_u32 (p); p += 4; + + if (mod->unit_count > 0) { + mod->units = pjs_mallocz (mod->unit_count * sizeof (CellUnit)); + if (!mod->units) goto fail; + + for (uint32_t u = 0; u < mod->unit_count; u++) { + CellUnit *unit = &mod->units[u]; + + /* Constants */ + if (p + 4 > end) goto fail; + unit->const_count = get_u32 (p); p += 4; + + if (unit->const_count > 0) { + unit->constants = pjs_mallocz (unit->const_count * sizeof (CellConst)); + if (!unit->constants) goto fail; + + for (uint32_t c = 0; c < unit->const_count; c++) { + if (p + 1 > end) goto fail; + unit->constants[c].type = *p++; + switch (unit->constants[c].type) { + case CELL_CONST_NULL: + break; + case CELL_CONST_INT: + if (p + 4 > end) goto fail; + unit->constants[c].i32 = (int32_t)get_u32 (p); p += 4; + break; + case CELL_CONST_FLOAT: { + if (p + 8 > end) goto fail; + JSFloat64Union fu; + fu.u64 = get_u32 (p); + fu.u64 |= ((uint64_t)get_u32 (p + 4)) << 32; + p += 8; + unit->constants[c].f64 = fu.d; + break; + } + case CELL_CONST_STRING: + case CELL_CONST_UNIT: + if (p + 4 > end) goto fail; + unit->constants[c].string_sid = get_u32 (p); p += 4; + break; + } + } + } + + /* Bytecode */ + if (p + 4 > end) goto fail; + unit->bytecode_len = get_u32 (p); p += 4; + + if (unit->bytecode_len > 0) { + if (p + unit->bytecode_len > end) goto fail; + unit->bytecode = pjs_malloc (unit->bytecode_len); + if (!unit->bytecode) goto fail; + memcpy (unit->bytecode, p, unit->bytecode_len); + p += unit->bytecode_len; + } + + /* Stack requirements */ + if (p + 6 > end) goto fail; + unit->arg_count = get_u16 (p); p += 2; + unit->var_count = get_u16 (p); p += 2; + unit->stack_size = get_u16 (p); p += 2; + + /* Upvalues */ + if (p + 2 > end) goto fail; + unit->upvalue_count = get_u16 (p); p += 2; + + if (unit->upvalue_count > 0) { + unit->upvalues = pjs_malloc (unit->upvalue_count * sizeof (CellCapDesc)); + if (!unit->upvalues) goto fail; + for (uint16_t i = 0; i < unit->upvalue_count; i++) { + if (p + 3 > end) goto fail; + unit->upvalues[i].kind = *p++; + unit->upvalues[i].index = get_u16 (p); p += 2; + } + } + + /* Externals */ + if (p + 4 > end) goto fail; + unit->external_count = get_u32 (p); p += 4; + + if (unit->external_count > 0) { + unit->externals = pjs_malloc (unit->external_count * sizeof (CellExternalReloc)); + if (!unit->externals) goto fail; + for (uint32_t i = 0; i < unit->external_count; i++) { + if (p + 9 > end) goto fail; + unit->externals[i].pc_offset = get_u32 (p); p += 4; + unit->externals[i].name_sid = get_u32 (p); p += 4; + unit->externals[i].kind = *p++; + } + } + + /* Debug */ + if (p + 4 > end) goto fail; + unit->pc2line_len = get_u32 (p); p += 4; + + if (unit->pc2line_len > 0) { + if (p + unit->pc2line_len > end) goto fail; + unit->pc2line = pjs_malloc (unit->pc2line_len); + if (!unit->pc2line) goto fail; + memcpy (unit->pc2line, p, unit->pc2line_len); + p += unit->pc2line_len; + } + + if (p + 4 > end) goto fail; + unit->name_sid = get_u32 (p); p += 4; + } + } + + /* Source */ + if (p + 4 > end) goto fail; + mod->source_len = get_u32 (p); p += 4; + + if (mod->source_len > 0) { + if (p + mod->source_len > end) goto fail; + mod->source = pjs_malloc (mod->source_len + 1); + if (!mod->source) goto fail; + memcpy (mod->source, p, mod->source_len); + mod->source[mod->source_len] = '\0'; + p += mod->source_len; + } + + return mod; + +fail: + cell_module_free (mod); + return NULL; +} + +/* Helper: get string from CellModule string table */ +static const char *cell_module_get_string (CellModule *mod, uint32_t sid, uint32_t *out_len) { + if (sid >= mod->string_count) return NULL; + uint32_t offset = mod->string_offsets[sid]; + uint32_t next_offset = (sid + 1 < mod->string_count) + ? mod->string_offsets[sid + 1] + : mod->string_data_size; + *out_len = next_offset - offset; + return (const char *)(mod->string_data + offset); +} + +/* Integrate a CellModule with an environment and execute. + This materializes the string table into the target context's stone arena, + creates runtime bytecode, patches external relocations, and returns the + main unit wrapped as a callable function. + + Parameters: + - ctx: target context + - mod: context-neutral module (ownership NOT transferred) + - env: stoned record for environment (or JS_NULL) + + Returns: callable function value, or JS_EXCEPTION on error. +*/ +JSValue cell_module_integrate (JSContext *ctx, CellModule *mod, JSValue env) { + JSValue *string_table = NULL; + JSFunctionBytecode **units = NULL; + JSValue result = JS_EXCEPTION; + uint32_t i, j; + + if (mod->unit_count == 0) { + JS_ThrowTypeError (ctx, "module has no units"); + return JS_EXCEPTION; + } + + /* Step 1: Materialize string table into context's stone arena */ + if (mod->string_count > 0) { + string_table = pjs_mallocz (mod->string_count * sizeof (JSValue)); + if (!string_table) goto fail; + + for (i = 0; i < mod->string_count; i++) { + uint32_t len; + const char *str = cell_module_get_string (mod, i, &len); + if (!str) { + string_table[i] = JS_NULL; + } else { + /* Intern as a stoned key */ + string_table[i] = js_key_new_len (ctx, str, len); + } + } + } + + /* Step 2: Create JSFunctionBytecode for each unit */ + units = pjs_mallocz (mod->unit_count * sizeof (JSFunctionBytecode *)); + if (!units) goto fail; + + for (i = 0; i < mod->unit_count; i++) { + CellUnit *cu = &mod->units[i]; + + /* Calculate bytecode structure size */ + int function_size = sizeof (JSFunctionBytecode); + int cpool_offset = function_size; + function_size += cu->const_count * sizeof (JSValue); + int byte_code_offset = function_size; + function_size += cu->bytecode_len; + + JSFunctionBytecode *b = pjs_mallocz (function_size); + if (!b) goto fail; + units[i] = b; + + /* Initialize header */ + b->header = objhdr_make (0, OBJ_CODE, false, false, false, false); + b->arg_count = cu->arg_count; + b->var_count = cu->var_count; + b->defined_arg_count = cu->arg_count; /* Same as arg_count for simple functions */ + b->has_simple_parameter_list = 1; /* Assume simple parameter list */ + b->stack_size = cu->stack_size; + b->cpool_count = cu->const_count; + b->byte_code_len = cu->bytecode_len; + + /* Set up pointers */ + b->cpool = (JSValue *)((uint8_t *)b + cpool_offset); + b->byte_code_buf = (uint8_t *)b + byte_code_offset; + + /* Materialize constants */ + for (j = 0; j < cu->const_count; j++) { + CellConst *cc = &cu->constants[j]; + switch (cc->type) { + case CELL_CONST_NULL: + b->cpool[j] = JS_NULL; + break; + case CELL_CONST_INT: + b->cpool[j] = JS_NewInt32 (ctx, cc->i32); + break; + case CELL_CONST_FLOAT: + b->cpool[j] = JS_NewFloat64 (ctx, cc->f64); + break; + case CELL_CONST_STRING: + if (cc->string_sid < mod->string_count) { + b->cpool[j] = string_table[cc->string_sid]; + } else { + b->cpool[j] = JS_NULL; + } + break; + case CELL_CONST_UNIT: + /* Will be patched after all units are created */ + b->cpool[j] = JS_NULL; + break; + } + } + + /* Copy bytecode */ + memcpy (b->byte_code_buf, cu->bytecode, cu->bytecode_len); + + /* Set function name from string table */ + if (cu->name_sid < mod->string_count) { + b->func_name = string_table[cu->name_sid]; + } else { + b->func_name = JS_KEY_empty; + } + } + + /* Step 3: Patch unit references in cpool */ + for (i = 0; i < mod->unit_count; i++) { + CellUnit *cu = &mod->units[i]; + JSFunctionBytecode *b = units[i]; + + for (j = 0; j < cu->const_count; j++) { + if (cu->constants[j].type == CELL_CONST_UNIT) { + uint32_t uid = cu->constants[j].unit_id; + if (uid < mod->unit_count) { + b->cpool[j] = JS_MKPTR (units[uid]); + } + } + } + } + + /* Step 4: Patch external relocations for main unit (unit 0) */ + { + CellUnit *cu = &mod->units[0]; + JSFunctionBytecode *b = units[0]; + uint8_t *bc = b->byte_code_buf; + + /* Get env record if provided */ + JSRecord *env_rec = NULL; + if (!JS_IsNull (env) && JS_IsRecord (env)) { + env_rec = (JSRecord *)JS_VALUE_GET_OBJ (env); + } + + for (j = 0; j < cu->external_count; j++) { + CellExternalReloc *rel = &cu->externals[j]; + uint32_t pc = rel->pc_offset; + uint32_t name_sid = rel->name_sid; + + if (name_sid >= mod->string_count) continue; + JSValue name = string_table[name_sid]; + + if (rel->kind == EXT_GET) { + /* Try env first */ + if (env_rec) { + int slot = rec_find_slot (env_rec, name); + if (slot > 0) { + bc[pc] = OP_get_env_slot; + put_u16 (bc + pc + 1, (uint16_t)slot); + bc[pc + 3] = OP_nop; + bc[pc + 4] = OP_nop; + continue; + } + } + + /* Try global */ + JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); + int slot = rec_find_slot (global, name); + if (slot > 0) { + bc[pc] = OP_get_global_slot; + put_u16 (bc + pc + 1, (uint16_t)slot); + bc[pc + 3] = OP_nop; + bc[pc + 4] = OP_nop; + continue; + } + + /* Link error */ + char buf[64]; + JS_ThrowReferenceError (ctx, "'%s' is not defined", + JS_KeyGetStr (ctx, buf, sizeof (buf), name)); + goto fail; + + } else if (rel->kind == EXT_SET) { + /* Try env first (writable) */ + if (env_rec) { + int slot = rec_find_slot (env_rec, name); + if (slot > 0) { + bc[pc] = OP_set_env_slot; + put_u16 (bc + pc + 1, (uint16_t)slot); + bc[pc + 3] = OP_nop; + bc[pc + 4] = OP_nop; + continue; + } + } + + /* Try global */ + JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); + int slot = rec_find_slot (global, name); + if (slot > 0) { + bc[pc] = OP_set_global_slot; + put_u16 (bc + pc + 1, (uint16_t)slot); + bc[pc + 3] = OP_nop; + bc[pc + 4] = OP_nop; + continue; + } + + /* Link error */ + char buf[64]; + JS_ThrowReferenceError (ctx, "cannot assign to '%s' - not found", + JS_KeyGetStr (ctx, buf, sizeof (buf), name)); + goto fail; + } + } + } + + /* Step 5: Create closure from main unit and set env_record */ + { + JSValue linked = JS_MKPTR (units[0]); + linked = js_closure (ctx, linked, NULL); + if (JS_IsException (linked)) goto fail; + + /* Set env_record on the function */ + JSFunction *f = JS_VALUE_GET_FUNCTION (linked); + f->u.func.env_record = env; + + result = linked; + } + + /* Success - don't free units (now owned by result closure) */ + pjs_free (string_table); + pjs_free (units); + return result; + +fail: + /* Free allocated units on failure */ + if (units) { + for (i = 0; i < mod->unit_count; i++) { + if (units[i]) pjs_free (units[i]); + } + pjs_free (units); + } + if (string_table) pjs_free (string_table); + return JS_EXCEPTION; +} + +/*******************************************************************/ +/* JSFunctionBytecode to CellModule conversion */ + +/* Helper structure for building string table */ +typedef struct { + JSValue *strings; /* array of JSValue strings */ + uint32_t count; + uint32_t capacity; +} StringTableBuilder; + +static void stb_init (StringTableBuilder *stb) { + stb->strings = NULL; + stb->count = 0; + stb->capacity = 0; +} + +static void stb_free (StringTableBuilder *stb) { + if (stb->strings) pjs_free (stb->strings); + stb->strings = NULL; + stb->count = 0; + stb->capacity = 0; +} + +/* Add a string to the builder, return its string_id. + If string already exists, return existing id. */ +static uint32_t stb_add (StringTableBuilder *stb, JSValue str) { + /* Check if already present */ + for (uint32_t i = 0; i < stb->count; i++) { + if (stb->strings[i] == str) return i; + } + + /* Add new entry */ + if (stb->count >= stb->capacity) { + uint32_t new_cap = stb->capacity ? stb->capacity * 2 : 16; + JSValue *new_arr = pjs_realloc (stb->strings, new_cap * sizeof (JSValue)); + if (!new_arr) return UINT32_MAX; + stb->strings = new_arr; + stb->capacity = new_cap; + } + + stb->strings[stb->count] = str; + return stb->count++; +} + +/* Helper structure for collecting bytecodes */ +typedef struct { + JSFunctionBytecode **funcs; + uint32_t count; + uint32_t capacity; +} FuncCollector; + +static void fc_init (FuncCollector *fc) { + fc->funcs = NULL; + fc->count = 0; + fc->capacity = 0; +} + +static void fc_free (FuncCollector *fc) { + if (fc->funcs) pjs_free (fc->funcs); + fc->funcs = NULL; + fc->count = 0; + fc->capacity = 0; +} + +/* Add a function to collector, return its unit_id */ +static uint32_t fc_add (FuncCollector *fc, JSFunctionBytecode *b) { + /* Check if already present */ + for (uint32_t i = 0; i < fc->count; i++) { + if (fc->funcs[i] == b) return i; + } + + /* Add new entry */ + if (fc->count >= fc->capacity) { + uint32_t new_cap = fc->capacity ? fc->capacity * 2 : 8; + JSFunctionBytecode **new_arr = pjs_realloc (fc->funcs, new_cap * sizeof (JSFunctionBytecode *)); + if (!new_arr) return UINT32_MAX; + fc->funcs = new_arr; + fc->capacity = new_cap; + } + + fc->funcs[fc->count] = b; + return fc->count++; +} + +/* Recursively collect all functions in bytecode tree */ +static int collect_functions (FuncCollector *fc, JSFunctionBytecode *b) { + uint32_t uid = fc_add (fc, b); + if (uid == UINT32_MAX) return -1; + + /* Scan cpool for nested functions */ + for (int i = 0; i < b->cpool_count; i++) { + JSValue v = b->cpool[i]; + if (JS_VALUE_GET_TAG (v) == JS_TAG_PTR) { + void *ptr = JS_VALUE_GET_PTR (v); + objhdr_t hdr = *(objhdr_t *)ptr; + if (objhdr_type (hdr) == OBJ_CODE) { + /* This is a nested function */ + if (collect_functions (fc, (JSFunctionBytecode *)ptr) < 0) + return -1; + } + } + } + return 0; +} + +/* Collect all strings from a function's cpool and name */ +static int collect_strings (StringTableBuilder *stb, JSFunctionBytecode *b) { + /* Function name */ + if (JS_IsText (b->func_name)) { + if (stb_add (stb, b->func_name) == UINT32_MAX) return -1; + } + + /* Filename */ + if (b->has_debug && JS_IsText (b->debug.filename)) { + if (stb_add (stb, b->debug.filename) == UINT32_MAX) return -1; + } + + /* Cpool strings */ + for (int i = 0; i < b->cpool_count; i++) { + JSValue v = b->cpool[i]; + if (JS_IsText (v)) { + if (stb_add (stb, v) == UINT32_MAX) return -1; + } + } + + /* Variable names (for debugging) */ + if (b->vardefs) { + for (int i = 0; i < b->arg_count + b->var_count; i++) { + if (JS_IsText (b->vardefs[i].var_name)) { + if (stb_add (stb, b->vardefs[i].var_name) == UINT32_MAX) return -1; + } + } + } + + /* Closure variable names */ + if (b->closure_var) { + for (int i = 0; i < b->closure_var_count; i++) { + if (JS_IsText (b->closure_var[i].var_name)) { + if (stb_add (stb, b->closure_var[i].var_name) == UINT32_MAX) return -1; + } + } + } + + return 0; +} + +/* Find string_id for a JSValue string */ +static uint32_t find_string_id (StringTableBuilder *stb, JSValue str) { + for (uint32_t i = 0; i < stb->count; i++) { + if (stb->strings[i] == str) return i; + } + return UINT32_MAX; +} + +/* Find unit_id for a JSFunctionBytecode */ +static uint32_t find_unit_id (FuncCollector *fc, JSFunctionBytecode *b) { + for (uint32_t i = 0; i < fc->count; i++) { + if (fc->funcs[i] == b) return i; + } + return UINT32_MAX; +} + +/* Convert JSFunctionBytecode tree to CellModule. + This extracts all functions, builds a shared string table, + and creates external relocations for unresolved variables. + + Parameters: + - ctx: context (for string conversion) + - main_func: compiled main function bytecode + + Returns: allocated CellModule (caller must free with cell_module_free), or NULL on error. +*/ +CellModule *cell_module_from_bytecode (JSContext *ctx, JSFunctionBytecode *main_func) { + CellModule *mod = NULL; + StringTableBuilder stb; + FuncCollector fc; + DynBuf string_data; + uint32_t i, j; + + stb_init (&stb); + fc_init (&fc); + dbuf_init (&string_data); + + /* Step 1: Collect all functions */ + if (collect_functions (&fc, main_func) < 0) goto fail; + + /* Step 2: Collect all strings from all functions */ + for (i = 0; i < fc.count; i++) { + if (collect_strings (&stb, fc.funcs[i]) < 0) goto fail; + } + + /* Step 3: Allocate module */ + mod = pjs_mallocz (sizeof (CellModule)); + if (!mod) goto fail; + + mod->magic = CELL_MODULE_MAGIC; + mod->version = CELL_MODULE_VERSION; + mod->flags = 0; + + /* Step 4: Build string table data */ + mod->string_count = stb.count; + if (stb.count > 0) { + mod->string_offsets = pjs_malloc (stb.count * sizeof (uint32_t)); + if (!mod->string_offsets) goto fail; + + for (i = 0; i < stb.count; i++) { + mod->string_offsets[i] = string_data.size; + + /* Convert JSValue string to UTF-8 */ + const char *cstr = JS_ToCString (ctx, stb.strings[i]); + if (cstr) { + size_t len = strlen (cstr); + dbuf_put (&string_data, (uint8_t *)cstr, len); + JS_FreeCString (ctx, cstr); + } + } + + if (string_data.error) goto fail; + + mod->string_data_size = string_data.size; + mod->string_data = string_data.buf; + string_data.buf = NULL; /* Transfer ownership */ + } + + /* Step 5: Create units */ + mod->unit_count = fc.count; + mod->units = pjs_mallocz (fc.count * sizeof (CellUnit)); + if (!mod->units) goto fail; + + for (i = 0; i < fc.count; i++) { + JSFunctionBytecode *b = fc.funcs[i]; + CellUnit *cu = &mod->units[i]; + + /* Function name */ + if (JS_IsText (b->func_name)) { + cu->name_sid = find_string_id (&stb, b->func_name); + } else { + cu->name_sid = UINT32_MAX; + } + + /* Stack requirements */ + cu->arg_count = b->arg_count; + cu->var_count = b->var_count; + cu->stack_size = b->stack_size; + + /* Copy bytecode */ + cu->bytecode_len = b->byte_code_len; + if (b->byte_code_len > 0) { + cu->bytecode = pjs_malloc (b->byte_code_len); + if (!cu->bytecode) goto fail; + memcpy (cu->bytecode, b->byte_code_buf, b->byte_code_len); + } + + /* Build constants */ + cu->const_count = b->cpool_count; + if (b->cpool_count > 0) { + cu->constants = pjs_mallocz (b->cpool_count * sizeof (CellConst)); + if (!cu->constants) goto fail; + + for (j = 0; j < (uint32_t)b->cpool_count; j++) { + JSValue v = b->cpool[j]; + CellConst *cc = &cu->constants[j]; + + if (JS_IsNull (v)) { + cc->type = CELL_CONST_NULL; + } else if (JS_VALUE_GET_TAG (v) == JS_TAG_INT) { + cc->type = CELL_CONST_INT; + cc->i32 = JS_VALUE_GET_INT (v); + } else if (JS_VALUE_GET_TAG (v) == JS_TAG_FLOAT64) { + cc->type = CELL_CONST_FLOAT; + cc->f64 = JS_VALUE_GET_FLOAT64 (v); + } else if (JS_IsText (v)) { + cc->type = CELL_CONST_STRING; + cc->string_sid = find_string_id (&stb, v); + } else if (JS_VALUE_GET_TAG (v) == JS_TAG_PTR) { + void *ptr = JS_VALUE_GET_PTR (v); + objhdr_t hdr = *(objhdr_t *)ptr; + if (objhdr_type (hdr) == OBJ_CODE) { + /* Nested function reference */ + cc->type = CELL_CONST_UNIT; + cc->unit_id = find_unit_id (&fc, (JSFunctionBytecode *)ptr); + } else { + cc->type = CELL_CONST_NULL; + } + } else { + cc->type = CELL_CONST_NULL; + } + } + } + + /* Build upvalue descriptors from closure_var */ + cu->upvalue_count = b->closure_var_count; + if (b->closure_var_count > 0 && b->closure_var) { + cu->upvalues = pjs_malloc (b->closure_var_count * sizeof (CellCapDesc)); + if (!cu->upvalues) goto fail; + + for (j = 0; j < (uint32_t)b->closure_var_count; j++) { + JSClosureVar *cv = &b->closure_var[j]; + cu->upvalues[j].kind = cv->is_local ? CAP_FROM_PARENT_LOCAL : CAP_FROM_PARENT_UPVALUE; + cu->upvalues[j].index = cv->var_idx; + } + } + + /* Scan bytecode for external relocations (OP_get_var, OP_put_var, etc.) */ + { + DynBuf relocs; + dbuf_init (&relocs); + + uint8_t *bc = cu->bytecode; + int pos = 0; + while (pos < (int)cu->bytecode_len) { + uint8_t op = bc[pos]; + int len = short_opcode_info (op).size; + + if (op == OP_get_var || op == OP_get_var_undef) { + CellExternalReloc rel; + rel.pc_offset = pos; + uint32_t cpool_idx = get_u32 (bc + pos + 1); + if (cpool_idx < (uint32_t)b->cpool_count && JS_IsText (b->cpool[cpool_idx])) { + rel.name_sid = find_string_id (&stb, b->cpool[cpool_idx]); + } else { + rel.name_sid = UINT32_MAX; + } + rel.kind = EXT_GET; + dbuf_put (&relocs, (uint8_t *)&rel, sizeof (rel)); + } else if (op == OP_put_var || op == OP_put_var_init || op == OP_put_var_strict) { + CellExternalReloc rel; + rel.pc_offset = pos; + uint32_t cpool_idx = get_u32 (bc + pos + 1); + if (cpool_idx < (uint32_t)b->cpool_count && JS_IsText (b->cpool[cpool_idx])) { + rel.name_sid = find_string_id (&stb, b->cpool[cpool_idx]); + } else { + rel.name_sid = UINT32_MAX; + } + rel.kind = EXT_SET; + dbuf_put (&relocs, (uint8_t *)&rel, sizeof (rel)); + } + + pos += len; + } + + if (relocs.size > 0 && !relocs.error) { + cu->external_count = relocs.size / sizeof (CellExternalReloc); + cu->externals = (CellExternalReloc *)relocs.buf; + relocs.buf = NULL; /* Transfer ownership */ + } + dbuf_free (&relocs); + } + + /* Copy debug info */ + if (b->has_debug) { + cu->pc2line_len = b->debug.pc2line_len; + if (b->debug.pc2line_len > 0 && b->debug.pc2line_buf) { + cu->pc2line = pjs_malloc (b->debug.pc2line_len); + if (!cu->pc2line) goto fail; + memcpy (cu->pc2line, b->debug.pc2line_buf, b->debug.pc2line_len); + } + } + } + + /* Step 6: Copy source from main function */ + if (main_func->has_debug && main_func->debug.source_len > 0 && main_func->debug.source) { + mod->source_len = main_func->debug.source_len; + mod->source = pjs_malloc (mod->source_len + 1); + if (!mod->source) goto fail; + memcpy (mod->source, main_func->debug.source, mod->source_len); + mod->source[mod->source_len] = '\0'; + } + + /* Success */ + stb_free (&stb); + fc_free (&fc); + dbuf_free (&string_data); + return mod; + +fail: + stb_free (&stb); + fc_free (&fc); + dbuf_free (&string_data); + if (mod) cell_module_free (mod); + return NULL; +} + +/* Compile source code directly to CellModule (context-neutral format). + This is a convenience function that combines JS_Compile + cell_module_from_bytecode. + + Parameters: + - ctx: context for compilation + - input: source code (must be null-terminated) + - input_len: length of source code + - filename: source filename for debug info + + Returns: allocated CellModule (caller must free with cell_module_free), or NULL on error. +*/ +CellModule *JS_CompileModule (JSContext *ctx, const char *input, size_t input_len, const char *filename) { + JSValue bytecode = JS_Compile (ctx, input, input_len, filename); + if (JS_IsException (bytecode)) { + return NULL; + } + + JSFunctionBytecode *b = JS_VALUE_GET_PTR (bytecode); + CellModule *mod = cell_module_from_bytecode (ctx, b); + + /* Note: bytecode is not freed here - it's still valid and could be used + with JS_Integrate if desired. Caller can free it if not needed. */ + + return mod; +} +/* JSON */ + +static int json_parse_expect (JSParseState *s, int tok) { + if (s->token.val != tok) { + /* XXX: dump token correctly in all cases */ + return js_parse_error (s, "expecting '%c'", tok); + } + return json_next_token (s); +} + +static JSValue json_parse_value (JSParseState *s) { + JSContext *ctx = s->ctx; + JSValue val = JS_NULL; + int ret; + + switch (s->token.val) { + case '{': { + JSValue prop_val; + JSValue prop_name; + JSGCRef val_ref, tok_ref; + + JS_PushGCRef (ctx, &val_ref); + JS_PushGCRef (ctx, &tok_ref); + + if (json_next_token (s)) { + JS_PopGCRef (ctx, &tok_ref); + JS_PopGCRef (ctx, &val_ref); + goto fail; + } + tok_ref.val = s->token.u.str.str; /* Root the token string before GC */ + + val_ref.val = JS_NewObject (ctx); + if (JS_IsException (val_ref.val)) { + JS_PopGCRef (ctx, &tok_ref); + JS_PopGCRef (ctx, &val_ref); + goto fail; + } + if (s->token.val != '}') { + for (;;) { + if (s->token.val == TOK_STRING) { + prop_name = js_key_from_string (ctx, tok_ref.val); /* Use rooted token */ + if (JS_IsNull (prop_name)) { + JS_PopGCRef (ctx, &tok_ref); + JS_PopGCRef (ctx, &val_ref); + goto fail; + } + } else if (s->ext_json && s->token.val == TOK_IDENT) { + prop_name = tok_ref.val; /* Use rooted ident */ + } else { + js_parse_error (s, "expecting property name"); + JS_PopGCRef (ctx, &tok_ref); + JS_PopGCRef (ctx, &val_ref); + goto fail; + } + if (json_next_token (s)) { + JS_PopGCRef (ctx, &tok_ref); + JS_PopGCRef (ctx, &val_ref); + goto fail; + } + if (json_parse_expect (s, ':')) { + JS_PopGCRef (ctx, &tok_ref); + JS_PopGCRef (ctx, &val_ref); + goto fail; + } + prop_val = json_parse_value (s); + if (JS_IsException (prop_val)) { + JS_PopGCRef (ctx, &tok_ref); + JS_PopGCRef (ctx, &val_ref); + goto fail; + } + ret = JS_SetPropertyInternal (ctx, val_ref.val, prop_name, prop_val); + if (ret < 0) { + JS_PopGCRef (ctx, &tok_ref); + JS_PopGCRef (ctx, &val_ref); + goto fail; + } + + if (s->token.val != ',') break; + if (json_next_token (s)) { + JS_PopGCRef (ctx, &tok_ref); + JS_PopGCRef (ctx, &val_ref); + goto fail; + } + tok_ref.val = s->token.u.str.str; /* Root the new key token */ + if (s->ext_json && s->token.val == '}') break; + } + } + if (json_parse_expect (s, '}')) { + JS_PopGCRef (ctx, &tok_ref); + JS_PopGCRef (ctx, &val_ref); + goto fail; + } + val = val_ref.val; + JS_PopGCRef (ctx, &tok_ref); + JS_PopGCRef (ctx, &val_ref); + } break; + case '[': { + JSValue el; + uint32_t idx; + JSGCRef val_ref; + + JS_PushGCRef (ctx, &val_ref); + + if (json_next_token (s)) { + JS_PopGCRef (ctx, &val_ref); + goto fail; + } + val_ref.val = JS_NewArray (ctx); + if (JS_IsException (val_ref.val)) { + JS_PopGCRef (ctx, &val_ref); + goto fail; + } + if (s->token.val != ']') { + idx = 0; + for (;;) { + el = json_parse_value (s); + if (JS_IsException (el)) { + JS_PopGCRef (ctx, &val_ref); + goto fail; + } + ret = JS_SetPropertyUint32 (ctx, val_ref.val, idx, el); + if (ret < 0) { + JS_PopGCRef (ctx, &val_ref); + goto fail; + } + if (s->token.val != ',') break; + if (json_next_token (s)) { + JS_PopGCRef (ctx, &val_ref); + goto fail; + } + idx++; + if (s->ext_json && s->token.val == ']') break; + } + } + if (json_parse_expect (s, ']')) { + JS_PopGCRef (ctx, &val_ref); + goto fail; + } + val = val_ref.val; + JS_PopGCRef (ctx, &val_ref); + } break; + case TOK_STRING: { + JSGCRef val_ref; + JS_PushGCRef (ctx, &val_ref); + val_ref.val = s->token.u.str.str; + if (json_next_token (s)) { + JS_PopGCRef (ctx, &val_ref); + goto fail; + } + val = val_ref.val; + JS_PopGCRef (ctx, &val_ref); + } break; + case TOK_NUMBER: + val = s->token.u.num.val; + if (json_next_token (s)) goto fail; + break; + case TOK_IDENT: + if (js_key_equal (s->token.u.ident.str, JS_KEY_false) + || js_key_equal (s->token.u.ident.str, JS_KEY_true)) { + val = JS_NewBool (ctx, js_key_equal (s->token.u.ident.str, JS_KEY_true)); + } else if (js_key_equal (s->token.u.ident.str, JS_KEY_null)) { + val = JS_NULL; + } else if (js_key_equal_str (s->token.u.ident.str, "NaN") && s->ext_json) { + /* Note: json5 identifier handling is ambiguous e.g. is + '{ NaN: 1 }' a valid JSON5 production ? */ + val = JS_NewFloat64 (s->ctx, NAN); + } else if (js_key_equal_str (s->token.u.ident.str, "Infinity") + && s->ext_json) { + val = JS_NewFloat64 (s->ctx, INFINITY); + } else { + goto def_token; + } + if (json_next_token (s)) goto fail; + break; + default: + def_token: + if (s->token.val == TOK_EOF) { + js_parse_error (s, "Unexpected end of JSON input"); + } else { + js_parse_error (s, "unexpected token: '%.*s'", (int)(s->buf_ptr - s->token.ptr), s->token.ptr); + } + goto fail; + } + return val; +fail: + return JS_EXCEPTION; +} + +JSValue JS_ParseJSON2 (JSContext *ctx, const char *buf, size_t buf_len, const char *filename, int flags) { + JSParseState s1, *s = &s1; + JSValue val = JS_NULL; + + js_parse_init (ctx, s, buf, buf_len, filename); + s->ext_json = ((flags & JS_PARSE_JSON_EXT) != 0); + if (json_next_token (s)) goto fail; + val = json_parse_value (s); + if (JS_IsException (val)) goto fail; + if (s->token.val != TOK_EOF) { + if (js_parse_error (s, "unexpected data at the end")) goto fail; + } + return val; +fail: + free_token (s, &s->token); + return JS_EXCEPTION; +} + +JSValue JS_ParseJSON (JSContext *ctx, const char *buf, size_t buf_len, const char *filename) { + return JS_ParseJSON2 (ctx, buf, buf_len, filename, 0); +} diff --git a/source/mach.c b/source/mach.c new file mode 100644 index 00000000..8845d4b2 --- /dev/null +++ b/source/mach.c @@ -0,0 +1,3316 @@ +/* + * 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" + + + +/* ---- Compile-time constant pool entry ---- */ +/* Stores raw data during compilation; converted to JSValues when loading into context */ +typedef enum { MACH_CP_INT, MACH_CP_FLOAT, MACH_CP_STR } MachCPType; +typedef struct { + MachCPType type; + union { + int32_t ival; /* integer constant */ + double fval; /* float constant */ + char *str; /* owned C string */ + }; +} MachCPoolEntry; + +/* ---- Compiled output (context-free) ---- */ +typedef struct MachCode { + uint16_t arity; + uint16_t nr_close_slots; + uint16_t nr_slots; + uint16_t entry_point; + + uint32_t cpool_count; + MachCPoolEntry *cpool; + + uint32_t instr_count; + MachInstr32 *instructions; + + uint32_t func_count; + struct MachCode **functions; + + char *name; /* owned C string, or NULL */ + MachLineEntry *line_table; /* [instr_count], parallel to instructions[] */ + char *filename; /* source filename (sys_malloc'd) */ + uint16_t disruption_pc; /* start of disruption handler (0 = none) */ +} MachCode; + +/* ---- Compiler state ---- */ + +typedef struct MachCompState { + /* Instruction buffer (growable) */ + MachInstr32 *code; + int code_count; + int code_capacity; + + /* Constant pool (raw entries, no GC objects) */ + MachCPoolEntry *cpool; + int cpool_count; + int cpool_capacity; + + /* Nested functions */ + MachCode **functions; + int func_count; + int func_capacity; + + /* Variables */ + MachVarInfo *vars; + int var_count; + int var_capacity; + + /* Register allocation (Lua-style) */ + int freereg; /* next free register */ + int maxreg; /* high-water mark */ + int nr_args; /* parameter count */ + + /* Loop labels for break/continue */ + int loop_break; /* instruction index to patch, or -1 */ + int loop_continue; /* instruction index to patch, or -1 */ + + /* Scope depth for block scoping */ + int scope_depth; + + /* Parent for nested function compilation */ + struct MachCompState *parent; + + /* AST semantic annotations */ + int function_nr; /* current function number (0=program body) */ + cJSON *scopes; /* pointer to AST "scopes" array (not owned) */ + + /* Error tracking */ + int has_error; + + /* Line tracking for debug info */ + int cur_line, cur_col; + MachLineEntry *line_info; /* growable, parallel to code[] */ + int line_capacity; + const char *filename; /* pointer into AST cJSON (not owned) */ +} MachCompState; + +/* Forward declarations */ +static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest); +static void mach_compile_stmt(MachCompState *cs, cJSON *stmt); + +/* ---- Compiler helpers ---- */ + +static void mach_set_pos(MachCompState *cs, cJSON *node) { + cJSON *r = cJSON_GetObjectItemCaseSensitive(node, "from_row"); + cJSON *c = cJSON_GetObjectItemCaseSensitive(node, "from_column"); + if (r) cs->cur_line = (int)r->valuedouble + 1; + if (c) cs->cur_col = (int)c->valuedouble + 1; +} + +static void mach_emit(MachCompState *cs, MachInstr32 instr) { + if (cs->code_count >= cs->code_capacity) { + int new_cap = cs->code_capacity ? cs->code_capacity * 2 : 64; + cs->code = sys_realloc(cs->code, new_cap * sizeof(MachInstr32)); + cs->code_capacity = new_cap; + } + if (cs->code_count >= cs->line_capacity) { + int new_cap = cs->line_capacity ? cs->line_capacity * 2 : 64; + cs->line_info = sys_realloc(cs->line_info, new_cap * sizeof(MachLineEntry)); + cs->line_capacity = new_cap; + } + cs->line_info[cs->code_count] = (MachLineEntry){cs->cur_line, cs->cur_col}; + cs->code[cs->code_count++] = instr; +} + +static int mach_current_pc(MachCompState *cs) { + return cs->code_count; +} + +/* Reserve a register at freereg */ +static int mach_reserve_reg(MachCompState *cs) { + int r = cs->freereg++; + if (cs->freereg > cs->maxreg) cs->maxreg = cs->freereg; + return r; +} + +/* Free temporary registers back to a saved freereg level */ +static void mach_free_reg_to(MachCompState *cs, int saved) { + cs->freereg = saved; +} + +/* Add an integer constant to the pool, return its index */ +static int mach_cpool_add_int(MachCompState *cs, int32_t val) { + for (int i = 0; i < cs->cpool_count; i++) { + MachCPoolEntry *e = &cs->cpool[i]; + if (e->type == MACH_CP_INT && e->ival == val) return i; + } + if (cs->cpool_count >= cs->cpool_capacity) { + int new_cap = cs->cpool_capacity ? cs->cpool_capacity * 2 : 16; + cs->cpool = sys_realloc(cs->cpool, new_cap * sizeof(MachCPoolEntry)); + cs->cpool_capacity = new_cap; + } + cs->cpool[cs->cpool_count] = (MachCPoolEntry){ .type = MACH_CP_INT, .ival = val }; + return cs->cpool_count++; +} + +/* Add a float constant to the pool, return its index */ +static int mach_cpool_add_float(MachCompState *cs, double val) { + for (int i = 0; i < cs->cpool_count; i++) { + MachCPoolEntry *e = &cs->cpool[i]; + if (e->type == MACH_CP_FLOAT && e->fval == val) return i; + } + if (cs->cpool_count >= cs->cpool_capacity) { + int new_cap = cs->cpool_capacity ? cs->cpool_capacity * 2 : 16; + cs->cpool = sys_realloc(cs->cpool, new_cap * sizeof(MachCPoolEntry)); + cs->cpool_capacity = new_cap; + } + cs->cpool[cs->cpool_count] = (MachCPoolEntry){ .type = MACH_CP_FLOAT, .fval = val }; + return cs->cpool_count++; +} + +/* Add a string constant, return its cpool index */ +static int mach_cpool_add_str(MachCompState *cs, const char *str) { + /* Check for existing identical string */ + for (int i = 0; i < cs->cpool_count; i++) { + MachCPoolEntry *e = &cs->cpool[i]; + if (e->type == MACH_CP_STR && strcmp(e->str, str) == 0) + return i; + } + if (cs->cpool_count >= cs->cpool_capacity) { + int new_cap = cs->cpool_capacity ? cs->cpool_capacity * 2 : 16; + cs->cpool = sys_realloc(cs->cpool, new_cap * sizeof(MachCPoolEntry)); + cs->cpool_capacity = new_cap; + } + char *dup = sys_malloc(strlen(str) + 1); + memcpy(dup, str, strlen(str) + 1); + cs->cpool[cs->cpool_count] = (MachCPoolEntry){ .type = MACH_CP_STR, .str = dup }; + return cs->cpool_count++; +} + +/* Convert compile-time cpool entries to JSValue array for JSCodeRegister. + Caller takes ownership of the returned array. Frees the raw entries. + Strings are interned into stone memory (no GC allocation). */ +static JSValue *mach_materialize_cpool(JSContext *ctx, MachCPoolEntry *entries, int count) { + if (count == 0) { sys_free(entries); return NULL; } + JSValue *cpool = js_malloc_rt(count * sizeof(JSValue)); + for (int i = 0; i < count; i++) { + switch (entries[i].type) { + case MACH_CP_INT: + cpool[i] = JS_NewInt32(ctx, entries[i].ival); + break; + case MACH_CP_FLOAT: + cpool[i] = JS_NewFloat64(ctx, entries[i].fval); + break; + case MACH_CP_STR: + cpool[i] = js_key_new(ctx, entries[i].str); + break; + } + } + return cpool; +} + +/* Add a variable */ +static void mach_add_var(MachCompState *cs, const char *name, int slot, int is_const) { + if (cs->var_count >= cs->var_capacity) { + int new_cap = cs->var_capacity ? cs->var_capacity * 2 : 16; + cs->vars = sys_realloc(cs->vars, new_cap * sizeof(MachVarInfo)); + cs->var_capacity = new_cap; + } + MachVarInfo *v = &cs->vars[cs->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; + v->scope_depth = cs->scope_depth; +} + +/* Find a variable in the current scope */ +static int mach_find_var(MachCompState *cs, const char *name) { + for (int i = cs->var_count - 1; i >= 0; i--) { + if (strcmp(cs->vars[i].name, name) == 0) + return cs->vars[i].slot; + } + return -1; +} + +/* Add a nested function, return its index */ +static int mach_add_function(MachCompState *cs, MachCode *fn) { + if (cs->func_count >= cs->func_capacity) { + int new_cap = cs->func_capacity ? cs->func_capacity * 2 : 4; + cs->functions = sys_realloc(cs->functions, new_cap * sizeof(MachCode*)); + cs->func_capacity = new_cap; + } + cs->functions[cs->func_count] = fn; + return cs->func_count++; +} + +/* Find the scope record for a given function_nr in the scopes array */ +cJSON *mach_find_scope_record(cJSON *scopes, int function_nr) { + if (!scopes) return NULL; + int count = cJSON_GetArraySize(scopes); + for (int i = 0; i < count; i++) { + cJSON *scope = cJSON_GetArrayItem(scopes, i); + cJSON *fn_nr = cJSON_GetObjectItemCaseSensitive(scope, "function_nr"); + if (fn_nr && (int)cJSON_GetNumberValue(fn_nr) == function_nr) + return scope; + } + return NULL; +} + +/* Scan AST scope record for variable declarations. + Variables are direct keys on the scope object with a "make" field. */ +static void mach_scan_scope(MachCompState *cs) { + cJSON *scope = mach_find_scope_record(cs->scopes, cs->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_find_var(cs, name) < 0) { + int is_const = (strcmp(make, "def") == 0 || strcmp(make, "function") == 0); + int slot = mach_reserve_reg(cs); + mach_add_var(cs, name, slot, is_const); + } + } +} + +/* ---- Expression compiler ---- */ + +/* Compile an expression into register dest. If dest < 0, allocate a temp. + Returns the register containing the result. */ +static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest) { + if (!node) { + if (dest < 0) dest = mach_reserve_reg(cs); + mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0)); + return dest; + } + + mach_set_pos(cs, node); + const char *kind = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "kind")); + if (!kind) { + if (dest < 0) dest = mach_reserve_reg(cs); + mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0)); + return dest; + } + + /* Number literal */ + if (strcmp(kind, "number") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + cJSON *num = cJSON_GetObjectItemCaseSensitive(node, "number"); + if (num && cJSON_IsNumber(num)) { + double dval = num->valuedouble; + int ival = (int)dval; + if (dval == (double)ival && ival >= -32768 && ival <= 32767) { + /* Small integer: use LOADI */ + mach_emit(cs, MACH_AsBx(MACH_LOADI, dest, (int16_t)ival)); + } else { + /* Large number: use constant pool */ + int ki; + if (dval == (double)(int32_t)dval) + ki = mach_cpool_add_int(cs, (int32_t)dval); + else + ki = mach_cpool_add_float(cs, dval); + mach_emit(cs, MACH_ABx(MACH_LOADK, dest, ki)); + } + } else { + mach_emit(cs, MACH_AsBx(MACH_LOADI, dest, 0)); + } + return dest; + } + + /* String literal */ + if (strcmp(kind, "string") == 0 || strcmp(kind, "text") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + const char *val = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "value")); + if (val) { + int ki = mach_cpool_add_str(cs, val); + mach_emit(cs, MACH_ABx(MACH_LOADK, dest, ki)); + } else { + int ki = mach_cpool_add_str(cs, ""); + mach_emit(cs, MACH_ABx(MACH_LOADK, dest, ki)); + } + return dest; + } + + /* 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) { + if (dest < 0) dest = mach_reserve_reg(cs); + int save_freereg = cs->freereg; + + cJSON *list = cJSON_GetObjectItemCaseSensitive(node, "list"); + int nexpr = list ? cJSON_GetArraySize(list) : 0; + + /* Reserve consecutive regs for call: [format_fn, fmt_str, arr] */ + int call_base = mach_reserve_reg(cs); + int arg1_reg = mach_reserve_reg(cs); + int arg2_reg = mach_reserve_reg(cs); + + /* Reserve consecutive regs for NEWARRAY: arr_reg, then elem slots */ + int arr_base = mach_reserve_reg(cs); + for (int i = 0; i < nexpr; i++) mach_reserve_reg(cs); + /* Now arr_base+1..arr_base+nexpr are the element slots */ + + /* Compile expressions into arr_base+1..arr_base+nexpr */ + for (int i = 0; i < nexpr; i++) { + cJSON *expr = cJSON_GetArrayItem(list, i); + int slot = arr_base + 1 + i; + int r = mach_compile_expr(cs, expr, slot); + if (r != slot) mach_emit(cs, MACH_ABC(MACH_MOVE, slot, r, 0)); + } + + /* Create array from consecutive element regs */ + mach_emit(cs, MACH_ABC(MACH_NEWARRAY, arr_base, nexpr, 0)); + + /* Load format function */ + int ki_format = mach_cpool_add_str(cs, "format"); + mach_emit(cs, MACH_ABx(MACH_GETINTRINSIC, call_base, ki_format)); + + /* Load format string */ + const char *fmt = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "value")); + int ki_fmt = mach_cpool_add_str(cs, fmt ? fmt : ""); + mach_emit(cs, MACH_ABx(MACH_LOADK, arg1_reg, ki_fmt)); + + /* Move array to arg2 position */ + mach_emit(cs, MACH_ABC(MACH_MOVE, arg2_reg, arr_base, 0)); + + /* Call format(fmt, arr) */ + mach_emit(cs, MACH_ABC(MACH_CALL, call_base, 2, 1)); + + mach_free_reg_to(cs, save_freereg); + if (dest != call_base) + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, call_base, 0)); + return dest; + } + + /* Boolean/null literals */ + if (strcmp(kind, "true") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + mach_emit(cs, MACH_ABx(MACH_LOADTRUE, dest, 0)); + return dest; + } + if (strcmp(kind, "false") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + mach_emit(cs, MACH_ABx(MACH_LOADFALSE, dest, 0)); + return dest; + } + if (strcmp(kind, "null") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0)); + return dest; + } + + /* Name (variable reference) */ + if (strcmp(kind, "name") == 0) { + const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "name")); + if (name) { + cJSON *level_node = cJSON_GetObjectItemCaseSensitive(node, "level"); + int level = level_node ? (int)cJSON_GetNumberValue(level_node) : -1; + + if (level == 0) { + /* Local variable */ + int slot = mach_find_var(cs, name); + if (slot >= 0) { + if (dest >= 0 && dest != slot) { + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, slot, 0)); + return dest; + } + return slot; + } + } else if (level > 0) { + /* Closure variable — walk parent compiler states for slot */ + MachCompState *target = cs; + for (int i = 0; i < level; i++) target = target->parent; + int slot = mach_find_var(target, name); + if (dest < 0) dest = mach_reserve_reg(cs); + mach_emit(cs, MACH_ABC(MACH_GETUP, dest, level, slot)); + return dest; + } + /* Unbound or fallback — emit placeholder, patched at link time */ + if (dest < 0) dest = mach_reserve_reg(cs); + int ki = mach_cpool_add_str(cs, name); + mach_emit(cs, MACH_ABx(MACH_GETNAME, dest, ki)); + return dest; + } + if (dest < 0) dest = mach_reserve_reg(cs); + mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0)); + return dest; + } + + /* Function call: kind="(" */ + if (strcmp(kind, "(") == 0) { + cJSON *fn_expr = cJSON_GetObjectItemCaseSensitive(node, "expression"); + cJSON *args = cJSON_GetObjectItemCaseSensitive(node, "list"); + int nargs = args ? cJSON_GetArraySize(args) : 0; + + /* Check if this is a method call: obj.method(args) or obj[key](args) */ + const char *fn_kind = NULL; + if (fn_expr) + fn_kind = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fn_expr, "kind")); + + /* Functino: inline operator call */ + if (fn_kind && strcmp(fn_kind, "name") == 0) { + const char *fn_make = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fn_expr, "make")); + if (fn_make && strcmp(fn_make, "functino") == 0) { + const char *fname = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fn_expr, "name")); + if (dest < 0) dest = mach_reserve_reg(cs); + + if (strcmp(fname, "~!") == 0) { + int save = cs->freereg; + int r = mach_compile_expr(cs, cJSON_GetArrayItem(args, 0), -1); + mach_emit(cs, MACH_ABC(MACH_BNOT, dest, r, 0)); + mach_free_reg_to(cs, save); + return dest; + } + if (strcmp(fname, "[]!") == 0) { + int save = cs->freereg; + int r0 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 0), -1); + if (cs->freereg <= r0) cs->freereg = r0 + 1; + int r1 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 1), -1); + mach_emit(cs, MACH_ABC(MACH_GETINDEX, dest, r0, r1)); + mach_free_reg_to(cs, save); + return dest; + } + if ((strcmp(fname, "=!") == 0 || strcmp(fname, "!=!") == 0) && nargs == 3) { + int save = cs->freereg; + int base = mach_reserve_reg(cs); + int r0 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 0), base); + if (r0 != base) mach_emit(cs, MACH_ABC(MACH_MOVE, base, r0, 0)); + int r1_reg = mach_reserve_reg(cs); + int r1 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 1), r1_reg); + if (r1 != r1_reg) mach_emit(cs, MACH_ABC(MACH_MOVE, r1_reg, r1, 0)); + int r2_reg = mach_reserve_reg(cs); + int r2 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 2), r2_reg); + if (r2 != r2_reg) mach_emit(cs, MACH_ABC(MACH_MOVE, r2_reg, r2, 0)); + MachOpcode top = (strcmp(fname, "=!") == 0) ? MACH_EQ_TOL : MACH_NEQ_TOL; + mach_emit(cs, MACH_ABC(top, dest, base, 3)); + mach_free_reg_to(cs, save); + return dest; + } + if (strcmp(fname, "&&!") == 0) { + int save = cs->freereg; + int r0 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 0), -1); + if (cs->freereg <= r0) cs->freereg = r0 + 1; + int r1 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 1), -1); + /* Non-short-circuiting: if left is falsy, result=left, else result=right */ + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, r0, 0)); + int jmp_pc = mach_current_pc(cs); + mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, dest, 0)); + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, r1, 0)); + { + int offset = mach_current_pc(cs) - (jmp_pc + 1); + cs->code[jmp_pc] = MACH_AsBx(MACH_GET_OP(cs->code[jmp_pc]), dest, (int16_t)offset); + } + mach_free_reg_to(cs, save); + return dest; + } + if (strcmp(fname, "||!") == 0) { + int save = cs->freereg; + int r0 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 0), -1); + if (cs->freereg <= r0) cs->freereg = r0 + 1; + int r1 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 1), -1); + /* Non-short-circuiting: if left is truthy, result=left, else result=right */ + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, r0, 0)); + int jmp_pc = mach_current_pc(cs); + mach_emit(cs, MACH_AsBx(MACH_JMPTRUE, dest, 0)); + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, r1, 0)); + { + int offset = mach_current_pc(cs) - (jmp_pc + 1); + cs->code[jmp_pc] = MACH_AsBx(MACH_GET_OP(cs->code[jmp_pc]), dest, (int16_t)offset); + } + mach_free_reg_to(cs, save); + return dest; + } + /* Standard 2-arg binary functino */ + { + int save = cs->freereg; + int r0 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 0), -1); + if (cs->freereg <= r0) cs->freereg = r0 + 1; + int r1 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 1), -1); + MachOpcode op; + if (strcmp(fname, "+!") == 0) op = MACH_ADD; + else if (strcmp(fname, "-!") == 0) op = MACH_SUB; + else if (strcmp(fname, "*!") == 0) op = MACH_MUL; + else if (strcmp(fname, "/!") == 0) op = MACH_DIV; + else if (strcmp(fname, "%!") == 0) op = MACH_MOD; + else if (strcmp(fname, "**!") == 0) op = MACH_POW; + else if (strcmp(fname, "!") == 0) op = MACH_GT; + else if (strcmp(fname, "<=!") == 0) op = MACH_LE; + else if (strcmp(fname, ">=!") == 0) op = MACH_GE; + else if (strcmp(fname, "=!") == 0) op = MACH_EQ; + else if (strcmp(fname, "!=!") == 0) op = MACH_NEQ; + else if (strcmp(fname, "&!") == 0) op = MACH_BAND; + else if (strcmp(fname, "|!") == 0) op = MACH_BOR; + else if (strcmp(fname, "^!") == 0) op = MACH_BXOR; + else if (strcmp(fname, "<>!") == 0) op = MACH_SHR; + else op = MACH_USHR; /* >>>! */ + mach_emit(cs, MACH_ABC(op, dest, r0, r1)); + mach_free_reg_to(cs, save); + return dest; + } + } + } + + if (fn_kind && strcmp(fn_kind, ".") == 0) { + /* Method call with dot notation: obj.method(args) */ + int save_freereg = cs->freereg; + int base = mach_reserve_reg(cs); /* R(base) = obj */ + if (dest < 0) dest = base; + mach_reserve_reg(cs); /* R(base+1) = temp slot for VM */ + + /* Compile obj into base */ + cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(fn_expr, "expression"); + if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(fn_expr, "left"); + int obj_r = mach_compile_expr(cs, obj_expr, base); + if (obj_r != base) + mach_emit(cs, MACH_ABC(MACH_MOVE, base, obj_r, 0)); + + /* Extract property name */ + cJSON *prop = cJSON_GetObjectItemCaseSensitive(fn_expr, "name"); + if (!prop) prop = cJSON_GetObjectItemCaseSensitive(fn_expr, "right"); + const char *prop_name = NULL; + if (cJSON_IsString(prop)) prop_name = cJSON_GetStringValue(prop); + else if (prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "value")); + if (!prop_name && prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "name")); + if (!prop_name) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fn_expr, "value")); + if (!prop_name) prop_name = "unknown"; + + int ki = mach_cpool_add_str(cs, prop_name); + + /* Compile args into R(base+2)..R(base+1+nargs) */ + for (int i = 0; i < nargs; i++) { + int arg_reg = mach_reserve_reg(cs); + cJSON *arg = cJSON_GetArrayItem(args, i); + int r = mach_compile_expr(cs, arg, arg_reg); + if (r != arg_reg) + mach_emit(cs, MACH_ABC(MACH_MOVE, arg_reg, r, 0)); + } + + mach_emit(cs, MACH_ABC(MACH_CALLMETHOD, base, nargs, ki)); + mach_free_reg_to(cs, save_freereg); + if (dest >= 0 && dest != base) + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, base, 0)); + else + dest = base; + return dest; + } + + if (fn_kind && strcmp(fn_kind, "[") == 0) { + /* Method call with bracket notation: obj[expr](args) */ + int save_freereg = cs->freereg; + int base = mach_reserve_reg(cs); /* R(base) = obj */ + if (dest < 0) dest = base; + int key_reg = mach_reserve_reg(cs); /* R(base+1) = key */ + + /* Compile obj into base */ + cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(fn_expr, "expression"); + if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(fn_expr, "left"); + int obj_r = mach_compile_expr(cs, obj_expr, base); + if (obj_r != base) + mach_emit(cs, MACH_ABC(MACH_MOVE, base, obj_r, 0)); + + /* Compile key expr into R(base+1) */ + cJSON *idx_expr = cJSON_GetObjectItemCaseSensitive(fn_expr, "index"); + if (!idx_expr) idx_expr = cJSON_GetObjectItemCaseSensitive(fn_expr, "right"); + int kr = mach_compile_expr(cs, idx_expr, key_reg); + if (kr != key_reg) + mach_emit(cs, MACH_ABC(MACH_MOVE, key_reg, kr, 0)); + + /* Compile args into R(base+2)..R(base+1+nargs) */ + for (int i = 0; i < nargs; i++) { + int arg_reg = mach_reserve_reg(cs); + cJSON *arg = cJSON_GetArrayItem(args, i); + int r = mach_compile_expr(cs, arg, arg_reg); + if (r != arg_reg) + mach_emit(cs, MACH_ABC(MACH_MOVE, arg_reg, r, 0)); + } + + /* C=0xFF signals key is in R(base+1) */ + mach_emit(cs, MACH_ABC(MACH_CALLMETHOD, base, nargs, 0xFF)); + mach_free_reg_to(cs, save_freereg); + if (dest >= 0 && dest != base) + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, base, 0)); + else + dest = base; + return dest; + } + + /* Save freereg so we can allocate consecutive regs for call */ + int save_freereg = cs->freereg; + + /* Allocate base register for the function */ + int base = mach_reserve_reg(cs); + if (dest < 0) dest = base; /* result goes to base */ + + /* Compile function expression into base */ + int fn_reg = mach_compile_expr(cs, fn_expr, base); + if (fn_reg != base) { + mach_emit(cs, MACH_ABC(MACH_MOVE, base, fn_reg, 0)); + } + + /* Allocate consecutive arg registers and compile args */ + for (int i = 0; i < nargs; i++) { + int arg_reg = mach_reserve_reg(cs); + cJSON *arg = cJSON_GetArrayItem(args, i); + int r = mach_compile_expr(cs, arg, arg_reg); + if (r != arg_reg) { + mach_emit(cs, MACH_ABC(MACH_MOVE, arg_reg, r, 0)); + } + } + + /* Emit CALL: base=func, B=nargs, C=1 if we want result */ + int keep = (dest >= 0) ? 1 : 0; + mach_emit(cs, MACH_ABC(MACH_CALL, base, nargs, keep)); + + /* Restore freereg */ + mach_free_reg_to(cs, save_freereg); + + /* If we want the result and dest != base, move it */ + if (dest >= 0 && dest != base) { + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, base, 0)); + } else { + dest = base; + } + return dest; + } + + /* Binary operators */ + if (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 || strcmp(kind, "|") == 0 || + strcmp(kind, "^") == 0 || strcmp(kind, "<<") == 0 || + strcmp(kind, ">>") == 0 || strcmp(kind, ">>>") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + + int save = cs->freereg; + cJSON *left = cJSON_GetObjectItemCaseSensitive(node, "left"); + cJSON *right = cJSON_GetObjectItemCaseSensitive(node, "right"); + + int lr = mach_compile_expr(cs, left, -1); + if (cs->freereg <= lr) cs->freereg = lr + 1; /* protect lr from reuse */ + int rr = mach_compile_expr(cs, right, -1); + + MachOpcode op; + if (strcmp(kind, "+") == 0) op = MACH_ADD; + else if (strcmp(kind, "-") == 0) op = MACH_SUB; + else if (strcmp(kind, "*") == 0) op = MACH_MUL; + else if (strcmp(kind, "/") == 0) op = MACH_DIV; + else if (strcmp(kind, "%") == 0) op = MACH_MOD; + else if (strcmp(kind, "**") == 0) op = MACH_POW; + else if (strcmp(kind, "==") == 0 || strcmp(kind, "===") == 0) op = MACH_EQ; + else if (strcmp(kind, "!=") == 0 || strcmp(kind, "!==") == 0) op = MACH_NEQ; + else if (strcmp(kind, "<") == 0) op = MACH_LT; + else if (strcmp(kind, "<=") == 0) op = MACH_LE; + else if (strcmp(kind, ">") == 0) op = MACH_GT; + else if (strcmp(kind, ">=") == 0) op = MACH_GE; + else if (strcmp(kind, "&") == 0) op = MACH_BAND; + else if (strcmp(kind, "|") == 0) op = MACH_BOR; + else if (strcmp(kind, "^") == 0) op = MACH_BXOR; + else if (strcmp(kind, "<<") == 0) op = MACH_SHL; + else if (strcmp(kind, ">>") == 0) op = MACH_SHR; + else op = MACH_USHR; /* >>> */ + + mach_emit(cs, MACH_ABC(op, dest, lr, rr)); + mach_free_reg_to(cs, save); + return dest; + } + + /* Short-circuit logical operators */ + if (strcmp(kind, "&&") == 0 || strcmp(kind, "||") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + cJSON *left = cJSON_GetObjectItemCaseSensitive(node, "left"); + cJSON *right = cJSON_GetObjectItemCaseSensitive(node, "right"); + + int lr = mach_compile_expr(cs, left, dest); + if (lr != dest) + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, lr, 0)); + + /* Emit conditional jump — patch offset later */ + int jmp_pc = mach_current_pc(cs); + if (strcmp(kind, "&&") == 0) + mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, dest, 0)); /* skip right if false */ + else + mach_emit(cs, MACH_AsBx(MACH_JMPTRUE, dest, 0)); /* skip right if true */ + + int rr = mach_compile_expr(cs, right, dest); + if (rr != dest) + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, rr, 0)); + + /* Patch jump offset: target is current PC, offset relative to instruction after jmp */ + int offset = mach_current_pc(cs) - (jmp_pc + 1); + cs->code[jmp_pc] = MACH_AsBx(MACH_GET_OP(cs->code[jmp_pc]), dest, (int16_t)offset); + return dest; + } + + /* Unary operators */ + if (strcmp(kind, "!") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + cJSON *operand = cJSON_GetObjectItemCaseSensitive(node, "expression"); + if (!operand) operand = cJSON_GetObjectItemCaseSensitive(node, "right"); + int save = cs->freereg; + int r = mach_compile_expr(cs, operand, -1); + mach_emit(cs, MACH_ABC(MACH_LNOT, dest, r, 0)); + mach_free_reg_to(cs, save); + return dest; + } + if (strcmp(kind, "~") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + cJSON *operand = cJSON_GetObjectItemCaseSensitive(node, "expression"); + if (!operand) operand = cJSON_GetObjectItemCaseSensitive(node, "right"); + int save = cs->freereg; + int r = mach_compile_expr(cs, operand, -1); + mach_emit(cs, MACH_ABC(MACH_BNOT, dest, r, 0)); + mach_free_reg_to(cs, save); + return dest; + } + if (strcmp(kind, "unary_-") == 0 || strcmp(kind, "-unary") == 0 || (strcmp(kind, "-") == 0 && !cJSON_GetObjectItemCaseSensitive(node, "left"))) { + if (dest < 0) dest = mach_reserve_reg(cs); + cJSON *operand = cJSON_GetObjectItemCaseSensitive(node, "expression"); + if (!operand) operand = cJSON_GetObjectItemCaseSensitive(node, "right"); + int save = cs->freereg; + int r = mach_compile_expr(cs, operand, -1); + mach_emit(cs, MACH_ABC(MACH_NEG, dest, r, 0)); + mach_free_reg_to(cs, save); + return dest; + } + + /* Unary plus: identity for numbers */ + if (strcmp(kind, "+unary") == 0 || strcmp(kind, "pos") == 0) { + cJSON *operand = cJSON_GetObjectItemCaseSensitive(node, "expression"); + return mach_compile_expr(cs, operand, dest); + } + + /* Comma operator: compile left for side effects, return right */ + if (strcmp(kind, ",") == 0) { + cJSON *left = cJSON_GetObjectItemCaseSensitive(node, "left"); + cJSON *right = cJSON_GetObjectItemCaseSensitive(node, "right"); + int save = cs->freereg; + mach_compile_expr(cs, left, -1); + mach_free_reg_to(cs, save); + return mach_compile_expr(cs, right, dest); + } + + /* Increment/Decrement as expression */ + if (strcmp(kind, "++") == 0 || strcmp(kind, "--") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + MachOpcode inc_op = (kind[0] == '+') ? MACH_INC : MACH_DEC; + cJSON *operand = cJSON_GetObjectItemCaseSensitive(node, "expression"); + cJSON *postfix_node = cJSON_GetObjectItemCaseSensitive(node, "postfix"); + int is_postfix = postfix_node && cJSON_IsTrue(postfix_node); + + const char *op_kind = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(operand, "kind")); + if (op_kind && strcmp(op_kind, "name") == 0) { + const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(operand, "name")); + cJSON *level_node = cJSON_GetObjectItemCaseSensitive(operand, "level"); + int level = level_node ? (int)cJSON_GetNumberValue(level_node) : -1; + if (level == 0 && name) { + int slot = mach_find_var(cs, name); + if (slot >= 0) { + if (is_postfix) { + /* Return old value, then increment */ + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, slot, 0)); + mach_emit(cs, MACH_ABC(inc_op, slot, slot, 0)); + } else { + /* Increment, then return new value */ + mach_emit(cs, MACH_ABC(inc_op, slot, slot, 0)); + if (dest != slot) + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, slot, 0)); + } + return dest; + } + } + } + /* Fallback: just compile operand */ + return mach_compile_expr(cs, operand, dest); + } + + /* Compound assignment operators */ + if (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) { + if (dest < 0) dest = mach_reserve_reg(cs); + cJSON *left = cJSON_GetObjectItemCaseSensitive(node, "left"); + cJSON *right = cJSON_GetObjectItemCaseSensitive(node, "right"); + + /* Map compound op to binary op */ + MachOpcode binop; + if (strcmp(kind, "+=") == 0) binop = MACH_ADD; + else if (strcmp(kind, "-=") == 0) binop = MACH_SUB; + else if (strcmp(kind, "*=") == 0) binop = MACH_MUL; + else if (strcmp(kind, "/=") == 0) binop = MACH_DIV; + else if (strcmp(kind, "%=") == 0) binop = MACH_MOD; + else if (strcmp(kind, "**=") == 0) binop = MACH_POW; + else if (strcmp(kind, "&=") == 0) binop = MACH_BAND; + else if (strcmp(kind, "|=") == 0) binop = MACH_BOR; + else if (strcmp(kind, "^=") == 0) binop = MACH_BXOR; + else if (strcmp(kind, "<<=") == 0) binop = MACH_SHL; + else if (strcmp(kind, ">>=") == 0) binop = MACH_SHR; + else binop = MACH_USHR; /* >>>= */ + + const char *lk = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(left, "kind")); + if (lk && strcmp(lk, "name") == 0) { + const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(left, "name")); + cJSON *level_node = cJSON_GetObjectItemCaseSensitive(left, "level"); + int level = level_node ? (int)cJSON_GetNumberValue(level_node) : -1; + if (level == 0 && name) { + int slot = mach_find_var(cs, name); + if (slot >= 0) { + int save = cs->freereg; + int rr = mach_compile_expr(cs, right, -1); + mach_emit(cs, MACH_ABC(binop, slot, slot, rr)); + mach_free_reg_to(cs, save); + if (dest != slot) + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, slot, 0)); + return dest; + } + } + } + /* Fallback: load null */ + mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0)); + return dest; + } + + /* In operator */ + if (strcmp(kind, "in") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + cJSON *left = cJSON_GetObjectItemCaseSensitive(node, "left"); + cJSON *right = cJSON_GetObjectItemCaseSensitive(node, "right"); + int save = cs->freereg; + int lr = mach_compile_expr(cs, left, -1); + if (cs->freereg <= lr) cs->freereg = lr + 1; + int rr = mach_compile_expr(cs, right, -1); + mach_emit(cs, MACH_ABC(MACH_HASPROP, dest, rr, lr)); + mach_free_reg_to(cs, save); + return dest; + } + + /* Assignment */ + if (strcmp(kind, "assign") == 0) { + cJSON *left = cJSON_GetObjectItemCaseSensitive(node, "left"); + cJSON *right = cJSON_GetObjectItemCaseSensitive(node, "right"); + + /* Push: arr[] = val */ + cJSON *push_node = cJSON_GetObjectItemCaseSensitive(node, "push"); + if (push_node && cJSON_IsTrue(push_node)) { + if (dest < 0) dest = mach_reserve_reg(cs); + int save = cs->freereg; + cJSON *arr_expr = cJSON_GetObjectItemCaseSensitive(left, "left"); + if (!arr_expr) arr_expr = cJSON_GetObjectItemCaseSensitive(left, "expression"); + int arr_r = mach_compile_expr(cs, arr_expr, -1); + int val_r = mach_compile_expr(cs, right, -1); + mach_emit(cs, MACH_ABC(MACH_PUSH, arr_r, val_r, 0)); + if (dest >= 0) + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, val_r, 0)); + mach_free_reg_to(cs, save); + return dest; + } + + const char *lk = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(left, "kind")); + + if (lk && strcmp(lk, "name") == 0) { + const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(left, "name")); + cJSON *level_node = cJSON_GetObjectItemCaseSensitive(left, "level"); + int level = level_node ? (int)cJSON_GetNumberValue(level_node) : -1; + + if (level == 0) { + /* Local assignment */ + int slot = name ? mach_find_var(cs, name) : -1; + if (slot >= 0) { + int r = mach_compile_expr(cs, right, slot); + if (r != slot) + mach_emit(cs, MACH_ABC(MACH_MOVE, slot, r, 0)); + if (dest >= 0 && dest != slot) + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, slot, 0)); + return slot; + } + } else if (level > 0) { + /* Closure assignment — walk parent states for slot */ + MachCompState *target = cs; + for (int i = 0; i < level; i++) target = target->parent; + int slot = mach_find_var(target, name); + if (dest < 0) dest = mach_reserve_reg(cs); + int r = mach_compile_expr(cs, right, dest); + if (r != dest) + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, r, 0)); + mach_emit(cs, MACH_ABC(MACH_SETUP, dest, level, slot)); + return dest; + } + /* Unbound (level -1) — error, AST parser should have rejected this */ + if (dest < 0) dest = mach_reserve_reg(cs); + mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0)); + return dest; + } + + /* Property assignment: left kind="." */ + if (lk && strcmp(lk, ".") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + int save = cs->freereg; + cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(left, "expression"); + if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(left, "left"); + cJSON *prop = cJSON_GetObjectItemCaseSensitive(left, "name"); + if (!prop) prop = cJSON_GetObjectItemCaseSensitive(left, "right"); + const char *prop_name = NULL; + if (cJSON_IsString(prop)) prop_name = cJSON_GetStringValue(prop); + else if (prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "value")); + if (!prop_name && prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "name")); + if (!prop_name) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(left, "value")); + + int obj_r = mach_compile_expr(cs, obj_expr, -1); + if (cs->freereg <= obj_r) cs->freereg = obj_r + 1; + int val_r = mach_compile_expr(cs, right, dest); + if (prop_name) { + int ki = mach_cpool_add_str(cs, prop_name); + mach_emit(cs, MACH_ABC(MACH_SETFIELD, obj_r, ki, val_r)); + } + mach_free_reg_to(cs, save); + return val_r; + } + + /* Computed property assignment: left kind="[" */ + if (lk && strcmp(lk, "[") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + int save = cs->freereg; + cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(left, "expression"); + if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(left, "left"); + cJSON *idx_expr = cJSON_GetObjectItemCaseSensitive(left, "index"); + if (!idx_expr) idx_expr = cJSON_GetObjectItemCaseSensitive(left, "right"); + + int obj_r = mach_compile_expr(cs, obj_expr, -1); + if (cs->freereg <= obj_r) cs->freereg = obj_r + 1; + int idx_r = mach_compile_expr(cs, idx_expr, -1); + if (cs->freereg <= idx_r) cs->freereg = idx_r + 1; + int val_r = mach_compile_expr(cs, right, dest); + mach_emit(cs, MACH_ABC(MACH_SETINDEX, obj_r, idx_r, val_r)); + mach_free_reg_to(cs, save); + return val_r; + } + + /* Fallback */ + if (dest < 0) dest = mach_reserve_reg(cs); + mach_compile_expr(cs, right, dest); + return dest; + } + + /* Property access: kind="." */ + if (strcmp(kind, ".") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + int save = cs->freereg; + cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(node, "expression"); + if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(node, "left"); + cJSON *prop = cJSON_GetObjectItemCaseSensitive(node, "name"); + if (!prop) prop = cJSON_GetObjectItemCaseSensitive(node, "right"); + const char *prop_name = NULL; + if (cJSON_IsString(prop)) prop_name = cJSON_GetStringValue(prop); + else if (prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "value")); + if (!prop_name && prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "name")); + if (!prop_name) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "value")); + + int obj_r = mach_compile_expr(cs, obj_expr, -1); + if (prop_name) { + int ki = mach_cpool_add_str(cs, prop_name); + mach_emit(cs, MACH_ABC(MACH_GETFIELD, dest, obj_r, ki)); + } + mach_free_reg_to(cs, save); + return dest; + } + + /* Computed property access: kind="[" */ + if (strcmp(kind, "[") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + int save = cs->freereg; + cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(node, "expression"); + if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(node, "left"); + cJSON *idx_expr = cJSON_GetObjectItemCaseSensitive(node, "index"); + if (!idx_expr) idx_expr = cJSON_GetObjectItemCaseSensitive(node, "right"); + + int obj_r = mach_compile_expr(cs, obj_expr, -1); + if (cs->freereg <= obj_r) cs->freereg = obj_r + 1; + int idx_r = mach_compile_expr(cs, idx_expr, -1); + mach_emit(cs, MACH_ABC(MACH_GETINDEX, dest, obj_r, idx_r)); + mach_free_reg_to(cs, save); + return dest; + } + + /* Object literal: kind="object" or "record" */ + if (strcmp(kind, "object") == 0 || strcmp(kind, "record") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + mach_emit(cs, MACH_ABC(MACH_NEWOBJECT, dest, 0, 0)); + cJSON *props = cJSON_GetObjectItemCaseSensitive(node, "list"); + if (props) { + int count = cJSON_GetArraySize(props); + for (int i = 0; i < count; i++) { + cJSON *prop = cJSON_GetArrayItem(props, i); + cJSON *key_node = cJSON_GetObjectItemCaseSensitive(prop, "key"); + if (!key_node) key_node = cJSON_GetObjectItemCaseSensitive(prop, "left"); + cJSON *val_node = cJSON_GetObjectItemCaseSensitive(prop, "value"); + if (!val_node) val_node = cJSON_GetObjectItemCaseSensitive(prop, "right"); + if (!val_node) val_node = cJSON_GetObjectItemCaseSensitive(prop, "expression"); + const char *key = cJSON_GetStringValue(key_node); + if (!key && key_node) key = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(key_node, "value")); + if (!key && key_node) key = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(key_node, "name")); + if (key && val_node) { + int save = cs->freereg; + int vr = mach_compile_expr(cs, val_node, -1); + int ki = mach_cpool_add_str(cs, key); + mach_emit(cs, MACH_ABC(MACH_SETFIELD, dest, ki, vr)); + mach_free_reg_to(cs, save); + } + } + } + return dest; + } + + /* Array literal: kind="array" */ + if (strcmp(kind, "array") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + cJSON *elems = cJSON_GetObjectItemCaseSensitive(node, "list"); + int count = elems ? cJSON_GetArraySize(elems) : 0; + + /* Reserve consecutive regs for elements starting at arr_base+1. + If dest is below freereg, other temps occupy dest+1..freereg-1 + so we must use a fresh base to avoid clobbering them. */ + int save = cs->freereg; + int arr_base; + if (dest + 1 >= cs->freereg) { + arr_base = dest; + cs->freereg = dest + 1; + } else { + arr_base = mach_reserve_reg(cs); + } + for (int i = 0; i < count; i++) { + int er = mach_reserve_reg(cs); + cJSON *elem = cJSON_GetArrayItem(elems, i); + int r = mach_compile_expr(cs, elem, er); + if (r != er) mach_emit(cs, MACH_ABC(MACH_MOVE, er, r, 0)); + } + mach_emit(cs, MACH_ABC(MACH_NEWARRAY, arr_base, count, 0)); + if (arr_base != dest) + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, arr_base, 0)); + mach_free_reg_to(cs, save); + return dest; + } + + /* Ternary: kind="?" or "then" */ + if (strcmp(kind, "?") == 0 || strcmp(kind, "then") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + cJSON *cond = cJSON_GetObjectItemCaseSensitive(node, "expression"); + if (!cond) cond = cJSON_GetObjectItemCaseSensitive(node, "condition"); + cJSON *then_expr = cJSON_GetObjectItemCaseSensitive(node, "then"); + if (!then_expr) then_expr = cJSON_GetObjectItemCaseSensitive(node, "left"); + cJSON *else_expr = cJSON_GetObjectItemCaseSensitive(node, "else"); + if (!else_expr) else_expr = cJSON_GetObjectItemCaseSensitive(node, "right"); + + int save = cs->freereg; + int cr = mach_compile_expr(cs, cond, -1); + int jmpfalse_pc = mach_current_pc(cs); + mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, cr, 0)); + mach_free_reg_to(cs, save); + + mach_compile_expr(cs, then_expr, dest); + int jmpend_pc = mach_current_pc(cs); + mach_emit(cs, MACH_sJ(MACH_JMP, 0)); + + /* Patch jmpfalse */ + int offset = mach_current_pc(cs) - (jmpfalse_pc + 1); + cs->code[jmpfalse_pc] = MACH_AsBx(MACH_JMPFALSE, cr, (int16_t)offset); + + mach_compile_expr(cs, else_expr, dest); + + /* Patch jmpend */ + offset = mach_current_pc(cs) - (jmpend_pc + 1); + cs->code[jmpend_pc] = MACH_sJ(MACH_JMP, offset); + return dest; + } + + /* Function literal */ + if (strcmp(kind, "function") == 0 || strcmp(kind, "=>") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + /* Compile nested function */ + MachCompState child = {0}; + child.parent = cs; + child.scopes = cs->scopes; + child.filename = cs->filename; + child.freereg = 1; /* slot 0 = this */ + + /* Read function_nr from AST node */ + cJSON *fn_nr_node = cJSON_GetObjectItemCaseSensitive(node, "function_nr"); + child.function_nr = fn_nr_node ? (int)cJSON_GetNumberValue(fn_nr_node) : 0; + + /* Register parameters */ + cJSON *params = cJSON_GetObjectItemCaseSensitive(node, "params"); + if (!params) params = cJSON_GetObjectItemCaseSensitive(node, "parameters"); + if (!params) params = cJSON_GetObjectItemCaseSensitive(node, "list"); + int nparams = params ? cJSON_GetArraySize(params) : 0; + child.nr_args = nparams; + for (int i = 0; i < nparams; i++) { + cJSON *p = cJSON_GetArrayItem(params, i); + const char *pname = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(p, "name")); + if (!pname) pname = cJSON_GetStringValue(p); + if (pname) { + int slot = mach_reserve_reg(&child); + mach_add_var(&child, pname, slot, 1); + } + } + + /* Scan scope record for var/def declarations */ + mach_scan_scope(&child); + + /* Emit default parameter initialization */ + for (int i = 0; i < nparams; i++) { + cJSON *p = cJSON_GetArrayItem(params, i); + cJSON *default_expr = cJSON_GetObjectItemCaseSensitive(p, "expression"); + if (default_expr) { + int slot = 1 + i; /* param slots start at 1 (slot 0 = this) */ + /* If param is null, skip the JMP and fall into default code */ + mach_emit(&child, MACH_AsBx(MACH_JMPNULL, slot, 1)); + /* If param is NOT null, jump past the default code */ + int jmp_pc = mach_current_pc(&child); + mach_emit(&child, MACH_sJ(MACH_JMP, 0)); /* placeholder */ + + int save = child.freereg; + mach_compile_expr(&child, default_expr, slot); + child.freereg = save; + + /* Patch JMP offset */ + int offset = mach_current_pc(&child) - (jmp_pc + 1); + child.code[jmp_pc] = MACH_sJ(MACH_JMP, offset); + } + } + + /* Compile body */ + cJSON *body = cJSON_GetObjectItemCaseSensitive(node, "body"); + if (!body) body = node; /* statements may be directly on the function node */ + { + cJSON *stmts = cJSON_GetObjectItemCaseSensitive(body, "statements"); + if (!stmts) stmts = body; /* body might be the statements array directly */ + if (cJSON_IsArray(stmts)) { + int count = cJSON_GetArraySize(stmts); + for (int i = 0; i < count; i++) { + mach_compile_stmt(&child, cJSON_GetArrayItem(stmts, i)); + } + } + } + + /* Implicit return null */ + mach_emit(&child, MACH_ABC(MACH_RETNIL, 0, 0, 0)); + + /* Disruption clause — emitted after body, recorded as disruption_pc */ + int disruption_start = 0; + cJSON *disruption = cJSON_GetObjectItemCaseSensitive(node, "disruption"); + if (disruption && cJSON_IsArray(disruption)) { + disruption_start = mach_current_pc(&child); + int dcount = cJSON_GetArraySize(disruption); + for (int i = 0; i < dcount; i++) + mach_compile_stmt(&child, cJSON_GetArrayItem(disruption, i)); + mach_emit(&child, MACH_ABC(MACH_RETNIL, 0, 0, 0)); + } + + /* Build MachCode for the child function */ + cJSON *fn_scope = mach_find_scope_record(cs->scopes, child.function_nr); + cJSON *fn_ncs = fn_scope ? cJSON_GetObjectItemCaseSensitive(fn_scope, "nr_close_slots") : NULL; + MachCode *fn_code = sys_malloc(sizeof(MachCode)); + memset(fn_code, 0, sizeof(MachCode)); + fn_code->arity = nparams; + fn_code->nr_slots = child.maxreg; + fn_code->nr_close_slots = fn_ncs ? (int)cJSON_GetNumberValue(fn_ncs) : 0; + fn_code->entry_point = 0; + fn_code->instr_count = child.code_count; + fn_code->instructions = child.code; + fn_code->cpool_count = child.cpool_count; + fn_code->cpool = child.cpool; + fn_code->func_count = child.func_count; + fn_code->functions = child.functions; + fn_code->line_table = child.line_info; + fn_code->filename = cs->filename ? strdup(cs->filename) : NULL; + fn_code->disruption_pc = disruption_start; + + cJSON *fname = cJSON_GetObjectItemCaseSensitive(node, "name"); + if (fname && cJSON_IsString(fname)) { + const char *ns = cJSON_GetStringValue(fname); + fn_code->name = sys_malloc(strlen(ns) + 1); + strcpy(fn_code->name, ns); + } else { + fn_code->name = NULL; + } + + /* Free child var table (not code/cpool, those are owned by fn_code now) */ + for (int i = 0; i < child.var_count; i++) + sys_free(child.vars[i].name); + sys_free(child.vars); + + int fi = mach_add_function(cs, fn_code); + mach_emit(cs, MACH_ABx(MACH_CLOSURE, dest, fi)); + return dest; + } + + /* Delete operator */ + if (strcmp(kind, "delete") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + cJSON *operand = cJSON_GetObjectItemCaseSensitive(node, "expression"); + if (!operand) operand = cJSON_GetObjectItemCaseSensitive(node, "right"); + if (operand) { + const char *okind = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(operand, "kind")); + if (okind && strcmp(okind, ".") == 0) { + /* delete obj.prop */ + cJSON *obj_node = cJSON_GetObjectItemCaseSensitive(operand, "left"); + cJSON *prop_node = cJSON_GetObjectItemCaseSensitive(operand, "right"); + const char *pname = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop_node, "name")); + if (!pname) pname = cJSON_GetStringValue(prop_node); + int save = cs->freereg; + int objr = mach_compile_expr(cs, obj_node, -1); + int ki = mach_cpool_add_str(cs, pname); + mach_emit(cs, MACH_ABC(MACH_DELETE, dest, objr, ki)); + mach_free_reg_to(cs, save); + return dest; + } else if (okind && strcmp(okind, "[") == 0) { + /* delete obj[expr] */ + cJSON *obj_node = cJSON_GetObjectItemCaseSensitive(operand, "left"); + cJSON *idx_node = cJSON_GetObjectItemCaseSensitive(operand, "right"); + int save = cs->freereg; + int objr = mach_compile_expr(cs, obj_node, -1); + int ir = mach_compile_expr(cs, idx_node, -1); + mach_emit(cs, MACH_ABC(MACH_DELETEINDEX, dest, objr, ir)); + mach_free_reg_to(cs, save); + return dest; + } + } + mach_emit(cs, MACH_ABx(MACH_LOADTRUE, dest, 0)); + return dest; + } + + /* This reference — slot 0 is always 'this' */ + if (strcmp(kind, "this") == 0) { + if (dest >= 0 && dest != 0) { + mach_emit(cs, MACH_ABC(MACH_MOVE, dest, 0, 0)); + return dest; + } + return 0; + } + + /* Regex literal */ + if (strcmp(kind, "regexp") == 0) { + if (dest < 0) dest = mach_reserve_reg(cs); + const char *pattern = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "pattern")); + const char *flags = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "flags")); + if (!pattern) pattern = ""; + if (!flags) flags = ""; + int pi = mach_cpool_add_str(cs, pattern); + int fi = mach_cpool_add_str(cs, flags); + mach_emit(cs, MACH_ABC(MACH_REGEXP, dest, pi, fi)); + return dest; + } + + /* Fallback: unsupported expression kind — load null */ + if (dest < 0) dest = mach_reserve_reg(cs); + mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0)); + return dest; +} + +/* ---- Statement compiler ---- */ + +static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) { + if (!stmt) return; + mach_set_pos(cs, stmt); + const char *kind = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(stmt, "kind")); + if (!kind) return; + + /* var / def declaration */ + 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")); + if (!name) return; + /* Check if var exists at current scope depth — if so, reuse it. + If it exists at a shallower depth, shadow it with a new slot. */ + int slot = -1; + for (int i = cs->var_count - 1; i >= 0; i--) { + if (strcmp(cs->vars[i].name, name) == 0) { + if (cs->vars[i].scope_depth == cs->scope_depth) { + slot = cs->vars[i].slot; /* same scope — reuse */ + } + break; + } + } + if (slot < 0) { + slot = mach_reserve_reg(cs); + mach_add_var(cs, name, slot, strcmp(kind, "def") == 0); + } + /* Pop: var x = arr[] */ + cJSON *pop_node = cJSON_GetObjectItemCaseSensitive(stmt, "pop"); + if (pop_node && cJSON_IsTrue(pop_node) && right) { + cJSON *arr_expr = cJSON_GetObjectItemCaseSensitive(right, "left"); + if (!arr_expr) arr_expr = cJSON_GetObjectItemCaseSensitive(right, "expression"); + int save = cs->freereg; + int arr_r = mach_compile_expr(cs, arr_expr, -1); + mach_emit(cs, MACH_ABC(MACH_POP, slot, arr_r, 0)); + mach_free_reg_to(cs, save); + return; + } + if (right) { + int r = mach_compile_expr(cs, right, slot); + if (r != slot) + mach_emit(cs, MACH_ABC(MACH_MOVE, slot, r, 0)); + } + return; + } + + /* var_list: multiple declarations in one statement */ + if (strcmp(kind, "var_list") == 0) { + cJSON *list = cJSON_GetObjectItemCaseSensitive(stmt, "list"); + if (list && cJSON_IsArray(list)) { + int count = cJSON_GetArraySize(list); + for (int i = 0; i < count; i++) + mach_compile_stmt(cs, cJSON_GetArrayItem(list, i)); + } + return; + } + + /* Function declaration statement */ + if (strcmp(kind, "function") == 0) { + const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(stmt, "name")); + if (!name) return; + int slot = mach_find_var(cs, name); + if (slot < 0) { + slot = mach_reserve_reg(cs); + mach_add_var(cs, name, slot, 1); + } + mach_compile_expr(cs, stmt, slot); + return; + } + + /* Expression statement (call) */ + if (strcmp(kind, "call") == 0) { + cJSON *expr = cJSON_GetObjectItemCaseSensitive(stmt, "expression"); + if (expr) { + int save = cs->freereg; + mach_compile_expr(cs, expr, -1); + mach_free_reg_to(cs, save); + } + return; + } + + /* Return statement */ + if (strcmp(kind, "return") == 0) { + cJSON *expr = cJSON_GetObjectItemCaseSensitive(stmt, "expression"); + if (expr) { + int save = cs->freereg; + int r = mach_compile_expr(cs, expr, -1); + mach_emit(cs, MACH_ABC(MACH_RETURN, r, 0, 0)); + mach_free_reg_to(cs, save); + } else { + mach_emit(cs, MACH_ABC(MACH_RETNIL, 0, 0, 0)); + } + return; + } + + /* Block */ + if (strcmp(kind, "block") == 0) { + int saved_var_count = cs->var_count; + cs->scope_depth++; + cJSON *stmts = cJSON_GetObjectItemCaseSensitive(stmt, "statements"); + if (stmts && cJSON_IsArray(stmts)) { + int count = cJSON_GetArraySize(stmts); + for (int i = 0; i < count; i++) { + mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i)); + } + } + cs->scope_depth--; + for (int i = saved_var_count; i < cs->var_count; i++) + sys_free(cs->vars[i].name); + cs->var_count = saved_var_count; + return; + } + + /* If statement */ + if (strcmp(kind, "if") == 0) { + cJSON *cond = cJSON_GetObjectItemCaseSensitive(stmt, "expression"); + if (!cond) cond = cJSON_GetObjectItemCaseSensitive(stmt, "condition"); + cJSON *then_body = cJSON_GetObjectItemCaseSensitive(stmt, "then"); + if (!then_body) then_body = cJSON_GetObjectItemCaseSensitive(stmt, "block"); + cJSON *else_body = cJSON_GetObjectItemCaseSensitive(stmt, "else"); + + int save = cs->freereg; + int cr = mach_compile_expr(cs, cond, -1); + int jmpfalse_pc = mach_current_pc(cs); + mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, cr, 0)); + mach_free_reg_to(cs, save); + + /* Compile then branch — "then" is a direct array of statements */ + if (then_body) { + int saved_vc = cs->var_count; + cs->scope_depth++; + if (cJSON_IsArray(then_body)) { + int count = cJSON_GetArraySize(then_body); + for (int i = 0; i < count; i++) + mach_compile_stmt(cs, cJSON_GetArrayItem(then_body, i)); + } else { + cJSON *stmts = cJSON_GetObjectItemCaseSensitive(then_body, "statements"); + if (stmts && cJSON_IsArray(stmts)) { + int count = cJSON_GetArraySize(stmts); + for (int i = 0; i < count; i++) + mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i)); + } else { + mach_compile_stmt(cs, then_body); + } + } + cs->scope_depth--; + for (int i = saved_vc; i < cs->var_count; i++) + sys_free(cs->vars[i].name); + cs->var_count = saved_vc; + } + + /* Check for else-if chain ("list") or plain else */ + if (!else_body) { + cJSON *list = cJSON_GetObjectItemCaseSensitive(stmt, "list"); + if (list && cJSON_IsArray(list) && cJSON_GetArraySize(list) > 0) + else_body = cJSON_GetArrayItem(list, 0); + } + + if (else_body) { + int jmpend_pc = mach_current_pc(cs); + mach_emit(cs, MACH_sJ(MACH_JMP, 0)); + + /* Patch jmpfalse to else */ + int offset = mach_current_pc(cs) - (jmpfalse_pc + 1); + cs->code[jmpfalse_pc] = MACH_AsBx(MACH_JMPFALSE, cr, (int16_t)offset); + + /* Compile else — could be a direct array, object, or else-if stmt */ + int saved_vc = cs->var_count; + cs->scope_depth++; + if (cJSON_IsArray(else_body)) { + int count = cJSON_GetArraySize(else_body); + for (int i = 0; i < count; i++) + mach_compile_stmt(cs, cJSON_GetArrayItem(else_body, i)); + } else { + cJSON *stmts = cJSON_GetObjectItemCaseSensitive(else_body, "statements"); + if (stmts && cJSON_IsArray(stmts)) { + int count = cJSON_GetArraySize(stmts); + for (int i = 0; i < count; i++) + mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i)); + } else { + mach_compile_stmt(cs, else_body); + } + } + cs->scope_depth--; + for (int i = saved_vc; i < cs->var_count; i++) + sys_free(cs->vars[i].name); + cs->var_count = saved_vc; + + /* Patch jmpend */ + offset = mach_current_pc(cs) - (jmpend_pc + 1); + cs->code[jmpend_pc] = MACH_sJ(MACH_JMP, offset); + } else { + /* No else — patch jmpfalse to after then */ + int offset = mach_current_pc(cs) - (jmpfalse_pc + 1); + cs->code[jmpfalse_pc] = MACH_AsBx(MACH_JMPFALSE, cr, (int16_t)offset); + } + return; + } + + /* While loop */ + if (strcmp(kind, "while") == 0) { + cJSON *cond = cJSON_GetObjectItemCaseSensitive(stmt, "expression"); + if (!cond) cond = cJSON_GetObjectItemCaseSensitive(stmt, "condition"); + + int old_break = cs->loop_break; + int old_continue = cs->loop_continue; + cs->loop_break = -1; + cs->loop_continue = -1; + + int loop_top = mach_current_pc(cs); + + int save = cs->freereg; + int cr = mach_compile_expr(cs, cond, -1); + int jmpfalse_pc = mach_current_pc(cs); + mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, cr, 0)); + mach_free_reg_to(cs, save); + + /* Compile body — "statements" on a child "block"/"body", or directly on the node */ + { + int saved_vc = cs->var_count; + cs->scope_depth++; + cJSON *body = cJSON_GetObjectItemCaseSensitive(stmt, "block"); + if (!body) body = cJSON_GetObjectItemCaseSensitive(stmt, "body"); + cJSON *stmts = body ? cJSON_GetObjectItemCaseSensitive(body, "statements") : NULL; + if (!stmts) stmts = cJSON_GetObjectItemCaseSensitive(stmt, "statements"); + if (stmts && cJSON_IsArray(stmts)) { + int count = cJSON_GetArraySize(stmts); + for (int i = 0; i < count; i++) + mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i)); + } else if (body) { + mach_compile_stmt(cs, body); + } + cs->scope_depth--; + for (int i = saved_vc; i < cs->var_count; i++) + sys_free(cs->vars[i].name); + cs->var_count = saved_vc; + } + + /* Patch continue chain to loop_top */ + { + int cp = cs->loop_continue; + while (cp >= 0) { + int prev = MACH_GET_sJ(cs->code[cp]); + int off = loop_top - (cp + 1); + cs->code[cp] = MACH_sJ(MACH_JMP, off); + cp = prev; + } + } + + /* Jump back to loop top */ + int offset = loop_top - (mach_current_pc(cs) + 1); + mach_emit(cs, MACH_sJ(MACH_JMP, offset)); + + /* Patch jmpfalse to after loop */ + offset = mach_current_pc(cs) - (jmpfalse_pc + 1); + cs->code[jmpfalse_pc] = MACH_AsBx(MACH_JMPFALSE, cr, (int16_t)offset); + + /* Patch break chain */ + int bp = cs->loop_break; + while (bp >= 0) { + int prev = MACH_GET_sJ(cs->code[bp]); + offset = mach_current_pc(cs) - (bp + 1); + cs->code[bp] = MACH_sJ(MACH_JMP, offset); + bp = prev; + } + cs->loop_break = old_break; + cs->loop_continue = old_continue; + return; + } + + /* For loop */ + if (strcmp(kind, "for") == 0) { + int saved_vc = cs->var_count; + cs->scope_depth++; + cJSON *init = cJSON_GetObjectItemCaseSensitive(stmt, "init"); + cJSON *cond = cJSON_GetObjectItemCaseSensitive(stmt, "test"); + cJSON *update = cJSON_GetObjectItemCaseSensitive(stmt, "update"); + cJSON *body = cJSON_GetObjectItemCaseSensitive(stmt, "block"); + if (!body) body = cJSON_GetObjectItemCaseSensitive(stmt, "body"); + + int old_break = cs->loop_break; + int old_continue = cs->loop_continue; + cs->loop_break = -1; + cs->loop_continue = -1; + + /* Init */ + if (init) mach_compile_stmt(cs, init); + + int loop_top = mach_current_pc(cs); + + /* Condition */ + int jmpfalse_pc = -1; + if (cond) { + int save = cs->freereg; + int cr = mach_compile_expr(cs, cond, -1); + jmpfalse_pc = mach_current_pc(cs); + mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, cr, 0)); + mach_free_reg_to(cs, save); + } + + /* Body — "statements" on a child "block"/"body", or directly on the for node */ + { + int body_vc = cs->var_count; + cs->scope_depth++; + cJSON *stmts = body ? cJSON_GetObjectItemCaseSensitive(body, "statements") : NULL; + if (!stmts) stmts = cJSON_GetObjectItemCaseSensitive(stmt, "statements"); + if (stmts && cJSON_IsArray(stmts)) { + int count = cJSON_GetArraySize(stmts); + for (int i = 0; i < count; i++) + mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i)); + } else if (body) { + mach_compile_stmt(cs, body); + } + cs->scope_depth--; + for (int i = body_vc; i < cs->var_count; i++) + sys_free(cs->vars[i].name); + cs->var_count = body_vc; + } + + /* Patch continue chain to update (or loop_top if no update) */ + { + int continue_target = mach_current_pc(cs); + int cp = cs->loop_continue; + while (cp >= 0) { + int prev = MACH_GET_sJ(cs->code[cp]); + int off = continue_target - (cp + 1); + cs->code[cp] = MACH_sJ(MACH_JMP, off); + cp = prev; + } + } + + /* Update — assignment expressions must be compiled as statements */ + if (update) { + mach_compile_stmt(cs, update); + } + + /* Jump back */ + int offset = loop_top - (mach_current_pc(cs) + 1); + mach_emit(cs, MACH_sJ(MACH_JMP, offset)); + + /* Patch condition exit */ + if (jmpfalse_pc >= 0) { + offset = mach_current_pc(cs) - (jmpfalse_pc + 1); + /* Need to recover the register used for condition - use A from the instruction */ + int cr = MACH_GET_A(cs->code[jmpfalse_pc]); + cs->code[jmpfalse_pc] = MACH_AsBx(MACH_JMPFALSE, cr, (int16_t)offset); + } + + /* Patch break chain */ + int bp = cs->loop_break; + while (bp >= 0) { + int prev = MACH_GET_sJ(cs->code[bp]); + offset = mach_current_pc(cs) - (bp + 1); + cs->code[bp] = MACH_sJ(MACH_JMP, offset); + bp = prev; + } + cs->loop_break = old_break; + cs->loop_continue = old_continue; + cs->scope_depth--; + for (int i = saved_vc; i < cs->var_count; i++) + sys_free(cs->vars[i].name); + cs->var_count = saved_vc; + return; + } + + /* Break */ + if (strcmp(kind, "break") == 0) { + int pc = mach_current_pc(cs); + mach_emit(cs, MACH_sJ(MACH_JMP, cs->loop_break)); + cs->loop_break = pc; + return; + } + + /* Continue */ + if (strcmp(kind, "continue") == 0) { + int pc = mach_current_pc(cs); + mach_emit(cs, MACH_sJ(MACH_JMP, cs->loop_continue)); + cs->loop_continue = pc; + return; + } + + /* Assignment as statement */ + 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) { + int save = cs->freereg; + mach_compile_expr(cs, stmt, -1); + mach_free_reg_to(cs, save); + return; + } + + /* Disrupt statement */ + if (strcmp(kind, "disrupt") == 0) { + mach_emit(cs, MACH_ABC(MACH_THROW, 0, 0, 0)); + return; + } + + /* Fallback: treat as expression statement */ + { + cJSON *expr = cJSON_GetObjectItemCaseSensitive(stmt, "expression"); + if (expr) { + int save = cs->freereg; + mach_compile_expr(cs, expr, -1); + mach_free_reg_to(cs, save); + } + } +} + +/* ---- Link pass: resolve GETNAME to GETINTRINSIC or GETENV ---- */ + +static void mach_link_code(JSContext *ctx, JSCodeRegister *code, JSValue env) { + for (uint32_t i = 0; i < code->instr_count; i++) { + MachInstr32 instr = code->instructions[i]; + if (MACH_GET_OP(instr) != MACH_GETNAME) continue; + int a = MACH_GET_A(instr); + int bx = MACH_GET_Bx(instr); + int in_env = 0; + if (!JS_IsNull(env) && (uint32_t)bx < code->cpool_count) { + JSValue val = JS_GetProperty(ctx, env, code->cpool[bx]); + in_env = !JS_IsNull(val) && !JS_IsException(val); + } + code->instructions[i] = MACH_ABx(in_env ? MACH_GETENV : MACH_GETINTRINSIC, a, bx); + } + for (uint32_t i = 0; i < code->func_count; i++) + if (code->functions[i]) mach_link_code(ctx, code->functions[i], env); +} + +/* ---- Top-level compiler ---- */ + +static MachCode *mach_compile_program(MachCompState *cs, cJSON *ast) { + cJSON *stmts = cJSON_GetObjectItemCaseSensitive(ast, "statements"); + if (!stmts || !cJSON_IsArray(stmts)) return NULL; + + /* Read scopes array from AST */ + cs->scopes = cJSON_GetObjectItemCaseSensitive(ast, "scopes"); + cs->function_nr = 0; + + /* Extract filename for debug info */ + cs->filename = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(ast, "filename")); + + /* Scan scope record for declarations */ + mach_scan_scope(cs); + + /* Hoist function declarations */ + cJSON *functions = cJSON_GetObjectItemCaseSensitive(ast, "functions"); + if (functions && cJSON_IsArray(functions)) { + int fcount = cJSON_GetArraySize(functions); + for (int i = 0; i < fcount; i++) { + cJSON *fn_node = cJSON_GetArrayItem(functions, i); + const char *fn_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fn_node, "name")); + if (!fn_name) continue; + int slot = mach_find_var(cs, fn_name); + if (slot < 0) continue; + mach_compile_expr(cs, fn_node, slot); + } + } + + /* Compile each statement */ + int count = cJSON_GetArraySize(stmts); + for (int i = 0; i < count; i++) { + mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i)); + } + + /* Implicit return null */ + mach_emit(cs, MACH_ABC(MACH_RETNIL, 0, 0, 0)); + + /* nr_close_slots from scope record */ + cJSON *prog_scope = mach_find_scope_record(cs->scopes, 0); + cJSON *ncs_node = prog_scope ? cJSON_GetObjectItemCaseSensitive(prog_scope, "nr_close_slots") : NULL; + + /* Build MachCode */ + MachCode *code = sys_malloc(sizeof(MachCode)); + memset(code, 0, sizeof(MachCode)); + code->arity = 0; + code->nr_slots = cs->maxreg; + code->nr_close_slots = ncs_node ? (int)cJSON_GetNumberValue(ncs_node) : 0; + code->entry_point = 0; + code->instr_count = cs->code_count; + code->instructions = cs->code; + code->cpool_count = cs->cpool_count; + code->cpool = cs->cpool; + code->func_count = cs->func_count; + code->functions = cs->functions; + code->name = NULL; + code->line_table = cs->line_info; + code->filename = cs->filename ? strdup(cs->filename) : NULL; + + return code; +} + +/* Public API: compile AST JSON to MachCode (context-free) */ +MachCode *JS_CompileMachTree(cJSON *ast) { + if (!ast) return NULL; + + MachCompState cs = {0}; + cs.freereg = 1; /* slot 0 = this */ + cs.maxreg = 1; + + MachCode *code = mach_compile_program(&cs, ast); + + /* Free var table (code/cpool/functions are owned by MachCode now) */ + for (int i = 0; i < cs.var_count; i++) + sys_free(cs.vars[i].name); + sys_free(cs.vars); + + return code; +} + +MachCode *JS_CompileMach(const char *ast_json) { + cJSON *ast = cJSON_Parse(ast_json); + if (!ast) return NULL; + MachCode *code = JS_CompileMachTree(ast); + cJSON_Delete(ast); + return code; +} + +/* Free a MachCode tree (compiled but not yet loaded) */ +void JS_FreeMachCode(MachCode *mc) { + if (!mc) return; + sys_free(mc->instructions); + for (uint32_t i = 0; i < mc->cpool_count; i++) { + if (mc->cpool[i].type == MACH_CP_STR) + sys_free(mc->cpool[i].str); + } + sys_free(mc->cpool); + for (uint32_t i = 0; i < mc->func_count; i++) + JS_FreeMachCode(mc->functions[i]); + sys_free(mc->functions); + sys_free(mc->name); + sys_free(mc->line_table); + sys_free(mc->filename); + sys_free(mc); +} + +/* Load a MachCode into a JSCodeRegister (materializes JSValues, needs ctx) */ +JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env) { + JSCodeRegister *code = js_mallocz_rt(sizeof(JSCodeRegister)); + code->arity = mc->arity; + code->nr_close_slots = mc->nr_close_slots; + code->nr_slots = mc->nr_slots; + code->entry_point = mc->entry_point; + code->instr_count = mc->instr_count; + code->instructions = mc->instructions; /* transfer ownership */ + + /* Materialize cpool: raw -> JSValue */ + code->cpool_count = mc->cpool_count; + code->cpool = mach_materialize_cpool(ctx, mc->cpool, mc->cpool_count); + + /* Recursively load nested functions */ + code->func_count = mc->func_count; + if (mc->func_count > 0) { + code->functions = js_malloc_rt(mc->func_count * sizeof(JSCodeRegister *)); + for (uint32_t i = 0; i < mc->func_count; i++) + code->functions[i] = JS_LoadMachCode(ctx, mc->functions[i], env); + } else { + code->functions = NULL; + } + + /* Intern function name */ + code->name = mc->name ? js_key_new(ctx, mc->name) : JS_NULL; + + /* Transfer debug info */ + code->line_table = mc->line_table; + mc->line_table = NULL; + code->filename_cstr = mc->filename ? js_strdup_rt(mc->filename) : NULL; + code->name_cstr = mc->name ? js_strdup_rt(mc->name) : NULL; + + code->disruption_pc = mc->disruption_pc; + + /* Link: resolve GETNAME to GETENV/GETINTRINSIC */ + mach_link_code(ctx, code, env); + + return code; +} + +/* Free a JSCodeRegister and all nested functions */ +static void js_free_code_register(JSCodeRegister *code) { + if (!code) return; + js_free_rt(code->instructions); + js_free_rt(code->cpool); + for (uint32_t i = 0; i < code->func_count; i++) { + js_free_code_register(code->functions[i]); + } + js_free_rt(code->functions); + js_free_rt(code->line_table); + js_free_rt(code->filename_cstr); + js_free_rt(code->name_cstr); + js_free_rt(code); +} + + +/* ============================================================ + MACH VM — register-based bytecode interpreter + ============================================================ */ + +/* Allocate a JSFrameRegister on the GC heap */ +JSFrameRegister *alloc_frame_register(JSContext *ctx, int slot_count) { + size_t size = sizeof(JSFrameRegister) + slot_count * sizeof(JSValue); + JSFrameRegister *frame = js_mallocz(ctx, size); + if (!frame) return NULL; + + /* cap56 = slot count (used by gc_object_size) */ + frame->hdr = objhdr_make(slot_count, OBJ_FRAME, 0, 0, 0, 0); + frame->function = JS_NULL; + frame->caller = JS_NULL; + frame->address = JS_NewInt32(ctx, 0); + + /* Initialize slots to null */ + for (int i = 0; i < slot_count; i++) { + frame->slots[i] = JS_NULL; + } + + return frame; +} + +/* Create a register-based function from JSCodeRegister */ +JSValue js_new_register_function(JSContext *ctx, JSCodeRegister *code, JSValue env, JSValue outer_frame) { + /* Protect env and outer_frame from GC — js_mallocz can trigger + collection which moves heap objects, invalidating stack-local copies */ + JSGCRef env_ref, frame_ref; + JS_PushGCRef(ctx, &env_ref); + env_ref.val = env; + JS_PushGCRef(ctx, &frame_ref); + frame_ref.val = outer_frame; + + JSFunction *fn = js_mallocz(ctx, sizeof(JSFunction)); + if (!fn) { + JS_PopGCRef(ctx, &frame_ref); + JS_PopGCRef(ctx, &env_ref); + return JS_EXCEPTION; + } + + fn->header = objhdr_make(0, OBJ_FUNCTION, 0, 0, 0, 0); + fn->kind = JS_FUNC_KIND_REGISTER; + fn->length = code->arity; + fn->name = code->name; + fn->u.reg.code = code; + fn->u.reg.env_record = env_ref.val; + fn->u.reg.outer_frame = frame_ref.val; + + JS_PopGCRef(ctx, &frame_ref); + JS_PopGCRef(ctx, &env_ref); + return JS_MKPTR(fn); +} + +/* Binary operations helper */ +static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) { + /* Fast path for integers */ + if (JS_VALUE_IS_BOTH_INT(a, b)) { + int32_t ia = JS_VALUE_GET_INT(a); + int32_t ib = JS_VALUE_GET_INT(b); + switch (op) { + case MACH_ADD: { + int64_t r = (int64_t)ia + (int64_t)ib; + if (r >= INT32_MIN && r <= INT32_MAX) + return JS_NewInt32(ctx, (int32_t)r); + return JS_NewFloat64(ctx, (double)r); + } + case MACH_SUB: { + int64_t r = (int64_t)ia - (int64_t)ib; + if (r >= INT32_MIN && r <= INT32_MAX) + return JS_NewInt32(ctx, (int32_t)r); + return JS_NewFloat64(ctx, (double)r); + } + case MACH_MUL: { + int64_t r = (int64_t)ia * (int64_t)ib; + if (r >= INT32_MIN && r <= INT32_MAX) + return JS_NewInt32(ctx, (int32_t)r); + return JS_NewFloat64(ctx, (double)r); + } + case MACH_DIV: + if (ib == 0) return JS_NULL; + if (ia % ib == 0) return JS_NewInt32(ctx, ia / ib); + return JS_NewFloat64(ctx, (double)ia / (double)ib); + case MACH_MOD: + if (ib == 0) return JS_NULL; + return JS_NewInt32(ctx, ia % ib); + case MACH_EQ: + return JS_NewBool(ctx, ia == ib); + case MACH_NEQ: + return JS_NewBool(ctx, ia != ib); + case MACH_LT: + return JS_NewBool(ctx, ia < ib); + case MACH_LE: + return JS_NewBool(ctx, ia <= ib); + case MACH_GT: + return JS_NewBool(ctx, ia > ib); + case MACH_GE: + return JS_NewBool(ctx, ia >= ib); + case MACH_BAND: + return JS_NewInt32(ctx, ia & ib); + case MACH_BOR: + return JS_NewInt32(ctx, ia | ib); + case MACH_BXOR: + return JS_NewInt32(ctx, ia ^ ib); + case MACH_SHL: + return JS_NewInt32(ctx, ia << (ib & 31)); + case MACH_SHR: + return JS_NewInt32(ctx, ia >> (ib & 31)); + case MACH_USHR: + return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31)); + default: + break; + } + } + + /* String concat for ADD */ + if (op == MACH_ADD && JS_IsText(a) && JS_IsText(b)) + return JS_ConcatString(ctx, a, b); + + /* Comparison ops allow mixed types — return false for mismatches */ + if (op >= MACH_EQ && op <= MACH_GE) { + /* Fast path: identical values (chase pointers for forwarded objects) */ + { + JSValue ca = JS_IsPtr(a) ? JS_MKPTR(chase(a)) : a; + JSValue cb = JS_IsPtr(b) ? JS_MKPTR(chase(b)) : b; + if (ca == cb) { + if (op == MACH_EQ || op == MACH_LE || op == MACH_GE) return JS_TRUE; + if (op == MACH_NEQ) return JS_FALSE; + } + } + if (JS_IsNumber(a) && JS_IsNumber(b)) { + double da, db; + JS_ToFloat64(ctx, &da, a); + JS_ToFloat64(ctx, &db, b); + switch (op) { + case MACH_EQ: return JS_NewBool(ctx, da == db); + case MACH_NEQ: return JS_NewBool(ctx, da != db); + case MACH_LT: return JS_NewBool(ctx, da < db); + case MACH_LE: return JS_NewBool(ctx, da <= db); + case MACH_GT: return JS_NewBool(ctx, da > db); + case MACH_GE: return JS_NewBool(ctx, da >= db); + default: break; + } + } + /* String comparisons */ + if (JS_IsText(a) && JS_IsText(b)) { + int cmp = js_string_compare_value(ctx, a, b, FALSE); + switch (op) { + case MACH_EQ: return JS_NewBool(ctx, cmp == 0); + case MACH_NEQ: return JS_NewBool(ctx, cmp != 0); + case MACH_LT: return JS_NewBool(ctx, cmp < 0); + case MACH_LE: return JS_NewBool(ctx, cmp <= 0); + case MACH_GT: return JS_NewBool(ctx, cmp > 0); + case MACH_GE: return JS_NewBool(ctx, cmp >= 0); + default: break; + } + } + /* Null comparisons */ + if (JS_IsNull(a) && JS_IsNull(b)) { + if (op == MACH_EQ || op == MACH_LE || op == MACH_GE) + return JS_TRUE; + return JS_FALSE; + } + /* Boolean comparisons */ + if (JS_IsBool(a) && JS_IsBool(b)) { + int ba = JS_VALUE_GET_BOOL(a); + int bb = JS_VALUE_GET_BOOL(b); + switch (op) { + case MACH_EQ: return JS_NewBool(ctx, ba == bb); + case MACH_NEQ: return JS_NewBool(ctx, ba != bb); + case MACH_LT: return JS_NewBool(ctx, ba < bb); + case MACH_LE: return JS_NewBool(ctx, ba <= bb); + case MACH_GT: return JS_NewBool(ctx, ba > bb); + case MACH_GE: return JS_NewBool(ctx, ba >= bb); + default: break; + } + } + /* Different types: EQ→false, NEQ→true, others→false */ + if (op == MACH_NEQ) return JS_NewBool(ctx, 1); + return JS_NewBool(ctx, 0); + } + + /* Numeric operations — both must be numeric */ + if (JS_IsNumber(a) && JS_IsNumber(b)) { + double da, db; + JS_ToFloat64(ctx, &da, a); + JS_ToFloat64(ctx, &db, b); + switch (op) { + case MACH_ADD: { + double r = da + db; + if (!isfinite(r)) return JS_NULL; + return JS_NewFloat64(ctx, r); + } + case MACH_SUB: { + double r = da - db; + if (!isfinite(r)) return JS_NULL; + return JS_NewFloat64(ctx, r); + } + case MACH_MUL: { + double r = da * db; + if (!isfinite(r)) return JS_NULL; + return JS_NewFloat64(ctx, r); + } + case MACH_DIV: { + if (db == 0.0) return JS_NULL; + double r = da / db; + if (!isfinite(r)) return JS_NULL; + return JS_NewFloat64(ctx, r); + } + case MACH_MOD: { + if (db == 0.0) return JS_NULL; + double r = fmod(da, db); + if (!isfinite(r)) return JS_NULL; + return JS_NewFloat64(ctx, r); + } + case MACH_POW: { + double r = pow(da, db); + if (!isfinite(r) && isfinite(da) && isfinite(db)) return JS_NULL; + return JS_NewFloat64(ctx, r); + } + case MACH_BAND: case MACH_BOR: case MACH_BXOR: + case MACH_SHL: case MACH_SHR: case MACH_USHR: { + int32_t ia = (int32_t)da; + int32_t ib = (int32_t)db; + switch (op) { + case MACH_BAND: return JS_NewInt32(ctx, ia & ib); + case MACH_BOR: return JS_NewInt32(ctx, ia | ib); + case MACH_BXOR: return JS_NewInt32(ctx, ia ^ ib); + case MACH_SHL: return JS_NewInt32(ctx, ia << (ib & 31)); + case MACH_SHR: return JS_NewInt32(ctx, ia >> (ib & 31)); + case MACH_USHR: return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31)); + default: break; + } + } + default: break; + } + } + + /* Type mismatch — disrupt */ + return JS_EXCEPTION; +} + +/* Check for interrupt */ +int reg_vm_check_interrupt(JSContext *ctx) { + if (--ctx->interrupt_counter <= 0) { + ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; + if (ctx->interrupt_handler) { + if (ctx->interrupt_handler(ctx->rt, ctx->interrupt_opaque)) { + return -1; + } + } + } + return 0; +} + +#ifdef HAVE_ASAN +void __asan_on_error(void) { + JSContext *ctx = __asan_js_ctx; + if (!ctx) return; + if (JS_IsNull(ctx->reg_current_frame)) return; + JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame); + uint32_t cur_pc = ctx->current_register_pc; + fprintf(stderr, "\n=== ASAN error: VM stack trace ===\n"); + int is_first = 1; + while (frame) { + if (!JS_IsFunction(frame->function)) break; + JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); + const char *func_name = NULL; + const char *file = NULL; + uint16_t line = 0; + uint32_t pc = is_first ? cur_pc : 0; + if (fn->kind == JS_FUNC_KIND_REGISTER && fn->u.reg.code) { + JSCodeRegister *code = fn->u.reg.code; + file = code->filename_cstr; + func_name = code->name_cstr; + if (!is_first) + pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16); + if (code->line_table && pc < code->instr_count) + line = code->line_table[pc].line; + } else if (fn->kind == JS_FUNC_KIND_MCODE && fn->u.mcode.code) { + JSMCode *code = fn->u.mcode.code; + file = code->filename; + func_name = code->name; + if (!is_first) + pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16); + if (code->line_table && pc < code->instr_count) + line = code->line_table[pc].line; + } + fprintf(stderr, " %s (%s:%u)\n", + func_name ? func_name : "", + file ? file : "", line); + if (JS_IsNull(frame->caller)) break; + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); + is_first = 0; + } + fprintf(stderr, "=================================\n"); +} +#endif + +/* Main register VM execution loop */ +JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, + JSValue this_obj, int argc, JSValue *argv, + JSValue env, JSValue outer_frame) { + /* Protect env and outer_frame from GC — alloc_frame_register can trigger + collection which moves heap objects, invalidating stack-local copies */ + JSGCRef env_gc, of_gc; + JS_PushGCRef(ctx, &env_gc); + env_gc.val = env; + JS_PushGCRef(ctx, &of_gc); + of_gc.val = outer_frame; + + /* Protect argv and this_obj from GC by pushing onto value_stack. + argv is a C-allocated array whose JSValues may point to GC heap objects; + alloc_frame_register and js_new_register_function can trigger GC. */ + int vs_save = ctx->value_stack_top; + int nargs_copy = (argc < code->arity) ? argc : code->arity; + ctx->value_stack[vs_save] = this_obj; + for (int i = 0; i < nargs_copy; i++) + ctx->value_stack[vs_save + 1 + i] = argv[i]; + ctx->value_stack_top = vs_save + 1 + nargs_copy; + + /* Allocate initial frame */ + JSFrameRegister *frame = alloc_frame_register(ctx, code->nr_slots); + if (!frame) { + ctx->value_stack_top = vs_save; + JS_PopGCRef(ctx, &of_gc); + JS_PopGCRef(ctx, &env_gc); + return JS_EXCEPTION; + } + + /* Protect frame from GC */ + JSGCRef frame_ref; + JS_AddGCRef(ctx, &frame_ref); + frame_ref.val = JS_MKPTR(frame); +#ifdef HAVE_ASAN + __asan_js_ctx = ctx; +#endif + + /* Setup initial frame — wrap top-level code in a function object so that + returning from a called register function can read code/env from frame */ + JSValue top_fn = js_new_register_function(ctx, code, env_gc.val, of_gc.val); + JS_PopGCRef(ctx, &of_gc); + JS_PopGCRef(ctx, &env_gc); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + frame->function = top_fn; + frame->slots[0] = ctx->value_stack[vs_save]; /* slot 0 = this (GC-safe from value_stack) */ + + /* Copy arguments from GC-safe value_stack */ + for (int i = 0; i < nargs_copy; i++) { + frame->slots[1 + i] = ctx->value_stack[vs_save + 1 + i]; + } + ctx->value_stack_top = vs_save; + + uint32_t pc = code->entry_point; + JSValue result = JS_NULL; + + /* Execution loop — 32-bit instruction dispatch */ + for (;;) { + if (reg_vm_check_interrupt(ctx)) { + result = JS_ThrowInternalError(ctx, "interrupted"); + goto done; + } + + if (pc >= code->instr_count) { + /* End of code — implicit return null */ + result = JS_NULL; + if (JS_IsNull(frame->caller)) goto done; + + /* Pop frame */ + JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); + frame->caller = JS_NULL; + frame = caller; + frame_ref.val = JS_MKPTR(frame); + int ret_info = JS_VALUE_GET_INT(frame->address); + JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); + code = fn->u.reg.code; + env = fn->u.reg.env_record; + pc = ret_info >> 16; + int ret_slot = ret_info & 0xFFFF; + if (ret_slot != 0xFFFF) frame->slots[ret_slot] = result; + continue; + } + + MachInstr32 instr = code->instructions[pc++]; + ctx->reg_current_frame = frame_ref.val; + ctx->current_register_pc = pc > 0 ? pc - 1 : 0; + int op = MACH_GET_OP(instr); + int a = MACH_GET_A(instr); + int b = MACH_GET_B(instr); + int c = MACH_GET_C(instr); + + switch (op) { + case MACH_NOP: + break; + + case MACH_LOADK: { + int bx = MACH_GET_Bx(instr); + if (bx < (int)code->cpool_count) + frame->slots[a] = code->cpool[bx]; + break; + } + + case MACH_LOADI: + frame->slots[a] = JS_NewInt32(ctx, MACH_GET_sBx(instr)); + break; + + case MACH_LOADNULL: + frame->slots[a] = JS_NULL; + break; + + case MACH_LOADTRUE: + frame->slots[a] = JS_TRUE; + break; + + case MACH_LOADFALSE: + frame->slots[a] = JS_FALSE; + break; + + case MACH_MOVE: + frame->slots[a] = frame->slots[b]; + break; + + /* Arithmetic / comparison / bitwise — all ABC format */ + case MACH_ADD: case MACH_SUB: case MACH_MUL: case MACH_DIV: + case MACH_MOD: case MACH_POW: + case MACH_EQ: case MACH_NEQ: case MACH_LT: case MACH_LE: + case MACH_GT: case MACH_GE: + case MACH_BAND: case MACH_BOR: case MACH_BXOR: + case MACH_SHL: case MACH_SHR: case MACH_USHR: { + JSValue left = frame->slots[b]; + JSValue right = frame->slots[c]; + JSValue res = reg_vm_binop(ctx, op, left, right); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (JS_IsException(res)) { goto disrupt; } + frame->slots[a] = res; + break; + } + + case MACH_EQ_TOL: + case MACH_NEQ_TOL: { + /* A=dest, B=base, C=3; args in R(B), R(B+1), R(B+2) */ + JSValue left = frame->slots[b]; + JSValue right = frame->slots[b + 1]; + JSValue tol = frame->slots[b + 2]; + BOOL is_eq_op = (op == MACH_EQ_TOL); + if (JS_IsNumber(left) && JS_IsNumber(right) && JS_IsNumber(tol)) { + double da, db, dt; + JS_ToFloat64(ctx, &da, left); + JS_ToFloat64(ctx, &db, right); + JS_ToFloat64(ctx, &dt, tol); + BOOL eq = fabs(da - db) <= dt; + frame->slots[a] = JS_NewBool(ctx, is_eq_op ? eq : !eq); + } else if (JS_IsText(left) && JS_IsText(right) && JS_VALUE_GET_TAG(tol) == JS_TAG_BOOL && JS_VALUE_GET_BOOL(tol)) { + BOOL eq = js_string_compare_value_nocase(ctx, left, right) == 0; + frame->slots[a] = JS_NewBool(ctx, is_eq_op ? eq : !eq); + } else { + /* Fall through to standard eq/neq */ + JSValue res = reg_vm_binop(ctx, is_eq_op ? MACH_EQ : MACH_NEQ, left, right); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (JS_IsException(res)) { goto disrupt; } + frame->slots[a] = res; + } + break; + } + + case MACH_NEG: { + JSValue v = frame->slots[b]; + if (JS_IsInt(v)) { + int32_t i = JS_VALUE_GET_INT(v); + if (i == INT32_MIN) + frame->slots[a] = JS_NewFloat64(ctx, -(double)i); + else + frame->slots[a] = JS_NewInt32(ctx, -i); + } else { + double d; + JS_ToFloat64(ctx, &d, v); + frame->slots[a] = JS_NewFloat64(ctx, -d); + } + break; + } + + case MACH_INC: { + JSValue v = frame->slots[b]; + if (JS_IsInt(v)) { + int32_t i = JS_VALUE_GET_INT(v); + if (i == INT32_MAX) + frame->slots[a] = JS_NewFloat64(ctx, (double)i + 1); + else + frame->slots[a] = JS_NewInt32(ctx, i + 1); + } else { + double d; + JS_ToFloat64(ctx, &d, v); + frame->slots[a] = JS_NewFloat64(ctx, d + 1); + } + break; + } + + case MACH_DEC: { + JSValue v = frame->slots[b]; + if (JS_IsInt(v)) { + int32_t i = JS_VALUE_GET_INT(v); + if (i == INT32_MIN) + frame->slots[a] = JS_NewFloat64(ctx, (double)i - 1); + else + frame->slots[a] = JS_NewInt32(ctx, i - 1); + } else { + double d; + JS_ToFloat64(ctx, &d, v); + frame->slots[a] = JS_NewFloat64(ctx, d - 1); + } + break; + } + + case MACH_LNOT: { + int bval = JS_ToBool(ctx, frame->slots[b]); + frame->slots[a] = JS_NewBool(ctx, !bval); + break; + } + + case MACH_BNOT: { + int32_t i; + JS_ToInt32(ctx, &i, frame->slots[b]); + frame->slots[a] = JS_NewInt32(ctx, ~i); + break; + } + + case MACH_GETFIELD: { + JSValue obj = frame->slots[b]; + JSValue key = code->cpool[c]; + /* Non-proxy functions (arity != 2) can't have properties read */ + 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 = JS_GetProperty(ctx, obj, key); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (JS_IsException(val)) goto disrupt; + frame->slots[a] = val; + break; + } + + case MACH_SETFIELD: { + /* R(A)[K(B)] = R(C) */ + JSValue obj = frame->slots[a]; + JSValue key = code->cpool[b]; + JSValue val = frame->slots[c]; + int ret = JS_SetProperty(ctx, obj, key, val); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (ret < 0) goto disrupt; + break; + } + + case MACH_GETINDEX: { + JSValue obj = frame->slots[b]; + JSValue idx = frame->slots[c]; + JSValue val; + 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[a] = val; + break; + } + + case MACH_SETINDEX: { + /* R(A)[R(B)] = R(C) */ + JSValue obj = frame->slots[a]; + JSValue idx = frame->slots[b]; + JSValue val = frame->slots[c]; + 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"); + ret = -1; + } else if (JS_IsRecord(obj) && !JS_IsText(idx) && !JS_IsRecord(idx)) { + JS_ThrowTypeError(ctx, "object key must be a string or object"); + ret = -1; + } else { + ret = JS_SetProperty(ctx, obj, idx, val); + } + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (ret < 0) goto disrupt; + break; + } + + case MACH_GETINTRINSIC: { + int bx = MACH_GET_Bx(instr); + JSValue key = code->cpool[bx]; + JSValue val = JS_GetProperty(ctx, ctx->global_obj, key); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (JS_IsNull(val)) { + int has = JS_HasProperty(ctx, ctx->global_obj, key); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (has <= 0) { + char buf[128]; + JS_KeyGetStr(ctx, buf, sizeof(buf), key); + JS_ThrowReferenceError(ctx, "'%s' is not defined", buf); + goto disrupt; + } + } + frame->slots[a] = val; + break; + } + + case MACH_GETENV: { + int bx = MACH_GET_Bx(instr); + JSValue key = code->cpool[bx]; + JSValue val = JS_GetProperty(ctx, env, key); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + frame->slots[a] = val; + break; + } + + case MACH_GETNAME: { + /* Runtime fallback: try env then global (should not appear in linked code) */ + int bx = MACH_GET_Bx(instr); + JSValue key = code->cpool[bx]; + JSValue val = JS_NULL; + if (!JS_IsNull(env)) { + val = JS_GetProperty(ctx, env, key); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + } + if (JS_IsNull(val) || JS_IsException(val)) { + val = JS_GetProperty(ctx, ctx->global_obj, key); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + } + frame->slots[a] = val; + break; + } + + case MACH_GETUP: { + /* R(A) = outer_frame[B].slots[C] — walk lexical scope chain */ + int depth = b; + JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); + JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.reg.outer_frame); + for (int d = 1; d < depth; d++) { + fn = JS_VALUE_GET_FUNCTION(target->function); + target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.reg.outer_frame); + } + frame->slots[a] = target->slots[c]; + break; + } + + case MACH_SETUP: { + /* outer_frame[B].slots[C] = R(A) — walk lexical scope chain */ + int depth = b; + JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); + JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.reg.outer_frame); + for (int d = 1; d < depth; d++) { + fn = JS_VALUE_GET_FUNCTION(target->function); + target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.reg.outer_frame); + } + target->slots[c] = frame->slots[a]; + break; + } + + case MACH_JMP: { + int offset = MACH_GET_sJ(instr); + pc = (uint32_t)((int32_t)pc + offset); + break; + } + + case MACH_JMPTRUE: { + int cond = JS_ToBool(ctx, frame->slots[a]); + if (cond) { + int offset = MACH_GET_sBx(instr); + pc = (uint32_t)((int32_t)pc + offset); + } + break; + } + + case MACH_JMPFALSE: { + int cond = JS_ToBool(ctx, frame->slots[a]); + if (!cond) { + int offset = MACH_GET_sBx(instr); + pc = (uint32_t)((int32_t)pc + offset); + } + break; + } + + case MACH_JMPNULL: { + if (JS_IsNull(frame->slots[a])) { + int offset = MACH_GET_sBx(instr); + pc = (uint32_t)((int32_t)pc + offset); + } + break; + } + + case MACH_CALL: { + /* Lua-style call: R(A)=func, B=nargs in R(A+1)..R(A+B), C=nresults */ + int base = a; + int nargs = b; + int nresults = c; + JSValue func_val = frame->slots[base]; + + if (!JS_IsFunction(func_val)) { + goto disrupt; + } + + JSFunction *fn = JS_VALUE_GET_FUNCTION(func_val); + if (fn->kind == JS_FUNC_KIND_C) { + /* C function: push args onto value stack (C-allocated, GC-scanned) */ + int vs_base = ctx->value_stack_top; + for (int i = 0; i < nargs; i++) + ctx->value_stack[vs_base + i] = frame->slots[base + 1 + i]; + 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_c_function(ctx, func_val, JS_NULL, 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; } + if (nresults > 0) frame->slots[base] = ret; + } else if (fn->kind == JS_FUNC_KIND_REGISTER) { + /* Register function: allocate frame, copy args, switch */ + JSCodeRegister *fn_code = fn->u.reg.code; + JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots); + if (!new_frame) { + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + goto disrupt; + } + /* Re-read pointers — GC may have moved them */ + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + func_val = frame->slots[base]; + fn = JS_VALUE_GET_FUNCTION(func_val); + new_frame->function = func_val; + new_frame->slots[0] = JS_NULL; /* this */ + for (int i = 0; i < nargs && i < fn_code->arity; i++) + new_frame->slots[1 + i] = frame->slots[base + 1 + i]; + + /* Save return info: pc in upper 16 bits, base reg or 0xFFFF (discard) in lower */ + int ret_slot = (nresults > 0) ? base : 0xFFFF; + frame->address = JS_NewInt32(ctx, (pc << 16) | ret_slot); + new_frame->caller = JS_MKPTR(frame); + + frame = new_frame; + frame_ref.val = JS_MKPTR(frame); + code = fn_code; + env = fn->u.reg.env_record; + pc = code->entry_point; + } else { + /* Other function kinds (bytecode) — push args onto value stack */ + int vs_base = ctx->value_stack_top; + for (int i = 0; i < nargs; i++) + ctx->value_stack[vs_base + i] = frame->slots[base + 1 + i]; + ctx->value_stack_top = vs_base + nargs; + JSValue ret = JS_CallInternal(ctx, func_val, JS_NULL, nargs, &ctx->value_stack[vs_base], 0); + ctx->value_stack_top = vs_base; + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (JS_IsException(ret)) { goto disrupt; } + if (nresults > 0) frame->slots[base] = ret; + } + break; + } + + case MACH_CALLMETHOD: { + /* Method call: R(A)=obj, B=nargs in R(A+2)..R(A+1+B), C=cpool key index + Result stored in R(A). C=0xFF means key is in R(A+1). + If obj is a function (proxy): call obj(key_str, [args...]) + Else (record): get property, call property(obj_as_this, args...) */ + int base = a; + int nargs = b; + JSGCRef key_ref; + JS_PushGCRef(ctx, &key_ref); + key_ref.val = (c == 0xFF) ? frame->slots[base + 1] : code->cpool[c]; + + if (JS_IsFunction(frame->slots[base]) && JS_IsText(key_ref.val)) { + /* Proxy call: obj(name, [args...]) */ + JSValue arr = JS_NewArray(ctx); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (JS_IsException(arr)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; } + frame->slots[base + 1] = arr; /* protect from GC in temp slot */ + for (int i = 0; i < nargs; i++) { + JS_SetPropertyUint32(ctx, frame->slots[base + 1], i, frame->slots[base + 2 + i]); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + } + /* Push proxy args onto value stack; re-read obj since GC may have moved it */ + int vs_base = ctx->value_stack_top; + ctx->value_stack[vs_base] = key_ref.val; + ctx->value_stack[vs_base + 1] = frame->slots[base + 1]; /* the array */ + 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[base], 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)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; } + frame->slots[base] = ret; + } else if (JS_IsFunction(frame->slots[base])) { + /* Non-proxy function with non-text key: disrupt */ + JS_ThrowTypeError(ctx, "cannot use bracket notation on non-proxy function"); + JS_PopGCRef(ctx, &key_ref); + goto disrupt; + } else { + /* Record method call: get property, call with this=obj */ + JSValue method = JS_GetProperty(ctx, frame->slots[base], key_ref.val); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (JS_IsException(method)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; } + if (!JS_IsFunction(method)) { + frame->slots[base] = JS_NULL; + JS_PopGCRef(ctx, &key_ref); + break; + } + JSFunction *fn = JS_VALUE_GET_FUNCTION(method); + if (fn->kind == JS_FUNC_KIND_C) { + int vs_base = ctx->value_stack_top; + for (int i = 0; i < nargs; i++) + ctx->value_stack[vs_base + i] = frame->slots[base + 2 + i]; + 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_c_function(ctx, method, frame->slots[base], 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)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; } + frame->slots[base] = ret; + } else if (fn->kind == JS_FUNC_KIND_REGISTER) { + JSCodeRegister *fn_code = fn->u.reg.code; + JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots); + if (!new_frame) { + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + JS_PopGCRef(ctx, &key_ref); + goto disrupt; + } + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + method = JS_GetProperty(ctx, frame->slots[base], key_ref.val); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + fn = JS_VALUE_GET_FUNCTION(method); + new_frame->function = method; + new_frame->slots[0] = frame->slots[base]; /* this */ + for (int i = 0; i < nargs && i < fn_code->arity; i++) + new_frame->slots[1 + i] = frame->slots[base + 2 + i]; + int ret_slot = base; + frame->address = JS_NewInt32(ctx, (pc << 16) | ret_slot); + new_frame->caller = JS_MKPTR(frame); + frame = new_frame; + frame_ref.val = JS_MKPTR(frame); + code = fn_code; + env = fn->u.reg.env_record; + pc = code->entry_point; + } else { + /* Bytecode or other function */ + int vs_base = ctx->value_stack_top; + for (int i = 0; i < nargs; i++) + ctx->value_stack[vs_base + i] = frame->slots[base + 2 + i]; + ctx->value_stack_top = vs_base + nargs; + JSValue ret = JS_CallInternal(ctx, method, frame->slots[base], nargs, &ctx->value_stack[vs_base], 0); + ctx->value_stack_top = vs_base; + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (JS_IsException(ret)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; } + frame->slots[base] = ret; + } + } + JS_PopGCRef(ctx, &key_ref); + break; + } + + case MACH_RETURN: + result = frame->slots[a]; + if (JS_IsNull(frame->caller)) goto done; + { + JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); + frame->caller = JS_NULL; + frame = caller; + frame_ref.val = JS_MKPTR(frame); + int ret_info = JS_VALUE_GET_INT(frame->address); + JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); + code = fn->u.reg.code; + env = fn->u.reg.env_record; + pc = ret_info >> 16; + int ret_slot = ret_info & 0xFFFF; + if (ret_slot != 0xFFFF) frame->slots[ret_slot] = result; + } + break; + + case MACH_RETNIL: + result = JS_NULL; + if (JS_IsNull(frame->caller)) goto done; + { + JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); + frame->caller = JS_NULL; + frame = caller; + frame_ref.val = JS_MKPTR(frame); + int ret_info = JS_VALUE_GET_INT(frame->address); + JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); + code = fn->u.reg.code; + env = fn->u.reg.env_record; + pc = ret_info >> 16; + int ret_slot = ret_info & 0xFFFF; + if (ret_slot != 0xFFFF) frame->slots[ret_slot] = result; + } + break; + + case MACH_NEWOBJECT: { + JSValue obj = JS_NewObject(ctx); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (JS_IsException(obj)) { goto disrupt; } + frame->slots[a] = obj; + break; + } + + case MACH_NEWARRAY: { + int count = b; + JSValue arr = JS_NewArray(ctx); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (JS_IsException(arr)) { goto disrupt; } + /* Store array in dest immediately so GC can track it */ + frame->slots[a] = arr; + for (int i = 0; i < count; i++) { + JS_SetPropertyUint32(ctx, frame->slots[a], i, frame->slots[a + 1 + i]); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + } + break; + } + + case MACH_CLOSURE: { + int bx = MACH_GET_Bx(instr); + if ((uint32_t)bx < code->func_count) { + JSCodeRegister *fn_code = code->functions[bx]; + JSValue fn_val = js_new_register_function(ctx, fn_code, env, frame_ref.val); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + frame->slots[a] = fn_val; + } else { + frame->slots[a] = JS_NULL; + } + break; + } + + case MACH_PUSH: { + /* push R(B) onto array R(A) */ + JSValue arr = frame->slots[a]; + JSValue val = frame->slots[b]; + if (!JS_IsArray(arr)) goto disrupt; + JSGCRef arr_gc; + JS_PushGCRef(ctx, &arr_gc); + arr_gc.val = arr; + int rc = JS_ArrayPush(ctx, &arr_gc.val, val); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + JS_PopGCRef(ctx, &arr_gc); + if (rc < 0) goto disrupt; + if (arr_gc.val != arr) frame->slots[a] = arr_gc.val; + break; + } + + case MACH_POP: { + /* R(A) = pop last element from array R(B) */ + JSValue arr = frame->slots[b]; + if (!JS_IsArray(arr)) goto disrupt; + JSValue val = JS_ArrayPop(ctx, arr); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (JS_IsException(val)) goto disrupt; + frame->slots[a] = val; + break; + } + + case MACH_DELETE: { + JSValue obj = frame->slots[b]; + JSValue key = code->cpool[c]; + int ret = JS_DeleteProperty(ctx, obj, key); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (ret < 0) goto disrupt; + frame->slots[a] = JS_NewBool(ctx, ret >= 0); + break; + } + + case MACH_DELETEINDEX: { + JSValue obj = frame->slots[b]; + JSValue key = frame->slots[c]; + int ret = JS_DeleteProperty(ctx, obj, key); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (ret < 0) goto disrupt; + frame->slots[a] = JS_NewBool(ctx, ret >= 0); + break; + } + + case MACH_HASPROP: { + JSValue obj = frame->slots[b]; + JSValue key = frame->slots[c]; + int has = JS_HasProperty(ctx, obj, key); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + frame->slots[a] = JS_NewBool(ctx, has > 0); + break; + } + + case MACH_REGEXP: { + JSValue argv[2]; + argv[0] = code->cpool[b]; /* pattern */ + argv[1] = code->cpool[c]; /* flags */ + JSValue re = js_regexp_constructor(ctx, JS_NULL, 2, argv); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + if (JS_IsException(re)) goto disrupt; + frame->slots[a] = re; + break; + } + + case MACH_THROW: + goto disrupt; + + default: + result = JS_ThrowInternalError(ctx, "unknown register VM opcode %d", 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); + code = fn->u.reg.code; + /* Only enter handler if we're not already inside it */ + if (code->disruption_pc > 0 && frame_pc < code->disruption_pc) { + env = fn->u.reg.env_record; + pc = 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: +#ifdef HAVE_ASAN + __asan_js_ctx = NULL; +#endif + ctx->reg_current_frame = JS_NULL; + 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; +} + +/* ============================================================ + MCODE Generator — AST to MCODE JSON (string-based IR) + ============================================================ */ + + +/* ============================================================ + MACH Public API + ============================================================ */ + +/* Print a single constant pool value for dump output */ +static void dump_cpool_value(JSContext *ctx, JSValue val) { + uint32_t tag = JS_VALUE_GET_TAG(val); + + if (JS_IsPtr(val)) { + void *ptr = JS_VALUE_GET_PTR(val); + objhdr_t hdr = *(objhdr_t *)ptr; + uint8_t mist_type = objhdr_type(hdr); + if (mist_type == OBJ_TEXT) { + const char *str = JS_ToCString(ctx, val); + if (str) { + printf("\"%s\"", str); + JS_FreeCString(ctx, str); + } else { + printf(""); + } + return; + } + printf("", mist_type); + return; + } + + switch (tag) { + case JS_TAG_INT: + printf("%d", JS_VALUE_GET_INT(val)); + break; + case JS_TAG_BOOL: + printf("%s", JS_VALUE_GET_BOOL(val) ? "true" : "false"); + break; + case JS_TAG_NULL: + printf("null"); + break; + case JS_TAG_SHORT_FLOAT: + printf("%g", JS_VALUE_GET_FLOAT64(val)); + break; + case JS_TAG_STRING_IMM: { + const char *str = JS_ToCString(ctx, val); + if (str) { + printf("\"%s\"", str); + JS_FreeCString(ctx, str); + } else { + printf(""); + } + break; + } + default: + printf("", tag); + break; + } +} + +/* (labels removed in new format) */ + +/* Internal helper to dump JSCodeRegister (32-bit instruction format) */ +static void dump_register_code(JSContext *ctx, JSCodeRegister *code, int indent) { + char pad[64]; + int pad_len = indent * 2; + if (pad_len > 60) pad_len = 60; + memset(pad, ' ', pad_len); + pad[pad_len] = '\0'; + + /* Function header */ + const char *name = ""; + if (!JS_IsNull(code->name)) { + const char *n = JS_ToCString(ctx, code->name); + if (n) name = n; + } + printf("%sFunction: %s\n", pad, name); + printf("%s Arity: %d, Slots: %d, Close: %d\n", pad, + code->arity, code->nr_slots, code->nr_close_slots); + if (!JS_IsNull(code->name)) { + JS_FreeCString(ctx, name); + } + if (code->disruption_pc > 0) + printf("%s Disruption handler at: %d\n", pad, code->disruption_pc); + + /* Constant pool */ + if (code->cpool_count > 0) { + printf("%s\n%sConstant Pool (%d entries):\n", pad, pad, code->cpool_count); + for (uint32_t i = 0; i < code->cpool_count; i++) { + printf("%s [%d]: ", pad, i); + dump_cpool_value(ctx, code->cpool[i]); + printf("\n"); + } + } + + /* Instructions */ + printf("%s\n%sInstructions (%d):\n", pad, pad, code->instr_count); + for (uint32_t i = 0; i < code->instr_count; i++) { + MachInstr32 instr = code->instructions[i]; + int op = MACH_GET_OP(instr); + int a = MACH_GET_A(instr); + int b = MACH_GET_B(instr); + int c = MACH_GET_C(instr); + const char *op_name = (op < MACH_OP_COUNT) ? mach_opcode_names[op] : "???"; + if (!op_name) op_name = "???"; + + printf("%s %3d: %-14s ", pad, i, op_name); + + switch (op) { + /* No operands */ + case MACH_NOP: + case MACH_RETNIL: + break; + + /* A only */ + case MACH_LOADNULL: + case MACH_LOADTRUE: + case MACH_LOADFALSE: + printf("r%d", a); + break; + + /* ABx: load constant */ + case MACH_LOADK: { + int bx = MACH_GET_Bx(instr); + printf("r%d, #%d", a, bx); + if (bx >= 0 && (uint32_t)bx < code->cpool_count) { + printf(" ; "); + dump_cpool_value(ctx, code->cpool[bx]); + } + break; + } + + /* AsBx: load small int */ + case MACH_LOADI: + printf("r%d, %d", a, MACH_GET_sBx(instr)); + break; + + /* A, B: move, unary ops */ + case MACH_MOVE: + case MACH_NEG: + case MACH_INC: + case MACH_DEC: + case MACH_LNOT: + case MACH_BNOT: + printf("r%d, r%d", a, b); + break; + + /* A, B, C: arithmetic, comparison, bitwise */ + case MACH_ADD: case MACH_SUB: case MACH_MUL: case MACH_DIV: + case MACH_MOD: case MACH_POW: + case MACH_EQ: case MACH_NEQ: case MACH_LT: case MACH_LE: + case MACH_GT: case MACH_GE: + case MACH_BAND: case MACH_BOR: case MACH_BXOR: + case MACH_SHL: case MACH_SHR: case MACH_USHR: + printf("r%d, r%d, r%d", a, b, c); + break; + + case MACH_EQ_TOL: case MACH_NEQ_TOL: + printf("r%d, r%d, %d", a, b, c); + break; + + /* Property access */ + case MACH_GETFIELD: + printf("r%d, r%d, #%d", a, b, c); + if ((uint32_t)c < code->cpool_count) { + printf(" ; "); + dump_cpool_value(ctx, code->cpool[c]); + } + break; + case MACH_SETFIELD: + printf("r%d, #%d, r%d", a, b, c); + if ((uint32_t)b < code->cpool_count) { + printf(" ; "); + dump_cpool_value(ctx, code->cpool[b]); + } + break; + case MACH_GETINDEX: + case MACH_SETINDEX: + printf("r%d, r%d, r%d", a, b, c); + break; + + /* ABx: name/intrinsic/env access */ + case MACH_GETNAME: + case MACH_GETINTRINSIC: + case MACH_GETENV: { + int bx = MACH_GET_Bx(instr); + printf("r%d, #%d", a, bx); + if ((uint32_t)bx < code->cpool_count) { + printf(" ; "); + dump_cpool_value(ctx, code->cpool[bx]); + } + break; + } + + /* Closure access */ + case MACH_GETUP: + case MACH_SETUP: + printf("r%d, depth=%d, slot=%d", a, b, c); + break; + + /* isJ: unconditional jump */ + case MACH_JMP: { + int offset = MACH_GET_sJ(instr); + printf("%+d", offset); + printf(" ; -> %d", (int)i + 1 + offset); + break; + } + + /* iAsBx: conditional jumps */ + case MACH_JMPTRUE: + case MACH_JMPFALSE: + case MACH_JMPNULL: { + int offset = MACH_GET_sBx(instr); + printf("r%d, %+d", a, offset); + printf(" ; -> %d", (int)i + 1 + offset); + break; + } + + /* Call */ + case MACH_CALL: + printf("r%d, %d, %d", a, b, c); + break; + + /* Return / throw */ + case MACH_RETURN: + case MACH_THROW: + printf("r%d", a); + break; + + /* Object/array creation */ + case MACH_NEWOBJECT: + printf("r%d", a); + break; + case MACH_NEWARRAY: + printf("r%d, %d", a, b); + break; + + /* Push/Pop */ + case MACH_PUSH: + printf("r%d, r%d", a, b); + break; + case MACH_POP: + printf("r%d, r%d", a, b); + break; + + /* Closure */ + case MACH_CLOSURE: { + int bx = MACH_GET_Bx(instr); + printf("r%d, func#%d", a, bx); + break; + } + + default: + printf("0x%08x", instr); + break; + } + printf("\n"); + } + + /* Nested functions */ + if (code->func_count > 0) { + printf("%s\n%sNested Functions (%d):\n", pad, pad, code->func_count); + for (uint32_t i = 0; i < code->func_count; i++) { + printf("%s [%d]:\n", pad, i); + if (code->functions[i]) { + dump_register_code(ctx, code->functions[i], indent + 2); + } else { + printf("%s \n", pad); + } + } + } +} + +/* Dump MACH bytecode to stdout for debugging. Takes AST cJSON tree. */ +void JS_DumpMachTree(JSContext *ctx, cJSON *ast, JSValue env) { + MachCode *mc = JS_CompileMachTree(ast); + if (!mc) { + printf("=== MACH Bytecode ===\nFailed to compile\n=== End MACH Bytecode ===\n"); + return; + } + + JSCodeRegister *code = JS_LoadMachCode(ctx, mc, env); + printf("=== MACH Bytecode ===\n"); + dump_register_code(ctx, code, 0); + printf("=== End MACH Bytecode ===\n"); +} + +void JS_DumpMach(JSContext *ctx, const char *ast_json, JSValue env) { + cJSON *ast = cJSON_Parse(ast_json); + if (!ast) { + printf("=== MACH Bytecode ===\nFailed to parse\n=== End MACH Bytecode ===\n"); + return; + } + JS_DumpMachTree(ctx, ast, env); + cJSON_Delete(ast); +} + +/* Compile and execute MACH bytecode. Takes AST cJSON tree. */ +JSValue JS_RunMachTree(JSContext *ctx, cJSON *ast, JSValue env) { + MachCode *mc = JS_CompileMachTree(ast); + if (!mc) { + return JS_ThrowSyntaxError(ctx, "failed to compile AST to MACH bytecode"); + } + + JSCodeRegister *code = JS_LoadMachCode(ctx, mc, env); + JSValue result = JS_CallRegisterVM(ctx, code, ctx->global_obj, 0, NULL, env, JS_NULL); + return result; +} + +JSValue JS_RunMach(JSContext *ctx, const char *ast_json, JSValue env) { + cJSON *ast = cJSON_Parse(ast_json); + if (!ast) { + return JS_ThrowSyntaxError(ctx, "failed to parse AST JSON"); + } + JSValue result = JS_RunMachTree(ctx, ast, env); + cJSON_Delete(ast); + return result; +} diff --git a/source/mcode.c b/source/mcode.c new file mode 100644 index 00000000..ab82a984 --- /dev/null +++ b/source/mcode.c @@ -0,0 +1,3461 @@ +/* + * QuickJS Javascript Engine + * + * Copyright (c) 2017-2025 Fabrice Bellard + * Copyright (c) 2017-2025 Charlie Gordon + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include "quickjs-internal.h" + +typedef struct MachGenState { + cJSON *instructions; + cJSON *data; + cJSON *functions; + + int this_slot; /* always 0 */ + int nr_args; + int nr_close_slots; /* captured from parents */ + int nr_local_slots; + int next_temp_slot; + int max_slot; + + MachVarInfo *vars; + int var_count; + int var_capacity; + + int label_counter; + int func_counter; + + struct MachGenState *parent; + const char *loop_break; + const char *loop_continue; + + int is_arrow; + + /* AST semantic annotations */ + int function_nr; + cJSON *scopes; + + /* Intrinsic (global) name cache */ + struct { const char *name; int slot; } intrinsic_cache[64]; + int intrinsic_count; + + /* Error tracking */ + cJSON *errors; + int has_error; + + /* Line tracking for debug info */ + int cur_line, cur_col; + const char *filename; +} MachGenState; + +static int mach_gen_expr (MachGenState *s, cJSON *expr, int target); +static void mach_gen_statement (MachGenState *s, cJSON *stmt); +static int mach_gen_alloc_slot (MachGenState *s); + +/* Look up an intrinsic in the cache, return slot or -1 */ +static int mach_gen_find_intrinsic (MachGenState *s, const char *name) { + for (int i = 0; i < s->intrinsic_count; i++) { + if (strcmp (s->intrinsic_cache[i].name, name) == 0) + return s->intrinsic_cache[i].slot; + } + return -1; +} + +/* Pre-load intrinsics from the AST intrinsics array */ +static void mach_gen_load_intrinsics (MachGenState *s, cJSON *intrinsics) { + if (!intrinsics) return; + cJSON *item; + cJSON_ArrayForEach (item, intrinsics) { + const char *name = cJSON_GetStringValue (item); + if (!name || s->intrinsic_count >= 64) continue; + if (mach_gen_find_intrinsic (s, name) >= 0) continue; + int slot = mach_gen_alloc_slot (s); + cJSON *instr = cJSON_CreateArray (); + cJSON_AddItemToArray (instr, cJSON_CreateString ("access")); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (slot)); + cJSON *lit = cJSON_CreateObject (); + cJSON_AddStringToObject (lit, "kind", "name"); + cJSON_AddStringToObject (lit, "name", name); + cJSON_AddStringToObject (lit, "make", "intrinsic"); + cJSON_AddItemToArray (instr, lit); + cJSON_AddItemToArray (s->instructions, instr); + s->intrinsic_cache[s->intrinsic_count].name = name; + s->intrinsic_cache[s->intrinsic_count].slot = slot; + s->intrinsic_count++; + } +} + +/* Allocate a temporary slot */ +static int mach_gen_alloc_slot (MachGenState *s) { + int slot = s->next_temp_slot++; + if (slot > s->max_slot) s->max_slot = slot; + return slot; +} + +/* Add a variable to the tracking table */ +static void mach_gen_add_var (MachGenState *s, const char *name, int slot, int is_const) { + if (s->var_count >= s->var_capacity) { + int new_cap = s->var_capacity ? s->var_capacity * 2 : 16; + s->vars = sys_realloc (s->vars, new_cap * sizeof(MachVarInfo)); + s->var_capacity = new_cap; + } + MachVarInfo *v = &s->vars[s->var_count++]; + v->name = sys_malloc (strlen (name) + 1); + strcpy (v->name, name); + v->slot = slot; + v->is_const = is_const; + v->is_closure = 0; +} + +/* Find a variable in the current scope only */ +static int mach_gen_find_var (MachGenState *s, const char *name) { + for (int i = 0; i < s->var_count; i++) { + if (strcmp (s->vars[i].name, name) == 0) { + return s->vars[i].slot; + } + } + return -1; +} + +/* Add an error to the state */ +static void mach_gen_error (MachGenState *s, cJSON *node, const char *fmt, ...) { + va_list ap; + char buf[256]; + + va_start (ap, fmt); + vsnprintf (buf, sizeof(buf), fmt, ap); + va_end (ap); + + cJSON *err = cJSON_CreateObject (); + cJSON_AddStringToObject (err, "message", buf); + + cJSON *line_obj = cJSON_GetObjectItemCaseSensitive (node, "from_row"); + cJSON *col_obj = cJSON_GetObjectItemCaseSensitive (node, "from_column"); + if (line_obj) + cJSON_AddNumberToObject (err, "line", cJSON_GetNumberValue (line_obj) + 1); + if (col_obj) + cJSON_AddNumberToObject (err, "column", cJSON_GetNumberValue (col_obj) + 1); + + if (!s->errors) + s->errors = cJSON_CreateArray (); + cJSON_AddItemToArray (s->errors, err); + s->has_error = 1; +} + +/* Scan AST scope record for variable declarations. + Variables are direct keys on the scope object with a "make" field. */ +static void mach_gen_scan_scope (MachGenState *s) { + cJSON *scope = mach_find_scope_record (s->scopes, s->function_nr); + if (!scope) return; + cJSON *v; + cJSON_ArrayForEach (v, scope) { + const char *name = v->string; + if (!name || strcmp (name, "function_nr") == 0 || strcmp (name, "nr_close_slots") == 0) continue; + const char *make = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (v, "make")); + if (!make || strcmp (make, "input") == 0) continue; + if (mach_gen_find_var (s, name) < 0) { + int is_const = (strcmp (make, "def") == 0 || strcmp (make, "function") == 0); + int slot = 1 + s->nr_args + s->nr_local_slots++; + mach_gen_add_var (s, name, slot, is_const); + cJSON *closure_flag = cJSON_GetObjectItemCaseSensitive (v, "closure"); + s->vars[s->var_count - 1].is_closure = (closure_flag && cJSON_IsTrue (closure_flag)); + } + } +} + +static char *mach_gen_label (MachGenState *s, const char *prefix) { + char *label = sys_malloc (64); + snprintf (label, 64, "%s_%d", prefix, s->label_counter++); + return label; +} + +static void mach_gen_emit_label (MachGenState *s, const char *label) { + cJSON *item = cJSON_CreateString (label); + cJSON_AddItemToArray (s->instructions, item); +} + +static void mach_gen_set_pos (MachGenState *s, cJSON *node) { + cJSON *r = cJSON_GetObjectItemCaseSensitive (node, "from_row"); + cJSON *c = cJSON_GetObjectItemCaseSensitive (node, "from_column"); + if (r) s->cur_line = (int)r->valuedouble + 1; + if (c) s->cur_col = (int)c->valuedouble + 1; +} + +static void mach_gen_add_instr (MachGenState *s, cJSON *instr) { + cJSON_AddItemToArray (instr, cJSON_CreateNumber (s->cur_line)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (s->cur_col)); + cJSON_AddItemToArray (s->instructions, instr); +} + +static void mach_gen_emit_0 (MachGenState *s, const char *op) { + cJSON *instr = cJSON_CreateArray (); + cJSON_AddItemToArray (instr, cJSON_CreateString (op)); + mach_gen_add_instr (s, instr); +} + +static void mach_gen_emit_1 (MachGenState *s, const char *op, int a) { + cJSON *instr = cJSON_CreateArray (); + cJSON_AddItemToArray (instr, cJSON_CreateString (op)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (a)); + mach_gen_add_instr (s, instr); +} + +static void mach_gen_emit_2 (MachGenState *s, const char *op, int a, int b) { + cJSON *instr = cJSON_CreateArray (); + cJSON_AddItemToArray (instr, cJSON_CreateString (op)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (a)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (b)); + mach_gen_add_instr (s, instr); +} + +static void mach_gen_emit_3 (MachGenState *s, const char *op, int a, int b, int c) { + cJSON *instr = cJSON_CreateArray (); + cJSON_AddItemToArray (instr, cJSON_CreateString (op)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (a)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (b)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (c)); + mach_gen_add_instr (s, instr); +} + +static void mach_gen_emit_4 (MachGenState *s, const char *op, int a, int b, int c, int d) { + cJSON *instr = cJSON_CreateArray (); + cJSON_AddItemToArray (instr, cJSON_CreateString (op)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (a)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (b)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (c)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (d)); + mach_gen_add_instr (s, instr); +} + +static void mach_gen_emit_const_num (MachGenState *s, int dest, double val) { + cJSON *instr = cJSON_CreateArray (); + cJSON_AddItemToArray (instr, cJSON_CreateString ("access")); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (val)); + mach_gen_add_instr (s, instr); +} + +static void mach_gen_emit_const_str (MachGenState *s, int dest, const char *val) { + cJSON *instr = cJSON_CreateArray (); + cJSON_AddItemToArray (instr, cJSON_CreateString ("access")); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); + cJSON_AddItemToArray (instr, cJSON_CreateString (val ? val : "")); + mach_gen_add_instr (s, instr); +} + +static void mach_gen_emit_const_bool (MachGenState *s, int dest, int val) { + mach_gen_emit_1 (s, val ? "true" : "false", dest); +} + +static void mach_gen_emit_const_null (MachGenState *s, int dest) { + mach_gen_emit_1 (s, "null", dest); +} + +static void mach_gen_emit_jump (MachGenState *s, const char *label) { + cJSON *instr = cJSON_CreateArray (); + cJSON_AddItemToArray (instr, cJSON_CreateString ("jump")); + cJSON_AddItemToArray (instr, cJSON_CreateString (label)); + mach_gen_add_instr (s, instr); +} + +static void mach_gen_emit_jump_cond (MachGenState *s, const char *op, int slot, const char *label) { + cJSON *instr = cJSON_CreateArray (); + cJSON_AddItemToArray (instr, cJSON_CreateString (op)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (slot)); + cJSON_AddItemToArray (instr, cJSON_CreateString (label)); + mach_gen_add_instr (s, instr); +} + +static void mach_gen_emit_get_prop (MachGenState *s, int dest, int obj, const char *prop) { + cJSON *instr = cJSON_CreateArray (); + cJSON_AddItemToArray (instr, cJSON_CreateString ("load")); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj)); + cJSON_AddItemToArray (instr, cJSON_CreateString (prop)); + mach_gen_add_instr (s, instr); +} + +static void mach_gen_emit_set_prop (MachGenState *s, int obj, const char *prop, int val) { + cJSON *instr = cJSON_CreateArray (); + cJSON_AddItemToArray (instr, cJSON_CreateString ("store")); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (val)); + cJSON_AddItemToArray (instr, cJSON_CreateString (prop)); + mach_gen_add_instr (s, instr); +} + +static void mach_gen_emit_get_elem (MachGenState *s, int dest, int obj, int idx) { + mach_gen_emit_3 (s, "load", dest, obj, idx); +} + +static void mach_gen_emit_set_elem (MachGenState *s, int obj, int idx, int val) { + mach_gen_emit_3 (s, "store", obj, val, idx); +} + +static void mach_gen_emit_call (MachGenState *s, int dest, int func_slot, cJSON *args) { + int argc = cJSON_GetArraySize (args); + int frame_slot = mach_gen_alloc_slot (s); + mach_gen_emit_3 (s, "frame", frame_slot, func_slot, argc); + int null_slot = mach_gen_alloc_slot (s); + mach_gen_emit_1 (s, "null", null_slot); + mach_gen_emit_3 (s, "setarg", frame_slot, 0, null_slot); + int arg_idx = 1; + cJSON *arg; + cJSON_ArrayForEach (arg, args) { + mach_gen_emit_3 (s, "setarg", frame_slot, arg_idx++, arg->valueint); + } + mach_gen_emit_2 (s, "invoke", frame_slot, dest); +} + +static void mach_gen_emit_call_method (MachGenState *s, int dest, int obj, const char *prop, cJSON *args) { + /* Emit a single callmethod instruction: + ["callmethod", dest, obj_reg, "method_name", arg_reg0, arg_reg1, ...] */ + cJSON *instr = cJSON_CreateArray (); + cJSON_AddItemToArray (instr, cJSON_CreateString ("callmethod")); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj)); + cJSON_AddItemToArray (instr, cJSON_CreateString (prop)); + cJSON *arg; + cJSON_ArrayForEach (arg, args) { + cJSON_AddItemToArray (instr, cJSON_CreateNumber (arg->valueint)); + } + mach_gen_add_instr (s, instr); +} + +static void mach_gen_emit_call_method_dyn (MachGenState *s, int dest, int obj, int key_reg, cJSON *args) { + /* Emit a dynamic callmethod instruction: + ["callmethod_dyn", dest, obj_reg, key_reg, arg_reg0, arg_reg1, ...] */ + cJSON *instr = cJSON_CreateArray (); + cJSON_AddItemToArray (instr, cJSON_CreateString ("callmethod_dyn")); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj)); + cJSON_AddItemToArray (instr, cJSON_CreateNumber (key_reg)); + cJSON *arg; + cJSON_ArrayForEach (arg, args) { + cJSON_AddItemToArray (instr, cJSON_CreateNumber (arg->valueint)); + } + mach_gen_add_instr (s, instr); +} + +static void mach_gen_emit_go_call (MachGenState *s, int func_slot, cJSON *args) { + int argc = cJSON_GetArraySize (args); + int frame_slot = mach_gen_alloc_slot (s); + mach_gen_emit_3 (s, "goframe", frame_slot, func_slot, argc); + int null_slot = mach_gen_alloc_slot (s); + mach_gen_emit_1 (s, "null", null_slot); + mach_gen_emit_3 (s, "setarg", frame_slot, 0, null_slot); + int arg_idx = 1; + cJSON *arg; + cJSON_ArrayForEach (arg, args) { + mach_gen_emit_3 (s, "setarg", frame_slot, arg_idx++, arg->valueint); + } + mach_gen_emit_1 (s, "goinvoke", frame_slot); +} + +static void mach_gen_emit_go_call_method (MachGenState *s, int obj, const char *prop, cJSON *args) { + int func_slot = mach_gen_alloc_slot (s); + mach_gen_emit_get_prop (s, func_slot, obj, prop); + int argc = cJSON_GetArraySize (args); + int frame_slot = mach_gen_alloc_slot (s); + mach_gen_emit_3 (s, "goframe", frame_slot, func_slot, argc); + mach_gen_emit_3 (s, "setarg", frame_slot, 0, obj); + int arg_idx = 1; + cJSON *arg; + cJSON_ArrayForEach (arg, args) { + mach_gen_emit_3 (s, "setarg", frame_slot, arg_idx++, arg->valueint); + } + mach_gen_emit_1 (s, "goinvoke", frame_slot); +} + +static const char *functino_to_mcode_op (const char *name) { + if (strcmp (name, "+!") == 0) return "add"; + if (strcmp (name, "-!") == 0) return "subtract"; + if (strcmp (name, "*!") == 0) return "multiply"; + if (strcmp (name, "/!") == 0) return "divide"; + if (strcmp (name, "%!") == 0) return "modulo"; + if (strcmp (name, "**!") == 0) return "pow"; + if (strcmp (name, "!") == 0) return "gt"; + if (strcmp (name, "<=!") == 0) return "le"; + if (strcmp (name, ">=!") == 0) return "ge"; + if (strcmp (name, "=!") == 0) return "eq"; + if (strcmp (name, "!=!") == 0) return "ne"; + if (strcmp (name, "&!") == 0) return "bitand"; + if (strcmp (name, "|!") == 0) return "bitor"; + if (strcmp (name, "^!") == 0) return "bitxor"; + if (strcmp (name, "<>!") == 0) return "shr"; + if (strcmp (name, ">>>!") == 0) return "ushr"; + if (strcmp (name, "&&!") == 0) return "and"; + if (strcmp (name, "||!") == 0) return "or"; + if (strcmp (name, "~!") == 0) return "bitnot"; + if (strcmp (name, "[]!") == 0) return "load"; + return NULL; +} + +static const char *mach_gen_binop_to_string (const char *kind) { + if (strcmp (kind, "+") == 0) return "add"; + if (strcmp (kind, "-") == 0) return "subtract"; + if (strcmp (kind, "*") == 0) return "multiply"; + if (strcmp (kind, "/") == 0) return "divide"; + if (strcmp (kind, "%") == 0) return "modulo"; + if (strcmp (kind, "&") == 0) return "bitand"; + if (strcmp (kind, "|") == 0) return "bitor"; + if (strcmp (kind, "^") == 0) return "bitxor"; + if (strcmp (kind, "<<") == 0) return "shl"; + if (strcmp (kind, ">>") == 0) return "shr"; + if (strcmp (kind, ">>>") == 0) return "ushr"; + if (strcmp (kind, "==") == 0 || strcmp (kind, "===") == 0) return "eq"; + if (strcmp (kind, "!=") == 0 || strcmp (kind, "!==") == 0) return "ne"; + if (strcmp (kind, "<") == 0) return "lt"; + if (strcmp (kind, "<=") == 0) return "le"; + if (strcmp (kind, ">") == 0) return "gt"; + if (strcmp (kind, ">=") == 0) return "ge"; + if (strcmp (kind, "**") == 0) return "pow"; + if (strcmp (kind, "in") == 0) return "in"; + return "add"; +} + +static int mach_gen_binary (MachGenState *s, cJSON *node) { + const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (node, "kind")); + cJSON *left = cJSON_GetObjectItemCaseSensitive (node, "left"); + cJSON *right = cJSON_GetObjectItemCaseSensitive (node, "right"); + + if (strcmp (kind, "&&") == 0) { + char *end_label = mach_gen_label (s, "and_end"); + int left_slot = mach_gen_expr (s, left, -1); + int dest = mach_gen_alloc_slot (s); + mach_gen_emit_2 (s, "move", dest, left_slot); + mach_gen_emit_jump_cond (s, "jump_false", dest, end_label); + int right_slot = mach_gen_expr (s, right, -1); + mach_gen_emit_2 (s, "move", dest, right_slot); + mach_gen_emit_label (s, end_label); + sys_free (end_label); + return dest; + } + + if (strcmp (kind, "||") == 0) { + char *end_label = mach_gen_label (s, "or_end"); + int left_slot = mach_gen_expr (s, left, -1); + int dest = mach_gen_alloc_slot (s); + mach_gen_emit_2 (s, "move", dest, left_slot); + mach_gen_emit_jump_cond (s, "jump_true", dest, end_label); + int right_slot = mach_gen_expr (s, right, -1); + mach_gen_emit_2 (s, "move", dest, right_slot); + mach_gen_emit_label (s, end_label); + sys_free (end_label); + return dest; + } + + if (strcmp (kind, "??") == 0) { + char *end_label = mach_gen_label (s, "nullish_end"); + int left_slot = mach_gen_expr (s, left, -1); + int dest = mach_gen_alloc_slot (s); + mach_gen_emit_2 (s, "move", dest, left_slot); + mach_gen_emit_jump_cond (s, "jump_not_null", dest, end_label); + int right_slot = mach_gen_expr (s, right, -1); + mach_gen_emit_2 (s, "move", dest, right_slot); + mach_gen_emit_label (s, end_label); + sys_free (end_label); + return dest; + } + + /* Comma operator: evaluate left (discard), evaluate right (keep) */ + if (strcmp (kind, ",") == 0) { + mach_gen_expr (s, left, -1); + return mach_gen_expr (s, right, -1); + } + + int left_slot = mach_gen_expr (s, left, -1); + int right_slot = mach_gen_expr (s, right, -1); + int dest = mach_gen_alloc_slot (s); + const char *op = mach_gen_binop_to_string (kind); + mach_gen_emit_3 (s, op, dest, left_slot, right_slot); + return dest; +} + +static int mach_gen_compound_assign (MachGenState *s, cJSON *node, const char *op) { + cJSON *left = cJSON_GetObjectItemCaseSensitive (node, "left"); + cJSON *right = cJSON_GetObjectItemCaseSensitive (node, "right"); + const char *left_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "kind")); + + if (strcmp (left_kind, "name") == 0) { + const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "name")); + 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", ""); + + if (s.filename) + cJSON_AddStringToObject (result, "filename", s.filename); + + cJSON *is_arrow = cJSON_GetObjectItemCaseSensitive (func_node, "arrow"); + s.is_arrow = is_arrow && cJSON_IsTrue (is_arrow); + + /* Read function_nr from AST node */ + cJSON *fn_nr_node = cJSON_GetObjectItemCaseSensitive (func_node, "function_nr"); + s.function_nr = fn_nr_node ? (int)cJSON_GetNumberValue (fn_nr_node) : 0; + + /* Parameters */ + cJSON *params = cJSON_GetObjectItemCaseSensitive (func_node, "list"); + if (!params) params = cJSON_GetObjectItemCaseSensitive (func_node, "parameters"); + s.nr_args = cJSON_GetArraySize (params); + s.this_slot = 0; + s.nr_close_slots = 0; + s.nr_local_slots = 0; + + /* Use nr_slots from AST to pre-allocate var capacity */ + cJSON *ns = cJSON_GetObjectItemCaseSensitive (func_node, "nr_slots"); + int ast_nr_slots = ns ? (int)cJSON_GetNumberValue (ns) : 0; + if (ast_nr_slots > 0) { + s.var_capacity = ast_nr_slots; + s.vars = sys_malloc (s.var_capacity * sizeof(MachVarInfo)); + } + + int param_slot = 1; + cJSON *param; + cJSON_ArrayForEach (param, params) { + const char *param_name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (param, "name")); + if (!param_name) param_name = cJSON_GetStringValue (param); + if (param_name) { + mach_gen_add_var (&s, param_name, param_slot, 1); + param_slot++; + } + } + + s.next_temp_slot = 1 + s.nr_args; + s.max_slot = 1 + s.nr_args; + + cJSON_AddNumberToObject (result, "nr_args", s.nr_args); + + /* Scan scope record for variable declarations */ + mach_gen_scan_scope (&s); + + s.next_temp_slot = 1 + s.nr_args + s.nr_local_slots; + if (s.next_temp_slot > s.max_slot) s.max_slot = s.next_temp_slot; + + /* Emit default parameter initialization */ + { + int ps = 1; + cJSON *pp; + cJSON_ArrayForEach(pp, params) { + cJSON *default_expr = cJSON_GetObjectItemCaseSensitive(pp, "expression"); + if (default_expr) { + char *end_label = mach_gen_label(&s, "default_end"); + mach_gen_emit_jump_cond(&s, "jump_not_null", ps, end_label); + int default_slot = mach_gen_expr(&s, default_expr, -1); + mach_gen_emit_2(&s, "move", ps, default_slot); + mach_gen_emit_label(&s, end_label); + sys_free(end_label); + } + ps++; + } + } + + /* Pre-load intrinsics (global names) */ + mach_gen_load_intrinsics (&s, cJSON_GetObjectItemCaseSensitive (func_node, "intrinsics")); + + /* Compile hoisted function declarations from func_node["functions"] */ + cJSON *hoisted = cJSON_GetObjectItemCaseSensitive (func_node, "functions"); + if (hoisted) { + cJSON *fn; + cJSON_ArrayForEach (fn, hoisted) { + const char *fname = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (fn, "name")); + if (fname) { + cJSON *compiled = mach_gen_function (&s, fn); + int func_id = s.func_counter++; + cJSON_AddItemToArray (s.functions, compiled); + int local_slot = mach_gen_find_var (&s, fname); + int dest = mach_gen_alloc_slot (&s); + mach_gen_emit_2 (&s, "function", dest, func_id); + if (local_slot >= 0) mach_gen_emit_2 (&s, "move", local_slot, dest); + } + } + } + + /* Compile body */ + cJSON *stmts = cJSON_GetObjectItemCaseSensitive (func_node, "statements"); + if (!stmts) { + cJSON *body = cJSON_GetObjectItemCaseSensitive (func_node, "body"); + if (body) { + stmts = cJSON_GetObjectItemCaseSensitive (body, "statements"); + if (!stmts) stmts = body; + } + } + cJSON *stmt; + if (stmts && cJSON_IsArray (stmts)) { + cJSON_ArrayForEach (stmt, stmts) { mach_gen_statement (&s, stmt); } + } + + { + int null_slot = mach_gen_alloc_slot (&s); + mach_gen_emit_1 (&s, "null", null_slot); + mach_gen_emit_1 (&s, "return", null_slot); + } + + /* Compile disruption clause if present */ + int disruption_start = 0; + cJSON *disruption = cJSON_GetObjectItemCaseSensitive (func_node, "disruption"); + if (disruption && cJSON_IsArray (disruption)) { + disruption_start = cJSON_GetArraySize (s.instructions); + cJSON_ArrayForEach (stmt, disruption) { mach_gen_statement (&s, stmt); } + int null_slot2 = mach_gen_alloc_slot (&s); + mach_gen_emit_1 (&s, "null", null_slot2); + mach_gen_emit_1 (&s, "return", null_slot2); + } + cJSON_AddNumberToObject (result, "disruption_pc", disruption_start); + + cJSON *fn_scope = mach_find_scope_record (s.scopes, s.function_nr); + cJSON *fn_ncs = fn_scope ? cJSON_GetObjectItemCaseSensitive (fn_scope, "nr_close_slots") : NULL; + cJSON_AddNumberToObject (result, "nr_close_slots", fn_ncs ? (int)cJSON_GetNumberValue (fn_ncs) : 0); + cJSON_AddNumberToObject (result, "nr_slots", s.max_slot + 1); + cJSON_AddItemToObject (result, "instructions", s.instructions); + + parent->label_counter = s.label_counter; + parent->func_counter = s.func_counter; + if (s.errors && s.errors != parent->errors) { + if (!parent->errors) { parent->errors = s.errors; } + else { + cJSON *err; + cJSON_ArrayForEach (err, s.errors) { cJSON_AddItemToArray (parent->errors, cJSON_Duplicate (err, 1)); } + cJSON_Delete (s.errors); + } + } + parent->has_error = parent->has_error || s.has_error; + + for (int i = 0; i < s.var_count; i++) sys_free (s.vars[i].name); + if (s.vars) sys_free (s.vars); + + return result; +} + +static cJSON *mach_gen_program (MachGenState *s, cJSON *ast) { + cJSON *result = cJSON_CreateObject (); + const char *filename = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (ast, "filename")); + cJSON_AddStringToObject (result, "name", filename ? filename : ""); + s->filename = filename; + if (filename) + cJSON_AddStringToObject (result, "filename", filename); + + s->data = cJSON_AddObjectToObject (result, "data"); + s->functions = cJSON_AddArrayToObject (result, "functions"); + + /* Read scopes from AST */ + s->scopes = cJSON_GetObjectItemCaseSensitive (ast, "scopes"); + + s->this_slot = 0; + s->nr_args = 0; + s->nr_close_slots = 0; + s->nr_local_slots = 0; + s->next_temp_slot = 1; + s->max_slot = 1; + + /* Use nr_slots from AST to pre-allocate var capacity */ + cJSON *ns = cJSON_GetObjectItemCaseSensitive (ast, "nr_slots"); + int ast_nr_slots = ns ? (int)cJSON_GetNumberValue (ns) : 0; + if (ast_nr_slots > 0) { + s->var_capacity = ast_nr_slots; + s->vars = sys_malloc (s->var_capacity * sizeof(MachVarInfo)); + } + + /* Scan scope record for variable declarations */ + mach_gen_scan_scope (s); + + s->next_temp_slot = 1 + s->nr_local_slots; + if (s->next_temp_slot > s->max_slot) s->max_slot = s->next_temp_slot; + + /* Intrinsics are loaded lazily at point of use instead of pre-loading, + to avoid loading nested function intrinsics in the wrong scope. */ + + /* Compile hoisted function declarations from ast["functions"] */ + cJSON *hoisted = cJSON_GetObjectItemCaseSensitive (ast, "functions"); + if (hoisted) { + cJSON *fn; + cJSON_ArrayForEach (fn, hoisted) { + const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (fn, "name")); + if (name) { + cJSON *compiled = mach_gen_function (s, fn); + int func_id = s->func_counter++; + cJSON_AddItemToArray (s->functions, compiled); + int local_slot = mach_gen_find_var (s, name); + int dest = mach_gen_alloc_slot (s); + mach_gen_emit_2 (s, "function", dest, func_id); + if (local_slot >= 0) mach_gen_emit_2 (s, "move", local_slot, dest); + } + } + } + + /* Generate main code */ + cJSON *statements = cJSON_GetObjectItemCaseSensitive (ast, "statements"); + int last_expr_slot = -1; + cJSON *stmt; + cJSON_ArrayForEach (stmt, statements) { + const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (stmt, "kind")); + if (kind) { + if (strcmp (kind, "call") == 0) { + cJSON *expr = cJSON_GetObjectItemCaseSensitive (stmt, "expression"); + last_expr_slot = mach_gen_expr (s, expr, -1); + } else if (strcmp (kind, "return") == 0 || strcmp (kind, "disrupt") == 0 || + strcmp (kind, "break") == 0 || strcmp (kind, "continue") == 0) { + mach_gen_statement (s, stmt); + last_expr_slot = -1; + } else if (strcmp (kind, "var") == 0 || strcmp (kind, "def") == 0 || + strcmp (kind, "var_list") == 0 || strcmp (kind, "def_list") == 0 || + strcmp (kind, "function") == 0 || strcmp (kind, "block") == 0 || + strcmp (kind, "if") == 0 || strcmp (kind, "while") == 0 || + strcmp (kind, "do") == 0 || strcmp (kind, "for") == 0 || + strcmp (kind, "switch") == 0) { + mach_gen_statement (s, stmt); + last_expr_slot = -1; + } else { + last_expr_slot = mach_gen_expr (s, stmt, -1); + } + } else { + mach_gen_statement (s, stmt); + } + } + + if (last_expr_slot >= 0) { + mach_gen_emit_1 (s, "return", last_expr_slot); + } else { + int null_slot = mach_gen_alloc_slot (s); + mach_gen_emit_1 (s, "null", null_slot); + mach_gen_emit_1 (s, "return", null_slot); + } + + cJSON *main_obj = cJSON_CreateObject (); + cJSON_AddNumberToObject (main_obj, "nr_args", 0); + cJSON_AddNumberToObject (main_obj, "nr_close_slots", 0); + cJSON_AddNumberToObject (main_obj, "nr_slots", s->max_slot + 1); + cJSON_AddItemToObject (main_obj, "instructions", s->instructions); + cJSON_AddItemToObject (result, "main", main_obj); + + return result; +} + +cJSON *JS_McodeTree (cJSON *ast) { + if (!ast) return NULL; + + MachGenState s = {0}; + s.instructions = cJSON_CreateArray (); + s.errors = NULL; + s.has_error = 0; + + cJSON *mach = mach_gen_program (&s, ast); + + for (int i = 0; i < s.var_count; i++) sys_free (s.vars[i].name); + if (s.vars) sys_free (s.vars); + + if (!mach) { + if (s.errors) cJSON_Delete (s.errors); + return NULL; + } + + if (s.errors) + cJSON_AddItemToObject (mach, "errors", s.errors); + + return mach; +} + +char *JS_Mcode (const char *ast_json) { + cJSON *ast = cJSON_Parse (ast_json); + if (!ast) return NULL; + cJSON *mach = JS_McodeTree (ast); + cJSON_Delete (ast); + if (!mach) return NULL; + char *json = cJSON_PrintUnformatted (mach); + cJSON_Delete (mach); + return json; +} + +/* ============================================================ + MCODE JSON Interpreter + ============================================================ */ + +/* Parse a single MCODE function from cJSON into a JSMCode struct */ +/* Parse a single function (no recursive function parsing) */ +JSMCode *jsmcode_parse_one(cJSON *func_def) { + JSMCode *code = js_mallocz_rt(sizeof(JSMCode)); + if (!code) return NULL; + + code->nr_args = (uint16_t)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(func_def, "nr_args")); + code->nr_slots = (uint16_t)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(func_def, "nr_slots")); + + /* Build instruction array from cJSON linked list */ + cJSON *instrs_arr = cJSON_GetObjectItemCaseSensitive(func_def, "instructions"); + int raw_count = cJSON_GetArraySize(instrs_arr); + code->instrs = js_mallocz_rt(raw_count * sizeof(cJSON *)); + code->instr_count = 0; + + /* First pass: count labels and build instruction array */ + uint32_t label_cap = 32; + code->labels = js_mallocz_rt(label_cap * sizeof(*code->labels)); + code->label_count = 0; + + cJSON *item; + uint32_t idx = 0; + cJSON_ArrayForEach(item, instrs_arr) { + if (cJSON_IsString(item)) { + /* Label marker — record position, don't add to instruction array */ + if (code->label_count >= label_cap) { + label_cap *= 2; + code->labels = js_realloc_rt(code->labels, label_cap * sizeof(*code->labels)); + } + code->labels[code->label_count].name = item->valuestring; + code->labels[code->label_count].index = idx; + code->label_count++; + } else { + /* Instruction (array) */ + code->instrs[idx++] = item; + } + } + code->instr_count = idx; + + /* Extract line table from trailing numbers in each instruction array */ + if (idx > 0) { + code->line_table = js_mallocz_rt(idx * sizeof(MachLineEntry)); + for (uint32_t i = 0; i < idx; i++) { + cJSON *instr = code->instrs[i]; + int n = cJSON_GetArraySize(instr); + if (n >= 2) { + cJSON *line_item = cJSON_GetArrayItem(instr, n - 2); + cJSON *col_item = cJSON_GetArrayItem(instr, n - 1); + if (cJSON_IsNumber(line_item) && cJSON_IsNumber(col_item)) { + code->line_table[i].line = (uint16_t)line_item->valuedouble; + code->line_table[i].col = (uint16_t)col_item->valuedouble; + } + } + } + } + + /* Extract name and filename from function definition */ + code->name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(func_def, "name")); + code->filename = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(func_def, "filename")); + + /* Extract disruption_pc */ + cJSON *dpc = cJSON_GetObjectItemCaseSensitive(func_def, "disruption_pc"); + code->disruption_pc = dpc ? (uint16_t)cJSON_GetNumberValue(dpc) : 0; + + return code; +} + +/* Parse MCODE: main + all functions from the global functions array. + All JSMCode structs share the same functions[] pointer. */ +JSMCode *jsmcode_parse(cJSON *func_def, cJSON *all_functions) { + /* Parse the global functions array first (flat, non-recursive) */ + uint32_t func_count = all_functions ? cJSON_GetArraySize(all_functions) : 0; + JSMCode **parsed_funcs = NULL; + if (func_count > 0) { + parsed_funcs = js_mallocz_rt(func_count * sizeof(JSMCode *)); + for (uint32_t i = 0; i < func_count; i++) { + cJSON *fn = cJSON_GetArrayItem(all_functions, i); + parsed_funcs[i] = jsmcode_parse_one(fn); + /* Each function shares the same functions array */ + parsed_funcs[i]->func_count = func_count; + parsed_funcs[i]->functions = parsed_funcs; + } + } + + /* Parse the main function */ + JSMCode *code = jsmcode_parse_one(func_def); + code->func_count = func_count; + code->functions = parsed_funcs; + + return code; +} + +/* Free a top-level JSMCode and all its shared functions. + Only call this on the main code returned by jsmcode_parse. */ +void jsmcode_free(JSMCode *code) { + if (!code) return; + /* Free all parsed functions (they share the same functions array) */ + if (code->functions) { + for (uint32_t i = 0; i < code->func_count; i++) { + if (code->functions[i]) { + /* Don't free functions[i]->functions — it's the shared pointer */ + if (code->functions[i]->instrs) js_free_rt(code->functions[i]->instrs); + if (code->functions[i]->labels) js_free_rt(code->functions[i]->labels); + if (code->functions[i]->line_table) js_free_rt(code->functions[i]->line_table); + js_free_rt(code->functions[i]); + } + } + js_free_rt(code->functions); + } + if (code->instrs) js_free_rt(code->instrs); + if (code->labels) js_free_rt(code->labels); + if (code->line_table) js_free_rt(code->line_table); + if (code->json_root) cJSON_Delete(code->json_root); + js_free_rt(code); +} + +/* Resolve label name → instruction index */ +static uint32_t mcode_resolve_label(JSMCode *code, const char *name) { + for (uint32_t i = 0; i < code->label_count; i++) { + if (strcmp(code->labels[i].name, name) == 0) + return code->labels[i].index; + } + return code->instr_count; /* past end = implicit return */ +} + +/* Create a MCODE function object. + outer_frame must be set by the caller AFTER refreshing from GC root, + since js_mallocz can trigger GC which invalidates stale JSValues. */ +JSValue js_new_mcode_function(JSContext *ctx, JSMCode *code) { + JSFunction *fn = js_mallocz(ctx, sizeof(JSFunction)); + if (!fn) return JS_EXCEPTION; + + fn->header = objhdr_make(0, OBJ_FUNCTION, 0, 0, 0, 0); + fn->kind = JS_FUNC_KIND_MCODE; + fn->length = code->nr_args; + fn->name = JS_NULL; + fn->u.mcode.code = code; + fn->u.mcode.outer_frame = JS_NULL; + fn->u.mcode.env_record = JS_NULL; + + return JS_MKPTR(fn); +} + +/* Main MCODE interpreter — executes pre-parsed JSMCode */ +JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj, + int argc, JSValue *argv, JSValue outer_frame) { + /* Protect argv, this_obj, outer_frame from GC by pushing onto value_stack. + alloc_frame_register and js_new_mcode_function can trigger GC. */ + int vs_save = ctx->value_stack_top; + int nargs_copy = (argc < code->nr_args) ? argc : code->nr_args; + ctx->value_stack[vs_save] = this_obj; + ctx->value_stack[vs_save + 1] = outer_frame; + for (int i = 0; i < nargs_copy; i++) + ctx->value_stack[vs_save + 2 + i] = argv[i]; + ctx->value_stack_top = vs_save + 2 + nargs_copy; + + JSFrameRegister *frame = alloc_frame_register(ctx, code->nr_slots); + if (!frame) { ctx->value_stack_top = vs_save; return JS_EXCEPTION; } + + /* Protect frame from GC */ + JSGCRef frame_ref; + JS_AddGCRef(ctx, &frame_ref); + frame_ref.val = JS_MKPTR(frame); + + /* Create a function object for the main frame so return can find the code */ + JSValue main_func = js_new_mcode_function(ctx, code); + if (JS_IsException(main_func)) { + ctx->value_stack_top = vs_save; + JS_DeleteGCRef(ctx, &frame_ref); + return JS_ThrowInternalError(ctx, "failed to allocate main function for MCODE"); + } + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + /* Set outer_frame AFTER allocation so it uses the post-GC value */ + JSFunction *main_fn = JS_VALUE_GET_FUNCTION(main_func); + main_fn->u.mcode.outer_frame = ctx->value_stack[vs_save + 1]; + frame->function = main_func; + + /* Setup initial frame from GC-safe value_stack */ + frame->slots[0] = ctx->value_stack[vs_save]; /* slot 0 is this */ + for (int i = 0; i < nargs_copy; i++) { + frame->slots[1 + i] = ctx->value_stack[vs_save + 2 + i]; + } + ctx->value_stack_top = vs_save; + + uint32_t pc = 0; + JSValue result = JS_NULL; + + for (;;) { + /* Check for interrupt */ + if (reg_vm_check_interrupt(ctx)) { + result = JS_ThrowInternalError(ctx, "interrupted"); + goto done; + } + + if (pc >= code->instr_count) { + /* Implicit return null */ + result = JS_NULL; + if (JS_IsNull(frame->caller)) goto done; + + /* Pop frame — read return info from CALLER */ + JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); + int ret_info = JS_VALUE_GET_INT(caller->address); + frame->caller = JS_NULL; + frame = caller; + frame_ref.val = JS_MKPTR(frame); + + JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); + code = fn->u.mcode.code; + pc = ret_info >> 16; + frame->slots[ret_info & 0xFFFF] = result; + continue; + } + + cJSON *instr = code->instrs[pc++]; + cJSON *op_item = cJSON_GetArrayItem(instr, 0); + const char *op = op_item->valuestring; + + /* Operand extraction helpers — items 1,2,3 */ + cJSON *a1 = cJSON_GetArrayItem(instr, 1); + cJSON *a2 = cJSON_GetArrayItem(instr, 2); + cJSON *a3 = cJSON_GetArrayItem(instr, 3); + + /* ---- Constants ---- */ + if (strcmp(op, "access") == 0) { + int dest = (int)a1->valuedouble; + if (cJSON_IsObject(a2)) { + /* Intrinsic: {"kind":"name","name":"...","make":"intrinsic"} */ + const char *iname = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(a2, "name")); + if (iname) { + JSValue key = JS_NewString(ctx, iname); + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); + 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); +} diff --git a/source/parse.c b/source/parse.c new file mode 100644 index 00000000..ba48777b --- /dev/null +++ b/source/parse.c @@ -0,0 +1,2087 @@ +/* + * 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" + +static cJSON *ast_parse_primary (ASTParseState *s) { + const uint8_t *start = s->token_ptr; + cJSON *node = NULL; + + switch (s->token_val) { + case TOK_NUMBER: { + node = ast_node (s, "number", start); + double d = s->token_u.num.val; + /* Store original text representation */ + size_t len = s->buf_ptr - start; + char *text = sys_malloc (len + 1); + memcpy (text, start, len); + text[len] = '\0'; + cJSON_AddStringToObject (node, "value", text); + cJSON_AddNumberToObject (node, "number", d); + sys_free (text); + ast_node_end (s, node, s->buf_ptr); + ast_next_token (s); + } break; + + case TOK_STRING: { + node = ast_node (s, "text", start); + cjson_add_strn (node, "value", s->token_u.str.str, s->token_u.str.len); + ast_node_end (s, node, s->buf_ptr); + ast_next_token (s); + } break; + + case TOK_TEMPLATE: { + const uint8_t *tmpl_start = start + 1; + const uint8_t *tmpl_end = s->buf_ptr - 1; + const uint8_t *saved_end = s->buf_ptr; + + /* Quick scan for ${ */ + BOOL has_expr = FALSE; + for (const uint8_t *sc = tmpl_start; sc < tmpl_end; sc++) { + if (*sc == '\\' && sc + 1 < tmpl_end) { sc++; continue; } + if (*sc == '$' && sc + 1 < tmpl_end && sc[1] == '{') { + has_expr = TRUE; break; + } + } + + if (!has_expr) { + /* Simple template — unchanged behavior */ + node = ast_node (s, "text", start); + cjson_add_strn (node, "value", s->token_u.str.str, s->token_u.str.len); + ast_node_end (s, node, s->buf_ptr); + ast_next_token (s); + } else { + node = ast_node (s, "text literal", start); + cJSON *list = cJSON_AddArrayToObject (node, "list"); + + /* Build format string with {N} placeholders */ + int cap = 256; + char *fmt = sys_malloc (cap); + int len = 0; + int idx = 0; + const uint8_t *p = tmpl_start; + + while (p < tmpl_end) { + if (*p == '\\' && p + 1 < tmpl_end) { + p++; /* skip backslash */ + if (len + 8 >= cap) { cap *= 2; fmt = sys_realloc (fmt, cap); } + switch (*p) { + case 'n': fmt[len++] = '\n'; p++; break; + case 't': fmt[len++] = '\t'; p++; break; + case 'r': fmt[len++] = '\r'; p++; break; + case '\\': fmt[len++] = '\\'; p++; break; + case '`': fmt[len++] = '`'; p++; break; + case '$': fmt[len++] = '$'; p++; break; + case '0': fmt[len++] = '\0'; p++; break; + case 'u': { + p++; + unsigned int cp = 0; + for (int i = 0; i < 4 && p < tmpl_end; i++, p++) { + cp <<= 4; + if (*p >= '0' && *p <= '9') cp |= *p - '0'; + else if (*p >= 'a' && *p <= 'f') cp |= *p - 'a' + 10; + else if (*p >= 'A' && *p <= 'F') cp |= *p - 'A' + 10; + else break; + } + len += unicode_to_utf8 ((uint8_t *)fmt + len, cp); + } break; + default: fmt[len++] = *p++; break; + } + continue; + } + if (*p == '$' && p + 1 < tmpl_end && p[1] == '{') { + /* Add {N} placeholder */ + if (len + 12 >= cap) { cap *= 2; fmt = sys_realloc (fmt, cap); } + len += snprintf (fmt + len, cap - len, "{%d}", idx++); + p += 2; /* skip ${ */ + + /* Parse expression: redirect buf_ptr, tokenize, parse */ + s->buf_ptr = p; + ast_next_token (s); + cJSON *expr = ast_parse_assign_expr (s); + if (expr) cJSON_AddItemToArray (list, expr); + + /* After expression, token should be '}' */ + if (s->token_val == '}') { + p = s->buf_ptr; + } else { + ast_error (s, p, "expected '}' after template expression"); + p = s->buf_ptr; + } + continue; + } + if (len + 1 >= cap) { cap *= 2; fmt = sys_realloc (fmt, cap); } + fmt[len++] = *p++; + } + fmt[len] = '\0'; + + cJSON_AddStringToObject (node, "value", fmt); + sys_free (fmt); + + s->buf_ptr = saved_end; + ast_node_end (s, node, saved_end); + ast_next_token (s); + } + } break; + + case TOK_IDENT: { + /* Check for single-param arrow function: x => ... */ + const uint8_t *p = s->buf_ptr; + while (p < s->buf_end && (*p == ' ' || *p == '\t')) p++; + if (p + 1 < s->buf_end && p[0] == '=' && p[1] == '>') { + node = ast_parse_arrow_function (s); + } else { + node = ast_node (s, "name", start); + cjson_add_strn (node, "name", s->token_u.ident.str, s->token_u.ident.len); + ast_node_end (s, node, s->buf_ptr); + ast_next_token (s); + } + } break; + + case TOK_NULL: + node = ast_node (s, "null", start); + ast_node_end (s, node, s->buf_ptr); + ast_next_token (s); + break; + + case TOK_TRUE: + node = ast_node (s, "true", start); + ast_node_end (s, node, s->buf_ptr); + ast_next_token (s); + break; + + case TOK_FALSE: + node = ast_node (s, "false", start); + ast_node_end (s, node, s->buf_ptr); + ast_next_token (s); + break; + + case TOK_THIS: + node = ast_node (s, "this", start); + ast_node_end (s, node, s->buf_ptr); + ast_next_token (s); + break; + + case '[': { + node = ast_node (s, "array", start); + cJSON *list = cJSON_AddArrayToObject (node, "list"); + ast_next_token (s); + while (s->token_val != ']' && s->token_val != TOK_EOF) { + cJSON *elem = ast_parse_assign_expr (s); + if (elem) cJSON_AddItemToArray (list, elem); + if (s->token_val == ',') ast_next_token (s); + else break; + } + ast_node_end (s, node, s->buf_ptr); + if (s->token_val == ']') { + ast_next_token (s); + } else if (s->token_val == TOK_EOF) { + ast_error (s, s->token_ptr, "unterminated array literal, expected ']'"); + } + } break; + + case '{': { + node = ast_node (s, "record", start); + cJSON *list = cJSON_AddArrayToObject (node, "list"); + ast_next_token (s); + while (s->token_val != '}' && s->token_val != TOK_EOF) { + cJSON *pair = cJSON_CreateObject (); + /* property name */ + int is_ident = (s->token_val == TOK_IDENT); + int is_keyword = (s->token_val >= TOK_FIRST_KEYWORD && s->token_val <= TOK_LAST_KEYWORD); + if (is_ident || is_keyword || s->token_val == TOK_STRING || s->token_val == TOK_NUMBER) { + cJSON *left; + if (is_keyword) { + left = ast_node (s, "name", s->token_ptr); + cjson_add_strn (left, "name", s->token_u.ident.str, s->token_u.ident.len); + ast_node_end (s, left, s->buf_ptr); + ast_next_token (s); + } else { + left = ast_parse_primary (s); + } + cJSON_AddItemToObject (pair, "left", left); + } else if (s->token_val == '[') { + /* computed property */ + ast_next_token (s); + cJSON *left = ast_parse_assign_expr (s); + cJSON_AddItemToObject (pair, "left", left); + if (s->token_val == ']') { + ast_next_token (s); + } else { + ast_error (s, s->token_ptr, "expected ']' after computed property"); + } + } else { + cJSON_Delete (pair); + ast_error (s, s->token_ptr, "expected property name in object literal"); + break; + } + /* colon and value */ + if (s->token_val == ':') { + ast_next_token (s); + cJSON *right = ast_parse_assign_expr (s); + cJSON_AddItemToObject (pair, "right", right); + } else if (s->token_val == '(') { + /* Method shorthand: init() {} => init: function init() {} */ + const uint8_t *fn_start = s->token_ptr; + cJSON *fn = ast_node (s, "function", fn_start); + + /* Set method name from property key */ + cJSON *left = cJSON_GetObjectItemCaseSensitive (pair, "left"); + cJSON *name_item = cJSON_GetObjectItemCaseSensitive (left, "name"); + if (name_item) + cJSON_AddStringToObject (fn, "name", name_item->valuestring); + + /* Parse parameters */ + cJSON *params = cJSON_AddArrayToObject (fn, "list"); + ast_next_token (s); /* skip '(' */ + while (s->token_val != ')' && s->token_val != TOK_EOF) { + if (s->token_val == TOK_IDENT) { + cJSON *param = ast_node (s, "name", s->token_ptr); + cjson_add_strn (param, "name", s->token_u.ident.str, s->token_u.ident.len); + ast_node_end (s, param, s->buf_ptr); + ast_next_token (s); + if (s->token_val == '=' || s->token_val == '|') { + ast_next_token (s); + cJSON *default_val = ast_parse_expr (s); + cJSON_AddItemToObject (param, "expression", default_val); + } + cJSON_AddItemToArray (params, param); + } else { + ast_error (s, s->token_ptr, "expected parameter name"); + break; + } + if (s->token_val == ',') ast_next_token (s); + else break; + } + if (s->token_val == ')') ast_next_token (s); + else if (s->token_val == TOK_EOF) + ast_error (s, s->token_ptr, "unterminated method parameter list"); + + if (cJSON_GetArraySize (params) > 4) + ast_error (s, s->token_ptr, "functions cannot have more than 4 parameters"); + + /* Parse body */ + if (s->token_val == '{') { + ast_next_token (s); + cJSON *stmts = ast_parse_block_statements (s); + cJSON_AddItemToObject (fn, "statements", stmts); + if (s->token_val == '}') ast_next_token (s); + else if (s->token_val == TOK_EOF) + ast_error (s, s->token_ptr, "unterminated method body"); + } else { + ast_error (s, s->token_ptr, "expected '{' for method body"); + } + + cJSON_AddNumberToObject (fn, "function_nr", s->function_nr++); + ast_node_end (s, fn, s->buf_ptr); + cJSON_AddItemToObject (pair, "right", fn); + } else if (!(is_ident && (s->token_val == ',' || s->token_val == '}'))) { + ast_error (s, s->token_ptr, "expected ':' after property name"); + } + cJSON_AddItemToArray (list, pair); + if (s->token_val == ',') ast_next_token (s); + else break; + } + ast_node_end (s, node, s->buf_ptr); + if (s->token_val == '}') { + ast_next_token (s); + } else if (s->token_val == TOK_EOF) { + ast_error (s, s->token_ptr, "unterminated object literal, expected '}'"); + } + } break; + + case '(': { + /* Check for arrow function: () => ..., (a, b) => ... */ + if (ast_is_arrow_function (s)) { + node = ast_parse_arrow_function (s); + } else { + ast_next_token (s); + node = ast_parse_expr (s); + if (s->token_val == ')') { + ast_next_token (s); + } else if (s->token_val == TOK_EOF) { + ast_error (s, s->token_ptr, "unterminated parenthesized expression, expected ')'"); + } else { + ast_error (s, s->token_ptr, "expected ')' after expression"); + } + } + } break; + + case TOK_FUNCTION: { + node = ast_parse_function_inner (s, TRUE); + } break; + + case '/': { + /* Regex literal - when / appears in primary position, it's a regex */ + node = ast_node (s, "regexp", start); + const uint8_t *p = s->token_ptr + 1; /* skip opening / */ + const uint8_t *pattern_start = p; + + /* Parse pattern - find closing / (not escaped) */ + while (p < s->buf_end && *p != '/') { + if (*p == '\\' && p + 1 < s->buf_end) { + p += 2; /* skip escape sequence */ + } else if (*p == '\n' || *p == '\r') { + ast_error (s, p, "unterminated regex literal"); + break; + } else { + p++; + } + } + size_t pattern_len = p - pattern_start; + if (p < s->buf_end) p++; /* skip closing / */ + + /* Parse flags */ + const uint8_t *flags_start = p; + while (p < s->buf_end && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z'))) { + p++; + } + size_t flags_len = p - flags_start; + + char *pattern = sys_malloc (pattern_len + 1); + memcpy (pattern, pattern_start, pattern_len); + pattern[pattern_len] = '\0'; + cJSON_AddStringToObject (node, "pattern", pattern); + sys_free (pattern); + + if (flags_len > 0) { + char *flags = sys_malloc (flags_len + 1); + memcpy (flags, flags_start, flags_len); + flags[flags_len] = '\0'; + cJSON_AddStringToObject (node, "flags", flags); + sys_free (flags); + } + + s->buf_ptr = p; + ast_node_end (s, node, s->buf_ptr); + ast_next_token (s); + } break; + + default: + /* Report syntax error with token info */ + if (s->token_val >= 32 && s->token_val < 127) { + ast_error (s, start, "unexpected '%c' in expression", s->token_val); + } else if (s->token_val == TOK_EOF) { + ast_error (s, start, "unexpected end of input"); + } else { + ast_error (s, start, "unexpected token (keyword or operator) where expression expected"); + } + ast_next_token (s); + return NULL; + } + + return node; +} + +static cJSON *ast_parse_postfix (ASTParseState *s) { + cJSON *node = ast_parse_primary (s); + if (!node) return NULL; + + for (;;) { + const uint8_t *start = s->token_ptr; + if (s->token_val == '.') { + ast_next_token (s); + cJSON *new_node = ast_node (s, ".", start); + cJSON_AddItemToObject (new_node, "left", node); + if (s->token_val == TOK_IDENT + || (s->token_val >= TOK_FIRST_KEYWORD && s->token_val <= TOK_LAST_KEYWORD)) { + cjson_add_strn (new_node, "right", s->token_u.ident.str, s->token_u.ident.len); + ast_next_token (s); + } else { + ast_error (s, s->token_ptr, "expected property name after '.'"); + } + ast_node_end (s, new_node, s->buf_ptr); + node = new_node; + } else if (s->token_val == '[') { + ast_next_token (s); + cJSON *new_node = ast_node (s, "[", start); + cJSON_AddItemToObject (new_node, "left", node); + if (s->token_val == ']') { + ast_next_token (s); + } else { + cJSON *index = ast_parse_assign_expr (s); + cJSON_AddItemToObject (new_node, "right", index); + if (s->token_val == ']') ast_next_token (s); + else ast_error (s, s->token_ptr, "expected ']'"); + } + ast_node_end (s, new_node, s->buf_ptr); + node = new_node; + } else if (s->token_val == '(') { + ast_next_token (s); + cJSON *new_node = ast_node (s, "(", start); + cJSON_AddItemToObject (new_node, "expression", node); + cJSON *list = cJSON_AddArrayToObject (new_node, "list"); + while (s->token_val != ')' && s->token_val != TOK_EOF) { + cJSON *arg = ast_parse_assign_expr (s); + if (arg) cJSON_AddItemToArray (list, arg); + if (s->token_val == ',') ast_next_token (s); + else break; + } + if (s->token_val == ')') ast_next_token (s); + else ast_error (s, s->token_ptr, "unterminated argument list, expected ')'"); + ast_node_end (s, new_node, s->buf_ptr); + node = new_node; + } else if (s->token_val == TOK_INC) { + cJSON *new_node = ast_node (s, "++", start); + cJSON_AddItemToObject (new_node, "expression", node); + cJSON_AddBoolToObject (new_node, "postfix", 1); + ast_next_token (s); + ast_node_end (s, new_node, s->buf_ptr); + node = new_node; + } else if (s->token_val == TOK_DEC) { + cJSON *new_node = ast_node (s, "--", start); + cJSON_AddItemToObject (new_node, "expression", node); + cJSON_AddBoolToObject (new_node, "postfix", 1); + ast_next_token (s); + ast_node_end (s, new_node, s->buf_ptr); + node = new_node; + } else { + break; + } + } + return node; +} + +static cJSON *ast_parse_unary (ASTParseState *s) { + const uint8_t *start = s->token_ptr; + + switch (s->token_val) { + case '!': { + ast_next_token (s); + cJSON *node = ast_node (s, "!", start); + cJSON *expr = ast_parse_unary (s); + cJSON_AddItemToObject (node, "expression", expr); + ast_node_end (s, node, s->buf_ptr); + return node; + } + case '~': { + ast_next_token (s); + cJSON *node = ast_node (s, "~", start); + cJSON *expr = ast_parse_unary (s); + cJSON_AddItemToObject (node, "expression", expr); + ast_node_end (s, node, s->buf_ptr); + return node; + } + case '+': { + ast_next_token (s); + cJSON *node = ast_node (s, "+unary", start); + cJSON *expr = ast_parse_unary (s); + cJSON_AddItemToObject (node, "expression", expr); + ast_node_end (s, node, s->buf_ptr); + return node; + } + case '-': { + ast_next_token (s); + cJSON *node = ast_node (s, "-unary", start); + cJSON *expr = ast_parse_unary (s); + cJSON_AddItemToObject (node, "expression", expr); + ast_node_end (s, node, s->buf_ptr); + return node; + } + case TOK_INC: { + ast_next_token (s); + cJSON *node = ast_node (s, "++", start); + cJSON *expr = ast_parse_unary (s); + cJSON_AddItemToObject (node, "expression", expr); + cJSON_AddBoolToObject (node, "postfix", 0); + ast_node_end (s, node, s->buf_ptr); + return node; + } + case TOK_DEC: { + ast_next_token (s); + cJSON *node = ast_node (s, "--", start); + cJSON *expr = ast_parse_unary (s); + cJSON_AddItemToObject (node, "expression", expr); + cJSON_AddBoolToObject (node, "postfix", 0); + ast_node_end (s, node, s->buf_ptr); + return node; + } + case TOK_DELETE: { + ast_next_token (s); + cJSON *node = ast_node (s, "delete", start); + cJSON *expr = ast_parse_unary (s); + cJSON_AddItemToObject (node, "expression", expr); + ast_node_end (s, node, s->buf_ptr); + return node; + } + default: + return ast_parse_postfix (s); + } +} + +/* Binary operator precedence levels */ +typedef struct { + int token; + const char *kind; + int prec; +} ASTBinOp; + +static const ASTBinOp ast_binops[] = { + { TOK_POW, "**", 14 }, + { '*', "*", 13 }, + { '/', "/", 13 }, + { '%', "%", 13 }, + { '+', "+", 12 }, + { '-', "-", 12 }, + { TOK_SHL, "<<", 11 }, + { TOK_SAR, ">>", 11 }, + { TOK_SHR, ">>>", 11 }, + { '<', "<", 10 }, + { '>', ">", 10 }, + { TOK_LTE, "<=", 10 }, + { TOK_GTE, ">=", 10 }, + { TOK_IN, "in", 10 }, + { TOK_EQ, "==", 9 }, + { TOK_NEQ, "!=", 9 }, + { TOK_STRICT_EQ, "===", 9 }, + { TOK_STRICT_NEQ, "!==", 9 }, + { '&', "&", 8 }, + { '^', "^", 7 }, + { '|', "|", 6 }, + { TOK_LAND, "&&", 5 }, + { TOK_LOR, "||", 4 }, + { 0, NULL, 0 } +}; + +static const ASTBinOp *ast_get_binop (int token) { + for (int i = 0; ast_binops[i].kind; i++) { + if (ast_binops[i].token == token) return &ast_binops[i]; + } + return NULL; +} + +static cJSON *ast_parse_binary (ASTParseState *s, int min_prec) { + cJSON *left = ast_parse_unary (s); + if (!left) return NULL; + + for (;;) { + const uint8_t *start = s->token_ptr; + const ASTBinOp *op = ast_get_binop (s->token_val); + if (!op || op->prec < min_prec) break; + + ast_next_token (s); + + /* Right associativity for ** */ + int next_prec = (op->prec == 14) ? op->prec : op->prec + 1; + cJSON *right = ast_parse_binary (s, next_prec); + + cJSON *node = ast_node (s, op->kind, start); + cJSON_AddItemToObject (node, "left", left); + cJSON_AddItemToObject (node, "right", right); + ast_node_end (s, node, s->buf_ptr); + left = node; + } + return left; +} + +static cJSON *ast_parse_ternary (ASTParseState *s) { + cJSON *cond = ast_parse_binary (s, 1); + if (!cond) return NULL; + + if (s->token_val == '?') { + const uint8_t *start = s->token_ptr; + ast_next_token (s); + cJSON *then_expr = ast_parse_expr (s); + if (s->token_val == ':') ast_next_token (s); + else ast_error (s, s->token_ptr, "expected ':' in ternary expression"); + cJSON *else_expr = ast_parse_expr (s); + + cJSON *node = ast_node (s, "then", start); + cJSON_AddItemToObject (node, "expression", cond); + cJSON_AddItemToObject (node, "then", then_expr); + cJSON_AddItemToObject (node, "else", else_expr); + ast_node_end (s, node, s->buf_ptr); + return node; + } + return cond; +} + +static cJSON *ast_parse_assign (ASTParseState *s) { + cJSON *left = ast_parse_ternary (s); + if (!left) return NULL; + + const uint8_t *start = s->token_ptr; + const char *kind = NULL; + + switch (s->token_val) { + case '=': kind = "assign"; break; + case TOK_PLUS_ASSIGN: kind = "+="; break; + case TOK_MINUS_ASSIGN: kind = "-="; break; + case TOK_MUL_ASSIGN: kind = "*="; break; + case TOK_DIV_ASSIGN: kind = "/="; break; + case TOK_MOD_ASSIGN: kind = "%="; break; + case TOK_SHL_ASSIGN: kind = "<<="; break; + case TOK_SAR_ASSIGN: kind = ">>="; break; + case TOK_SHR_ASSIGN: kind = ">>>="; break; + case TOK_AND_ASSIGN: kind = "&="; break; + case TOK_XOR_ASSIGN: kind = "^="; break; + case TOK_OR_ASSIGN: kind = "|="; break; + case TOK_POW_ASSIGN: kind = "**="; break; + case TOK_LAND_ASSIGN: kind = "&&="; break; + case TOK_LOR_ASSIGN: kind = "||="; break; + default: + return left; + } + + /* Validate assignment target */ + { + const char *left_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "kind")); + if (left_kind && + strcmp (left_kind, "name") != 0 && + strcmp (left_kind, ".") != 0 && + strcmp (left_kind, "[") != 0 && + strcmp (left_kind, "?.") != 0 && + strcmp (left_kind, "?.[") != 0) { + ast_error (s, start, "invalid assignment left-hand side"); + } + } + + ast_next_token (s); + cJSON *right = ast_parse_assign (s); + + cJSON *node = ast_node (s, kind, start); + cJSON_AddItemToObject (node, "left", left); + cJSON_AddItemToObject (node, "right", right); + + /* Check for push/pop bracket syntax */ + const char *left_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "kind")); + if (left_kind && strcmp (left_kind, "[") == 0 && !cJSON_GetObjectItemCaseSensitive (left, "right")) + cJSON_AddBoolToObject (node, "push", 1); + const char *right_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (right, "kind")); + if (right_kind && strcmp (right_kind, "[") == 0 && !cJSON_GetObjectItemCaseSensitive (right, "right")) + cJSON_AddBoolToObject (node, "pop", 1); + + ast_node_end (s, node, s->buf_ptr); + return node; +} + +/* Parse assignment expression (excludes comma operator) */ +cJSON *ast_parse_assign_expr (ASTParseState *s) { + return ast_parse_assign (s); +} + +/* Parse full expression including comma operator */ +cJSON *ast_parse_expr (ASTParseState *s) { + cJSON *left = ast_parse_assign (s); + if (!left) return NULL; + + /* Handle comma operator: (1, 2, 3) => 3 */ + while (s->token_val == ',') { + const uint8_t *start = s->token_ptr; + ast_next_token (s); + cJSON *right = ast_parse_assign (s); + + cJSON *node = ast_node (s, ",", start); + cJSON_AddItemToObject (node, "left", left); + cJSON_AddItemToObject (node, "right", right); + ast_node_end (s, node, s->buf_ptr); + left = node; + } + return left; +} + +cJSON *ast_parse_block_statements (ASTParseState *s) { + cJSON *stmts = cJSON_CreateArray (); + while (s->token_val != '}' && s->token_val != TOK_EOF) { + const uint8_t *before = s->token_ptr; + cJSON *stmt = ast_parse_statement (s); + if (stmt) { + cJSON_AddItemToArray (stmts, stmt); + } else if (s->token_ptr == before) { + ast_sync_to_statement (s); + } + } + return stmts; +} + +cJSON *ast_parse_function_inner (ASTParseState *s, BOOL is_expr) { + const uint8_t *start = s->token_ptr; + cJSON *node = ast_node (s, "function", start); + + if (s->in_disruption) { + ast_error (s, s->token_ptr, "cannot define function inside disruption clause"); + } + + ast_next_token (s); /* skip 'function' */ + + /* Optional function name */ + if (s->token_val == TOK_IDENT) { + cjson_add_strn (node, "name", s->token_u.ident.str, s->token_u.ident.len); + ast_next_token (s); + } + + /* Parameters */ + cJSON *params = cJSON_AddArrayToObject (node, "list"); + if (s->token_val == '(') { + ast_next_token (s); + while (s->token_val != ')' && s->token_val != TOK_EOF) { + if (s->token_val == TOK_IDENT) { + const uint8_t *param_ptr = s->token_ptr; + cJSON *param = ast_node (s, "name", param_ptr); + cjson_add_strn (param, "name", s->token_u.ident.str, s->token_u.ident.len); + /* Check for duplicate parameter name */ + { + char *tmp_name = sys_malloc (s->token_u.ident.len + 1); + memcpy (tmp_name, s->token_u.ident.str, s->token_u.ident.len); + tmp_name[s->token_u.ident.len] = '\0'; + cJSON *prev; + cJSON_ArrayForEach (prev, params) { + const char *prev_name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (prev, "name")); + if (prev_name && strcmp (prev_name, tmp_name) == 0) { + ast_error (s, param_ptr, "duplicate parameter name '%s'", tmp_name); + break; + } + } + sys_free (tmp_name); + } + ast_node_end (s, param, s->buf_ptr); + ast_next_token (s); + if (s->token_val == '=' || s->token_val == '|') { + ast_next_token (s); + cJSON *default_val = ast_parse_assign_expr (s); + cJSON_AddItemToObject (param, "expression", default_val); + } + cJSON_AddItemToArray (params, param); + } else { + ast_error (s, s->token_ptr, "expected parameter name"); + break; + } + if (s->token_val == ',') ast_next_token (s); + else break; + } + if (s->token_val == ')') { + ast_next_token (s); + } else if (s->token_val == TOK_EOF) { + ast_error (s, s->token_ptr, "unterminated function parameter list, expected ')'"); + } + } else { + ast_error (s, s->token_ptr, "expected '(' after function name"); + } + + if (cJSON_GetArraySize (params) > 4) { + ast_error (s, s->token_ptr, "functions cannot have more than 4 parameters"); + } + + /* Body */ + if (s->token_val == '{') { + ast_next_token (s); + cJSON *stmts = ast_parse_block_statements (s); + cJSON_AddItemToObject (node, "statements", stmts); + if (s->token_val == '}') { + ast_next_token (s); + } else if (s->token_val == TOK_EOF) { + ast_error (s, s->token_ptr, "unterminated function body, expected '}'"); + } + } else { + ast_error (s, s->token_ptr, "expected '{' for function body"); + } + + /* Optional disruption clause */ + if (s->token_val == TOK_DISRUPTION) { + ast_next_token (s); + if (s->token_val == '{') { + ast_next_token (s); + int old_in_disruption = s->in_disruption; + s->in_disruption = 1; + cJSON *disruption_stmts = ast_parse_block_statements (s); + s->in_disruption = old_in_disruption; + cJSON_AddItemToObject (node, "disruption", disruption_stmts); + if (s->token_val == '}') { + ast_next_token (s); + } else if (s->token_val == TOK_EOF) { + ast_error (s, s->token_ptr, "unterminated disruption clause, expected '}'"); + } + } else { + ast_error (s, s->token_ptr, "expected '{' after disruption"); + } + } + + cJSON_AddNumberToObject (node, "function_nr", s->function_nr++); + ast_node_end (s, node, s->buf_ptr); + return node; +} + +/* Parse arrow function: x => expr, (a, b) => expr, (x = 10) => expr, () => expr */ +cJSON *ast_parse_arrow_function (ASTParseState *s) { + const uint8_t *start = s->token_ptr; + cJSON *node = ast_node (s, "function", start); + cJSON_AddBoolToObject (node, "arrow", 1); + + if (s->in_disruption) { + ast_error (s, s->token_ptr, "cannot define function inside disruption clause"); + } + + /* Parameters */ + cJSON *params = cJSON_AddArrayToObject (node, "list"); + + if (s->token_val == TOK_IDENT) { + /* Single parameter without parens: x => ... */ + cJSON *param = ast_node (s, "name", s->token_ptr); + cjson_add_strn (param, "name", s->token_u.ident.str, s->token_u.ident.len); + ast_node_end (s, param, s->buf_ptr); + cJSON_AddItemToArray (params, param); + ast_next_token (s); + } else if (s->token_val == '(') { + /* Parenthesized parameters: () => ..., (a, b) => ..., (x = 10) => ... */ + ast_next_token (s); + while (s->token_val != ')' && s->token_val != TOK_EOF) { + if (s->token_val == TOK_IDENT) { + const uint8_t *param_ptr = s->token_ptr; + cJSON *param = ast_node (s, "name", param_ptr); + cjson_add_strn (param, "name", s->token_u.ident.str, s->token_u.ident.len); + /* Check for duplicate parameter name */ + { + char *tmp_name = sys_malloc (s->token_u.ident.len + 1); + memcpy (tmp_name, s->token_u.ident.str, s->token_u.ident.len); + tmp_name[s->token_u.ident.len] = '\0'; + cJSON *prev; + cJSON_ArrayForEach (prev, params) { + const char *prev_name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (prev, "name")); + if (prev_name && strcmp (prev_name, tmp_name) == 0) { + ast_error (s, param_ptr, "duplicate parameter name '%s'", tmp_name); + break; + } + } + sys_free (tmp_name); + } + ast_node_end (s, param, s->buf_ptr); + ast_next_token (s); + + /* Check for default value */ + if (s->token_val == '=' || s->token_val == '|') { + ast_next_token (s); + cJSON *default_val = ast_parse_assign_expr (s); + cJSON_AddItemToObject (param, "expression", default_val); + } + cJSON_AddItemToArray (params, param); + } else { + ast_error (s, s->token_ptr, "expected parameter name"); + break; + } + if (s->token_val == ',') ast_next_token (s); + else break; + } + if (s->token_val == ')') ast_next_token (s); + } + + if (cJSON_GetArraySize (params) > 4) { + ast_error (s, s->token_ptr, "functions cannot have more than 4 parameters"); + } + + /* Arrow token */ + if (s->token_val != TOK_ARROW) { + ast_error (s, s->token_ptr, "expected '=>' in arrow function"); + } else { + ast_next_token (s); + } + + /* Body: either block or expression */ + if (s->token_val == '{') { + ast_next_token (s); + cJSON *stmts = ast_parse_block_statements (s); + cJSON_AddItemToObject (node, "statements", stmts); + if (s->token_val == '}') ast_next_token (s); + } else { + /* Expression body - wrap in implicit return. + Use assign_expr (not full expr) so commas after the body + are NOT consumed — matches JS spec (AssignmentExpression). */ + cJSON *stmts = cJSON_CreateArray (); + cJSON *ret = ast_node (s, "return", s->token_ptr); + cJSON *expr = ast_parse_assign_expr (s); + cJSON_AddItemToObject (ret, "expression", expr); + ast_node_end (s, ret, s->buf_ptr); + cJSON_AddItemToArray (stmts, ret); + cJSON_AddItemToObject (node, "statements", stmts); + } + + cJSON_AddNumberToObject (node, "function_nr", s->function_nr++); + ast_node_end (s, node, s->buf_ptr); + return node; +} + +static void ast_expect_semi (ASTParseState *s) { + if (s->token_val == ';') { ast_next_token (s); return; } + if (s->token_val == TOK_EOF || s->token_val == '}' || s->got_lf + || s->token_val == TOK_ELSE) return; + ast_error (s, s->token_ptr, "expecting ';'"); +} + +/* Skip tokens until a statement sync point to recover from errors */ +void ast_sync_to_statement (ASTParseState *s) { + while (s->token_val != TOK_EOF) { + switch (s->token_val) { + case ';': + ast_next_token (s); /* consume semicolon */ + return; + case '}': + return; /* don't consume - let caller handle */ + case TOK_VAR: case TOK_DEF: case TOK_IF: case TOK_WHILE: + case TOK_FOR: case TOK_RETURN: case TOK_DISRUPT: + case TOK_FUNCTION: case TOK_BREAK: + case TOK_CONTINUE: case TOK_DO: + return; /* statement-starting keyword found */ + default: + ast_next_token (s); + break; + } + } +} + +cJSON *ast_parse_statement (ASTParseState *s) { + const uint8_t *start = s->token_ptr; + cJSON *node = NULL; + + switch (s->token_val) { + case '{': { + node = ast_node (s, "block", start); + ast_next_token (s); + cJSON *stmts = ast_parse_block_statements (s); + cJSON_AddItemToObject (node, "statements", stmts); + if (s->token_val == '}') ast_next_token (s); + ast_node_end (s, node, s->buf_ptr); + } break; + + case TOK_VAR: + case TOK_DEF: { + const char *kind_name = (s->token_val == TOK_VAR) ? "var" : "def"; + int is_def = (s->token_val == TOK_DEF); + ast_next_token (s); + + /* Expect an identifier */ + if (s->token_val != TOK_IDENT) { + ast_error (s, s->token_ptr, "expected identifier after '%s'", kind_name); + return NULL; + } + + /* Can have multiple declarations: var x = 1, y = 2 */ + cJSON *decls = cJSON_CreateArray (); + int decl_count = 0; + while (s->token_val == TOK_IDENT) { + const uint8_t *var_ptr = s->token_ptr; + node = ast_node (s, kind_name, start); + cJSON *left = ast_node (s, "name", s->token_ptr); + cjson_add_strn (left, "name", s->token_u.ident.str, s->token_u.ident.len); + /* Save name for potential error message */ + char *var_name = sys_malloc (s->token_u.ident.len + 1); + memcpy (var_name, s->token_u.ident.str, s->token_u.ident.len); + var_name[s->token_u.ident.len] = '\0'; + ast_node_end (s, left, s->buf_ptr); + cJSON_AddItemToObject (node, "left", left); + ast_next_token (s); + + if (s->token_val == '=') { + ast_next_token (s); + cJSON *right = ast_parse_assign_expr (s); + cJSON_AddItemToObject (node, "right", right); + const char *right_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (right, "kind")); + if (right_kind && strcmp (right_kind, "[") == 0 && !cJSON_GetObjectItemCaseSensitive (right, "right")) + cJSON_AddBoolToObject (node, "pop", 1); + } else if (is_def) { + /* def (constant) requires initializer */ + ast_error (s, var_ptr, "missing initializer for constant '%s'", var_name); + } + sys_free (var_name); + ast_node_end (s, node, s->buf_ptr); + cJSON_AddItemToArray (decls, node); + decl_count++; + + if (s->token_val == ',') { + ast_next_token (s); + } else { + break; + } + } + ast_expect_semi (s); + if (decl_count == 1) { + node = cJSON_DetachItemFromArray (decls, 0); + cJSON_Delete (decls); + } else { + node = ast_node (s, "var_list", start); + cJSON_AddItemToObject (node, "list", decls); + ast_node_end (s, node, s->buf_ptr); + } + } break; + + case TOK_IF: { + node = ast_node (s, "if", start); + ast_next_token (s); + if (s->token_val == '(') ast_next_token (s); + else ast_error (s, s->token_ptr, "expected '(' before condition"); + cJSON *cond = ast_parse_expr (s); + cJSON_AddItemToObject (node, "expression", cond); + if (s->token_val == ')') ast_next_token (s); + else ast_error (s, s->token_ptr, "expected ')' after if condition"); + + cJSON *then_stmts = cJSON_AddArrayToObject (node, "then"); + cJSON *then_stmt = ast_parse_statement (s); + if (then_stmt) cJSON_AddItemToArray (then_stmts, then_stmt); + + cJSON *else_ifs = cJSON_AddArrayToObject (node, "list"); + + if (s->token_val == TOK_ELSE) { + ast_next_token (s); + if (s->token_val == TOK_IF) { + /* else if - add to list */ + cJSON *elif = ast_parse_statement (s); + if (elif) cJSON_AddItemToArray (else_ifs, elif); + } else { + cJSON *else_stmts = cJSON_AddArrayToObject (node, "else"); + cJSON *else_stmt = ast_parse_statement (s); + if (else_stmt) cJSON_AddItemToArray (else_stmts, else_stmt); + } + } + ast_node_end (s, node, s->buf_ptr); + } break; + + case TOK_WHILE: { + node = ast_node (s, "while", start); + ast_next_token (s); + if (s->token_val == '(') ast_next_token (s); + else ast_error (s, s->token_ptr, "expected '(' before condition"); + cJSON *cond = ast_parse_expr (s); + cJSON_AddItemToObject (node, "expression", cond); + if (s->token_val == ')') ast_next_token (s); + else ast_error (s, s->token_ptr, "expected ')' after while condition"); + + cJSON *stmts = cJSON_AddArrayToObject (node, "statements"); + cJSON *body = ast_parse_statement (s); + if (body) cJSON_AddItemToArray (stmts, body); + ast_node_end (s, node, s->buf_ptr); + } break; + + case TOK_DO: { + node = ast_node (s, "do", start); + ast_next_token (s); + + cJSON *stmts = cJSON_AddArrayToObject (node, "statements"); + cJSON *body = ast_parse_statement (s); + if (body) cJSON_AddItemToArray (stmts, body); + + if (s->token_val == TOK_WHILE) ast_next_token (s); + else ast_error (s, s->token_ptr, "expected 'while' after do body"); + if (s->token_val == '(') ast_next_token (s); + else ast_error (s, s->token_ptr, "expected '(' before condition"); + cJSON *cond = ast_parse_expr (s); + cJSON_AddItemToObject (node, "expression", cond); + if (s->token_val == ')') ast_next_token (s); + else ast_error (s, s->token_ptr, "expected ')' after do-while condition"); + ast_expect_semi (s); + ast_node_end (s, node, s->buf_ptr); + } break; + + case TOK_FOR: { + node = ast_node (s, "for", start); + ast_next_token (s); + if (s->token_val == '(') ast_next_token (s); + else ast_error (s, s->token_ptr, "expected '(' after for"); + + /* Init */ + if (s->token_val != ';') { + if (s->token_val == TOK_VAR || s->token_val == TOK_DEF) { + cJSON *init = ast_parse_statement (s); + cJSON_AddItemToObject (node, "init", init); + } else { + cJSON *init = ast_parse_expr (s); + cJSON_AddItemToObject (node, "init", init); + if (s->token_val == ';') ast_next_token (s); + } + } else { + ast_next_token (s); + } + + /* Test */ + if (s->token_val != ';') { + cJSON *test = ast_parse_expr (s); + cJSON_AddItemToObject (node, "test", test); + } + if (s->token_val == ';') ast_next_token (s); + + /* Update */ + if (s->token_val != ')') { + cJSON *update = ast_parse_expr (s); + cJSON_AddItemToObject (node, "update", update); + } + if (s->token_val == ')') ast_next_token (s); + else ast_error (s, s->token_ptr, "expected ')' after for clauses"); + + cJSON *stmts = cJSON_AddArrayToObject (node, "statements"); + cJSON *body = ast_parse_statement (s); + if (body) cJSON_AddItemToArray (stmts, body); + ast_node_end (s, node, s->buf_ptr); + } break; + + case TOK_RETURN: { + node = ast_node (s, "return", start); + ast_next_token (s); + if (s->token_val != ';' && s->token_val != '}' && !s->got_lf) { + cJSON *expr = ast_parse_expr (s); + cJSON_AddItemToObject (node, "expression", expr); + } + ast_expect_semi (s); + ast_node_end (s, node, s->buf_ptr); + } break; + + case TOK_GO: { + node = ast_node (s, "go", start); + ast_next_token (s); + if (s->token_val != ';' && s->token_val != '}' && !s->got_lf) { + cJSON *expr = ast_parse_expr (s); + cJSON_AddItemToObject (node, "expression", expr); + } + ast_expect_semi (s); + ast_node_end (s, node, s->buf_ptr); + } break; + + case TOK_DISRUPT: { + node = ast_node (s, "disrupt", start); + ast_next_token (s); + ast_expect_semi (s); + ast_node_end (s, node, s->buf_ptr); + } break; + + case TOK_BREAK: { + node = ast_node (s, "break", start); + ast_next_token (s); + if (s->token_val == TOK_IDENT && !s->got_lf) { + cjson_add_strn (node, "name", s->token_u.ident.str, s->token_u.ident.len); + ast_next_token (s); + } + ast_expect_semi (s); + ast_node_end (s, node, s->buf_ptr); + } break; + + case TOK_CONTINUE: { + node = ast_node (s, "continue", start); + ast_next_token (s); + if (s->token_val == TOK_IDENT && !s->got_lf) { + cjson_add_strn (node, "name", s->token_u.ident.str, s->token_u.ident.len); + ast_next_token (s); + } + ast_expect_semi (s); + ast_node_end (s, node, s->buf_ptr); + } break; + + + case TOK_FUNCTION: { + node = ast_parse_function_inner (s, FALSE); + } break; + + case ';': + /* Empty statement */ + ast_next_token (s); + return NULL; + + case TOK_IDENT: { + /* Check if this is a labeled statement: identifier: statement */ + const uint8_t *p = s->buf_ptr; + while (p < s->buf_end && (*p == ' ' || *p == '\t')) p++; + if (p < s->buf_end && *p == ':') { + /* Labeled statement */ + node = ast_node (s, "label", start); + cjson_add_strn (node, "name", s->token_u.ident.str, s->token_u.ident.len); + ast_next_token (s); /* skip identifier */ + ast_next_token (s); /* skip colon */ + cJSON *stmt = ast_parse_statement (s); + cJSON_AddItemToObject (node, "statement", stmt); + ast_node_end (s, node, s->buf_ptr); + } else { + /* Expression statement */ + cJSON *expr = ast_parse_expr (s); + if (expr) { + node = ast_node (s, "call", start); + cJSON_AddItemToObject (node, "expression", expr); + ast_node_end (s, node, s->buf_ptr); + } + ast_expect_semi (s); + } + } break; + + default: { + /* Expression statement */ + cJSON *expr = ast_parse_expr (s); + if (expr) { + node = ast_node (s, "call", start); + cJSON_AddItemToObject (node, "expression", expr); + ast_node_end (s, node, s->buf_ptr); + } else { + ast_error (s, start, "unexpected token at start of statement"); + return NULL; /* caller syncs */ + } + ast_expect_semi (s); + } break; + } + + return node; +} + +static cJSON *ast_parse_program (ASTParseState *s) { + cJSON *root = cJSON_CreateObject (); + cJSON_AddStringToObject (root, "kind", "program"); + cJSON_AddStringToObject (root, "filename", s->filename); + + cJSON *functions = cJSON_AddArrayToObject (root, "functions"); + cJSON *statements = cJSON_AddArrayToObject (root, "statements"); + + while (s->token_val != TOK_EOF) { + const uint8_t *before = s->token_ptr; + cJSON *stmt = ast_parse_statement (s); + if (stmt) { + const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (stmt, "kind")); + if (kind && strcmp (kind, "function") == 0) { + cJSON_AddItemToArray (functions, stmt); + } else { + cJSON_AddItemToArray (statements, stmt); + } + } else if (s->token_ptr == before) { + /* Statement returned NULL and didn't advance - sync to avoid infinite loop */ + ast_sync_to_statement (s); + } + } + + return root; +} + +/* ============================================================ + AST Semantic Pass + ============================================================ */ + +#define AST_SEM_MAX_VARS 256 + +typedef struct ASTSemVar { + const char *name; + const char *scope_name; /* disambiguated name for block-scope vars (NULL = use name) */ + int is_const; + const char *make; /* "def", "var", "function", "input" */ + int function_nr; /* which function this var belongs to */ + int nr_uses; /* reference count */ + int closure; /* 1 if used by inner function */ +} ASTSemVar; + +typedef struct ASTSemScope { + struct ASTSemScope *parent; + ASTSemVar vars[AST_SEM_MAX_VARS]; + int var_count; + int in_loop; + int function_nr; /* function_nr of enclosing function */ + int is_function_scope; /* 1 if this is a function's top-level scope */ + int block_depth; /* 0 = function scope, 1+ = block scope */ +} ASTSemScope; + +typedef struct ASTSemState { + cJSON *errors; + int has_error; + cJSON *scopes_array; + const char *intrinsics[256]; + int intrinsic_count; + int block_var_counter; /* monotonically increasing counter for unique block var names */ +} ASTSemState; + +static void ast_sem_error (ASTSemState *st, 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 (!st->errors) st->errors = cJSON_CreateArray (); + cJSON_AddItemToArray (st->errors, err); + st->has_error = 1; +} + +static void ast_sem_add_var (ASTSemScope *scope, const char *name, int is_const, + const char *make, int function_nr) { + if (scope->var_count < AST_SEM_MAX_VARS) { + ASTSemVar *v = &scope->vars[scope->var_count]; + v->name = name; + v->scope_name = NULL; + v->is_const = is_const; + v->make = make; + v->function_nr = function_nr; + v->nr_uses = 0; + v->closure = 0; + scope->var_count++; + } +} + +/* Propagate block-scope vars to the function scope (parent) with disambiguated names */ +static void ast_sem_propagate_block_vars (ASTSemState *st, ASTSemScope *parent, + ASTSemScope *block) { + for (int i = 0; i < block->var_count; i++) { + ASTSemVar *v = &block->vars[i]; + const char *sn = v->scope_name ? v->scope_name : v->name; + if (parent->var_count < AST_SEM_MAX_VARS) { + ASTSemVar *pv = &parent->vars[parent->var_count]; + pv->name = sn; + pv->scope_name = NULL; + pv->is_const = v->is_const; + pv->make = v->make; + pv->function_nr = v->function_nr; + pv->nr_uses = v->nr_uses; + pv->closure = v->closure; + parent->var_count++; + } + } +} + +typedef struct { + ASTSemVar *var; + int level; + int def_function_nr; +} ASTSemLookup; + +static ASTSemLookup ast_sem_lookup_var (ASTSemScope *scope, const char *name) { + ASTSemLookup result = {NULL, 0, -1}; + int cur_fn = scope->function_nr; + for (ASTSemScope *s = scope; s; s = s->parent) { + for (int i = 0; i < s->var_count; i++) { + if (strcmp (s->vars[i].name, name) == 0) { + result.var = &s->vars[i]; + result.def_function_nr = s->vars[i].function_nr; + return result; + } + } + /* When crossing into a parent with a different function_nr, increment level */ + if (s->parent && s->parent->function_nr != cur_fn) { + result.level++; + cur_fn = s->parent->function_nr; + } + } + return result; +} + +static ASTSemVar *ast_sem_find_var (ASTSemScope *scope, const char *name) { + ASTSemLookup r = ast_sem_lookup_var (scope, name); + return r.var; +} + +static void ast_sem_add_intrinsic (ASTSemState *st, const char *name) { + for (int i = 0; i < st->intrinsic_count; i++) { + if (strcmp (st->intrinsics[i], name) == 0) return; + } + if (st->intrinsic_count < 256) { + st->intrinsics[st->intrinsic_count++] = name; + } +} + +static cJSON *ast_sem_build_scope_record (ASTSemScope *scope, int *nr_slots, int *nr_close) { + cJSON *rec = cJSON_CreateObject (); + cJSON_AddNumberToObject (rec, "function_nr", scope->function_nr); + int slots = 0, close_slots = 0; + for (int i = 0; i < scope->var_count; i++) { + ASTSemVar *v = &scope->vars[i]; + cJSON *entry = cJSON_CreateObject (); + cJSON_AddStringToObject (entry, "make", v->make); + cJSON_AddNumberToObject (entry, "function_nr", v->function_nr); + cJSON_AddNumberToObject (entry, "nr_uses", v->nr_uses); + cJSON_AddBoolToObject (entry, "closure", v->closure); + cJSON_AddNumberToObject (entry, "level", 0); + cJSON_AddItemToObject (rec, v->name, entry); + slots++; + if (v->closure) close_slots++; + } + *nr_slots = slots; + *nr_close = close_slots; + return rec; +} + +static int ast_sem_in_loop (ASTSemScope *scope) { + for (ASTSemScope *s = scope; s; s = s->parent) { + if (s->in_loop) return 1; + } + return 0; +} + +static BOOL is_functino_name(const char *name) { + static const char *functinos[] = { + "+!", "-!", "*!", "/!", "%!", "**!", + "!", "<=!", ">=!", "=!", "!=!", + "&!", "|!", "^!", "<>!", ">>>!", + "&&!", "||!", "~!", "[]!", NULL + }; + for (int i = 0; functinos[i]; i++) + if (strcmp(name, functinos[i]) == 0) return TRUE; + return FALSE; +} + +static void ast_sem_check_expr (ASTSemState *st, ASTSemScope *scope, cJSON *expr); +static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt); + +static void ast_sem_predeclare_vars (ASTSemState *st, ASTSemScope *scope, cJSON *stmts) { + cJSON *stmt; + cJSON_ArrayForEach (stmt, stmts) { + const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (stmt, "kind")); + if (!kind) continue; + if (strcmp (kind, "function") == 0) { + const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (stmt, "name")); + if (name && !ast_sem_find_var (scope, name)) + ast_sem_add_var (scope, name, 0, "function", scope->function_nr); + } else if (strcmp (kind, "var") == 0) { + cJSON *left = cJSON_GetObjectItemCaseSensitive (stmt, "left"); + const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "name")); + if (name && !ast_sem_find_var (scope, name)) + ast_sem_add_var (scope, name, 0, kind, scope->function_nr); + } else if (strcmp (kind, "var_list") == 0) { + cJSON *item; + cJSON_ArrayForEach (item, cJSON_GetObjectItemCaseSensitive (stmt, "list")) { + const char *ik = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (item, "kind")); + if (ik && strcmp (ik, "var") == 0) { + cJSON *left = cJSON_GetObjectItemCaseSensitive (item, "left"); + const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "name")); + if (name && !ast_sem_find_var (scope, name)) + ast_sem_add_var (scope, name, 0, ik, scope->function_nr); + } + } + } + } +} + +/* Check whether an expression is being assigned to (=, +=, etc.) */ +static void ast_sem_check_assign_target (ASTSemState *st, ASTSemScope *scope, cJSON *left) { + if (!left) return; + const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "kind")); + if (!kind) return; + + if (strcmp (kind, "name") == 0) { + const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "name")); + if (!name) return; + ASTSemVar *v = ast_sem_find_var (scope, name); + if (!v) { + ast_sem_error (st, left, "cannot assign to unbound variable '%s'", name); + } else if (v->is_const) { + ast_sem_error (st, left, "cannot assign to constant '%s'", name); + } + /* Annotate with level/function_nr so compilers can emit correct set instructions */ + ASTSemLookup r = ast_sem_lookup_var (scope, name); + if (r.var) { + cJSON_AddNumberToObject (left, "level", r.level); + cJSON_AddNumberToObject (left, "function_nr", r.def_function_nr); + if (r.var->scope_name) + cJSON_AddStringToObject (left, "scope_name", r.var->scope_name); + } else { + cJSON_AddNumberToObject (left, "level", -1); + } + } else if (strcmp (kind, ".") == 0 || strcmp (kind, "[") == 0 || + strcmp (kind, "?.") == 0 || strcmp (kind, "?.[") == 0) { + /* Property access as assignment target: resolve the object expression */ + cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive (left, "expression"); + if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive (left, "left"); + ast_sem_check_expr (st, scope, obj_expr); + /* Also resolve the index expression for computed access */ + cJSON *idx_expr = cJSON_GetObjectItemCaseSensitive (left, "index"); + if (!idx_expr && strcmp (kind, "[") == 0) + idx_expr = cJSON_GetObjectItemCaseSensitive (left, "right"); + if (idx_expr && cJSON_IsObject (idx_expr)) + ast_sem_check_expr (st, scope, idx_expr); + } +} + +/* Recursively check an expression for semantic errors */ +static void ast_sem_check_expr (ASTSemState *st, ASTSemScope *scope, cJSON *expr) { + if (!expr) return; + const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (expr, "kind")); + if (!kind) return; + + /* 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) { + ast_sem_check_assign_target (st, scope, cJSON_GetObjectItemCaseSensitive (expr, "left")); + ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (expr, "right")); + return; + } + + /* Increment/decrement */ + if (strcmp (kind, "++") == 0 || strcmp (kind, "--") == 0) { + cJSON *operand = cJSON_GetObjectItemCaseSensitive (expr, "expression"); + if (operand) { + const char *op_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (operand, "kind")); + if (op_kind && strcmp (op_kind, "name") == 0) { + const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (operand, "name")); + if (name) { + ASTSemVar *v = ast_sem_find_var (scope, name); + if (!v) { + ast_sem_error (st, expr, "cannot assign to unbound variable '%s'", name); + } else if (v->is_const) { + ast_sem_error (st, expr, "cannot assign to constant '%s'", name); + } + /* Annotate with level/function_nr/scope_name so compilers can emit correct set instructions */ + ASTSemLookup r = ast_sem_lookup_var (scope, name); + if (r.var) { + cJSON_AddNumberToObject (operand, "level", r.level); + cJSON_AddNumberToObject (operand, "function_nr", r.def_function_nr); + if (r.var->scope_name) + cJSON_AddStringToObject (operand, "scope_name", r.var->scope_name); + } else { + cJSON_AddNumberToObject (operand, "level", -1); + } + } + } + } + return; + } + + /* Binary ops, ternary, comma — recurse into children */ + if (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 || strcmp (kind, "&") == 0 || + strcmp (kind, "|") == 0 || strcmp (kind, "^") == 0 || + strcmp (kind, "<<") == 0 || strcmp (kind, ">>") == 0 || + strcmp (kind, ">>>") == 0 || strcmp (kind, "**") == 0 || + strcmp (kind, "in") == 0 || strcmp (kind, "of") == 0 || + strcmp (kind, ".") == 0 || strcmp (kind, "[") == 0 || + strcmp (kind, "?.") == 0 || strcmp (kind, "?.[") == 0) { + ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (expr, "left")); + ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (expr, "right")); + return; + } + + /* Ternary */ + if (strcmp (kind, "then") == 0) { + ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (expr, "expression")); + ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (expr, "then")); + ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (expr, "else")); + return; + } + + /* Call and optional call */ + if (strcmp (kind, "(") == 0 || strcmp (kind, "?.(") == 0) { + ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (expr, "expression")); + cJSON *arg; + cJSON_ArrayForEach (arg, cJSON_GetObjectItemCaseSensitive (expr, "list")) { + ast_sem_check_expr (st, scope, arg); + } + return; + } + + /* Unary ops */ + if (strcmp (kind, "!") == 0 || strcmp (kind, "~") == 0 || + strcmp (kind, "delete") == 0 || strcmp (kind, "neg") == 0 || + strcmp (kind, "pos") == 0 || strcmp (kind, "spread") == 0 || + strcmp (kind, "-unary") == 0 || strcmp (kind, "+unary") == 0 || + strcmp (kind, "unary_-") == 0) { + ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (expr, "expression")); + return; + } + + /* Array literal */ + if (strcmp (kind, "array") == 0) { + cJSON *el; + cJSON_ArrayForEach (el, cJSON_GetObjectItemCaseSensitive (expr, "list")) { + ast_sem_check_expr (st, scope, el); + } + return; + } + + /* Object literal */ + if (strcmp (kind, "object") == 0 || strcmp (kind, "record") == 0) { + cJSON *prop; + cJSON_ArrayForEach (prop, cJSON_GetObjectItemCaseSensitive (expr, "list")) { + cJSON *val = cJSON_GetObjectItemCaseSensitive (prop, "value"); + if (!val) val = cJSON_GetObjectItemCaseSensitive (prop, "right"); + ast_sem_check_expr (st, scope, val); + } + return; + } + + /* Function expression / arrow function — create new scope */ + if (strcmp (kind, "function") == 0) { + cJSON *fn_nr_node = cJSON_GetObjectItemCaseSensitive (expr, "function_nr"); + int fn_nr = fn_nr_node ? (int)cJSON_GetNumberValue (fn_nr_node) : scope->function_nr; + + ASTSemScope fn_scope = {0}; + fn_scope.parent = scope; + fn_scope.function_nr = fn_nr; + fn_scope.is_function_scope = 1; + + cJSON_AddNumberToObject (expr, "outer", scope->function_nr); + + /* Add parameters as input */ + cJSON *param; + cJSON_ArrayForEach (param, cJSON_GetObjectItemCaseSensitive (expr, "list")) { + const char *pname = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (param, "name")); + if (pname) ast_sem_add_var (&fn_scope, pname, 1, "input", fn_nr); + /* Check default value expressions */ + cJSON *def_val = cJSON_GetObjectItemCaseSensitive (param, "expression"); + if (def_val) ast_sem_check_expr (st, &fn_scope, def_val); + } + + /* Pre-register all declarations for mutual recursion / forward references */ + cJSON *fn_stmts = cJSON_GetObjectItemCaseSensitive (expr, "statements"); + ast_sem_predeclare_vars (st, &fn_scope, fn_stmts); + + /* Check function body */ + cJSON *stmt; + cJSON_ArrayForEach (stmt, fn_stmts) { + ast_sem_check_stmt (st, &fn_scope, stmt); + } + + /* Check disruption clause */ + cJSON *disruption = cJSON_GetObjectItemCaseSensitive (expr, "disruption"); + if (disruption) { + cJSON_ArrayForEach (stmt, disruption) { + ast_sem_check_stmt (st, &fn_scope, stmt); + } + } + + /* Build scope record and attach to scopes array */ + int nr_slots, nr_close; + cJSON *rec = ast_sem_build_scope_record (&fn_scope, &nr_slots, &nr_close); + cJSON_AddItemToArray (st->scopes_array, rec); + cJSON_AddNumberToObject (expr, "nr_slots", nr_slots); + cJSON_AddNumberToObject (expr, "nr_close_slots", nr_close); + return; + } + + /* Template literal */ + if (strcmp (kind, "template") == 0 || strcmp (kind, "text literal") == 0) { + cJSON *el; + cJSON_ArrayForEach (el, cJSON_GetObjectItemCaseSensitive (expr, "list")) { + ast_sem_check_expr (st, scope, el); + } + return; + } + + /* Name token — annotate with level and function_nr */ + if (strcmp (kind, "name") == 0) { + const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (expr, "name")); + if (name) { + if (is_functino_name(name)) { + cJSON_AddStringToObject (expr, "make", "functino"); + cJSON_AddNumberToObject (expr, "level", -1); + return; + } + ASTSemLookup r = ast_sem_lookup_var (scope, name); + if (r.var) { + cJSON_AddNumberToObject (expr, "level", r.level); + cJSON_AddNumberToObject (expr, "function_nr", r.def_function_nr); + r.var->nr_uses++; + if (r.level > 0) r.var->closure = 1; + if (r.var->scope_name) + cJSON_AddStringToObject (expr, "scope_name", r.var->scope_name); + } else { + cJSON_AddNumberToObject (expr, "level", -1); + ast_sem_add_intrinsic (st, name); + } + } + return; + } + + /* number, string, regexp, null, true, false, this — leaf nodes, no check needed */ +} + +/* Check a statement for semantic errors */ +static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt) { + if (!stmt) return; + const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (stmt, "kind")); + if (!kind) return; + + if (strcmp (kind, "var_list") == 0) { + cJSON *item; + cJSON_ArrayForEach (item, cJSON_GetObjectItemCaseSensitive (stmt, "list")) { + ast_sem_check_stmt (st, scope, item); + } + return; + } + + if (strcmp (kind, "var") == 0) { + /* Register variable */ + cJSON *left = cJSON_GetObjectItemCaseSensitive (stmt, "left"); + const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "name")); + if (name) { + ASTSemVar *existing = ast_sem_find_var (scope, name); + if (existing && existing->is_const) { + ast_sem_error (st, left, "cannot redeclare constant '%s'", name); + } + if (!existing || existing->function_nr != scope->function_nr + || scope->block_depth > 0) + ast_sem_add_var (scope, name, 0, "var", scope->function_nr); + if (scope->block_depth > 0) { + char buf[128]; + snprintf (buf, sizeof (buf), "_%s_%d", name, st->block_var_counter++); + char *sn = sys_malloc (strlen (buf) + 1); + strcpy (sn, buf); + scope->vars[scope->var_count - 1].scope_name = sn; + cJSON_AddStringToObject (left, "scope_name", sn); + } + } + ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (stmt, "right")); + return; + } + + if (strcmp (kind, "def") == 0) { + /* Register constant */ + cJSON *left = cJSON_GetObjectItemCaseSensitive (stmt, "left"); + const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "name")); + if (name) { + ASTSemVar *existing = ast_sem_find_var (scope, name); + if (existing && existing->is_const) { + ast_sem_error (st, left, "cannot redeclare constant '%s'", name); + } else if (existing && !existing->is_const && existing->function_nr == scope->function_nr) { + /* Pre-scanned as var, now upgrading to const */ + existing->is_const = 1; + existing->make = "def"; + } else { + ast_sem_add_var (scope, name, 1, "def", scope->function_nr); + if (scope->block_depth > 0) { + char buf[128]; + snprintf (buf, sizeof (buf), "_%s_%d", name, st->block_var_counter++); + char *sn = sys_malloc (strlen (buf) + 1); + strcpy (sn, buf); + scope->vars[scope->var_count - 1].scope_name = sn; + cJSON_AddStringToObject (left, "scope_name", sn); + } + } + } + ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (stmt, "right")); + return; + } + + if (strcmp (kind, "call") == 0) { + ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (stmt, "expression")); + return; + } + + if (strcmp (kind, "if") == 0) { + ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (stmt, "expression")); + cJSON *s2; + { + ASTSemScope then_scope = {0}; + then_scope.parent = scope; + then_scope.function_nr = scope->function_nr; + then_scope.block_depth = scope->block_depth + 1; + cJSON_ArrayForEach (s2, cJSON_GetObjectItemCaseSensitive (stmt, "then")) { + ast_sem_check_stmt (st, &then_scope, s2); + } + ast_sem_propagate_block_vars (st, scope, &then_scope); + } + { + ASTSemScope list_scope = {0}; + list_scope.parent = scope; + list_scope.function_nr = scope->function_nr; + list_scope.block_depth = scope->block_depth + 1; + cJSON_ArrayForEach (s2, cJSON_GetObjectItemCaseSensitive (stmt, "list")) { + ast_sem_check_stmt (st, &list_scope, s2); + } + ast_sem_propagate_block_vars (st, scope, &list_scope); + } + { + ASTSemScope else_scope = {0}; + else_scope.parent = scope; + else_scope.function_nr = scope->function_nr; + else_scope.block_depth = scope->block_depth + 1; + cJSON_ArrayForEach (s2, cJSON_GetObjectItemCaseSensitive (stmt, "else")) { + ast_sem_check_stmt (st, &else_scope, s2); + } + ast_sem_propagate_block_vars (st, scope, &else_scope); + } + return; + } + + if (strcmp (kind, "while") == 0) { + ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (stmt, "expression")); + ASTSemScope loop_scope = {0}; + loop_scope.parent = scope; + loop_scope.in_loop = 1; + loop_scope.function_nr = scope->function_nr; + loop_scope.block_depth = scope->block_depth + 1; + cJSON *s2; + cJSON_ArrayForEach (s2, cJSON_GetObjectItemCaseSensitive (stmt, "statements")) { + ast_sem_check_stmt (st, &loop_scope, s2); + } + ast_sem_propagate_block_vars (st, scope, &loop_scope); + return; + } + + if (strcmp (kind, "do") == 0) { + ASTSemScope loop_scope = {0}; + loop_scope.parent = scope; + loop_scope.in_loop = 1; + loop_scope.function_nr = scope->function_nr; + loop_scope.block_depth = scope->block_depth + 1; + cJSON *s2; + cJSON_ArrayForEach (s2, cJSON_GetObjectItemCaseSensitive (stmt, "statements")) { + ast_sem_check_stmt (st, &loop_scope, s2); + } + ast_sem_propagate_block_vars (st, scope, &loop_scope); + ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (stmt, "expression")); + return; + } + + if (strcmp (kind, "for") == 0) { + ASTSemScope loop_scope = {0}; + loop_scope.parent = scope; + loop_scope.in_loop = 1; + loop_scope.function_nr = scope->function_nr; + loop_scope.block_depth = scope->block_depth + 1; + /* init may be a var/def statement or expression */ + cJSON *init = cJSON_GetObjectItemCaseSensitive (stmt, "init"); + 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)) { + ast_sem_check_stmt (st, &loop_scope, init); + } else { + ast_sem_check_expr (st, &loop_scope, init); + } + } + ast_sem_check_expr (st, &loop_scope, cJSON_GetObjectItemCaseSensitive (stmt, "test")); + ast_sem_check_expr (st, &loop_scope, cJSON_GetObjectItemCaseSensitive (stmt, "update")); + cJSON *s2; + cJSON_ArrayForEach (s2, cJSON_GetObjectItemCaseSensitive (stmt, "statements")) { + ast_sem_check_stmt (st, &loop_scope, s2); + } + ast_sem_propagate_block_vars (st, scope, &loop_scope); + return; + } + + if (strcmp (kind, "return") == 0 || strcmp (kind, "go") == 0) { + ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (stmt, "expression")); + return; + } + + if (strcmp (kind, "disrupt") == 0) { + return; + } + + if (strcmp (kind, "break") == 0) { + if (!ast_sem_in_loop (scope)) { + ast_sem_error (st, stmt, "'break' used outside of loop"); + } + return; + } + + if (strcmp (kind, "continue") == 0) { + if (!ast_sem_in_loop (scope)) { + ast_sem_error (st, stmt, "'continue' used outside of loop"); + } + return; + } + + if (strcmp (kind, "block") == 0) { + ASTSemScope block_scope = {0}; + block_scope.parent = scope; + block_scope.function_nr = scope->function_nr; + block_scope.block_depth = scope->block_depth + 1; + cJSON *s2; + cJSON_ArrayForEach (s2, cJSON_GetObjectItemCaseSensitive (stmt, "statements")) { + ast_sem_check_stmt (st, &block_scope, s2); + } + ast_sem_propagate_block_vars (st, scope, &block_scope); + return; + } + + if (strcmp (kind, "label") == 0) { + ast_sem_check_stmt (st, scope, cJSON_GetObjectItemCaseSensitive (stmt, "statement")); + return; + } + + if (strcmp (kind, "function") == 0) { + /* Function declaration — register name, then check body in new scope */ + const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (stmt, "name")); + if (name) ast_sem_add_var (scope, name, 0, "function", scope->function_nr); + + cJSON *fn_nr_node = cJSON_GetObjectItemCaseSensitive (stmt, "function_nr"); + int fn_nr = fn_nr_node ? (int)cJSON_GetNumberValue (fn_nr_node) : scope->function_nr; + + ASTSemScope fn_scope = {0}; + fn_scope.parent = scope; + fn_scope.function_nr = fn_nr; + fn_scope.is_function_scope = 1; + + cJSON_AddNumberToObject (stmt, "outer", scope->function_nr); + + cJSON *param; + cJSON_ArrayForEach (param, cJSON_GetObjectItemCaseSensitive (stmt, "list")) { + const char *pname = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (param, "name")); + if (pname) ast_sem_add_var (&fn_scope, pname, 1, "input", fn_nr); + /* Check default value expressions */ + cJSON *def_val = cJSON_GetObjectItemCaseSensitive (param, "expression"); + if (def_val) ast_sem_check_expr (st, &fn_scope, def_val); + } + + /* Pre-register all var/def/function declarations for mutual recursion */ + ast_sem_predeclare_vars (st, &fn_scope, cJSON_GetObjectItemCaseSensitive (stmt, "statements")); + + cJSON *s2; + cJSON_ArrayForEach (s2, cJSON_GetObjectItemCaseSensitive (stmt, "statements")) { + ast_sem_check_stmt (st, &fn_scope, s2); + } + + /* Check disruption clause */ + cJSON *disruption = cJSON_GetObjectItemCaseSensitive (stmt, "disruption"); + if (disruption) { + cJSON_ArrayForEach (s2, disruption) { + ast_sem_check_stmt (st, &fn_scope, s2); + } + } + + /* Build scope record and attach to scopes array */ + int nr_slots, nr_close; + cJSON *rec = ast_sem_build_scope_record (&fn_scope, &nr_slots, &nr_close); + cJSON_AddItemToArray (st->scopes_array, rec); + cJSON_AddNumberToObject (stmt, "nr_slots", nr_slots); + cJSON_AddNumberToObject (stmt, "nr_close_slots", nr_close); + return; + } +} + +/* Run the semantic pass on a parsed AST, adding errors to the AST */ +static void ast_semantic_check (cJSON *ast, cJSON **errors_out, + cJSON **scopes_out, cJSON **intrinsics_out) { + ASTSemState st = {0}; + st.scopes_array = cJSON_CreateArray (); + + ASTSemScope global_scope = {0}; + global_scope.function_nr = 0; + global_scope.is_function_scope = 1; + + /* Process top-level function declarations first (they are hoisted) */ + cJSON *stmt; + cJSON_ArrayForEach (stmt, cJSON_GetObjectItemCaseSensitive (ast, "functions")) { + const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (stmt, "name")); + if (name) ast_sem_add_var (&global_scope, name, 0, "function", 0); + } + + /* Check all statements (var/def are registered as they are encountered) */ + cJSON_ArrayForEach (stmt, cJSON_GetObjectItemCaseSensitive (ast, "statements")) { + ast_sem_check_stmt (&st, &global_scope, stmt); + } + + /* Check function bodies */ + cJSON_ArrayForEach (stmt, cJSON_GetObjectItemCaseSensitive (ast, "functions")) { + ast_sem_check_stmt (&st, &global_scope, stmt); + } + + /* Build program scope record (function_nr 0) and prepend to scopes array */ + int nr_slots, nr_close; + cJSON *prog_rec = ast_sem_build_scope_record (&global_scope, &nr_slots, &nr_close); + /* Prepend: detach all children, add prog_rec, re-add children */ + cJSON *existing = st.scopes_array->child; + st.scopes_array->child = NULL; + cJSON_AddItemToArray (st.scopes_array, prog_rec); + if (existing) { + cJSON *last = prog_rec; + last->next = existing; + existing->prev = last; + } + + /* Build intrinsics array */ + cJSON *intr_arr = cJSON_CreateArray (); + for (int i = 0; i < st.intrinsic_count; i++) { + cJSON_AddItemToArray (intr_arr, cJSON_CreateString (st.intrinsics[i])); + } + + *errors_out = st.errors; + *scopes_out = st.scopes_array; + *intrinsics_out = intr_arr; +} + +cJSON *JS_ASTTree (const char *source, size_t len, const char *filename) { + ASTParseState s; + memset (&s, 0, sizeof (s)); + + s.filename = filename; + s.buf_start = (const uint8_t *)source; + s.buf_ptr = (const uint8_t *)source; + s.buf_end = (const uint8_t *)source + len; + s.function_nr = 1; + s.errors = NULL; + s.has_error = 0; + s.lc_cache.ptr = s.buf_start; + s.lc_cache.buf_start = s.buf_start; + + /* Get first token */ + ast_next_token (&s); + + /* Parse program */ + cJSON *ast = ast_parse_program (&s); + if (!ast) { + if (s.errors) cJSON_Delete (s.errors); + return NULL; + } + + /* Run semantic pass - only if parsing succeeded without errors */ + if (!s.has_error) { + cJSON *sem_errors = NULL; + cJSON *scopes = NULL; + cJSON *intrinsics = NULL; + ast_semantic_check (ast, &sem_errors, &scopes, &intrinsics); + + /* Attach scopes and intrinsics to AST */ + if (scopes) cJSON_AddItemToObject (ast, "scopes", scopes); + if (intrinsics) cJSON_AddItemToObject (ast, "intrinsics", intrinsics); + + if (sem_errors) { + cJSON_AddItemToObject (ast, "errors", sem_errors); + } + } + + if (s.errors) { + cJSON *existing = cJSON_GetObjectItemCaseSensitive (ast, "errors"); + if (existing) { + /* Append parse errors to existing semantic errors */ + cJSON *err; + cJSON *next; + for (err = s.errors->child; err; err = next) { + next = err->next; + cJSON_DetachItemViaPointer (s.errors, err); + cJSON_AddItemToArray (existing, err); + } + cJSON_Delete (s.errors); + } else { + cJSON_AddItemToObject (ast, "errors", s.errors); + } + } + + return ast; +} + +char *JS_AST (const char *source, size_t len, const char *filename) { + cJSON *ast = JS_ASTTree (source, len, filename); + if (!ast) return NULL; + char *json = cJSON_PrintUnformatted (ast); + cJSON_Delete (ast); + return json; +} + +/* Build a token object for the tokenizer output */ diff --git a/source/quickjs-internal.h b/source/quickjs-internal.h new file mode 100644 index 00000000..b17464cc --- /dev/null +++ b/source/quickjs-internal.h @@ -0,0 +1,2007 @@ +#ifndef QUICKJS_INTERNAL_H +#define QUICKJS_INTERNAL_H + +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(__APPLE__) +#include +#elif defined(__linux__) || defined(__GLIBC__) +#include +#elif defined(__FreeBSD__) +#include +#endif + +#include "cutils.h" +#include "dtoa.h" +#include "libregexp.h" +#include "libunicode.h" +#include "list.h" +#include "quickjs.h" +#include "cJSON.h" +#include "blob.h" +#include "nota.h" +#include "wota.h" + +#define OPTIMIZE 1 +#define SHORT_OPCODES 1 +#if defined(EMSCRIPTEN) +#define DIRECT_DISPATCH 0 +#else +#define DIRECT_DISPATCH 1 +#endif + +#if !defined(_WIN32) +/* define it if printf uses the RNDN rounding mode instead of RNDNA */ +#define CONFIG_PRINTF_RNDN +#endif + +#if !defined(EMSCRIPTEN) +/* enable stack limitation */ +#define CONFIG_STACK_CHECK +#endif + +/* dump object free */ +// #define DUMP_FREE +// #define DUMP_CLOSURE +/* dump the bytecode of the compiled functions: combination of bits + 1: dump pass 3 final byte code + 2: dump pass 2 code + 4: dump pass 1 code + 8: dump stdlib functions + 16: dump bytecode in hex + 32: dump line number table + 64: dump compute_stack_size + */ +// #define DUMP_BYTECODE (1) +/* dump GC summary: old/new heap, recovery %, heap growth */ +// #define DUMP_GC +/* dump detailed GC: roots, scanning, object traversal (implies DUMP_GC) */ +// #define DUMP_GC_DETAIL +#ifdef DUMP_GC_DETAIL +#define DUMP_GC +#endif +/* dump objects freed by the garbage collector */ +// #define DUMP_GC_FREE +/* dump memory usage before running the garbage collector */ +// #define DUMP_MEM +// #define DUMP_OBJECTS /* dump objects in JS_FreeContext */ +// #define DUMP_READ_OBJECT +// #define DUMP_ROPE_REBALANCE + +/* test the GC by forcing it before each object allocation */ +#define FORCE_GC_AT_MALLOC + +#define POISON_HEAP +/* POISON_HEAP: Use ASan's memory poisoning to detect stale pointer access */ +#ifdef POISON_HEAP +#if defined(__has_feature) + #if __has_feature(address_sanitizer) + #define HAVE_ASAN 1 + #endif +#elif defined(__SANITIZE_ADDRESS__) + #define HAVE_ASAN 1 +#endif + +#ifdef HAVE_ASAN +#include +#define gc_poison_region(addr, size) __asan_poison_memory_region((addr), (size)) +#define gc_unpoison_region(addr, size) __asan_unpoison_memory_region((addr), (size)) +#else +/* Fallback: no-op when not building with ASan */ +#define gc_poison_region(addr, size) ((void)0) +#define gc_unpoison_region(addr, size) ((void)0) +#endif +#endif /* POISON_HEAP */ + +#ifdef HAVE_ASAN +static struct JSContext *__asan_js_ctx; +#endif + +/* Forward declarations for heap object types */ +typedef struct JSArray JSArray; +typedef struct JSBlob JSBlob; +typedef struct JSText JSText; +typedef struct JSRecord JSRecord; +typedef struct JSFunction JSFunction; +typedef struct JSFrame JSFrame; +typedef struct JSCode JSCode; + +#define OBJHDR_CAP_SHIFT 8u +#define OBJHDR_CAP_MASK (((objhdr_t)1ull << 56) - 1ull) + +#define JS_MKPTR(ptr) (((JSValue)(uintptr_t)(ptr)) | JS_TAG_PTR) +#define JS_MKFWD(ptr) (((objhdr_t)(uintptr_t)(ptr) << OBJHDR_FWD_PTR_SHIFT) | OBJ_FORWARD) +#define JS_VALUE_GET_FWD_PTR(v) ((void*)(uintptr_t)(((v) >> OBJHDR_FWD_PTR_SHIFT) & OBJHDR_FWD_PTR_MASK)) + +/* For OBJ_FORWARD type: bits 3-63 contain a 61-bit pointer */ +#define OBJHDR_FWD_PTR_SHIFT 3u +#define OBJHDR_FWD_PTR_MASK (((objhdr_t)1ull << 61) - 1ull) +#define objhdr_fwd_ptr(h) ((void*)(uintptr_t)(((h) >> OBJHDR_FWD_PTR_SHIFT) & OBJHDR_FWD_PTR_MASK)) +#define objhdr_make_fwd(ptr) (((objhdr_t)(uintptr_t)(ptr) << OBJHDR_FWD_PTR_SHIFT) | OBJ_FORWARD) + +/* Extract pointer (clear low bits) */ +#define JS_VALUE_GET_PTR(v) ((void *)((v) & ~((JSValue)(JSW - 1)))) + + +static inline JS_BOOL JS_VALUE_IS_TEXT (JSValue v) { + int tag = JS_VALUE_GET_TAG (v); + return tag == JS_TAG_STRING_IMM || (JS_IsPtr(v) && objhdr_type(*(objhdr_t *)JS_VALUE_GET_PTR(v)) == OBJ_TEXT); +} + +static inline JS_BOOL JS_VALUE_IS_NUMBER (JSValue v) { + int tag = JS_VALUE_GET_TAG (v); + return tag == JS_TAG_INT || tag == JS_TAG_SHORT_FLOAT; +} + +/* JS_KEY_* macros: JSValue immediate ASCII strings for common property names. + These replace JS_ATOM_* for use with the new JSValue-based property API. */ +#define _JS_KEY1(c1) \ + ((JSValue)JS_TAG_STRING_IMM | ((JSValue)1 << 5) | ((JSValue)(c1) << 8)) +#define _JS_KEY2(c1, c2) \ + ((JSValue)JS_TAG_STRING_IMM | ((JSValue)2 << 5) | ((JSValue)(c1) << 8) \ + | ((JSValue)(c2) << 16)) +#define _JS_KEY3(c1, c2, c3) \ + ((JSValue)JS_TAG_STRING_IMM | ((JSValue)3 << 5) | ((JSValue)(c1) << 8) \ + | ((JSValue)(c2) << 16) | ((JSValue)(c3) << 24)) +#define _JS_KEY4(c1, c2, c3, c4) \ + ((JSValue)JS_TAG_STRING_IMM | ((JSValue)4 << 5) | ((JSValue)(c1) << 8) \ + | ((JSValue)(c2) << 16) | ((JSValue)(c3) << 24) | ((JSValue)(c4) << 32)) +#define _JS_KEY5(c1, c2, c3, c4, c5) \ + ((JSValue)JS_TAG_STRING_IMM | ((JSValue)5 << 5) | ((JSValue)(c1) << 8) \ + | ((JSValue)(c2) << 16) | ((JSValue)(c3) << 24) | ((JSValue)(c4) << 32) \ + | ((JSValue)(c5) << 40)) +#define _JS_KEY6(c1, c2, c3, c4, c5, c6) \ + ((JSValue)JS_TAG_STRING_IMM | ((JSValue)6 << 5) | ((JSValue)(c1) << 8) \ + | ((JSValue)(c2) << 16) | ((JSValue)(c3) << 24) | ((JSValue)(c4) << 32) \ + | ((JSValue)(c5) << 40) | ((JSValue)(c6) << 48)) +#define _JS_KEY7(c1, c2, c3, c4, c5, c6, c7) \ + ((JSValue)JS_TAG_STRING_IMM | ((JSValue)7 << 5) | ((JSValue)(c1) << 8) \ + | ((JSValue)(c2) << 16) | ((JSValue)(c3) << 24) | ((JSValue)(c4) << 32) \ + | ((JSValue)(c5) << 40) | ((JSValue)(c6) << 48) | ((JSValue)(c7) << 56)) + +/* Common property keys as JSValue immediate strings (max 7 ASCII chars) */ +/* Empty string: tag with length 0 */ +#define JS_KEY_empty ((JSValue)JS_TAG_STRING_IMM) +#define JS_KEY_name _JS_KEY4 ('n', 'a', 'm', 'e') +#define JS_KEY_length _JS_KEY6 ('l', 'e', 'n', 'g', 't', 'h') +#define JS_KEY_message _JS_KEY7 ('m', 'e', 's', 's', 'a', 'g', 'e') +#define JS_KEY_stack _JS_KEY5 ('s', 't', 'a', 'c', 'k') +#define JS_KEY_cause _JS_KEY5 ('c', 'a', 'u', 's', 'e') +#define JS_KEY_errors _JS_KEY6 ('e', 'r', 'r', 'o', 'r', 's') +#define JS_KEY_Error _JS_KEY5 ('E', 'r', 'r', 'o', 'r') +#define JS_KEY_raw _JS_KEY3 ('r', 'a', 'w') +#define JS_KEY_flags _JS_KEY5 ('f', 'l', 'a', 'g', 's') +#define JS_KEY_source _JS_KEY6 ('s', 'o', 'u', 'r', 'c', 'e') +#define JS_KEY_exec _JS_KEY4 ('e', 'x', 'e', 'c') +#define JS_KEY_toJSON _JS_KEY6 ('t', 'o', 'J', 'S', 'O', 'N') +#define JS_KEY_eval _JS_KEY4 ('e', 'v', 'a', 'l') +#define JS_KEY_this _JS_KEY4 ('t', 'h', 'i', 's') +#define JS_KEY_true _JS_KEY4 ('t', 'r', 'u', 'e') +#define JS_KEY_false _JS_KEY5 ('f', 'a', 'l', 's', 'e') +#define JS_KEY_null _JS_KEY4 ('n', 'u', 'l', 'l') +#define JS_KEY_NaN _JS_KEY3 ('N', 'a', 'N') +#define JS_KEY_default _JS_KEY7 ('d', 'e', 'f', 'a', 'u', 'l', 't') +#define JS_KEY_value _JS_KEY5 ('v', 'a', 'l', 'u', 'e') +#define JS_KEY_index _JS_KEY5 ('i', 'n', 'd', 'e', 'x') +#define JS_KEY_input _JS_KEY5 ('i', 'n', 'p', 'u', 't') +#define JS_KEY_groups _JS_KEY6 ('g', 'r', 'o', 'u', 'p', 's') +#define JS_KEY_indices _JS_KEY7 ('i', 'n', 'd', 'i', 'c', 'e', 's') +#define JS_KEY_let _JS_KEY3 ('l', 'e', 't') +#define JS_KEY_var _JS_KEY3 ('v', 'a', 'r') +#define JS_KEY_new _JS_KEY3 ('n', 'e', 'w') +#define JS_KEY_of _JS_KEY2 ('o', 'f') +#define JS_KEY_yield _JS_KEY5 ('y', 'i', 'e', 'l', 'd') +#define JS_KEY_async _JS_KEY5 ('a', 's', 'y', 'n', 'c') +#define JS_KEY_target _JS_KEY6 ('t', 'a', 'r', 'g', 'e', 't') +#define JS_KEY_from _JS_KEY4 ('f', 'r', 'o', 'm') +#define JS_KEY_meta _JS_KEY4 ('m', 'e', 't', 'a') +#define JS_KEY_as _JS_KEY2 ('a', 's') +#define JS_KEY_get _JS_KEY3 ('g', 'e', 't') +#define JS_KEY_set _JS_KEY3 ('s', 'e', 't') +#define JS_KEY_with _JS_KEY4 ('w', 'i', 't', 'h') + +/* Internal variable names */ +#define JS_KEY__ret_ _JS_KEY5 ('<', 'r', 'e', 't', '>') +#define JS_KEY__eval_ _JS_KEY6 ('<', 'e', 'v', 'a', 'l', '>') +#define JS_KEY__var_ _JS_KEY5 ('<', 'v', 'a', 'r', '>') + +/* Keys for strings > 7 chars - these use string literals and are created at + runtime. The caller must free the returned JSValue if it's a heap string. */ +#define JS_KEY_STR(ctx, str) JS_NewStringLen ((ctx), (str), sizeof (str) - 1) + +#define KEY_GET_STR_BUF_SIZE 256 + +/* JS_IsPretext, JS_KeyGetStr, JS_PushGCRef, JS_PopGCRef, JS_AddGCRef, JS_DeleteGCRef + are defined after JSContext (they need its fields) */ + +/* Forward declarations for memory functions (now declared in quickjs.h) */ +void *js_realloc (JSContext *ctx, void *ptr, size_t size); + +/* Forward declaration for string_get */ +static inline int string_get (const JSText *p, int idx); + +#undef JS_PUSH_VALUE +#undef JS_POP_VALUE + +#define JS_PUSH_VALUE(ctx, v) \ + do { \ + v##_ref.prev = ctx->top_gc_ref; \ + ctx->top_gc_ref = &v##_ref; \ + v##_ref.val = v; \ + } while (0) + +#define JS_POP_VALUE(ctx, v) \ + do { \ + v = v##_ref.val; \ + ctx->top_gc_ref = v##_ref.prev; \ + } while (0) + +/* JS_Invoke - invoke method on object using JSValue key */ +static JSValue JS_Invoke (JSContext *ctx, JSValue this_val, JSValue method, int argc, JSValue *argv); + +enum { + /* classid tag */ /* union usage | properties */ + JS_CLASS_OBJECT = 1, /* must be first */ + JS_CLASS_ERROR, + JS_CLASS_REGEXP, /* u.regexp */ + JS_CLASS_BLOB, /* u.opaque (blob *) */ + + JS_CLASS_INIT_COUNT, /* last entry for predefined classes */ +}; + +typedef enum JSErrorEnum { + JS_EVAL_ERROR, + JS_RANGE_ERROR, + JS_REFERENCE_ERROR, + JS_SYNTAX_ERROR, + JS_TYPE_ERROR, + JS_URI_ERROR, + JS_INTERNAL_ERROR, + JS_AGGREGATE_ERROR, + + JS_NATIVE_ERROR_COUNT, /* number of different NativeError objects */ +} JSErrorEnum; + +/* the variable and scope indexes must fit on 16 bits. The (-1) and + ARG_SCOPE_END values are reserved. */ +#define JS_MAX_LOCAL_VARS 65534 +#define JS_STACK_SIZE_MAX 65534 +/* Max string length matches header capacity (56 bits on 64-bit builds) */ +#define JS_STRING_LEN_MAX OBJHDR_CAP_MASK + +#define __exception __attribute__ ((warn_unused_result)) + +/* Forward declaration for bytecode freeing */ +struct JSFunctionBytecode; + +#define JS_VALUE_GET_ARRAY(v) ((JSArray *)chase (v)) +#define JS_VALUE_GET_OBJ(v) ((JSRecord *)chase (v)) +#define JS_VALUE_GET_TEXT(v) ((JSText *)chase (v)) +#define JS_VALUE_GET_BLOB(v) ((JSBlob *)JS_VALUE_GET_PTR (v)) +#define JS_VALUE_GET_FUNCTION(v) ((JSFunction *)chase (v)) +#define JS_VALUE_GET_FRAME(v) ((JSFrame *)chase (v)) +#define JS_VALUE_GET_CODE(v) ((JSFunctionBytecode *)JS_VALUE_GET_PTR (v)) +#define JS_VALUE_GET_STRING(v) ((JSText *)chase (v)) + +/* Compatibility: JS_TAG_STRING is an alias for text type checks */ +#define JS_TAG_STRING JS_TAG_STRING_IMM + +/* JS_TAG_FUNCTION doesn't exist in new encoding - use JS_IsFunction check instead */ +#define JS_TAG_FUNCTION 0xFE /* dummy value, never matches any tag */ + +/* JS_ThrowMemoryError is an alias for JS_ThrowOutOfMemory */ +#define JS_ThrowMemoryError(ctx) JS_ThrowOutOfMemory(ctx) + +/* Helper to set cap in objhdr */ +static inline objhdr_t objhdr_set_cap56 (objhdr_t h, uint64_t cap) { + return (h & 0xFF) | ((cap & OBJHDR_CAP_MASK) << OBJHDR_CAP_SHIFT); +} + +typedef enum OPCodeEnum OPCodeEnum; + +/* ============================================================ + Buddy Allocator for Actor Memory Blocks + ============================================================ */ + +/* Platform-specific minimum block size */ +#if defined(__LP64__) || defined(_WIN64) + #define BUDDY_MIN_ORDER 10 /* 1KB minimum on 64-bit */ +#else + #define BUDDY_MIN_ORDER 9 /* 512B minimum on 32-bit */ +#endif +#define BUDDY_MAX_ORDER 28 /* 256MB maximum */ +#define BUDDY_LEVELS (BUDDY_MAX_ORDER - BUDDY_MIN_ORDER + 1) +#define BUDDY_POOL_SIZE (1ULL << BUDDY_MAX_ORDER) + +typedef struct BuddyBlock { + struct BuddyBlock *next; + struct BuddyBlock *prev; + uint8_t order; /* log2 of size */ + uint8_t is_free; +} BuddyBlock; + +typedef struct BuddyAllocator { + uint8_t *base; /* 256MB base address */ + size_t total_size; /* 256MB */ + BuddyBlock *free_lists[BUDDY_LEVELS]; + uint8_t initialized; +} BuddyAllocator; + +/* Forward declarations for buddy allocator functions */ +static void buddy_destroy (BuddyAllocator *b); + +struct JSRuntime { + const char *rt_info; + + size_t malloc_limit; + + /* Buddy allocator for actor memory blocks */ + BuddyAllocator buddy; + + /* see JS_SetStripInfo() */ + uint8_t strip_flags; + + /* User data */ + void *user_opaque; +}; + +struct JSClass { + const char *class_name; + JSClassFinalizer *finalizer; + JSClassGCMark *gc_mark; + uint32_t class_id; /* 0 means free entry */ +}; + +#define JS_MODE_BACKTRACE_BARRIER \ + (1 << 3) /* stop backtrace before this frame */ + +typedef struct JSStackFrame { + struct JSStackFrame *prev_frame; /* NULL if first stack frame */ + JSValue cur_func; /* current function, JS_NULL if the frame is detached */ + JSValue *arg_buf; /* arguments */ + JSValue *var_buf; /* variables */ + const uint8_t *cur_pc; /* only used in bytecode functions : PC of the + instruction after the call */ + int arg_count; + int js_mode; /* not supported for C functions */ + JSValue js_frame; /* GC-managed JSFrame (use JS_VALUE_GET_FRAME to access) */ + JSValue *stack_buf; /* operand stack base (for GC scanning) */ + JSValue **p_sp; /* pointer to current sp (for GC scanning) */ +} JSStackFrame; + +/* Heap-allocated VM frame for trampoline execution */ +struct VMFrame { + struct JSFunctionBytecode *b; /* current function bytecode */ + JSContext *ctx; /* execution context / realm */ + const uint8_t *pc; /* program counter */ + + /* Offset-based storage (safe with realloc) */ + int value_stack_base; /* base index into ctx->value_stack */ + int sp_offset; /* sp offset from base */ + int arg_buf_offset; /* arg buffer offset from base (or -1 if aliased) */ + int var_buf_offset; /* var buffer offset from base */ + + /* Continuation info for return */ + const uint8_t *ret_pc; /* where to resume in caller */ + int ret_sp_offset; /* caller's sp before call (for cleanup) */ + int call_argc; /* number of args to clean up */ + int call_has_this; /* whether call had this (method call) */ + + JSValue cur_func; /* current function object */ + JSValue this_obj; /* this binding */ + int arg_count; + int js_mode; + int stack_size_allocated; /* total size allocated for this frame */ +}; + +typedef struct JSFrameRegister { + objhdr_t hdr; // capacity in this is the total number of words of the object, including the 4 words of overhead and all slots + JSValue function; // JSFunction, function object being invoked + JSValue caller; // JSFrameRegister, the frame that called this one + JSValue address; // address of the instruction in the code that should be executed upon return + JSValue slots[]; // inline memory. order is [this][input args][closed over vars][non closed over vars][temporaries] +} JSFrameRegister; /// extra note: when this frame returns, caller should be set to 0. If caller is found to be 0, then the GC can reduce this frame's slots down to [this][input_args][closed over vars]; if no closed over vars it can be totally removed; may happen naturally in GC since it would have no refs? + +/* ============================================================ + Register-Based VM Data Structures + ============================================================ */ + +/* 32-bit instruction encoding (Lua-style) + Formats: + iABC: [op:8][A:8][B:8][C:8] — register ops + iABx: [op:8][A:8][Bx:16] — constant/global loads (unsigned) + iAsBx: [op:8][A:8][sBx:16] — conditional jumps (signed offset) + isJ: [op:8][sJ:24] — unconditional jump (signed offset) */ + +typedef uint32_t MachInstr32; + +typedef struct { uint16_t line; uint16_t col; } MachLineEntry; + +/* Encoding macros */ +#define MACH_ABC(op, a, b, c) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(b)<<16) | ((uint32_t)(c)<<24)) +#define MACH_ABx(op, a, bx) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(bx)<<16)) +#define MACH_AsBx(op, a, sbx) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(uint16_t)(sbx)<<16)) +#define MACH_sJ(op, sj) ((uint32_t)(op) | (((uint32_t)(sj) & 0xFFFFFF) << 8)) + +/* Decoding macros */ +#define MACH_GET_OP(i) ((i) & 0xFF) +#define MACH_GET_A(i) (((i) >> 8) & 0xFF) +#define MACH_GET_B(i) (((i) >> 16) & 0xFF) +#define MACH_GET_C(i) (((i) >> 24) & 0xFF) +#define MACH_GET_Bx(i) ((i) >> 16) +#define MACH_GET_sBx(i) ((int16_t)((i) >> 16)) +#define MACH_GET_sJ(i) ((int32_t)((i) & 0xFFFFFF00) >> 8) + +typedef enum MachOpcode { + /* Constants & Loading */ + MACH_LOADK, /* R(A) = K(Bx) — load from constant pool (ABx) */ + MACH_LOADI, /* R(A) = (int16_t)sBx — load small integer (AsBx) */ + MACH_LOADNULL, /* R(A) = null (A only) */ + MACH_LOADTRUE, /* R(A) = true (A only) */ + MACH_LOADFALSE, /* R(A) = false (A only) */ + + /* Movement */ + MACH_MOVE, /* R(A) = R(B) */ + + /* Arithmetic (ABC) */ + MACH_ADD, /* R(A) = R(B) + R(C) */ + MACH_SUB, /* R(A) = R(B) - R(C) */ + MACH_MUL, /* R(A) = R(B) * R(C) */ + MACH_DIV, /* R(A) = R(B) / R(C) */ + MACH_MOD, /* R(A) = R(B) % R(C) */ + MACH_POW, /* R(A) = R(B) ** R(C) */ + MACH_NEG, /* R(A) = -R(B) */ + MACH_INC, /* R(A) = R(B) + 1 */ + MACH_DEC, /* R(A) = R(B) - 1 */ + + /* Comparison (ABC) */ + MACH_EQ, /* R(A) = (R(B) == R(C)) */ + MACH_NEQ, /* R(A) = (R(B) != R(C)) */ + MACH_LT, /* R(A) = (R(B) < R(C)) */ + MACH_LE, /* R(A) = (R(B) <= R(C)) */ + MACH_GT, /* R(A) = (R(B) > R(C)) */ + MACH_GE, /* R(A) = (R(B) >= R(C)) */ + + /* Logical/Bitwise */ + MACH_LNOT, /* R(A) = !R(B) */ + MACH_BNOT, /* R(A) = ~R(B) */ + MACH_BAND, /* R(A) = R(B) & R(C) */ + MACH_BOR, /* R(A) = R(B) | R(C) */ + MACH_BXOR, /* R(A) = R(B) ^ R(C) */ + MACH_SHL, /* R(A) = R(B) << R(C) */ + MACH_SHR, /* R(A) = R(B) >> R(C) */ + MACH_USHR, /* R(A) = R(B) >>> R(C) */ + + /* Property access */ + MACH_GETFIELD, /* R(A) = R(B)[K(C)] — named property */ + MACH_SETFIELD, /* R(A)[K(B)] = R(C) — named property */ + MACH_GETINDEX, /* R(A) = R(B)[R(C)] — computed property */ + MACH_SETINDEX, /* R(A)[R(B)] = R(C) — computed property */ + + /* Unbound variable access (ABx) */ + MACH_GETNAME, /* R(A) = resolve(K(Bx)) — compiler placeholder, patched by link */ + MACH_GETINTRINSIC, /* R(A) = global[K(Bx)] — post-link, intrinsic/built-in */ + MACH_GETENV, /* R(A) = env[K(Bx)] — post-link, module environment */ + + /* Closure access (ABC) */ + MACH_GETUP, /* R(A) = outer_frame[B].slots[C] */ + MACH_SETUP, /* outer_frame[B].slots[C] = R(A) */ + + /* Control flow */ + MACH_JMP, /* pc += sJ — unconditional (isJ format) */ + MACH_JMPTRUE, /* if R(A): pc += sBx — (iAsBx format) */ + MACH_JMPFALSE, /* if !R(A): pc += sBx — (iAsBx format) */ + MACH_JMPNULL, /* if R(A)==null: pc += sBx */ + + /* Function calls — Lua-style consecutive registers */ + MACH_CALL, /* Call R(A) with B args R(A+1)..R(A+B), C=0 discard, C=1 keep result in R(A) */ + MACH_RETURN, /* Return R(A) */ + MACH_RETNIL, /* Return null */ + + /* Object/array creation */ + MACH_NEWOBJECT, /* R(A) = {} */ + MACH_NEWARRAY, /* R(A) = new array, B = element count in R(A+1)..R(A+B) */ + MACH_CLOSURE, /* R(A) = closure(functions[Bx]) (ABx) */ + + MACH_THROW, /* disrupt — trigger disruption */ + + MACH_PUSH, /* push R(B) onto array R(A) */ + MACH_POP, /* R(A) = pop last element from array R(B) */ + + MACH_DELETE, /* R(A) = delete R(B)[K(C)] — named property delete */ + MACH_DELETEINDEX, /* R(A) = delete R(B)[R(C)] — computed property delete */ + MACH_HASPROP, /* R(A) = R(C) in R(B) — has property check */ + MACH_REGEXP, /* R(A) = regexp(K(B), K(C)) — regex literal */ + + MACH_CALLMETHOD, /* Method call: R(A)=obj, B=nargs in R(A+2)..R(A+1+B), C=cpool key */ + + MACH_EQ_TOL, /* R(A) = eq_tol(R(B), R(B+1), R(B+2)), C=3 */ + MACH_NEQ_TOL, /* R(A) = ne_tol(R(B), R(B+1), R(B+2)), C=3 */ + + MACH_NOP, + + MACH_OP_COUNT +} MachOpcode; + +static const char *mach_opcode_names[MACH_OP_COUNT] = { + [MACH_LOADK] = "loadk", + [MACH_LOADI] = "loadi", + [MACH_LOADNULL] = "loadnull", + [MACH_LOADTRUE] = "loadtrue", + [MACH_LOADFALSE] = "loadfalse", + [MACH_MOVE] = "move", + [MACH_ADD] = "add", + [MACH_SUB] = "sub", + [MACH_MUL] = "mul", + [MACH_DIV] = "div", + [MACH_MOD] = "mod", + [MACH_POW] = "pow", + [MACH_NEG] = "neg", + [MACH_INC] = "inc", + [MACH_DEC] = "dec", + [MACH_EQ] = "eq", + [MACH_NEQ] = "neq", + [MACH_LT] = "lt", + [MACH_LE] = "le", + [MACH_GT] = "gt", + [MACH_GE] = "ge", + [MACH_LNOT] = "lnot", + [MACH_BNOT] = "bnot", + [MACH_BAND] = "band", + [MACH_BOR] = "bor", + [MACH_BXOR] = "bxor", + [MACH_SHL] = "shl", + [MACH_SHR] = "shr", + [MACH_USHR] = "ushr", + [MACH_GETFIELD] = "getfield", + [MACH_SETFIELD] = "setfield", + [MACH_GETINDEX] = "getindex", + [MACH_SETINDEX] = "setindex", + [MACH_GETNAME] = "getname", + [MACH_GETINTRINSIC] = "getintrinsic", + [MACH_GETENV] = "getenv", + [MACH_GETUP] = "getup", + [MACH_SETUP] = "setup", + [MACH_JMP] = "jmp", + [MACH_JMPTRUE] = "jmptrue", + [MACH_JMPFALSE] = "jmpfalse", + [MACH_JMPNULL] = "jmpnull", + [MACH_CALL] = "call", + [MACH_RETURN] = "return", + [MACH_RETNIL] = "retnil", + [MACH_NEWOBJECT] = "newobject", + [MACH_NEWARRAY] = "newarray", + [MACH_CLOSURE] = "closure", + [MACH_THROW] = "throw", + [MACH_PUSH] = "push", + [MACH_POP] = "pop", + [MACH_DELETE] = "delete", + [MACH_DELETEINDEX] = "deleteindex", + [MACH_HASPROP] = "hasprop", + [MACH_REGEXP] = "regexp", + [MACH_CALLMETHOD] = "callmethod", + [MACH_EQ_TOL] = "eq_tol", + [MACH_NEQ_TOL] = "neq_tol", + [MACH_NOP] = "nop", +}; + +/* Compiled register-based code (off-heap, never GC'd). + Created by JS_CompileMach from AST JSON. */ +typedef struct JSCodeRegister { + uint16_t arity; /* number of arguments */ + uint16_t nr_close_slots; /* closed-over variable count */ + uint16_t nr_slots; /* total frame size */ + uint16_t entry_point; /* start instruction (usually 0) */ + + /* Constant pool */ + uint32_t cpool_count; + JSValue *cpool; /* allocated via js_malloc_rt */ + + /* Compiled 32-bit instructions */ + uint32_t instr_count; + MachInstr32 *instructions; + + /* Nested functions (for closure) */ + uint32_t func_count; + struct JSCodeRegister **functions; + + /* Debug info */ + JSValue name; /* function name (stone text) */ + MachLineEntry *line_table; /* [instr_count], parallel to instructions[] */ + char *filename_cstr; /* plain C string for stack trace (js_malloc_rt) */ + char *name_cstr; /* plain C string for stack trace (js_malloc_rt) */ + uint16_t disruption_pc; /* start of disruption handler (0 = none) */ +} JSCodeRegister; + +/* Pre-parsed MCODE for a single function (off-heap, never GC'd). + Created by jsmcode_parse from cJSON MCODE output. + Instructions remain as cJSON pointers for string-based dispatch. */ +typedef struct JSMCode { + uint16_t nr_args; + uint16_t nr_slots; + /* Pre-flattened instruction array (cJSON array items → C array for O(1) access) */ + cJSON **instrs; + uint32_t instr_count; + /* Label map: label string → instruction index */ + struct { const char *name; uint32_t index; } *labels; + uint32_t label_count; + /* Nested function definitions (indexes into top-level functions array) */ + struct JSMCode **functions; + uint32_t func_count; + /* Keep root cJSON alive (owns all the cJSON nodes instrs[] point into) */ + cJSON *json_root; + MachLineEntry *line_table; /* [instr_count], parallel to instrs[] */ + const char *name; /* function name (points into cJSON tree) */ + const char *filename; /* source filename (points into cJSON tree) */ + uint16_t disruption_pc; /* start of disruption handler (0 = none) */ +} JSMCode; + +/* Frame for closures - used by link-time relocation model where closures + reference outer frames via (depth, slot) addressing. + Stores function as JSValue to survive GC movements. */ +typedef struct JSFrame { + objhdr_t header; /* OBJ_FRAME, cap56 = slot count */ + JSValue function; /* JSValue for GC safety (use JS_VALUE_GET_FUNCTION) */ + JSValue caller; /* JSValue for GC safety (unused currently) */ + uint32_t return_pc; + JSValue slots[]; /* args, captured, locals, temps */ +} JSFrame; + +/* Execution state returned by vm_execute_frame */ +typedef enum { + VM_EXEC_NORMAL, /* Continue executing current frame */ + VM_EXEC_RETURN, /* Frame returned, pop and resume caller */ + VM_EXEC_CALL, /* Need to push new frame for call */ + VM_EXEC_EXCEPTION, /* Exception thrown, unwind frames */ +} VMExecState; + +/* Call info for frame push */ +typedef struct { + JSValue func_obj; + JSValue this_obj; + int argc; + JSValue *argv; + const uint8_t *ret_pc; + int ret_sp_offset; + int call_argc; + int call_has_this; + int is_tail_call; +} VMCallInfo; + +static inline objhdr_t objhdr_set_s (objhdr_t h, bool s) { + return s ? (h | OBJHDR_S_MASK) : (h & ~OBJHDR_S_MASK); +} +static inline uint64_t objhdr_cap56 (objhdr_t h) { + return (uint64_t)((h >> OBJHDR_CAP_SHIFT) & OBJHDR_CAP_MASK); +} + +static inline objhdr_t objhdr_make (uint64_t cap56, uint8_t type, bool r, bool a, bool p, bool s) { + objhdr_t h = 0; + h |= ((objhdr_t)(cap56 & OBJHDR_CAP_MASK)) << OBJHDR_CAP_SHIFT; + h |= (objhdr_t)(type & 7u); + if (s) h |= OBJHDR_S_MASK; + if (p) h |= OBJHDR_P_MASK; + if (a) h |= OBJHDR_A_MASK; + if (r) h |= OBJHDR_R_MASK; + return h; +} + +static inline objhdr_t *chase(JSValue v) { + objhdr_t *oh = JS_VALUE_GET_PTR(v); + if (objhdr_type(*oh) != OBJ_FORWARD) return oh; + do { + objhdr_t *next = (objhdr_t*)objhdr_fwd_ptr(*oh); + if (!next) return oh; /* NULL forward pointer (stale reference bug) */ + oh = next; + } while (objhdr_type(*oh) == OBJ_FORWARD); + return oh; +} + + +/* Intrinsic array type - tagged as JS_TAG_PTR with mist_hdr type OBJ_ARRAY */ +typedef struct JSArray { + objhdr_t mist_hdr; /* mist header at offset 0 */ + word_t len; /* current length */ + JSValue values[]; /* inline flexible array member */ +} JSArray; + +/* JSBlob - binary data per memory.md */ +typedef struct JSBlob { + objhdr_t mist_hdr; + word_t length; + uint8_t bits[]; +} JSBlob; + +typedef struct JSText { + objhdr_t hdr; /* mist header */ + word_t length; /* length (or hash for stoned text) */ + word_t packed[]; /* two chars per packed word */ +} JSText; + +typedef struct slot { + JSValue key; + JSValue val; /* use 'val' to match existing code */ +} slot; + +/* Compatibility alias - old code uses JSRecordEntry */ +typedef slot JSRecordEntry; + +typedef struct JSRecord { + objhdr_t mist_hdr; + struct JSRecord *proto; + word_t len; /* number of entries */ + slot slots[]; /* slots[0] reserved: key low32=class_id, key high32=rec_id, val=opaque */ +} JSRecord; + +/* Helper macros to access class_id, rec_id, opaque from slots[0] per memory.md */ +#define REC_GET_CLASS_ID(rec) ((uint32_t)((rec)->slots[0].key & 0xFFFFFFFF)) +#define REC_GET_REC_ID(rec) ((uint32_t)((rec)->slots[0].key >> 32)) +#define REC_SET_CLASS_ID(rec, cid) do { \ + (rec)->slots[0].key = ((rec)->slots[0].key & 0xFFFFFFFF00000000ULL) | (uint64_t)(cid); \ +} while(0) +#define REC_SET_REC_ID(rec, rid) do { \ + (rec)->slots[0].key = ((rec)->slots[0].key & 0xFFFFFFFF) | ((uint64_t)(rid) << 32); \ +} while(0) +#define REC_GET_OPAQUE(rec) ((void*)(uintptr_t)(rec)->slots[0].val) +#define REC_SET_OPAQUE(rec, ptr) do { (rec)->slots[0].val = (JSValue)(uintptr_t)(ptr); } while(0) + +/* ============================================================ + Record key helpers (JSValue keys) + ============================================================ */ +/* GC-SAFE: No allocations. */ +static inline JS_BOOL rec_key_is_empty (JSValue key) { + return JS_IsNull (key); +} + +/* GC-SAFE: No allocations. */ +static inline JS_BOOL rec_key_is_tomb (JSValue key) { + return JS_IsException (key); +} + +typedef struct fash64_state { + uint64_t result; + uint64_t sum; +} fash64_state; + +enum { + FASH64_PRIME_11 = 11111111111111111027ull, + FASH64_PRIME_8 = 8888888888888888881ull, + FASH64_PRIME_3 = 3333333333333333271ull +}; + +static inline void fash64_mul_hi_lo (uint64_t a, uint64_t b, uint64_t *hi, uint64_t *lo) { +#if defined(__SIZEOF_INT128__) + __uint128_t p = (__uint128_t)a * (__uint128_t)b; + *lo = (uint64_t)p; + *hi = (uint64_t)(p >> 64); +#elif defined(_MSC_VER) && defined(_M_X64) + *lo = _umul128 (a, b, hi); +#else + /* Portable fallback (no 128-bit type, no _umul128). */ + uint64_t a0 = (uint32_t)a; + uint64_t a1 = a >> 32; + uint64_t b0 = (uint32_t)b; + uint64_t b1 = b >> 32; + + uint64_t p00 = a0 * b0; + uint64_t p01 = a0 * b1; + uint64_t p10 = a1 * b0; + uint64_t p11 = a1 * b1; + + uint64_t mid = (p00 >> 32) + (uint32_t)p01 + (uint32_t)p10; + *lo = (p00 & 0xffffffffull) | (mid << 32); + *hi = p11 + (p01 >> 32) + (p10 >> 32) + (mid >> 32); +#endif +} + +static inline void fash64_begin (fash64_state *s) { + s->result = (uint64_t)FASH64_PRIME_8; + s->sum = (uint64_t)FASH64_PRIME_3; +} + +static inline void fash64_word (fash64_state *s, uint64_t word) { + uint64_t high, low; + uint64_t mixed = s->result ^ word; + + fash64_mul_hi_lo (mixed, (uint64_t)FASH64_PRIME_11, &high, &low); + + s->sum += high; + s->result = low ^ s->sum; +} + +static inline void fash64_block (fash64_state *s, const uint64_t *block, size_t word_count) { + for (size_t i = 0; i < word_count; i++) + fash64_word (s, block[i]); +} + +static inline uint64_t fash64_end (const fash64_state *s) { return s->result; } + +/* Convenience one-shot helper */ +static inline uint64_t fash64_hash_words (const uint64_t *words, + size_t word_count, + uint64_t extra_word) { + fash64_state s; + fash64_begin (&s); + fash64_block (&s, words, word_count); + fash64_word (&s, extra_word); + return fash64_end (&s); +} + +static inline uint64_t fash64_hash_one (uint64_t word) { + uint64_t high, low; + uint64_t mixed = (uint64_t)FASH64_PRIME_8 ^ word; + fash64_mul_hi_lo (mixed, (uint64_t)FASH64_PRIME_11, &high, &low); + uint64_t sum = (uint64_t)FASH64_PRIME_3 + high; + return low ^ sum; +} + +static inline word_t JSText_len (const JSText *text) { + if (objhdr_s (text->hdr)) return objhdr_cap56 (text->hdr); + return text->length; +} + +static inline JS_BOOL JSText_equal (const JSText *a, const JSText *b) { + word_t len_a = JSText_len (a); + word_t len_b = JSText_len (b); + if (len_a != len_b) return FALSE; + size_t word_count = (len_a + 1) / 2; + return memcmp (a->packed, b->packed, word_count * sizeof (word_t)) == 0; +} + +static JS_BOOL JSText_equal_ascii (const JSText *text, JSValue imm) { + int len = MIST_GetImmediateASCIILen (imm); + if (len != JSText_len (text)) return FALSE; + for (int i = 0; i < len; i++) { + uint32_t c = string_get (text, i); + if (c >= 0x80) return FALSE; + if ((uint8_t)c != (uint8_t)MIST_GetImmediateASCIIChar (imm, i)) + return FALSE; + } + return TRUE; +} + +/* Get hash for a JSText value. + For stoned text (s=1): hash is cached in length field (computed on first access). + For pre-text (s=0): compute hash on the fly. */ + +/* Max length for key strings (identifiers, property names) */ +#define JS_KEY_MAX_LEN 4096 + +/* Forward declarations for stone arena functions (defined after JSContext) */ + +/* must be large enough to have a negligible runtime cost and small + enough to call the interrupt callback often. */ +#define JS_INTERRUPT_COUNTER_INIT 10000 + +struct JSContext { + JSRuntime *rt; + + /* Actor memory block (bump allocation) */ + uint8_t *heap_base; /* start of current block */ + uint8_t *heap_free; /* bump pointer */ + uint8_t *heap_end; /* end of block */ + size_t current_block_size; /* current block size (64KB initially) */ + size_t next_block_size; /* doubles if <10% recovered after GC */ + + /* Stone arena - permanent immutable allocations */ + uint8_t *stone_base; /* stone arena base */ + uint8_t *stone_free; /* stone arena bump pointer */ + uint8_t *stone_end; /* stone arena end */ + + /* Stone text intern table */ + void *st_pages; /* stone page list for large allocations */ + uint32_t *st_text_hash; /* hash table (slot -> id) */ + JSText **st_text_array; /* array of JSText pointers indexed by id */ + uint32_t st_text_size; /* hash table size (power of 2) */ + uint32_t st_text_count; /* number of interned texts */ + uint32_t st_text_resize; /* threshold for resize */ + + uint16_t binary_object_count; + int binary_object_size; + + /* Record key allocator */ + uint32_t rec_key_next; + + JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */ + JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */ + + int class_count; /* size of class_array and class_proto */ + JSClass *class_array; + JSValue *class_proto; + JSValue regexp_ctor; + JSValue native_error_proto[JS_NATIVE_ERROR_COUNT]; + JSValue throw_type_error; + + JSValue global_obj; /* global object (immutable intrinsics) */ + + uint64_t random_state; + + /* when the counter reaches zero, JSRutime.interrupt_handler is called */ + int interrupt_counter; + + /* if NULL, RegExp compilation is not supported */ + JSValue (*compile_regexp) (JSContext *ctx, JSValue pattern, JSValue flags); + void *user_opaque; + + js_hook trace_hook; + int trace_type; + void *trace_data; + + /* Trampoline VM stacks (per actor/context) */ + struct VMFrame *frame_stack; /* array of frames */ + int frame_stack_top; /* current frame index (-1 = empty) */ + int frame_stack_capacity; /* allocated capacity */ + JSValue *value_stack; /* array of JSValues for locals/operands */ + int value_stack_top; /* current top index */ + int value_stack_capacity; /* allocated capacity */ + + /* Register VM frame root (updated by GC when frame moves) */ + JSValue reg_current_frame; /* current JSFrameRegister being executed */ + uint32_t current_register_pc; /* PC at exception time */ + + /* Execution state (moved from JSRuntime — per-actor) */ + JSValue current_exception; + struct JSStackFrame *current_stack_frame; + BOOL current_exception_is_uncatchable : 8; + BOOL in_out_of_memory : 8; + + JSInterruptHandler *interrupt_handler; + void *interrupt_opaque; + + /* Stack overflow protection */ + size_t stack_size; + const uint8_t *stack_top; + const uint8_t *stack_limit; + + /* Parser state (for GC to scan cpool during parsing) */ + struct JSFunctionDef *current_parse_fd; +}; + +/* ============================================================ + Functions that need JSContext definition + ============================================================ */ + + +/* Convert JSValue key (string) to C string in buffer. + Returns buf, filled with the string content or "[?]" if not a string. */ +static inline const char *JS_KeyGetStr (JSContext *ctx, char *buf, size_t buf_size, JSValue key) { + if (JS_IsText (key)) { + const char *cstr = JS_ToCString (ctx, key); + if (cstr) { + snprintf (buf, buf_size, "%s", cstr); + JS_FreeCString (ctx, cstr); + return buf; + } + } + snprintf (buf, buf_size, "[?]"); + return buf; +} + + +/* ============================================================ + Stone Arena Functions + ============================================================ */ + +/* Stone page for large allocations */ +typedef struct StonePage { + struct StonePage *next; + size_t size; + uint8_t data[]; +} StonePage; + +/* Initial stone text table size */ +#define ST_TEXT_INITIAL_SIZE 256 + +/* Allocate from stone arena (permanent, immutable memory) */ + +/* Resize the stone text intern hash table */ + +/* Realloc with slack reporting (for bump allocator) + WARNING: This function is NOT GC-safe! The caller must protect the source + object with a GC ref before calling, and re-chase the pointer after. */ + +/* ============================================================ + GC Public API + ============================================================ */ + +/* Forward declaration for ctx_gc */ +static int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size); + +/* JS_MarkValue - mark a value during GC traversal. + With copying GC, this is a no-op as we discover live objects by tracing. */ + +/* Helper to check if a pointer is in stone memory */ +static inline int is_stone_ptr (JSContext *ctx, void *ptr) { + return (uint8_t *)ptr >= ctx->stone_base && (uint8_t *)ptr < ctx->stone_end; +} + +/* Intern a UTF-32 string as a stone text, returning a JSValue string */ + +/* Create a stoned, interned key from a UTF-8 C string. + Returns immediate ASCII text if ≤7 ASCII chars, otherwise stoned interned + text. The returned JSValue does NOT need to be freed (it's either immediate + or part of the stone arena). */ + +/* Create a key from a UTF-8 string with explicit length */ + +typedef union JSFloat64Union { + double d; + uint64_t u64; + uint32_t u32[2]; +} JSFloat64Union; + +/* JS_IsText is defined in quickjs.h */ + +/* Helper to get array capacity from mist_hdr */ +static inline uint32_t js_array_cap (JSArray *arr) { + return objhdr_cap56 (arr->mist_hdr); +} + +/* JSRegExp: regular expression object data (must come before JSRecord/JSRecord) */ +typedef struct JSRegExp { + char *pattern; /* UTF-8, null-terminated, js_malloc_rt'd */ + uint32_t pattern_len; + uint8_t *bytecode; /* raw lre bytecode, js_malloc_rt'd */ + uint32_t bytecode_len; +} JSRegExp; + +#define obj_is_stone(rec) objhdr_s ((rec)->mist_hdr) +#define obj_set_stone(rec) ((rec)->mist_hdr = objhdr_set_s ((rec)->mist_hdr, true)) + +#define JS_VALUE_GET_RECORD(v) ((JSRecord *)chase (v)) + +/* Get prototype from object (works for both JSRecord and JSRecord since they + * share layout) */ +#define JS_OBJ_GET_PROTO(p) ((JSRecord *)((JSRecord *)(p))->proto) + +/* Initial capacity for new records (mask = 7, 8 slots total) */ +#define JS_RECORD_INITIAL_MASK 7 + +/* ============================================================ + JSRecord Core Operations + ============================================================ */ + +// can check if key by checking for 0 here + + +/* Compare a JSValue key with a C string literal. + Used for comparing with internal names that are too long for immediate + ASCII. */ + +/* GC-SAFE: No allocations. Caller must pass freshly-chased rec. + Find slot for a key in record's own table. + Returns slot index (>0) if found, or -(insert_slot) if not found. */ + +/* GC-SAFE: No allocations. Walks prototype chain via direct pointers. + Caller must pass freshly-chased rec. */ + +/* GC-SAFE: Resize record by allocating a new larger record and copying all data. + Uses GC ref to protect source during allocation. + Updates *pobj to point to the new record. + Returns 0 on success, -1 on failure. */ + +/* GC-SAFE: May call rec_resize which allocates. + Takes JSValue* so the object can be tracked through GC. + Updates *pobj if the record is resized. */ + +/* Helper macros to access class_id and rec_id from slots[0].key per memory.md: + - Low 32 bits of slots[0].key = class_id + - High 32 bits of slots[0].key = rec_id + - slots[0].val = opaque C pointer */ +#define REC_GET_CLASS_ID(rec) ((uint32_t)((rec)->slots[0].key & 0xFFFFFFFF)) +#define REC_GET_REC_ID(rec) ((uint32_t)((rec)->slots[0].key >> 32)) +#define REC_SET_CLASS_ID(rec, cid) do { \ + (rec)->slots[0].key = ((rec)->slots[0].key & 0xFFFFFFFF00000000ULL) | (uint64_t)(cid); \ +} while(0) +#define REC_SET_REC_ID(rec, rid) do { \ + (rec)->slots[0].key = ((rec)->slots[0].key & 0xFFFFFFFF) | ((uint64_t)(rid) << 32); \ +} while(0) +#define REC_GET_OPAQUE(rec) ((void*)(uintptr_t)(rec)->slots[0].val) +#define REC_SET_OPAQUE(rec, ptr) do { (rec)->slots[0].val = (JSValue)(uintptr_t)(ptr); } while(0) + +/* Allocate a new record with specified class_id. + Uses bump allocation. Slots are inline (flexible array member). + Per memory.md: slots[0] is reserved for class_id, rec_id, and opaque. */ + +typedef enum { + JS_FUNC_KIND_C, + JS_FUNC_KIND_BYTECODE, + JS_FUNC_KIND_C_DATA, + JS_FUNC_KIND_REGISTER, /* register-based VM function */ + JS_FUNC_KIND_MCODE, /* MCODE JSON interpreter */ +} JSFunctionKind; + +typedef struct JSFunction { + objhdr_t header; /* must come first */ + JSValue name; /* function name as JSValue text */ + int16_t length; /* arity: max allowed arguments (-1 = variadic) */ + uint8_t kind; + union { + struct { + JSCFunctionType c_function; + uint8_t cproto; + int16_t magic; + } cfunc; + struct { + struct JSFunctionBytecode *function_bytecode; + JSValue outer_frame; /* JSFrame JSValue, lexical parent for closures */ + JSValue env_record; /* stone record, module environment */ + } func; + struct { + JSCodeRegister *code; /* compiled register code (off-heap) */ + JSValue env_record; /* stone record, module environment */ + JSValue outer_frame; /* JSFrame JSValue, for closures */ + } reg; + struct { + JSMCode *code; /* pre-parsed MCODE (off-heap) */ + JSValue outer_frame; /* lexical parent frame for closures */ + JSValue env_record; /* module env or JS_NULL */ + } mcode; + } u; +} JSFunction; + +typedef struct JSClosureVar { + uint8_t is_local : 1; + uint8_t is_arg : 1; + uint8_t is_const : 1; + uint8_t is_lexical : 1; + uint8_t var_kind : 4; /* see JSVarKindEnum */ + /* 8 bits available */ + uint16_t var_idx; /* is_local = TRUE: index to a normal variable of the + parent function. otherwise: index to a closure + variable of the parent function */ + JSValue var_name; +} JSClosureVar; + +#define ARG_SCOPE_INDEX 1 +#define ARG_SCOPE_END (-2) + +typedef struct JSVarScope { + int parent; /* index into fd->scopes of the enclosing scope */ + int first; /* index into fd->vars of the last variable in this scope */ +} JSVarScope; + +typedef enum { + /* XXX: add more variable kinds here instead of using bit fields */ + JS_VAR_NORMAL, + JS_VAR_FUNCTION_DECL, /* lexical var with function declaration */ + JS_VAR_NEW_FUNCTION_DECL, /* lexical var with async/generator + function declaration */ + JS_VAR_CATCH, + JS_VAR_FUNCTION_NAME, /* function expression name */ +} JSVarKindEnum; + +/* XXX: could use a different structure in bytecode functions to save + memory */ +typedef struct JSVarDef { + JSValue var_name; + /* index into fd->scopes of this variable lexical scope */ + int scope_level; + /* during compilation: + - if scope_level = 0: scope in which the variable is defined + - if scope_level != 0: index into fd->vars of the next + variable in the same or enclosing lexical scope + in a bytecode function: + index into fd->vars of the next + variable in the same or enclosing lexical scope + */ + int scope_next; + uint8_t is_const : 1; + uint8_t is_lexical : 1; + uint8_t is_captured : 1; + uint8_t var_kind : 4; /* see JSVarKindEnum */ + /* only used during compilation: function pool index for lexical + variables with var_kind = + JS_VAR_FUNCTION_DECL/JS_VAR_NEW_FUNCTION_DECL or scope level of + the definition of the 'var' variables (they have scope_level = + 0) */ + int func_pool_idx : 24; /* only used during compilation : index in + the constant pool for hoisted function + definition */ +} JSVarDef; + +/* for the encoding of the pc2line table */ +#define PC2LINE_BASE (-1) +#define PC2LINE_RANGE 5 +#define PC2LINE_OP_FIRST 1 +#define PC2LINE_DIFF_PC_MAX ((255 - PC2LINE_OP_FIRST) / PC2LINE_RANGE) + +typedef struct JSFunctionBytecode { + objhdr_t header; /* must come first */ + uint8_t js_mode; + uint8_t has_prototype : 1; /* true if a prototype field is necessary */ + uint8_t has_simple_parameter_list : 1; + uint8_t func_kind : 2; + uint8_t has_debug : 1; + uint8_t read_only_bytecode : 1; + uint8_t is_direct_or_indirect_eval + : 1; /* used by JS_GetScriptOrModuleName() */ + /* XXX: 10 bits available */ + uint8_t *byte_code_buf; /* (self pointer) */ + int byte_code_len; + JSValue func_name; + JSVarDef *vardefs; /* arguments + local variables (arg_count + var_count) + (self pointer) */ + JSClosureVar + *closure_var; /* list of variables in the closure (self pointer) */ + uint16_t arg_count; + uint16_t var_count; + uint16_t defined_arg_count; /* for length function property */ + uint16_t stack_size; /* maximum stack size */ + JSValue *cpool; /* constant pool (self pointer) */ + int cpool_count; + int closure_var_count; + struct { + /* debug info, move to separate structure to save memory? */ + JSValue filename; + int source_len; + int pc2line_len; + uint8_t *pc2line_buf; + char *source; + } debug; +} JSFunctionBytecode; + +/* New simplified compiled unit structure for Phase 1+ simplification. + Replaces JSFunctionBytecode with a simpler model: + - No closure machinery (uses outer_frame chain at runtime) + - Free variables resolved at link time against env + globals + - Nested functions stored as separate units in cpool */ +typedef struct JSCompiledUnit { + objhdr_t header; /* must come first */ + + /* Bytecode (self pointer) */ + uint8_t *byte_code_buf; + int byte_code_len; + + /* Constants - strings, numbers, nested unit refs (self pointer) */ + JSValue *cpool; + int cpool_count; + + /* Stack requirements */ + uint16_t local_count; /* total local slots (args + vars) */ + uint16_t stack_size; /* operand stack depth */ + + /* Flags */ + uint8_t has_debug : 1; + uint8_t read_only_bytecode : 1; + + /* Debug info (optional - only present if has_debug) */ + struct { + JSValue filename; + int source_len; + int pc2line_len; + uint8_t *pc2line_buf; + char *source; + } debug; +} JSCompiledUnit; + +/* ============================================================ + Context-Neutral Module Format (Phase 2+) + Struct definitions are in quickjs.h + ============================================================ */ + +typedef struct JSProperty { + JSValue value; +} JSProperty; + +#define JS_PROP_INITIAL_SIZE 2 +#define JS_PROP_INITIAL_HASH_SIZE 4 /* must be a power of two */ +#define JS_ARRAY_INITIAL_SIZE 2 + +/* Max capacity depends on platform - cap field size varies */ +#ifdef JS_PTR64 +#define JS_ARRAY_MAX_CAP ((word_t)((1ULL << 56) - 1)) +#else +#define JS_ARRAY_MAX_CAP ((word_t)((1UL << 24) - 1)) +#endif + +typedef enum OPCodeFormat { +#define FMT(f) OP_FMT_##f, +#define DEF(id, size, n_pop, n_push, f) +#include "quickjs-opcode.h" +#undef DEF +#undef FMT +} OPCodeFormat; + +enum OPCodeEnum { +#define FMT(f) +#define DEF(id, size, n_pop, n_push, f) OP_##id, +#define def(id, size, n_pop, n_push, f) +#include "quickjs-opcode.h" +#undef def +#undef DEF +#undef FMT + OP_COUNT, /* excluding temporary opcodes */ + /* temporary opcodes : overlap with the short opcodes */ + OP_TEMP_START = OP_nop + 1, + OP___dummy = OP_TEMP_START - 1, +#define FMT(f) +#define DEF(id, size, n_pop, n_push, f) +#define def(id, size, n_pop, n_push, f) OP_##id, +#include "quickjs-opcode.h" +#undef def +#undef DEF +#undef FMT + OP_TEMP_END, +}; + +JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv); +JSValue js_call_bound_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv); +JSValue JS_CallInternal (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv, int flags); +JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue this_obj, int argc, JSValue *argv, JSValue env, JSValue outer_frame); +JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj, + int argc, JSValue *argv, JSValue outer_frame); +int JS_DeleteProperty (JSContext *ctx, JSValue obj, JSValue prop); +JSValue __attribute__ ((format (printf, 2, 3))) +JS_ThrowInternalError (JSContext *ctx, const char *fmt, ...); +__maybe_unused void JS_DumpString (JSRuntime *rt, const JSText *text); +__maybe_unused void JS_DumpObjectHeader (JSRuntime *rt); +__maybe_unused void JS_DumpObject (JSRuntime *rt, JSRecord *rec); +__maybe_unused void JS_DumpGCObject (JSRuntime *rt, objhdr_t *p); +__maybe_unused void JS_DumpValue (JSContext *ctx, const char *str, JSValue val); +void js_dump_value_write (void *opaque, const char *buf, size_t len); +void js_regexp_finalizer (JSRuntime *rt, JSValue val); +JSValue js_new_function (JSContext *ctx, JSFunctionKind kind); + +/* Forward declarations for intrinsics (now declared in quickjs.h) */ + +/* Forward declaration - helper to set cap in objhdr */ +static inline objhdr_t objhdr_set_cap56 (objhdr_t h, uint64_t cap); + +/* JS_VALUE_GET_STRING is an alias for getting JSText from a string value */ +/* Note: Uses chase() for GC safety - already defined at line 293 */ + +/* JS_ThrowMemoryError is an alias for JS_ThrowOutOfMemory */ +#define JS_ThrowMemoryError(ctx) JS_ThrowOutOfMemory(ctx) + +/* GC-SAFE: JS_SetPropertyInternal: same as JS_SetProperty but doesn't check stone. + Internal use only. May trigger GC if record needs to resize. */ + +blob *js_get_blob (JSContext *ctx, JSValue val); +JSValue js_new_string8_len (JSContext *ctx, const char *buf, int len); +JSValue pretext_end (JSContext *ctx, JSText *s); +JSValue js_compile_regexp (JSContext *ctx, JSValue pattern, JSValue flags); +JSValue js_regexp_constructor_internal (JSContext *ctx, JSValue pattern, JSValue bc); +int JS_NewClass1 (JSContext *ctx, JSClassID class_id, const JSClassDef *class_def, const char *name); + +BOOL js_strict_eq (JSContext *ctx, JSValue op1, JSValue op2); +static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_push (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_pop (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_mach_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_stone (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_length (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_reverse (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_proto (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_splat (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_meme (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_fn_apply (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_call (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_modulo (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_neg (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_not (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +JSValue js_cell_text_lower (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +JSValue js_cell_text_upper (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_text_trim (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +JSValue js_cell_text_codepoint (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_text_search (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_text_extract (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +JSValue js_cell_character (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_abs (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_sign (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_floor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_ceiling (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_round (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_trunc (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_whole (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_fraction (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_min (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_max (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_number_remainder (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +static JSValue js_print (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +cJSON *JS_GetStack(JSContext *ctx); +JSValue JS_ThrowOutOfMemory (JSContext *ctx); +JSValue JS_EvalInternal (JSContext *ctx, JSValue this_obj, const char *input, size_t input_len, const char *filename, int flags, int scope_idx); + +JSValue JS_ToNumber (JSContext *ctx, JSValue val); +int JS_SetPropertyValue (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val); +int JS_GetOwnPropertyInternal (JSContext *ctx, + JSValue *desc, + JSRecord *p, + JSValue prop); +/* JS_AddIntrinsicBasicObjects is declared in quickjs.h */ +__exception int js_get_length32 (JSContext *ctx, uint32_t *pres, JSValue obj); +__exception int js_get_length64 (JSContext *ctx, int64_t *pres, JSValue obj); +void free_arg_list (JSContext *ctx, JSValue *tab, uint32_t len); +JSValue *build_arg_list (JSContext *ctx, uint32_t *plen, JSValue *parray_arg); +JSValue js_regexp_toString (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); + +JSValue js_error_toString (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); + + +/* === Inline utility functions (used across modules) === */ + +static inline int is_digit (int c) { return c >= '0' && c <= '9'; } + +static inline int string_get (const JSText *p, int idx) { + int word_idx = idx >> 1; + int shift = (1 - (idx & 1)) * 32; + return (uint32_t)((p->packed[word_idx] >> shift) & 0xFFFFFFFF); +} + +static inline void string_put (JSText *p, int idx, uint32_t c) { + int word_idx = idx >> 1; + int shift = (1 - (idx & 1)) * 32; + uint64_t mask = 0xFFFFFFFFULL << shift; + p->packed[word_idx] = (p->packed[word_idx] & ~mask) | ((uint64_t)c << shift); +} + +/* Get character from any string value (immediate ASCII or JSText) */ +static inline uint32_t js_string_value_get (JSValue v, int idx) { + if (MIST_IsImmediateASCII (v)) { + return MIST_GetImmediateASCIIChar (v, idx); + } else { + JSText *s = JS_VALUE_GET_TEXT (v); + return string_get (s, idx); + } +} + +/* Get length from any string value */ +static inline int js_string_value_len (JSValue v) { + if (MIST_IsImmediateASCII (v)) { + return MIST_GetImmediateASCIILen (v); + } else { + return JSText_len (JS_VALUE_GET_TEXT (v)); + } +} + + +/* System allocator wrappers (not GC-managed) */ +static inline void sys_free (void *ptr) { free (ptr); } +static inline void *sys_malloc (size_t size) { return malloc (size); } +static inline void *sys_realloc (void *ptr, size_t size) { return realloc (ptr, size); } + +/* Parser system allocator wrappers (not GC-managed) */ +static inline void *pjs_malloc (size_t size) { + return malloc (size); +} + +static inline void *pjs_mallocz (size_t size) { + void *ptr = malloc (size); + if (ptr) memset (ptr, 0, size); + return ptr; +} + +static inline void *pjs_realloc (void *ptr, size_t size) { + return realloc (ptr, size); +} + +static inline void pjs_free (void *ptr) { + free (ptr); +} + +static inline int pjs_realloc_array (void **parray, int elem_size, int *psize, int req_size) { + int new_size; + void *new_array; + size_t old_size; + new_size = max_int (req_size, *psize * 3 / 2); + old_size = (size_t)(*psize) * elem_size; + new_array = pjs_realloc (*parray, (size_t)new_size * elem_size); + if (!new_array) return -1; + if (new_size > *psize) { + memset ((char *)new_array + old_size, 0, (size_t)(new_size - *psize) * elem_size); + } + *psize = new_size; + *parray = new_array; + return 0; +} + +static inline int pjs_resize_array (void **parray, int elem_size, int *psize, int req_size) { + if (unlikely (req_size > *psize)) + return pjs_realloc_array (parray, elem_size, psize, req_size); + else + return 0; +} + +/* set the new value and free the old value after */ +static inline void set_value (JSContext *ctx, JSValue *pval, JSValue new_val) { + (void)ctx; + *pval = new_val; +} + +#if !defined(CONFIG_STACK_CHECK) +static inline uintptr_t js_get_stack_pointer (void) { return 0; } +static inline BOOL js_check_stack_overflow (JSContext *ctx, size_t alloca_size) { + return FALSE; +} +#else +static inline uintptr_t js_get_stack_pointer (void) { + return (uintptr_t)__builtin_frame_address (0); +} +static inline BOOL js_check_stack_overflow (JSContext *ctx, size_t alloca_size) { + uintptr_t sp; + sp = js_get_stack_pointer () - alloca_size; + return unlikely (sp < (uintptr_t)ctx->stack_limit); +} +#endif + +void JS_ThrowInterrupted (JSContext *ctx); + +static no_inline __exception int __js_poll_interrupts (JSContext *ctx) { + ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; + if (ctx->interrupt_handler) { + if (ctx->interrupt_handler (ctx->rt, ctx->interrupt_opaque)) { + JS_ThrowInterrupted (ctx); + return -1; + } + } + return 0; +} + +static inline __exception int js_poll_interrupts (JSContext *ctx) { + if (unlikely (--ctx->interrupt_counter <= 0)) { + return __js_poll_interrupts (ctx); + } else { + return 0; + } +} + +/* === Token enum (shared by parser, tokenizer, AST) === */ +enum { + TOK_NUMBER = -128, + TOK_STRING, + TOK_TEMPLATE, + TOK_IDENT, + TOK_REGEXP, + /* warning: order matters (see js_parse_assign_expr) */ + TOK_MUL_ASSIGN, + TOK_DIV_ASSIGN, + TOK_MOD_ASSIGN, + TOK_PLUS_ASSIGN, + TOK_MINUS_ASSIGN, + TOK_SHL_ASSIGN, + TOK_SAR_ASSIGN, + TOK_SHR_ASSIGN, + TOK_AND_ASSIGN, + TOK_XOR_ASSIGN, + TOK_OR_ASSIGN, + TOK_POW_ASSIGN, + TOK_LAND_ASSIGN, + TOK_LOR_ASSIGN, + TOK_DEC, + TOK_INC, + TOK_SHL, + TOK_SAR, + TOK_SHR, + TOK_LT, + TOK_LTE, + TOK_GT, + TOK_GTE, + TOK_EQ, + TOK_STRICT_EQ, + TOK_NEQ, + TOK_STRICT_NEQ, + TOK_LAND, + TOK_LOR, + TOK_POW, + TOK_ARROW, + TOK_ERROR, + TOK_PRIVATE_NAME, + TOK_EOF, + /* whitespace/comment tokens for tokenizer */ + TOK_COMMENT, + TOK_NEWLINE, + TOK_SPACE, + /* keywords: WARNING: same order as atoms */ + TOK_NULL, /* must be first */ + TOK_FALSE, + TOK_TRUE, + TOK_IF, + TOK_ELSE, + TOK_RETURN, + TOK_GO, + TOK_VAR, + TOK_DEF, + TOK_THIS, + TOK_DELETE, + TOK_IN, + TOK_DO, + TOK_WHILE, + TOK_FOR, + TOK_BREAK, + TOK_CONTINUE, + TOK_DISRUPT, + TOK_DISRUPTION, + TOK_FUNCTION, + TOK_DEBUGGER, + TOK_WITH, + /* FutureReservedWord */ + TOK_CLASS, + TOK_CONST, + TOK_ENUM, + TOK_EXPORT, + TOK_EXTENDS, + TOK_IMPORT, + TOK_SUPER, + /* FutureReservedWords when parsing strict mode code */ + TOK_IMPLEMENTS, + TOK_INTERFACE, + TOK_LET, + TOK_PRIVATE, + TOK_PROTECTED, + TOK_PUBLIC, + TOK_STATIC, + TOK_YIELD, + TOK_AWAIT, /* must be last */ + TOK_OF, /* only used for js_parse_skip_parens_token() */ +}; + +#define TOK_FIRST_KEYWORD TOK_NULL +#define TOK_LAST_KEYWORD TOK_AWAIT + +/* unicode code points */ +#define CP_NBSP 0x00a0 +#define CP_BOM 0xfeff + +#define CP_LS 0x2028 +#define CP_PS 0x2029 + + +/* === Line/column cache === */ +typedef struct { + /* last source position */ + const uint8_t *ptr; + int line_num; + int col_num; + const uint8_t *buf_start; +} GetLineColCache; + +/* === JSOpCode (needed by debugger in runtime.c and bytecode in cell_js.c) === */ +typedef struct JSOpCode { +#ifdef DUMP_BYTECODE + const char *name; +#endif + uint8_t size; /* in bytes */ + /* the opcodes remove n_pop items from the top of the stack, then + pushes n_push items */ + uint8_t n_pop; + uint8_t n_push; + uint8_t fmt; +} JSOpCode; + +static const JSOpCode opcode_info[OP_COUNT + (OP_TEMP_END - OP_TEMP_START)] = { +#define FMT(f) +#ifdef DUMP_BYTECODE +#define DEF(id, size, n_pop, n_push, f) \ + { #id, size, n_pop, n_push, OP_FMT_##f }, +#else +#define DEF(id, size, n_pop, n_push, f) { size, n_pop, n_push, OP_FMT_##f }, +#endif +#include "quickjs-opcode.h" +#undef DEF +#undef FMT +}; + +#if SHORT_OPCODES +/* After the final compilation pass, short opcodes are used. Their + opcodes overlap with the temporary opcodes which cannot appear in + the final bytecode. Their description is after the temporary + opcodes in opcode_info[]. */ +#define short_opcode_info(op) \ + opcode_info[(op) >= OP_TEMP_START ? (op) + (OP_TEMP_END - OP_TEMP_START) \ + : (op)] +#else +#define short_opcode_info(op) opcode_info[op] +#endif + +/* === MachVarInfo (shared by mach.c and mcode.c) === */ +typedef struct MachVarInfo { + char *name; + int slot; + int is_const; /* 1 for def, function args; 0 for var */ + int is_closure; /* 1 if captured by a nested function */ + int scope_depth; /* block scope nesting level */ +} MachVarInfo; + +/* === PPretext (parser pretext, system-malloc, used by cell_js.c parser) === */ +typedef struct PPretext { + uint32_t *data; + int len; + int cap; +} PPretext; + +/* === AST Parse State (shared by tokenize.c and parse.c) === */ +typedef struct ASTParseState { + const char *filename; + const uint8_t *buf_start; + const uint8_t *buf_ptr; + const uint8_t *buf_end; + const uint8_t *token_ptr; + int token_val; + BOOL got_lf; + int function_nr; + cJSON *errors; /* array of error objects */ + int has_error; + int error_count; + int in_disruption; + char *decoded_str; /* allocated buffer for decoded string escapes */ + GetLineColCache lc_cache; + union { + struct { + const char *str; + size_t len; + } str; + struct { + double val; + } num; + struct { + const char *str; + size_t len; + BOOL has_escape; + BOOL is_reserved; + } ident; + struct { + const char *body; + size_t body_len; + const char *flags; + size_t flags_len; + } regexp; + } token_u; +} ASTParseState; + +#define JS_CALL_FLAG_COPY_ARGV (1 << 1) + +extern JSClassID js_class_id_alloc; + +/* === Forward declarations for functions split across modules === */ + +/* cell_js.c exports */ +void gc_scan_parser_cpool (JSContext *ctx, uint8_t *from_base, uint8_t *from_end, + uint8_t *to_base, uint8_t **to_free, uint8_t *to_end); +void gc_scan_bytecode_cpool (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *from_end, + uint8_t *to_base, uint8_t **to_free, uint8_t *to_end); +int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size); +JSValue JS_CallInternal (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv, int flags); +int get_line_col (int *pcol_num, const uint8_t *buf, size_t len); +int get_line_col_cached (GetLineColCache *s, int *pcol_num, const uint8_t *ptr); + +/* runtime.c exports */ +JSValue JS_ThrowStackOverflow (JSContext *ctx); +JSValue JS_ThrowSyntaxErrorVarRedeclaration (JSContext *ctx, JSValue prop); +JSValue JS_ThrowReferenceErrorUninitialized (JSContext *ctx, JSValue name); +JSValue JS_ThrowReferenceErrorUninitialized2 (JSContext *ctx, JSFunctionBytecode *b, int idx, BOOL is_ref); +int JS_DefineObjectName (JSContext *ctx, JSValue obj, JSValue name); +int JS_DefineObjectNameComputed (JSContext *ctx, JSValue obj, JSValue str); +int js_method_set_properties (JSContext *ctx, JSValue func_obj, JSValue name, int flags, JSValue home_obj); +JSValue JS_GetPropertyValue (JSContext *ctx, JSValue this_obj, JSValue prop); +JSValue js_closure (JSContext *ctx, JSValue bfunc, JSStackFrame *sf); +__exception int JS_CopyDataProperties (JSContext *ctx, JSValue target, JSValue source, JSValue excluded, BOOL setprop); +int js_string_compare_value (JSContext *ctx, JSValue op1, JSValue op2, BOOL eq_only); +int js_string_compare_value_nocase (JSContext *ctx, JSValue op1, JSValue op2); +JSValue js_regexp_constructor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); +int JS_HasProperty (JSContext *ctx, JSValue obj, JSValue prop); +int JS_HasPropertyKey (JSContext *ctx, JSValue obj, JSValue key); +void *js_realloc_rt (void *ptr, size_t size); +char *js_strdup_rt (const char *str); +JSValue JS_ConcatString (JSContext *ctx, JSValue op1, JSValue op2); +no_inline __exception int js_binary_arith_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op); +no_inline __exception int js_unary_arith_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op); +__exception int js_post_inc_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op); +no_inline int js_not_slow (JSContext *ctx, JSValue *sp); +no_inline int js_relational_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op); +no_inline int js_strict_eq_slow (JSContext *ctx, JSValue *sp, BOOL is_neq); +__exception int js_operator_in (JSContext *ctx, JSValue *sp); +__exception int js_operator_delete (JSContext *ctx, JSValue *sp); +JSText *pretext_init (JSContext *ctx, int capacity); +JSText *pretext_putc (JSContext *ctx, JSText *s, uint32_t c); +JSText *pretext_concat_value (JSContext *ctx, JSText *s, JSValue v); +JSValue js_new_blob (JSContext *ctx, blob *b); +/* Functions from header region (defined in runtime.c) */ +void *js_realloc (JSContext *ctx, void *ptr, size_t size); +void *st_alloc (JSContext *ctx, size_t bytes, size_t align); +void st_free_all (JSContext *ctx); +int st_text_resize (JSContext *ctx); +JSValue intern_text_to_value (JSContext *ctx, const uint32_t *utf32, uint32_t len); +JSValue js_key_new (JSContext *ctx, const char *str); +JSValue js_key_new_len (JSContext *ctx, const char *str, size_t len); +uint64_t js_key_hash (JSValue key); +JS_BOOL js_key_equal (JSValue a, JSValue b); +JS_BOOL js_key_equal_str (JSValue a, const char *str); +int rec_find_slot (JSRecord *rec, JSValue k); +JSValue rec_get (JSContext *ctx, JSRecord *rec, JSValue k); +int rec_resize (JSContext *ctx, JSValue *pobj, uint64_t new_mask); +int rec_set_own (JSContext *ctx, JSValue *pobj, JSValue k, JSValue val); +JSRecord *js_new_record_class (JSContext *ctx, uint32_t initial_mask, JSClassID class_id); +int JS_SetPropertyInternal (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val); +uint64_t get_text_hash (JSText *text); +void pack_utf32_to_words (const uint32_t *utf32, uint32_t len, uint64_t *packed); +int text_equal (JSText *a, const uint64_t *packed_b, uint32_t len_b); + +static inline JSValue *get_upvalue_ptr (JSValue frame_val, int depth, int slot) { + if (JS_IsNull(frame_val)) return NULL; + JSFrame *frame = JS_VALUE_GET_FRAME(frame_val); + while (depth > 0) { + JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); + frame_val = fn->u.func.outer_frame; + if (JS_IsNull(frame_val)) return NULL; + frame = JS_VALUE_GET_FRAME(frame_val); + depth--; + } + return &frame->slots[slot]; +} + +void build_backtrace (JSContext *ctx, JSValue error_obj, const char *filename, int line_num, int col_num, int backtrace_flags); +BOOL is_backtrace_needed (JSContext *ctx, JSValue obj); +JSValue JS_ThrowError2 (JSContext *ctx, JSErrorEnum error_num, const char *fmt, va_list ap, BOOL add_backtrace); +JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *from_end, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end); +PPretext *ppretext_init (int capacity); +PPretext *ppretext_putc (PPretext *p, uint32_t c); +void ppretext_free (PPretext *p); +JSValue ppretext_end (JSContext *ctx, PPretext *p); +PPretext *ppretext_append_jsvalue (PPretext *p, JSValue str); +PPretext *ppretext_append_int (PPretext *p, int n); +JSValue js_atof (JSContext *ctx, const char *str, const char **pp, int radix, int flags); + +/* Defines from runtime section needed by cell_js.c */ +#define DEFINE_GLOBAL_LEX_VAR (1 << 7) +#define DEFINE_GLOBAL_FUNC_VAR (1 << 6) + +#define ATOD_INT_ONLY (1 << 0) +/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */ +#define ATOD_ACCEPT_BIN_OCT (1 << 2) +/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */ +#define ATOD_ACCEPT_LEGACY_OCTAL (1 << 4) +/* accept _ between digits as a digit separator */ +#define ATOD_ACCEPT_UNDERSCORES (1 << 5) +/* allow a suffix to override the type */ +#define ATOD_ACCEPT_SUFFIX (1 << 6) +/* default type */ +#define ATOD_TYPE_MASK (3 << 7) +#define ATOD_TYPE_FLOAT64 (0 << 7) +#define ATOD_TYPE_BIG_INT (1 << 7) +/* accept -0x1 */ +#define ATOD_ACCEPT_PREFIX_AFTER_SIGN (1 << 10) + +#define GLOBAL_VAR_OFFSET 0x40000000 +#define ARGUMENT_VAR_OFFSET 0x20000000 + +/* Inline functions from runtime section needed by cell_js.c */ +static inline void js_dbuf_init (JSContext *ctx, DynBuf *s) { + dbuf_init2 (s, ctx->rt, NULL); +} + +static inline int to_digit (int c) { + if (c >= '0' && c <= '9') return c - '0'; + else if (c >= 'A' && c <= 'Z') return c - 'A' + 10; + else if (c >= 'a' && c <= 'z') return c - 'a' + 10; + else return 36; +} + +static inline void dbuf_put_leb128 (DynBuf *s, uint32_t v) { + uint32_t a; + for (;;) { + a = v & 0x7f; + v >>= 7; + if (v != 0) { + dbuf_putc (s, a | 0x80); + } else { + dbuf_putc (s, a); + break; + } + } +} + +static inline void dbuf_put_sleb128 (DynBuf *s, int32_t v1) { + uint32_t v = v1; + dbuf_put_leb128 (s, (2 * v) ^ -(v >> 31)); +} + +static inline int get_leb128 (uint32_t *pval, const uint8_t *buf, const uint8_t *buf_end) { + const uint8_t *ptr = buf; + uint32_t v, a, i; + v = 0; + for (i = 0; i < 5; i++) { + if (unlikely (ptr >= buf_end)) break; + a = *ptr++; + v |= (a & 0x7f) << (i * 7); + if (!(a & 0x80)) { + *pval = v; + return ptr - buf; + } + } + *pval = 0; + return -1; +} + +static inline int get_sleb128 (int32_t *pval, const uint8_t *buf, const uint8_t *buf_end) { + int ret; + uint32_t val; + ret = get_leb128 (&val, buf, buf_end); + if (ret < 0) { + *pval = 0; + return -1; + } + *pval = (val >> 1) ^ -(val & 1); + return ret; +} + +no_inline int js_realloc_array (JSContext *ctx, void **parray, int elem_size, int *psize, int req_size); +static inline int js_resize_array (JSContext *ctx, void **parray, int elem_size, int *psize, int req_size) { + if (unlikely (req_size > *psize)) + return js_realloc_array (ctx, parray, elem_size, psize, req_size); + else + return 0; +} + +JSText *js_alloc_string (JSContext *ctx, int max_len); +JSValue js_key_from_string (JSContext *ctx, JSValue val); + +/* mach.c exports */ +JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue this_obj, int argc, JSValue *argv, JSValue env, JSValue outer_frame); +JSFrameRegister *alloc_frame_register(JSContext *ctx, int slot_count); +cJSON *mach_find_scope_record(cJSON *scopes, int function_nr); +int reg_vm_check_interrupt(JSContext *ctx); + +/* mcode.c exports */ +JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj, int argc, JSValue *argv, JSValue outer_frame); + +/* tokenize.c exports (called by parse.c) */ +void cjson_add_strn (cJSON *obj, const char *key, const char *str, size_t len); +cJSON *ast_node (ASTParseState *s, const char *kind, const uint8_t *start_ptr); +void ast_node_end (ASTParseState *s, cJSON *node, const uint8_t *end_ptr); +int ast_next_token (ASTParseState *s); +void ast_free_token (ASTParseState *s); +void ast_get_line_col (ASTParseState *s, const uint8_t *ptr, int *line, int *col); +BOOL tok_eq (const char *str, size_t len, const char *lit); +BOOL ast_is_arrow_function (ASTParseState *s); +int tokenize_next (ASTParseState *s); +void ast_error (ASTParseState *s, const uint8_t *ptr, const char *fmt, ...); + +/* parse.c forward declarations */ +cJSON *ast_parse_expr (ASTParseState *s); +cJSON *ast_parse_assign_expr (ASTParseState *s); +cJSON *ast_parse_statement (ASTParseState *s); +void ast_sync_to_statement (ASTParseState *s); +cJSON *ast_parse_block_statements (ASTParseState *s); +cJSON *ast_parse_function_inner (ASTParseState *s, BOOL is_expr); +cJSON *ast_parse_arrow_function (ASTParseState *s); + +#endif /* QUICKJS_INTERNAL_H */ diff --git a/source/quickjs.c b/source/quickjs.c deleted file mode 100644 index 069b19e9..00000000 --- a/source/quickjs.c +++ /dev/null @@ -1,38487 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#if defined(__APPLE__) -#include -#elif defined(__linux__) || defined(__GLIBC__) -#include -#elif defined(__FreeBSD__) -#include -#endif - -#include "cutils.h" -#include "dtoa.h" -#include "libregexp.h" -#include "libunicode.h" -#include "list.h" -#include "quickjs.h" -#include "cJSON.h" - -#define BLOB_IMPLEMENTATION -#include "blob.h" - -#define NOTA_IMPLEMENTATION -#include "nota.h" - -#define WOTA_IMPLEMENTATION -#include "wota.h" - -#define OPTIMIZE 1 -#define SHORT_OPCODES 1 -#if defined(EMSCRIPTEN) -#define DIRECT_DISPATCH 0 -#else -#define DIRECT_DISPATCH 1 -#endif - -#if !defined(_WIN32) -/* define it if printf uses the RNDN rounding mode instead of RNDNA */ -#define CONFIG_PRINTF_RNDN -#endif - -#if !defined(EMSCRIPTEN) -/* enable stack limitation */ -#define CONFIG_STACK_CHECK -#endif - -/* dump object free */ -// #define DUMP_FREE -// #define DUMP_CLOSURE -/* dump the bytecode of the compiled functions: combination of bits - 1: dump pass 3 final byte code - 2: dump pass 2 code - 4: dump pass 1 code - 8: dump stdlib functions - 16: dump bytecode in hex - 32: dump line number table - 64: dump compute_stack_size - */ -// #define DUMP_BYTECODE (1) -/* dump GC summary: old/new heap, recovery %, heap growth */ -// #define DUMP_GC -/* dump detailed GC: roots, scanning, object traversal (implies DUMP_GC) */ -// #define DUMP_GC_DETAIL -#ifdef DUMP_GC_DETAIL -#define DUMP_GC -#endif -/* dump objects freed by the garbage collector */ -// #define DUMP_GC_FREE -/* dump memory usage before running the garbage collector */ -// #define DUMP_MEM -// #define DUMP_OBJECTS /* dump objects in JS_FreeContext */ -// #define DUMP_READ_OBJECT -// #define DUMP_ROPE_REBALANCE - -/* test the GC by forcing it before each object allocation */ -#define FORCE_GC_AT_MALLOC - -#define POISON_HEAP -/* POISON_HEAP: Use ASan's memory poisoning to detect stale pointer access */ -#ifdef POISON_HEAP -#if defined(__has_feature) - #if __has_feature(address_sanitizer) - #define HAVE_ASAN 1 - #endif -#elif defined(__SANITIZE_ADDRESS__) - #define HAVE_ASAN 1 -#endif - -#ifdef HAVE_ASAN -#include -#define gc_poison_region(addr, size) __asan_poison_memory_region((addr), (size)) -#define gc_unpoison_region(addr, size) __asan_unpoison_memory_region((addr), (size)) -#else -/* Fallback: no-op when not building with ASan */ -#define gc_poison_region(addr, size) ((void)0) -#define gc_unpoison_region(addr, size) ((void)0) -#endif -#endif /* POISON_HEAP */ - -#ifdef HAVE_ASAN -static struct JSContext *__asan_js_ctx; -#endif - -/* Forward declarations for heap object types */ -typedef struct JSArray JSArray; -typedef struct JSBlob JSBlob; -typedef struct JSText JSText; -typedef struct JSRecord JSRecord; -typedef struct JSFunction JSFunction; -typedef struct JSFrame JSFrame; -typedef struct JSCode JSCode; - -#define OBJHDR_CAP_SHIFT 8u -#define OBJHDR_CAP_MASK (((objhdr_t)1ull << 56) - 1ull) - -#define JS_MKPTR(ptr) (((JSValue)(uintptr_t)(ptr)) | JS_TAG_PTR) -#define JS_MKFWD(ptr) (((objhdr_t)(uintptr_t)(ptr) << OBJHDR_FWD_PTR_SHIFT) | OBJ_FORWARD) -#define JS_VALUE_GET_FWD_PTR(v) ((void*)(uintptr_t)(((v) >> OBJHDR_FWD_PTR_SHIFT) & OBJHDR_FWD_PTR_MASK)) - -/* For OBJ_FORWARD type: bits 3-63 contain a 61-bit pointer */ -#define OBJHDR_FWD_PTR_SHIFT 3u -#define OBJHDR_FWD_PTR_MASK (((objhdr_t)1ull << 61) - 1ull) -#define objhdr_fwd_ptr(h) ((void*)(uintptr_t)(((h) >> OBJHDR_FWD_PTR_SHIFT) & OBJHDR_FWD_PTR_MASK)) -#define objhdr_make_fwd(ptr) (((objhdr_t)(uintptr_t)(ptr) << OBJHDR_FWD_PTR_SHIFT) | OBJ_FORWARD) - -/* Extract pointer (clear low bits) */ -#define JS_VALUE_GET_PTR(v) ((void *)((v) & ~((JSValue)(JSW - 1)))) - - -static inline JS_BOOL JS_VALUE_IS_TEXT (JSValue v) { - int tag = JS_VALUE_GET_TAG (v); - return tag == JS_TAG_STRING_IMM || (JS_IsPtr(v) && objhdr_type(*(objhdr_t *)JS_VALUE_GET_PTR(v)) == OBJ_TEXT); -} - -static inline JS_BOOL JS_VALUE_IS_NUMBER (JSValue v) { - int tag = JS_VALUE_GET_TAG (v); - return tag == JS_TAG_INT || tag == JS_TAG_SHORT_FLOAT; -} - -/* JS_KEY_* macros: JSValue immediate ASCII strings for common property names. - These replace JS_ATOM_* for use with the new JSValue-based property API. */ -#define _JS_KEY1(c1) \ - ((JSValue)JS_TAG_STRING_IMM | ((JSValue)1 << 5) | ((JSValue)(c1) << 8)) -#define _JS_KEY2(c1, c2) \ - ((JSValue)JS_TAG_STRING_IMM | ((JSValue)2 << 5) | ((JSValue)(c1) << 8) \ - | ((JSValue)(c2) << 16)) -#define _JS_KEY3(c1, c2, c3) \ - ((JSValue)JS_TAG_STRING_IMM | ((JSValue)3 << 5) | ((JSValue)(c1) << 8) \ - | ((JSValue)(c2) << 16) | ((JSValue)(c3) << 24)) -#define _JS_KEY4(c1, c2, c3, c4) \ - ((JSValue)JS_TAG_STRING_IMM | ((JSValue)4 << 5) | ((JSValue)(c1) << 8) \ - | ((JSValue)(c2) << 16) | ((JSValue)(c3) << 24) | ((JSValue)(c4) << 32)) -#define _JS_KEY5(c1, c2, c3, c4, c5) \ - ((JSValue)JS_TAG_STRING_IMM | ((JSValue)5 << 5) | ((JSValue)(c1) << 8) \ - | ((JSValue)(c2) << 16) | ((JSValue)(c3) << 24) | ((JSValue)(c4) << 32) \ - | ((JSValue)(c5) << 40)) -#define _JS_KEY6(c1, c2, c3, c4, c5, c6) \ - ((JSValue)JS_TAG_STRING_IMM | ((JSValue)6 << 5) | ((JSValue)(c1) << 8) \ - | ((JSValue)(c2) << 16) | ((JSValue)(c3) << 24) | ((JSValue)(c4) << 32) \ - | ((JSValue)(c5) << 40) | ((JSValue)(c6) << 48)) -#define _JS_KEY7(c1, c2, c3, c4, c5, c6, c7) \ - ((JSValue)JS_TAG_STRING_IMM | ((JSValue)7 << 5) | ((JSValue)(c1) << 8) \ - | ((JSValue)(c2) << 16) | ((JSValue)(c3) << 24) | ((JSValue)(c4) << 32) \ - | ((JSValue)(c5) << 40) | ((JSValue)(c6) << 48) | ((JSValue)(c7) << 56)) - -/* Common property keys as JSValue immediate strings (max 7 ASCII chars) */ -/* Empty string: tag with length 0 */ -#define JS_KEY_empty ((JSValue)JS_TAG_STRING_IMM) -#define JS_KEY_name _JS_KEY4 ('n', 'a', 'm', 'e') -#define JS_KEY_length _JS_KEY6 ('l', 'e', 'n', 'g', 't', 'h') -#define JS_KEY_message _JS_KEY7 ('m', 'e', 's', 's', 'a', 'g', 'e') -#define JS_KEY_stack _JS_KEY5 ('s', 't', 'a', 'c', 'k') -#define JS_KEY_cause _JS_KEY5 ('c', 'a', 'u', 's', 'e') -#define JS_KEY_errors _JS_KEY6 ('e', 'r', 'r', 'o', 'r', 's') -#define JS_KEY_Error _JS_KEY5 ('E', 'r', 'r', 'o', 'r') -#define JS_KEY_raw _JS_KEY3 ('r', 'a', 'w') -#define JS_KEY_flags _JS_KEY5 ('f', 'l', 'a', 'g', 's') -#define JS_KEY_source _JS_KEY6 ('s', 'o', 'u', 'r', 'c', 'e') -#define JS_KEY_exec _JS_KEY4 ('e', 'x', 'e', 'c') -#define JS_KEY_toJSON _JS_KEY6 ('t', 'o', 'J', 'S', 'O', 'N') -#define JS_KEY_eval _JS_KEY4 ('e', 'v', 'a', 'l') -#define JS_KEY_this _JS_KEY4 ('t', 'h', 'i', 's') -#define JS_KEY_true _JS_KEY4 ('t', 'r', 'u', 'e') -#define JS_KEY_false _JS_KEY5 ('f', 'a', 'l', 's', 'e') -#define JS_KEY_null _JS_KEY4 ('n', 'u', 'l', 'l') -#define JS_KEY_NaN _JS_KEY3 ('N', 'a', 'N') -#define JS_KEY_default _JS_KEY7 ('d', 'e', 'f', 'a', 'u', 'l', 't') -#define JS_KEY_value _JS_KEY5 ('v', 'a', 'l', 'u', 'e') -#define JS_KEY_index _JS_KEY5 ('i', 'n', 'd', 'e', 'x') -#define JS_KEY_input _JS_KEY5 ('i', 'n', 'p', 'u', 't') -#define JS_KEY_groups _JS_KEY6 ('g', 'r', 'o', 'u', 'p', 's') -#define JS_KEY_indices _JS_KEY7 ('i', 'n', 'd', 'i', 'c', 'e', 's') -#define JS_KEY_let _JS_KEY3 ('l', 'e', 't') -#define JS_KEY_var _JS_KEY3 ('v', 'a', 'r') -#define JS_KEY_new _JS_KEY3 ('n', 'e', 'w') -#define JS_KEY_of _JS_KEY2 ('o', 'f') -#define JS_KEY_yield _JS_KEY5 ('y', 'i', 'e', 'l', 'd') -#define JS_KEY_async _JS_KEY5 ('a', 's', 'y', 'n', 'c') -#define JS_KEY_target _JS_KEY6 ('t', 'a', 'r', 'g', 'e', 't') -#define JS_KEY_from _JS_KEY4 ('f', 'r', 'o', 'm') -#define JS_KEY_meta _JS_KEY4 ('m', 'e', 't', 'a') -#define JS_KEY_as _JS_KEY2 ('a', 's') -#define JS_KEY_get _JS_KEY3 ('g', 'e', 't') -#define JS_KEY_set _JS_KEY3 ('s', 'e', 't') -#define JS_KEY_with _JS_KEY4 ('w', 'i', 't', 'h') - -/* Internal variable names */ -#define JS_KEY__ret_ _JS_KEY5 ('<', 'r', 'e', 't', '>') -#define JS_KEY__eval_ _JS_KEY6 ('<', 'e', 'v', 'a', 'l', '>') -#define JS_KEY__var_ _JS_KEY5 ('<', 'v', 'a', 'r', '>') - -/* Keys for strings > 7 chars - these use string literals and are created at - runtime. The caller must free the returned JSValue if it's a heap string. */ -#define JS_KEY_STR(ctx, str) JS_NewStringLen ((ctx), (str), sizeof (str) - 1) - -#define KEY_GET_STR_BUF_SIZE 256 - -/* JS_IsPretext, JS_KeyGetStr, JS_PushGCRef, JS_PopGCRef, JS_AddGCRef, JS_DeleteGCRef - are defined after JSContext (they need its fields) */ - -/* Forward declarations for memory functions (now declared in quickjs.h) */ -void *js_realloc (JSContext *ctx, void *ptr, size_t size); - -/* Forward declaration for string_get */ -static inline int string_get (const JSText *p, int idx); - -#undef JS_PUSH_VALUE -#undef JS_POP_VALUE - -#define JS_PUSH_VALUE(ctx, v) \ - do { \ - v##_ref.prev = ctx->top_gc_ref; \ - ctx->top_gc_ref = &v##_ref; \ - v##_ref.val = v; \ - } while (0) - -#define JS_POP_VALUE(ctx, v) \ - do { \ - v = v##_ref.val; \ - ctx->top_gc_ref = v##_ref.prev; \ - } while (0) - -/* JS_Invoke - invoke method on object using JSValue key */ -static JSValue JS_Invoke (JSContext *ctx, JSValue this_val, JSValue method, int argc, JSValue *argv); - -enum { - /* classid tag */ /* union usage | properties */ - JS_CLASS_OBJECT = 1, /* must be first */ - JS_CLASS_ERROR, - JS_CLASS_REGEXP, /* u.regexp */ - JS_CLASS_BLOB, /* u.opaque (blob *) */ - - JS_CLASS_INIT_COUNT, /* last entry for predefined classes */ -}; - -typedef enum JSErrorEnum { - JS_EVAL_ERROR, - JS_RANGE_ERROR, - JS_REFERENCE_ERROR, - JS_SYNTAX_ERROR, - JS_TYPE_ERROR, - JS_URI_ERROR, - JS_INTERNAL_ERROR, - JS_AGGREGATE_ERROR, - - JS_NATIVE_ERROR_COUNT, /* number of different NativeError objects */ -} JSErrorEnum; - -/* the variable and scope indexes must fit on 16 bits. The (-1) and - ARG_SCOPE_END values are reserved. */ -#define JS_MAX_LOCAL_VARS 65534 -#define JS_STACK_SIZE_MAX 65534 -/* Max string length matches header capacity (56 bits on 64-bit builds) */ -#define JS_STRING_LEN_MAX OBJHDR_CAP_MASK - -#define __exception __attribute__ ((warn_unused_result)) - -/* Forward declaration for bytecode freeing */ -struct JSFunctionBytecode; - -#define JS_VALUE_GET_ARRAY(v) ((JSArray *)chase (v)) -#define JS_VALUE_GET_OBJ(v) ((JSRecord *)chase (v)) -#define JS_VALUE_GET_TEXT(v) ((JSText *)chase (v)) -#define JS_VALUE_GET_BLOB(v) ((JSBlob *)JS_VALUE_GET_PTR (v)) -#define JS_VALUE_GET_FUNCTION(v) ((JSFunction *)chase (v)) -#define JS_VALUE_GET_FRAME(v) ((JSFrame *)chase (v)) -#define JS_VALUE_GET_CODE(v) ((JSFunctionBytecode *)JS_VALUE_GET_PTR (v)) -#define JS_VALUE_GET_STRING(v) ((JSText *)chase (v)) - -/* Compatibility: JS_TAG_STRING is an alias for text type checks */ -#define JS_TAG_STRING JS_TAG_STRING_IMM - -/* JS_TAG_FUNCTION doesn't exist in new encoding - use JS_IsFunction check instead */ -#define JS_TAG_FUNCTION 0xFE /* dummy value, never matches any tag */ - -/* JS_ThrowMemoryError is an alias for JS_ThrowOutOfMemory */ -#define JS_ThrowMemoryError(ctx) JS_ThrowOutOfMemory(ctx) - -/* Helper to set cap in objhdr */ -static inline objhdr_t objhdr_set_cap56 (objhdr_t h, uint64_t cap) { - return (h & 0xFF) | ((cap & OBJHDR_CAP_MASK) << OBJHDR_CAP_SHIFT); -} - -typedef enum OPCodeEnum OPCodeEnum; - -/* ============================================================ - Buddy Allocator for Actor Memory Blocks - ============================================================ */ - -/* Platform-specific minimum block size */ -#if defined(__LP64__) || defined(_WIN64) - #define BUDDY_MIN_ORDER 10 /* 1KB minimum on 64-bit */ -#else - #define BUDDY_MIN_ORDER 9 /* 512B minimum on 32-bit */ -#endif -#define BUDDY_MAX_ORDER 28 /* 256MB maximum */ -#define BUDDY_LEVELS (BUDDY_MAX_ORDER - BUDDY_MIN_ORDER + 1) -#define BUDDY_POOL_SIZE (1ULL << BUDDY_MAX_ORDER) - -typedef struct BuddyBlock { - struct BuddyBlock *next; - struct BuddyBlock *prev; - uint8_t order; /* log2 of size */ - uint8_t is_free; -} BuddyBlock; - -typedef struct BuddyAllocator { - uint8_t *base; /* 256MB base address */ - size_t total_size; /* 256MB */ - BuddyBlock *free_lists[BUDDY_LEVELS]; - uint8_t initialized; -} BuddyAllocator; - -/* Forward declarations for buddy allocator functions */ -static void buddy_destroy (BuddyAllocator *b); - -struct JSRuntime { - const char *rt_info; - - size_t malloc_limit; - - /* Buddy allocator for actor memory blocks */ - BuddyAllocator buddy; - - /* see JS_SetStripInfo() */ - uint8_t strip_flags; - - /* User data */ - void *user_opaque; -}; - -struct JSClass { - const char *class_name; - JSClassFinalizer *finalizer; - JSClassGCMark *gc_mark; - uint32_t class_id; /* 0 means free entry */ -}; - -#define JS_MODE_BACKTRACE_BARRIER \ - (1 << 3) /* stop backtrace before this frame */ - -typedef struct JSStackFrame { - struct JSStackFrame *prev_frame; /* NULL if first stack frame */ - JSValue cur_func; /* current function, JS_NULL if the frame is detached */ - JSValue *arg_buf; /* arguments */ - JSValue *var_buf; /* variables */ - const uint8_t *cur_pc; /* only used in bytecode functions : PC of the - instruction after the call */ - int arg_count; - int js_mode; /* not supported for C functions */ - JSValue js_frame; /* GC-managed JSFrame (use JS_VALUE_GET_FRAME to access) */ - JSValue *stack_buf; /* operand stack base (for GC scanning) */ - JSValue **p_sp; /* pointer to current sp (for GC scanning) */ -} JSStackFrame; - -/* Heap-allocated VM frame for trampoline execution */ -struct VMFrame { - struct JSFunctionBytecode *b; /* current function bytecode */ - JSContext *ctx; /* execution context / realm */ - const uint8_t *pc; /* program counter */ - - /* Offset-based storage (safe with realloc) */ - int value_stack_base; /* base index into ctx->value_stack */ - int sp_offset; /* sp offset from base */ - int arg_buf_offset; /* arg buffer offset from base (or -1 if aliased) */ - int var_buf_offset; /* var buffer offset from base */ - - /* Continuation info for return */ - const uint8_t *ret_pc; /* where to resume in caller */ - int ret_sp_offset; /* caller's sp before call (for cleanup) */ - int call_argc; /* number of args to clean up */ - int call_has_this; /* whether call had this (method call) */ - - JSValue cur_func; /* current function object */ - JSValue this_obj; /* this binding */ - int arg_count; - int js_mode; - int stack_size_allocated; /* total size allocated for this frame */ -}; - -typedef struct JSFrameRegister { - objhdr_t hdr; // capacity in this is the total number of words of the object, including the 4 words of overhead and all slots - JSValue function; // JSFunction, function object being invoked - JSValue caller; // JSFrameRegister, the frame that called this one - JSValue address; // address of the instruction in the code that should be executed upon return - JSValue slots[]; // inline memory. order is [this][input args][closed over vars][non closed over vars][temporaries] -} JSFrameRegister; /// extra note: when this frame returns, caller should be set to 0. If caller is found to be 0, then the GC can reduce this frame's slots down to [this][input_args][closed over vars]; if no closed over vars it can be totally removed; may happen naturally in GC since it would have no refs? - -/* ============================================================ - Register-Based VM Data Structures - ============================================================ */ - -/* 32-bit instruction encoding (Lua-style) - Formats: - iABC: [op:8][A:8][B:8][C:8] — register ops - iABx: [op:8][A:8][Bx:16] — constant/global loads (unsigned) - iAsBx: [op:8][A:8][sBx:16] — conditional jumps (signed offset) - isJ: [op:8][sJ:24] — unconditional jump (signed offset) */ - -typedef uint32_t MachInstr32; - -typedef struct { uint16_t line; uint16_t col; } MachLineEntry; - -/* Encoding macros */ -#define MACH_ABC(op, a, b, c) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(b)<<16) | ((uint32_t)(c)<<24)) -#define MACH_ABx(op, a, bx) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(bx)<<16)) -#define MACH_AsBx(op, a, sbx) ((uint32_t)(op) | ((uint32_t)(a)<<8) | ((uint32_t)(uint16_t)(sbx)<<16)) -#define MACH_sJ(op, sj) ((uint32_t)(op) | (((uint32_t)(sj) & 0xFFFFFF) << 8)) - -/* Decoding macros */ -#define MACH_GET_OP(i) ((i) & 0xFF) -#define MACH_GET_A(i) (((i) >> 8) & 0xFF) -#define MACH_GET_B(i) (((i) >> 16) & 0xFF) -#define MACH_GET_C(i) (((i) >> 24) & 0xFF) -#define MACH_GET_Bx(i) ((i) >> 16) -#define MACH_GET_sBx(i) ((int16_t)((i) >> 16)) -#define MACH_GET_sJ(i) ((int32_t)((i) & 0xFFFFFF00) >> 8) - -typedef enum MachOpcode { - /* Constants & Loading */ - MACH_LOADK, /* R(A) = K(Bx) — load from constant pool (ABx) */ - MACH_LOADI, /* R(A) = (int16_t)sBx — load small integer (AsBx) */ - MACH_LOADNULL, /* R(A) = null (A only) */ - MACH_LOADTRUE, /* R(A) = true (A only) */ - MACH_LOADFALSE, /* R(A) = false (A only) */ - - /* Movement */ - MACH_MOVE, /* R(A) = R(B) */ - - /* Arithmetic (ABC) */ - MACH_ADD, /* R(A) = R(B) + R(C) */ - MACH_SUB, /* R(A) = R(B) - R(C) */ - MACH_MUL, /* R(A) = R(B) * R(C) */ - MACH_DIV, /* R(A) = R(B) / R(C) */ - MACH_MOD, /* R(A) = R(B) % R(C) */ - MACH_POW, /* R(A) = R(B) ** R(C) */ - MACH_NEG, /* R(A) = -R(B) */ - MACH_INC, /* R(A) = R(B) + 1 */ - MACH_DEC, /* R(A) = R(B) - 1 */ - - /* Comparison (ABC) */ - MACH_EQ, /* R(A) = (R(B) == R(C)) */ - MACH_NEQ, /* R(A) = (R(B) != R(C)) */ - MACH_LT, /* R(A) = (R(B) < R(C)) */ - MACH_LE, /* R(A) = (R(B) <= R(C)) */ - MACH_GT, /* R(A) = (R(B) > R(C)) */ - MACH_GE, /* R(A) = (R(B) >= R(C)) */ - - /* Logical/Bitwise */ - MACH_LNOT, /* R(A) = !R(B) */ - MACH_BNOT, /* R(A) = ~R(B) */ - MACH_BAND, /* R(A) = R(B) & R(C) */ - MACH_BOR, /* R(A) = R(B) | R(C) */ - MACH_BXOR, /* R(A) = R(B) ^ R(C) */ - MACH_SHL, /* R(A) = R(B) << R(C) */ - MACH_SHR, /* R(A) = R(B) >> R(C) */ - MACH_USHR, /* R(A) = R(B) >>> R(C) */ - - /* Property access */ - MACH_GETFIELD, /* R(A) = R(B)[K(C)] — named property */ - MACH_SETFIELD, /* R(A)[K(B)] = R(C) — named property */ - MACH_GETINDEX, /* R(A) = R(B)[R(C)] — computed property */ - MACH_SETINDEX, /* R(A)[R(B)] = R(C) — computed property */ - - /* Unbound variable access (ABx) */ - MACH_GETNAME, /* R(A) = resolve(K(Bx)) — compiler placeholder, patched by link */ - MACH_GETINTRINSIC, /* R(A) = global[K(Bx)] — post-link, intrinsic/built-in */ - MACH_GETENV, /* R(A) = env[K(Bx)] — post-link, module environment */ - - /* Closure access (ABC) */ - MACH_GETUP, /* R(A) = outer_frame[B].slots[C] */ - MACH_SETUP, /* outer_frame[B].slots[C] = R(A) */ - - /* Control flow */ - MACH_JMP, /* pc += sJ — unconditional (isJ format) */ - MACH_JMPTRUE, /* if R(A): pc += sBx — (iAsBx format) */ - MACH_JMPFALSE, /* if !R(A): pc += sBx — (iAsBx format) */ - MACH_JMPNULL, /* if R(A)==null: pc += sBx */ - - /* Function calls — Lua-style consecutive registers */ - MACH_CALL, /* Call R(A) with B args R(A+1)..R(A+B), C=0 discard, C=1 keep result in R(A) */ - MACH_RETURN, /* Return R(A) */ - MACH_RETNIL, /* Return null */ - - /* Object/array creation */ - MACH_NEWOBJECT, /* R(A) = {} */ - MACH_NEWARRAY, /* R(A) = new array, B = element count in R(A+1)..R(A+B) */ - MACH_CLOSURE, /* R(A) = closure(functions[Bx]) (ABx) */ - - MACH_THROW, /* disrupt — trigger disruption */ - - MACH_PUSH, /* push R(B) onto array R(A) */ - MACH_POP, /* R(A) = pop last element from array R(B) */ - - MACH_DELETE, /* R(A) = delete R(B)[K(C)] — named property delete */ - MACH_DELETEINDEX, /* R(A) = delete R(B)[R(C)] — computed property delete */ - MACH_HASPROP, /* R(A) = R(C) in R(B) — has property check */ - MACH_REGEXP, /* R(A) = regexp(K(B), K(C)) — regex literal */ - - MACH_CALLMETHOD, /* Method call: R(A)=obj, B=nargs in R(A+2)..R(A+1+B), C=cpool key */ - - MACH_EQ_TOL, /* R(A) = eq_tol(R(B), R(B+1), R(B+2)), C=3 */ - MACH_NEQ_TOL, /* R(A) = ne_tol(R(B), R(B+1), R(B+2)), C=3 */ - - MACH_NOP, - - MACH_OP_COUNT -} MachOpcode; - -static const char *mach_opcode_names[MACH_OP_COUNT] = { - [MACH_LOADK] = "loadk", - [MACH_LOADI] = "loadi", - [MACH_LOADNULL] = "loadnull", - [MACH_LOADTRUE] = "loadtrue", - [MACH_LOADFALSE] = "loadfalse", - [MACH_MOVE] = "move", - [MACH_ADD] = "add", - [MACH_SUB] = "sub", - [MACH_MUL] = "mul", - [MACH_DIV] = "div", - [MACH_MOD] = "mod", - [MACH_POW] = "pow", - [MACH_NEG] = "neg", - [MACH_INC] = "inc", - [MACH_DEC] = "dec", - [MACH_EQ] = "eq", - [MACH_NEQ] = "neq", - [MACH_LT] = "lt", - [MACH_LE] = "le", - [MACH_GT] = "gt", - [MACH_GE] = "ge", - [MACH_LNOT] = "lnot", - [MACH_BNOT] = "bnot", - [MACH_BAND] = "band", - [MACH_BOR] = "bor", - [MACH_BXOR] = "bxor", - [MACH_SHL] = "shl", - [MACH_SHR] = "shr", - [MACH_USHR] = "ushr", - [MACH_GETFIELD] = "getfield", - [MACH_SETFIELD] = "setfield", - [MACH_GETINDEX] = "getindex", - [MACH_SETINDEX] = "setindex", - [MACH_GETNAME] = "getname", - [MACH_GETINTRINSIC] = "getintrinsic", - [MACH_GETENV] = "getenv", - [MACH_GETUP] = "getup", - [MACH_SETUP] = "setup", - [MACH_JMP] = "jmp", - [MACH_JMPTRUE] = "jmptrue", - [MACH_JMPFALSE] = "jmpfalse", - [MACH_JMPNULL] = "jmpnull", - [MACH_CALL] = "call", - [MACH_RETURN] = "return", - [MACH_RETNIL] = "retnil", - [MACH_NEWOBJECT] = "newobject", - [MACH_NEWARRAY] = "newarray", - [MACH_CLOSURE] = "closure", - [MACH_THROW] = "throw", - [MACH_PUSH] = "push", - [MACH_POP] = "pop", - [MACH_DELETE] = "delete", - [MACH_DELETEINDEX] = "deleteindex", - [MACH_HASPROP] = "hasprop", - [MACH_REGEXP] = "regexp", - [MACH_CALLMETHOD] = "callmethod", - [MACH_EQ_TOL] = "eq_tol", - [MACH_NEQ_TOL] = "neq_tol", - [MACH_NOP] = "nop", -}; - -/* Compiled register-based code (off-heap, never GC'd). - Created by JS_CompileMach from AST JSON. */ -typedef struct JSCodeRegister { - uint16_t arity; /* number of arguments */ - uint16_t nr_close_slots; /* closed-over variable count */ - uint16_t nr_slots; /* total frame size */ - uint16_t entry_point; /* start instruction (usually 0) */ - - /* Constant pool */ - uint32_t cpool_count; - JSValue *cpool; /* allocated via js_malloc_rt */ - - /* Compiled 32-bit instructions */ - uint32_t instr_count; - MachInstr32 *instructions; - - /* Nested functions (for closure) */ - uint32_t func_count; - struct JSCodeRegister **functions; - - /* Debug info */ - JSValue name; /* function name (stone text) */ - MachLineEntry *line_table; /* [instr_count], parallel to instructions[] */ - char *filename_cstr; /* plain C string for stack trace (js_malloc_rt) */ - char *name_cstr; /* plain C string for stack trace (js_malloc_rt) */ - uint16_t disruption_pc; /* start of disruption handler (0 = none) */ -} JSCodeRegister; - -/* Pre-parsed MCODE for a single function (off-heap, never GC'd). - Created by jsmcode_parse from cJSON MCODE output. - Instructions remain as cJSON pointers for string-based dispatch. */ -typedef struct JSMCode { - uint16_t nr_args; - uint16_t nr_slots; - /* Pre-flattened instruction array (cJSON array items → C array for O(1) access) */ - cJSON **instrs; - uint32_t instr_count; - /* Label map: label string → instruction index */ - struct { const char *name; uint32_t index; } *labels; - uint32_t label_count; - /* Nested function definitions (indexes into top-level functions array) */ - struct JSMCode **functions; - uint32_t func_count; - /* Keep root cJSON alive (owns all the cJSON nodes instrs[] point into) */ - cJSON *json_root; - MachLineEntry *line_table; /* [instr_count], parallel to instrs[] */ - const char *name; /* function name (points into cJSON tree) */ - const char *filename; /* source filename (points into cJSON tree) */ - uint16_t disruption_pc; /* start of disruption handler (0 = none) */ -} JSMCode; - -/* Frame for closures - used by link-time relocation model where closures - reference outer frames via (depth, slot) addressing. - Stores function as JSValue to survive GC movements. */ -typedef struct JSFrame { - objhdr_t header; /* OBJ_FRAME, cap56 = slot count */ - JSValue function; /* JSValue for GC safety (use JS_VALUE_GET_FUNCTION) */ - JSValue caller; /* JSValue for GC safety (unused currently) */ - uint32_t return_pc; - JSValue slots[]; /* args, captured, locals, temps */ -} JSFrame; - -/* Execution state returned by vm_execute_frame */ -typedef enum { - VM_EXEC_NORMAL, /* Continue executing current frame */ - VM_EXEC_RETURN, /* Frame returned, pop and resume caller */ - VM_EXEC_CALL, /* Need to push new frame for call */ - VM_EXEC_EXCEPTION, /* Exception thrown, unwind frames */ -} VMExecState; - -/* Call info for frame push */ -typedef struct { - JSValue func_obj; - JSValue this_obj; - int argc; - JSValue *argv; - const uint8_t *ret_pc; - int ret_sp_offset; - int call_argc; - int call_has_this; - int is_tail_call; -} VMCallInfo; - -static inline objhdr_t objhdr_set_s (objhdr_t h, bool s) { - return s ? (h | OBJHDR_S_MASK) : (h & ~OBJHDR_S_MASK); -} -static inline uint64_t objhdr_cap56 (objhdr_t h) { - return (uint64_t)((h >> OBJHDR_CAP_SHIFT) & OBJHDR_CAP_MASK); -} - -static inline objhdr_t objhdr_make (uint64_t cap56, uint8_t type, bool r, bool a, bool p, bool s) { - objhdr_t h = 0; - h |= ((objhdr_t)(cap56 & OBJHDR_CAP_MASK)) << OBJHDR_CAP_SHIFT; - h |= (objhdr_t)(type & 7u); - if (s) h |= OBJHDR_S_MASK; - if (p) h |= OBJHDR_P_MASK; - if (a) h |= OBJHDR_A_MASK; - if (r) h |= OBJHDR_R_MASK; - return h; -} - -static inline objhdr_t *chase(JSValue v) { - objhdr_t *oh = JS_VALUE_GET_PTR(v); - if (objhdr_type(*oh) != OBJ_FORWARD) return oh; - do { - objhdr_t *next = (objhdr_t*)objhdr_fwd_ptr(*oh); - if (!next) return oh; /* NULL forward pointer (stale reference bug) */ - oh = next; - } while (objhdr_type(*oh) == OBJ_FORWARD); - return oh; -} - -JS_BOOL JS_IsStone(JSValue v) { - return !JS_IsObject(v) || objhdr_s(*chase(v)); -} - -JS_BOOL JS_IsArray(JSValue v) { - return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_ARRAY; -} - -JS_BOOL JS_IsRecord (JSValue v) { - return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_RECORD; -} - -JS_BOOL JS_IsFunction (JSValue v) { - return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_FUNCTION; -} - -JS_BOOL JS_IsCode (JSValue v) { - return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_CODE; -} - -JS_BOOL JS_IsForwarded (JSValue v) { - return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_FORWARD; -} - -JS_BOOL JS_IsFrame (JSValue v) { - return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_FRAME; -} - -JS_BOOL JS_IsBlob (JSValue v) { - return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_BLOB; -} - -JS_BOOL JS_IsText(JSValue v) { - return MIST_IsImmediateASCII(v) || (JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_TEXT); -} - -/* Intrinsic array type - tagged as JS_TAG_PTR with mist_hdr type OBJ_ARRAY */ -typedef struct JSArray { - objhdr_t mist_hdr; /* mist header at offset 0 */ - word_t len; /* current length */ - JSValue values[]; /* inline flexible array member */ -} JSArray; - -/* JSBlob - binary data per memory.md */ -typedef struct JSBlob { - objhdr_t mist_hdr; - word_t length; - uint8_t bits[]; -} JSBlob; - -typedef struct JSText { - objhdr_t hdr; /* mist header */ - word_t length; /* length (or hash for stoned text) */ - word_t packed[]; /* two chars per packed word */ -} JSText; - -typedef struct slot { - JSValue key; - JSValue val; /* use 'val' to match existing code */ -} slot; - -/* Compatibility alias - old code uses JSRecordEntry */ -typedef slot JSRecordEntry; - -typedef struct JSRecord { - objhdr_t mist_hdr; - struct JSRecord *proto; - word_t len; /* number of entries */ - slot slots[]; /* slots[0] reserved: key low32=class_id, key high32=rec_id, val=opaque */ -} JSRecord; - -/* Helper macros to access class_id, rec_id, opaque from slots[0] per memory.md */ -#define REC_GET_CLASS_ID(rec) ((uint32_t)((rec)->slots[0].key & 0xFFFFFFFF)) -#define REC_GET_REC_ID(rec) ((uint32_t)((rec)->slots[0].key >> 32)) -#define REC_SET_CLASS_ID(rec, cid) do { \ - (rec)->slots[0].key = ((rec)->slots[0].key & 0xFFFFFFFF00000000ULL) | (uint64_t)(cid); \ -} while(0) -#define REC_SET_REC_ID(rec, rid) do { \ - (rec)->slots[0].key = ((rec)->slots[0].key & 0xFFFFFFFF) | ((uint64_t)(rid) << 32); \ -} while(0) -#define REC_GET_OPAQUE(rec) ((void*)(uintptr_t)(rec)->slots[0].val) -#define REC_SET_OPAQUE(rec, ptr) do { (rec)->slots[0].val = (JSValue)(uintptr_t)(ptr); } while(0) - -/* -typedef struct JSFrame { - objhdr_t hdr; - JSFunction *function; - JSFrame *caller; - word_t ret; // return address of the instruction that should be executed - JSValue vars[]; // var[0] is this -} JSFrame; - -typedef struct JSCode { - objhdr_t hdr; - word_t arity; - word_t size; // capacity of an activation from that will execute - word_t closure_size; // reduced capacity for return frames - word_t entry_point; - uint8_t *bytecode; -}; -*/ -/* ============================================================ - Record key helpers (JSValue keys) - ============================================================ */ -/* GC-SAFE: No allocations. */ -static inline JS_BOOL rec_key_is_empty (JSValue key) { - return JS_IsNull (key); -} - -/* GC-SAFE: No allocations. */ -static inline JS_BOOL rec_key_is_tomb (JSValue key) { - return JS_IsException (key); -} - -typedef struct fash64_state { - uint64_t result; - uint64_t sum; -} fash64_state; - -enum { - FASH64_PRIME_11 = 11111111111111111027ull, - FASH64_PRIME_8 = 8888888888888888881ull, - FASH64_PRIME_3 = 3333333333333333271ull -}; - -static inline void fash64_mul_hi_lo (uint64_t a, uint64_t b, uint64_t *hi, uint64_t *lo) { -#if defined(__SIZEOF_INT128__) - __uint128_t p = (__uint128_t)a * (__uint128_t)b; - *lo = (uint64_t)p; - *hi = (uint64_t)(p >> 64); -#elif defined(_MSC_VER) && defined(_M_X64) - *lo = _umul128 (a, b, hi); -#else - /* Portable fallback (no 128-bit type, no _umul128). */ - uint64_t a0 = (uint32_t)a; - uint64_t a1 = a >> 32; - uint64_t b0 = (uint32_t)b; - uint64_t b1 = b >> 32; - - uint64_t p00 = a0 * b0; - uint64_t p01 = a0 * b1; - uint64_t p10 = a1 * b0; - uint64_t p11 = a1 * b1; - - uint64_t mid = (p00 >> 32) + (uint32_t)p01 + (uint32_t)p10; - *lo = (p00 & 0xffffffffull) | (mid << 32); - *hi = p11 + (p01 >> 32) + (p10 >> 32) + (mid >> 32); -#endif -} - -static inline void fash64_begin (fash64_state *s) { - s->result = (uint64_t)FASH64_PRIME_8; - s->sum = (uint64_t)FASH64_PRIME_3; -} - -static inline void fash64_word (fash64_state *s, uint64_t word) { - uint64_t high, low; - uint64_t mixed = s->result ^ word; - - fash64_mul_hi_lo (mixed, (uint64_t)FASH64_PRIME_11, &high, &low); - - s->sum += high; - s->result = low ^ s->sum; -} - -static inline void fash64_block (fash64_state *s, const uint64_t *block, size_t word_count) { - for (size_t i = 0; i < word_count; i++) - fash64_word (s, block[i]); -} - -static inline uint64_t fash64_end (const fash64_state *s) { return s->result; } - -/* Convenience one-shot helper */ -static inline uint64_t fash64_hash_words (const uint64_t *words, - size_t word_count, - uint64_t extra_word) { - fash64_state s; - fash64_begin (&s); - fash64_block (&s, words, word_count); - fash64_word (&s, extra_word); - return fash64_end (&s); -} - -static inline uint64_t fash64_hash_one (uint64_t word) { - uint64_t high, low; - uint64_t mixed = (uint64_t)FASH64_PRIME_8 ^ word; - fash64_mul_hi_lo (mixed, (uint64_t)FASH64_PRIME_11, &high, &low); - uint64_t sum = (uint64_t)FASH64_PRIME_3 + high; - return low ^ sum; -} - -static inline word_t JSText_len (const JSText *text) { - if (objhdr_s (text->hdr)) return objhdr_cap56 (text->hdr); - return text->length; -} - -static inline JS_BOOL JSText_equal (const JSText *a, const JSText *b) { - word_t len_a = JSText_len (a); - word_t len_b = JSText_len (b); - if (len_a != len_b) return FALSE; - size_t word_count = (len_a + 1) / 2; - return memcmp (a->packed, b->packed, word_count * sizeof (word_t)) == 0; -} - -static JS_BOOL JSText_equal_ascii (const JSText *text, JSValue imm) { - int len = MIST_GetImmediateASCIILen (imm); - if (len != JSText_len (text)) return FALSE; - for (int i = 0; i < len; i++) { - uint32_t c = string_get (text, i); - if (c >= 0x80) return FALSE; - if ((uint8_t)c != (uint8_t)MIST_GetImmediateASCIIChar (imm, i)) - return FALSE; - } - return TRUE; -} - -/* Get hash for a JSText value. - For stoned text (s=1): hash is cached in length field (computed on first access). - For pre-text (s=0): compute hash on the fly. */ -static uint64_t get_text_hash (JSText *text) { - uint64_t len = objhdr_cap56 (text->hdr); - size_t word_count = (len + 1) / 2; - - if (objhdr_s (text->hdr)) { - /* Stoned text: check for cached hash */ - if (text->length != 0) return text->length; - /* Compute and cache hash using content length from header */ - text->length = fash64_hash_words (text->packed, word_count, len); - if (!text->length) text->length = 1; - return text->length; - } else { - /* Pre-text: compute hash on the fly */ - return fash64_hash_words (text->packed, word_count, len); - } -} - -/* Pack UTF-32 characters into 64-bit words (2 chars per word) */ -static void pack_utf32_to_words (const uint32_t *utf32, uint32_t len, uint64_t *packed) { - for (uint32_t i = 0; i < len; i += 2) { - uint64_t hi = utf32[i]; - uint64_t lo = (i + 1 < len) ? utf32[i + 1] : 0; - packed[i / 2] = (hi << 32) | lo; - } -} - -/* Compare two packed UTF-32 texts for equality */ -static int text_equal (JSText *a, const uint64_t *packed_b, uint32_t len_b) { - uint32_t len_a = (uint32_t)objhdr_cap56 (a->hdr); - if (len_a != len_b) return 0; - size_t word_count = (len_a + 1) / 2; - return memcmp (a->packed, packed_b, word_count * sizeof (uint64_t)) == 0; -} - -/* Max length for key strings (identifiers, property names) */ -#define JS_KEY_MAX_LEN 4096 - -/* Forward declarations for stone arena functions (defined after JSContext) */ -static void *st_alloc (JSContext *ctx, size_t bytes, size_t align); -static void st_free_all (JSContext *ctx); -static int st_text_resize (JSContext *ctx); -static JSValue intern_text_to_value (JSContext *ctx, const uint32_t *utf32, uint32_t len); -static JSValue js_key_new (JSContext *ctx, const char *str); -static JSValue js_key_new_len (JSContext *ctx, const char *str, size_t len); - -/* must be large enough to have a negligible runtime cost and small - enough to call the interrupt callback often. */ -#define JS_INTERRUPT_COUNTER_INIT 10000 - -struct JSContext { - JSRuntime *rt; - - /* Actor memory block (bump allocation) */ - uint8_t *heap_base; /* start of current block */ - uint8_t *heap_free; /* bump pointer */ - uint8_t *heap_end; /* end of block */ - size_t current_block_size; /* current block size (64KB initially) */ - size_t next_block_size; /* doubles if <10% recovered after GC */ - - /* Stone arena - permanent immutable allocations */ - uint8_t *stone_base; /* stone arena base */ - uint8_t *stone_free; /* stone arena bump pointer */ - uint8_t *stone_end; /* stone arena end */ - - /* Stone text intern table */ - void *st_pages; /* stone page list for large allocations */ - uint32_t *st_text_hash; /* hash table (slot -> id) */ - JSText **st_text_array; /* array of JSText pointers indexed by id */ - uint32_t st_text_size; /* hash table size (power of 2) */ - uint32_t st_text_count; /* number of interned texts */ - uint32_t st_text_resize; /* threshold for resize */ - - uint16_t binary_object_count; - int binary_object_size; - - /* Record key allocator */ - uint32_t rec_key_next; - - JSGCRef *top_gc_ref; /* used to reference temporary GC roots (stack top) */ - JSGCRef *last_gc_ref; /* used to reference temporary GC roots (list) */ - - int class_count; /* size of class_array and class_proto */ - JSClass *class_array; - JSValue *class_proto; - JSValue regexp_ctor; - JSValue native_error_proto[JS_NATIVE_ERROR_COUNT]; - JSValue throw_type_error; - - JSValue global_obj; /* global object (immutable intrinsics) */ - - uint64_t random_state; - - /* when the counter reaches zero, JSRutime.interrupt_handler is called */ - int interrupt_counter; - - /* if NULL, RegExp compilation is not supported */ - JSValue (*compile_regexp) (JSContext *ctx, JSValue pattern, JSValue flags); - void *user_opaque; - - js_hook trace_hook; - int trace_type; - void *trace_data; - - /* Trampoline VM stacks (per actor/context) */ - struct VMFrame *frame_stack; /* array of frames */ - int frame_stack_top; /* current frame index (-1 = empty) */ - int frame_stack_capacity; /* allocated capacity */ - JSValue *value_stack; /* array of JSValues for locals/operands */ - int value_stack_top; /* current top index */ - int value_stack_capacity; /* allocated capacity */ - - /* Register VM frame root (updated by GC when frame moves) */ - JSValue reg_current_frame; /* current JSFrameRegister being executed */ - uint32_t current_register_pc; /* PC at exception time */ - - /* Execution state (moved from JSRuntime — per-actor) */ - JSValue current_exception; - struct JSStackFrame *current_stack_frame; - BOOL current_exception_is_uncatchable : 8; - BOOL in_out_of_memory : 8; - - JSInterruptHandler *interrupt_handler; - void *interrupt_opaque; - - /* Stack overflow protection */ - size_t stack_size; - const uint8_t *stack_top; - const uint8_t *stack_limit; - - /* Parser state (for GC to scan cpool during parsing) */ - struct JSFunctionDef *current_parse_fd; -}; - -/* ============================================================ - Functions that need JSContext definition - ============================================================ */ - -int JS_IsPretext (JSValue v) { - if (!JS_IsText (v)) return 0; - JSText *text = (JSText *)JS_VALUE_GET_PTR (v); - return !objhdr_s (text->hdr); -} - -/* Convert JSValue key (string) to C string in buffer. - Returns buf, filled with the string content or "[?]" if not a string. */ -static inline const char *JS_KeyGetStr (JSContext *ctx, char *buf, size_t buf_size, JSValue key) { - if (JS_IsText (key)) { - const char *cstr = JS_ToCString (ctx, key); - if (cstr) { - snprintf (buf, buf_size, "%s", cstr); - JS_FreeCString (ctx, cstr); - return buf; - } - } - snprintf (buf, buf_size, "[?]"); - return buf; -} - -JSValue *JS_PushGCRef (JSContext *ctx, JSGCRef *ref) { - ref->prev = ctx->top_gc_ref; - ctx->top_gc_ref = ref; - ref->val = JS_NULL; - return &ref->val; -} - -JSValue JS_PopGCRef (JSContext *ctx, JSGCRef *ref) { - ctx->top_gc_ref = ref->prev; - return ref->val; -} - -JSValue *JS_AddGCRef (JSContext *ctx, JSGCRef *ref) { - ref->prev = ctx->last_gc_ref; - ctx->last_gc_ref = ref; - ref->val = JS_NULL; - return &ref->val; -} - -void JS_DeleteGCRef (JSContext *ctx, JSGCRef *ref) { - JSGCRef **pref, *ref1; - pref = &ctx->last_gc_ref; - for (;;) { - ref1 = *pref; - if (ref1 == NULL) - abort (); - if (ref1 == ref) { - *pref = ref1->prev; - break; - } - pref = &ref1->prev; - } -} - -/* ============================================================ - Stone Arena Functions - ============================================================ */ - -/* Stone page for large allocations */ -typedef struct StonePage { - struct StonePage *next; - size_t size; - uint8_t data[]; -} StonePage; - -/* Initial stone text table size */ -#define ST_TEXT_INITIAL_SIZE 256 - -/* Allocate from stone arena (permanent, immutable memory) */ -static void *st_alloc (JSContext *ctx, size_t bytes, size_t align) { - /* Align the request */ - bytes = (bytes + align - 1) & ~(align - 1); - - /* Check if we have space in the stone arena */ - if (ctx->stone_base && ctx->stone_free + bytes <= ctx->stone_end) { - void *ptr = ctx->stone_free; - ctx->stone_free += bytes; - return ptr; - } - - /* No stone arena or not enough space - allocate a page */ - size_t page_size = sizeof (StonePage) + bytes; - StonePage *page = malloc (page_size); - if (!page) return NULL; - - page->next = ctx->st_pages; - page->size = bytes; - ctx->st_pages = page; - - return page->data; -} - -/* Free all stone arena pages */ -static void st_free_all (JSContext *ctx) { - StonePage *page = ctx->st_pages; - while (page) { - StonePage *next = page->next; - free (page); - page = next; - } - ctx->st_pages = NULL; -} - -/* Resize the stone text intern hash table */ -static int st_text_resize (JSContext *ctx) { - uint32_t new_size, new_resize; - uint32_t *new_hash; - JSText **new_array; - - if (ctx->st_text_size == 0) { - /* Initial allocation */ - new_size = ST_TEXT_INITIAL_SIZE; - } else { - /* Double the size */ - new_size = ctx->st_text_size * 2; - } - new_resize = new_size * 3 / 4; /* 75% load factor */ - - /* Allocate new hash table (use runtime malloc, not bump allocator) */ - new_hash = js_malloc_rt (new_size * sizeof (uint32_t)); - if (!new_hash) return -1; - memset (new_hash, 0, new_size * sizeof (uint32_t)); - - /* Allocate new text array (one extra for 1-based indexing) */ - new_array = js_malloc_rt ((new_size + 1) * sizeof (JSText *)); - if (!new_array) { - js_free_rt(new_hash); - return -1; - } - memset (new_array, 0, (new_size + 1) * sizeof (JSText *)); - - /* Rehash existing entries */ - if (ctx->st_text_count > 0) { - uint32_t mask = new_size - 1; - for (uint32_t id = 1; id <= ctx->st_text_count; id++) { - JSText *text = ctx->st_text_array[id]; - new_array[id] = text; - - /* Compute hash and find slot */ - uint64_t hash = get_text_hash (text); - uint32_t slot = hash & mask; - while (new_hash[slot] != 0) - slot = (slot + 1) & mask; - new_hash[slot] = id; - } - } - - /* Free old tables */ - if (ctx->st_text_hash) js_free_rt (ctx->st_text_hash); - if (ctx->st_text_array) js_free_rt (ctx->st_text_array); - - ctx->st_text_hash = new_hash; - ctx->st_text_array = new_array; - ctx->st_text_size = new_size; - ctx->st_text_resize = new_resize; - - return 0; -} - -/* Realloc with slack reporting (for bump allocator) - WARNING: This function is NOT GC-safe! The caller must protect the source - object with a GC ref before calling, and re-chase the pointer after. */ -void *js_realloc (JSContext *ctx, void *ptr, size_t size) { - void *new_ptr; - - /* Align size to 8 bytes */ - size = (size + 7) & ~7; - - if (!ptr) { - /* New allocation */ - new_ptr = js_malloc (ctx, size); - if (!new_ptr) return NULL; - return new_ptr; - } - - /* Bump allocator: just allocate new space. - Caller is responsible for protecting ptr and copying data. */ - new_ptr = js_malloc (ctx, size); - return new_ptr; -} - -/* ============================================================ - GC Public API - ============================================================ */ - -/* Forward declaration for ctx_gc */ -static int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size); - -/* JS_MarkValue - mark a value during GC traversal. - With copying GC, this is a no-op as we discover live objects by tracing. */ -void JS_MarkValue (JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) { - (void)rt; - (void)val; - (void)mark_func; - /* No-op with copying GC - values are discovered by tracing from roots */ -} - -/* Helper to check if a pointer is in stone memory */ -static inline int is_stone_ptr (JSContext *ctx, void *ptr) { - return (uint8_t *)ptr >= ctx->stone_base && (uint8_t *)ptr < ctx->stone_end; -} - -/* Intern a UTF-32 string as a stone text, returning a JSValue string */ -static JSValue intern_text_to_value (JSContext *ctx, const uint32_t *utf32, uint32_t len) { - /* Pack UTF-32 for hashing and comparison */ - size_t word_count = (len + 1) / 2; - uint64_t *packed = alloca (word_count * sizeof (uint64_t)); - pack_utf32_to_words (utf32, len, packed); - - uint64_t hash = fash64_hash_words (packed, word_count, len); - - /* Look up in hash table */ - uint32_t mask = ctx->st_text_size - 1; - uint32_t slot = hash & mask; - - while (ctx->st_text_hash[slot] != 0) { - uint32_t id = ctx->st_text_hash[slot]; - JSText *existing = ctx->st_text_array[id]; - if (text_equal (existing, packed, len)) { - /* Found existing entry */ - return JS_MKPTR (existing); - } - slot = (slot + 1) & mask; - } - - /* Not found - create new entry */ - if (ctx->st_text_count >= ctx->st_text_resize) { - if (st_text_resize (ctx) < 0) return JS_NULL; /* OOM */ - /* Recompute slot after resize */ - mask = ctx->st_text_size - 1; - slot = hash & mask; - while (ctx->st_text_hash[slot] != 0) - slot = (slot + 1) & mask; - } - - /* Allocate JSText in stone arena */ - size_t text_size = sizeof (JSText) + word_count * sizeof (uint64_t); - JSText *text = st_alloc (ctx, text_size, 8); - if (!text) return JS_NULL; /* OOM */ - - /* Initialize the text */ - text->hdr = objhdr_make (len, OBJ_TEXT, false, false, false, true); /* s=1 for stoned */ - text->length = hash; /* Store hash in length field for stoned text */ - memcpy (text->packed, packed, word_count * sizeof (uint64_t)); - - /* Add to intern table */ - uint32_t new_id = ++ctx->st_text_count; - ctx->st_text_hash[slot] = new_id; - ctx->st_text_array[new_id] = text; - - return JS_MKPTR (text); -} - -/* Create a stoned, interned key from a UTF-8 C string. - Returns immediate ASCII text if ≤7 ASCII chars, otherwise stoned interned - text. The returned JSValue does NOT need to be freed (it's either immediate - or part of the stone arena). */ -static JSValue js_key_new (JSContext *ctx, const char *str) { - size_t len = strlen (str); - - /* Try immediate ASCII first (≤7 ASCII chars) */ - if (len <= MIST_ASCII_MAX_LEN) { - JSValue imm = MIST_TryNewImmediateASCII (str, len); - if (!JS_IsNull (imm)) return imm; - } - - /* Check length limit */ - if (len > JS_KEY_MAX_LEN) return JS_NULL; - - /* Convert UTF-8 to UTF-32 for interning - use stack buffer */ - const uint8_t *p = (const uint8_t *)str; - const uint8_t *p_end = p + len; - uint32_t utf32_buf[JS_KEY_MAX_LEN]; - uint32_t utf32_len = 0; - - while (p < p_end && utf32_len < JS_KEY_MAX_LEN) { - uint32_t c; - const uint8_t *p_next; - if (*p < 0x80) { - c = *p++; - } else { - c = unicode_from_utf8 (p, p_end - p, &p_next); - if (c == (uint32_t)-1) { - c = 0xFFFD; /* replacement char for invalid UTF-8 */ - p++; - } else { - p = p_next; - } - } - utf32_buf[utf32_len++] = c; - } - - return intern_text_to_value (ctx, utf32_buf, utf32_len); -} - -/* JS_NewAtomString - creates JSValue string from C string (for compatibility) - */ -static JSValue JS_NewAtomString (JSContext *ctx, const char *str) { - return js_key_new (ctx, str); -} - -/* Create a key from a UTF-8 string with explicit length */ -static JSValue js_key_new_len (JSContext *ctx, const char *str, size_t len) { - /* Try immediate ASCII first (≤7 ASCII chars) */ - if (len <= MIST_ASCII_MAX_LEN) { - JSValue imm = MIST_TryNewImmediateASCII (str, len); - if (!JS_IsNull (imm)) return imm; - } - - /* Check length limit */ - if (len > JS_KEY_MAX_LEN) return JS_NULL; - - /* Convert UTF-8 to UTF-32 for interning */ - const uint8_t *p = (const uint8_t *)str; - const uint8_t *p_end = p + len; - uint32_t utf32_buf[JS_KEY_MAX_LEN]; - uint32_t utf32_len = 0; - - while (p < p_end && utf32_len < JS_KEY_MAX_LEN) { - uint32_t c; - const uint8_t *p_next; - if (*p < 0x80) { - c = *p++; - } else { - c = unicode_from_utf8 (p, p_end - p, &p_next); - if (c == (uint32_t)-1) { - c = 0xFFFD; - p++; - } else { - p = p_next; - } - } - utf32_buf[utf32_len++] = c; - } - - return intern_text_to_value (ctx, utf32_buf, utf32_len); -} - -typedef union JSFloat64Union { - double d; - uint64_t u64; - uint32_t u32[2]; -} JSFloat64Union; - -/* JS_IsText is defined in quickjs.h */ - -/* Helper to get array capacity from mist_hdr */ -static inline uint32_t js_array_cap (JSArray *arr) { - return objhdr_cap56 (arr->mist_hdr); -} - -/* JSRegExp: regular expression object data (must come before JSRecord/JSRecord) */ -typedef struct JSRegExp { - char *pattern; /* UTF-8, null-terminated, js_malloc_rt'd */ - uint32_t pattern_len; - uint8_t *bytecode; /* raw lre bytecode, js_malloc_rt'd */ - uint32_t bytecode_len; -} JSRegExp; - -#define obj_is_stone(rec) objhdr_s ((rec)->mist_hdr) -#define obj_set_stone(rec) ((rec)->mist_hdr = objhdr_set_s ((rec)->mist_hdr, true)) - -#define JS_VALUE_GET_RECORD(v) ((JSRecord *)chase (v)) - -/* Get prototype from object (works for both JSRecord and JSRecord since they - * share layout) */ -#define JS_OBJ_GET_PROTO(p) ((JSRecord *)((JSRecord *)(p))->proto) - -/* Initial capacity for new records (mask = 7, 8 slots total) */ -#define JS_RECORD_INITIAL_MASK 7 - -/* ============================================================ - JSRecord Core Operations - ============================================================ */ - -// can check if key by checking for 0 here -static uint64_t js_key_hash (JSValue key) { - if (MIST_IsImmediateASCII (key)) { - /* Hash immediate ASCII the same way as heap strings for consistency */ - int len = MIST_GetImmediateASCIILen (key); - if (len == 0) return 1; - size_t word_count = (len + 1) / 2; - uint64_t packed[4]; /* Max 7 chars = 4 words */ - for (size_t i = 0; i < word_count; i++) { - uint32_t c0 = (i * 2 < (size_t)len) ? MIST_GetImmediateASCIIChar (key, i * 2) : 0; - uint32_t c1 = (i * 2 + 1 < (size_t)len) ? MIST_GetImmediateASCIIChar (key, i * 2 + 1) : 0; - packed[i] = ((uint64_t)c0 << 32) | c1; - } - uint64_t h = fash64_hash_words (packed, word_count, len); - return h ? h : 1; - } - - if (!JS_IsPtr (key)) return 0; - - /* Use chase to follow forwarding pointers */ - void *ptr = chase (key); - objhdr_t hdr = *(objhdr_t *)ptr; - uint8_t type = objhdr_type (hdr); - - if (type == OBJ_TEXT) { - /* For JSText (stoned strings), use get_text_hash */ - JSText *text = (JSText *)ptr; - return get_text_hash (text); - } - if (type == OBJ_RECORD) { - JSRecord *rec = (JSRecord *)ptr; - uint32_t rec_id = REC_GET_REC_ID(rec); - if (rec_id == 0) return 0; - return fash64_hash_one (rec_id); - } - - return 0; -} - -static JS_BOOL js_key_equal (JSValue a, JSValue b) { - if (a == b) return TRUE; - - /* Use chase to follow forwarding pointers */ - if (MIST_IsImmediateASCII (a)) { - if (MIST_IsImmediateASCII (b)) return FALSE; - if (!JS_IsPtr (b)) return FALSE; - JSText *tb = (JSText *)chase (b); - if (objhdr_type (tb->hdr) != OBJ_TEXT) return FALSE; - return JSText_equal_ascii (tb, a); - } - if (MIST_IsImmediateASCII (b)) { - if (!JS_IsPtr (a)) return FALSE; - JSText *ta = (JSText *)chase (a); - if (objhdr_type (ta->hdr) != OBJ_TEXT) return FALSE; - return JSText_equal_ascii (ta, b); - } - - if (!JS_IsPtr (a) || !JS_IsPtr (b)) return FALSE; - - void *pa = chase (a); - void *pb = chase (b); - objhdr_t ha = *(objhdr_t *)pa; - objhdr_t hb = *(objhdr_t *)pb; - uint8_t type_a = objhdr_type (ha); - uint8_t type_b = objhdr_type (hb); - - if (type_a != type_b) return FALSE; - if (type_a == OBJ_RECORD) return FALSE; /* pointer equality handled above */ - if (type_a == OBJ_TEXT) - return JSText_equal ((JSText *)pa, (JSText *)pb); - - return FALSE; -} - -/* Compare a JSValue key with a C string literal. - Used for comparing with internal names that are too long for immediate - ASCII. */ -static JS_BOOL js_key_equal_str (JSValue a, const char *str) { - size_t len = strlen (str); - - if (MIST_IsImmediateASCII (a)) { - int imm_len = MIST_GetImmediateASCIILen (a); - if ((size_t)imm_len != len) return FALSE; - for (int i = 0; i < imm_len; i++) { - if (MIST_GetImmediateASCIIChar (a, i) != str[i]) return FALSE; - } - return TRUE; - } - - if (!JS_IsPtr (a)) return FALSE; - JSText *ta = (JSText *)JS_VALUE_GET_PTR (a); - if (objhdr_type (ta->hdr) != OBJ_TEXT) return FALSE; - uint64_t txt_len = objhdr_cap56 (ta->hdr); - if (txt_len != len) return FALSE; - - /* Compare character by character (UTF-32 vs ASCII) */ - for (size_t i = 0; i < len; i++) { - if (string_get (ta, i) != (uint32_t)(unsigned char)str[i]) - return FALSE; - } - return TRUE; -} - -/* GC-SAFE: No allocations. Caller must pass freshly-chased rec. - Find slot for a key in record's own table. - Returns slot index (>0) if found, or -(insert_slot) if not found. */ -static int rec_find_slot (JSRecord *rec, JSValue k) { - uint64_t mask = objhdr_cap56 (rec->mist_hdr); - uint64_t h64 = js_key_hash (k); - uint64_t slot = (h64 & mask); - if (slot == 0) slot = 1; /* slot 0 is reserved */ - - uint64_t first_tomb = 0; - - for (uint64_t i = 0; i <= mask; i++) { - JSValue slot_key = rec->slots[slot].key; - - if (rec_key_is_empty (slot_key)) { - /* Empty slot - key not found */ - return first_tomb ? -(int)first_tomb : -(int)slot; - } - if (rec_key_is_tomb (slot_key)) { - /* Tombstone - remember for insertion but keep searching */ - if (first_tomb == 0) first_tomb = slot; - } else if (js_key_equal (slot_key, k)) { - /* Found it */ - return (int)slot; - } - - slot = (slot + 1) & mask; - if (slot == 0) slot = 1; - } - - /* Table full (shouldn't happen with proper load factor) */ - return first_tomb ? -(int)first_tomb : -1; -} - -/* GC-SAFE: No allocations. Walks prototype chain via direct pointers. - Caller must pass freshly-chased rec. */ -static JSValue rec_get (JSContext *ctx, JSRecord *rec, JSValue k) { - if (rec_key_is_empty (k) || rec_key_is_tomb (k)) return JS_NULL; - - /* Walk prototype chain */ - JSRecord *p = rec; - while (p) { - int slot = rec_find_slot (p, k); - if (slot > 0) { return p->slots[slot].val; } - p = p->proto; - } - return JS_NULL; -} - -/* GC-SAFE: Resize record by allocating a new larger record and copying all data. - Uses GC ref to protect source during allocation. - Updates *pobj to point to the new record. - Returns 0 on success, -1 on failure. */ -static int rec_resize (JSContext *ctx, JSValue *pobj, uint64_t new_mask) { - /* Protect the source object with a GC ref in case js_malloc triggers GC */ - JSGCRef obj_ref; - JS_AddGCRef (ctx, &obj_ref); - obj_ref.val = *pobj; - - JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (*pobj); - uint64_t old_mask = objhdr_cap56 (rec->mist_hdr); - - /* Allocate new record with larger capacity - may trigger GC! */ - size_t slots_size = sizeof (slot) * (new_mask + 1); - size_t total_size = sizeof (JSRecord) + slots_size; - - JSRecord *new_rec = js_malloc (ctx, total_size); - if (!new_rec) { - JS_DeleteGCRef (ctx, &obj_ref); - return -1; - } - - /* Re-get record from GC ref - it may have moved during GC */ - rec = (JSRecord *)JS_VALUE_GET_OBJ (obj_ref.val); - old_mask = objhdr_cap56 (rec->mist_hdr); - - /* Initialize new record */ - new_rec->mist_hdr = objhdr_make (new_mask, OBJ_RECORD, false, false, false, false); - new_rec->proto = rec->proto; - new_rec->len = 0; - - /* Initialize all slots to empty */ - for (uint64_t i = 0; i <= new_mask; i++) { - new_rec->slots[i].key = JS_NULL; - new_rec->slots[i].val = JS_NULL; - } - - /* Copy slot[0] (class_id, rec_id, opaque) */ - new_rec->slots[0].key = rec->slots[0].key; - new_rec->slots[0].val = rec->slots[0].val; - - /* Rehash all valid entries from old to new */ - for (uint64_t i = 1; i <= old_mask; i++) { - JSValue k = rec->slots[i].key; - if (!rec_key_is_empty (k) && !rec_key_is_tomb (k)) { - /* Insert into new record using linear probing */ - uint64_t h64 = js_key_hash (k); - uint64_t slot = (h64 & new_mask); - if (slot == 0) slot = 1; - - while (!rec_key_is_empty (new_rec->slots[slot].key)) { - slot = (slot + 1) & new_mask; - if (slot == 0) slot = 1; - } - - new_rec->slots[slot].key = k; - new_rec->slots[slot].val = rec->slots[i].val; - new_rec->len++; - } - } - - /* Install forward header at old location so stale references can find the new record */ - rec->mist_hdr = objhdr_make_fwd (new_rec); - - /* Update caller's JSValue to point to new record */ - *pobj = JS_MKPTR (new_rec); - - JS_DeleteGCRef (ctx, &obj_ref); - return 0; -} - -/* GC-SAFE: May call rec_resize which allocates. - Takes JSValue* so the object can be tracked through GC. - Updates *pobj if the record is resized. */ -static int rec_set_own (JSContext *ctx, JSValue *pobj, JSValue k, JSValue val) { - if (rec_key_is_empty (k) || rec_key_is_tomb (k)) { - return -1; - } - - JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (*pobj); - int slot = rec_find_slot (rec, k); - - if (slot > 0) { - /* Existing key - replace value */ - rec->slots[slot].val = val; - return 0; - } - - /* New key - check if resize needed (75% load factor) */ - uint32_t mask = (uint32_t)objhdr_cap56 (rec->mist_hdr); - if ((rec->len + 1) * 4 > mask * 3) { - /* Over 75% load factor - resize. Protect key and value in case GC runs. */ - JSGCRef k_ref, val_ref; - JS_AddGCRef (ctx, &k_ref); - JS_AddGCRef (ctx, &val_ref); - k_ref.val = k; - val_ref.val = val; - - uint32_t new_mask = (mask + 1) * 2 - 1; - if (rec_resize (ctx, pobj, new_mask) < 0) { - JS_DeleteGCRef (ctx, &val_ref); - JS_DeleteGCRef (ctx, &k_ref); - return -1; - } - - /* Re-get values after resize (they may have moved during GC) */ - k = k_ref.val; - val = val_ref.val; - JS_DeleteGCRef (ctx, &val_ref); - JS_DeleteGCRef (ctx, &k_ref); - - /* Re-get rec after resize (pobj now points to new record) */ - rec = (JSRecord *)JS_VALUE_GET_OBJ (*pobj); - /* Re-find slot after resize */ - slot = rec_find_slot (rec, k); - } - - /* Insert at -slot */ - int insert_slot = -slot; - rec->slots[insert_slot].key = k; - rec->slots[insert_slot].val = val; - rec->len++; - - return 0; -} - -/* Helper macros to access class_id and rec_id from slots[0].key per memory.md: - - Low 32 bits of slots[0].key = class_id - - High 32 bits of slots[0].key = rec_id - - slots[0].val = opaque C pointer */ -#define REC_GET_CLASS_ID(rec) ((uint32_t)((rec)->slots[0].key & 0xFFFFFFFF)) -#define REC_GET_REC_ID(rec) ((uint32_t)((rec)->slots[0].key >> 32)) -#define REC_SET_CLASS_ID(rec, cid) do { \ - (rec)->slots[0].key = ((rec)->slots[0].key & 0xFFFFFFFF00000000ULL) | (uint64_t)(cid); \ -} while(0) -#define REC_SET_REC_ID(rec, rid) do { \ - (rec)->slots[0].key = ((rec)->slots[0].key & 0xFFFFFFFF) | ((uint64_t)(rid) << 32); \ -} while(0) -#define REC_GET_OPAQUE(rec) ((void*)(uintptr_t)(rec)->slots[0].val) -#define REC_SET_OPAQUE(rec, ptr) do { (rec)->slots[0].val = (JSValue)(uintptr_t)(ptr); } while(0) - -/* Allocate a new record with specified class_id. - Uses bump allocation. Slots are inline (flexible array member). - Per memory.md: slots[0] is reserved for class_id, rec_id, and opaque. */ -static JSRecord *js_new_record_class (JSContext *ctx, uint32_t initial_mask, JSClassID class_id) { - if (initial_mask == 0) initial_mask = JS_RECORD_INITIAL_MASK; - - /* Allocate record + inline slots in one bump allocation */ - size_t slots_size = sizeof (slot) * (initial_mask + 1); - size_t total_size = sizeof (JSRecord) + slots_size; - - JSRecord *rec = js_malloc (ctx, total_size); - if (!rec) return NULL; - - rec->mist_hdr = objhdr_make (initial_mask, OBJ_RECORD, false, false, false, false); - rec->proto = NULL; - rec->len = 0; - - /* Initialize all slots to empty (JS_NULL) */ - for (uint32_t i = 0; i <= initial_mask; i++) { - rec->slots[i].key = JS_NULL; - rec->slots[i].val = JS_NULL; - } - - /* slots[0] is reserved: store class_id (low 32) and rec_id (high 32) in key */ - uint32_t rec_id = ++ctx->rec_key_next; - rec->slots[0].key = (JSValue)class_id | ((JSValue)rec_id << 32); - rec->slots[0].val = 0; /* opaque pointer, initially NULL */ - - return rec; -} - -typedef enum { - JS_FUNC_KIND_C, - JS_FUNC_KIND_BYTECODE, - JS_FUNC_KIND_C_DATA, - JS_FUNC_KIND_REGISTER, /* register-based VM function */ - JS_FUNC_KIND_MCODE, /* MCODE JSON interpreter */ -} JSFunctionKind; - -typedef struct JSFunction { - objhdr_t header; /* must come first */ - JSValue name; /* function name as JSValue text */ - int16_t length; /* arity: max allowed arguments (-1 = variadic) */ - uint8_t kind; - union { - struct { - JSCFunctionType c_function; - uint8_t cproto; - int16_t magic; - } cfunc; - struct { - struct JSFunctionBytecode *function_bytecode; - JSValue outer_frame; /* JSFrame JSValue, lexical parent for closures */ - JSValue env_record; /* stone record, module environment */ - } func; - struct { - JSCodeRegister *code; /* compiled register code (off-heap) */ - JSValue env_record; /* stone record, module environment */ - JSValue outer_frame; /* JSFrame JSValue, for closures */ - } reg; - struct { - JSMCode *code; /* pre-parsed MCODE (off-heap) */ - JSValue outer_frame; /* lexical parent frame for closures */ - JSValue env_record; /* module env or JS_NULL */ - } mcode; - } u; -} JSFunction; - -typedef struct JSClosureVar { - uint8_t is_local : 1; - uint8_t is_arg : 1; - uint8_t is_const : 1; - uint8_t is_lexical : 1; - uint8_t var_kind : 4; /* see JSVarKindEnum */ - /* 8 bits available */ - uint16_t var_idx; /* is_local = TRUE: index to a normal variable of the - parent function. otherwise: index to a closure - variable of the parent function */ - JSValue var_name; -} JSClosureVar; - -#define ARG_SCOPE_INDEX 1 -#define ARG_SCOPE_END (-2) - -typedef struct JSVarScope { - int parent; /* index into fd->scopes of the enclosing scope */ - int first; /* index into fd->vars of the last variable in this scope */ -} JSVarScope; - -typedef enum { - /* XXX: add more variable kinds here instead of using bit fields */ - JS_VAR_NORMAL, - JS_VAR_FUNCTION_DECL, /* lexical var with function declaration */ - JS_VAR_NEW_FUNCTION_DECL, /* lexical var with async/generator - function declaration */ - JS_VAR_CATCH, - JS_VAR_FUNCTION_NAME, /* function expression name */ -} JSVarKindEnum; - -/* XXX: could use a different structure in bytecode functions to save - memory */ -typedef struct JSVarDef { - JSValue var_name; - /* index into fd->scopes of this variable lexical scope */ - int scope_level; - /* during compilation: - - if scope_level = 0: scope in which the variable is defined - - if scope_level != 0: index into fd->vars of the next - variable in the same or enclosing lexical scope - in a bytecode function: - index into fd->vars of the next - variable in the same or enclosing lexical scope - */ - int scope_next; - uint8_t is_const : 1; - uint8_t is_lexical : 1; - uint8_t is_captured : 1; - uint8_t var_kind : 4; /* see JSVarKindEnum */ - /* only used during compilation: function pool index for lexical - variables with var_kind = - JS_VAR_FUNCTION_DECL/JS_VAR_NEW_FUNCTION_DECL or scope level of - the definition of the 'var' variables (they have scope_level = - 0) */ - int func_pool_idx : 24; /* only used during compilation : index in - the constant pool for hoisted function - definition */ -} JSVarDef; - -/* for the encoding of the pc2line table */ -#define PC2LINE_BASE (-1) -#define PC2LINE_RANGE 5 -#define PC2LINE_OP_FIRST 1 -#define PC2LINE_DIFF_PC_MAX ((255 - PC2LINE_OP_FIRST) / PC2LINE_RANGE) - -typedef struct JSFunctionBytecode { - objhdr_t header; /* must come first */ - uint8_t js_mode; - uint8_t has_prototype : 1; /* true if a prototype field is necessary */ - uint8_t has_simple_parameter_list : 1; - uint8_t func_kind : 2; - uint8_t has_debug : 1; - uint8_t read_only_bytecode : 1; - uint8_t is_direct_or_indirect_eval - : 1; /* used by JS_GetScriptOrModuleName() */ - /* XXX: 10 bits available */ - uint8_t *byte_code_buf; /* (self pointer) */ - int byte_code_len; - JSValue func_name; - JSVarDef *vardefs; /* arguments + local variables (arg_count + var_count) - (self pointer) */ - JSClosureVar - *closure_var; /* list of variables in the closure (self pointer) */ - uint16_t arg_count; - uint16_t var_count; - uint16_t defined_arg_count; /* for length function property */ - uint16_t stack_size; /* maximum stack size */ - JSValue *cpool; /* constant pool (self pointer) */ - int cpool_count; - int closure_var_count; - struct { - /* debug info, move to separate structure to save memory? */ - JSValue filename; - int source_len; - int pc2line_len; - uint8_t *pc2line_buf; - char *source; - } debug; -} JSFunctionBytecode; - -/* New simplified compiled unit structure for Phase 1+ simplification. - Replaces JSFunctionBytecode with a simpler model: - - No closure machinery (uses outer_frame chain at runtime) - - Free variables resolved at link time against env + globals - - Nested functions stored as separate units in cpool */ -typedef struct JSCompiledUnit { - objhdr_t header; /* must come first */ - - /* Bytecode (self pointer) */ - uint8_t *byte_code_buf; - int byte_code_len; - - /* Constants - strings, numbers, nested unit refs (self pointer) */ - JSValue *cpool; - int cpool_count; - - /* Stack requirements */ - uint16_t local_count; /* total local slots (args + vars) */ - uint16_t stack_size; /* operand stack depth */ - - /* Flags */ - uint8_t has_debug : 1; - uint8_t read_only_bytecode : 1; - - /* Debug info (optional - only present if has_debug) */ - struct { - JSValue filename; - int source_len; - int pc2line_len; - uint8_t *pc2line_buf; - char *source; - } debug; -} JSCompiledUnit; - -/* ============================================================ - Context-Neutral Module Format (Phase 2+) - Struct definitions are in quickjs.h - ============================================================ */ - -typedef struct JSProperty { - JSValue value; -} JSProperty; - -#define JS_PROP_INITIAL_SIZE 2 -#define JS_PROP_INITIAL_HASH_SIZE 4 /* must be a power of two */ -#define JS_ARRAY_INITIAL_SIZE 2 - -/* Max capacity depends on platform - cap field size varies */ -#ifdef JS_PTR64 -#define JS_ARRAY_MAX_CAP ((word_t)((1ULL << 56) - 1)) -#else -#define JS_ARRAY_MAX_CAP ((word_t)((1UL << 24) - 1)) -#endif - -typedef enum OPCodeFormat { -#define FMT(f) OP_FMT_##f, -#define DEF(id, size, n_pop, n_push, f) -#include "quickjs-opcode.h" -#undef DEF -#undef FMT -} OPCodeFormat; - -enum OPCodeEnum { -#define FMT(f) -#define DEF(id, size, n_pop, n_push, f) OP_##id, -#define def(id, size, n_pop, n_push, f) -#include "quickjs-opcode.h" -#undef def -#undef DEF -#undef FMT - OP_COUNT, /* excluding temporary opcodes */ - /* temporary opcodes : overlap with the short opcodes */ - OP_TEMP_START = OP_nop + 1, - OP___dummy = OP_TEMP_START - 1, -#define FMT(f) -#define DEF(id, size, n_pop, n_push, f) -#define def(id, size, n_pop, n_push, f) OP_##id, -#include "quickjs-opcode.h" -#undef def -#undef DEF -#undef FMT - OP_TEMP_END, -}; - -static JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv); -static JSValue js_call_bound_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv); -static JSValue JS_CallInternal (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv, int flags); -static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, JSValue this_obj, int argc, JSValue *argv, JSValue env, JSValue outer_frame); -static JSValue mcode_exec(JSContext *ctx, JSMCode *code, JSValue this_obj, - int argc, JSValue *argv, JSValue outer_frame); -int JS_DeleteProperty (JSContext *ctx, JSValue obj, JSValue prop); -JSValue __attribute__ ((format (printf, 2, 3))) -JS_ThrowInternalError (JSContext *ctx, const char *fmt, ...); -static __maybe_unused void JS_DumpString (JSRuntime *rt, const JSText *text); -static __maybe_unused void JS_DumpObjectHeader (JSRuntime *rt); -static __maybe_unused void JS_DumpObject (JSRuntime *rt, JSRecord *rec); -static __maybe_unused void JS_DumpGCObject (JSRuntime *rt, objhdr_t *p); -static __maybe_unused void JS_DumpValue (JSContext *ctx, const char *str, JSValue val); -static void js_dump_value_write (void *opaque, const char *buf, size_t len); -static void js_regexp_finalizer (JSRuntime *rt, JSValue val); -static JSValue js_new_function (JSContext *ctx, JSFunctionKind kind); - -/* Forward declarations for intrinsics (now declared in quickjs.h) */ - -/* Forward declaration - helper to set cap in objhdr */ -static inline objhdr_t objhdr_set_cap56 (objhdr_t h, uint64_t cap); - -/* JS_VALUE_GET_STRING is an alias for getting JSText from a string value */ -/* Note: Uses chase() for GC safety - already defined at line 293 */ - -/* JS_ThrowMemoryError is an alias for JS_ThrowOutOfMemory */ -#define JS_ThrowMemoryError(ctx) JS_ThrowOutOfMemory(ctx) - -/* GC-SAFE: JS_SetPropertyInternal: same as JS_SetProperty but doesn't check stone. - Internal use only. May trigger GC if record needs to resize. */ -int JS_SetPropertyInternal (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val) { - if (!JS_IsRecord (this_obj)) { - return -1; - } - /* Use a local copy that rec_set_own can update if resize happens */ - JSValue obj = this_obj; - return rec_set_own (ctx, &obj, prop, val); -} - -static blob *js_get_blob (JSContext *ctx, JSValue val); -static JSValue js_new_string8_len (JSContext *ctx, const char *buf, int len); -static JSValue pretext_end (JSContext *ctx, JSText *s); -static JSValue js_compile_regexp (JSContext *ctx, JSValue pattern, JSValue flags); -static JSValue js_regexp_constructor_internal (JSContext *ctx, JSValue pattern, JSValue bc); -static int JS_NewClass1 (JSContext *ctx, JSClassID class_id, const JSClassDef *class_def, const char *name); - -static BOOL js_strict_eq (JSContext *ctx, JSValue op1, JSValue op2); -static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_push (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_pop (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_mach_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_stone (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_length (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_reverse (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_proto (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_splat (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_meme (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_fn_apply (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_call (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_modulo (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_neg (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_not (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_text_lower (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_text_upper (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_text_trim (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_text_codepoint (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_text_search (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_text_extract (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_character (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_number (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_number_abs (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_number_sign (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_number_floor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_number_ceiling (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_number_round (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_number_trunc (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_number_whole (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_number_fraction (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_number_min (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_number_max (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_number_remainder (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -static JSValue js_print (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); -cJSON *JS_GetStack(JSContext *ctx); -JSValue JS_ThrowOutOfMemory (JSContext *ctx); -static JSValue JS_EvalInternal (JSContext *ctx, JSValue this_obj, const char *input, size_t input_len, const char *filename, int flags, int scope_idx); - -static JSValue JS_ToNumber (JSContext *ctx, JSValue val); -static int JS_SetPropertyValue (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val); -static int JS_GetOwnPropertyInternal (JSContext *ctx, - JSValue *desc, - JSRecord *p, - JSValue prop); -/* JS_AddIntrinsicBasicObjects is declared in quickjs.h */ -static __exception int js_get_length32 (JSContext *ctx, uint32_t *pres, JSValue obj); -static __exception int js_get_length64 (JSContext *ctx, int64_t *pres, JSValue obj); -static void free_arg_list (JSContext *ctx, JSValue *tab, uint32_t len); -static JSValue *build_arg_list (JSContext *ctx, uint32_t *plen, JSValue *parray_arg); -static JSValue js_regexp_toString (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); - -static JSValue js_error_toString (JSContext *ctx, JSValue this_val, int argc, JSValue *argv); - -JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT; - -void *js_malloc_rt (size_t size) { - return malloc(size); -} - -void js_free_rt (void *ptr) { - free (ptr); -} - -void *js_realloc_rt (void *ptr, size_t size) { - return realloc (ptr, size); -} - -void *js_mallocz_rt (size_t size) { - void *ptr; - ptr = js_malloc_rt (size); - if (!ptr) return NULL; - return memset (ptr, 0, size); -} - -char *js_strdup_rt (const char *str) { - if (!str) return NULL; - size_t len = strlen(str) + 1; - char *dup = js_malloc_rt(len); - if (dup) memcpy(dup, str, len); - return dup; -} - -/* Throw out of memory in case of error */ -void *js_malloc (JSContext *ctx, size_t size) { - /* Align size to 8 bytes */ - size = (size + 7) & ~7; - -#ifdef FORCE_GC_AT_MALLOC - /* Force GC on every allocation for testing - but don't grow heap unless needed */ - int need_space = (uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end; - if (ctx_gc(ctx, need_space, size) < 0) { - JS_ThrowOutOfMemory(ctx); - return NULL; - } - if ((uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end) { - JS_ThrowOutOfMemory(ctx); - return NULL; - } -#else - /* Check if we have space in current block */ - if ((uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end) { - /* Trigger GC to reclaim memory */ - if (ctx_gc (ctx, 1, size) < 0) { - JS_ThrowOutOfMemory (ctx); - return NULL; - } - /* Re-check after GC */ - if ((uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end) { - JS_ThrowOutOfMemory (ctx); - return NULL; - } - } -#endif - - void *ptr = ctx->heap_free; - ctx->heap_free = (uint8_t *)ctx->heap_free + size; - return ptr; -} - -/* Throw out of memory in case of error */ -void *js_mallocz (JSContext *ctx, size_t size) { - void *ptr = js_malloc (ctx, size); - if (!ptr) return NULL; - return memset (ptr, 0, size); -} - -void js_free (JSContext *ctx, void *ptr) { - /* Bump allocator doesn't free individual allocations - GC handles it */ - (void)ctx; - (void)ptr; -} - -/* Parser memory functions - use system allocator to avoid GC issues */ -static void *pjs_malloc (size_t size) { - return malloc (size); -} - -static void *pjs_mallocz (size_t size) { - void *ptr = malloc (size); - if (ptr) memset (ptr, 0, size); - return ptr; -} - -static void *pjs_realloc (void *ptr, size_t size) { - return realloc (ptr, size); -} - -static void pjs_free (void *ptr) { - free (ptr); -} - -/* Parser-specific resize array using system allocator */ -static no_inline int pjs_realloc_array (void **parray, int elem_size, int *psize, int req_size) { - int new_size; - void *new_array; - size_t old_size; - new_size = max_int (req_size, *psize * 3 / 2); - old_size = (size_t)(*psize) * elem_size; - new_array = pjs_realloc (*parray, (size_t)new_size * elem_size); - if (!new_array) return -1; - /* Zero newly allocated memory */ - if (new_size > *psize) { - memset ((char *)new_array + old_size, 0, (size_t)(new_size - *psize) * elem_size); - } - *psize = new_size; - *parray = new_array; - return 0; -} - -static inline int pjs_resize_array (void **parray, int elem_size, int *psize, int req_size) { - if (unlikely (req_size > *psize)) - return pjs_realloc_array (parray, elem_size, psize, req_size); - else - return 0; -} - -/* Parser pretext - mutable string using system allocator (not JS heap). - This avoids GC issues during parsing since GC can move JS heap objects. */ -typedef struct PPretext { - uint32_t *data; - int len; - int cap; -} PPretext; - -/* Forward declarations for ppretext_end */ -static JSText *js_alloc_string (JSContext *ctx, int max_len); -static inline void string_put (JSText *p, int idx, uint32_t c); - -static PPretext *ppretext_init (int capacity) { - PPretext *p = pjs_malloc (sizeof (PPretext)); - if (!p) return NULL; - if (capacity <= 0) capacity = 32; - p->data = pjs_malloc (capacity * sizeof (uint32_t)); - if (!p->data) { pjs_free (p); return NULL; } - p->len = 0; - p->cap = capacity; - return p; -} - -static void ppretext_free (PPretext *p) { - if (p) { - pjs_free (p->data); - pjs_free (p); - } -} - -static no_inline PPretext *ppretext_realloc (PPretext *p, int new_cap) { - uint32_t *new_data = pjs_realloc (p->data, new_cap * sizeof (uint32_t)); - if (!new_data) return NULL; - p->data = new_data; - p->cap = new_cap; - return p; -} - -static PPretext *ppretext_putc (PPretext *p, uint32_t c) { - if (p->len >= p->cap) { - int new_cap = max_int (p->len + 1, p->cap * 3 / 2); - if (!ppretext_realloc (p, new_cap)) return NULL; - } - p->data[p->len++] = c; - return p; -} - -static JSValue ppretext_end (JSContext *ctx, PPretext *p) { - if (!p) return JS_EXCEPTION; - int len = p->len; - if (len == 0) { - ppretext_free (p); - return JS_KEY_empty; - } - - /* Allocate heap string (single allocation) */ - JSText *str = js_alloc_string (ctx, len); - if (!str) { - ppretext_free (p); - return JS_EXCEPTION; - } - for (int i = 0; i < len; i++) { - string_put (str, i, p->data[i]); - } - str->hdr = objhdr_set_cap56 (str->hdr, len); - str->hdr = objhdr_set_s (str->hdr, true); - - ppretext_free (p); - return JS_MKPTR (str); -} - -static no_inline int js_realloc_array (JSContext *ctx, void **parray, int elem_size, int *psize, int req_size) { - int new_size; - void *new_array; - void *old_array = *parray; - int old_size = *psize; - - /* XXX: potential arithmetic overflow */ - new_size = max_int (req_size, old_size * 3 / 2); - - /* Protect source object with a GC ref before allocating (GC may move it) */ - JSGCRef src_ref; - JS_PushGCRef (ctx, &src_ref); - if (old_array) { - src_ref.val = JS_MKPTR (old_array); - } - - new_array = js_malloc (ctx, new_size * elem_size); - if (!new_array) { - JS_PopGCRef (ctx, &src_ref); - return -1; - } - - /* Get possibly-moved source pointer after GC */ - if (old_array) { - old_array = (void *)chase (src_ref.val); - memcpy (new_array, old_array, old_size * elem_size); - } - JS_PopGCRef (ctx, &src_ref); - - *psize = new_size; - *parray = new_array; - return 0; -} - -/* resize the array and update its size if req_size > *psize */ -static inline int js_resize_array (JSContext *ctx, void **parray, int elem_size, int *psize, int req_size) { - if (unlikely (req_size > *psize)) - return js_realloc_array (ctx, parray, elem_size, psize, req_size); - else - return 0; -} - -static inline void js_dbuf_init (JSContext *ctx, DynBuf *s) { - dbuf_init2 (s, ctx->rt, NULL); -} - -static inline int is_digit (int c) { return c >= '0' && c <= '9'; } - -static inline int string_get (const JSText *p, int idx) { - int word_idx = idx >> 1; - int shift = (1 - (idx & 1)) * 32; - return (uint32_t)((p->packed[word_idx] >> shift) & 0xFFFFFFFF); -} - -static inline void string_put (JSText *p, int idx, uint32_t c) { - int word_idx = idx >> 1; - int shift = (1 - (idx & 1)) * 32; - uint64_t mask = 0xFFFFFFFFULL << shift; - p->packed[word_idx] = (p->packed[word_idx] & ~mask) | ((uint64_t)c << shift); -} - -/* Get character from any string value (immediate ASCII or JSText) */ -static inline uint32_t js_string_value_get (JSValue v, int idx) { - if (MIST_IsImmediateASCII (v)) { - return MIST_GetImmediateASCIIChar (v, idx); - } else { - JSText *s = JS_VALUE_GET_TEXT (v); - return string_get (s, idx); - } -} - -/* Get length from any string value */ -static inline int js_string_value_len (JSValue v) { - if (MIST_IsImmediateASCII (v)) { - return MIST_GetImmediateASCIILen (v); - } else { - return JSText_len (JS_VALUE_GET_TEXT (v)); - } -} - -/* Append a JSValue string to a PPretext (parser pretext) */ -static PPretext *ppretext_append_jsvalue (PPretext *p, JSValue str) { - int len = js_string_value_len (str); - for (int i = 0; i < len; i++) { - uint32_t c = js_string_value_get (str, i); - p = ppretext_putc (p, c); - if (!p) return NULL; - } - return p; -} - -/* Append an integer to a PPretext */ -static PPretext *ppretext_append_int (PPretext *p, int n) { - char buf[16]; - int len = snprintf (buf, sizeof (buf), "%d", n); - for (int i = 0; i < len; i++) { - p = ppretext_putc (p, buf[i]); - if (!p) return NULL; - } - return p; -} - -/* Convert a JSValue string to a property key. - For immediates, returns the value as-is (can be used directly as keys). - For heap strings, returns interned version. */ -static JSValue js_key_from_string (JSContext *ctx, JSValue val) { - if (MIST_IsImmediateASCII (val)) { - return val; /* Immediates can be used directly as keys */ - } - if (JS_IsText (val)) { - JSText *p = JS_VALUE_GET_TEXT (val); - int64_t len = JSText_len (p); /* Use JSText_len which checks header for stoned text */ - /* Extract UTF-32 characters and intern */ - uint32_t *utf32_buf = alloca (len * sizeof (uint32_t)); - for (int64_t i = 0; i < len; i++) { - utf32_buf[i] = string_get (p, i); - } - return intern_text_to_value (ctx, utf32_buf, len); - } - return JS_NULL; -} - -static JSClass const js_std_class_def[] = { - { "error", NULL, NULL }, /* JS_CLASS_ERROR */ - { "regexp", js_regexp_finalizer, NULL }, /* JS_CLASS_REGEXP */ - { "blob", NULL, NULL }, /* JS_CLASS_BLOB - registered separately */ -}; - -static int init_class_range (JSContext *ctx, JSClass const *tab, int start, int count) { - JSClassDef cm_s, *cm = &cm_s; - int i, class_id; - - for (i = 0; i < count; i++) { - class_id = i + start; - memset (cm, 0, sizeof (*cm)); - cm->finalizer = tab[i].finalizer; - cm->gc_mark = tab[i].gc_mark; - if (JS_NewClass1 (ctx, class_id, cm, tab[i].class_name) < 0) return -1; - } - return 0; -} - -#if !defined(CONFIG_STACK_CHECK) -/* no stack limitation */ -static inline uintptr_t js_get_stack_pointer (void) { return 0; } - -static inline BOOL js_check_stack_overflow (JSContext *ctx, - size_t alloca_size) { - return FALSE; -} -#else -/* Note: OS and CPU dependent */ -static inline uintptr_t js_get_stack_pointer (void) { - return (uintptr_t)__builtin_frame_address (0); -} - -static inline BOOL js_check_stack_overflow (JSContext *ctx, - size_t alloca_size) { - uintptr_t sp; - sp = js_get_stack_pointer () - alloca_size; - return unlikely (sp < (uintptr_t)ctx->stack_limit); -} -#endif - -/* Destroy buddy allocator and free pool */ -static void buddy_destroy (BuddyAllocator *b) { - if (!b->initialized) return; - - free (b->base); - b->base = NULL; - b->initialized = 0; - for (int i = 0; i < BUDDY_LEVELS; i++) { - b->free_lists[i] = NULL; - } -} - -/* ============================================================ - Heap block allocation wrappers - In POISON_HEAP mode, use malloc so poisoned memory stays poisoned. - Otherwise use buddy allocator for efficiency. - ============================================================ */ - -static void *heap_block_alloc(JSRuntime *rt, size_t size) { -#ifdef POISON_HEAP - (void)rt; - return malloc(size); -#else - return buddy_alloc(&rt->buddy, size); -#endif -} - -static void heap_block_free(JSRuntime *rt, void *ptr, size_t size) { -#ifdef POISON_HEAP - (void)rt; - (void)size; - /* Don't free - leave it poisoned to catch stale accesses */ - gc_poison_region(ptr, size); -#else - buddy_free(&rt->buddy, ptr, size); -#endif -} - -/* ============================================================ - Bump Allocator and Cheney GC - ============================================================ */ - -/* Forward declarations for GC helpers */ -static int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size); -static JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *from_end, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end); -static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8_t *from_end, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end); -static size_t gc_object_size (void *ptr); - -/* Alignment for GC object sizes - must match max alignment requirement */ -#define GC_ALIGN 8 -static inline size_t gc_align_up (size_t n) { return (n + GC_ALIGN - 1) & ~(GC_ALIGN - 1); } - -/* Get size of a heap object based on its type */ -static size_t gc_object_size (void *ptr) { - objhdr_t hdr = *(objhdr_t *)ptr; - uint8_t type = objhdr_type (hdr); - uint64_t cap = objhdr_cap56 (hdr); - - switch (type) { - case OBJ_ARRAY: { - /* JSArray + inline values array. Cap is element capacity. */ - size_t values_size = sizeof (JSValue) * cap; - return gc_align_up (sizeof (JSArray) + values_size); - } - case OBJ_TEXT: { - /* JSText: header + pad + hdr + length + packed chars */ - size_t word_count = (cap + 1) / 2; - return gc_align_up (sizeof (JSText) + word_count * sizeof (uint64_t)); - } - case OBJ_RECORD: { - /* JSRecord + inline tab. Cap is mask, so tab size is mask+1 entries. */ - size_t tab_size = sizeof (JSRecordEntry) * (cap + 1); - return gc_align_up (sizeof (JSRecord) + tab_size); - } - case OBJ_FUNCTION: - return gc_align_up (sizeof (JSFunction)); - case OBJ_FRAME: { - /* JSFrame + slots array. cap56 stores slot count */ - uint64_t slot_count = cap; - return gc_align_up (sizeof (JSFrame) + slot_count * sizeof (JSValue)); - } - default: - /* Unknown type - fatal error, heap is corrupt or scan desync'd */ - fflush(stdout); - fprintf (stderr, "gc_object_size: unknown type %d at %p, hdr=0x%llx cap=%llu\n", - type, ptr, (unsigned long long)hdr, (unsigned long long)cap); - /* Dump surrounding memory for debugging */ - uint64_t *words = (uint64_t *)ptr; - fprintf (stderr, " words: [0]=0x%llx [1]=0x%llx [2]=0x%llx [3]=0x%llx\n", - (unsigned long long)words[0], (unsigned long long)words[1], - (unsigned long long)words[2], (unsigned long long)words[3]); - fflush(stderr); - abort (); - } -} -static inline int ptr_in_range (void *p, uint8_t *b, uint8_t *e) { - uint8_t *q = (uint8_t *)p; - return q >= b && q < e; -} - -static JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *from_end, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) { - if (!JS_IsPtr (v)) return v; - - for (;;) { - void *ptr = JS_VALUE_GET_PTR (v); - if (is_stone_ptr (ctx, ptr)) return v; - - if (!ptr_in_range (ptr, from_base, from_end)) return v; - - objhdr_t *hdr_ptr = (objhdr_t *)ptr; - objhdr_t hdr = *hdr_ptr; - uint8_t type = objhdr_type (hdr); - - if (type == OBJ_FORWARD) { - void *t = objhdr_fwd_ptr (hdr); - - /* If it already points into to-space, it's a GC forward. */ - if (ptr_in_range (t, to_base, *to_free)) return JS_MKPTR (t); - - /* Otherwise it's a growth-forward (still in from-space). Chase. */ - v = JS_MKPTR (t); - continue; - } - - if (type != OBJ_ARRAY && type != OBJ_TEXT && type != OBJ_RECORD && type != OBJ_FUNCTION && type != OBJ_BLOB && type != OBJ_CODE && type != OBJ_FRAME) { - fprintf (stderr, "gc_copy_value: invalid object type %d at %p (hdr=0x%llx)\n", type, ptr, (unsigned long long)hdr); - fprintf (stderr, " This may be an interior pointer or corrupt root\n"); - fflush (stderr); - abort (); - } - - size_t size = gc_object_size (hdr_ptr); - if (*to_free + size > to_end) { - fprintf (stderr, "gc_copy_value: out of to-space, need %zu bytes\n", size); - abort (); - } - - void *new_ptr = *to_free; - memcpy (new_ptr, hdr_ptr, size); - *to_free += size; - - *hdr_ptr = objhdr_make_fwd (new_ptr); - - return JS_MKPTR (new_ptr); - } -} - -/* Scan a copied object and update its internal references */ -static void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8_t *from_end, - uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) { - objhdr_t hdr = *(objhdr_t *)ptr; - uint8_t type = objhdr_type (hdr); - - switch (type) { - case OBJ_ARRAY: { - JSArray *arr = (JSArray *)ptr; - for (uint32_t i = 0; i < arr->len; i++) { - arr->values[i] = gc_copy_value (ctx, arr->values[i], from_base, from_end, to_base, to_free, to_end); - } - break; - } - case OBJ_RECORD: { - JSRecord *rec = (JSRecord *)ptr; - uint32_t mask = (uint32_t)objhdr_cap56 (rec->mist_hdr); -#ifdef DUMP_GC_DETAIL - printf(" record: slots=%u used=%u proto=%p\n", mask + 1, (uint32_t)rec->len, (void*)rec->proto); - fflush(stdout); -#endif - /* Copy prototype */ - if (rec->proto) { - JSValue proto_val = JS_MKPTR (rec->proto); - proto_val = gc_copy_value (ctx, proto_val, from_base, from_end, to_base, to_free, to_end); - rec->proto = (JSRecord *)JS_VALUE_GET_PTR (proto_val); - } - /* Copy table entries */ - for (uint32_t i = 0; i <= mask; i++) { - JSValue k = rec->slots[i].key; - if (!rec_key_is_empty (k) && !rec_key_is_tomb (k)) { - rec->slots[i].key = gc_copy_value (ctx, k, from_base, from_end, to_base, to_free, to_end); - rec->slots[i].val = gc_copy_value (ctx, rec->slots[i].val, from_base, from_end, to_base, to_free, to_end); - } - } - break; - } - case OBJ_FUNCTION: { - JSFunction *fn = (JSFunction *)ptr; - /* Scan the function name */ - fn->name = gc_copy_value (ctx, fn->name, from_base, from_end, to_base, to_free, to_end); - /* Scan bytecode's cpool - it contains JSValues that may reference GC objects */ - if (fn->kind == JS_FUNC_KIND_BYTECODE && fn->u.func.function_bytecode) { - JSFunctionBytecode *b = fn->u.func.function_bytecode; - /* Scan cpool entries */ - for (int i = 0; i < b->cpool_count; i++) { - b->cpool[i] = gc_copy_value (ctx, b->cpool[i], from_base, from_end, to_base, to_free, to_end); - } - /* Scan func_name and filename */ - b->func_name = gc_copy_value (ctx, b->func_name, from_base, from_end, to_base, to_free, to_end); - if (b->has_debug) { - b->debug.filename = gc_copy_value (ctx, b->debug.filename, from_base, from_end, to_base, to_free, to_end); - } - /* Scan outer_frame (for closures) - it's already a JSValue */ - fn->u.func.outer_frame = gc_copy_value (ctx, fn->u.func.outer_frame, from_base, from_end, to_base, to_free, to_end); - /* Scan env_record (stone record / module environment) */ - fn->u.func.env_record = gc_copy_value (ctx, fn->u.func.env_record, from_base, from_end, to_base, to_free, to_end); - } else if (fn->kind == JS_FUNC_KIND_REGISTER && fn->u.reg.code) { - /* Register VM function - scan cpool (off-heap but contains JSValues) */ - JSCodeRegister *code = fn->u.reg.code; - for (uint32_t i = 0; i < code->cpool_count; i++) { - code->cpool[i] = gc_copy_value (ctx, code->cpool[i], from_base, from_end, to_base, to_free, to_end); - } - /* Scan function name */ - code->name = gc_copy_value (ctx, code->name, from_base, from_end, to_base, to_free, to_end); - /* Scan outer_frame and env_record */ - fn->u.reg.outer_frame = gc_copy_value (ctx, fn->u.reg.outer_frame, from_base, from_end, to_base, to_free, to_end); - fn->u.reg.env_record = gc_copy_value (ctx, fn->u.reg.env_record, from_base, from_end, to_base, to_free, to_end); - /* Recursively scan nested function cpools */ - for (uint32_t i = 0; i < code->func_count; i++) { - if (code->functions[i]) { - JSCodeRegister *nested = code->functions[i]; - for (uint32_t j = 0; j < nested->cpool_count; j++) { - nested->cpool[j] = gc_copy_value (ctx, nested->cpool[j], from_base, from_end, to_base, to_free, to_end); - } - nested->name = gc_copy_value (ctx, nested->name, from_base, from_end, to_base, to_free, to_end); - } - } - } else if (fn->kind == JS_FUNC_KIND_MCODE) { - /* MCODE function - scan outer_frame and env_record */ - fn->u.mcode.outer_frame = gc_copy_value (ctx, fn->u.mcode.outer_frame, from_base, from_end, to_base, to_free, to_end); - fn->u.mcode.env_record = gc_copy_value (ctx, fn->u.mcode.env_record, from_base, from_end, to_base, to_free, to_end); - } - break; - } - case OBJ_TEXT: - case OBJ_BLOB: - /* No internal references to scan */ - break; - case OBJ_CODE: { - /* JSFunctionBytecode - scan func_name and filename */ - JSFunctionBytecode *bc = (JSFunctionBytecode *)ptr; - bc->func_name = gc_copy_value (ctx, bc->func_name, from_base, from_end, to_base, to_free, to_end); - if (bc->has_debug) { - bc->debug.filename = gc_copy_value (ctx, bc->debug.filename, from_base, from_end, to_base, to_free, to_end); - } - /* Note: cpool, vardefs, closure_var, byte_code_buf are allocated via js_malloc */ - break; - } - case OBJ_FRAME: { - /* JSFrame - scan function, caller, and slots */ - JSFrame *frame = (JSFrame *)ptr; - /* function and caller are now JSValues - copy them directly */ - frame->function = gc_copy_value (ctx, frame->function, from_base, from_end, to_base, to_free, to_end); - frame->caller = gc_copy_value (ctx, frame->caller, from_base, from_end, to_base, to_free, to_end); - /* Scan all slots */ - uint64_t slot_count = objhdr_cap56 (frame->header); - for (uint64_t i = 0; i < slot_count; i++) { - frame->slots[i] = gc_copy_value (ctx, frame->slots[i], from_base, from_end, to_base, to_free, to_end); - } - break; - } - default: - /* Unknown type during scan - fatal error */ - fprintf (stderr, "gc_scan_object: unknown object type %d at %p\n", type, ptr); - abort (); - } -} - -/* Forward declaration - defined after JSFunctionDef */ -static void gc_scan_parser_cpool (JSContext *ctx, uint8_t *from_base, uint8_t *from_end, - uint8_t *to_base, uint8_t **to_free, uint8_t *to_end); - -/* Scan OBJ_CODE cpool for bytecode objects outside GC heap */ -static void gc_scan_bytecode_cpool (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *from_end, - uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) { - if (!JS_IsPtr (v)) return; - void *ptr = JS_VALUE_GET_PTR (v); - if (ptr_in_range (ptr, from_base, from_end)) return; /* On GC heap, handled normally */ - if (is_stone_ptr (ctx, ptr)) return; /* Stone memory */ - - /* Check if this is an OBJ_CODE outside GC heap */ - objhdr_t hdr = *(objhdr_t *)ptr; - if (objhdr_type (hdr) == OBJ_CODE) { - JSFunctionBytecode *bc = (JSFunctionBytecode *)ptr; - /* Scan cpool entries */ - for (int i = 0; i < bc->cpool_count; i++) { - bc->cpool[i] = gc_copy_value (ctx, bc->cpool[i], from_base, from_end, to_base, to_free, to_end); - } - /* Scan func_name and filename */ - bc->func_name = gc_copy_value (ctx, bc->func_name, from_base, from_end, to_base, to_free, to_end); - if (bc->has_debug) { - bc->debug.filename = gc_copy_value (ctx, bc->debug.filename, from_base, from_end, to_base, to_free, to_end); - } - } -} - -/* Cheney copying GC - collect garbage and compact live objects - allow_grow: if true, grow heap when recovery is poor - alloc_size: the allocation that triggered GC — used to size the new block */ -static int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) { - JSRuntime *rt = ctx->rt; - size_t old_used = ctx->heap_free - ctx->heap_base; - size_t old_heap_size = ctx->current_block_size; - - /* Save OLD heap bounds before allocating new block - Use heap_free (not heap_end) as from_end - only the portion up to heap_free - contains valid objects. Beyond heap_free is uninitialized garbage. */ - uint8_t *from_base = ctx->heap_base; - uint8_t *from_end = ctx->heap_free; - -#ifdef DUMP_GC_DETAIL - printf("ctx_gc: from_base=%p from_end=%p size=%zu\n", (void*)from_base, (void*)from_end, old_heap_size); -#endif - - /* Request new block from runtime. - When allow_grow is set and the pending allocation won't fit in the - current next_block_size, jump straight to a block that can hold - live_data + alloc_size instead of doubling one step at a time. */ - size_t new_size = ctx->next_block_size; - if (allow_grow) { - size_t live_est = (size_t)(from_end - from_base); /* upper bound on live data */ - size_t need = live_est + alloc_size; - while (new_size < need && new_size < (1ULL << BUDDY_MAX_ORDER)) - new_size *= 2; - } - uint8_t *new_block = heap_block_alloc (rt, new_size); - if (!new_block) { - /* Try with same size */ - new_size = ctx->current_block_size; - new_block = heap_block_alloc (rt, new_size); - if (!new_block) return -1; - } - - uint8_t *to_base = new_block; - uint8_t *to_free = new_block; - uint8_t *to_end = new_block + new_size; - - /* Copy roots: global object, class prototypes, exception, etc. */ -#ifdef DUMP_GC_DETAIL - printf(" roots: global_obj = 0x%llx\n", (unsigned long long)ctx->global_obj); fflush(stdout); - if (JS_IsPtr(ctx->global_obj)) { - void *gptr = JS_VALUE_GET_PTR(ctx->global_obj); - printf(" ptr=%p in_from=%d is_stone=%d\n", gptr, - ((uint8_t*)gptr >= from_base && (uint8_t*)gptr < from_end), - is_stone_ptr(ctx, gptr)); - fflush(stdout); - } -#endif - ctx->global_obj = gc_copy_value (ctx, ctx->global_obj, from_base, from_end, to_base, &to_free, to_end); -#ifdef DUMP_GC_DETAIL - printf(" after copy: global_obj = 0x%llx\n", (unsigned long long)ctx->global_obj); fflush(stdout); -#endif -#ifdef DUMP_GC_DETAIL - printf(" roots: regexp_ctor\n"); fflush(stdout); -#endif - ctx->regexp_ctor = gc_copy_value (ctx, ctx->regexp_ctor, from_base, from_end, to_base, &to_free, to_end); -#ifdef DUMP_GC_DETAIL - printf(" roots: throw_type_error\n"); fflush(stdout); -#endif - ctx->throw_type_error = gc_copy_value (ctx, ctx->throw_type_error, from_base, from_end, to_base, &to_free, to_end); - - /* Copy current exception if pending */ -#ifdef DUMP_GC_DETAIL - printf(" roots: current_exception\n"); fflush(stdout); -#endif - ctx->current_exception = gc_copy_value (ctx, ctx->current_exception, from_base, from_end, to_base, &to_free, to_end); - -#ifdef DUMP_GC_DETAIL - printf(" roots: native_error_proto\n"); fflush(stdout); -#endif - for (int i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { - ctx->native_error_proto[i] = gc_copy_value (ctx, ctx->native_error_proto[i], from_base, from_end, to_base, &to_free, to_end); - } - - /* Copy class prototypes */ -#ifdef DUMP_GC_DETAIL - printf(" roots: class_proto (count=%d)\n", ctx->class_count); fflush(stdout); -#endif - for (int i = 0; i < ctx->class_count; i++) { - ctx->class_proto[i] = gc_copy_value (ctx, ctx->class_proto[i], from_base, from_end, to_base, &to_free, to_end); - } - - /* Copy value stack */ -#ifdef DUMP_GC_DETAIL - printf(" roots: value_stack (top=%d)\n", ctx->value_stack_top); fflush(stdout); -#endif - for (int i = 0; i < ctx->value_stack_top; i++) { - ctx->value_stack[i] = gc_copy_value (ctx, ctx->value_stack[i], from_base, from_end, to_base, &to_free, to_end); - } - - /* Copy frame stack references */ -#ifdef DUMP_GC_DETAIL - printf(" roots: frame_stack (top=%d)\n", ctx->frame_stack_top); fflush(stdout); -#endif - for (int i = 0; i <= ctx->frame_stack_top; i++) { - struct VMFrame *frame = &ctx->frame_stack[i]; - frame->cur_func = gc_copy_value (ctx, frame->cur_func, from_base, from_end, to_base, &to_free, to_end); - frame->this_obj = gc_copy_value (ctx, frame->this_obj, from_base, from_end, to_base, &to_free, to_end); - } - - /* Copy register VM current frame */ -#ifdef DUMP_GC_DETAIL - printf(" roots: reg_current_frame\n"); fflush(stdout); -#endif - ctx->reg_current_frame = gc_copy_value (ctx, ctx->reg_current_frame, from_base, from_end, to_base, &to_free, to_end); - - /* Copy JSStackFrame chain (C stack frames) */ -#ifdef DUMP_GC_DETAIL - printf(" roots: current_stack_frame chain\n"); fflush(stdout); -#endif - for (JSStackFrame *sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) { - sf->cur_func = gc_copy_value (ctx, sf->cur_func, from_base, from_end, to_base, &to_free, to_end); - /* Also scan bytecode cpool if it's a bytecode object outside GC heap */ - gc_scan_bytecode_cpool (ctx, sf->cur_func, from_base, from_end, to_base, &to_free, to_end); - /* Scan arg_buf and var_buf contents - they're on C stack but hold JSValues */ - if (JS_IsFunction(sf->cur_func)) { - JSFunction *fn = JS_VALUE_GET_FUNCTION(sf->cur_func); - if (fn->kind == JS_FUNC_KIND_BYTECODE && fn->u.func.function_bytecode) { - JSFunctionBytecode *b = fn->u.func.function_bytecode; - /* Scan arg_buf */ - if (sf->arg_buf) { - for (int i = 0; i < sf->arg_count; i++) { - sf->arg_buf[i] = gc_copy_value(ctx, sf->arg_buf[i], from_base, from_end, to_base, &to_free, to_end); - } - } - /* Scan var_buf */ - if (sf->var_buf) { - for (int i = 0; i < b->var_count; i++) { - sf->var_buf[i] = gc_copy_value(ctx, sf->var_buf[i], from_base, from_end, to_base, &to_free, to_end); - } - } - /* Scan js_frame if present - it's on regular heap but contains JSValues pointing to GC heap */ - sf->js_frame = gc_copy_value(ctx, sf->js_frame, from_base, from_end, to_base, &to_free, to_end); - if (!JS_IsNull(sf->js_frame)) { - JSFrame *jf = JS_VALUE_GET_FRAME(sf->js_frame); - jf->function = gc_copy_value(ctx, jf->function, from_base, from_end, to_base, &to_free, to_end); - jf->caller = gc_copy_value(ctx, jf->caller, from_base, from_end, to_base, &to_free, to_end); - /* Note: jf->slots are same as arg_buf/var_buf, already scanned above */ - } - /* Scan operand stack */ - if (sf->stack_buf && sf->p_sp) { - JSValue *sp = *sf->p_sp; - for (JSValue *p = sf->stack_buf; p < sp; p++) { - *p = gc_copy_value(ctx, *p, from_base, from_end, to_base, &to_free, to_end); - } - } - } - } - } - - /* Copy JS_PUSH_VALUE/JS_POP_VALUE roots */ -#ifdef DUMP_GC_DETAIL - printf(" roots: top_gc_ref\n"); fflush(stdout); -#endif - for (JSGCRef *ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) { - gc_scan_bytecode_cpool (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); - ref->val = gc_copy_value (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); - } - - /* Copy JS_AddGCRef/JS_DeleteGCRef roots */ -#ifdef DUMP_GC_DETAIL - printf(" roots: last_gc_ref\n"); fflush(stdout); -#endif - for (JSGCRef *ref = ctx->last_gc_ref; ref != NULL; ref = ref->prev) { - gc_scan_bytecode_cpool (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); - ref->val = gc_copy_value (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); - } - - /* Scan parser's cpool (if parsing is in progress) */ - if (ctx->current_parse_fd) { - gc_scan_parser_cpool (ctx, from_base, from_end, to_base, &to_free, to_end); - } - - /* Cheney scan: scan copied objects to find more references */ - uint8_t *scan = to_base; -#ifdef DUMP_GC_DETAIL - printf(" scan: to_base=%p to_free=%p to_end=%p\n", (void*)to_base, (void*)to_free, (void*)to_end); - fflush(stdout); -#endif - while (scan < to_free) { -#ifdef DUMP_GC_DETAIL - objhdr_t scan_hdr = *(objhdr_t *)scan; - printf(" scan %p: type=%d hdr=0x%llx", (void*)scan, objhdr_type(scan_hdr), (unsigned long long)scan_hdr); - fflush(stdout); -#endif - size_t obj_size = gc_object_size (scan); -#ifdef DUMP_GC_DETAIL - printf(" size=%zu\n", obj_size); - fflush(stdout); -#endif - gc_scan_object (ctx, scan, from_base, from_end, to_base, &to_free, to_end); - scan += obj_size; - } - - /* Return old block (in poison mode, just poison it and leak) */ - heap_block_free (rt, from_base, old_heap_size); - - /* Update context with new block */ - size_t new_used = to_free - to_base; - size_t recovered = old_used > new_used ? old_used - new_used : 0; - - ctx->heap_base = to_base; - ctx->heap_free = to_free; - ctx->heap_end = to_end; - ctx->current_block_size = new_size; - -#ifdef DUMP_GC_DETAIL - /* Verify global_obj is in valid heap range after GC */ - if (JS_IsPtr(ctx->global_obj)) { - void *gptr = JS_VALUE_GET_PTR(ctx->global_obj); - if ((uint8_t*)gptr < to_base || (uint8_t*)gptr >= to_free) { - printf(" WARNING: global_obj=%p outside [%p, %p) after GC!\n", - gptr, (void*)to_base, (void*)to_free); - fflush(stdout); - } - } -#endif - - /* If <20% recovered, double next block size for future allocations - But only if allow_grow is set (i.e., GC was triggered due to low space) */ -#ifdef DUMP_GC - int will_grow = 0; -#endif - if (allow_grow && old_used > 0 && recovered < old_used / 5) { - size_t doubled = new_size * 2; - if (doubled <= (1ULL << BUDDY_MAX_ORDER)) { - ctx->next_block_size = doubled; -#ifdef DUMP_GC - will_grow = 1; -#endif - } - } - -#ifdef DUMP_GC - printf ("\nGC: %zu -> %zu bytes, recovered %zu (%.1f%%)%s\n", - old_heap_size, - new_size, - recovered, - old_used > 0 ? (recovered * 100.0 / old_used) : 0.0, - will_grow ? ", heap will grow" : ""); -#endif - - return 0; -} - -JSRuntime *JS_NewRuntime (void) { - JSRuntime *rt; - - rt = malloc (sizeof (JSRuntime)); - if (!rt) return NULL; - memset (rt, 0, sizeof (*rt)); - - return rt; -} - -void *JS_GetRuntimeOpaque (JSRuntime *rt) { return rt->user_opaque; } - -void JS_SetRuntimeOpaque (JSRuntime *rt, void *opaque) { - rt->user_opaque = opaque; -} - -void JS_SetMemoryLimit (JSRuntime *rt, size_t limit) { - rt->malloc_limit = limit; -} - -/* Helpers to call system memory functions (for memory allocated by external libs) */ -static void sys_free (void *ptr) { free (ptr); } -static void *sys_malloc (size_t size) { return malloc (size); } -static void *sys_realloc (void *ptr, size_t size) { return realloc (ptr, size); } - -#define malloc(s) malloc_is_forbidden (s) -#define free(p) free_is_forbidden (p) -#define realloc(p, s) realloc_is_forbidden (p, s) - -void JS_SetInterruptHandler (JSContext *ctx, JSInterruptHandler *cb, void *opaque) { - ctx->interrupt_handler = cb; - ctx->interrupt_opaque = opaque; -} - -void JS_SetStripInfo (JSRuntime *rt, int flags) { rt->strip_flags = flags; } -int JS_GetStripInfo (JSRuntime *rt) { return rt->strip_flags; } - -/* Allocate a string using bump allocation from context heap. - Note: the string contents are uninitialized */ -static JSText *js_alloc_string (JSContext *ctx, int max_len) { - JSText *str; - /* Allocate packed UTF-32: 2 chars per 64-bit word. */ - size_t data_words = (max_len + 1) / 2; - size_t size = sizeof (JSText) + data_words * sizeof (uint64_t); - - str = js_malloc (ctx, size); - if (unlikely (!str)) { - JS_ThrowOutOfMemory (ctx); - return NULL; - } - /* Initialize objhdr_t with OBJ_TEXT type and capacity in cap56 */ - str->hdr = objhdr_make (max_len, OBJ_TEXT, false, false, false, false); - str->length = 0; /* length starts at 0, capacity is in hdr */ - - return str; -} - -void JS_SetRuntimeInfo (JSRuntime *rt, const char *s) { - if (rt) rt->rt_info = s; -} - -void JS_FreeRuntime (JSRuntime *rt) { - /* Destroy buddy allocator */ - buddy_destroy (&rt->buddy); - sys_free (rt); -} - -/* Forward declarations for intrinsics */ -static void JS_AddIntrinsicBasicObjects (JSContext *ctx); -static void JS_AddIntrinsicBaseObjects (JSContext *ctx); -static void JS_AddIntrinsicRegExp (JSContext *ctx); - -JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) { - JSContext *ctx; - - /* Round up to buddy allocator minimum */ - size_t min_size = 1ULL << BUDDY_MIN_ORDER; - if (heap_size < min_size) heap_size = min_size; - - /* Round up to power of 2 for buddy allocator */ - size_t actual = min_size; - while (actual < heap_size && actual < (1ULL << BUDDY_MAX_ORDER)) { - actual <<= 1; - } - heap_size = actual; - - ctx = js_mallocz_rt (sizeof (JSContext)); - - if (!ctx) return NULL; - ctx->trace_hook = NULL; - ctx->rt = rt; - - /* Bootstrap class_array and class_proto together via JS_NewClass1 */ - if (init_class_range (ctx, js_std_class_def, JS_CLASS_OBJECT, countof (js_std_class_def)) < 0) { - js_free_rt (ctx->class_array); - js_free_rt (ctx->class_proto); - js_free_rt (ctx); - return NULL; - } - - ctx->regexp_ctor = JS_NULL; - - /* Initialize VM stacks for trampoline */ - ctx->frame_stack_capacity = 512; - ctx->frame_stack - = js_malloc_rt (sizeof (struct VMFrame) * ctx->frame_stack_capacity); - if (!ctx->frame_stack) { - js_free_rt (ctx->class_array); - js_free_rt (ctx->class_proto); - js_free_rt (ctx); - return NULL; - } - ctx->frame_stack_top = -1; - - ctx->value_stack_capacity = 65536; /* 64K JSValue slots */ - ctx->value_stack - = js_malloc_rt (sizeof (JSValue) * ctx->value_stack_capacity); - if (!ctx->value_stack) { - js_free_rt (ctx->frame_stack); - js_free_rt (ctx->class_array); - js_free_rt (ctx->class_proto); - js_free_rt (ctx); - return NULL; - } - ctx->value_stack_top = 0; - - /* Initialize register VM frame root */ - ctx->reg_current_frame = JS_NULL; - - /* Initialize per-context execution state (moved from JSRuntime) */ - ctx->current_exception = JS_UNINITIALIZED; - ctx->current_stack_frame = NULL; - ctx->stack_size = JS_DEFAULT_STACK_SIZE; - JS_UpdateStackTop (ctx); - - /* Initialize stone text intern table */ - ctx->st_pages = NULL; - ctx->st_text_array = NULL; - ctx->st_text_hash = NULL; - ctx->st_text_count = 0; - ctx->st_text_size = 0; - ctx->st_text_resize = 0; - if (st_text_resize (ctx) < 0) { - js_free_rt (ctx->value_stack); - js_free_rt (ctx->frame_stack); - js_free_rt (ctx->class_array); - js_free_rt (ctx->class_proto); - js_free_rt (ctx); - return NULL; - } - - /* Allocate initial heap block for bump allocation */ - ctx->current_block_size = heap_size; - ctx->next_block_size = ctx->current_block_size; - ctx->heap_base = heap_block_alloc (rt, ctx->current_block_size); - if (!ctx->heap_base) { - js_free_rt (ctx->st_text_hash); - js_free_rt (ctx->st_text_array); - js_free_rt (ctx->value_stack); - js_free_rt (ctx->frame_stack); - js_free_rt (ctx->class_array); - js_free_rt (ctx->class_proto); - js_free_rt (ctx); - return NULL; - } - ctx->heap_free = ctx->heap_base; - ctx->heap_end = ctx->heap_base + ctx->current_block_size; - -#ifdef DUMP_GC_DETAIL - printf("Context init: heap_base=%p heap_end=%p size=%zu\n", (void*)ctx->heap_base, (void*)ctx->heap_end, ctx->current_block_size); -#endif - -#ifdef DUMP_GC_DETAIL - printf("After BasicObjects: heap_base=%p heap_end=%p heap_free=%p\n", (void*)ctx->heap_base, (void*)ctx->heap_end, (void*)ctx->heap_free); -#endif - - return ctx; -} - -JSContext *JS_NewContextRaw (JSRuntime *rt) { - return JS_NewContextRawWithHeapSize (rt, 1ULL << BUDDY_MIN_ORDER); -} - -static void JS_AddIntrinsics (JSContext *ctx) { - JS_AddIntrinsicBaseObjects (ctx); - JS_AddIntrinsicRegExp (ctx); -} - -JSContext *JS_NewContext (JSRuntime *rt) { - JSContext *ctx = JS_NewContextRaw (rt); - if (!ctx) return NULL; - JS_AddIntrinsics (ctx); - return ctx; -} - -JSContext *JS_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size) { - JSContext *ctx = JS_NewContextRawWithHeapSize (rt, heap_size); - if (!ctx) return NULL; - JS_AddIntrinsics (ctx); - return ctx; -} - -void *JS_GetContextOpaque (JSContext *ctx) { return ctx->user_opaque; } - -void JS_SetContextOpaque (JSContext *ctx, void *opaque) { - ctx->user_opaque = opaque; -} - -/* set the new value and free the old value after (freeing the value - can reallocate the object data) */ -static inline void set_value (JSContext *ctx, JSValue *pval, JSValue new_val) { - (void)ctx; - *pval = new_val; -} - -void JS_SetClassProto (JSContext *ctx, JSClassID class_id, JSValue obj) { - assert (class_id < ctx->class_count); - set_value (ctx, &ctx->class_proto[class_id], obj); -} - -JSValue JS_GetClassProto (JSContext *ctx, JSClassID class_id) { - assert (class_id < ctx->class_count); - return ctx->class_proto[class_id]; -} - -void JS_FreeContext (JSContext *ctx) { - JSRuntime *rt = ctx->rt; - int i; - -#ifdef DUMP_MEM - { - JSMemoryUsage stats; - JS_ComputeMemoryUsage (rt, &stats); - JS_DumpMemoryUsage (stdout, &stats, rt); - } -#endif - - for (i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { - } - for (i = 0; i < ctx->class_count; i++) { - } - js_free_rt (ctx->class_array); - js_free_rt (ctx->class_proto); - - /* Free VM stacks */ - if (ctx->frame_stack) js_free_rt (ctx->frame_stack); - if (ctx->value_stack) js_free_rt (ctx->value_stack); - - /* Free stone arena and intern table */ - st_free_all (ctx); - js_free_rt (ctx->st_text_hash); - js_free_rt (ctx->st_text_array); - - /* Free heap block */ - if (ctx->heap_base) { - heap_block_free (rt, ctx->heap_base, ctx->current_block_size); - ctx->heap_base = NULL; - ctx->heap_free = NULL; - ctx->heap_end = NULL; - } - - js_free_rt (ctx); -} - -JSRuntime *JS_GetRuntime (JSContext *ctx) { return ctx->rt; } - -static void update_stack_limit (JSContext *ctx) { - if (ctx->stack_size == 0) { - ctx->stack_limit = 0; /* no limit */ - } else { - ctx->stack_limit = ctx->stack_top - ctx->stack_size; - } -} - -void JS_SetMaxStackSize (JSContext *ctx, size_t stack_size) { - ctx->stack_size = stack_size; - update_stack_limit (ctx); -} - -void JS_UpdateStackTop (JSContext *ctx) { - ctx->stack_top = (const uint8_t *)js_get_stack_pointer (); - update_stack_limit (ctx); -} - -static JSText *js_alloc_string (JSContext *ctx, int max_len); - -static inline int is_num (int c) { return c >= '0' && c <= '9'; } - -static __maybe_unused void JS_DumpChar (FILE *fo, int c, int sep) { - if (c == sep || c == '\\') { - fputc ('\\', fo); - fputc (c, fo); - } else if (c >= ' ' && c <= 126) { - fputc (c, fo); - } else if (c == '\n') { - fputc ('\\', fo); - fputc ('n', fo); - } else { - fprintf (fo, "\\u%04x", c); - } -} - -static __maybe_unused void JS_DumpString (JSRuntime *rt, const JSText *p) { - int i; - - if (p == NULL) { - printf (""); - return; - } - putchar ('"'); - for (i = 0; i < (int)JSText_len (p); i++) { - JS_DumpChar (stdout, string_get (p, i), '"'); - } - putchar ('"'); -} - -static inline BOOL JS_IsEmptyString (JSValue v) { - return v == JS_EMPTY_TEXT; -} - -/* JSClass support */ - -/* a new class ID is allocated if *pclass_id != 0 */ -JSClassID JS_NewClassID (JSClassID *pclass_id) { - JSClassID class_id; - class_id = *pclass_id; - if (class_id == 0) { - class_id = js_class_id_alloc++; - *pclass_id = class_id; - } - return class_id; -} - -JSClassID JS_GetClassID (JSValue v) { - JSRecord *rec; - if (!JS_IsRecord (v)) return JS_INVALID_CLASS_ID; - rec = JS_VALUE_GET_RECORD (v); - return REC_GET_CLASS_ID(rec); -} - -BOOL JS_IsRegisteredClass (JSContext *ctx, JSClassID class_id) { - return (class_id < ctx->class_count - && ctx->class_array[class_id].class_id != 0); -} - -/* create a new object internal class. Return -1 if error, 0 if - OK. The finalizer can be NULL if none is needed. */ -static int JS_NewClass1 (JSContext *ctx, JSClassID class_id, const JSClassDef *class_def, const char *name) { - int new_size, i; - JSClass *cl, *new_class_array; - JSValue *new_class_proto; - - if (class_id >= (1 << 16)) return -1; - if (class_id < ctx->class_count && ctx->class_array[class_id].class_id != 0) - return -1; - - if (class_id >= ctx->class_count) { - new_size = max_int (JS_CLASS_INIT_COUNT, - max_int (class_id + 1, ctx->class_count * 3 / 2)); - - /* reallocate the class array */ - new_class_array - = js_realloc_rt (ctx->class_array, sizeof (JSClass) * new_size); - if (!new_class_array) return -1; - memset (new_class_array + ctx->class_count, 0, (new_size - ctx->class_count) * sizeof (JSClass)); - ctx->class_array = new_class_array; - - /* reallocate the class proto array */ - new_class_proto - = js_realloc_rt (ctx->class_proto, sizeof (JSValue) * new_size); - if (!new_class_proto) return -1; - for (i = ctx->class_count; i < new_size; i++) - new_class_proto[i] = JS_NULL; - ctx->class_proto = new_class_proto; - - ctx->class_count = new_size; - } - cl = &ctx->class_array[class_id]; - cl->class_id = class_id; - cl->class_name = name; /* name is already a const char* */ - cl->finalizer = class_def->finalizer; - cl->gc_mark = class_def->gc_mark; - return 0; -} - -int JS_NewClass (JSContext *ctx, JSClassID class_id, const JSClassDef *class_def) { - /* class_name is stored directly as const char* */ - return JS_NewClass1 (ctx, class_id, class_def, class_def->class_name); -} - -static JSValue js_new_string8_len (JSContext *ctx, const char *buf, int len) { - JSText *str; - int i; - - /* Empty string - return immediate empty */ - if (len <= 0) { return MIST_TryNewImmediateASCII ("", 0); } - - /* Try immediate ASCII for short strings (≤7 chars) */ - if (len <= MIST_ASCII_MAX_LEN) { - JSValue imm = MIST_TryNewImmediateASCII (buf, len); - if (!JS_IsNull (imm)) return imm; - } - - /* Fall back to heap string */ - str = js_alloc_string (ctx, len); - if (!str) return JS_ThrowMemoryError (ctx); - for (i = 0; i < len; i++) - string_put (str, i, buf[i]); - str->length = len; - - return pretext_end (ctx, str); -} - -static JSValue js_new_string8 (JSContext *ctx, const char *buf) { - return js_new_string8_len (ctx, buf, strlen (buf)); -} - -/* GC-safe substring: takes JSValue (which must be rooted by caller) */ -static JSValue js_sub_string (JSContext *ctx, JSText *p, int start, int end) { - int i; - int len = end - start; - if (start == 0 && end == (int)JSText_len (p)) { - return JS_MKPTR (p); - } - - /* Root the source string as a JSValue so it survives js_alloc_string GC */ - JSGCRef src_ref; - JS_PushGCRef (ctx, &src_ref); - src_ref.val = JS_MKPTR (p); - - JSText *str = js_alloc_string (ctx, len); - if (!str) { - JS_PopGCRef (ctx, &src_ref); - return JS_EXCEPTION; - } - - /* Re-chase p after allocation */ - p = JS_VALUE_GET_STRING (src_ref.val); - - for (i = 0; i < len; i++) - string_put (str, i, string_get (p, start + i)); - str->length = len; - - JS_PopGCRef (ctx, &src_ref); - return pretext_end (ctx, str); -} - -/* Substring from a JSValue (handles both immediate ASCII and heap strings) */ -static JSValue js_sub_string_val (JSContext *ctx, JSValue src, int start, int end) { - int len = end - start; - if (len <= 0) return JS_NewString (ctx, ""); - - if (MIST_IsImmediateASCII (src)) { - /* IMM: extract chars directly, try to return IMM */ - if (len <= MIST_ASCII_MAX_LEN) { - char buf[MIST_ASCII_MAX_LEN + 1]; - for (int i = 0; i < len; i++) - buf[i] = (char)MIST_GetImmediateASCIIChar (src, start + i); - return js_new_string8_len (ctx, buf, len); - } - /* Longer than 7 — shouldn't happen for IMM (max 7 chars) but handle it */ - JSText *str = js_alloc_string (ctx, len); - if (!str) return JS_EXCEPTION; - for (int i = 0; i < len; i++) - string_put (str, i, MIST_GetImmediateASCIIChar (src, start + i)); - str->length = len; - return pretext_end (ctx, str); - } - - /* Heap string — delegate to existing js_sub_string */ - return js_sub_string (ctx, JS_VALUE_GET_STRING (src), start, end); -} - -/* Allocate a new pretext (mutable JSText) with initial capacity */ -static JSText *pretext_init (JSContext *ctx, int capacity) { - if (capacity <= 0) capacity = 16; - JSText *s = js_alloc_string (ctx, capacity); - if (!s) return NULL; - s->length = 0; - return s; -} - -/* Reallocate a pretext to hold new_len characters */ -static no_inline JSText *pretext_realloc (JSContext *ctx, JSText *s, int new_len) { - if (new_len > JS_STRING_LEN_MAX) { - JS_ThrowInternalError (ctx, "string too long"); - return NULL; - } - int old_cap = (int)objhdr_cap56 (s->hdr); - int old_len = (int)s->length; - /* Grow by 50%, ensuring we have at least new_len capacity */ - int new_cap = max_int (new_len, old_cap * 3 / 2); - - /* Protect source object with a GC ref before allocating (GC may move it) */ - JSGCRef src_ref; - JS_PushGCRef (ctx, &src_ref); - src_ref.val = JS_MKPTR (s); - - /* Allocate new string - this may trigger GC */ - JSText *new_str = js_alloc_string (ctx, new_cap); - if (!new_str) { - JS_PopGCRef (ctx, &src_ref); - return NULL; - } - - /* Get possibly-moved source pointer after GC */ - s = (JSText *)chase (src_ref.val); - JS_PopGCRef (ctx, &src_ref); - - /* Copy data from old string to new */ - new_str->length = old_len; - for (int i = 0; i < old_len; i++) { - string_put (new_str, i, string_get (s, i)); - } - - return new_str; -} - -static no_inline JSText *pretext_putc_slow (JSContext *ctx, JSText *s, uint32_t c) { - int len = (int)s->length; - int cap = (int)objhdr_cap56 (s->hdr); - if (unlikely (len >= cap)) { - s = pretext_realloc (ctx, s, len + 1); - if (!s) return NULL; - } - string_put (s, len, c); - s->length++; - return s; -} - -/* 0 <= c <= 0x10ffff */ -static JSText *pretext_putc (JSContext *ctx, JSText *s, uint32_t c) { - int len = (int)s->length; - int cap = (int)objhdr_cap56 (s->hdr); - if (likely (len < cap)) { - string_put (s, len, c); - s->length++; - return s; - } - return pretext_putc_slow (ctx, s, c); -} - -static JSText *pretext_write8 (JSContext *ctx, JSText *s, const uint8_t *p, int len) { - int cur_len = (int)s->length; - int cap = (int)objhdr_cap56 (s->hdr); - if (cur_len + len > cap) { - s = pretext_realloc (ctx, s, cur_len + len); - if (!s) return NULL; - } - for (int i = 0; i < len; i++) { - string_put (s, cur_len + i, p[i]); - } - s->length += len; - return s; -} - -/* appending an ASCII string */ -static JSText *pretext_puts8 (JSContext *ctx, JSText *s, const char *str) { - return pretext_write8 (ctx, s, (const uint8_t *)str, strlen (str)); -} - -static JSText *pretext_concat (JSContext *ctx, JSText *s, const JSText *p, uint32_t from, uint32_t to) { - if (to <= from) return s; - int len = (int)(to - from); - int cur_len = (int)s->length; - int cap = (int)objhdr_cap56 (s->hdr); - if (cur_len + len > cap) { - /* Root p across pretext_realloc which can trigger GC */ - JSGCRef p_ref; - JS_PushGCRef (ctx, &p_ref); - p_ref.val = JS_MKPTR ((void *)p); - s = pretext_realloc (ctx, s, cur_len + len); - p = (const JSText *)chase (p_ref.val); - JS_PopGCRef (ctx, &p_ref); - if (!s) return NULL; - } - for (int i = 0; i < len; i++) { - string_put (s, cur_len + i, string_get (p, (int)from + i)); - } - s->length += len; - return s; -} - -static JSText *pretext_concat_value (JSContext *ctx, JSText *s, JSValue v) { - if (MIST_IsImmediateASCII (v)) { - int len = MIST_GetImmediateASCIILen (v); - char buf[8]; - for (int i = 0; i < len; i++) - buf[i] = MIST_GetImmediateASCIIChar (v, i); - return pretext_write8 (ctx, s, (const uint8_t *)buf, len); - } - if (JS_IsText (v)) { - JSText *p = JS_VALUE_GET_PTR (v); - return pretext_concat (ctx, s, p, 0, (uint32_t)JSText_len (p)); - } - JSValue v1 = JS_ToString (ctx, v); - if (JS_IsException (v1)) return NULL; - - if (MIST_IsImmediateASCII (v1)) { - int len = MIST_GetImmediateASCIILen (v1); - char buf[8]; - for (int i = 0; i < len; i++) - buf[i] = MIST_GetImmediateASCIIChar (v1, i); - s = pretext_write8 (ctx, s, (const uint8_t *)buf, len); - return s; - } - - JSText *p = JS_VALUE_GET_STRING (v1); - s = pretext_concat (ctx, s, p, 0, (uint32_t)JSText_len (p)); - return s; -} - -/* Finalize a pretext into an immutable JSValue string */ -static JSValue pretext_end (JSContext *ctx, JSText *s) { - if (!s) return JS_EXCEPTION; - int len = (int)s->length; - if (len == 0) { - js_free (ctx, s); - return JS_KEY_empty; - } - /* Set final length in capacity field and clear length for hash storage */ - s->hdr = objhdr_set_cap56 (s->hdr, len); - s->length = 0; - s->hdr = objhdr_set_s (s->hdr, true); /* mark as stone */ - return JS_MKPTR (s); -} - -/* create a string from a UTF-8 buffer */ -JSValue JS_NewStringLen (JSContext *ctx, const char *buf, size_t buf_len) { - if (buf_len > JS_STRING_LEN_MAX) - return JS_ThrowInternalError (ctx, "string too long"); - - /* Try immediate ASCII first (<=7 ASCII chars) */ - if (buf_len <= MIST_ASCII_MAX_LEN) { - JSValue ret = MIST_TryNewImmediateASCII (buf, buf_len); - if (!JS_IsNull (ret)) return ret; - } - - /* Count actual codepoints for allocation */ - const uint8_t *p = (const uint8_t *)buf; - const uint8_t *end = p + buf_len; - int codepoint_count = 0; - while (p < end) { - if (*p < 128) { - p++; - codepoint_count++; - } else { - const uint8_t *next; - int c = unicode_from_utf8 (p, (int)(end - p), &next); - if (c < 0) { - /* Invalid UTF-8 byte, treat as single byte */ - p++; - } else { - p = next; - } - codepoint_count++; - } - } - - JSText *str = js_alloc_string (ctx, codepoint_count); - if (!str) return JS_ThrowMemoryError (ctx); - - /* Decode UTF-8 to UTF-32 */ - p = (const uint8_t *)buf; - int i = 0; - while (p < end) { - uint32_t c; - if (*p < 128) { - c = *p++; - } else { - const uint8_t *next; - int decoded = unicode_from_utf8 (p, (int)(end - p), &next); - if (decoded < 0) { - /* Invalid UTF-8 byte, use replacement char or the byte itself */ - c = *p++; - } else { - c = (uint32_t)decoded; - p = next; - } - } - string_put (str, i++, c); - } - str->length = codepoint_count; - return pretext_end (ctx, str); -} - -static JSValue JS_ConcatString3 (JSContext *ctx, const char *str1, JSValue str2, const char *str3) { - JSText *b; - int len1, len3, str2_len; - - if (!JS_IsText (str2)) { - str2 = JS_ToString (ctx, str2); - if (JS_IsException (str2)) goto fail; - } - - str2_len = js_string_value_len (str2); - len1 = strlen (str1); - len3 = strlen (str3); - - b = pretext_init (ctx, len1 + str2_len + len3); - if (!b) goto fail; - - b = pretext_write8 (ctx, b, (const uint8_t *)str1, len1); - if (!b) goto fail; - b = pretext_concat_value (ctx, b, str2); - if (!b) goto fail; - b = pretext_write8 (ctx, b, (const uint8_t *)str3, len3); - if (!b) goto fail; - - return pretext_end (ctx, b); - -fail: - return JS_EXCEPTION; -} - -/* return (NULL, 0) if exception. */ -/* return pointer into a JSText with a live ref_count */ -/* cesu8 determines if non-BMP1 codepoints are encoded as 1 or 2 utf-8 - * sequences */ -const char *JS_ToCStringLen2 (JSContext *ctx, size_t *plen, JSValue val1, BOOL cesu8) { - JSGCRef val_ref; - char *q, *ret; - size_t size; - int i, len; - - JS_PushGCRef (ctx, &val_ref); - - if (!JS_IsText (val1)) { - val_ref.val = JS_ToString (ctx, val1); - if (JS_IsException (val_ref.val)) goto fail; - } else { - val_ref.val = val1; - } - - /* Handle immediate ASCII strings */ - if (MIST_IsImmediateASCII (val_ref.val)) { - len = MIST_GetImmediateASCIILen (val_ref.val); - ret = js_malloc_rt (len + 1); /* Use non-GC heap for returned C string */ - if (!ret) goto fail; - /* Re-read from val_ref after potential GC */ - for (i = 0; i < len; i++) { - ret[i] = MIST_GetImmediateASCIIChar (val_ref.val, i); - } - ret[len] = '\0'; - if (plen) *plen = len; - JS_PopGCRef (ctx, &val_ref); - return ret; - } - - /* Handle heap strings (JSText) */ - JSText *str = JS_VALUE_GET_STRING (val_ref.val); - len = (int)JSText_len (str); - - /* Calculate UTF-8 size */ - size = 0; - for (i = 0; i < len; i++) { - uint32_t c = string_get (str, i); - if (c < 0x80) - size += 1; - else if (c < 0x800) - size += 2; - else if (c < 0x10000) - size += 3; - else - size += 4; - } - - ret = js_malloc_rt (size + 1); /* Use non-GC heap for returned C string */ - if (!ret) goto fail; - - /* str pointer is still valid - no GC triggered by js_malloc_rt */ - /* Re-extract for safety in case code above changes */ - str = JS_VALUE_GET_STRING (val_ref.val); - q = ret; - for (i = 0; i < len; i++) { - uint32_t c = string_get (str, i); - q += unicode_to_utf8 ((uint8_t *)q, c); - } - *q = '\0'; - - if (plen) *plen = size; - - JS_PopGCRef (ctx, &val_ref); - return ret; - -fail: - JS_PopGCRef (ctx, &val_ref); - if (plen) *plen = 0; - return NULL; -} - -void JS_FreeCString (JSContext *ctx, const char *ptr) { - /* Free C string allocated from non-GC heap */ - js_free_rt ((void *)ptr); - (void)ctx; - (void)ptr; -} - -static JSValue JS_ConcatString1 (JSContext *ctx, const JSText *p1, const JSText *p2) { - JSText *p; - uint32_t len; - int len1 = (int)JSText_len (p1); - int len2 = (int)JSText_len (p2); - - len = len1 + len2; - /* len is uint32_t, JS_STRING_LEN_MAX is 56 bits, so this always fits */ - p = js_alloc_string (ctx, len); - if (!p) return JS_EXCEPTION; - /* Pack first string */ - { - int i; - for (i = 0; i < len1; i++) - string_put (p, i, string_get (p1, i)); - for (i = 0; i < len2; i++) - string_put (p, len1 + i, string_get (p2, i)); - } - return JS_MKPTR (p); -} - -// TODO: this function is fucked. -static BOOL JS_ConcatStringInPlace (JSContext *ctx, JSText *p1, JSValue op2) { - (void)ctx; - if (JS_VALUE_GET_TAG (op2) == JS_TAG_STRING) { - JSText *p2 = JS_VALUE_GET_STRING (op2); - int64_t new_len; - int64_t len1 = JSText_len (p1); - int64_t len2 = JSText_len (p2); - - if (len2 == 0) return TRUE; - - new_len = len1 + len2; - - /* Append p2's characters using string_put/string_get */ - for (int64_t i = 0; i < len2; i++) { - string_put (p1, len1 + i, string_get (p2, i)); - } - p1->hdr = objhdr_set_cap56 (p1->hdr, new_len); - return TRUE; - } - return FALSE; -} - -/* Helper for string value comparison (handles immediate and heap strings) */ -static int js_string_compare_value (JSContext *ctx, JSValue op1, JSValue op2, BOOL eq_only) { - (void)ctx; - if (eq_only && op1 == op2) return 0; - - int len1 = js_string_value_len (op1); - int len2 = js_string_value_len (op2); - - if (eq_only && len1 != len2) return 1; - - int len = min_int (len1, len2); - - for (int i = 0; i < len; i++) { - uint32_t c1 = js_string_value_get (op1, i); - uint32_t c2 = js_string_value_get (op2, i); - if (c1 != c2) { return (c1 < c2) ? -1 : 1; } - } - - if (len1 == len2) return 0; - return (len1 < len2) ? -1 : 1; -} - -static int js_string_compare_value_nocase (JSContext *ctx, JSValue op1, JSValue op2) { - (void)ctx; - int len1 = js_string_value_len (op1); - int len2 = js_string_value_len (op2); - if (len1 != len2) return 1; - for (int i = 0; i < len1; i++) { - uint32_t c1 = js_string_value_get (op1, i); - uint32_t c2 = js_string_value_get (op2, i); - if (c1 >= 'A' && c1 <= 'Z') c1 += 32; - if (c2 >= 'A' && c2 <= 'Z') c2 += 32; - if (c1 != c2) return 1; - } - return 0; -} - -static JSValue JS_ConcatString (JSContext *ctx, JSValue op1, JSValue op2) { - if (unlikely (!JS_IsText (op1))) { - op1 = JS_ToString (ctx, op1); - if (JS_IsException (op1)) { - return JS_EXCEPTION; - } - } - if (unlikely (!JS_IsText (op2))) { - op2 = JS_ToString (ctx, op2); - if (JS_IsException (op2)) { - return JS_EXCEPTION; - } - } - - int len1 = js_string_value_len (op1); - int len2 = js_string_value_len (op2); - int new_len = len1 + len2; - JSValue ret_val = JS_NULL; - - /* Try to create immediate ASCII if short enough and all ASCII */ - if (new_len <= MIST_ASCII_MAX_LEN) { - char buf[8]; - BOOL all_ascii = TRUE; - for (int i = 0; i < len1 && all_ascii; i++) { - uint32_t c = js_string_value_get (op1, i); - if (c >= 0x80) - all_ascii = FALSE; - else - buf[i] = (char)c; - } - for (int i = 0; i < len2 && all_ascii; i++) { - uint32_t c = js_string_value_get (op2, i); - if (c >= 0x80) - all_ascii = FALSE; - else - buf[len1 + i] = (char)c; - } - if (all_ascii) { ret_val = MIST_TryNewImmediateASCII (buf, new_len); } - } - - if (JS_IsNull (ret_val)) { - /* Protect op1 and op2 from GC during allocation */ - JSGCRef op1_ref, op2_ref; - JS_PushGCRef (ctx, &op1_ref); - op1_ref.val = op1; - JS_PushGCRef (ctx, &op2_ref); - op2_ref.val = op2; - - JSText *p = js_alloc_string (ctx, new_len); - if (!p) { - JS_PopGCRef (ctx, &op2_ref); - JS_PopGCRef (ctx, &op1_ref); - return JS_EXCEPTION; - } - - /* Get possibly-moved values after GC */ - op1 = op1_ref.val; - op2 = op2_ref.val; - JS_PopGCRef (ctx, &op2_ref); - JS_PopGCRef (ctx, &op1_ref); - - /* Copy characters using string_put/get */ - for (int i = 0; i < len1; i++) { - string_put (p, i, js_string_value_get (op1, i)); - } - for (int i = 0; i < len2; i++) { - string_put (p, len1 + i, js_string_value_get (op2, i)); - } - p->length = new_len; - ret_val = JS_MKPTR (p); - } - - return ret_val; -} - -/* WARNING: proto must be an object or JS_NULL */ -JSValue JS_NewObjectProtoClass (JSContext *ctx, JSValue proto_val, JSClassID class_id) { - JSGCRef proto_ref; - JS_PushGCRef (ctx, &proto_ref); - proto_ref.val = proto_val; - - JSRecord *rec = js_new_record_class (ctx, 0, class_id); - - proto_val = proto_ref.val; /* Get potentially-updated value after GC */ - JS_PopGCRef (ctx, &proto_ref); - - if (!rec) return JS_EXCEPTION; - - /* Set prototype if provided */ - if (JS_IsRecord (proto_val)) { - rec->proto = JS_VALUE_GET_RECORD (proto_val); - } - - return JS_MKPTR (rec); -} - -JSValue JS_NewObjectClass (JSContext *ctx, int class_id) { - return JS_NewObjectProtoClass (ctx, ctx->class_proto[class_id], class_id); -} - -JSValue JS_NewObjectProto (JSContext *ctx, JSValue proto) { - return JS_NewObjectProtoClass (ctx, proto, JS_CLASS_OBJECT); -} - -/* Create an intrinsic array with specified capacity - Uses bump allocation - values are inline after the JSArray struct */ -JSValue JS_NewArrayLen (JSContext *ctx, uint32_t len) { - JSArray *arr; - uint32_t cap; - - cap = len > 0 ? len : JS_ARRAY_INITIAL_SIZE; - size_t values_size = sizeof (JSValue) * cap; - size_t total_size = sizeof (JSArray) + values_size; - - arr = js_malloc (ctx, total_size); - if (!arr) return JS_EXCEPTION; - - arr->mist_hdr = objhdr_make (cap, OBJ_ARRAY, false, false, false, false); - arr->len = len; - - /* Initialize all values to null (values[] is inline flexible array member) */ - for (uint32_t i = 0; i < cap; i++) { - arr->values[i] = JS_NULL; - } - - return JS_MKPTR (arr); -} - -JSValue JS_NewArray (JSContext *ctx) { return JS_NewArrayLen (ctx, 0); } - -JSValue JS_NewObject (JSContext *ctx) { - /* inline JS_NewObjectClass(ctx, JS_CLASS_OBJECT); */ - return JS_NewObjectProtoClass (ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_OBJECT); -} - -/* Helper to check if a value is a bytecode function */ -static BOOL js_is_bytecode_function (JSValue val) { - if (!JS_IsFunction (val)) return FALSE; - JSFunction *f = JS_VALUE_GET_FUNCTION (val); - return f->kind == JS_FUNC_KIND_BYTECODE; -} - -/* return NULL without exception if not a function or no bytecode */ -static JSFunctionBytecode *JS_GetFunctionBytecode (JSValue val) { - JSFunction *f; - if (!JS_IsFunction (val)) return NULL; - f = JS_VALUE_GET_FUNCTION (val); - if (f->kind != JS_FUNC_KIND_BYTECODE) return NULL; - return f->u.func.function_bytecode; -} - -// TODO: needs reworked -static int js_method_set_properties (JSContext *ctx, JSValue func_obj, JSValue name, int flags, JSValue home_obj) { - (void)ctx; - (void)flags; - (void)home_obj; - if (!JS_IsFunction (func_obj)) return -1; - JSFunction *f = JS_VALUE_GET_FUNCTION (func_obj); - /* name is now JSValue text */ - if (JS_IsText (name)) { f->name = name; } - return 0; -} - -/* Note: at least 'length' arguments will be readable in 'argv' */ -static JSValue JS_NewCFunction3 (JSContext *ctx, JSCFunction *func, const char *name, int length, JSCFunctionEnum cproto, int magic) { - JSValue func_obj; - JSFunction *f; - - func_obj = js_new_function (ctx, JS_FUNC_KIND_C); - if (JS_IsException (func_obj)) return func_obj; - f = JS_VALUE_GET_FUNCTION (func_obj); - f->u.cfunc.c_function.generic = func; - f->u.cfunc.cproto = cproto; - f->u.cfunc.magic = magic; - f->length = length; - if (name) { - JSGCRef fobj_ref; - JS_PushGCRef (ctx, &fobj_ref); - fobj_ref.val = func_obj; - JSValue key = js_key_new (ctx, name); - func_obj = fobj_ref.val; - JS_PopGCRef (ctx, &fobj_ref); - f = JS_VALUE_GET_FUNCTION (func_obj); /* re-chase after allocation */ - f->name = key; - } else { - f->name = JS_KEY_empty; - } - return func_obj; -} - -/* Note: at least 'length' arguments will be readable in 'argv' */ -JSValue JS_NewCFunction2 (JSContext *ctx, JSCFunction *func, const char *name, int length, JSCFunctionEnum cproto, int magic) { - return JS_NewCFunction3 (ctx, func, name, length, cproto, magic); -} - -/* free_property is defined earlier as a stub since shapes are removed */ - -/* GC-safe array growth function. - Takes JSValue* pointer to a GC-tracked location (like &argv[n]). - Allocates new array, copies data, installs forward header at old location. */ -static int js_array_grow (JSContext *ctx, JSValue *arr_ptr, word_t min_cap) { - JSArray *arr = JS_VALUE_GET_ARRAY (*arr_ptr); - word_t old_cap = js_array_cap (arr); - if (min_cap <= old_cap) return 0; - - if (objhdr_s (arr->mist_hdr)) { - JS_ThrowInternalError (ctx, "cannot grow a stoned array"); - return -1; - } - - word_t new_cap = old_cap ? old_cap : JS_ARRAY_INITIAL_SIZE; - while (new_cap < min_cap && new_cap <= JS_ARRAY_MAX_CAP / 2) - new_cap *= 2; - if (new_cap > JS_ARRAY_MAX_CAP) new_cap = JS_ARRAY_MAX_CAP; - if (new_cap < min_cap) { - JS_ThrowRangeError (ctx, "array capacity overflow"); - return -1; - } - - size_t total_size = sizeof (JSArray) + sizeof (JSValue) * new_cap; - JSArray *new_arr = js_malloc (ctx, total_size); - if (!new_arr) return -1; - - /* Re-chase arr via arr_ptr (GC may have moved it during js_malloc) */ - arr = JS_VALUE_GET_ARRAY (*arr_ptr); - - new_arr->mist_hdr = objhdr_make (new_cap, OBJ_ARRAY, false, false, false, false); - new_arr->len = arr->len; - - JSValue old_ptr = *arr_ptr; - for (word_t i = 0; i < arr->len; i++) - new_arr->values[i] = arr->values[i]; - for (word_t i = arr->len; i < new_cap; i++) - new_arr->values[i] = JS_NULL; - - /* Install forward header at old location */ - arr->mist_hdr = objhdr_make_fwd (new_arr); - - /* Update the tracked JSValue to point to new array */ - *arr_ptr = JS_MKPTR (new_arr); - - /* Fix self-references: update elements that pointed to the old array */ - for (word_t i = 0; i < new_arr->len; i++) { - if (new_arr->values[i] == old_ptr) - new_arr->values[i] = *arr_ptr; - } - - return 0; -} - -/* GC-safe array push. Takes JSValue* to GC-tracked location. */ -static int js_intrinsic_array_push (JSContext *ctx, JSValue *arr_ptr, JSValue val) { - JSArray *arr = JS_VALUE_GET_ARRAY (*arr_ptr); - - if (objhdr_s (arr->mist_hdr)) { - JS_ThrowInternalError (ctx, "cannot push to a stoned array"); - return -1; - } - - if (arr->len >= js_array_cap (arr)) { - if (js_array_grow (ctx, arr_ptr, arr->len + 1) < 0) - return -1; - arr = JS_VALUE_GET_ARRAY (*arr_ptr); /* re-chase after grow */ - } - - arr->values[arr->len++] = val; - return 0; -} - -/* GC-safe array set. Takes JSValue* to GC-tracked location. */ -static int js_intrinsic_array_set (JSContext *ctx, JSValue *arr_ptr, word_t idx, JSValue val) { - JSArray *arr = JS_VALUE_GET_ARRAY (*arr_ptr); - - if (objhdr_s (arr->mist_hdr)) { - JS_ThrowInternalError (ctx, "cannot set on a stoned array"); - return -1; - } - - if (idx >= js_array_cap (arr)) { - /* Root val across js_array_grow which can trigger GC */ - JSGCRef val_ref; - JS_PushGCRef (ctx, &val_ref); - val_ref.val = val; - if (js_array_grow (ctx, arr_ptr, idx + 1) < 0) { - JS_PopGCRef (ctx, &val_ref); - return -1; - } - val = val_ref.val; - JS_PopGCRef (ctx, &val_ref); - arr = JS_VALUE_GET_ARRAY (*arr_ptr); /* re-chase after grow */ - } - - if (idx >= arr->len) { - for (word_t i = arr->len; i < idx; i++) - arr->values[i] = JS_NULL; - arr->len = idx + 1; - } - - arr->values[idx] = val; - return 0; -} - -/* Allocate intrinsic function (JS_TAG_FUNCTION) */ -static JSValue js_new_function (JSContext *ctx, JSFunctionKind kind) { - JSFunction *func = js_mallocz (ctx, sizeof (JSFunction)); - if (!func) return JS_EXCEPTION; - func->header = objhdr_make (0, OBJ_FUNCTION, false, false, false, false); - func->kind = kind; - func->name = JS_NULL; - func->length = 0; - /* Initialize closure fields for bytecode functions */ - if (kind == JS_FUNC_KIND_BYTECODE) { - func->u.func.outer_frame = JS_NULL; - func->u.func.env_record = JS_NULL; - } - return JS_MKPTR (func); -} - -/* Get pointer to an upvalue in outer scope frame chain. - depth=0 is current frame, depth=1 is immediate outer, etc. - Returns NULL if depth exceeds the frame chain. - frame_val is a JSValue containing a JSFrame pointer. */ -static inline JSValue *get_upvalue_ptr (JSValue frame_val, int depth, int slot) { - if (JS_IsNull(frame_val)) return NULL; - JSFrame *frame = JS_VALUE_GET_FRAME(frame_val); - while (depth > 0) { - JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); - frame_val = fn->u.func.outer_frame; - if (JS_IsNull(frame_val)) return NULL; - frame = JS_VALUE_GET_FRAME(frame_val); - depth--; - } - return &frame->slots[slot]; -} - -void JS_ComputeMemoryUsage (JSRuntime *rt, JSMemoryUsage *s) { -} - -void JS_DumpMemoryUsage (FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) { -} - -/* WARNING: obj is freed */ -JSValue JS_Throw (JSContext *ctx, JSValue obj) { - ctx->current_exception = obj; - return JS_EXCEPTION; -} - -/* return the pending exception (cannot be called twice). */ -JSValue JS_GetException (JSContext *ctx) { - JSValue val = ctx->current_exception; - ctx->current_exception = JS_UNINITIALIZED; - return val; -} - -JS_BOOL -JS_HasException (JSContext *ctx) { - return !JS_IsUninitialized (ctx->current_exception); -} - -static void dbuf_put_leb128 (DynBuf *s, uint32_t v) { - uint32_t a; - for (;;) { - a = v & 0x7f; - v >>= 7; - if (v != 0) { - dbuf_putc (s, a | 0x80); - } else { - dbuf_putc (s, a); - break; - } - } -} - -static void dbuf_put_sleb128 (DynBuf *s, int32_t v1) { - uint32_t v = v1; - dbuf_put_leb128 (s, (2 * v) ^ -(v >> 31)); -} - -static int get_leb128 (uint32_t *pval, const uint8_t *buf, const uint8_t *buf_end) { - const uint8_t *ptr = buf; - uint32_t v, a, i; - v = 0; - for (i = 0; i < 5; i++) { - if (unlikely (ptr >= buf_end)) break; - a = *ptr++; - v |= (a & 0x7f) << (i * 7); - if (!(a & 0x80)) { - *pval = v; - return ptr - buf; - } - } - *pval = 0; - return -1; -} - -static int get_sleb128 (int32_t *pval, const uint8_t *buf, const uint8_t *buf_end) { - int ret; - uint32_t val; - ret = get_leb128 (&val, buf, buf_end); - if (ret < 0) { - *pval = 0; - return -1; - } - *pval = (val >> 1) ^ -(val & 1); - return ret; -} - -/* use pc_value = -1 to get the position of the function definition */ -static int find_line_num (JSContext *ctx, JSFunctionBytecode *b, uint32_t pc_value, int *pcol_num) { - const uint8_t *p_end, *p; - int new_line_num, line_num, pc, v, ret, new_col_num, col_num; - uint32_t val; - unsigned int op; - - if (!b->has_debug || !b->debug.pc2line_buf) - goto fail; /* function was stripped */ - - p = b->debug.pc2line_buf; - p_end = p + b->debug.pc2line_len; - - /* get the function line and column numbers */ - ret = get_leb128 (&val, p, p_end); - if (ret < 0) goto fail; - p += ret; - line_num = val + 1; - - ret = get_leb128 (&val, p, p_end); - if (ret < 0) goto fail; - p += ret; - col_num = val + 1; - - if (pc_value != -1) { - pc = 0; - while (p < p_end) { - op = *p++; - if (op == 0) { - ret = get_leb128 (&val, p, p_end); - if (ret < 0) goto fail; - pc += val; - p += ret; - ret = get_sleb128 (&v, p, p_end); - if (ret < 0) goto fail; - p += ret; - new_line_num = line_num + v; - } else { - op -= PC2LINE_OP_FIRST; - pc += (op / PC2LINE_RANGE); - new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE; - } - ret = get_sleb128 (&v, p, p_end); - if (ret < 0) goto fail; - p += ret; - new_col_num = col_num + v; - - if (pc_value < pc) goto done; - line_num = new_line_num; - col_num = new_col_num; - } - } -done: - *pcol_num = col_num; - return line_num; -fail: - *pcol_num = 0; - return 0; -} - -/* in order to avoid executing arbitrary code during the stack trace - generation, we only look at simple 'name' properties containing a - string. */ -static const char *get_func_name (JSContext *ctx, JSValue func) { - if (!JS_IsRecord (func)) return NULL; - JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (func); - - /* Create "name" key as immediate ASCII string */ - JSValue name_key = MIST_TryNewImmediateASCII ("name", 4); - - int slot = rec_find_slot (rec, name_key); - if (slot <= 0) return NULL; - - JSValue val = rec->slots[slot].val; - if (!JS_IsText (val)) return NULL; - return JS_ToCString (ctx, val); -} - -#define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0) - -/* if filename != NULL, an additional level is added with the filename - and line number information (used for parse error). */ -static void build_backtrace (JSContext *ctx, JSValue error_obj, const char *filename, int line_num, int col_num, int backtrace_flags) { - JSStackFrame *sf; - JSValue str; - DynBuf dbuf; - const char *func_name_str; - const char *str1; - JSGCRef err_ref; - - if (!JS_IsObject (error_obj)) - return; /* protection in the out of memory case */ - - /* Protect error_obj from GC during backtrace building */ - JS_PushGCRef (ctx, &err_ref); - err_ref.val = error_obj; - - js_dbuf_init (ctx, &dbuf); - if (filename) { - dbuf_printf (&dbuf, " at %s", filename); - if (line_num != -1) dbuf_printf (&dbuf, ":%d:%d", line_num, col_num); - dbuf_putc (&dbuf, '\n'); - /* Use short immediate strings for keys to avoid GC issues */ - JSValue key_fileName = MIST_TryNewImmediateASCII ("file", 4); - JSValue key_lineNumber = MIST_TryNewImmediateASCII ("line", 4); - JSValue key_columnNumber = MIST_TryNewImmediateASCII ("col", 3); - str = JS_NewString (ctx, filename); - if (JS_IsException (str)) { - JS_PopGCRef (ctx, &err_ref); - return; - } - if (JS_SetPropertyInternal (ctx, err_ref.val, key_fileName, str) < 0 - || JS_SetPropertyInternal (ctx, err_ref.val, key_lineNumber, JS_NewInt32 (ctx, line_num)) - < 0 - || JS_SetPropertyInternal (ctx, err_ref.val, key_columnNumber, JS_NewInt32 (ctx, col_num)) - < 0) { - JS_PopGCRef (ctx, &err_ref); - return; - } - } - for (sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) { - if (sf->js_mode & JS_MODE_BACKTRACE_BARRIER) break; - if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) { - backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL; - continue; - } - func_name_str = get_func_name (ctx, sf->cur_func); - if (!func_name_str || func_name_str[0] == '\0') - str1 = ""; - else - str1 = func_name_str; - dbuf_printf (&dbuf, " at %s", str1); - JS_FreeCString (ctx, func_name_str); - - if (JS_IsFunction (sf->cur_func)) { - JSFunction *fn = JS_VALUE_GET_FUNCTION (sf->cur_func); - if (fn->kind == JS_FUNC_KIND_BYTECODE) { - JSFunctionBytecode *b; - const char *atom_str; - int line_num1, col_num1; - - b = fn->u.func.function_bytecode; - if (b->has_debug) { - line_num1 = find_line_num (ctx, b, sf->cur_pc - b->byte_code_buf - 1, &col_num1); - atom_str = JS_ToCString (ctx, b->debug.filename); - dbuf_printf (&dbuf, " (%s", atom_str ? atom_str : ""); - JS_FreeCString (ctx, atom_str); - if (line_num1 != 0) - dbuf_printf (&dbuf, ":%d:%d", line_num1, col_num1); - dbuf_putc (&dbuf, ')'); - } - } else { - dbuf_printf (&dbuf, " (native)"); - } - } else { - dbuf_printf (&dbuf, " (native)"); - } - dbuf_putc (&dbuf, '\n'); - } - dbuf_putc (&dbuf, '\0'); - if (dbuf_error (&dbuf)) - str = JS_NULL; - else - str = JS_NewString (ctx, (char *)dbuf.buf); - dbuf_free (&dbuf); - JS_SetPropertyInternal (ctx, err_ref.val, JS_KEY_stack, str); - JS_PopGCRef (ctx, &err_ref); -} - -/* Note: it is important that no exception is returned by this function */ -static BOOL is_backtrace_needed (JSContext *ctx, JSValue obj) { - JSRecord *p; - if (!JS_IsRecord (obj)) return FALSE; - p = JS_VALUE_GET_OBJ (obj); - if (REC_GET_CLASS_ID(p) != JS_CLASS_ERROR) return FALSE; - /* Check if "stack" property already exists */ - JSValue stack_key = MIST_TryNewImmediateASCII ("stack", 5); - JSRecord *rec = (JSRecord *)p; - int slot = rec_find_slot (rec, stack_key); - if (slot > 0) return FALSE; - return TRUE; -} - -JSValue JS_NewError (JSContext *ctx) { - return JS_NewObjectClass (ctx, JS_CLASS_ERROR); -} - -static JSValue JS_ThrowError2 (JSContext *ctx, JSErrorEnum error_num, const char *fmt, va_list ap, BOOL add_backtrace) { - char buf[256]; - JSValue ret; - JSGCRef obj_ref; - - vsnprintf (buf, sizeof (buf), fmt, ap); - JS_PushGCRef (ctx, &obj_ref); - obj_ref.val = JS_NewObjectProtoClass (ctx, ctx->native_error_proto[error_num], JS_CLASS_ERROR); - if (unlikely (JS_IsException (obj_ref.val))) { - /* out of memory: throw JS_NULL to avoid recursing */ - obj_ref.val = JS_NULL; - } else { - JSValue msg = JS_NewString (ctx, buf); - JS_SetPropertyInternal (ctx, obj_ref.val, JS_KEY_message, msg); - if (add_backtrace) { build_backtrace (ctx, obj_ref.val, NULL, 0, 0, 0); } - } - ret = JS_Throw (ctx, obj_ref.val); - JS_PopGCRef (ctx, &obj_ref); - return ret; -} - -static JSValue JS_ThrowError (JSContext *ctx, JSErrorEnum error_num, const char *fmt, va_list ap) { - JSStackFrame *sf; - BOOL add_backtrace; - - /* the backtrace is added later if called from a bytecode function */ - sf = ctx->current_stack_frame; - add_backtrace = (!sf || (JS_GetFunctionBytecode (sf->cur_func) == NULL)); - return JS_ThrowError2 (ctx, error_num, fmt, ap, add_backtrace); -} - -JSValue __attribute__ ((format (printf, 2, 3))) -JS_ThrowSyntaxError (JSContext *ctx, const char *fmt, ...) { - JSValue val; - va_list ap; - - va_start (ap, fmt); - val = JS_ThrowError (ctx, JS_SYNTAX_ERROR, fmt, ap); - va_end (ap); - return val; -} - -JSValue __attribute__ ((format (printf, 2, 3))) -JS_ThrowTypeError (JSContext *ctx, const char *fmt, ...) { - JSValue val; - va_list ap; - - va_start (ap, fmt); - val = JS_ThrowError (ctx, JS_TYPE_ERROR, fmt, ap); - va_end (ap); - return val; -} - -JSValue __attribute__ ((format (printf, 2, 3))) -JS_ThrowReferenceError (JSContext *ctx, const char *fmt, ...) { - JSValue val; - va_list ap; - - va_start (ap, fmt); - val = JS_ThrowError (ctx, JS_REFERENCE_ERROR, fmt, ap); - va_end (ap); - return val; -} - -JSValue __attribute__ ((format (printf, 2, 3))) -JS_ThrowRangeError (JSContext *ctx, const char *fmt, ...) { - JSValue val; - va_list ap; - - va_start (ap, fmt); - val = JS_ThrowError (ctx, JS_RANGE_ERROR, fmt, ap); - va_end (ap); - return val; -} - -JSValue __attribute__ ((format (printf, 2, 3))) -JS_ThrowInternalError (JSContext *ctx, const char *fmt, ...) { - JSValue val; - va_list ap; - - va_start (ap, fmt); - val = JS_ThrowError (ctx, JS_INTERNAL_ERROR, fmt, ap); - va_end (ap); - return val; -} - -JSValue JS_ThrowOutOfMemory (JSContext *ctx) { - /* Simplified: no re-entry guard needed with copying GC */ - JS_ThrowInternalError (ctx, "out of memory"); - return JS_EXCEPTION; -} - -static JSValue JS_ThrowStackOverflow (JSContext *ctx) { - return JS_ThrowInternalError (ctx, "stack overflow"); -} - -static JSValue JS_ThrowTypeErrorNotAnObject (JSContext *ctx) { - return JS_ThrowTypeError (ctx, "not an object"); -} - -static JSValue JS_ThrowReferenceErrorUninitialized (JSContext *ctx, - JSValue name) { - char buf[KEY_GET_STR_BUF_SIZE]; - return JS_ThrowReferenceError ( - ctx, "%s is not initialized", JS_IsNull (name) ? "lexical variable" : JS_KeyGetStr (ctx, buf, sizeof (buf), name)); -} - -static JSValue JS_ThrowReferenceErrorUninitialized2 (JSContext *ctx, - JSFunctionBytecode *b, - int idx, - BOOL is_ref) { - JSValue name = JS_NULL; - if (is_ref) { - name = b->closure_var[idx].var_name; - } else { - /* not present if the function is stripped and contains no eval() */ - if (b->vardefs) name = b->vardefs[b->arg_count + idx].var_name; - } - return JS_ThrowReferenceErrorUninitialized (ctx, name); -} - -static JSValue JS_ThrowTypeErrorInvalidClass (JSContext *ctx, int class_id) { - const char *name = ctx->class_array[class_id].class_name; - return JS_ThrowTypeError (ctx, "%s object expected", name ? name : "unknown"); -} - -static void JS_ThrowInterrupted (JSContext *ctx) { - JS_ThrowInternalError (ctx, "interrupted"); - JS_SetUncatchableException (ctx, TRUE); -} - -static no_inline __exception int __js_poll_interrupts (JSContext *ctx) { - ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; - if (ctx->interrupt_handler) { - if (ctx->interrupt_handler (ctx->rt, ctx->interrupt_opaque)) { - JS_ThrowInterrupted (ctx); - return -1; - } - } - return 0; -} - -static inline __exception int js_poll_interrupts (JSContext *ctx) { - if (unlikely (--ctx->interrupt_counter <= 0)) { - return __js_poll_interrupts (ctx); - } else { - return 0; - } -} - -/* Return an Object, JS_NULL or JS_EXCEPTION in case of exotic object. */ -JSValue JS_GetPrototype (JSContext *ctx, JSValue obj) { - JSValue val; - if (JS_IsRecord (obj)) { - JSRecord *p; - p = JS_VALUE_GET_OBJ (obj); - p = JS_OBJ_GET_PROTO (p); - if (!p) - val = JS_NULL; - else - val = JS_MKPTR (p); - } else { - /* Primitives have no prototype */ - val = JS_NULL; - } - return val; -} - -/* Get property from object using JSRecord-based lookup */ -JSValue JS_GetProperty (JSContext *ctx, JSValue obj, JSValue prop) { - if (JS_IsNull (obj)) return JS_NULL; - if (JS_IsException (obj)) return JS_EXCEPTION; - - if (unlikely (!JS_IsRecord (obj))) { - /* Primitives have no properties */ - return JS_NULL; - } - - /* All objects are JSRecords now */ - JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (obj); - return rec_get (ctx, rec, prop); -} - -/* GC-SAFE: Collects keys to stack buffer before any allocation. - Returns a JSValue array of text keys. */ -JSValue JS_GetOwnPropertyNames (JSContext *ctx, JSValue obj) { - uint32_t mask, count, i; - - if (!JS_IsRecord (obj)) { - JS_ThrowTypeErrorNotAnObject (ctx); - return JS_EXCEPTION; - } - - /* Reading slots is GC-safe - no allocation */ - JSRecord *rec = JS_VALUE_GET_OBJ (obj); - mask = (uint32_t)objhdr_cap56 (rec->mist_hdr); - - /* Count text keys first */ - count = 0; - for (i = 1; i <= mask; i++) { - if (JS_IsText (rec->slots[i].key)) count++; - } - - if (count == 0) return JS_NewArrayLen (ctx, 0); - - /* Collect keys into stack buffer (JSValues are just uint64_t) */ - JSValue *keys = alloca (count * sizeof (JSValue)); - uint32_t idx = 0; - for (i = 1; i <= mask; i++) { - JSValue k = rec->slots[i].key; - if (JS_IsText (k)) keys[idx++] = k; - } - - /* Now allocate and fill - GC point, but keys are on stack */ - JSValue arr = JS_NewArrayLen (ctx, count); - if (JS_IsException (arr)) return JS_EXCEPTION; - - for (i = 0; i < count; i++) { - JS_SetPropertyUint32 (ctx, arr, i, keys[i]); - } - - return arr; -} - -/* Return -1 if exception, - FALSE if the property does not exist, TRUE if it exists. If TRUE is - returned, the property descriptor 'desc' is filled present. - Now uses JSRecord-based lookup. */ -static int JS_GetOwnPropertyInternal (JSContext *ctx, - JSValue *desc, - JSRecord *p, - JSValue prop) { - JSRecord *rec = (JSRecord *)p; - int slot = rec_find_slot (rec, prop); - - if (slot > 0) { - if (desc) - *desc = rec->slots[slot].val; - return TRUE; - } - return FALSE; -} - -int JS_GetOwnProperty (JSContext *ctx, JSValue *desc, JSValue obj, JSValue prop) { - if (!JS_IsRecord (obj)) { - JS_ThrowTypeErrorNotAnObject (ctx); - return -1; - } - return JS_GetOwnPropertyInternal (ctx, desc, JS_VALUE_GET_OBJ (obj), prop); -} - -/* GC-SAFE: Only calls rec_find_slot and reads prototype pointers. - return -1 if exception otherwise TRUE or FALSE */ -int JS_HasProperty (JSContext *ctx, JSValue obj, JSValue prop) { - JSRecord *p; - int ret; - if (unlikely (!JS_IsRecord (obj))) return FALSE; - p = JS_VALUE_GET_OBJ (obj); - for (;;) { - ret = JS_GetOwnPropertyInternal (ctx, NULL, p, prop); - if (ret != 0) return ret; - p = p->proto; /* Direct pointer chase is safe - no allocation */ - if (!p) break; - } - return FALSE; -} - -static uint32_t js_string_get_length (JSValue val) { - if (JS_IsPtr (val)) { - void *ptr = JS_VALUE_GET_PTR (val); - /* Check objhdr_t at offset 8 for type */ - objhdr_t hdr = *(objhdr_t *)ptr; - if (objhdr_type (hdr) == OBJ_TEXT) { - /* String (JSText or JSText) */ - return (uint32_t)objhdr_cap56 (hdr); - } - return 0; - } else if (MIST_IsImmediateASCII (val)) { - return MIST_GetImmediateASCIILen (val); - } else { - return 0; - } -} - -static JSValue JS_GetPropertyValue (JSContext *ctx, JSValue this_obj, JSValue prop) { - JSValue ret; - uint32_t prop_tag = JS_VALUE_GET_TAG (prop); - - if (JS_IsNull (this_obj)) { - return JS_NULL; - } - - if (prop_tag == JS_TAG_INT) { - int idx = JS_VALUE_GET_INT (prop); - return JS_GetPropertyNumber (ctx, this_obj, idx); - } - - if (prop_tag == JS_TAG_SHORT_FLOAT) { - double d = JS_VALUE_GET_FLOAT64 (prop); - uint32_t idx = (uint32_t)d; - if (d != (double)idx) return JS_NULL; - return JS_GetPropertyNumber (ctx, this_obj, idx); - } - - /* Check for string property (immediate or heap) */ - if (JS_IsText (prop)) { - /* Intrinsic arrays don't support string keys */ - if (JS_IsArray (this_obj)) { - return JS_NULL; - } - /* Create an interned key from the string */ - JSValue key = js_key_from_string (ctx, prop); - ret = JS_GetProperty (ctx, this_obj, key); - /* key is interned or immediate, no need to free */ - return ret; - } - - /* Handle object keys directly via objkey map */ - if (JS_IsRecord (prop)) { - /* Intrinsic arrays don't support object keys */ - if (!JS_IsRecord (this_obj)) { - return JS_NULL; - } - JSRecord *rec = JS_VALUE_GET_RECORD (this_obj); - JSValue val = rec_get (ctx, rec, prop); - return val; - } - - /* Unknown type -> null */ - return JS_NULL; -} - -JSValue JS_SetPropertyNumber (JSContext *js, JSValue obj, int idx, JSValue val) { - if (!JS_IsArray (obj)) { - return JS_ThrowInternalError (js, - "cannot set with a number on a non array"); - } - - if (idx < 0) { - return JS_ThrowRangeError (js, "array index out of bounds"); - } - - /* Root obj since js_intrinsic_array_set may trigger GC during array grow */ - JSGCRef obj_ref; - JS_PushGCRef (js, &obj_ref); - obj_ref.val = obj; - if (js_intrinsic_array_set (js, &obj_ref.val, (word_t)idx, val) < 0) { - JS_PopGCRef (js, &obj_ref); - return JS_EXCEPTION; - } - JS_PopGCRef (js, &obj_ref); - return val; -} - -JSValue JS_GetPropertyNumber (JSContext *js, JSValue obj, int idx) { - if (JS_IsArray (obj)) { - JSArray *a = JS_VALUE_GET_ARRAY (obj); - int len = a->len; - if (idx < 0 || idx >= len) { return JS_NULL; } - return a->values[idx]; - } - - if (JS_IsText (obj)) { - uint32_t len = js_string_get_length (obj); - if (idx < 0 || (uint32_t)idx >= len) { return JS_NULL; } - return js_sub_string (js, JS_VALUE_GET_STRING (obj), idx, idx + 1); - } - - return JS_NULL; -} - -JSValue JS_GetPropertyUint32 (JSContext *ctx, JSValue this_obj, uint32_t idx) { - return JS_GetPropertyNumber (ctx, this_obj, idx); -} - -static JSValue JS_GetPropertyInt64 (JSContext *ctx, JSValue obj, int64_t idx) { - return JS_GetPropertyNumber (ctx, obj, idx); -} - -JSValue JS_GetPropertyStr (JSContext *ctx, JSValue this_obj, const char *prop) { - if (JS_VALUE_GET_TAG (this_obj) != JS_TAG_PTR) return JS_NULL; - - size_t len = strlen (prop); - JSValue key; - JSValue ret; - JSGCRef obj_ref; - - JS_PushGCRef (ctx, &obj_ref); - obj_ref.val = this_obj; - - /* Try immediate ASCII first */ - if (len <= MIST_ASCII_MAX_LEN) { - key = MIST_TryNewImmediateASCII (prop, len); - if (JS_IsNull (key)) { key = JS_NewStringLen (ctx, prop, len); } - } else { - key = JS_NewStringLen (ctx, prop, len); - } - if (JS_IsException (key)) { - JS_PopGCRef (ctx, &obj_ref); - return JS_EXCEPTION; - } - ret = JS_GetProperty (ctx, obj_ref.val, key); - JS_PopGCRef (ctx, &obj_ref); - return ret; -} - -/* JS_Invoke - invoke a method on an object by name */ -static JSValue JS_Invoke (JSContext *ctx, JSValue this_val, JSValue method, int argc, JSValue *argv) { - JSGCRef this_ref; - JS_PushGCRef (ctx, &this_ref); - this_ref.val = this_val; - JSValue func = JS_GetProperty (ctx, this_ref.val, method); - if (JS_IsException (func)) { JS_PopGCRef (ctx, &this_ref); return JS_EXCEPTION; } - if (!JS_IsFunction (func)) { - JS_PopGCRef (ctx, &this_ref); - return JS_NULL; /* Method not found or not callable */ - } - JSValue ret = JS_Call (ctx, func, this_ref.val, argc, argv); - JS_PopGCRef (ctx, &this_ref); - return ret; -} - -/* GC-SAFE: May trigger GC if record needs to resize */ -int JS_SetProperty (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val) { - if (!JS_IsRecord (this_obj)) { - if (JS_IsNull (this_obj)) { - JS_ThrowTypeError (ctx, "cannot set property of null"); - } else { - JS_ThrowTypeError (ctx, "cannot set property on a primitive"); - } - return -1; - } - - /* All objects are now records - use record set */ - JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (this_obj); - - if (unlikely (obj_is_stone (rec))) { - JS_ThrowTypeError (ctx, "object is stone"); - return -1; - } - - /* Use a local copy that rec_set_own can update if resize happens */ - JSValue obj = this_obj; - return rec_set_own (ctx, &obj, prop, val); -} - -int JS_SetPropertyUint32 (JSContext *ctx, JSValue this_obj, uint32_t idx, JSValue val) { - JSValue ret = JS_SetPropertyNumber (ctx, (JSValue)this_obj, (int)idx, val); - if (JS_IsException (ret)) return -1; - return 0; -} - -int JS_SetPropertyInt64 (JSContext *ctx, JSValue this_obj, int64_t idx, JSValue val) { - if (idx < INT32_MIN || idx > INT32_MAX) { - JS_ThrowRangeError (ctx, "array index out of bounds"); - return -1; - } - JSValue ret = JS_SetPropertyNumber (ctx, (JSValue)this_obj, (int)idx, val); - if (JS_IsException (ret)) return -1; - return 0; -} - -/* GC-SAFE: Protects this_obj and val in case key creation triggers GC */ -int JS_SetPropertyStr (JSContext *ctx, JSValue this_obj, const char *prop, JSValue val) { - /* Protect this_obj and val in case key creation triggers GC */ - JSGCRef obj_ref, val_ref; - JS_AddGCRef (ctx, &obj_ref); - JS_AddGCRef (ctx, &val_ref); - obj_ref.val = this_obj; - val_ref.val = val; - - /* Create JSValue key from string - use js_key_new for interned stone keys */ - JSValue key = js_key_new (ctx, prop); - if (JS_IsException (key)) { - JS_DeleteGCRef (ctx, &val_ref); - JS_DeleteGCRef (ctx, &obj_ref); - return -1; - } - - int ret = JS_SetProperty (ctx, obj_ref.val, key, val_ref.val); - JS_DeleteGCRef (ctx, &val_ref); - JS_DeleteGCRef (ctx, &obj_ref); - return ret; -} - -/* Set property with JSValue prop/key - handles int, string, object keys */ -static int JS_SetPropertyValue (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val) { - uint32_t prop_tag = JS_VALUE_GET_TAG (prop); - - if (prop_tag == JS_TAG_INT) { - int idx = JS_VALUE_GET_INT (prop); - JSValue ret = JS_SetPropertyNumber (ctx, this_obj, idx, val); - if (JS_IsException (ret)) return -1; - return 0; - } - - if (JS_IsText (prop)) { - JSValue key = js_key_from_string (ctx, prop); - return JS_SetProperty (ctx, this_obj, key, val); - } - - if (JS_IsRecord (prop)) { - return JS_SetProperty (ctx, this_obj, prop, val); - } - - return -1; -} - -/* Property access with JSValue key - supports object keys directly */ -JSValue JS_GetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key) { - if (JS_IsRecord (key)) { - if (!JS_IsRecord (this_obj)) return JS_NULL; - JSRecord *rec = JS_VALUE_GET_RECORD (this_obj); - return rec_get (ctx, rec, key); - } - - /* For string keys, create an interned key and use JS_GetProperty */ - if (JS_IsText (key)) { - JSValue prop_key = js_key_from_string (ctx, key); - return JS_GetProperty (ctx, this_obj, prop_key); - } - - /* For other types, try to use the value directly as a key */ - return JS_GetProperty (ctx, this_obj, key); -} - -/* CAUTION: rec_set_own is NOT GC-safe if rec_resize is implemented. - Currently safe because rec_resize always fails. - When resize is implemented, rec pointer may become stale. */ -int JS_SetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key, JSValue val) { - if (JS_IsRecord (key)) { - if (!JS_IsRecord (this_obj)) { - JS_ThrowTypeError (ctx, "cannot set property on this value"); - return -1; - } - JSRecord *rec = JS_VALUE_GET_RECORD (this_obj); - if (obj_is_stone (rec)) { - JS_ThrowTypeError (ctx, "cannot modify frozen object"); - return -1; - } - return rec_set_own (ctx, rec, key, val); - } - - /* For string keys, create an interned key */ - if (JS_IsText (key)) { - JSValue prop_key = js_key_from_string (ctx, key); - return JS_SetPropertyInternal (ctx, this_obj, prop_key, val); - } - - /* For other types, use the key directly */ - return JS_SetPropertyInternal (ctx, this_obj, key, val); -} - -/* GC-SAFE for record keys (no allocations). - String keys call js_key_from_string then JS_HasProperty which re-chases. */ -int JS_HasPropertyKey (JSContext *ctx, JSValue obj, JSValue key) { - if (JS_IsRecord (key)) { - if (!JS_IsRecord (obj)) return FALSE; - JSRecord *rec = JS_VALUE_GET_RECORD (obj); - /* Check own and prototype chain */ - while (rec) { - if (rec_find_slot (rec, key) > 0) return TRUE; - rec = rec->proto; - } - return FALSE; - } - - /* For string keys, create an interned key */ - if (JS_IsText (key)) { - JSValue prop_key = js_key_from_string (ctx, key); - return JS_HasProperty (ctx, obj, prop_key); - } - - /* For other types, use directly */ - return JS_HasProperty (ctx, obj, key); -} - -/* GC-SAFE: Only calls rec_find_slot and modifies slots directly */ -int JS_DeletePropertyKey (JSContext *ctx, JSValue obj, JSValue key) { - if (JS_IsRecord (key)) { - if (!JS_IsRecord (obj)) return FALSE; - JSRecord *rec = JS_VALUE_GET_RECORD (obj); - if (obj_is_stone (rec)) { - JS_ThrowTypeError (ctx, "cannot modify frozen object"); - return -1; - } - int slot = rec_find_slot (rec, key); - if (slot <= 0) return FALSE; - /* Delete by marking as tombstone */ - rec->slots[slot].key = JS_EXCEPTION; /* tombstone */ - rec->slots[slot].val = JS_NULL; - rec->len--; - return TRUE; - } - - /* For string keys, create an interned key */ - if (JS_IsText (key)) { - JSValue prop_key = js_key_from_string (ctx, key); - return JS_DeleteProperty (ctx, obj, prop_key); - } - - /* For other types, use directly */ - return JS_DeleteProperty (ctx, obj, key); -} - -/* compute the property flags. For each flag: (JS_PROP_HAS_x forces - it, otherwise def_flags is used) - Note: makes assumption about the bit pattern of the flags -*/ - -/* return TRUE if 'obj' has a non empty 'name' string */ -static BOOL js_object_has_name (JSContext *ctx, JSValue obj) { - if (JS_VALUE_GET_TAG (obj) != JS_TAG_PTR) return FALSE; - JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (obj); - JSValue name_key = MIST_TryNewImmediateASCII ("name", 4); - int slot = rec_find_slot (rec, name_key); - if (slot <= 0) return FALSE; - JSValue val = rec->slots[slot].val; - if (!JS_IsText (val)) return TRUE; /* has name but it's not a string = truthy */ - return (js_string_value_len (val) != 0); -} - -static int JS_DefineObjectName (JSContext *ctx, JSValue obj, JSValue name) { - if (!JS_IsNull (name) && JS_IsObject (obj) - && !js_object_has_name (ctx, obj)) { - JSValue name_key = MIST_TryNewImmediateASCII ("name", 4); - if (JS_SetPropertyInternal (ctx, obj, name_key, name) - < 0) - return -1; - } - return 0; -} - -static int JS_DefineObjectNameComputed (JSContext *ctx, JSValue obj, JSValue str) { - if (JS_IsObject (obj) && !js_object_has_name (ctx, obj)) { - JSValue name_key = MIST_TryNewImmediateASCII ("name", 4); - if (JS_SetPropertyInternal (ctx, obj, name_key, str) - < 0) - return -1; - } - return 0; -} - -#define DEFINE_GLOBAL_LEX_VAR (1 << 7) -#define DEFINE_GLOBAL_FUNC_VAR (1 << 6) - -static JSValue JS_ThrowSyntaxErrorVarRedeclaration (JSContext *ctx, - JSValue prop) { - char buf[KEY_GET_STR_BUF_SIZE]; - return JS_ThrowSyntaxError (ctx, "redeclaration of '%s'", JS_KeyGetStr (ctx, buf, sizeof (buf), prop)); -} - -int JS_DeleteProperty (JSContext *ctx, JSValue obj, JSValue prop) { - JSRecord *rec; - int slot; - - /* Arrays do not support property deletion */ - if (JS_IsArray (obj)) { - JS_ThrowTypeError (ctx, "cannot delete array element"); - return -1; - } - - if (JS_VALUE_GET_TAG (obj) != JS_TAG_PTR) { - JS_ThrowTypeErrorNotAnObject (ctx); - return -1; - } - - rec = (JSRecord *)JS_VALUE_GET_OBJ (obj); - if (obj_is_stone (rec)) { - JS_ThrowTypeError (ctx, "cannot delete property of stone object"); - return -1; - } - - slot = rec_find_slot (rec, prop); - if (slot > 0) { - /* Delete by marking as tombstone */ - rec->slots[slot].key = JS_EXCEPTION; /* tombstone */ - rec->slots[slot].val = JS_NULL; - rec->len--; - /* tombs tracking removed - not needed with copying GC */ - return TRUE; - } - return TRUE; /* property not found = deletion succeeded */ -} - -BOOL JS_IsCFunction (JSValue val, JSCFunction *func, int magic) { - JSFunction *f; - if (!JS_IsFunction (val)) return FALSE; - f = JS_VALUE_GET_FUNCTION (val); - if (f->kind == JS_FUNC_KIND_C) - return (f->u.cfunc.c_function.generic == func - && f->u.cfunc.magic == magic); - else - return FALSE; -} - -BOOL JS_IsError (JSContext *ctx, JSValue val) { - JSRecord *p; - if (JS_VALUE_GET_TAG (val) != JS_TAG_PTR) return FALSE; - p = JS_VALUE_GET_OBJ (val); - return (REC_GET_CLASS_ID(p) == JS_CLASS_ERROR); -} - -/* must be called after JS_Throw() - stubbed out, uncatchable not implemented */ -void JS_SetUncatchableException (JSContext *ctx, JS_BOOL flag) { - (void)ctx; (void)flag; - /* uncatchable exception flag not supported with copying GC */ -} - -void JS_SetOpaque (JSValue obj, void *opaque) { - JSRecord *p; - if (JS_VALUE_GET_TAG (obj) == JS_TAG_PTR) { - p = JS_VALUE_GET_OBJ (obj); - REC_SET_OPAQUE(p, opaque); - } -} - -/* return NULL if not an object of class class_id */ -void *JS_GetOpaque (JSValue obj, JSClassID class_id) { - JSRecord *p; - if (JS_VALUE_GET_TAG (obj) != JS_TAG_PTR) return NULL; - p = JS_VALUE_GET_OBJ (obj); - if (REC_GET_CLASS_ID(p) != class_id) return NULL; - return REC_GET_OPAQUE(p); -} - -void *JS_GetOpaque2 (JSContext *ctx, JSValue obj, JSClassID class_id) { - void *p = JS_GetOpaque (obj, class_id); - if (unlikely (!p)) { JS_ThrowTypeErrorInvalidClass (ctx, class_id); } - return p; -} - -void *JS_GetAnyOpaque (JSValue obj, JSClassID *class_id) { - JSRecord *p; - if (!JS_IsRecord (obj)) { - *class_id = 0; - return NULL; - } - p = JS_VALUE_GET_OBJ (obj); - *class_id = REC_GET_CLASS_ID(p); - return REC_GET_OPAQUE(p); -} - -int JS_ToBool (JSContext *ctx, JSValue val) { - uint32_t tag = JS_VALUE_GET_TAG (val); - - /* Check for pointer types first (new tagging system) */ - if (JS_IsPtr (val)) { - void *ptr = JS_VALUE_GET_PTR (val); - objhdr_t hdr = *(objhdr_t *)ptr; - if (objhdr_type (hdr) == OBJ_TEXT) { - /* String (JSText or JSText) - truthy if non-empty */ - BOOL ret = objhdr_cap56 (hdr) != 0; - return ret; - } - /* Objects (record, array, function) are truthy */ - return 1; - } - - switch (tag) { - case JS_TAG_INT: - return JS_VALUE_GET_INT (val) != 0; - case JS_TAG_BOOL: - return JS_VALUE_GET_BOOL (val); - case JS_TAG_NULL: - return 0; - case JS_TAG_EXCEPTION: - return -1; - case JS_TAG_STRING_IMM: { - BOOL ret = MIST_GetImmediateASCIILen (val) != 0; - return ret; - } - default: - if (JS_TAG_IS_FLOAT64 (tag)) { - double d = JS_VALUE_GET_FLOAT64 (val); - return d != 0; /* NaN impossible in short floats */ - } else { - return TRUE; - } - } -} - -static inline int to_digit (int c) { - if (c >= '0' && c <= '9') - return c - '0'; - else if (c >= 'A' && c <= 'Z') - return c - 'A' + 10; - else if (c >= 'a' && c <= 'z') - return c - 'a' + 10; - else - return 36; -} - -#define ATOD_INT_ONLY (1 << 0) -/* accept Oo and Ob prefixes in addition to 0x prefix if radix = 0 */ -#define ATOD_ACCEPT_BIN_OCT (1 << 2) -/* accept O prefix as octal if radix == 0 and properly formed (Annex B) */ -#define ATOD_ACCEPT_LEGACY_OCTAL (1 << 4) -/* accept _ between digits as a digit separator */ -#define ATOD_ACCEPT_UNDERSCORES (1 << 5) -/* allow a suffix to override the type */ -#define ATOD_ACCEPT_SUFFIX (1 << 6) -/* default type */ -#define ATOD_TYPE_MASK (3 << 7) -#define ATOD_TYPE_FLOAT64 (0 << 7) -#define ATOD_TYPE_BIG_INT (1 << 7) -/* accept -0x1 */ -#define ATOD_ACCEPT_PREFIX_AFTER_SIGN (1 << 10) - -/* return an exception in case of memory error. Return JS_NAN if - invalid syntax */ -/* XXX: directly use js_atod() */ -static JSValue js_atof (JSContext *ctx, const char *str, const char **pp, int radix, int flags) { - const char *p, *p_start; - int sep, is_neg; - BOOL is_float; - int atod_type = flags & ATOD_TYPE_MASK; - char buf1[64], *buf; - int i, j, len; - BOOL buf_allocated = FALSE; - JSValue val; - JSATODTempMem atod_mem; - - /* optional separator between digits */ - sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; - - p = str; - p_start = p; - is_neg = 0; - if (p[0] == '+') { - p++; - p_start++; - if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) goto no_radix_prefix; - } else if (p[0] == '-') { - p++; - p_start++; - is_neg = 1; - if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) goto no_radix_prefix; - } - if (p[0] == '0') { - if ((p[1] == 'x' || p[1] == 'X') && (radix == 0 || radix == 16)) { - p += 2; - radix = 16; - } else if ((p[1] == 'o' || p[1] == 'O') && radix == 0 - && (flags & ATOD_ACCEPT_BIN_OCT)) { - p += 2; - radix = 8; - } else if ((p[1] == 'b' || p[1] == 'B') && radix == 0 - && (flags & ATOD_ACCEPT_BIN_OCT)) { - p += 2; - radix = 2; - } else if ((p[1] >= '0' && p[1] <= '9') && radix == 0 - && (flags & ATOD_ACCEPT_LEGACY_OCTAL)) { - int i; - sep = 256; - for (i = 1; (p[i] >= '0' && p[i] <= '7'); i++) - continue; - if (p[i] == '8' || p[i] == '9') goto no_prefix; - p += 1; - radix = 8; - } else { - goto no_prefix; - } - /* there must be a digit after the prefix */ - if (to_digit ((uint8_t)*p) >= radix) goto fail; - no_prefix:; - } else { - no_radix_prefix: - if (!(flags & ATOD_INT_ONLY) && (atod_type == ATOD_TYPE_FLOAT64) - && strstart (p, "Infinity", &p)) { - double d = 1.0 / 0.0; - if (is_neg) d = -d; - val = JS_NewFloat64 (ctx, d); - goto done; - } - } - if (radix == 0) radix = 10; - is_float = FALSE; - p_start = p; - while (to_digit ((uint8_t)*p) < radix - || (*p == sep && (radix != 10 || p != p_start + 1 || p[-1] != '0') - && to_digit ((uint8_t)p[1]) < radix)) { - p++; - } - if (!(flags & ATOD_INT_ONLY)) { - if (*p == '.' && (p > p_start || to_digit ((uint8_t)p[1]) < radix)) { - is_float = TRUE; - p++; - if (*p == sep) goto fail; - while (to_digit ((uint8_t)*p) < radix - || (*p == sep && to_digit ((uint8_t)p[1]) < radix)) - p++; - } - if (p > p_start - && (((*p == 'e' || *p == 'E') && radix == 10) - || ((*p == 'p' || *p == 'P') - && (radix == 2 || radix == 8 || radix == 16)))) { - const char *p1 = p + 1; - is_float = TRUE; - if (*p1 == '+') { - p1++; - } else if (*p1 == '-') { - p1++; - } - if (is_digit ((uint8_t)*p1)) { - p = p1 + 1; - while (is_digit ((uint8_t)*p) - || (*p == sep && is_digit ((uint8_t)p[1]))) - p++; - } - } - } - if (p == p_start) goto fail; - - buf = buf1; - buf_allocated = FALSE; - len = p - p_start; - if (unlikely ((len + 2) > sizeof (buf1))) { - buf = js_malloc_rt (len + 2); /* no exception raised */ - if (!buf) goto mem_error; - buf_allocated = TRUE; - } - /* remove the separators and the radix prefixes */ - j = 0; - if (is_neg) buf[j++] = '-'; - for (i = 0; i < len; i++) { - if (p_start[i] != '_') buf[j++] = p_start[i]; - } - buf[j] = '\0'; - - if (flags & ATOD_ACCEPT_SUFFIX) { - if (*p == 'n') { - p++; - atod_type = ATOD_TYPE_BIG_INT; - } else { - if (is_float && radix != 10) goto fail; - } - } else { - if (atod_type == ATOD_TYPE_FLOAT64) { - if (is_float && radix != 10) goto fail; - } - } - - switch (atod_type) { - case ATOD_TYPE_FLOAT64: { - double d; - d = js_atod (buf, NULL, radix, is_float ? 0 : JS_ATOD_INT_ONLY, &atod_mem); - /* return int or float64 */ - val = JS_NewFloat64 (ctx, d); - } break; - default: - abort (); - } - -done: - if (buf_allocated) js_free_rt (buf); - if (pp) *pp = p; - return val; -fail: - val = JS_NAN; - goto done; -mem_error: - val = JS_ThrowOutOfMemory (ctx); - goto done; -} - -static JSValue JS_ToNumber (JSContext *ctx, JSValue val) { - uint32_t tag; - JSValue ret; - - /* Handle pointer types (new tagging system) */ - if (JS_IsPtr (val)) { - void *ptr = JS_VALUE_GET_PTR (val); - objhdr_t hdr = *(objhdr_t *)ptr; - if (objhdr_type (hdr) == OBJ_TEXT) { - /* String */ - return JS_ThrowTypeError (ctx, "cannot convert text to a number"); - } - /* Objects */ - return JS_ThrowTypeError (ctx, "cannot convert object to number"); - } - - tag = JS_VALUE_GET_NORM_TAG (val); - switch (tag) { - case JS_TAG_SHORT_FLOAT: - case JS_TAG_INT: - case JS_TAG_EXCEPTION: - ret = val; - break; - case JS_TAG_BOOL: - case JS_TAG_NULL: - ret = JS_NewInt32 (ctx, JS_VALUE_GET_INT (val)); - break; - case JS_TAG_STRING_IMM: - return JS_ThrowTypeError (ctx, "cannot convert text to a number"); - default: - ret = JS_NAN; - break; - } - return ret; -} - -static __exception int __JS_ToFloat64 (JSContext *ctx, double *pres, JSValue val) { - double d; - uint32_t tag; - - val = JS_ToNumber (ctx, val); - if (JS_IsException (val)) goto fail; - tag = JS_VALUE_GET_NORM_TAG (val); - switch (tag) { - case JS_TAG_INT: - d = JS_VALUE_GET_INT (val); - break; - case JS_TAG_FLOAT64: - d = JS_VALUE_GET_FLOAT64 (val); - break; - default: - abort (); - } - *pres = d; - return 0; -fail: - *pres = NAN; - return -1; -} - -int JS_ToFloat64 (JSContext *ctx, double *pres, JSValue val) { - uint32_t tag; - - tag = JS_VALUE_GET_TAG (val); - if (tag == JS_TAG_INT) { - *pres = JS_VALUE_GET_INT (val); - return 0; - } else if (JS_TAG_IS_FLOAT64 (tag)) { - *pres = JS_VALUE_GET_FLOAT64 (val); - return 0; - } else { - return __JS_ToFloat64 (ctx, pres, val); - } -} - -/* Note: the integer value is satured to 32 bits */ -int JS_ToInt32Sat (JSContext *ctx, int *pres, JSValue val) { - uint32_t tag; - int ret; - -redo: - tag = JS_VALUE_GET_NORM_TAG (val); - switch (tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - ret = JS_VALUE_GET_INT (val); - break; - case JS_TAG_EXCEPTION: - *pres = 0; - return -1; - case JS_TAG_FLOAT64: { - double d = JS_VALUE_GET_FLOAT64 (val); - /* NaN impossible in short floats */ - if (d < INT32_MIN) - ret = INT32_MIN; - else if (d > INT32_MAX) - ret = INT32_MAX; - else - ret = (int)d; - } break; - default: - val = JS_ToNumber (ctx, val); - if (JS_IsException (val)) { - *pres = 0; - return -1; - } - goto redo; - } - *pres = ret; - return 0; -} - -int JS_ToInt32Clamp (JSContext *ctx, int *pres, JSValue val, int min, int max, int min_offset) { - int res = JS_ToInt32Sat (ctx, pres, val); - if (res == 0) { - if (*pres < min) { - *pres += min_offset; - if (*pres < min) *pres = min; - } else { - if (*pres > max) *pres = max; - } - } - return res; -} - -int JS_ToInt64Sat (JSContext *ctx, int64_t *pres, JSValue val) { - uint32_t tag; - -redo: - tag = JS_VALUE_GET_NORM_TAG (val); - switch (tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - *pres = JS_VALUE_GET_INT (val); - return 0; - case JS_TAG_EXCEPTION: - *pres = 0; - return -1; - case JS_TAG_FLOAT64: { - double d = JS_VALUE_GET_FLOAT64 (val); - /* NaN impossible in short floats */ - if (d < INT64_MIN) - *pres = INT64_MIN; - else if (d >= 0x1p63) /* must use INT64_MAX + 1 because INT64_MAX cannot - be exactly represented as a double */ - *pres = INT64_MAX; - else - *pres = (int64_t)d; - } - return 0; - default: - val = JS_ToNumber (ctx, val); - if (JS_IsException (val)) { - *pres = 0; - return -1; - } - goto redo; - } -} - -int JS_ToInt64Clamp (JSContext *ctx, int64_t *pres, JSValue val, int64_t min, int64_t max, int64_t neg_offset) { - int res = JS_ToInt64Sat (ctx, pres, val); - if (res == 0) { - if (*pres < 0) *pres += neg_offset; - if (*pres < min) - *pres = min; - else if (*pres > max) - *pres = max; - } - return res; -} - -/* Same as JS_ToInt32() but with a 64 bit result. Return (<0, 0) - in case of exception */ -int JS_ToInt64 (JSContext *ctx, int64_t *pres, JSValue val) { - uint32_t tag; - int64_t ret; - -redo: - tag = JS_VALUE_GET_NORM_TAG (val); - switch (tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - ret = JS_VALUE_GET_INT (val); - break; - case JS_TAG_FLOAT64: { - JSFloat64Union u; - double d; - int e; - d = JS_VALUE_GET_FLOAT64 (val); - u.d = d; - /* we avoid doing fmod(x, 2^64) */ - e = (u.u64 >> 52) & 0x7ff; - if (likely (e <= (1023 + 62))) { - /* fast case */ - ret = (int64_t)d; - } else if (e <= (1023 + 62 + 53)) { - uint64_t v; - /* remainder modulo 2^64 */ - v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); - ret = v << ((e - 1023) - 52); - /* take the sign into account */ - if (u.u64 >> 63) ret = -ret; - } else { - ret = 0; /* also handles NaN and +inf */ - } - } break; - default: - val = JS_ToNumber (ctx, val); - if (JS_IsException (val)) { - *pres = 0; - return -1; - } - goto redo; - } - *pres = ret; - return 0; -} - -/* return (<0, 0) in case of exception */ -int JS_ToInt32 (JSContext *ctx, int32_t *pres, JSValue val) { - uint32_t tag; - int32_t ret; - -redo: - tag = JS_VALUE_GET_NORM_TAG (val); - switch (tag) { - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - ret = JS_VALUE_GET_INT (val); - break; - case JS_TAG_FLOAT64: { - JSFloat64Union u; - double d; - int e; - d = JS_VALUE_GET_FLOAT64 (val); - u.d = d; - /* we avoid doing fmod(x, 2^32) */ - e = (u.u64 >> 52) & 0x7ff; - if (likely (e <= (1023 + 30))) { - /* fast case */ - ret = (int32_t)d; - } else if (e <= (1023 + 30 + 53)) { - uint64_t v; - /* remainder modulo 2^32 */ - v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); - v = v << ((e - 1023) - 52 + 32); - ret = v >> 32; - /* take the sign into account */ - if (u.u64 >> 63) ret = -ret; - } else { - ret = 0; /* also handles NaN and +inf */ - } - } break; - default: - *pres = 0; - return -1; - val = JS_ToNumber (ctx, val); - if (JS_IsException (val)) { - *pres = 0; - return -1; - } - goto redo; - } - *pres = ret; - return 0; -} - -#define MAX_SAFE_INTEGER (((int64_t)1 << 53) - 1) - -/* convert a value to a length between 0 and MAX_SAFE_INTEGER. - return -1 for exception */ -static __exception int JS_ToLength (JSContext *ctx, int64_t *plen, JSValue val) { - int res = JS_ToInt64Clamp (ctx, plen, val, 0, MAX_SAFE_INTEGER, 0); - return res; -} - -static JSValue js_dtoa2 (JSContext *ctx, double d, int radix, int n_digits, int flags) { - char static_buf[128], *buf, *tmp_buf; - int len, len_max; - JSValue res; - JSDTOATempMem dtoa_mem; - len_max = js_dtoa_max_len (d, radix, n_digits, flags); - - /* longer buffer may be used if radix != 10 */ - if (len_max > sizeof (static_buf) - 1) { - tmp_buf = js_malloc (ctx, len_max + 1); - if (!tmp_buf) return JS_EXCEPTION; - buf = tmp_buf; - } else { - tmp_buf = NULL; - buf = static_buf; - } - len = js_dtoa (buf, d, radix, n_digits, flags, &dtoa_mem); - res = js_new_string8_len (ctx, buf, len); - js_free (ctx, tmp_buf); - return res; -} - -JSValue JS_ToString (JSContext *ctx, JSValue val) { - uint32_t tag; - char buf[32]; - - /* Handle pointer types (new tagging system) */ - if (JS_IsPtr (val)) { - void *ptr = JS_VALUE_GET_PTR (val); - objhdr_t hdr = *(objhdr_t *)ptr; - uint8_t mist_type = objhdr_type (hdr); - if (mist_type == OBJ_TEXT) { - /* String - return as-is */ - return val; - } - /* Objects (record, array, function) */ - return JS_KEY_true; - } - - tag = JS_VALUE_GET_NORM_TAG (val); - switch (tag) { - case JS_TAG_STRING_IMM: - return val; - case JS_TAG_INT: { - size_t len; - len = i32toa (buf, JS_VALUE_GET_INT (val)); - return js_new_string8_len (ctx, buf, len); - } break; - case JS_TAG_BOOL: - return JS_VALUE_GET_BOOL (val) ? JS_KEY_true : JS_KEY_false; - case JS_TAG_NULL: - return JS_KEY_null; - case JS_TAG_EXCEPTION: - return JS_EXCEPTION; - case JS_TAG_SHORT_FLOAT: - return js_dtoa2 (ctx, JS_VALUE_GET_FLOAT64 (val), 10, 0, JS_DTOA_FORMAT_FREE); - default: - return js_new_string8 (ctx, "[unsupported type]"); - } -} - -static JSValue JS_ToStringCheckObject (JSContext *ctx, JSValue val) { - uint32_t tag = JS_VALUE_GET_TAG (val); - if (tag == JS_TAG_NULL) return JS_ThrowTypeError (ctx, "null is forbidden"); - return JS_ToString (ctx, val); -} - -static JSValue JS_ToQuotedString (JSContext *ctx, JSValue val1) { - JSValue val; - int i, len; - uint32_t c; - JSText *b; - char buf[16]; - - val = JS_ToStringCheckObject (ctx, val1); - if (JS_IsException (val)) return val; - - /* Use js_string_value_len to handle both immediate and heap strings */ - len = js_string_value_len (val); - - b = pretext_init (ctx, len + 2); - if (!b) goto fail; - - b = pretext_putc (ctx, b, '\"'); - if (!b) goto fail; - for (i = 0; i < len; i++) { - c = js_string_value_get (val, i); - switch (c) { - case '\t': - c = 't'; - goto quote; - case '\r': - c = 'r'; - goto quote; - case '\n': - c = 'n'; - goto quote; - case '\b': - c = 'b'; - goto quote; - case '\f': - c = 'f'; - goto quote; - case '\"': - case '\\': - quote: - b = pretext_putc (ctx, b, '\\'); - if (!b) goto fail; - b = pretext_putc (ctx, b, c); - if (!b) goto fail; - break; - default: - if (c < 32 || is_surrogate (c)) { - snprintf (buf, sizeof (buf), "\\u%04x", c); - b = pretext_puts8 (ctx, b, buf); - if (!b) goto fail; - } else { - b = pretext_putc (ctx, b, c); - if (!b) goto fail; - } - break; - } - } - b = pretext_putc (ctx, b, '\"'); - if (!b) goto fail; - return pretext_end (ctx, b); -fail: - return JS_EXCEPTION; -} - -#define JS_PRINT_MAX_DEPTH 8 - -typedef struct { - JSRuntime *rt; - JSContext *ctx; /* may be NULL */ - JSPrintValueOptions options; - JSPrintValueWrite *write_func; - void *write_opaque; - int level; - JSRecord *print_stack[JS_PRINT_MAX_DEPTH]; /* level values */ -} JSPrintValueState; - -static void js_print_value (JSPrintValueState *s, JSValue val); - -static void js_putc (JSPrintValueState *s, char c) { - s->write_func (s->write_opaque, &c, 1); -} - -static void js_puts (JSPrintValueState *s, const char *str) { - s->write_func (s->write_opaque, str, strlen (str)); -} - -static void __attribute__ ((format (printf, 2, 3))) -js_printf (JSPrintValueState *s, const char *fmt, ...) { - va_list ap; - char buf[256]; - - va_start (ap, fmt); - vsnprintf (buf, sizeof (buf), fmt, ap); - va_end (ap); - s->write_func (s->write_opaque, buf, strlen (buf)); -} - -static void js_print_float64 (JSPrintValueState *s, double d) { - JSDTOATempMem dtoa_mem; - char buf[32]; - int len; - len = js_dtoa (buf, d, 10, 0, JS_DTOA_FORMAT_FREE | JS_DTOA_MINUS_ZERO, &dtoa_mem); - s->write_func (s->write_opaque, buf, len); -} - -static void js_dump_char (JSPrintValueState *s, int c, int sep) { - if (c == sep || c == '\\') { - js_putc (s, '\\'); - js_putc (s, c); - } else if (c >= ' ' && c <= 126) { - js_putc (s, c); - } else if (c == '\n') { - js_putc (s, '\\'); - js_putc (s, 'n'); - } else { - js_printf (s, "\\u%04x", c); - } -} - -static void js_print_string_rec (JSPrintValueState *s, JSValue val, int sep, uint32_t pos) { - if (MIST_IsImmediateASCII (val)) { - /* Immediate ASCII string */ - int len = MIST_GetImmediateASCIILen (val); - if (pos < s->options.max_string_length) { - uint32_t i, l; - l = min_uint32 (len, s->options.max_string_length - pos); - for (i = 0; i < l; i++) { - js_dump_char (s, MIST_GetImmediateASCIIChar (val, i), sep); - } - } - } else if (JS_IsPtr (val) && objhdr_type (*(objhdr_t *)JS_VALUE_GET_PTR (val)) == OBJ_TEXT) { - /* Heap text (JSText) */ - JSText *p = (JSText *)JS_VALUE_GET_PTR (val); - uint32_t i, len; - if (pos < s->options.max_string_length) { - len = min_uint32 ((uint32_t)JSText_len (p), s->options.max_string_length - pos); - for (i = 0; i < len; i++) { - js_dump_char (s, string_get (p, i), sep); - } - } - } else { - js_printf (s, "", (int)JS_VALUE_GET_TAG (val)); - } -} - -static void js_print_string (JSPrintValueState *s, JSValue val) { - int sep = '\"'; - js_putc (s, sep); - js_print_string_rec (s, val, sep, 0); - js_putc (s, sep); - if (js_string_get_length (val) > s->options.max_string_length) { - uint32_t n = js_string_get_length (val) - s->options.max_string_length; - js_printf (s, "... %u more character%s", n, n > 1 ? "s" : ""); - } -} - -static void js_print_raw_string2 (JSPrintValueState *s, JSValue val, BOOL remove_last_lf) { - const char *cstr; - size_t len; - cstr = JS_ToCStringLen (s->ctx, &len, val); - if (cstr) { - if (remove_last_lf && len > 0 && cstr[len - 1] == '\n') len--; - s->write_func (s->write_opaque, cstr, len); - JS_FreeCString (s->ctx, cstr); - } -} - -static void js_print_raw_string (JSPrintValueState *s, JSValue val) { - js_print_raw_string2 (s, val, FALSE); -} - -static void js_print_comma (JSPrintValueState *s, int *pcomma_state) { - switch (*pcomma_state) { - case 0: - break; - case 1: - js_printf (s, ", "); - break; - case 2: - js_printf (s, " { "); - break; - } - *pcomma_state = 1; -} - -static void js_print_more_items (JSPrintValueState *s, int *pcomma_state, uint32_t n) { - js_print_comma (s, pcomma_state); - js_printf (s, "... %u more item%s", n, n > 1 ? "s" : ""); -} - -static void js_print_value (JSPrintValueState *s, JSValue val) { - uint32_t tag = JS_VALUE_GET_NORM_TAG (val); - const char *str; - - /* Handle pointer types first (new tagging system) */ - if (JS_IsPtr (val)) { - void *ptr = JS_VALUE_GET_PTR (val); - /* Check objhdr_t at offset 8 for type */ - objhdr_t hdr = *(objhdr_t *)ptr; - uint8_t mist_type = objhdr_type (hdr); - - if (mist_type == OBJ_TEXT) { - /* String (JSText or JSText) */ - js_print_string (s, val); - return; - } - return; - } - - switch (tag) { - case JS_TAG_INT: - js_printf (s, "%d", JS_VALUE_GET_INT (val)); - break; - case JS_TAG_BOOL: - if (JS_VALUE_GET_BOOL (val)) - str = "true"; - else - str = "false"; - goto print_str; - case JS_TAG_NULL: - str = "null"; - goto print_str; - case JS_TAG_EXCEPTION: - str = "exception"; - goto print_str; - case JS_TAG_UNINITIALIZED: - str = "uninitialized"; - goto print_str; - print_str: - js_puts (s, str); - break; - case JS_TAG_SHORT_FLOAT: - js_print_float64 (s, JS_VALUE_GET_FLOAT64 (val)); - break; - case JS_TAG_STRING_IMM: - js_print_string (s, val); - break; - default: - js_printf (s, "[unknown tag %d]", tag); - break; - } -} - -void JS_PrintValueSetDefaultOptions (JSPrintValueOptions *options) { - memset (options, 0, sizeof (*options)); - options->max_depth = 2; - options->max_string_length = 1000; - options->max_item_count = 100; -} - -static void JS_PrintValueInternal (JSRuntime *rt, JSContext *ctx, JSPrintValueWrite *write_func, void *write_opaque, JSValue val, const JSPrintValueOptions *options) { - JSPrintValueState ss, *s = &ss; - if (options) - s->options = *options; - else - JS_PrintValueSetDefaultOptions (&s->options); - if (s->options.max_depth <= 0) - s->options.max_depth = JS_PRINT_MAX_DEPTH; - else - s->options.max_depth = min_int (s->options.max_depth, JS_PRINT_MAX_DEPTH); - if (s->options.max_string_length == 0) - s->options.max_string_length = UINT32_MAX; - if (s->options.max_item_count == 0) s->options.max_item_count = UINT32_MAX; - s->rt = rt; - s->ctx = ctx; - s->write_func = write_func; - s->write_opaque = write_opaque; - s->level = 0; - js_print_value (s, val); -} - -void JS_PrintValueRT (JSRuntime *rt, JSPrintValueWrite *write_func, void *write_opaque, JSValue val, const JSPrintValueOptions *options) { - JS_PrintValueInternal (rt, NULL, write_func, write_opaque, val, options); -} - -void JS_PrintValue (JSContext *ctx, JSPrintValueWrite *write_func, void *write_opaque, JSValue val, const JSPrintValueOptions *options) { - JS_PrintValueInternal (ctx->rt, ctx, write_func, write_opaque, val, options); -} - -static void js_dump_value_write (void *opaque, const char *buf, size_t len) { - FILE *fo = opaque; - fwrite (buf, 1, len, fo); -} - -/* print_atom removed - atoms no longer used */ - -static __maybe_unused void JS_DumpValue (JSContext *ctx, const char *str, JSValue val) { - printf ("%s=", str); - JS_PrintValue (ctx, js_dump_value_write, stdout, val, NULL); - printf ("\n"); -} - -static __maybe_unused void JS_DumpObjectHeader (JSRuntime *rt) { - printf ("%14s %4s %4s %14s %s\n", "ADDRESS", "REFS", "SHRF", "PROTO", "CONTENT"); -} - -/* for debug only: dump an object without side effect */ -static __maybe_unused void JS_DumpObject (JSRuntime *rt, JSRecord *rec) { - JSPrintValueOptions options; - - printf ("%14p ", (void *)rec); - /* Print prototype from JSRecord */ - if (rec->proto) { - printf ("%14p ", (void *)rec->proto); - } else { - printf ("%14s ", "-"); - } - - JS_PrintValueSetDefaultOptions (&options); - options.max_depth = 1; - options.show_hidden = TRUE; - options.raw_dump = TRUE; - JS_PrintValueRT (rt, js_dump_value_write, stdout, JS_MKPTR (rec), &options); - - printf ("\n"); -} - -static __maybe_unused void JS_DumpGCObject (JSRuntime *rt, - objhdr_t *p) { - if (objhdr_type (*p) == OBJ_RECORD) { - JS_DumpObject (rt, (JSRecord *)p); - } else { - switch (objhdr_type (*p)) { - case OBJ_CODE: - printf ("[function bytecode]"); - break; - case OBJ_ARRAY: - printf ("[array]"); - break; - case OBJ_RECORD: - printf ("[record]"); - break; - default: - printf ("[unknown %d]", objhdr_type (*p)); - break; - } - printf ("\n"); - } -} - -/* return -1 if exception (proxy case) or TRUE/FALSE */ -static double js_pow (double a, double b) { - if (unlikely (!isfinite (b)) && fabs (a) == 1) { - /* not compatible with IEEE 754 */ - return NAN; - } else { - return pow (a, b); - } -} - -static no_inline __exception int -js_unary_arith_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op) { - JSValue op1; - int v; - uint32_t tag; - - op1 = sp[-1]; - tag = JS_VALUE_GET_TAG (op1); - switch (tag) { - case JS_TAG_INT: { - int64_t v64; - v64 = JS_VALUE_GET_INT (op1); - switch (op) { - case OP_inc: - case OP_dec: - v = 2 * (op - OP_dec) - 1; - v64 += v; - break; - case OP_plus: - break; - case OP_neg: - v64 = -v64; /* -0 normalized to 0 by __JS_NewFloat64 */ - break; - default: - abort (); - } - sp[-1] = JS_NewInt64 (ctx, v64); - } break; - case JS_TAG_FLOAT64: { - double d; - d = JS_VALUE_GET_FLOAT64 (op1); - switch (op) { - case OP_inc: - case OP_dec: - v = 2 * (op - OP_dec) - 1; - d += v; - break; - case OP_plus: - break; - case OP_neg: - d = -d; - break; - default: - abort (); - } - sp[-1] = __JS_NewFloat64 (ctx, d); - } break; - default: - sp[-1] = JS_NULL; - } - return 0; -} - -static __exception int js_post_inc_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op) { - sp[0] = sp[-1]; - return js_unary_arith_slow (ctx, sp + 1, op - OP_post_dec + OP_dec); -} - -static no_inline int js_not_slow (JSContext *ctx, JSValue *sp) { - JSValue op1; - - op1 = sp[-1]; - op1 = JS_ToNumber (ctx, op1); - if (JS_IsException (op1)) goto exception; - int32_t v1; - if (unlikely (JS_ToInt32 (ctx, &v1, op1))) goto exception; - sp[-1] = JS_NewInt32 (ctx, ~v1); - return 0; -exception: - sp[-1] = JS_NULL; - return -1; -} - -static no_inline __exception int -js_binary_arith_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op) { - JSValue op1, op2; - uint32_t tag1, tag2; - double d1, d2; - - op1 = sp[-2]; - op2 = sp[-1]; - tag1 = JS_VALUE_GET_NORM_TAG (op1); - tag2 = JS_VALUE_GET_NORM_TAG (op2); - /* fast path for float operations */ - if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { - d1 = JS_VALUE_GET_FLOAT64 (op1); - d2 = JS_VALUE_GET_FLOAT64 (op2); - goto handle_float64; - } - - if ((tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64) - || (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_INT)) { - if (tag1 == JS_TAG_INT) - d1 = (double)JS_VALUE_GET_INT (op1); - else - d1 = JS_VALUE_GET_FLOAT64 (op1); - if (tag2 == JS_TAG_INT) - d2 = (double)JS_VALUE_GET_INT (op2); - else - d2 = JS_VALUE_GET_FLOAT64 (op2); - goto handle_float64; - } - - op1 = JS_ToNumber (ctx, op1); - if (JS_IsException (op1)) { - goto exception; - } - op2 = JS_ToNumber (ctx, op2); - if (JS_IsException (op2)) { - goto exception; - } - tag1 = JS_VALUE_GET_NORM_TAG (op1); - tag2 = JS_VALUE_GET_NORM_TAG (op2); - - if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { - int32_t v1, v2; - int64_t v; - v1 = JS_VALUE_GET_INT (op1); - v2 = JS_VALUE_GET_INT (op2); - switch (op) { - case OP_sub: - v = (int64_t)v1 - (int64_t)v2; - break; - case OP_mul: - v = (int64_t)v1 * (int64_t)v2; - /* -0 normalized to 0, no special case needed */ - break; - case OP_div: - sp[-2] = JS_NewFloat64 (ctx, (double)v1 / (double)v2); - return 0; - case OP_mod: - if (v1 < 0 || v2 <= 0) { - sp[-2] = JS_NewFloat64 (ctx, fmod (v1, v2)); - return 0; - } else { - v = (int64_t)v1 % (int64_t)v2; - } - break; - case OP_pow: - sp[-2] = JS_NewFloat64 (ctx, js_pow (v1, v2)); - return 0; - default: - abort (); - } - sp[-2] = JS_NewInt64 (ctx, v); - } else { - double dr; - /* float64 result */ - if (JS_ToFloat64 (ctx, &d1, op1)) { - goto exception; - } - if (JS_ToFloat64 (ctx, &d2, op2)) goto exception; - handle_float64: - switch (op) { - case OP_sub: - dr = d1 - d2; - break; - case OP_mul: - dr = d1 * d2; - break; - case OP_div: - dr = d1 / d2; - break; - case OP_mod: - dr = fmod (d1, d2); - break; - case OP_pow: - dr = js_pow (d1, d2); - break; - default: - abort (); - } - sp[-2] = __JS_NewFloat64 (ctx, dr); - } - return 0; -exception: - sp[-2] = JS_NULL; - sp[-1] = JS_NULL; - return -1; -} - -static no_inline int js_relational_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op) { - JSValue op1 = sp[-2], op2 = sp[-1]; - uint32_t tag1 = JS_VALUE_GET_NORM_TAG (op1); - uint32_t tag2 = JS_VALUE_GET_NORM_TAG (op2); - int res; - - /* string <=> string */ - if (JS_IsText (op1) && JS_IsText (op2)) { - res = js_string_compare_value (ctx, op1, op2, FALSE); - - switch (op) { - case OP_lt: - res = (res < 0); - break; - case OP_lte: - res = (res <= 0); - break; - case OP_gt: - res = (res > 0); - break; - default: - res = (res >= 0); - break; - } - - /* number <=> number */ - } else if ((tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64) - && (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64)) { - double d1 = (tag1 == JS_TAG_FLOAT64 ? JS_VALUE_GET_FLOAT64 (op1) - : (double)JS_VALUE_GET_INT (op1)); - double d2 = (tag2 == JS_TAG_FLOAT64 ? JS_VALUE_GET_FLOAT64 (op2) - : (double)JS_VALUE_GET_INT (op2)); - - switch (op) { - case OP_lt: - res = (d1 < d2); - break; - case OP_lte: - res = (d1 <= d2); - break; - case OP_gt: - res = (d1 > d2); - break; - default: - res = (d1 >= d2); - break; - } - - /* anything else → TypeError */ - } else { - JS_ThrowTypeError ( - ctx, - "Relational operators only supported on two strings or two numbers"); - goto exception; - } - - /* free the two input values and push the result */ - sp[-2] = JS_NewBool (ctx, res); - return 0; - -exception: - sp[-2] = JS_NULL; - sp[-1] = JS_NULL; - return -1; -} - -/* Simplified equality: no NaN (becomes null), no coercion, no SameValue distinction */ -static BOOL js_strict_eq (JSContext *ctx, JSValue op1, JSValue op2) { - /* Fast path: identical values */ - if (op1 == op2) return TRUE; - - int tag1 = JS_VALUE_GET_NORM_TAG (op1); - int tag2 = JS_VALUE_GET_NORM_TAG (op2); - - /* Different types are never equal (no coercion) */ - /* Special case: INT and FLOAT can be equal */ - if (tag1 != tag2) { - if (!((tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64) && - (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64))) - return FALSE; - } - - switch (tag1) { - case JS_TAG_INT: - case JS_TAG_FLOAT64: { - /* Numbers: unpack and compare */ - double d1 = (tag1 == JS_TAG_INT) ? (double)JS_VALUE_GET_INT (op1) - : JS_VALUE_GET_FLOAT64 (op1); - double d2 = (tag2 == JS_TAG_INT) ? (double)JS_VALUE_GET_INT (op2) - : JS_VALUE_GET_FLOAT64 (op2); - return d1 == d2; - } - case JS_TAG_STRING_IMM: - /* Immediate text vs immediate text (handled by op1 == op2 fast path) */ - /* or vs heap text */ - if (JS_IsText (op2)) - return js_string_compare_value (ctx, op1, op2, TRUE) == 0; - return FALSE; - case JS_TAG_PTR: - /* Heap text vs heap text or vs immediate text */ - if (JS_IsText (op1) && JS_IsText (op2)) - return js_string_compare_value (ctx, op1, op2, TRUE) == 0; - /* Records/objects: pointer equality (op1 == op2 handles same object) */ - return FALSE; /* Different objects */ - case JS_TAG_BOOL: - case JS_TAG_NULL: - /* Already handled by op1 == op2 fast path */ - return FALSE; - default: - return FALSE; - } -} - -BOOL JS_StrictEq (JSContext *ctx, JSValue op1, JSValue op2) { - return js_strict_eq (ctx, op1, op2); -} - -static no_inline int js_strict_eq_slow (JSContext *ctx, JSValue *sp, BOOL is_neq) { - BOOL res = js_strict_eq (ctx, sp[-2], sp[-1]); - sp[-2] = JS_NewBool (ctx, res ^ is_neq); - return 0; -} - -static __exception int js_operator_in (JSContext *ctx, JSValue *sp) { - JSValue op1, op2; - int ret; - - op1 = sp[-2]; - op2 = sp[-1]; - - if (JS_VALUE_GET_TAG (op2) != JS_TAG_PTR) { - JS_ThrowTypeError (ctx, "invalid 'in' operand"); - return -1; - } - ret = JS_HasPropertyKey (ctx, op2, op1); - if (ret < 0) return -1; - sp[-2] = JS_NewBool (ctx, ret); - return 0; -} - -static __exception int js_operator_delete (JSContext *ctx, JSValue *sp) { - JSValue op1, op2; - int ret; - - op1 = sp[-2]; - op2 = sp[-1]; - - ret = JS_DeletePropertyKey (ctx, op1, op2); - if (unlikely (ret < 0)) return -1; - sp[-2] = JS_NewBool (ctx, ret); - return 0; -} - -/* XXX: not 100% compatible, but mozilla seems to use a similar - implementation to ensure that caller in non strict mode does not - throw (ES5 compatibility) */ -static JSValue js_throw_type_error (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - return JS_ThrowTypeError (ctx, "invalid property access"); -} - -#define GLOBAL_VAR_OFFSET 0x40000000 -#define ARGUMENT_VAR_OFFSET 0x20000000 - -static __exception int JS_CopyDataProperties (JSContext *ctx, JSValue target, JSValue source, JSValue excluded, BOOL setprop) { - JSValue keys, key, val; - uint32_t i, key_count; - int ret; - - if (JS_VALUE_GET_TAG (source) != JS_TAG_PTR) return 0; - - /* Get all string keys from source */ - keys = JS_GetOwnPropertyNames (ctx, source); - if (JS_IsException (keys)) return -1; - if (js_get_length32 (ctx, &key_count, keys)) { - return -1; - } - - for (i = 0; i < key_count; i++) { - key = JS_GetPropertyUint32 (ctx, keys, i); - if (JS_IsException (key)) goto exception; - - /* Check if key is excluded */ - if (JS_VALUE_GET_TAG (excluded) == JS_TAG_PTR) { - /* Check if key exists in excluded object */ - JSValue test = JS_GetProperty (ctx, excluded, key); - if (!JS_IsNull (test) && !JS_IsException (test)) { - continue; - } - } - - /* Get property value from source */ - val = JS_GetProperty (ctx, source, key); - if (JS_IsException (val)) { - goto exception; - } - - /* Set property on target */ - ret = JS_SetProperty (ctx, target, key, val); - if (ret < 0) goto exception; - } - - return 0; - -exception: - return -1; -} - -static JSValue js_closure2 (JSContext *ctx, JSValue func_obj, JSFunctionBytecode *b, JSStackFrame *sf) { - JSFunction *f; - f = JS_VALUE_GET_FUNCTION (func_obj); - f->u.func.function_bytecode = b; - - /* Set outer_frame to parent's JSFrame for the new closure model. - This allows OP_get_up/OP_set_up to access captured variables via frame chain. */ - f->u.func.outer_frame = sf ? sf->js_frame : JS_NULL; - - return func_obj; -} - -static JSValue js_closure (JSContext *ctx, JSValue bfunc, JSStackFrame *sf) { - JSFunctionBytecode *b; - JSValue func_obj; - JSFunction *f; - JSGCRef bfunc_ref; - - /* Protect bfunc from GC during function allocation */ - JS_PUSH_VALUE (ctx, bfunc); - - func_obj = js_new_function (ctx, JS_FUNC_KIND_BYTECODE); - if (JS_IsException (func_obj)) { - JS_POP_VALUE (ctx, bfunc); - return JS_EXCEPTION; - } - - JS_POP_VALUE (ctx, bfunc); - b = JS_VALUE_GET_PTR (bfunc); - - func_obj = js_closure2 (ctx, func_obj, b, sf); - if (JS_IsException (func_obj)) { - /* bfunc has been freed */ - goto fail; - } - f = JS_VALUE_GET_FUNCTION (func_obj); - /* Use bytecode func_name if valid, otherwise empty string */ - f->name = JS_IsText (b->func_name) ? b->func_name - : JS_KEY_empty; - f->length = b->arg_count; /* arity = total parameter count */ - return func_obj; -fail: - /* bfunc is freed when func_obj is freed */ - return JS_EXCEPTION; -} - - -#define JS_CALL_FLAG_COPY_ARGV (1 << 1) - -static JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv) { - JSCFunctionType func; - JSFunction *f; - JSStackFrame sf_s, *sf = &sf_s, *prev_sf; - JSValue ret_val; - JSValue *arg_buf; - int arg_count, i; - int saved_vs_top = -1; /* for value stack padding cleanup */ - JSCFunctionEnum cproto; - - f = JS_VALUE_GET_FUNCTION (func_obj); - cproto = f->u.cfunc.cproto; - arg_count = f->length; - - /* better to always check stack overflow */ - if (js_check_stack_overflow (ctx, sizeof (arg_buf[0]) * arg_count)) - return JS_ThrowStackOverflow (ctx); - - prev_sf = ctx->current_stack_frame; - sf->prev_frame = prev_sf; - ctx->current_stack_frame = sf; - sf->js_mode = 0; - sf->cur_func = (JSValue)func_obj; - sf->arg_count = argc; - sf->js_frame = JS_NULL; /* C functions don't have JSFrame */ - sf->stack_buf = NULL; /* C functions don't have operand stack */ - sf->p_sp = NULL; - arg_buf = argv; - - if (unlikely (argc < arg_count)) { - /* Pad args on the value stack (GC-scanned) instead of alloca */ - saved_vs_top = ctx->value_stack_top; - for (i = 0; i < argc; i++) - ctx->value_stack[saved_vs_top + i] = argv[i]; - for (i = argc; i < arg_count; i++) - ctx->value_stack[saved_vs_top + i] = JS_NULL; - ctx->value_stack_top = saved_vs_top + arg_count; - arg_buf = &ctx->value_stack[saved_vs_top]; - sf->arg_count = arg_count; - } - sf->arg_buf = (JSValue *)arg_buf; - - func = f->u.cfunc.c_function; - - if (unlikely (ctx->trace_hook) && (ctx->trace_type & JS_HOOK_CALL)) { - js_debug dbg; - js_debug_info (ctx, func_obj, &dbg); - ctx->trace_hook (ctx, JS_HOOK_CALL, &dbg, ctx->trace_data); - } - - switch (cproto) { - case JS_CFUNC_generic: - ret_val = func.generic (ctx, this_obj, argc, arg_buf); - break; - case JS_CFUNC_generic_magic: - ret_val - = func.generic_magic (ctx, this_obj, argc, arg_buf, f->u.cfunc.magic); - break; - case JS_CFUNC_f_f: { - double d1; - - if (unlikely (JS_ToFloat64 (ctx, &d1, arg_buf[0]))) { - ret_val = JS_EXCEPTION; - break; - } - ret_val = JS_NewFloat64 (ctx, func.f_f (d1)); - } break; - case JS_CFUNC_f_f_f: { - double d1, d2; - - if (unlikely (JS_ToFloat64 (ctx, &d1, arg_buf[0]))) { - ret_val = JS_EXCEPTION; - break; - } - if (unlikely (JS_ToFloat64 (ctx, &d2, arg_buf[1]))) { - ret_val = JS_EXCEPTION; - break; - } - ret_val = JS_NewFloat64 (ctx, func.f_f_f (d1, d2)); - } break; - /* Fixed-arity fast paths - direct call without argc/argv marshaling */ - case JS_CFUNC_0: - ret_val = func.f0 (ctx, this_obj); - break; - case JS_CFUNC_1: - ret_val = func.f1 (ctx, this_obj, arg_buf[0]); - break; - case JS_CFUNC_2: - ret_val = func.f2 (ctx, this_obj, arg_buf[0], arg_buf[1]); - break; - case JS_CFUNC_3: - ret_val = func.f3 (ctx, this_obj, arg_buf[0], arg_buf[1], arg_buf[2]); - break; - case JS_CFUNC_4: - ret_val = func.f4 (ctx, this_obj, arg_buf[0], arg_buf[1], arg_buf[2], arg_buf[3]); - break; - /* Pure functions (no this_val) */ - case JS_CFUNC_PURE: - ret_val = func.pure (ctx, argc, arg_buf); - break; - case JS_CFUNC_PURE_0: - ret_val = func.pure0 (ctx); - break; - case JS_CFUNC_PURE_1: - ret_val = func.pure1 (ctx, arg_buf[0]); - break; - case JS_CFUNC_PURE_2: - ret_val = func.pure2 (ctx, arg_buf[0], arg_buf[1]); - break; - case JS_CFUNC_PURE_3: - ret_val = func.pure3 (ctx, arg_buf[0], arg_buf[1], arg_buf[2]); - break; - case JS_CFUNC_PURE_4: - ret_val = func.pure4 (ctx, arg_buf[0], arg_buf[1], arg_buf[2], arg_buf[3]); - break; - default: - abort (); - } - - ctx->current_stack_frame = sf->prev_frame; - - /* Restore value stack if we used it for arg padding */ - if (saved_vs_top >= 0) - ctx->value_stack_top = saved_vs_top; - - if (unlikely (ctx->trace_hook) && (ctx->trace_type & JS_HOOK_RET)) - ctx->trace_hook (ctx, JS_HOOK_RET, NULL, ctx->trace_data); - - return ret_val; -} - -/* argument of OP_special_object */ -typedef enum { - OP_SPECIAL_OBJECT_THIS_FUNC, - OP_SPECIAL_OBJECT_VAR_OBJECT, -} OPSpecialObjectEnum; - -/* argv[] is modified if (flags & JS_CALL_FLAG_COPY_ARGV) = 0. */ -static JSValue JS_CallInternal (JSContext *caller_ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv, int flags) { - JSContext *ctx = caller_ctx; - JSFunction *f; - JSFunctionBytecode *b; - JSStackFrame sf_s, *sf = &sf_s; - const uint8_t *pc; - int opcode, arg_allocated_size, i; - JSValue *stack_buf, *var_buf, *arg_buf, *sp, ret_val; - size_t alloca_size; - -#if !DIRECT_DISPATCH -#define SWITCH(pc) switch (opcode = *pc++) -#define CASE(op) case op -#define DEFAULT default -#define BREAK break -#define SWITCH_OPCODE(new_op, target_label) \ - do { \ - const uint8_t *instr_ptr = pc - 1; \ - uint8_t *bc = (uint8_t *)b->byte_code_buf; \ - size_t instr_idx = instr_ptr - b->byte_code_buf; \ - bc[instr_idx] = new_op; \ - goto target_label; \ - } while (0) -#else - static const void *const dispatch_table[256] = { -#define DEF(id, size, n_pop, n_push, f) &&case_OP_##id, -#if SHORT_OPCODES -#define def(id, size, n_pop, n_push, f) -#else -#define def(id, size, n_pop, n_push, f) &&case_default, -#endif -#include "quickjs-opcode.h" - [OP_COUNT... 255] = &&case_default - }; -#define SWITCH(pc) goto *dispatch_table[opcode = *pc++]; -#define CASE(op) case_##op -#define DEFAULT case_default -#define BREAK SWITCH (pc) -#endif - - if (js_poll_interrupts (caller_ctx)) return JS_EXCEPTION; - if (unlikely (!JS_IsFunction (func_obj))) { - not_a_function: - return JS_ThrowTypeError (caller_ctx, "not a function"); - } - f = JS_VALUE_GET_FUNCTION (func_obj); - /* Strict arity enforcement: too many arguments throws (length < 0 means variadic) */ - if (unlikely (f->length >= 0 && argc > f->length)) { - char buf[KEY_GET_STR_BUF_SIZE]; - return JS_ThrowTypeError ( - caller_ctx, "too many arguments for %s: expected %d, got %d", JS_KeyGetStr (caller_ctx, buf, KEY_GET_STR_BUF_SIZE, f->name), f->length, argc); - } - switch (f->kind) { - case JS_FUNC_KIND_C: - return js_call_c_function (caller_ctx, func_obj, this_obj, argc, (JSValue *)argv); - case JS_FUNC_KIND_BYTECODE: - break; /* continue to bytecode execution below */ - case JS_FUNC_KIND_REGISTER: - return JS_CallRegisterVM(caller_ctx, f->u.reg.code, this_obj, argc, (JSValue *)argv, - f->u.reg.env_record, f->u.reg.outer_frame); - case JS_FUNC_KIND_MCODE: - return mcode_exec(caller_ctx, f->u.mcode.code, this_obj, argc, (JSValue *)argv, f->u.mcode.outer_frame); - default: - goto not_a_function; - } - b = f->u.func.function_bytecode; - - if (unlikely (caller_ctx->trace_hook) - && (caller_ctx->trace_type & JS_HOOK_CALL)) { - js_debug dbg; - js_debug_info (caller_ctx, func_obj, &dbg); - caller_ctx->trace_hook (caller_ctx, JS_HOOK_CALL, &dbg, caller_ctx->trace_data); - } - - if (unlikely (argc < b->arg_count || (flags & JS_CALL_FLAG_COPY_ARGV))) { - arg_allocated_size = b->arg_count; - } else { - arg_allocated_size = 0; - } - - alloca_size - = sizeof (JSValue) * (arg_allocated_size + b->var_count + b->stack_size); - if (js_check_stack_overflow (ctx, alloca_size)) - return JS_ThrowStackOverflow (caller_ctx); - - sf->js_mode = b->js_mode; - sf->arg_count = argc; - sf->cur_func = (JSValue)func_obj; - - /* Allocate JSFrame for args + vars on regular heap (stable pointers). - This enables the outer_frame closure model. */ - { - int frame_slot_count = b->arg_count + b->var_count; - size_t frame_size = sizeof(JSFrame) + frame_slot_count * sizeof(JSValue); - JSFrame *jf = pjs_mallocz(frame_size); - if (!jf) - return JS_ThrowOutOfMemory(caller_ctx); - jf->header = objhdr_make(frame_slot_count, OBJ_FRAME, false, false, false, false); - jf->function = JS_MKPTR(f); - jf->caller = JS_NULL; - jf->return_pc = 0; - sf->js_frame = JS_MKPTR(jf); - - /* Point arg_buf and var_buf into JSFrame slots */ - arg_buf = jf->slots; - var_buf = jf->slots + b->arg_count; - sf->arg_buf = arg_buf; - sf->var_buf = var_buf; - - /* Copy arguments into frame */ - int n = min_int(argc, b->arg_count); - for (i = 0; i < n; i++) - arg_buf[i] = argv[i]; - for (; i < b->arg_count; i++) - arg_buf[i] = JS_NULL; - if (argc < b->arg_count) - sf->arg_count = b->arg_count; - - /* Initialize local variables */ - for (i = 0; i < b->var_count; i++) - var_buf[i] = JS_NULL; - } - - /* Stack is still on C stack (transient) */ - stack_buf = alloca(sizeof(JSValue) * b->stack_size); - sp = stack_buf; - pc = b->byte_code_buf; - sf->stack_buf = stack_buf; - sf->p_sp = &sp; /* GC uses this to find current stack top */ - sf->prev_frame = ctx->current_stack_frame; - ctx->current_stack_frame = sf; - -restart: - for (;;) { - int call_argc; - JSValue *call_argv; - - SWITCH (pc) { - CASE (OP_push_i32) : *sp++ = JS_NewInt32 (ctx, get_u32 (pc)); - pc += 4; - BREAK; - CASE (OP_push_const) : *sp++ = b->cpool[get_u32 (pc)]; - pc += 4; - BREAK; -#if SHORT_OPCODES - CASE (OP_push_minus1) - : CASE (OP_push_0) - : CASE (OP_push_1) - : CASE (OP_push_2) - : CASE (OP_push_3) - : CASE (OP_push_4) - : CASE (OP_push_5) - : CASE (OP_push_6) - : CASE (OP_push_7) : *sp++ = JS_NewInt32 (ctx, opcode - OP_push_0); - BREAK; - CASE (OP_push_i8) : *sp++ = JS_NewInt32 (ctx, get_i8 (pc)); - pc += 1; - BREAK; - CASE (OP_push_i16) : *sp++ = JS_NewInt32 (ctx, get_i16 (pc)); - pc += 2; - BREAK; - CASE (OP_push_const8) : *sp++ = b->cpool[*pc++]; - BREAK; - CASE (OP_fclosure8) - : *sp++ = js_closure (ctx, b->cpool[*pc++], sf); - if (unlikely (JS_IsException (sp[-1]))) goto exception; - BREAK; - CASE (OP_push_empty_string) : *sp++ = JS_KEY_empty; - BREAK; -#endif - CASE (OP_null) : *sp++ = JS_NULL; - BREAK; - CASE (OP_push_this) - : /* OP_push_this is only called at the start of a function */ - { - JSValue val = this_obj; - *sp++ = val; - } - BREAK; - CASE (OP_push_false) : *sp++ = JS_FALSE; - BREAK; - CASE (OP_push_true) : *sp++ = JS_TRUE; - BREAK; - CASE (OP_object) : *sp++ = JS_NewObject (ctx); - if (unlikely (JS_IsException (sp[-1]))) goto exception; - BREAK; - CASE (OP_special_object) : { - int arg = *pc++; - switch (arg) { - case OP_SPECIAL_OBJECT_THIS_FUNC: - *sp++ = sf->cur_func; - break; - case OP_SPECIAL_OBJECT_VAR_OBJECT: - *sp++ = JS_NewObjectProto (ctx, JS_NULL); - if (unlikely (JS_IsException (sp[-1]))) goto exception; - break; - default: - abort (); - } - } - BREAK; - CASE (OP_drop) : ; - sp--; - BREAK; - CASE (OP_nip) : ; - sp[-2] = sp[-1]; - sp--; - BREAK; - CASE (OP_nip1) - : /* a b c -> b c */ - sp[-3] = sp[-2]; - sp[-2] = sp[-1]; - sp--; - BREAK; - CASE (OP_dup) : sp[0] = sp[-1]; - sp++; - BREAK; - CASE (OP_dup2) - : /* a b -> a b a b */ - sp[0] = sp[-2]; - sp[1] = sp[-1]; - sp += 2; - BREAK; - CASE (OP_dup3) - : /* a b c -> a b c a b c */ - sp[0] = sp[-3]; - sp[1] = sp[-2]; - sp[2] = sp[-1]; - sp += 3; - BREAK; - CASE (OP_dup1) - : /* a b -> a a b */ - sp[0] = sp[-1]; - sp[-1] = sp[-2]; - sp++; - BREAK; - CASE (OP_insert2) - : /* obj a -> a obj a (dup_x1) */ - sp[0] = sp[-1]; - sp[-1] = sp[-2]; - sp[-2] = sp[0]; - sp++; - BREAK; - CASE (OP_insert3) - : /* obj prop a -> a obj prop a (dup_x2) */ - sp[0] = sp[-1]; - sp[-1] = sp[-2]; - sp[-2] = sp[-3]; - sp[-3] = sp[0]; - sp++; - BREAK; - CASE (OP_insert4) - : /* this obj prop a -> a this obj prop a */ - sp[0] = sp[-1]; - sp[-1] = sp[-2]; - sp[-2] = sp[-3]; - sp[-3] = sp[-4]; - sp[-4] = sp[0]; - sp++; - BREAK; - CASE (OP_perm3) - : /* obj a b -> a obj b (213) */ - { - JSValue tmp; - tmp = sp[-2]; - sp[-2] = sp[-3]; - sp[-3] = tmp; - } - BREAK; - CASE (OP_rot3l) - : /* x a b -> a b x (231) */ - { - JSValue tmp; - tmp = sp[-3]; - sp[-3] = sp[-2]; - sp[-2] = sp[-1]; - sp[-1] = tmp; - } - BREAK; - CASE (OP_rot4l) - : /* x a b c -> a b c x */ - { - JSValue tmp; - tmp = sp[-4]; - sp[-4] = sp[-3]; - sp[-3] = sp[-2]; - sp[-2] = sp[-1]; - sp[-1] = tmp; - } - BREAK; - CASE (OP_rot5l) - : /* x a b c d -> a b c d x */ - { - JSValue tmp; - tmp = sp[-5]; - sp[-5] = sp[-4]; - sp[-4] = sp[-3]; - sp[-3] = sp[-2]; - sp[-2] = sp[-1]; - sp[-1] = tmp; - } - BREAK; - CASE (OP_rot3r) - : /* a b x -> x a b (312) */ - { - JSValue tmp; - tmp = sp[-1]; - sp[-1] = sp[-2]; - sp[-2] = sp[-3]; - sp[-3] = tmp; - } - BREAK; - CASE (OP_perm4) - : /* obj prop a b -> a obj prop b */ - { - JSValue tmp; - tmp = sp[-2]; - sp[-2] = sp[-3]; - sp[-3] = sp[-4]; - sp[-4] = tmp; - } - BREAK; - CASE (OP_perm5) - : /* this obj prop a b -> a this obj prop b */ - { - JSValue tmp; - tmp = sp[-2]; - sp[-2] = sp[-3]; - sp[-3] = sp[-4]; - sp[-4] = sp[-5]; - sp[-5] = tmp; - } - BREAK; - CASE (OP_swap) - : /* a b -> b a */ - { - JSValue tmp; - tmp = sp[-2]; - sp[-2] = sp[-1]; - sp[-1] = tmp; - } - BREAK; - CASE (OP_swap2) - : /* a b c d -> c d a b */ - { - JSValue tmp1, tmp2; - tmp1 = sp[-4]; - tmp2 = sp[-3]; - sp[-4] = sp[-2]; - sp[-3] = sp[-1]; - sp[-2] = tmp1; - sp[-1] = tmp2; - } - BREAK; - - CASE (OP_fclosure) : { - JSValue bfunc = b->cpool[get_u32 (pc)]; - pc += 4; - *sp++ = js_closure (ctx, bfunc, sf); - if (unlikely (JS_IsException (sp[-1]))) goto exception; - } - BREAK; -#if SHORT_OPCODES - CASE (OP_call0) - : CASE (OP_call1) - : CASE (OP_call2) : CASE (OP_call3) : call_argc = opcode - OP_call0; - goto has_call_argc; -#endif - CASE (OP_call) : CASE (OP_tail_call) : { - call_argc = get_u16 (pc); - pc += 2; - goto has_call_argc; - has_call_argc: - call_argv = sp - call_argc; - sf->cur_pc = pc; - /* TODO: Use trampoline - for now keep recursive */ - ret_val = JS_CallInternal (ctx, call_argv[-1], JS_NULL, call_argc, call_argv, 0); - if (unlikely (JS_IsException (ret_val))) goto exception; - if (opcode == OP_tail_call) goto done; - sp -= call_argc + 1; - *sp++ = ret_val; - } - BREAK; - CASE (OP_call_method) : CASE (OP_tail_call_method) : { - call_argc = get_u16 (pc); - pc += 2; - call_argv = sp - call_argc; - sf->cur_pc = pc; - /* Proxy method-call: detect [func, "name", ...args] - and rewrite as func("name", [args]) */ - - if (JS_IsFunction (call_argv[-2])) { - JSValue name = call_argv[-1]; - if (!JS_IsText (name)) { - ret_val - = JS_ThrowTypeError (ctx, "second argument must be a string"); - goto exception; - } - - JSValue args = JS_NewArrayLen (ctx, call_argc); - if (unlikely (JS_IsException (args))) goto exception; - - /* Move args into the array, then null out stack slots. */ - JSArray *ar = JS_VALUE_GET_ARRAY (args); - for (i = 0; i < call_argc; i++) { - ar->values[i] = call_argv[i]; - call_argv[i] = JS_NULL; - } - - JSValue proxy_argv[2]; - proxy_argv[0] - = name; /* still owned by stack; freed by normal cleanup */ - proxy_argv[1] = args; - - ret_val - = JS_CallInternal (ctx, call_argv[-2], JS_NULL, 2, proxy_argv, 0); - } else { - ret_val = JS_CallInternal (ctx, call_argv[-1], call_argv[-2], call_argc, call_argv, 0); - } - if (unlikely (JS_IsException (ret_val))) goto exception; - if (opcode == OP_tail_call_method) goto done; - for (i = -2; i < call_argc; i++) - sp -= call_argc + 2; - *sp++ = ret_val; - } - BREAK; - CASE (OP_array_from) : { - int i; - - call_argc = get_u16 (pc); - pc += 2; - ret_val = JS_NewArrayLen (ctx, call_argc); - if (unlikely (JS_IsException (ret_val))) goto exception; - call_argv = sp - call_argc; - JSArray *ar = JS_VALUE_GET_ARRAY (ret_val); - for (i = 0; i < call_argc; i++) { - ar->values[i] = call_argv[i]; - call_argv[i] = JS_NULL; - } - sp -= call_argc; - *sp++ = ret_val; - } - BREAK; - CASE (OP_return) : ret_val = *--sp; - goto done; - CASE (OP_return_undef) : ret_val = JS_NULL; - goto done; - - CASE (OP_throw) : JS_Throw (ctx, *--sp); - goto exception; - - CASE (OP_throw_error) - : -#define JS_THROW_VAR_RO 0 -#define JS_THROW_VAR_REDECL 1 -#define JS_THROW_VAR_UNINITIALIZED 2 -#define JS_THROW_ERROR_ITERATOR_THROW 4 - { - JSValue key; - int type; - key = b->cpool[get_u32 (pc)]; - type = pc[4]; - pc += 5; - if (type == JS_THROW_VAR_REDECL) - JS_ThrowSyntaxErrorVarRedeclaration (ctx, key); - else if (type == JS_THROW_VAR_UNINITIALIZED) - JS_ThrowReferenceErrorUninitialized (ctx, key); - else if (type == JS_THROW_ERROR_ITERATOR_THROW) - JS_ThrowTypeError (ctx, "iterator does not have a throw method"); - else - JS_ThrowInternalError (ctx, "invalid throw var type %d", type); - } - goto exception; - - CASE (OP_regexp) : { - sp[-2] = js_regexp_constructor_internal (ctx, sp[-2], sp[-1]); - sp--; - } - BREAK; - - /* Global variable opcodes - resolved by linker to get/set_global_slot */ - CASE (OP_check_var) : - pc += 4; - JS_ThrowInternalError (ctx, "OP_check_var: linker should have resolved this"); - goto exception; - BREAK; - CASE (OP_get_var_undef) : - CASE (OP_get_var) : - pc += 4; - JS_ThrowInternalError (ctx, "OP_get_var: linker should have resolved to get_global_slot"); - goto exception; - BREAK; - CASE (OP_put_var) : - CASE (OP_put_var_init) : - pc += 4; - sp--; - JS_ThrowInternalError (ctx, "OP_put_var: global object is immutable, linker should resolve"); - goto exception; - BREAK; - CASE (OP_put_var_strict) : - pc += 4; - sp -= 2; - JS_ThrowInternalError (ctx, "OP_put_var_strict: global object is immutable, linker should resolve"); - goto exception; - BREAK; - - CASE (OP_define_var) : - CASE (OP_check_define_var) : - pc += 5; - JS_ThrowInternalError (ctx, "global object is immutable - cannot define variables at runtime"); - goto exception; - BREAK; - CASE (OP_define_func) : - pc += 5; - sp--; - JS_ThrowInternalError (ctx, "global object is immutable - cannot define functions at runtime"); - goto exception; - BREAK; - - CASE (OP_get_loc) : { - int idx; - idx = get_u16 (pc); - pc += 2; - sp[0] = var_buf[idx]; - sp++; - } - BREAK; - CASE (OP_put_loc) : { - int idx; - idx = get_u16 (pc); - pc += 2; - set_value (ctx, &var_buf[idx], sp[-1]); - sp--; - } - BREAK; - CASE (OP_set_loc) : { - int idx; - idx = get_u16 (pc); - pc += 2; - set_value (ctx, &var_buf[idx], sp[-1]); - } - BREAK; - CASE (OP_get_arg) : { - int idx; - idx = get_u16 (pc); - pc += 2; - sp[0] = arg_buf[idx]; - sp++; - } - BREAK; - CASE (OP_put_arg) : { - int idx; - idx = get_u16 (pc); - pc += 2; - set_value (ctx, &arg_buf[idx], sp[-1]); - sp--; - } - BREAK; - CASE (OP_set_arg) : { - int idx; - idx = get_u16 (pc); - pc += 2; - set_value (ctx, &arg_buf[idx], sp[-1]); - } - BREAK; - -#if SHORT_OPCODES - CASE (OP_get_loc8) : *sp++ = var_buf[*pc++]; - BREAK; - CASE (OP_put_loc8) : set_value (ctx, &var_buf[*pc++], *--sp); - BREAK; - CASE (OP_set_loc8) - : set_value (ctx, &var_buf[*pc++], sp[-1]); - BREAK; - - CASE (OP_get_loc0) : *sp++ = var_buf[0]; - BREAK; - CASE (OP_get_loc1) : *sp++ = var_buf[1]; - BREAK; - CASE (OP_get_loc2) : *sp++ = var_buf[2]; - BREAK; - CASE (OP_get_loc3) : *sp++ = var_buf[3]; - BREAK; - CASE (OP_put_loc0) : set_value (ctx, &var_buf[0], *--sp); - BREAK; - CASE (OP_put_loc1) : set_value (ctx, &var_buf[1], *--sp); - BREAK; - CASE (OP_put_loc2) : set_value (ctx, &var_buf[2], *--sp); - BREAK; - CASE (OP_put_loc3) : set_value (ctx, &var_buf[3], *--sp); - BREAK; - CASE (OP_set_loc0) - : set_value (ctx, &var_buf[0], sp[-1]); - BREAK; - CASE (OP_set_loc1) - : set_value (ctx, &var_buf[1], sp[-1]); - BREAK; - CASE (OP_set_loc2) - : set_value (ctx, &var_buf[2], sp[-1]); - BREAK; - CASE (OP_set_loc3) - : set_value (ctx, &var_buf[3], sp[-1]); - BREAK; - CASE (OP_get_arg0) : *sp++ = arg_buf[0]; - BREAK; - CASE (OP_get_arg1) : *sp++ = arg_buf[1]; - BREAK; - CASE (OP_get_arg2) : *sp++ = arg_buf[2]; - BREAK; - CASE (OP_get_arg3) : *sp++ = arg_buf[3]; - BREAK; - CASE (OP_put_arg0) : set_value (ctx, &arg_buf[0], *--sp); - BREAK; - CASE (OP_put_arg1) : set_value (ctx, &arg_buf[1], *--sp); - BREAK; - CASE (OP_put_arg2) : set_value (ctx, &arg_buf[2], *--sp); - BREAK; - CASE (OP_put_arg3) : set_value (ctx, &arg_buf[3], *--sp); - BREAK; - CASE (OP_set_arg0) - : set_value (ctx, &arg_buf[0], sp[-1]); - BREAK; - CASE (OP_set_arg1) - : set_value (ctx, &arg_buf[1], sp[-1]); - BREAK; - CASE (OP_set_arg2) - : set_value (ctx, &arg_buf[2], sp[-1]); - BREAK; - CASE (OP_set_arg3) - : set_value (ctx, &arg_buf[3], sp[-1]); - BREAK; -#endif - CASE (OP_set_loc_uninitialized) : { - int idx; - idx = get_u16 (pc); - pc += 2; - set_value (ctx, &var_buf[idx], JS_UNINITIALIZED); - } - BREAK; - CASE (OP_get_loc_check) : { - int idx; - idx = get_u16 (pc); - pc += 2; - if (unlikely (JS_IsUninitialized (var_buf[idx]))) { - JS_ThrowReferenceErrorUninitialized2 (ctx, b, idx, FALSE); - goto exception; - } - sp[0] = var_buf[idx]; - sp++; - } - BREAK; - CASE (OP_get_loc_checkthis) : { - int idx; - idx = get_u16 (pc); - pc += 2; - if (unlikely (JS_IsUninitialized (var_buf[idx]))) { - JS_ThrowReferenceErrorUninitialized2 (caller_ctx, b, idx, FALSE); - goto exception; - } - sp[0] = var_buf[idx]; - sp++; - } - BREAK; - CASE (OP_put_loc_check) : { - int idx; - idx = get_u16 (pc); - pc += 2; - if (unlikely (JS_IsUninitialized (var_buf[idx]))) { - JS_ThrowReferenceErrorUninitialized2 (ctx, b, idx, FALSE); - goto exception; - } - set_value (ctx, &var_buf[idx], sp[-1]); - sp--; - } - BREAK; - CASE (OP_put_loc_check_init) : { - int idx; - idx = get_u16 (pc); - pc += 2; - if (unlikely (!JS_IsUninitialized (var_buf[idx]))) { - JS_ThrowReferenceError (ctx, "'this' can be initialized only once"); - goto exception; - } - set_value (ctx, &var_buf[idx], sp[-1]); - sp--; - } - BREAK; - - CASE (OP_goto) : pc += (int32_t)get_u32 (pc); - if (unlikely (js_poll_interrupts (ctx))) goto exception; - BREAK; -#if SHORT_OPCODES - CASE (OP_goto16) : pc += (int16_t)get_u16 (pc); - if (unlikely (js_poll_interrupts (ctx))) goto exception; - BREAK; - CASE (OP_goto8) : pc += (int8_t)pc[0]; - if (unlikely (js_poll_interrupts (ctx))) goto exception; - BREAK; -#endif - CASE (OP_if_true) : { - int res; - JSValue op1; - - op1 = sp[-1]; - pc += 4; - if ((uint32_t)JS_VALUE_GET_TAG (op1) <= JS_TAG_NULL) { - res = JS_VALUE_GET_INT (op1); - } else { - res = JS_ToBool (ctx, op1); - } - sp--; - if (res) { pc += (int32_t)get_u32 (pc - 4) - 4; } - if (unlikely (js_poll_interrupts (ctx))) goto exception; - } - BREAK; - CASE (OP_if_false) : { - int res; - JSValue op1; - - op1 = sp[-1]; - pc += 4; - /* quick and dirty test for JS_TAG_INT, JS_TAG_BOOL, JS_TAG_NULL and - * JS_TAG_UNDEFINED */ - if ((uint32_t)JS_VALUE_GET_TAG (op1) <= JS_TAG_NULL) { - res = JS_VALUE_GET_INT (op1); - } else { - res = JS_ToBool (ctx, op1); - } - sp--; - if (!res) { pc += (int32_t)get_u32 (pc - 4) - 4; } - if (unlikely (js_poll_interrupts (ctx))) goto exception; - } - BREAK; -#if SHORT_OPCODES - CASE (OP_if_true8) : { - int res; - JSValue op1; - - op1 = sp[-1]; - pc += 1; - if ((uint32_t)JS_VALUE_GET_TAG (op1) <= JS_TAG_NULL) { - res = JS_VALUE_GET_INT (op1); - } else { - res = JS_ToBool (ctx, op1); - } - sp--; - if (res) { pc += (int8_t)pc[-1] - 1; } - if (unlikely (js_poll_interrupts (ctx))) goto exception; - } - BREAK; - CASE (OP_if_false8) : { - int res; - JSValue op1; - - op1 = sp[-1]; - pc += 1; - if ((uint32_t)JS_VALUE_GET_TAG (op1) <= JS_TAG_NULL) { - res = JS_VALUE_GET_INT (op1); - } else { - res = JS_ToBool (ctx, op1); - } - sp--; - if (!res) { pc += (int8_t)pc[-1] - 1; } - if (unlikely (js_poll_interrupts (ctx))) goto exception; - } - BREAK; -#endif - CASE (OP_catch) : { - int32_t diff; - diff = get_u32 (pc); - sp[0] = JS_NewCatchOffset (ctx, pc + diff - b->byte_code_buf); - sp++; - pc += 4; - } - BREAK; - CASE (OP_gosub) : { - int32_t diff; - diff = get_u32 (pc); - /* XXX: should have a different tag to avoid security flaw */ - sp[0] = JS_NewInt32 (ctx, pc + 4 - b->byte_code_buf); - sp++; - pc += diff; - } - BREAK; - CASE (OP_ret) : { - JSValue op1; - uint32_t pos; - op1 = sp[-1]; - if (unlikely (JS_VALUE_GET_TAG (op1) != JS_TAG_INT)) goto ret_fail; - pos = JS_VALUE_GET_INT (op1); - if (unlikely (pos >= b->byte_code_len)) { - ret_fail: - JS_ThrowInternalError (ctx, "invalid ret value"); - goto exception; - } - sp--; - pc = b->byte_code_buf + pos; - } - BREAK; - - CASE (OP_nip_catch) : { - JSValue ret_val; - /* catch_offset ... ret_val -> ret_eval */ - ret_val = *--sp; - while (sp > stack_buf - && JS_VALUE_GET_TAG (sp[-1]) != JS_TAG_CATCH_OFFSET) { - } - if (unlikely (sp == stack_buf)) { - JS_ThrowInternalError (ctx, "nip_catch"); - goto exception; - } - sp[-1] = ret_val; - } - BREAK; - - CASE (OP_lnot) : { - int res; - JSValue op1; - - op1 = sp[-1]; - if ((uint32_t)JS_VALUE_GET_TAG (op1) <= JS_TAG_NULL) { - res = JS_VALUE_GET_INT (op1) != 0; - } else { - res = JS_ToBool (ctx, op1); - } - sp[-1] = JS_NewBool (ctx, !res); - } - BREAK; - - CASE (OP_get_field) : { - JSValue val; - uint32_t idx; - JSValue key; - JSValue obj; - - idx = get_u32 (pc); - pc += 4; - - sf->cur_pc = pc; - - /* Get JSValue key from cpool */ - key = b->cpool[idx]; - - obj = sp[-1]; - - /* Record property access - use JSValue key directly */ - if (JS_IsRecord (obj)) { - JSRecord *rec = JS_VALUE_GET_RECORD (obj); - val = rec_get (ctx, rec, key); - sp[-1] = val; - } else { - /* Non-record: return null */ - sp[-1] = JS_NULL; - } - } - BREAK; - - CASE (OP_get_field2) : { - JSValue val; - uint32_t idx; - JSValue key; - JSValue obj; - - idx = get_u32 (pc); - pc += 4; - - sf->cur_pc = pc; - - /* Get JSValue key from cpool */ - key = b->cpool[idx]; - - obj = sp[-1]; - - /* Proxy method-call sugar: func.name(...) -> func("name", [args...]) - OP_get_field2 is only emitted when a call immediately follows. */ - if (JS_IsFunction (obj)) { - val = key; /* "name" as JSValue string */ - *sp++ = val; /* stack becomes [func, "name"] */ - } else if (JS_IsRecord (obj)) { - /* Record property access - use JSValue key directly */ - JSRecord *rec = JS_VALUE_GET_RECORD (obj); - val = rec_get (ctx, rec, key); - *sp++ = val; - } else { - /* Non-record: push null */ - *sp++ = JS_NULL; - } - } - BREAK; - - CASE (OP_put_field) : { - int ret; - uint32_t idx; - JSValue key; - - idx = get_u32 (pc); - pc += 4; - sf->cur_pc = pc; - - /* Get JSValue key from cpool */ - key = b->cpool[idx]; - - /* Must be a record to set property */ - if (!JS_IsRecord (sp[-2])) { - sp -= 2; - JS_ThrowTypeError (ctx, "cannot set property of non-record"); - goto exception; - } - - /* Record property set - use JSValue key directly */ - ret = rec_set_own (ctx, &sp[-2], key, sp[-1]); - sp -= 2; - if (unlikely (ret < 0)) goto exception; - } - BREAK; - - CASE (OP_define_field) : { - int ret; - uint32_t idx; - JSValue key; - - idx = get_u32 (pc); - pc += 4; - - /* Get JSValue key from cpool */ - key = b->cpool[idx]; - - /* Must be a record */ - if (!JS_IsRecord (sp[-2])) { - sp--; - JS_ThrowTypeError (ctx, "cannot define field on non-record"); - goto exception; - } - - /* Record property set - use JSValue key directly */ - ret = rec_set_own (ctx, &sp[-2], key, sp[-1]); - sp--; - if (unlikely (ret < 0)) goto exception; - } - BREAK; - - CASE (OP_set_name) : { - int ret; - JSValue key; - key = b->cpool[get_u32 (pc)]; - pc += 4; - - ret = JS_DefineObjectName (ctx, sp[-1], key); - if (unlikely (ret < 0)) goto exception; - } - BREAK; - CASE (OP_set_name_computed) : { - int ret; - ret = JS_DefineObjectNameComputed (ctx, sp[-1], sp[-2]); - if (unlikely (ret < 0)) goto exception; - } - BREAK; - CASE (OP_define_method) : CASE (OP_define_method_computed) : { - JSValue value; - JSValue obj; - JSValue key; - int ret, op_flags; - BOOL is_computed; -#define OP_DEFINE_METHOD_METHOD 0 -#define OP_DEFINE_METHOD_ENUMERABLE 4 - - is_computed = (opcode == OP_define_method_computed); - if (is_computed) { - key = sp[-2]; - opcode += OP_define_method - OP_define_method_computed; - } else { - key = b->cpool[get_u32 (pc)]; - pc += 4; - } - op_flags = *pc++; - - obj = sp[-2 - is_computed]; - op_flags &= 3; - value = JS_NULL; - - if (op_flags == OP_DEFINE_METHOD_METHOD) { value = sp[-1]; } - ret = js_method_set_properties (ctx, sp[-1], key, 0, obj); - if (ret >= 0) { - /* JS_SetProperty consumes value, so don't free sp[-1] on success */ - ret = JS_SetProperty (ctx, obj, key, value); - } else { - } - if (is_computed) { ; } - sp -= 1 + is_computed; - if (unlikely (ret < 0)) goto exception; - } - BREAK; - - CASE (OP_define_class) - : CASE (OP_define_class_computed) - : JS_ThrowTypeError (ctx, "classes are not supported"); - goto exception; - - CASE (OP_get_array_el) : { - JSValue val; - sf->cur_pc = pc; - val = JS_GetPropertyValue (ctx, sp[-2], sp[-1]); - sp[-2] = val; - sp--; - if (unlikely (JS_IsException (val))) goto exception; - } - BREAK; - - CASE (OP_get_array_el2) : { - JSValue val; - sf->cur_pc = pc; - val = JS_GetPropertyValue (ctx, sp[-2], sp[-1]); - sp[-1] = val; - if (unlikely (JS_IsException (val))) goto exception; - } - BREAK; - - CASE (OP_get_array_el3) : { - JSValue val; - switch (JS_VALUE_GET_TAG (sp[-2])) { - case JS_TAG_INT: - case JS_TAG_STRING: - break; - default: - /* must be tested nefore JS_ToPropertyKey */ - if (unlikely (JS_IsNull (sp[-2]))) { - JS_ThrowTypeError (ctx, "value has no property"); - goto exception; - } - sf->cur_pc = pc; - ret_val = sp[-1]; // JS_ToPropertyKey (ctx, sp[-1]); // is this correct? No conversion can happen. - if (JS_IsException (ret_val)) goto exception; - sp[-1] = ret_val; - break; - } - sf->cur_pc = pc; - val = JS_GetPropertyValue (ctx, sp[-2], sp[-1]); - *sp++ = val; - if (unlikely (JS_IsException (val))) goto exception; - } - BREAK; - - CASE (OP_put_array_el) : { - int ret; - - /* Functions don't support property assignment in cell script */ - if (JS_IsFunction (sp[-3])) { - JS_ThrowTypeError (ctx, "cannot set property of function"); - goto exception; - } - - sf->cur_pc = pc; - ret = JS_SetPropertyValue (ctx, sp[-3], sp[-2], sp[-1]); - sp -= 3; - if (unlikely (ret < 0)) goto exception; - } - BREAK; - - CASE (OP_define_array_el) : { - int ret; - ret = JS_SetPropertyValue (ctx, sp[-3], sp[-2], sp[-1]); - sp -= 1; - if (unlikely (ret < 0)) goto exception; - } - BREAK; - - CASE (OP_copy_data_properties) - : /* target source excludeList */ - { - /* stack offsets (-1 based): - 2 bits for target, - 3 bits for source, - 2 bits for exclusionList */ - int mask; - - mask = *pc++; - sf->cur_pc = pc; - if (JS_CopyDataProperties (ctx, sp[-1 - (mask & 3)], sp[-1 - ((mask >> 2) & 7)], sp[-1 - ((mask >> 5) & 7)], 0)) - goto exception; - } - BREAK; - - CASE (OP_add) : CASE (OP_add_float) : { - JSValue op1 = sp[-2], op2 = sp[-1], res; - int tag1 = JS_VALUE_GET_NORM_TAG (op1); - int tag2 = JS_VALUE_GET_NORM_TAG (op2); - - /* 1) both ints? keep fast int path with overflow check */ - if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { - int64_t tmp - = (int64_t)JS_VALUE_GET_INT (op1) + JS_VALUE_GET_INT (op2); - if (likely ((int)tmp == tmp)) { - res = JS_NewInt32 (ctx, (int)tmp); - } else { - res = __JS_NewFloat64 (ctx, (double)JS_VALUE_GET_INT (op1) + (double)JS_VALUE_GET_INT (op2)); - } - } - /* 2) both floats? */ - else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2)) { - res = __JS_NewFloat64 (ctx, JS_VALUE_GET_FLOAT64 (op1) + JS_VALUE_GET_FLOAT64 (op2)); - } - /* 3) both strings? */ - else if (JS_IsText (op1) && JS_IsText (op2)) { - res = JS_ConcatString (ctx, op1, op2); - if (JS_IsException (res)) goto exception; - } - /* 4) mixed int/float? promote to float */ - // TODO: Seems slow - else if ((tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64) - || (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_INT)) { - double a, b; - if (tag1 == JS_TAG_INT) - a = (double)JS_VALUE_GET_INT (op1); - else - a = JS_VALUE_GET_FLOAT64 (op1); - if (tag2 == JS_TAG_INT) - b = (double)JS_VALUE_GET_INT (op2); - else - b = JS_VALUE_GET_FLOAT64 (op2); - res = __JS_NewFloat64 (ctx, a + b); - } else if (tag1 == JS_TAG_NULL || tag2 == JS_TAG_NULL) { - /* null + string or string + null should throw */ - if (JS_IsText (op1) || JS_IsText (op2)) { - JS_ThrowTypeError (ctx, "cannot concatenate null with string"); - goto exception; - } - res = JS_NULL; - } - /* 5) anything else → throw */ - else { - JS_ThrowTypeError (ctx, "cannot concatenate with string"); - goto exception; - } - - sp[-2] = res; - sp--; - } - BREAK; - CASE (OP_add_loc) : { - int idx = *pc++; - JSValue rhs = sp[-1]; - JSValue lhs = var_buf[idx]; - JSValue res; - int tag1 = JS_VALUE_GET_NORM_TAG (lhs); - int tag2 = JS_VALUE_GET_NORM_TAG (rhs); - - /* 1) both ints? fast path, overflow → float64 */ - if (likely (JS_VALUE_IS_BOTH_INT (lhs, rhs))) { - int a_i = JS_VALUE_GET_INT (lhs); - int b_i = JS_VALUE_GET_INT (rhs); - int64_t tmp = (int64_t)a_i + b_i; - if ((int)tmp == tmp) - res = JS_NewInt32 (ctx, (int)tmp); - else - res = __JS_NewFloat64 (ctx, (double)a_i + (double)b_i); - } - /* 2) both floats? */ - else if (JS_VALUE_IS_BOTH_FLOAT (lhs, rhs)) { - res = __JS_NewFloat64 (ctx, JS_VALUE_GET_FLOAT64 (lhs) + JS_VALUE_GET_FLOAT64 (rhs)); - } - /* 3) both strings? */ - else if (JS_IsText (lhs) && JS_IsText (rhs)) { - res = JS_ConcatString (ctx, lhs, rhs); - if (JS_IsException (res)) goto exception; - } - /* 4) mixed int/float? promote to float64 */ - else if ((tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64) - || (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_INT)) { - double a = tag1 == JS_TAG_INT ? (double)JS_VALUE_GET_INT (lhs) - : JS_VALUE_GET_FLOAT64 (lhs); - double b = tag2 == JS_TAG_INT ? (double)JS_VALUE_GET_INT (rhs) - : JS_VALUE_GET_FLOAT64 (rhs); - res = __JS_NewFloat64 (ctx, a + b); - } - /* 5) anything else → throw */ - else { - JS_ThrowTypeError (ctx, "cannot concatenate with string"); - goto exception; - } - - var_buf[idx] = res; - sp--; - } - BREAK; - CASE (OP_sub) : CASE (OP_sub_float) : { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { - int64_t r; - r = (int64_t)JS_VALUE_GET_INT (op1) - JS_VALUE_GET_INT (op2); - if (unlikely ((int)r != r)) goto binary_arith_slow; - sp[-2] = JS_NewInt32 (ctx, r); - sp--; - } else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2)) { - sp[-2] = __JS_NewFloat64 (ctx, JS_VALUE_GET_FLOAT64 (op1) - JS_VALUE_GET_FLOAT64 (op2)); - sp--; - } else { - goto binary_arith_slow; - } - } - BREAK; - CASE (OP_mul) : CASE (OP_mul_float) : { - JSValue op1, op2; - double d; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { - int32_t v1, v2; - int64_t r; - v1 = JS_VALUE_GET_INT (op1); - v2 = JS_VALUE_GET_INT (op2); - r = (int64_t)v1 * v2; - if (unlikely ((int)r != r)) { - d = (double)r; - goto mul_fp_res; - } - /* -0 normalized to 0, no special case needed */ - sp[-2] = JS_NewInt32 (ctx, r); - sp--; - } else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2)) { - d = JS_VALUE_GET_FLOAT64 (op1) * JS_VALUE_GET_FLOAT64 (op2); - mul_fp_res: - sp[-2] = __JS_NewFloat64 (ctx, d); - sp--; - } else { - goto binary_arith_slow; - } - } - BREAK; - CASE (OP_div) : CASE (OP_div_float) : { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { - int v1, v2; - v1 = JS_VALUE_GET_INT (op1); - v2 = JS_VALUE_GET_INT (op2); - sp[-2] = JS_NewFloat64 (ctx, (double)v1 / (double)v2); - sp--; - } else { - goto binary_arith_slow; - } - } - BREAK; - CASE (OP_mod) : { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { - int v1, v2, r; - v1 = JS_VALUE_GET_INT (op1); - v2 = JS_VALUE_GET_INT (op2); - /* We must avoid v2 = 0, v1 = INT32_MIN and v2 = - -1 and the cases where the result is -0. */ - if (unlikely (v1 < 0 || v2 <= 0)) goto binary_arith_slow; - r = v1 % v2; - sp[-2] = JS_NewInt32 (ctx, r); - sp--; - } else { - goto binary_arith_slow; - } - } - BREAK; - CASE (OP_pow) : binary_arith_slow : sf->cur_pc = pc; - if (js_binary_arith_slow (ctx, sp, opcode)) goto exception; - sp--; - BREAK; - - CASE (OP_plus) : { - JSValue op1; - uint32_t tag; - op1 = sp[-1]; - tag = JS_VALUE_GET_TAG (op1); - if (tag == JS_TAG_INT || JS_TAG_IS_FLOAT64 (tag)) { - } else { - sf->cur_pc = pc; - if (js_unary_arith_slow (ctx, sp, opcode)) goto exception; - } - } - BREAK; - CASE (OP_neg) : { - JSValue op1; - uint32_t tag; - int val; - double d; - op1 = sp[-1]; - tag = JS_VALUE_GET_TAG (op1); - if (tag == JS_TAG_INT) { - val = JS_VALUE_GET_INT (op1); - /* -0 normalized to 0, val==0 just stays 0 */ - if (unlikely (val == INT32_MIN)) { - d = -(double)val; - goto neg_fp_res; - } - sp[-1] = JS_NewInt32 (ctx, -val); - } else if (JS_TAG_IS_FLOAT64 (tag)) { - d = -JS_VALUE_GET_FLOAT64 (op1); - neg_fp_res: - sp[-1] = __JS_NewFloat64 (ctx, d); - } else { - sf->cur_pc = pc; - if (js_unary_arith_slow (ctx, sp, opcode)) goto exception; - } - } - BREAK; - CASE (OP_inc) : { - JSValue op1; - int val; - op1 = sp[-1]; - if (JS_VALUE_GET_TAG (op1) == JS_TAG_INT) { - val = JS_VALUE_GET_INT (op1); - if (unlikely (val == INT32_MAX)) goto inc_slow; - sp[-1] = JS_NewInt32 (ctx, val + 1); - } else { - inc_slow: - sf->cur_pc = pc; - if (js_unary_arith_slow (ctx, sp, opcode)) goto exception; - } - } - BREAK; - CASE (OP_dec) : { - JSValue op1; - int val; - op1 = sp[-1]; - if (JS_VALUE_GET_TAG (op1) == JS_TAG_INT) { - val = JS_VALUE_GET_INT (op1); - if (unlikely (val == INT32_MIN)) goto dec_slow; - sp[-1] = JS_NewInt32 (ctx, val - 1); - } else { - dec_slow: - sf->cur_pc = pc; - if (js_unary_arith_slow (ctx, sp, opcode)) goto exception; - } - } - BREAK; - CASE (OP_post_inc) : CASE (OP_post_dec) : sf->cur_pc = pc; - if (js_post_inc_slow (ctx, sp, opcode)) goto exception; - sp++; - BREAK; - CASE (OP_inc_loc) : { - JSValue op1; - int val; - int idx; - idx = *pc; - pc += 1; - - op1 = var_buf[idx]; - if (JS_VALUE_GET_TAG (op1) == JS_TAG_INT) { - val = JS_VALUE_GET_INT (op1); - if (unlikely (val == INT32_MAX)) goto inc_loc_slow; - var_buf[idx] = JS_NewInt32 (ctx, val + 1); - } else { - inc_loc_slow: - sf->cur_pc = pc; - /* op1 is read from var_buf, passed by reference to js_unary_arith_slow */ - if (js_unary_arith_slow (ctx, &op1 + 1, OP_inc)) goto exception; - set_value (ctx, &var_buf[idx], op1); - } - } - BREAK; - CASE (OP_dec_loc) : { - JSValue op1; - int val; - int idx; - idx = *pc; - pc += 1; - - op1 = var_buf[idx]; - if (JS_VALUE_GET_TAG (op1) == JS_TAG_INT) { - val = JS_VALUE_GET_INT (op1); - if (unlikely (val == INT32_MIN)) goto dec_loc_slow; - var_buf[idx] = JS_NewInt32 (ctx, val - 1); - } else { - dec_loc_slow: - sf->cur_pc = pc; - /* op1 is read from var_buf, passed by reference to js_unary_arith_slow */ - if (js_unary_arith_slow (ctx, &op1 + 1, OP_dec)) goto exception; - set_value (ctx, &var_buf[idx], op1); - } - } - BREAK; - CASE (OP_not) : { - JSValue op1; - op1 = sp[-1]; - if (JS_VALUE_GET_TAG (op1) == JS_TAG_INT) { - sp[-1] = JS_NewInt32 (ctx, ~JS_VALUE_GET_INT (op1)); - } else { - sf->cur_pc = pc; - if (js_not_slow (ctx, sp)) goto exception; - } - } - BREAK; - - CASE (OP_shl) : { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { - uint32_t v1, v2; - v1 = JS_VALUE_GET_INT (op1); - v2 = JS_VALUE_GET_INT (op2); - v2 &= 0x1f; - sp[-2] = JS_NewInt32 (ctx, v1 << v2); - sp--; - } else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2) - || (JS_VALUE_GET_TAG (op1) == JS_TAG_INT - && JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op2))) - || (JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op1)) - && JS_VALUE_GET_TAG (op2) == JS_TAG_INT)) { - uint32_t v1, v2; - v1 = JS_VALUE_GET_TAG (op1) == JS_TAG_INT - ? JS_VALUE_GET_INT (op1) - : (int32_t)JS_VALUE_GET_FLOAT64 (op1); - v2 = JS_VALUE_GET_TAG (op2) == JS_TAG_INT - ? JS_VALUE_GET_INT (op2) - : (int32_t)JS_VALUE_GET_FLOAT64 (op2); - v2 &= 0x1f; - sp[-2] = JS_NewInt32 (ctx, v1 << v2); - sp--; - } else { - sp[-2] = JS_NULL; - sp--; - } - } - BREAK; - CASE (OP_shr) : { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { - uint32_t v2; - v2 = JS_VALUE_GET_INT (op2); - v2 &= 0x1f; - sp[-2] = JS_NewUint32 (ctx, (uint32_t)JS_VALUE_GET_INT (op1) >> v2); - sp--; - } else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2) - || (JS_VALUE_GET_TAG (op1) == JS_TAG_INT - && JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op2))) - || (JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op1)) - && JS_VALUE_GET_TAG (op2) == JS_TAG_INT)) { - uint32_t v1, v2; - v1 = JS_VALUE_GET_TAG (op1) == JS_TAG_INT - ? JS_VALUE_GET_INT (op1) - : (int32_t)JS_VALUE_GET_FLOAT64 (op1); - v2 = JS_VALUE_GET_TAG (op2) == JS_TAG_INT - ? JS_VALUE_GET_INT (op2) - : (int32_t)JS_VALUE_GET_FLOAT64 (op2); - v2 &= 0x1f; - sp[-2] = JS_NewUint32 (ctx, v1 >> v2); - sp--; - } else { - sp[-2] = JS_NULL; - sp--; - } - } - BREAK; - CASE (OP_sar) : { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { - uint32_t v2; - v2 = JS_VALUE_GET_INT (op2); - v2 &= 0x1f; - sp[-2] = JS_NewInt32 (ctx, (int)JS_VALUE_GET_INT (op1) >> v2); - sp--; - } else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2) - || (JS_VALUE_GET_TAG (op1) == JS_TAG_INT - && JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op2))) - || (JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op1)) - && JS_VALUE_GET_TAG (op2) == JS_TAG_INT)) { - int32_t v1; - uint32_t v2; - v1 = JS_VALUE_GET_TAG (op1) == JS_TAG_INT - ? JS_VALUE_GET_INT (op1) - : (int32_t)JS_VALUE_GET_FLOAT64 (op1); - v2 = JS_VALUE_GET_TAG (op2) == JS_TAG_INT - ? JS_VALUE_GET_INT (op2) - : (int32_t)JS_VALUE_GET_FLOAT64 (op2); - v2 &= 0x1f; - sp[-2] = JS_NewInt32 (ctx, v1 >> v2); - sp--; - } else { - sp[-2] = JS_NULL; - sp--; - } - } - BREAK; - CASE (OP_and) : { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { - sp[-2] = JS_NewInt32 (ctx, JS_VALUE_GET_INT (op1) & JS_VALUE_GET_INT (op2)); - sp--; - } else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2) - || (JS_VALUE_GET_TAG (op1) == JS_TAG_INT - && JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op2))) - || (JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op1)) - && JS_VALUE_GET_TAG (op2) == JS_TAG_INT)) { - int32_t v1, v2; - v1 = JS_VALUE_GET_TAG (op1) == JS_TAG_INT - ? JS_VALUE_GET_INT (op1) - : (int32_t)JS_VALUE_GET_FLOAT64 (op1); - v2 = JS_VALUE_GET_TAG (op2) == JS_TAG_INT - ? JS_VALUE_GET_INT (op2) - : (int32_t)JS_VALUE_GET_FLOAT64 (op2); - sp[-2] = JS_NewInt32 (ctx, v1 & v2); - sp--; - } else { - sp[-2] = JS_NULL; - sp--; - } - } - BREAK; - CASE (OP_or) : { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { - sp[-2] = JS_NewInt32 (ctx, JS_VALUE_GET_INT (op1) | JS_VALUE_GET_INT (op2)); - sp--; - } else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2) - || (JS_VALUE_GET_TAG (op1) == JS_TAG_INT - && JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op2))) - || (JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op1)) - && JS_VALUE_GET_TAG (op2) == JS_TAG_INT)) { - int32_t v1, v2; - v1 = JS_VALUE_GET_TAG (op1) == JS_TAG_INT - ? JS_VALUE_GET_INT (op1) - : (int32_t)JS_VALUE_GET_FLOAT64 (op1); - v2 = JS_VALUE_GET_TAG (op2) == JS_TAG_INT - ? JS_VALUE_GET_INT (op2) - : (int32_t)JS_VALUE_GET_FLOAT64 (op2); - sp[-2] = JS_NewInt32 (ctx, v1 | v2); - sp--; - } else { - sp[-2] = JS_NULL; - sp--; - } - } - BREAK; - CASE (OP_xor) : { - JSValue op1, op2; - op1 = sp[-2]; - op2 = sp[-1]; - if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { - sp[-2] = JS_NewInt32 (ctx, JS_VALUE_GET_INT (op1) ^ JS_VALUE_GET_INT (op2)); - sp--; - } else if (JS_VALUE_IS_BOTH_FLOAT (op1, op2) - || (JS_VALUE_GET_TAG (op1) == JS_TAG_INT - && JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op2))) - || (JS_TAG_IS_FLOAT64 (JS_VALUE_GET_TAG (op1)) - && JS_VALUE_GET_TAG (op2) == JS_TAG_INT)) { - int32_t v1, v2; - v1 = JS_VALUE_GET_TAG (op1) == JS_TAG_INT - ? JS_VALUE_GET_INT (op1) - : (int32_t)JS_VALUE_GET_FLOAT64 (op1); - v2 = JS_VALUE_GET_TAG (op2) == JS_TAG_INT - ? JS_VALUE_GET_INT (op2) - : (int32_t)JS_VALUE_GET_FLOAT64 (op2); - sp[-2] = JS_NewInt32 (ctx, v1 ^ v2); - sp--; - } else { - sp[-2] = JS_NULL; - sp--; - } - } - BREAK; - -#define OP_CMP(opcode, binary_op, slow_call) \ - CASE (opcode) : { \ - JSValue op1, op2; \ - op1 = sp[-2]; \ - op2 = sp[-1]; \ - if (likely (JS_VALUE_IS_BOTH_INT (op1, op2))) { \ - sp[-2] = JS_NewBool (ctx, JS_VALUE_GET_INT (op1) binary_op JS_VALUE_GET_INT (op2)); \ - sp--; \ - } else { \ - sf->cur_pc = pc; \ - if (slow_call) goto exception; \ - sp--; \ - } \ - } \ - BREAK - - OP_CMP (OP_lt, <, js_relational_slow (ctx, sp, opcode)); - OP_CMP (OP_lte, <=, js_relational_slow (ctx, sp, opcode)); - OP_CMP (OP_gt, >, js_relational_slow (ctx, sp, opcode)); - OP_CMP (OP_gte, >=, js_relational_slow (ctx, sp, opcode)); - OP_CMP (OP_strict_eq, ==, js_strict_eq_slow (ctx, sp, 0)); - OP_CMP (OP_strict_neq, !=, js_strict_eq_slow (ctx, sp, 1)); - - CASE (OP_in) : sf->cur_pc = pc; - if (js_operator_in (ctx, sp)) goto exception; - sp--; - BREAK; - CASE (OP_delete) : sf->cur_pc = pc; - if (js_operator_delete (ctx, sp)) goto exception; - sp--; - BREAK; - - CASE (OP_delete_var) : - pc += 4; - JS_ThrowInternalError (ctx, "OP_delete_var: global object is immutable"); - goto exception; - BREAK; - - CASE (OP_to_propkey) : switch (JS_VALUE_GET_TAG (sp[-1])) { - case JS_TAG_INT: - case JS_TAG_STRING: - break; - default: - sf->cur_pc = pc; - ret_val = sp[-1]; // JS_ToPropertyKey (ctx, sp[-1]); // is this correct? No conversion can happen. - if (JS_IsException (ret_val)) goto exception; - sp[-1] = ret_val; - break; - } - BREAK; - - CASE (OP_format_template) : { - int expr_count = get_u16 (pc); pc += 2; - uint32_t cpool_idx = get_u32 (pc); pc += 4; - - /* Expression values are on the stack. We'll process them in place, - building the result string using pretext. */ - JSText *result = pretext_init (ctx, 64); - if (!result) goto exception; - - /* Re-read format_str after pretext_init (may have triggered GC) */ - JSValue format_str = b->cpool[cpool_idx]; - - /* Parse format string and substitute {N} with stringified stack values. - Format string is like "hello {0} world {1}" for `hello ${a} world ${b}`. - Each {N} refers to stack value at position N (0-indexed from base). */ - JSValue *expr_base = sp - expr_count; - int fmt_len = js_string_value_len (format_str); - int pos = 0; - - while (pos < fmt_len) { - /* Re-read format_str in case GC moved the cpool entry */ - format_str = b->cpool[cpool_idx]; - - /* Find next '{' */ - int brace_start = -1; - for (int i = pos; i < fmt_len; i++) { - if (js_string_value_get (format_str, i) == '{') { - brace_start = i; - break; - } - } - - if (brace_start < 0) { - /* No more braces, copy rest of string */ - format_str = b->cpool[cpool_idx]; /* Re-read */ - for (int i = pos; i < fmt_len; i++) { - result = pretext_putc (ctx, result, js_string_value_get (format_str, i)); - if (!result) goto exception; - } - break; - } - - /* Copy text before brace */ - format_str = b->cpool[cpool_idx]; /* Re-read */ - for (int i = pos; i < brace_start; i++) { - result = pretext_putc (ctx, result, js_string_value_get (format_str, i)); - if (!result) goto exception; - } - - /* Find closing '}' */ - format_str = b->cpool[cpool_idx]; /* Re-read */ - int brace_end = -1; - for (int i = brace_start + 1; i < fmt_len; i++) { - if (js_string_value_get (format_str, i) == '}') { - brace_end = i; - break; - } - } - - if (brace_end < 0) { - /* No closing brace, copy '{' and continue */ - result = pretext_putc (ctx, result, '{'); - if (!result) goto exception; - pos = brace_start + 1; - continue; - } - - /* Parse index from {N} */ - int idx = 0; - format_str = b->cpool[cpool_idx]; /* Re-read */ - for (int i = brace_start + 1; i < brace_end; i++) { - uint32_t c = js_string_value_get (format_str, i); - if (c >= '0' && c <= '9') { - idx = idx * 10 + (c - '0'); - } else { - idx = -1; /* Invalid */ - break; - } - } - - if (idx >= 0 && idx < expr_count) { - /* Valid index, stringify the stack value */ - JSValue val = expr_base[idx]; - JSValue str_val = JS_ToString (ctx, val); - if (JS_IsException (str_val)) { - goto exception; - } - /* Append stringified value to result */ - int str_len = js_string_value_len (str_val); - for (int i = 0; i < str_len; i++) { - result = pretext_putc (ctx, result, js_string_value_get (str_val, i)); - if (!result) goto exception; - } - } else { - /* Invalid index, keep original {N} */ - format_str = b->cpool[cpool_idx]; /* Re-read */ - for (int i = brace_start; i <= brace_end; i++) { - result = pretext_putc (ctx, result, js_string_value_get (format_str, i)); - if (!result) goto exception; - } - } - - pos = brace_end + 1; - } - - /* Finalize result string */ - JSValue result_str = pretext_end (ctx, result); - if (JS_IsException (result_str)) goto exception; - - /* Pop expr values, push result */ - sp -= expr_count; - *sp++ = result_str; - } - BREAK; - - /* Upvalue access via outer_frame chain (Phase 5) */ - CASE (OP_get_up) : { - int depth = *pc++; - int slot = get_u16 (pc); pc += 2; - JSValue *p = get_upvalue_ptr(sf->js_frame, depth, slot); - if (!p) { - JS_ThrowInternalError(ctx, "invalid upvalue: depth=%d slot=%d", depth, slot); - goto exception; - } - *sp++ = *p; - } - BREAK; - - CASE (OP_set_up) : { - int depth = *pc++; - int slot = get_u16 (pc); pc += 2; - JSValue *p = get_upvalue_ptr(sf->js_frame, depth, slot); - if (!p) { - JS_ThrowInternalError(ctx, "invalid upvalue: depth=%d slot=%d", depth, slot); - goto exception; - } - *p = *--sp; - } - BREAK; - - /* Name resolution with bytecode patching (Phase 6) */ - CASE (OP_get_name) : { - uint32_t idx = get_u32 (pc); pc += 4; - (void)idx; - JS_ThrowInternalError (ctx, "OP_get_name not yet implemented"); - goto exception; - } - BREAK; - - CASE (OP_get_env_slot) : { - int slot = get_u16 (pc); pc += 2; - /* Get env_record from current function, not global ctx->eval_env */ - JSFunction *fn = JS_VALUE_GET_FUNCTION (sf->cur_func); - JSValue env_val = fn->u.func.env_record; - if (JS_IsNull (env_val)) { - JS_ThrowReferenceError (ctx, "no environment record"); - goto exception; - } - JSRecord *env = (JSRecord *)JS_VALUE_GET_OBJ (env_val); - *sp++ = env->slots[slot].val; - } - BREAK; - - CASE (OP_set_env_slot) : { - int slot = get_u16 (pc); pc += 2; - /* Get env_record from current function */ - JSFunction *fn = JS_VALUE_GET_FUNCTION (sf->cur_func); - JSValue env_val = fn->u.func.env_record; - if (JS_IsNull (env_val)) { - JS_ThrowReferenceError (ctx, "no environment record"); - goto exception; - } - JSRecord *env = (JSRecord *)JS_VALUE_GET_OBJ (env_val); - env->slots[slot].val = *--sp; - } - BREAK; - - CASE (OP_get_global_slot) : { - int slot = get_u16 (pc); pc += 2; - JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); - *sp++ = global->slots[slot].val; - } - BREAK; - - CASE (OP_set_global_slot) : { - int slot = get_u16 (pc); pc += 2; - JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); - global->slots[slot].val = *--sp; - } - BREAK; - - CASE (OP_nop) : BREAK; - CASE (OP_is_null) : if (JS_VALUE_GET_TAG (sp[-1]) == JS_TAG_NULL) { - goto set_true; - } - else { goto free_and_set_false; } - set_true: - sp[-1] = JS_TRUE; - BREAK; - free_and_set_false: - sp[-1] = JS_FALSE; - BREAK; - CASE (OP_invalid) - : DEFAULT - : JS_ThrowInternalError (ctx, "invalid opcode: pc=%u opcode=0x%02x", (int)(pc - b->byte_code_buf - 1), opcode); - goto exception; - } - } -exception: - if (is_backtrace_needed (ctx, ctx->current_exception)) { - /* add the backtrace information now (it is not done - before if the exception happens in a bytecode - operation */ - sf->cur_pc = pc; - build_backtrace (ctx, ctx->current_exception, NULL, 0, 0, 0); - } - /* All exceptions are catchable in the simplified runtime */ - while (sp > stack_buf) { - JSValue val = *--sp; - if (JS_VALUE_GET_TAG (val) == JS_TAG_CATCH_OFFSET) { - int pos = JS_VALUE_GET_INT (val); - if (pos != 0) { - *sp++ = ctx->current_exception; - ctx->current_exception = JS_UNINITIALIZED; - pc = b->byte_code_buf + pos; - goto restart; - } - } - } - ret_val = JS_EXCEPTION; -done: - ctx->current_stack_frame = sf->prev_frame; - - if (unlikely (caller_ctx->trace_hook) - && (caller_ctx->trace_type & JS_HOOK_RET)) - caller_ctx->trace_hook (caller_ctx, JS_HOOK_RET, NULL, caller_ctx->trace_data); - - return ret_val; -} - -JSValue JS_Call (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv) { - return JS_CallInternal (ctx, func_obj, this_obj, argc, (JSValue *)argv, JS_CALL_FLAG_COPY_ARGV); -} - -/* JS parser */ - -enum { - TOK_NUMBER = -128, - TOK_STRING, - TOK_TEMPLATE, - TOK_IDENT, - TOK_REGEXP, - /* warning: order matters (see js_parse_assign_expr) */ - TOK_MUL_ASSIGN, - TOK_DIV_ASSIGN, - TOK_MOD_ASSIGN, - TOK_PLUS_ASSIGN, - TOK_MINUS_ASSIGN, - TOK_SHL_ASSIGN, - TOK_SAR_ASSIGN, - TOK_SHR_ASSIGN, - TOK_AND_ASSIGN, - TOK_XOR_ASSIGN, - TOK_OR_ASSIGN, - TOK_POW_ASSIGN, - TOK_LAND_ASSIGN, - TOK_LOR_ASSIGN, - TOK_DEC, - TOK_INC, - TOK_SHL, - TOK_SAR, - TOK_SHR, - TOK_LT, - TOK_LTE, - TOK_GT, - TOK_GTE, - TOK_EQ, - TOK_STRICT_EQ, - TOK_NEQ, - TOK_STRICT_NEQ, - TOK_LAND, - TOK_LOR, - TOK_POW, - TOK_ARROW, - TOK_ERROR, - TOK_PRIVATE_NAME, - TOK_EOF, - /* whitespace/comment tokens for tokenizer */ - TOK_COMMENT, - TOK_NEWLINE, - TOK_SPACE, - /* keywords: WARNING: same order as atoms */ - TOK_NULL, /* must be first */ - TOK_FALSE, - TOK_TRUE, - TOK_IF, - TOK_ELSE, - TOK_RETURN, - TOK_GO, - TOK_VAR, - TOK_DEF, - TOK_THIS, - TOK_DELETE, - TOK_IN, - TOK_DO, - TOK_WHILE, - TOK_FOR, - TOK_BREAK, - TOK_CONTINUE, - TOK_DISRUPT, - TOK_DISRUPTION, - TOK_FUNCTION, - TOK_DEBUGGER, - TOK_WITH, - /* FutureReservedWord */ - TOK_CLASS, - TOK_CONST, - TOK_ENUM, - TOK_EXPORT, - TOK_EXTENDS, - TOK_IMPORT, - TOK_SUPER, - /* FutureReservedWords when parsing strict mode code */ - TOK_IMPLEMENTS, - TOK_INTERFACE, - TOK_LET, - TOK_PRIVATE, - TOK_PROTECTED, - TOK_PUBLIC, - TOK_STATIC, - TOK_YIELD, - TOK_AWAIT, /* must be last */ - TOK_OF, /* only used for js_parse_skip_parens_token() */ -}; - -#define TOK_FIRST_KEYWORD TOK_NULL -#define TOK_LAST_KEYWORD TOK_AWAIT - -/* unicode code points */ -#define CP_NBSP 0x00a0 -#define CP_BOM 0xfeff - -#define CP_LS 0x2028 -#define CP_PS 0x2029 - -/* Map token values to kind strings for tokenizer output */ -static const char *ast_token_kind_str(int token_val) { - static char single_char[2] = {0, 0}; - switch (token_val) { - case TOK_NUMBER: return "number"; - case TOK_STRING: return "text"; - case TOK_TEMPLATE: return "text"; - case TOK_IDENT: return "name"; - case TOK_COMMENT: return "comment"; - case TOK_NEWLINE: return "newline"; - case TOK_SPACE: return "space"; - case TOK_REGEXP: return "regexp"; - case TOK_PRIVATE_NAME: return "private_name"; - case TOK_EOF: return "eof"; - case TOK_ERROR: return "error"; - /* compound operators */ - case TOK_MUL_ASSIGN: return "*="; - case TOK_DIV_ASSIGN: return "/="; - case TOK_MOD_ASSIGN: return "%="; - case TOK_PLUS_ASSIGN: return "+="; - case TOK_MINUS_ASSIGN: return "-="; - case TOK_SHL_ASSIGN: return "<<="; - case TOK_SAR_ASSIGN: return ">>="; - case TOK_SHR_ASSIGN: return ">>>="; - case TOK_AND_ASSIGN: return "&="; - case TOK_XOR_ASSIGN: return "^="; - case TOK_OR_ASSIGN: return "|="; - case TOK_POW_ASSIGN: return "**="; - case TOK_LAND_ASSIGN: return "&&="; - case TOK_LOR_ASSIGN: return "||="; - case TOK_DEC: return "--"; - case TOK_INC: return "++"; - case TOK_SHL: return "<<"; - case TOK_SAR: return ">>"; - case TOK_SHR: return ">>>"; - case TOK_LT: return "<"; - case TOK_LTE: return "<="; - case TOK_GT: return ">"; - case TOK_GTE: return ">="; - case TOK_EQ: return "=="; - case TOK_STRICT_EQ: return "==="; - case TOK_NEQ: return "!="; - case TOK_STRICT_NEQ: return "!=="; - case TOK_LAND: return "&&"; - case TOK_LOR: return "||"; - case TOK_POW: return "**"; - case TOK_ARROW: return "=>"; - /* keywords */ - case TOK_NULL: return "null"; - case TOK_FALSE: return "false"; - case TOK_TRUE: return "true"; - case TOK_IF: return "if"; - case TOK_ELSE: return "else"; - case TOK_RETURN: return "return"; - case TOK_GO: return "go"; - case TOK_VAR: return "var"; - case TOK_DEF: return "def"; - case TOK_THIS: return "this"; - case TOK_DELETE: return "delete"; - case TOK_IN: return "in"; - case TOK_DO: return "do"; - case TOK_WHILE: return "while"; - case TOK_FOR: return "for"; - case TOK_BREAK: return "break"; - case TOK_CONTINUE: return "continue"; - case TOK_DISRUPT: return "disrupt"; - case TOK_DISRUPTION: return "disruption"; - case TOK_FUNCTION: return "function"; - case TOK_DEBUGGER: return "debugger"; - case TOK_WITH: return "with"; - case TOK_CLASS: return "class"; - case TOK_CONST: return "const"; - case TOK_ENUM: return "enum"; - case TOK_EXPORT: return "export"; - case TOK_EXTENDS: return "extends"; - case TOK_IMPORT: return "import"; - case TOK_SUPER: return "super"; - case TOK_IMPLEMENTS: return "implements"; - case TOK_INTERFACE: return "interface"; - case TOK_LET: return "let"; - case TOK_PRIVATE: return "private"; - case TOK_PROTECTED: return "protected"; - case TOK_PUBLIC: return "public"; - case TOK_STATIC: return "static"; - case TOK_YIELD: return "yield"; - case TOK_AWAIT: return "await"; - case TOK_OF: return "of"; - default: - /* Single character tokens */ - if (token_val >= 0 && token_val < 128) { - single_char[0] = (char)token_val; - return single_char; - } - return "unknown"; - } -} - -typedef struct BlockEnv { - struct BlockEnv *prev; - JSValue label_name; /* JS_NULL if none */ - int label_break; /* -1 if none */ - int label_cont; /* -1 if none */ - int drop_count; /* number of stack elements to drop */ - int label_finally; /* -1 if none */ - int scope_level; - uint8_t is_regular_stmt : 1; /* i.e. not a loop statement */ -} BlockEnv; - -typedef struct JSGlobalVar { - int cpool_idx; /* if >= 0, index in the constant pool for hoisted - function defintion*/ - uint8_t force_init : 1; /* force initialization to undefined */ - uint8_t is_lexical : 1; /* global let/const definition */ - uint8_t is_const : 1; /* const definition */ - int scope_level; /* scope of definition */ - JSValue var_name; /* variable name as JSValue text */ -} JSGlobalVar; - -typedef struct RelocEntry { - struct RelocEntry *next; - uint32_t addr; /* address to patch */ - int size; /* address size: 1, 2 or 4 bytes */ -} RelocEntry; - -typedef struct JumpSlot { - int op; - int size; - int pos; - int label; -} JumpSlot; - -typedef struct LabelSlot { - int ref_count; - int pos; /* phase 1 address, -1 means not resolved yet */ - int pos2; /* phase 2 address, -1 means not resolved yet */ - int addr; /* phase 3 address, -1 means not resolved yet */ - RelocEntry *first_reloc; -} LabelSlot; - -typedef struct LineNumberSlot { - uint32_t pc; - uint32_t source_pos; -} LineNumberSlot; - -typedef struct { - /* last source position */ - const uint8_t *ptr; - int line_num; - int col_num; - const uint8_t *buf_start; -} GetLineColCache; - -typedef enum JSParseFunctionEnum { - JS_PARSE_FUNC_STATEMENT, - JS_PARSE_FUNC_VAR, - JS_PARSE_FUNC_EXPR, - JS_PARSE_FUNC_ARROW, - JS_PARSE_FUNC_GETTER, - JS_PARSE_FUNC_SETTER, - JS_PARSE_FUNC_METHOD, - JS_PARSE_FUNC_CLASS_STATIC_INIT, -} JSParseFunctionEnum; - -typedef struct JSFunctionDef { - JSContext *ctx; - struct JSFunctionDef *parent; - int parent_cpool_idx; /* index in the constant pool of the parent - or -1 if none */ - int parent_scope_level; /* scope level in parent at point of definition */ - struct list_head child_list; /* list of JSFunctionDef.link */ - struct list_head link; - - BOOL is_global_var; /* TRUE if variables are not defined locally */ - BOOL is_func_expr; /* TRUE if function expression */ - BOOL has_prototype; /* true if a prototype field is necessary */ - BOOL has_simple_parameter_list; - BOOL has_parameter_expressions; /* if true, an argument scope is created */ - BOOL has_use_strict; /* to reject directive in special cases */ - BOOL has_this_binding; /* true if the 'this' binding is available in the - function */ - BOOL in_function_body; - JSParseFunctionEnum func_type : 8; - uint8_t js_mode; /* bitmap of JS_MODE_x */ - JSValue func_name; /* JS_NULL if no name */ - - JSVarDef *vars; - int var_size; /* allocated size for vars[] */ - int var_count; - JSVarDef *args; - int arg_size; /* allocated size for args[] */ - int arg_count; /* number of arguments */ - int defined_arg_count; - int var_object_idx; /* -1 if none */ - int arg_var_object_idx; /* -1 if none (var object for the argument scope) */ - int func_var_idx; /* variable containing the current function (-1 - if none, only used if is_func_expr is true) */ - int this_var_idx; /* variable containg the 'this' value, -1 if none */ - int this_active_func_var_idx; /* variable containg the 'this.active_func' - value, -1 if none */ - - int scope_level; /* index into fd->scopes if the current lexical scope */ - int scope_first; /* index into vd->vars of first lexically scoped variable */ - int scope_size; /* allocated size of fd->scopes array */ - int scope_count; /* number of entries used in the fd->scopes array */ - JSVarScope *scopes; - JSVarScope def_scope_array[4]; - int body_scope; /* scope of the body of the function or eval */ - - int global_var_count; - int global_var_size; - JSGlobalVar *global_vars; - - DynBuf byte_code; - int last_opcode_pos; /* -1 if no last opcode */ - const uint8_t *last_opcode_source_ptr; - BOOL use_short_opcodes; /* true if short opcodes are used in byte_code */ - - LabelSlot *label_slots; - int label_size; /* allocated size for label_slots[] */ - int label_count; - BlockEnv *top_break; /* break/continue label stack */ - - /* constant pool (strings, functions, numbers) */ - JSValue *cpool; - int cpool_count; - int cpool_size; - - /* list of variables in the closure */ - int closure_var_count; - int closure_var_size; - JSClosureVar *closure_var; - - JumpSlot *jump_slots; - int jump_size; - int jump_count; - - LineNumberSlot *line_number_slots; - int line_number_size; - int line_number_count; - int line_number_last; - int line_number_last_pc; - - /* pc2line table */ - BOOL strip_debug - : 1; /* strip all debug info (implies strip_source = TRUE) */ - BOOL strip_source : 1; /* strip only source code */ - JSValue filename; - uint32_t source_pos; /* pointer in the eval() source */ - GetLineColCache *get_line_col_cache; /* XXX: could remove to save memory */ - DynBuf pc2line; - - char *source; /* raw source, utf-8 encoded */ - int source_len; -} JSFunctionDef; - -/* Scan a single JSFunctionDef's cpool and recursively scan its children */ -static void gc_scan_parser_fd (JSContext *ctx, JSFunctionDef *fd, - uint8_t *from_base, uint8_t *from_end, - uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) { - if (!fd) return; - /* Scan constant pool */ - for (int i = 0; i < fd->cpool_count; i++) { - fd->cpool[i] = gc_copy_value (ctx, fd->cpool[i], from_base, from_end, to_base, to_free, to_end); - } - /* Scan func_name */ - fd->func_name = gc_copy_value (ctx, fd->func_name, from_base, from_end, to_base, to_free, to_end); - /* Scan filename */ - fd->filename = gc_copy_value (ctx, fd->filename, from_base, from_end, to_base, to_free, to_end); - /* Recursively scan child functions */ - struct list_head *el; - list_for_each (el, &fd->child_list) { - JSFunctionDef *child = list_entry (el, JSFunctionDef, link); - gc_scan_parser_fd (ctx, child, from_base, from_end, to_base, to_free, to_end); - } -} - -/* Scan parser's cpool values during GC - called when parsing is in progress */ -static void gc_scan_parser_cpool (JSContext *ctx, uint8_t *from_base, uint8_t *from_end, - uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) { - gc_scan_parser_fd (ctx, ctx->current_parse_fd, from_base, from_end, to_base, to_free, to_end); -} - -typedef struct JSToken { - int val; - const uint8_t *ptr; /* position in the source */ - union { - struct { - JSValue str; - int sep; - } str; - struct { - JSValue val; - } num; - struct { - JSValue str; /* identifier as JSValue text */ - BOOL has_escape; - BOOL is_reserved; - } ident; - struct { - JSValue body; - JSValue flags; - } regexp; - } u; -} JSToken; - -typedef struct JSParseState { - JSContext *ctx; - const char *filename; - JSToken token; - BOOL got_lf; /* true if got line feed before the current token */ - const uint8_t *last_ptr; - const uint8_t *buf_start; - const uint8_t *buf_ptr; - const uint8_t *buf_end; - - /* current function code */ - JSFunctionDef *cur_func; - BOOL ext_json; /* true if accepting JSON superset */ - GetLineColCache get_line_col_cache; -} JSParseState; - -typedef struct JSOpCode { -#ifdef DUMP_BYTECODE - const char *name; -#endif - uint8_t size; /* in bytes */ - /* the opcodes remove n_pop items from the top of the stack, then - pushes n_push items */ - uint8_t n_pop; - uint8_t n_push; - uint8_t fmt; -} JSOpCode; - -static const JSOpCode opcode_info[OP_COUNT + (OP_TEMP_END - OP_TEMP_START)] = { -#define FMT(f) -#ifdef DUMP_BYTECODE -#define DEF(id, size, n_pop, n_push, f) \ - { #id, size, n_pop, n_push, OP_FMT_##f }, -#else -#define DEF(id, size, n_pop, n_push, f) { size, n_pop, n_push, OP_FMT_##f }, -#endif -#include "quickjs-opcode.h" -#undef DEF -#undef FMT -}; - -#if SHORT_OPCODES -/* After the final compilation pass, short opcodes are used. Their - opcodes overlap with the temporary opcodes which cannot appear in - the final bytecode. Their description is after the temporary - opcodes in opcode_info[]. */ -#define short_opcode_info(op) \ - opcode_info[(op) >= OP_TEMP_START ? (op) + (OP_TEMP_END - OP_TEMP_START) \ - : (op)] -#else -#define short_opcode_info(op) opcode_info[op] -#endif - -/* Clone bytecode and resolve OP_get_var to OP_get_global_slot. - Returns new bytecode on success, NULL on link error. - The linked bytecode is a separate allocation that can be modified. - Note: closure_var is not copied - closures use outer_frame chain at runtime. */ -static JSFunctionBytecode *js_link_bytecode (JSContext *ctx, - JSFunctionBytecode *tpl, - JSValue env) { - /* Calculate total size of bytecode allocation */ - int function_size; - int cpool_offset, vardefs_offset, byte_code_offset; - - if (tpl->has_debug) { - function_size = sizeof (JSFunctionBytecode); - } else { - function_size = offsetof (JSFunctionBytecode, debug); - } - - cpool_offset = function_size; - function_size += tpl->cpool_count * sizeof (JSValue); - - vardefs_offset = function_size; - if (tpl->vardefs) { - function_size += (tpl->arg_count + tpl->var_count) * sizeof (JSVarDef); - } - - /* closure_var not needed at runtime - closures use outer_frame chain */ - - byte_code_offset = function_size; - function_size += tpl->byte_code_len; - - /* Allocate and copy */ - JSFunctionBytecode *linked = pjs_malloc (function_size); - if (!linked) return NULL; - - /* Copy base structure */ - if (tpl->has_debug) { - memcpy (linked, tpl, sizeof (JSFunctionBytecode)); - } else { - memcpy (linked, tpl, offsetof (JSFunctionBytecode, debug)); - } - - /* Clear closure_var - not needed at runtime */ - linked->closure_var = NULL; - linked->closure_var_count = 0; - - /* Fix up self pointers */ - if (tpl->cpool_count > 0) { - linked->cpool = (JSValue *)((uint8_t *)linked + cpool_offset); - memcpy (linked->cpool, tpl->cpool, tpl->cpool_count * sizeof (JSValue)); - } - - if (tpl->vardefs) { - linked->vardefs = (JSVarDef *)((uint8_t *)linked + vardefs_offset); - memcpy (linked->vardefs, tpl->vardefs, - (tpl->arg_count + tpl->var_count) * sizeof (JSVarDef)); - } - - linked->byte_code_buf = (uint8_t *)linked + byte_code_offset; - memcpy (linked->byte_code_buf, tpl->byte_code_buf, tpl->byte_code_len); - - /* Mark as writable (for patching) */ - linked->read_only_bytecode = 0; - - /* Walk bytecode and patch global variable access opcodes */ - uint8_t *bc = linked->byte_code_buf; - int pos = 0; - - /* Get env record if provided */ - JSRecord *env_rec = NULL; - if (!JS_IsNull (env) && JS_IsRecord (env)) { - env_rec = (JSRecord *)JS_VALUE_GET_OBJ (env); - } - - while (pos < linked->byte_code_len) { - uint8_t op = bc[pos]; - int len = short_opcode_info (op).size; - - /* Patch OP_get_var -> OP_get_global_slot or OP_get_env_slot */ - if (op == OP_get_var || op == OP_get_var_undef) { - uint32_t cpool_idx = get_u32 (bc + pos + 1); - JSValue name = linked->cpool[cpool_idx]; - - /* Try env first (if provided) */ - if (env_rec) { - int slot = rec_find_slot (env_rec, name); - if (slot > 0) { - bc[pos] = OP_get_env_slot; - put_u16 (bc + pos + 1, (uint16_t)slot); - bc[pos + 3] = OP_nop; - bc[pos + 4] = OP_nop; - pos += len; - continue; - } - } - - /* Try global_obj (intrinsics like 'print') */ - JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); - int slot = rec_find_slot (global, name); - if (slot > 0) { - bc[pos] = OP_get_global_slot; - put_u16 (bc + pos + 1, (uint16_t)slot); - bc[pos + 3] = OP_nop; - bc[pos + 4] = OP_nop; - pos += len; - continue; - } - - /* Link error: variable not found */ - if (op == OP_get_var) { - char buf[64]; - JS_ThrowReferenceError (ctx, "'%s' is not defined", - JS_KeyGetStr (ctx, buf, sizeof (buf), name)); - pjs_free (linked); - return NULL; - } - /* OP_get_var_undef is ok - leaves as is for runtime check */ - } - - /* Patch OP_put_var family -> OP_set_env_slot or error */ - if (op == OP_put_var || op == OP_put_var_init || op == OP_put_var_strict) { - uint32_t cpool_idx = get_u32 (bc + pos + 1); - JSValue name = linked->cpool[cpool_idx]; - - /* Try env first (if provided) - env is writable */ - if (env_rec) { - int slot = rec_find_slot (env_rec, name); - if (slot > 0) { - bc[pos] = OP_set_env_slot; - put_u16 (bc + pos + 1, (uint16_t)slot); - bc[pos + 3] = OP_nop; - bc[pos + 4] = OP_nop; - pos += len; - continue; - } - } - - /* Try global for set_global_slot */ - JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); - int slot = rec_find_slot (global, name); - if (slot > 0) { - bc[pos] = OP_set_global_slot; - put_u16 (bc + pos + 1, (uint16_t)slot); - bc[pos + 3] = OP_nop; - bc[pos + 4] = OP_nop; - pos += len; - continue; - } - - /* Global object is immutable - can't write to intrinsics */ - char buf[64]; - JS_ThrowReferenceError (ctx, "cannot assign to '%s' - not found in environment", - JS_KeyGetStr (ctx, buf, sizeof (buf), name)); - pjs_free (linked); - return NULL; - } - - /* Patch OP_check_var -> OP_nop (if variable exists) */ - if (op == OP_check_var) { - uint32_t cpool_idx = get_u32 (bc + pos + 1); - JSValue name = linked->cpool[cpool_idx]; - - BOOL found = FALSE; - if (env_rec && rec_find_slot (env_rec, name) > 0) { - found = TRUE; - } - if (!found) { - JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); - if (rec_find_slot (global, name) > 0) { - found = TRUE; - } - } - - if (found) { - /* Variable exists, replace with NOPs */ - bc[pos] = OP_nop; - bc[pos + 1] = OP_nop; - bc[pos + 2] = OP_nop; - bc[pos + 3] = OP_nop; - bc[pos + 4] = OP_nop; - } - /* else leave check_var for runtime */ - } - - pos += len; - } - - return linked; -} - -/* New simplified linker producing JSCompiledUnit. - Converts JSFunctionBytecode template to JSCompiledUnit: - - Copies bytecode, cpool (no vardefs, no closure_var) - - Patches OP_get_var -> OP_get_env_slot or OP_get_global_slot - - Returns standalone unit ready for execution */ -static JSCompiledUnit *js_link_unit (JSContext *ctx, - JSFunctionBytecode *tpl, - JSValue env) { - int function_size; - int cpool_offset, byte_code_offset; - - /* Calculate size: base struct + cpool + bytecode */ - if (tpl->has_debug) { - function_size = sizeof (JSCompiledUnit); - } else { - function_size = offsetof (JSCompiledUnit, debug); - } - - cpool_offset = function_size; - function_size += tpl->cpool_count * sizeof (JSValue); - - byte_code_offset = function_size; - function_size += tpl->byte_code_len; - - /* Allocate */ - JSCompiledUnit *unit = pjs_malloc (function_size); - if (!unit) return NULL; - - /* Initialize header */ - unit->header = objhdr_make (0, OBJ_CODE, false, false, false, false); - unit->has_debug = tpl->has_debug; - unit->read_only_bytecode = 0; - - /* Copy stack requirements */ - unit->local_count = tpl->arg_count + tpl->var_count; - unit->stack_size = tpl->stack_size; - - /* Setup cpool */ - unit->cpool_count = tpl->cpool_count; - if (tpl->cpool_count > 0) { - unit->cpool = (JSValue *)((uint8_t *)unit + cpool_offset); - memcpy (unit->cpool, tpl->cpool, tpl->cpool_count * sizeof (JSValue)); - } else { - unit->cpool = NULL; - } - - /* Copy bytecode */ - unit->byte_code_buf = (uint8_t *)unit + byte_code_offset; - unit->byte_code_len = tpl->byte_code_len; - memcpy (unit->byte_code_buf, tpl->byte_code_buf, tpl->byte_code_len); - - /* Copy debug info if present */ - if (tpl->has_debug) { - unit->debug.filename = tpl->debug.filename; - unit->debug.source_len = tpl->debug.source_len; - unit->debug.pc2line_len = tpl->debug.pc2line_len; - unit->debug.pc2line_buf = tpl->debug.pc2line_buf; - unit->debug.source = tpl->debug.source; - } - - /* Walk bytecode and patch global variable access opcodes */ - uint8_t *bc = unit->byte_code_buf; - int pos = 0; - - /* Get env record if provided */ - JSRecord *env_rec = NULL; - if (!JS_IsNull (env) && JS_IsRecord (env)) { - env_rec = (JSRecord *)JS_VALUE_GET_OBJ (env); - } - - while (pos < unit->byte_code_len) { - uint8_t op = bc[pos]; - int len = short_opcode_info (op).size; - - /* Patch OP_get_var -> OP_get_global_slot or OP_get_env_slot */ - if (op == OP_get_var || op == OP_get_var_undef) { - uint32_t cpool_idx = get_u32 (bc + pos + 1); - JSValue name = unit->cpool[cpool_idx]; - - /* Try env first (if provided) */ - if (env_rec) { - int slot = rec_find_slot (env_rec, name); - if (slot > 0) { - bc[pos] = OP_get_env_slot; - put_u16 (bc + pos + 1, (uint16_t)slot); - bc[pos + 3] = OP_nop; - bc[pos + 4] = OP_nop; - pos += len; - continue; - } - } - - /* Try global_obj (intrinsics like 'print') */ - JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); - int slot = rec_find_slot (global, name); - if (slot > 0) { - bc[pos] = OP_get_global_slot; - put_u16 (bc + pos + 1, (uint16_t)slot); - bc[pos + 3] = OP_nop; - bc[pos + 4] = OP_nop; - pos += len; - continue; - } - - /* Link error: variable not found */ - if (op == OP_get_var) { - char buf[64]; - JS_ThrowReferenceError (ctx, "'%s' is not defined", - JS_KeyGetStr (ctx, buf, sizeof (buf), name)); - pjs_free (unit); - return NULL; - } - } - - /* Patch OP_put_var family -> error (global is immutable) */ - if (op == OP_put_var || op == OP_put_var_init || op == OP_put_var_strict) { - uint32_t cpool_idx = get_u32 (bc + pos + 1); - JSValue name = unit->cpool[cpool_idx]; - char buf[64]; - JS_ThrowReferenceError (ctx, "cannot assign to '%s' - global object is immutable", - JS_KeyGetStr (ctx, buf, sizeof (buf), name)); - pjs_free (unit); - return NULL; - } - - /* Patch OP_check_var -> OP_nop (if variable exists) */ - if (op == OP_check_var) { - uint32_t cpool_idx = get_u32 (bc + pos + 1); - JSValue name = unit->cpool[cpool_idx]; - - BOOL found = FALSE; - if (env_rec && rec_find_slot (env_rec, name) > 0) { - found = TRUE; - } - if (!found) { - JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); - if (rec_find_slot (global, name) > 0) { - found = TRUE; - } - } - - if (found) { - bc[pos] = OP_nop; - bc[pos + 1] = OP_nop; - bc[pos + 2] = OP_nop; - bc[pos + 3] = OP_nop; - bc[pos + 4] = OP_nop; - } - } - - pos += len; - } - - return unit; -} - -static __exception int next_token (JSParseState *s); - -static void free_token (JSParseState *s, JSToken *token) { - switch (token->val) { - case TOK_NUMBER: - break; - case TOK_STRING: - case TOK_TEMPLATE: - break; - case TOK_REGEXP: - break; - case TOK_IDENT: - break; - default: - if (token->val >= TOK_FIRST_KEYWORD && token->val <= TOK_LAST_KEYWORD) { - } - break; - } -} - -static void __attribute ((unused)) dump_token (JSParseState *s, - const JSToken *token) { - switch (token->val) { - case TOK_NUMBER: { - double d; - JS_ToFloat64 (s->ctx, &d, token->u.num.val); /* no exception possible */ - printf ("number: %.14g\n", d); - } break; - case TOK_IDENT: - dump_ident: { - const char *str = JS_ToCString (s->ctx, token->u.ident.str); - printf ("ident: '%s'\n", str ? str : ""); - JS_FreeCString (s->ctx, str); - } break; - case TOK_STRING: { - const char *str; - /* XXX: quote the string */ - str = JS_ToCString (s->ctx, token->u.str.str); - printf ("string: '%s'\n", str); - JS_FreeCString (s->ctx, str); - } break; - case TOK_TEMPLATE: { - const char *str; - str = JS_ToCString (s->ctx, token->u.str.str); - printf ("template: `%s`\n", str); - JS_FreeCString (s->ctx, str); - } break; - case TOK_REGEXP: { - const char *str, *str2; - str = JS_ToCString (s->ctx, token->u.regexp.body); - str2 = JS_ToCString (s->ctx, token->u.regexp.flags); - printf ("regexp: '%s' '%s'\n", str, str2); - JS_FreeCString (s->ctx, str); - JS_FreeCString (s->ctx, str2); - } break; - case TOK_EOF: - printf ("eof\n"); - break; - default: - if (s->token.val >= TOK_NULL && s->token.val <= TOK_LAST_KEYWORD) { - goto dump_ident; - } else if (s->token.val >= 256) { - printf ("token: %d\n", token->val); - } else { - printf ("token: '%c'\n", token->val); - } - break; - } -} - -/* return the zero based line and column number in the source. */ -/* Note: we no longer support '\r' as line terminator */ -static int get_line_col (int *pcol_num, const uint8_t *buf, size_t len) { - int line_num, col_num, c; - size_t i; - - line_num = 0; - col_num = 0; - for (i = 0; i < len; i++) { - c = buf[i]; - if (c == '\n') { - line_num++; - col_num = 0; - } else if (c < 0x80 || c >= 0xc0) { - col_num++; - } - } - *pcol_num = col_num; - return line_num; -} - -static int get_line_col_cached (GetLineColCache *s, int *pcol_num, const uint8_t *ptr) { - int line_num, col_num; - if (ptr >= s->ptr) { - line_num = get_line_col (&col_num, s->ptr, ptr - s->ptr); - if (line_num == 0) { - s->col_num += col_num; - } else { - s->line_num += line_num; - s->col_num = col_num; - } - } else { - line_num = get_line_col (&col_num, ptr, s->ptr - ptr); - if (line_num == 0) { - s->col_num -= col_num; - } else { - const uint8_t *p; - s->line_num -= line_num; - /* find the absolute column position */ - col_num = 0; - for (p = ptr - 1; p >= s->buf_start; p--) { - if (*p == '\n') { - break; - } else if (*p < 0x80 || *p >= 0xc0) { - col_num++; - } - } - s->col_num = col_num; - } - } - s->ptr = ptr; - *pcol_num = s->col_num; - return s->line_num; -} - -/* 'ptr' is the position of the error in the source */ -static int js_parse_error_v (JSParseState *s, const uint8_t *ptr, const char *fmt, va_list ap) { - JSContext *ctx = s->ctx; - int line_num, col_num; - line_num = get_line_col (&col_num, s->buf_start, ptr - s->buf_start); - JS_ThrowError2 (ctx, JS_SYNTAX_ERROR, fmt, ap, FALSE); - build_backtrace (ctx, ctx->current_exception, s->filename, line_num + 1, col_num + 1, 0); - return -1; -} - -static __attribute__ ((format (printf, 3, 4))) int -js_parse_error_pos (JSParseState *s, const uint8_t *ptr, const char *fmt, ...) { - va_list ap; - int ret; - - va_start (ap, fmt); - ret = js_parse_error_v (s, ptr, fmt, ap); - va_end (ap); - return ret; -} - -static __attribute__ ((format (printf, 2, 3))) int -js_parse_error (JSParseState *s, const char *fmt, ...) { - va_list ap; - int ret; - - va_start (ap, fmt); - ret = js_parse_error_v (s, s->token.ptr, fmt, ap); - va_end (ap); - return ret; -} - -static int js_parse_expect (JSParseState *s, int tok) { - if (s->token.val != tok) { - /* XXX: dump token correctly in all cases */ - return js_parse_error (s, "expecting '%c'", tok); - } - return next_token (s); -} - -static int js_parse_expect_semi (JSParseState *s) { - if (s->token.val != ';') { - /* automatic insertion of ';' */ - if (s->token.val == TOK_EOF || s->token.val == '}' || s->got_lf - || s->token.val == TOK_ELSE) { - return 0; - } - return js_parse_error (s, "expecting '%c'", ';'); - } - return next_token (s); -} - -static int js_parse_error_reserved_identifier (JSParseState *s) { - char buf1[KEY_GET_STR_BUF_SIZE]; - return js_parse_error ( - s, "'%s' is a reserved identifier", JS_KeyGetStr (s->ctx, buf1, sizeof (buf1), s->token.u.ident.str)); -} - -static __exception int js_parse_template_part (JSParseState *s, - const uint8_t *p) { - uint32_t c; - PPretext *b; - JSValue str; - - /* p points to the first byte of the template part */ - b = ppretext_init (32); - if (!b) goto fail; - for (;;) { - if (p >= s->buf_end) goto unexpected_eof; - c = *p++; - if (c == '`') { - /* template end part */ - break; - } - if (c == '$' && *p == '{') { - /* template start or middle part */ - p++; - break; - } - if (c == '\\') { - b = ppretext_putc (b, c); - if (!b) goto fail; - if (p >= s->buf_end) goto unexpected_eof; - c = *p++; - } - /* newline sequences are normalized as single '\n' bytes */ - if (c == '\r') { - if (*p == '\n') p++; - c = '\n'; - } - if (c >= 0x80) { - const uint8_t *p_next; - c = unicode_from_utf8 (p - 1, UTF8_CHAR_LEN_MAX, &p_next); - if (c > 0x10FFFF) { - js_parse_error_pos (s, p - 1, "invalid UTF-8 sequence"); - goto fail; - } - p = p_next; - } - b = ppretext_putc (b, c); - if (!b) goto fail; - } - str = ppretext_end (s->ctx, b); - if (JS_IsException (str)) return -1; - s->token.val = TOK_TEMPLATE; - s->token.u.str.sep = c; - s->token.u.str.str = str; - s->buf_ptr = p; - return 0; - -unexpected_eof: - js_parse_error (s, "unexpected end of string"); -fail: - ppretext_free (b); - return -1; -} - -static __exception int js_parse_string (JSParseState *s, int sep, BOOL do_throw, const uint8_t *p, JSToken *token, const uint8_t **pp) { - int ret; - uint32_t c; - PPretext *b; - const uint8_t *p_escape; - JSValue str; - - /* string */ - b = ppretext_init (32); - if (!b) goto fail; - for (;;) { - if (p >= s->buf_end) goto invalid_char; - c = *p; - if (c < 0x20) { - if (sep == '`') { - if (c == '\r') { - if (p[1] == '\n') p++; - c = '\n'; - } - /* do not update s->line_num */ - } else if (c == '\n' || c == '\r') - goto invalid_char; - } - p++; - if (c == sep) break; - if (c == '$' && *p == '{' && sep == '`') { - /* template start or middle part */ - p++; - break; - } - if (c == '\\') { - p_escape = p - 1; - c = *p; - /* XXX: need a specific JSON case to avoid - accepting invalid escapes */ - switch (c) { - case '\0': - if (p >= s->buf_end) goto invalid_char; - p++; - break; - case '\'': - case '\"': - case '\\': - p++; - break; - case '\r': /* accept DOS and MAC newline sequences */ - if (p[1] == '\n') { p++; } - /* fall thru */ - case '\n': - /* ignore escaped newline sequence */ - p++; - continue; - default: - if (c >= '0' && c <= '9') { - if (c == '0' && !(p[1] >= '0' && p[1] <= '9')) { - p++; - c = '\0'; - } else { - if (c >= '8' || sep == '`') { - /* Note: according to ES2021, \8 and \9 are not - accepted in strict mode or in templates. */ - goto invalid_escape; - } else { - if (do_throw) - js_parse_error_pos ( - s, p_escape, "octal escape sequences are not allowed in strict mode"); - } - goto fail; - } - } else if (c >= 0x80) { - const uint8_t *p_next; - c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p_next); - if (c > 0x10FFFF) { goto invalid_utf8; } - p = p_next; - /* LS or PS are skipped */ - if (c == CP_LS || c == CP_PS) continue; - } else { - ret = lre_parse_escape (&p, TRUE); - if (ret == -1) { - invalid_escape: - if (do_throw) - js_parse_error_pos ( - s, p_escape, "malformed escape sequence in string literal"); - goto fail; - } else if (ret < 0) { - /* ignore the '\' (could output a warning) */ - p++; - } else { - c = ret; - } - } - break; - } - } else if (c >= 0x80) { - const uint8_t *p_next; - c = unicode_from_utf8 (p - 1, UTF8_CHAR_LEN_MAX, &p_next); - if (c > 0x10FFFF) goto invalid_utf8; - p = p_next; - } - b = ppretext_putc (b, c); - if (!b) goto fail; - } - str = ppretext_end (s->ctx, b); - if (JS_IsException (str)) return -1; - token->val = TOK_STRING; - token->u.str.sep = c; - token->u.str.str = str; - *pp = p; - return 0; - -invalid_utf8: - if (do_throw) js_parse_error (s, "invalid UTF-8 sequence"); - goto fail; -invalid_char: - if (do_throw) js_parse_error (s, "unexpected end of string"); -fail: - ppretext_free (b); - return -1; -} - -static inline BOOL token_is_pseudo_keyword (JSParseState *s, JSValue key) { - return s->token.val == TOK_IDENT && js_key_equal (s->token.u.ident.str, key) - && !s->token.u.ident.has_escape; -} - -static __exception int js_parse_regexp (JSParseState *s) { - const uint8_t *p; - BOOL in_class; - PPretext *b = NULL; - PPretext *b2 = NULL; - uint32_t c; - JSValue body_str, flags_str; - - p = s->buf_ptr; - p++; - in_class = FALSE; - b = ppretext_init (32); - if (!b) return -1; - b2 = ppretext_init (1); - if (!b2) goto fail; - for (;;) { - if (p >= s->buf_end) { - eof_error: - js_parse_error (s, "unexpected end of regexp"); - goto fail; - } - c = *p++; - if (c == '\n' || c == '\r') { - goto eol_error; - } else if (c == '/') { - if (!in_class) break; - } else if (c == '[') { - in_class = TRUE; - } else if (c == ']') { - /* XXX: incorrect as the first character in a class */ - in_class = FALSE; - } else if (c == '\\') { - b = ppretext_putc (b, c); - if (!b) goto fail; - c = *p++; - if (c == '\n' || c == '\r') - goto eol_error; - else if (c == '\0' && p >= s->buf_end) - goto eof_error; - else if (c >= 0x80) { - const uint8_t *p_next; - c = unicode_from_utf8 (p - 1, UTF8_CHAR_LEN_MAX, &p_next); - if (c > 0x10FFFF) { goto invalid_utf8; } - p = p_next; - if (c == CP_LS || c == CP_PS) goto eol_error; - } - } else if (c >= 0x80) { - const uint8_t *p_next; - c = unicode_from_utf8 (p - 1, UTF8_CHAR_LEN_MAX, &p_next); - if (c > 0x10FFFF) { - invalid_utf8: - js_parse_error_pos (s, p - 1, "invalid UTF-8 sequence"); - goto fail; - } - /* LS or PS are considered as line terminator */ - if (c == CP_LS || c == CP_PS) { - eol_error: - js_parse_error_pos (s, p - 1, "unexpected line terminator in regexp"); - goto fail; - } - p = p_next; - } - b = ppretext_putc (b, c); - if (!b) goto fail; - } - - /* flags */ - for (;;) { - const uint8_t *p_next = p; - c = *p_next++; - if (c >= 0x80) { - c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p_next); - if (c > 0x10FFFF) { - p++; - goto invalid_utf8; - } - } - if (!lre_js_is_ident_next (c)) break; - b2 = ppretext_putc (b2, c); - if (!b2) goto fail; - p = p_next; - } - - body_str = ppretext_end (s->ctx, b); - flags_str = ppretext_end (s->ctx, b2); - if (JS_IsException (body_str) || JS_IsException (flags_str)) { - return -1; - } - s->token.val = TOK_REGEXP; - s->token.u.regexp.body = body_str; - s->token.u.regexp.flags = flags_str; - s->buf_ptr = p; - return 0; -fail: - ppretext_free (b); - ppretext_free (b2); - return -1; -} - -static __exception int ident_realloc (JSContext *ctx, char **pbuf, size_t *psize, char *static_buf) { - (void)ctx; /* unused - uses system allocator */ - char *buf, *new_buf; - size_t size, new_size; - - buf = *pbuf; - size = *psize; - if (size >= (SIZE_MAX / 3) * 2) - new_size = SIZE_MAX; - else - new_size = size + (size >> 1); - if (buf == static_buf) { - new_buf = pjs_malloc (new_size); - if (!new_buf) return -1; - memcpy (new_buf, buf, size); - } else { - new_buf = pjs_realloc (buf, new_size); - if (!new_buf) return -1; - } - *pbuf = new_buf; - *psize = new_size; - return 0; -} - -/* keyword lookup table */ -typedef struct { - const char *name; - int token; - BOOL is_strict_reserved; /* TRUE if reserved only in strict mode */ -} JSKeywordEntry; - -static const JSKeywordEntry js_keywords[] = { - { "null", TOK_NULL, FALSE }, - { "false", TOK_FALSE, FALSE }, - { "true", TOK_TRUE, FALSE }, - { "if", TOK_IF, FALSE }, - { "else", TOK_ELSE, FALSE }, - { "return", TOK_RETURN, FALSE }, - { "go", TOK_GO, FALSE }, - { "var", TOK_VAR, FALSE }, - { "def", TOK_DEF, FALSE }, - { "this", TOK_THIS, FALSE }, - { "delete", TOK_DELETE, FALSE }, - { "in", TOK_IN, FALSE }, - { "do", TOK_DO, FALSE }, - { "while", TOK_WHILE, FALSE }, - { "for", TOK_FOR, FALSE }, - { "break", TOK_BREAK, FALSE }, - { "continue", TOK_CONTINUE, FALSE }, - { "disrupt", TOK_DISRUPT, FALSE }, - { "disruption", TOK_DISRUPTION, FALSE }, - { "function", TOK_FUNCTION, FALSE }, - { "debugger", TOK_DEBUGGER, FALSE }, - { "with", TOK_WITH, FALSE }, - { "class", TOK_CLASS, FALSE }, - { "const", TOK_CONST, FALSE }, - { "enum", TOK_ENUM, FALSE }, - { "export", TOK_EXPORT, FALSE }, - { "extends", TOK_EXTENDS, FALSE }, - { "import", TOK_IMPORT, FALSE }, - { "super", TOK_SUPER, FALSE }, - /* strict mode future reserved words */ - { "implements", TOK_IMPLEMENTS, TRUE }, - { "interface", TOK_INTERFACE, TRUE }, - { "let", TOK_LET, TRUE }, - { "private", TOK_PRIVATE, TRUE }, - { "protected", TOK_PROTECTED, TRUE }, - { "public", TOK_PUBLIC, TRUE }, - { "static", TOK_STATIC, TRUE }, - { "yield", TOK_YIELD, TRUE }, - { "await", TOK_AWAIT, TRUE }, -}; - -#define JS_KEYWORD_COUNT (sizeof (js_keywords) / sizeof (js_keywords[0])) - -/* lookup keyword by JSValue string, return token or -1 if not found */ -static int js_keyword_lookup (JSValue str, BOOL *is_strict_reserved) { - char buf[16]; - const char *cstr; - size_t len; - - if (MIST_IsImmediateASCII (str)) { - len = MIST_GetImmediateASCIILen (str); - if (len >= sizeof (buf)) return -1; - for (size_t i = 0; i < len; i++) { - buf[i] = MIST_GetImmediateASCIIChar (str, i); - } - buf[len] = '\0'; - cstr = buf; - } else if (JS_IsPtr (str)) { - /* Handle heap strings (JSText) - keywords like "function" are 8 chars */ - JSText *text = (JSText *)JS_VALUE_GET_PTR (str); -#ifdef PRINT_LEXER - fprintf(stderr, "DEBUG: JS_IsPtr branch, ptr=%p, hdr_type=%d\n", (void*)text, objhdr_type(text->hdr)); -#endif - if (objhdr_type (text->hdr) != OBJ_TEXT) return -1; - len = JSText_len (text); -#ifdef PRINT_LEXER - fprintf(stderr, "DEBUG: text len=%zu\n", len); -#endif - if (len >= sizeof (buf)) return -1; - for (size_t i = 0; i < len; i++) { - uint32_t c = string_get (text, i); - if (c >= 128) return -1; /* Keywords are ASCII only */ - buf[i] = (char)c; - } - buf[len] = '\0'; - cstr = buf; -#ifdef PRINT_LEXER - fprintf(stderr, "DEBUG: extracted string='%s' len=%zu\n", cstr, len); -#endif - } else { -#ifdef PRINT_LEXER - fprintf(stderr, "DEBUG: unknown str type, tag=%llx\n", (unsigned long long)(str & 0xFF)); -#endif - return -1; - } - -#ifdef PRINT_LEXER - fprintf(stderr, "DEBUG: looking up '%s' in %zu keywords\n", cstr, (size_t)JS_KEYWORD_COUNT); -#endif - for (size_t i = 0; i < JS_KEYWORD_COUNT; i++) { - if (strcmp (cstr, js_keywords[i].name) == 0) { -#ifdef PRINT_LEXER - fprintf(stderr, "DEBUG: matched keyword '%s' -> token %d\n", js_keywords[i].name, js_keywords[i].token); -#endif - if (is_strict_reserved) - *is_strict_reserved = js_keywords[i].is_strict_reserved; - return js_keywords[i].token; - } - } -#ifdef PRINT_LEXER - fprintf(stderr, "DEBUG: no keyword match for '%s'\n", cstr); -#endif - return -1; -} - -/* convert a TOK_IDENT to a keyword when needed */ -static void update_token_ident (JSParseState *s) { - BOOL is_strict_reserved = FALSE; - int token = js_keyword_lookup (s->token.u.ident.str, &is_strict_reserved); - - if (token != -1) { - if (s->token.u.ident.has_escape) { - /* identifiers with escape sequences stay identifiers */ - s->token.u.ident.is_reserved = TRUE; - s->token.val = TOK_IDENT; - } else { - s->token.val = token; - } - } -} - -/* if the current token is an identifier or keyword, reparse it - according to the current function type */ -static void reparse_ident_token (JSParseState *s) { - if (s->token.val == TOK_IDENT - || (s->token.val >= TOK_FIRST_KEYWORD - && s->token.val <= TOK_LAST_KEYWORD)) { - s->token.val = TOK_IDENT; - s->token.u.ident.is_reserved = FALSE; - update_token_ident (s); - } -} - -/* 'c' is the first character. Return JS_NULL in case of error */ -static JSValue parse_ident (JSParseState *s, const uint8_t **pp, BOOL *pident_has_escape, int c) { - const uint8_t *p, *p1; - char ident_buf[128], *buf; - size_t ident_size, ident_pos; - JSValue str; - - p = *pp; - buf = ident_buf; - ident_size = sizeof (ident_buf); - ident_pos = 0; - for (;;) { - p1 = p; - - if (c < 128) { - buf[ident_pos++] = c; - } else { - ident_pos += unicode_to_utf8 ((uint8_t *)buf + ident_pos, c); - } - c = *p1++; - if (c == '\\' && *p1 == 'u') { - c = lre_parse_escape (&p1, TRUE); - *pident_has_escape = TRUE; - } else if (c >= 128) { - c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p1); - } - if (!lre_js_is_ident_next (c) && c != '?' && c != '!') break; - p = p1; - if (unlikely (ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { - if (ident_realloc (s->ctx, &buf, &ident_size, ident_buf)) { - str = JS_NULL; - goto done; - } - } - } - /* Create interned JSValue key */ - str = js_key_new_len (s->ctx, buf, ident_pos); -done: - if (unlikely (buf != ident_buf)) pjs_free (buf); - *pp = p; - return str; -} - -static __exception int next_token (JSParseState *s) { - const uint8_t *p; - int c; - BOOL ident_has_escape; - JSValue ident_str; - - if (js_check_stack_overflow (s->ctx, 0)) { - return js_parse_error (s, "stack overflow"); - } - - free_token (s, &s->token); - - p = s->last_ptr = s->buf_ptr; - s->got_lf = FALSE; -redo: - s->token.ptr = p; - c = *p; - switch (c) { - case 0: - if (p >= s->buf_end) { - s->token.val = TOK_EOF; - } else { - goto def_token; - } - break; - case '`': - if (js_parse_template_part (s, p + 1)) goto fail; - p = s->buf_ptr; - break; - case '\'': - case '\"': - if (js_parse_string (s, c, TRUE, p + 1, &s->token, &p)) goto fail; - break; - case '\r': /* accept DOS and MAC newline sequences */ - if (p[1] == '\n') { p++; } - /* fall thru */ - case '\n': - p++; - line_terminator: - s->got_lf = TRUE; - goto redo; - case '\f': - case '\v': - case ' ': - case '\t': - p++; - goto redo; - case '/': - if (p[1] == '*') { - /* comment */ - p += 2; - for (;;) { - if (*p == '\0' && p >= s->buf_end) { - js_parse_error (s, "unexpected end of comment"); - goto fail; - } - if (p[0] == '*' && p[1] == '/') { - p += 2; - break; - } - if (*p == '\n' || *p == '\r') { - s->got_lf = TRUE; /* considered as LF for ASI */ - p++; - } else if (*p >= 0x80) { - c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p); - if (c == CP_LS || c == CP_PS) { - s->got_lf = TRUE; /* considered as LF for ASI */ - } else if (c == -1) { - p++; /* skip invalid UTF-8 */ - } - } else { - p++; - } - } - goto redo; - } else if (p[1] == '/') { - /* line comment */ - p += 2; - for (;;) { - if (*p == '\0' && p >= s->buf_end) break; - if (*p == '\r' || *p == '\n') break; - if (*p >= 0x80) { - c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p); - /* LS or PS are considered as line terminator */ - if (c == CP_LS || c == CP_PS) { - break; - } else if (c == -1) { - p++; /* skip invalid UTF-8 */ - } - } else { - p++; - } - } - goto redo; - } else if (p[1] == '=') { - p += 2; - s->token.val = TOK_DIV_ASSIGN; - } else { - p++; - s->token.val = c; - } - break; - case '\\': - if (p[1] == 'u') { - const uint8_t *p1 = p + 1; - int c1 = lre_parse_escape (&p1, TRUE); - if (c1 >= 0 && lre_js_is_ident_first (c1)) { - c = c1; - p = p1; - ident_has_escape = TRUE; - goto has_ident; - } else { - /* XXX: syntax error? */ - } - } - goto def_token; - case 'a': - case 'b': - case 'c': - case 'd': - case 'e': - case 'f': - case 'g': - case 'h': - case 'i': - case 'j': - case 'k': - case 'l': - case 'm': - case 'n': - case 'o': - case 'p': - case 'q': - case 'r': - case 's': - case 't': - case 'u': - case 'v': - case 'w': - case 'x': - case 'y': - case 'z': - case 'A': - case 'B': - case 'C': - case 'D': - case 'E': - case 'F': - case 'G': - case 'H': - case 'I': - case 'J': - case 'K': - case 'L': - case 'M': - case 'N': - case 'O': - case 'P': - case 'Q': - case 'R': - case 'S': - case 'T': - case 'U': - case 'V': - case 'W': - case 'X': - case 'Y': - case 'Z': - case '_': - case '$': - /* identifier */ - p++; - ident_has_escape = FALSE; - has_ident: - ident_str = parse_ident (s, &p, &ident_has_escape, c); - if (JS_IsNull (ident_str)) goto fail; - s->token.u.ident.str = ident_str; - s->token.u.ident.has_escape = ident_has_escape; - s->token.u.ident.is_reserved = FALSE; - s->token.val = TOK_IDENT; - update_token_ident (s); - break; - case '.': - if (p[1] == '.' && p[2] == '.') { - js_parse_error (s, "ellipsis (...) is not supported"); - goto fail; - } - if (p[1] >= '0' && p[1] <= '9') { - goto parse_number; - } else { - goto def_token; - } - break; - case '0': - /* in strict mode, octal literals are not accepted */ - if (is_digit (p[1])) { - js_parse_error (s, "octal literals are not allowed"); - goto fail; - } - goto parse_number; - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - /* number */ - parse_number: { - JSValue ret; - const uint8_t *p1; - int flags; - flags = ATOD_ACCEPT_BIN_OCT | ATOD_ACCEPT_LEGACY_OCTAL - | ATOD_ACCEPT_UNDERSCORES | ATOD_ACCEPT_SUFFIX; - ret = js_atof (s->ctx, (const char *)p, (const char **)&p, 0, flags); - if (JS_IsException (ret)) goto fail; - /* reject number immediately followed by identifier */ - if (JS_IsNull (ret) - || lre_js_is_ident_next ( - unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p1))) { - js_parse_error (s, "invalid number literal"); - goto fail; - } - s->token.val = TOK_NUMBER; - s->token.u.num.val = ret; - } break; - case '*': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_MUL_ASSIGN; - } else if (p[1] == '*') { - if (p[2] == '=') { - p += 3; - s->token.val = TOK_POW_ASSIGN; - } else { - p += 2; - s->token.val = TOK_POW; - } - } else { - goto def_token; - } - break; - case '%': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_MOD_ASSIGN; - } else { - goto def_token; - } - break; - case '+': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_PLUS_ASSIGN; - } else if (p[1] == '+') { - p += 2; - s->token.val = TOK_INC; - } else { - goto def_token; - } - break; - case '-': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_MINUS_ASSIGN; - } else if (p[1] == '-') { - p += 2; - s->token.val = TOK_DEC; - } else { - goto def_token; - } - break; - case '<': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_LTE; - } else if (p[1] == '<') { - if (p[2] == '=') { - p += 3; - s->token.val = TOK_SHL_ASSIGN; - } else { - p += 2; - s->token.val = TOK_SHL; - } - } else { - goto def_token; - } - break; - case '>': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_GTE; - } else if (p[1] == '>') { - if (p[2] == '>') { - if (p[3] == '=') { - p += 4; - s->token.val = TOK_SHR_ASSIGN; - } else { - p += 3; - s->token.val = TOK_SHR; - } - } else if (p[2] == '=') { - p += 3; - s->token.val = TOK_SAR_ASSIGN; - } else { - p += 2; - s->token.val = TOK_SAR; - } - } else { - goto def_token; - } - break; - case '=': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_STRICT_EQ; - } else if (p[1] == '>') { - p += 2; - s->token.val = TOK_ARROW; - } else { - goto def_token; - } - break; - case '!': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_STRICT_NEQ; - } else { - goto def_token; - } - break; - case '&': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_AND_ASSIGN; - } else if (p[1] == '&') { - if (p[2] == '=') { - p += 3; - s->token.val = TOK_LAND_ASSIGN; - } else { - p += 2; - s->token.val = TOK_LAND; - } - } else { - goto def_token; - } - break; - case '^': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_XOR_ASSIGN; - } else { - goto def_token; - } - break; - case '|': - if (p[1] == '=') { - p += 2; - s->token.val = TOK_OR_ASSIGN; - } else if (p[1] == '|') { - if (p[2] == '=') { - p += 3; - s->token.val = TOK_LOR_ASSIGN; - } else { - p += 2; - s->token.val = TOK_LOR; - } - } else { - goto def_token; - } - break; - case '?': - goto def_token; - default: - if (c >= 128) { - /* unicode value */ - c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p); - switch (c) { - case CP_PS: - case CP_LS: - /* XXX: should avoid incrementing line_number, but - needed to handle HTML comments */ - goto line_terminator; - default: - if (lre_is_space (c)) { - goto redo; - } else if (lre_js_is_ident_first (c)) { - ident_has_escape = FALSE; - goto has_ident; - } else { - js_parse_error (s, "unexpected character"); - goto fail; - } - } - } - def_token: - s->token.val = c; - p++; - break; - } - s->buf_ptr = p; - - // dump_token(s, &s->token); - return 0; - -fail: - s->token.val = TOK_ERROR; - return -1; -} - -/* 'c' is the first character. Return JS_NULL in case of error */ -/* XXX: accept unicode identifiers as JSON5 ? */ -static JSValue json_parse_ident (JSParseState *s, const uint8_t **pp, int c) { - const uint8_t *p; - char ident_buf[128], *buf; - size_t ident_size, ident_pos; - JSValue str; - - p = *pp; - buf = ident_buf; - ident_size = sizeof (ident_buf); - ident_pos = 0; - for (;;) { - buf[ident_pos++] = c; - c = *p; - if (c >= 128 || !lre_is_id_continue_byte (c)) break; - p++; - if (unlikely (ident_pos >= ident_size - UTF8_CHAR_LEN_MAX)) { - if (ident_realloc (s->ctx, &buf, &ident_size, ident_buf)) { - str = JS_NULL; - goto done; - } - } - } - /* Create interned JSValue key */ - str = js_key_new_len (s->ctx, buf, ident_pos); -done: - if (unlikely (buf != ident_buf)) pjs_free (buf); - *pp = p; - return str; -} - -static int json_parse_string (JSParseState *s, const uint8_t **pp, int sep) { - const uint8_t *p, *p_next; - int i; - uint32_t c; - PPretext *b; - - b = ppretext_init (32); - if (!b) goto fail; - - p = *pp; - for (;;) { - if (p >= s->buf_end) { goto end_of_input; } - c = *p++; - if (c == sep) break; - if (c < 0x20) { - js_parse_error_pos (s, p - 1, "Bad control character in string literal"); - goto fail; - } - if (c == '\\') { - c = *p++; - switch (c) { - case 'b': - c = '\b'; - break; - case 'f': - c = '\f'; - break; - case 'n': - c = '\n'; - break; - case 'r': - c = '\r'; - break; - case 't': - c = '\t'; - break; - case '\\': - break; - case '/': - break; - case 'u': - c = 0; - for (i = 0; i < 4; i++) { - int h = from_hex (*p++); - if (h < 0) { - js_parse_error_pos (s, p - 1, "Bad Unicode escape"); - goto fail; - } - c = (c << 4) | h; - } - break; - case '\n': - if (s->ext_json) continue; - goto bad_escape; - case 'v': - if (s->ext_json) { - c = '\v'; - break; - } - goto bad_escape; - default: - if (c == sep) break; - if (p > s->buf_end) goto end_of_input; - bad_escape: - js_parse_error_pos (s, p - 1, "Bad escaped character"); - goto fail; - } - } else if (c >= 0x80) { - c = unicode_from_utf8 (p - 1, UTF8_CHAR_LEN_MAX, &p_next); - if (c > 0x10FFFF) { - js_parse_error_pos (s, p - 1, "Bad UTF-8 sequence"); - goto fail; - } - p = p_next; - } - b = ppretext_putc (b, c); - if (!b) goto fail; - } - s->token.val = TOK_STRING; - s->token.u.str.sep = sep; - s->token.u.str.str = ppretext_end (s->ctx, b); - *pp = p; - return 0; - -end_of_input: - js_parse_error (s, "Unexpected end of JSON input"); -fail: - ppretext_free (b); - return -1; -} - -static int json_parse_number (JSParseState *s, const uint8_t **pp) { - const uint8_t *p = *pp; - const uint8_t *p_start = p; - int radix; - double d; - JSATODTempMem atod_mem; - - if (*p == '+' || *p == '-') p++; - - if (!is_digit (*p)) { - if (s->ext_json) { - if (strstart ((const char *)p, "Infinity", (const char **)&p)) { - d = 1.0 / 0.0; - if (*p_start == '-') d = -d; - goto done; - } else if (strstart ((const char *)p, "NaN", (const char **)&p)) { - d = NAN; - goto done; - } else if (*p != '.') { - goto unexpected_token; - } - } else { - goto unexpected_token; - } - } - - if (p[0] == '0') { - if (s->ext_json) { - /* also accepts base 16, 8 and 2 prefix for integers */ - radix = 10; - if (p[1] == 'x' || p[1] == 'X') { - p += 2; - radix = 16; - } else if ((p[1] == 'o' || p[1] == 'O')) { - p += 2; - radix = 8; - } else if ((p[1] == 'b' || p[1] == 'B')) { - p += 2; - radix = 2; - } - if (radix != 10) { - /* prefix is present */ - if (to_digit (*p) >= radix) { - unexpected_token: - return js_parse_error_pos (s, p, "Unexpected token '%c'", *p); - } - d = js_atod ((const char *)p_start, (const char **)&p, 0, JS_ATOD_INT_ONLY | JS_ATOD_ACCEPT_BIN_OCT, &atod_mem); - goto done; - } - } - if (is_digit (p[1])) return js_parse_error_pos (s, p, "Unexpected number"); - } - - while (is_digit (*p)) - p++; - - if (*p == '.') { - p++; - if (!is_digit (*p)) - return js_parse_error_pos (s, p, "Unterminated fractional number"); - while (is_digit (*p)) - p++; - } - if (*p == 'e' || *p == 'E') { - p++; - if (*p == '+' || *p == '-') p++; - if (!is_digit (*p)) - return js_parse_error_pos (s, p, "Exponent part is missing a number"); - while (is_digit (*p)) - p++; - } - d = js_atod ((const char *)p_start, NULL, 10, 0, &atod_mem); -done: - s->token.val = TOK_NUMBER; - s->token.u.num.val = JS_NewFloat64 (s->ctx, d); - *pp = p; - return 0; -} - -static __exception int json_next_token (JSParseState *s) { - const uint8_t *p; - int c; - JSValue ident_str; - - if (js_check_stack_overflow (s->ctx, 0)) { - return js_parse_error (s, "stack overflow"); - } - - free_token (s, &s->token); - - p = s->last_ptr = s->buf_ptr; -redo: - s->token.ptr = p; - c = *p; - switch (c) { - case 0: - if (p >= s->buf_end) { - s->token.val = TOK_EOF; - } else { - goto def_token; - } - break; - case '\'': - if (!s->ext_json) { - /* JSON does not accept single quoted strings */ - goto def_token; - } - /* fall through */ - case '\"': - p++; - if (json_parse_string (s, &p, c)) goto fail; - break; - case '\r': /* accept DOS and MAC newline sequences */ - if (p[1] == '\n') { p++; } - /* fall thru */ - case '\n': - p++; - goto redo; - case '\f': - case '\v': - if (!s->ext_json) { - /* JSONWhitespace does not match , nor */ - goto def_token; - } - /* fall through */ - case ' ': - case '\t': - p++; - goto redo; - case '/': - if (!s->ext_json) { - /* JSON does not accept comments */ - goto def_token; - } - if (p[1] == '*') { - /* comment */ - p += 2; - for (;;) { - if (*p == '\0' && p >= s->buf_end) { - js_parse_error (s, "unexpected end of comment"); - goto fail; - } - if (p[0] == '*' && p[1] == '/') { - p += 2; - break; - } - if (*p >= 0x80) { - c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p); - if (c == -1) { p++; /* skip invalid UTF-8 */ } - } else { - p++; - } - } - goto redo; - } else if (p[1] == '/') { - /* line comment */ - p += 2; - for (;;) { - if (*p == '\0' && p >= s->buf_end) break; - if (*p == '\r' || *p == '\n') break; - if (*p >= 0x80) { - c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p); - /* LS or PS are considered as line terminator */ - if (c == CP_LS || c == CP_PS) { - break; - } else if (c == -1) { - p++; /* skip invalid UTF-8 */ - } - } else { - p++; - } - } - goto redo; - } else { - goto def_token; - } - break; - case 'a': - case 'b': - case 'c': - case 'd': - case 'e': - case 'f': - case 'g': - case 'h': - case 'i': - case 'j': - case 'k': - case 'l': - case 'm': - case 'n': - case 'o': - case 'p': - case 'q': - case 'r': - case 's': - case 't': - case 'u': - case 'v': - case 'w': - case 'x': - case 'y': - case 'z': - case 'A': - case 'B': - case 'C': - case 'D': - case 'E': - case 'F': - case 'G': - case 'H': - case 'I': - case 'J': - case 'K': - case 'L': - case 'M': - case 'N': - case 'O': - case 'P': - case 'Q': - case 'R': - case 'S': - case 'T': - case 'U': - case 'V': - case 'W': - case 'X': - case 'Y': - case 'Z': - case '_': - case '$': - p++; - ident_str = json_parse_ident (s, &p, c); - if (JS_IsNull (ident_str)) goto fail; - s->token.u.ident.str = ident_str; - s->token.u.ident.has_escape = FALSE; - s->token.u.ident.is_reserved = FALSE; - s->token.val = TOK_IDENT; - break; - case '+': - if (!s->ext_json) goto def_token; - goto parse_number; - case '.': - if (s->ext_json && is_digit (p[1])) - goto parse_number; - else - goto def_token; - case '-': - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - /* number */ - parse_number: - if (json_parse_number (s, &p)) goto fail; - break; - default: - if (c >= 128) { - js_parse_error (s, "unexpected character"); - goto fail; - } - def_token: - s->token.val = c; - p++; - break; - } - s->buf_ptr = p; - - // dump_token(s, &s->token); - return 0; - -fail: - s->token.val = TOK_ERROR; - return -1; -} - -static int match_identifier (const uint8_t *p, const char *s) { - uint32_t c; - while (*s) { - if ((uint8_t)*s++ != *p++) return 0; - } - c = *p; - if (c >= 128) c = unicode_from_utf8 (p, UTF8_CHAR_LEN_MAX, &p); - return !lre_js_is_ident_next (c); -} - -/* simple_next_token() is used to check for the next token in simple cases. - It is only used for ':' and '=>', 'let' or 'function' look-ahead. - (*pp) is only set if TOK_IMPORT is returned for JS_DetectModule() - Whitespace and comments are skipped correctly. - Then the next token is analyzed, only for specific words. - Return values: - - '\n' if !no_line_terminator - - TOK_ARROW, TOK_IN, TOK_IMPORT, TOK_OF, TOK_EXPORT, TOK_FUNCTION - - TOK_IDENT is returned for other identifiers and keywords - - otherwise the next character or unicode codepoint is returned. - */ -static int simple_next_token (const uint8_t **pp, BOOL no_line_terminator) { - const uint8_t *p; - uint32_t c; - - /* skip spaces and comments */ - p = *pp; - for (;;) { - switch (c = *p++) { - case '\r': - case '\n': - if (no_line_terminator) return '\n'; - continue; - case ' ': - case '\t': - case '\v': - case '\f': - continue; - case '/': - if (*p == '/') { - if (no_line_terminator) return '\n'; - while (*p && *p != '\r' && *p != '\n') - p++; - continue; - } - if (*p == '*') { - while (*++p) { - if ((*p == '\r' || *p == '\n') && no_line_terminator) return '\n'; - if (*p == '*' && p[1] == '/') { - p += 2; - break; - } - } - continue; - } - break; - case '=': - if (*p == '>') return TOK_ARROW; - break; - case 'i': - if (match_identifier (p, "n")) return TOK_IN; - if (match_identifier (p, "mport")) { - *pp = p + 5; - return TOK_IMPORT; - } - return TOK_IDENT; - case 'o': - if (match_identifier (p, "f")) return TOK_OF; - return TOK_IDENT; - case 'e': - if (match_identifier (p, "xport")) return TOK_EXPORT; - return TOK_IDENT; - case 'f': - if (match_identifier (p, "unction")) return TOK_FUNCTION; - return TOK_IDENT; - case '\\': - if (*p == 'u') { - if (lre_js_is_ident_first (lre_parse_escape (&p, TRUE))) - return TOK_IDENT; - } - break; - default: - if (c >= 128) { - c = unicode_from_utf8 (p - 1, UTF8_CHAR_LEN_MAX, &p); - if (no_line_terminator && (c == CP_PS || c == CP_LS)) return '\n'; - } - if (lre_is_space (c)) continue; - if (lre_js_is_ident_first (c)) return TOK_IDENT; - break; - } - return c; - } -} - -static int peek_token (JSParseState *s, BOOL no_line_terminator) { - const uint8_t *p = s->buf_ptr; - return simple_next_token (&p, no_line_terminator); -} - -static inline int get_prev_opcode (JSFunctionDef *fd) { - if (fd->last_opcode_pos < 0 || dbuf_error (&fd->byte_code)) - return OP_invalid; - else - return fd->byte_code.buf[fd->last_opcode_pos]; -} - -static BOOL js_is_live_code (JSParseState *s) { - switch (get_prev_opcode (s->cur_func)) { - case OP_tail_call: - case OP_tail_call_method: - case OP_return: - case OP_return_undef: - case OP_throw: - case OP_throw_error: - case OP_goto: -#if SHORT_OPCODES - case OP_goto8: - case OP_goto16: -#endif - case OP_ret: - return FALSE; - default: - return TRUE; - } -} - -static void emit_u8 (JSParseState *s, uint8_t val) { - dbuf_putc (&s->cur_func->byte_code, val); -} - -static void emit_u16 (JSParseState *s, uint16_t val) { - dbuf_put_u16 (&s->cur_func->byte_code, val); -} - -static void emit_u32 (JSParseState *s, uint32_t val) { - dbuf_put_u32 (&s->cur_func->byte_code, val); -} - -static void emit_source_pos (JSParseState *s, const uint8_t *source_ptr) { - JSFunctionDef *fd = s->cur_func; - DynBuf *bc = &fd->byte_code; - - if (unlikely (fd->last_opcode_source_ptr != source_ptr)) { - dbuf_putc (bc, OP_line_num); - dbuf_put_u32 (bc, source_ptr - s->buf_start); - fd->last_opcode_source_ptr = source_ptr; - } -} - -static void emit_op (JSParseState *s, uint8_t val) { - JSFunctionDef *fd = s->cur_func; - DynBuf *bc = &fd->byte_code; - - fd->last_opcode_pos = bc->size; - dbuf_putc (bc, val); -} - -/* Forward declarations for cpool */ -static int fd_cpool_add (JSContext *ctx, JSFunctionDef *fd, JSValue val); -static int cpool_add (JSParseState *s, JSValue val); - -/* Emit a key (JSValue) by adding to cpool and emitting the index. - Used for variable opcodes (get_var, put_var, etc.) and property ops. */ -static int emit_key (JSParseState *s, JSValue name) { - int idx = cpool_add (s, name); - if (idx < 0) return -1; - emit_u32 (s, idx); - return 0; -} - -/* Emit a property key for field access opcodes. */ -static int emit_prop_key (JSParseState *s, JSValue key) { - return emit_key (s, key); -} - -static int update_label (JSFunctionDef *s, int label, int delta) { - LabelSlot *ls; - - assert (label >= 0 && label < s->label_count); - ls = &s->label_slots[label]; - ls->ref_count += delta; - assert (ls->ref_count >= 0); - return ls->ref_count; -} - -static int new_label_fd (JSFunctionDef *fd) { - int label; - LabelSlot *ls; - - if (pjs_resize_array ((void **)&fd->label_slots, sizeof (fd->label_slots[0]), &fd->label_size, fd->label_count + 1)) - return -1; - label = fd->label_count++; - ls = &fd->label_slots[label]; - ls->ref_count = 0; - ls->pos = -1; - ls->pos2 = -1; - ls->addr = -1; - ls->first_reloc = NULL; - return label; -} - -static int new_label (JSParseState *s) { - int label; - label = new_label_fd (s->cur_func); - if (unlikely (label < 0)) { dbuf_set_error (&s->cur_func->byte_code); } - return label; -} - -/* don't update the last opcode and don't emit line number info */ -static void emit_label_raw (JSParseState *s, int label) { - emit_u8 (s, OP_label); - emit_u32 (s, label); - s->cur_func->label_slots[label].pos = s->cur_func->byte_code.size; -} - -/* return the label ID offset */ -static int emit_label (JSParseState *s, int label) { - if (label >= 0) { - emit_op (s, OP_label); - emit_u32 (s, label); - s->cur_func->label_slots[label].pos = s->cur_func->byte_code.size; - return s->cur_func->byte_code.size - 4; - } else { - return -1; - } -} - -/* return label or -1 if dead code */ -static int emit_goto (JSParseState *s, int opcode, int label) { - if (js_is_live_code (s)) { - if (label < 0) { - label = new_label (s); - if (label < 0) return -1; - } - emit_op (s, opcode); - emit_u32 (s, label); - s->cur_func->label_slots[label].ref_count++; - return label; - } - return -1; -} - -/* return the constant pool index. 'val' is not duplicated. */ -static int fd_cpool_add (JSContext *ctx, JSFunctionDef *fd, JSValue val) { - (void)ctx; - if (pjs_resize_array ((void **)&fd->cpool, sizeof (fd->cpool[0]), &fd->cpool_size, fd->cpool_count + 1)) - return -1; - fd->cpool[fd->cpool_count++] = val; - return fd->cpool_count - 1; -} - -/* return the constant pool index. 'val' is not duplicated. */ -static int cpool_add (JSParseState *s, JSValue val) { - return fd_cpool_add (s->ctx, s->cur_func, val); -} - -static __exception int emit_push_const (JSParseState *s, JSValue val) { - int idx; - idx = cpool_add (s, val); - if (idx < 0) return -1; - emit_op (s, OP_push_const); - emit_u32 (s, idx); - return 0; -} - -/* return the variable index or -1 if not found, - add ARGUMENT_VAR_OFFSET for argument variables */ -static int find_arg (JSContext *ctx, JSFunctionDef *fd, JSValue name) { - int i; - for (i = fd->arg_count; i-- > 0;) { - if (js_key_equal (fd->args[i].var_name, name)) - return i | ARGUMENT_VAR_OFFSET; - } - return -1; -} - -static int find_var (JSContext *ctx, JSFunctionDef *fd, JSValue name) { - int i; - for (i = fd->var_count; i-- > 0;) { - if (js_key_equal (fd->vars[i].var_name, name) && fd->vars[i].scope_level == 0) - return i; - } - return find_arg (ctx, fd, name); -} - -/* return true if scope == parent_scope or if scope is a child of - parent_scope */ -static BOOL is_child_scope (JSContext *ctx, JSFunctionDef *fd, int scope, int parent_scope) { - while (scope >= 0) { - if (scope == parent_scope) return TRUE; - scope = fd->scopes[scope].parent; - } - return FALSE; -} - -/* find a 'var' declaration in the same scope or a child scope */ -static int find_var_in_child_scope (JSContext *ctx, JSFunctionDef *fd, JSValue name, int scope_level) { - int i; - for (i = 0; i < fd->var_count; i++) { - JSVarDef *vd = &fd->vars[i]; - if (js_key_equal (vd->var_name, name) && vd->scope_level == 0) { - if (is_child_scope (ctx, fd, vd->scope_next, scope_level)) return i; - } - } - return -1; -} - -static JSGlobalVar *find_global_var (JSFunctionDef *fd, JSValue name) { - int i; - for (i = 0; i < fd->global_var_count; i++) { - JSGlobalVar *hf = &fd->global_vars[i]; - if (js_key_equal (hf->var_name, name)) return hf; - } - return NULL; -} - -static JSGlobalVar *find_lexical_global_var (JSFunctionDef *fd, JSValue name) { - JSGlobalVar *hf = find_global_var (fd, name); - if (hf && hf->is_lexical) - return hf; - else - return NULL; -} - -static int find_lexical_decl (JSContext *ctx, JSFunctionDef *fd, JSValue name, int scope_idx, BOOL check_catch_var) { - while (scope_idx >= 0) { - JSVarDef *vd = &fd->vars[scope_idx]; - if (js_key_equal (vd->var_name, name) - && (vd->is_lexical - || (vd->var_kind == JS_VAR_CATCH && check_catch_var))) - return scope_idx; - scope_idx = vd->scope_next; - } - - return -1; -} - -static int push_scope (JSParseState *s) { - if (s->cur_func) { - JSFunctionDef *fd = s->cur_func; - int scope = fd->scope_count; - /* XXX: should check for scope overflow */ - if ((fd->scope_count + 1) > fd->scope_size) { - int new_size; - JSVarScope *new_buf; - /* XXX: potential arithmetic overflow */ - new_size = max_int (fd->scope_count + 1, fd->scope_size * 3 / 2); - if (fd->scopes == fd->def_scope_array) { - new_buf = pjs_malloc (new_size * sizeof (*fd->scopes)); - if (!new_buf) return -1; - memcpy (new_buf, fd->scopes, fd->scope_count * sizeof (*fd->scopes)); - } else { - new_buf = pjs_realloc (fd->scopes, new_size * sizeof (*fd->scopes)); - if (!new_buf) return -1; - } - fd->scopes = new_buf; - fd->scope_size = new_size; - } - fd->scope_count++; - fd->scopes[scope].parent = fd->scope_level; - fd->scopes[scope].first = fd->scope_first; - emit_op (s, OP_enter_scope); - emit_u16 (s, scope); - return fd->scope_level = scope; - } - return 0; -} - -static int get_first_lexical_var (JSFunctionDef *fd, int scope) { - while (scope >= 0) { - int scope_idx = fd->scopes[scope].first; - if (scope_idx >= 0) return scope_idx; - scope = fd->scopes[scope].parent; - } - return -1; -} - -static void pop_scope (JSParseState *s) { - if (s->cur_func) { - /* disable scoped variables */ - JSFunctionDef *fd = s->cur_func; - int scope = fd->scope_level; - emit_op (s, OP_leave_scope); - emit_u16 (s, scope); - fd->scope_level = fd->scopes[scope].parent; - fd->scope_first = get_first_lexical_var (fd, fd->scope_level); - } -} - -static void close_scopes (JSParseState *s, int scope, int scope_stop) { - while (scope > scope_stop) { - emit_op (s, OP_leave_scope); - emit_u16 (s, scope); - scope = s->cur_func->scopes[scope].parent; - } -} - -/* return the variable index or -1 if error */ -static int add_var (JSContext *ctx, JSFunctionDef *fd, JSValue name) { - JSVarDef *vd; - - /* the local variable indexes are currently stored on 16 bits */ - if (fd->var_count >= JS_MAX_LOCAL_VARS) { - JS_ThrowInternalError (ctx, "too many local variables"); - return -1; - } - if (pjs_resize_array ((void **)&fd->vars, sizeof (fd->vars[0]), &fd->var_size, fd->var_count + 1)) - return -1; - vd = &fd->vars[fd->var_count++]; - memset (vd, 0, sizeof (*vd)); - vd->var_name = name; - vd->func_pool_idx = -1; - return fd->var_count - 1; -} - -static int add_scope_var (JSContext *ctx, JSFunctionDef *fd, JSValue name, JSVarKindEnum var_kind) { - int idx = add_var (ctx, fd, name); - if (idx >= 0) { - JSVarDef *vd = &fd->vars[idx]; - vd->var_kind = var_kind; - vd->scope_level = fd->scope_level; - vd->scope_next = fd->scope_first; - fd->scopes[fd->scope_level].first = idx; - fd->scope_first = idx; - } - return idx; -} - -static int add_func_var (JSContext *ctx, JSFunctionDef *fd, JSValue name) { - int idx = fd->func_var_idx; - if (idx < 0 && (idx = add_var (ctx, fd, name)) >= 0) { - fd->func_var_idx = idx; - fd->vars[idx].var_kind = JS_VAR_FUNCTION_NAME; - fd->vars[idx].is_const = TRUE; - } - return idx; -} - -static int add_arg (JSContext *ctx, JSFunctionDef *fd, JSValue name) { - JSVarDef *vd; - - /* the local variable indexes are currently stored on 16 bits */ - if (fd->arg_count >= JS_MAX_LOCAL_VARS) { - JS_ThrowInternalError (ctx, "too many arguments"); - return -1; - } - if (pjs_resize_array ((void **)&fd->args, sizeof (fd->args[0]), &fd->arg_size, fd->arg_count + 1)) - return -1; - vd = &fd->args[fd->arg_count++]; - memset (vd, 0, sizeof (*vd)); - vd->var_name = name; - vd->func_pool_idx = -1; - return fd->arg_count - 1; -} - -/* add a global variable definition */ -static JSGlobalVar *add_global_var (JSContext *ctx, JSFunctionDef *s, JSValue name) { - JSGlobalVar *hf; - (void)ctx; - - if (pjs_resize_array ((void **)&s->global_vars, sizeof (s->global_vars[0]), &s->global_var_size, s->global_var_count + 1)) - return NULL; - hf = &s->global_vars[s->global_var_count++]; - hf->cpool_idx = -1; - hf->force_init = FALSE; - hf->is_lexical = FALSE; - hf->is_const = FALSE; - hf->scope_level = s->scope_level; - hf->var_name = name; - return hf; -} - -typedef enum { - JS_VAR_DEF_WITH, - JS_VAR_DEF_LET, - JS_VAR_DEF_CONST, - JS_VAR_DEF_FUNCTION_DECL, /* function declaration */ - JS_VAR_DEF_NEW_FUNCTION_DECL, /* async/generator function declaration */ - JS_VAR_DEF_CATCH, - JS_VAR_DEF_VAR, -} JSVarDefEnum; - -static int define_var (JSParseState *s, JSFunctionDef *fd, JSValue name, JSVarDefEnum var_def_type) { - JSContext *ctx = s->ctx; - JSVarDef *vd; - int idx; - - switch (var_def_type) { - case JS_VAR_DEF_WITH: - idx = add_scope_var (ctx, fd, name, JS_VAR_NORMAL); - break; - - case JS_VAR_DEF_LET: - case JS_VAR_DEF_CONST: - case JS_VAR_DEF_FUNCTION_DECL: - case JS_VAR_DEF_NEW_FUNCTION_DECL: - idx = find_lexical_decl (ctx, fd, name, fd->scope_first, TRUE); - if (idx >= 0) { - if (idx < GLOBAL_VAR_OFFSET) { - if (fd->vars[idx].scope_level == fd->scope_level) { - goto redef_lex_error; - } else if (fd->vars[idx].var_kind == JS_VAR_CATCH - && (fd->vars[idx].scope_level + 2) == fd->scope_level) { - goto redef_lex_error; - } - } else { - if (fd->scope_level == fd->body_scope) { - redef_lex_error: - return js_parse_error (s, - "invalid redefinition of lexical identifier"); - } - } - } - if (var_def_type != JS_VAR_DEF_FUNCTION_DECL - && var_def_type != JS_VAR_DEF_NEW_FUNCTION_DECL - && fd->scope_level == fd->body_scope - && find_arg (ctx, fd, name) >= 0) { - /* lexical variable redefines a parameter name */ - return js_parse_error (s, "invalid redefinition of parameter name"); - } - - if (find_var_in_child_scope (ctx, fd, name, fd->scope_level) >= 0) { - return js_parse_error (s, "invalid redefinition of a variable"); - } - - if (fd->is_global_var) { - JSGlobalVar *hf; - hf = find_global_var (fd, name); - if (hf && is_child_scope (ctx, fd, hf->scope_level, fd->scope_level)) { - return js_parse_error (s, "invalid redefinition of global identifier"); - } - } - - /* Global object is immutable - always use local variables */ - { - JSVarKindEnum var_kind; - if (var_def_type == JS_VAR_DEF_FUNCTION_DECL) - var_kind = JS_VAR_FUNCTION_DECL; - else if (var_def_type == JS_VAR_DEF_NEW_FUNCTION_DECL) - var_kind = JS_VAR_NEW_FUNCTION_DECL; - else - var_kind = JS_VAR_NORMAL; - idx = add_scope_var (ctx, fd, name, var_kind); - if (idx >= 0) { - vd = &fd->vars[idx]; - vd->is_lexical = 1; - vd->is_const = (var_def_type == JS_VAR_DEF_CONST); - } - } - break; - - case JS_VAR_DEF_CATCH: - idx = add_scope_var (ctx, fd, name, JS_VAR_CATCH); - break; - - case JS_VAR_DEF_VAR: - if (find_lexical_decl (ctx, fd, name, fd->scope_first, FALSE) >= 0) { - /* error to redefine a var that inside a lexical scope */ - return js_parse_error (s, "invalid redefinition of lexical identifier"); - } - if (fd->is_global_var) { - JSGlobalVar *hf; - hf = find_global_var (fd, name); - hf = add_global_var (s->ctx, fd, name); - if (!hf) return -1; - idx = GLOBAL_VAR_OFFSET; - } else { - /* if the variable already exists, don't add it again */ - idx = find_var (ctx, fd, name); - if (idx >= 0) break; - idx = add_var (ctx, fd, name); - if (idx >= 0) { fd->vars[idx].scope_next = fd->scope_level; } - } - break; - default: - abort (); - } - return idx; -} - -static __exception int js_parse_expr (JSParseState *s); -static __exception int js_parse_function_decl (JSParseState *s, - JSParseFunctionEnum func_type, - JSValue func_name, - const uint8_t *ptr); -static __exception int js_parse_function_decl2 (JSParseState *s, - JSParseFunctionEnum func_type, - JSValue func_name, - const uint8_t *ptr, - JSFunctionDef **pfd); -static __exception int js_parse_assign_expr2 (JSParseState *s, - int parse_flags); -static __exception int js_parse_assign_expr (JSParseState *s); -static __exception int js_parse_unary (JSParseState *s, int parse_flags); -static void push_break_entry (JSFunctionDef *fd, BlockEnv *be, JSValue label_name, int label_break, int label_cont, int drop_count); -static void pop_break_entry (JSFunctionDef *fd); - -static __exception int js_parse_template (JSParseState *s) { - JSContext *ctx = s->ctx; - PPretext *fmt = ppretext_init (64); - JSToken cooked; - int expr_count = 0; - - if (!fmt) return -1; - - while (s->token.val == TOK_TEMPLATE) { - const uint8_t *p = s->token.ptr + 1; - - /* re-parse the string with escape sequences and throw a - syntax error if it contains invalid sequences */ - s->token.u.str.str = JS_NULL; - if (js_parse_string (s, '`', TRUE, p, &cooked, &p)) { - ppretext_free (fmt); - return -1; - } - - /* Append cooked string to format string */ - fmt = ppretext_append_jsvalue (fmt, cooked.u.str.str); - if (!fmt) return -1; - - /* Check for end of template */ - if (s->token.u.str.sep == '`') break; - - /* Append placeholder {N} */ - fmt = ppretext_putc (fmt, '{'); - if (!fmt) return -1; - fmt = ppretext_append_int (fmt, expr_count); - if (!fmt) return -1; - fmt = ppretext_putc (fmt, '}'); - if (!fmt) return -1; - - /* Parse expression */ - if (next_token (s)) { ppretext_free (fmt); return -1; } - if (js_parse_expr (s)) { ppretext_free (fmt); return -1; } - expr_count++; - - if (s->token.val != '}') { - ppretext_free (fmt); - return js_parse_error (s, "expected '}' after template expression"); - } - - free_token (s, &s->token); - s->got_lf = FALSE; - if (js_parse_template_part (s, s->buf_ptr)) { - ppretext_free (fmt); - return -1; - } - } - - /* Finalize format string */ - JSValue format_str = ppretext_end (ctx, fmt); - if (JS_IsException (format_str)) return -1; - - int idx = cpool_add (s, format_str); - if (idx < 0) return -1; - - if (expr_count == 0) { - /* Simple template - just push the string */ - emit_op (s, OP_push_const); - emit_u32 (s, idx); - } else { - /* Template with expressions - emit u16 first for stack size computation */ - emit_op (s, OP_format_template); - emit_u16 (s, expr_count); - emit_u32 (s, idx); - } - - return next_token (s); -} - -#define PROP_TYPE_IDENT 0 -#define PROP_TYPE_VAR 1 - -static BOOL token_is_ident (int tok) { - /* Accept keywords and reserved words as property names */ - return (tok == TOK_IDENT - || (tok >= TOK_FIRST_KEYWORD && tok <= TOK_LAST_KEYWORD)); -} - -/* if the property is an expression, name = JS_NULL */ -static int __exception js_parse_property_name (JSParseState *s, JSValue *pname, BOOL allow_method, BOOL allow_var) { - BOOL is_non_reserved_ident; - JSValue name; - int prop_type; - - prop_type = PROP_TYPE_IDENT; - if (token_is_ident (s->token.val)) { - /* variable can only be a non-reserved identifier */ - is_non_reserved_ident - = (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved); - /* keywords and reserved words have a valid JSValue key */ - name = s->token.u.ident.str; - if (next_token (s)) goto fail1; - if (is_non_reserved_ident && prop_type == PROP_TYPE_IDENT && allow_var) { - if (!(s->token.val == ':' || (s->token.val == '(' && allow_method))) { - prop_type = PROP_TYPE_VAR; - } - } - } else if (s->token.val == TOK_STRING) { - /* String literal as property name - already interned from parsing */ - name = s->token.u.str.str; - if (next_token (s)) goto fail1; - } else if (s->token.val == TOK_NUMBER) { - /* Numeric property name - convert to string key */ - JSValue val = s->token.u.num.val; - JSValue str = JS_ToString (s->ctx, val); - if (JS_IsException (str)) goto fail; - name = str; - if (next_token (s)) goto fail1; - } else if (s->token.val == '[') { - if (next_token (s)) goto fail; - if (js_parse_expr (s)) goto fail; - if (js_parse_expect (s, ']')) goto fail; - name = JS_NULL; - } else { - goto invalid_prop; - } - if (prop_type != PROP_TYPE_IDENT && prop_type != PROP_TYPE_VAR - && s->token.val != '(') { - invalid_prop: - js_parse_error (s, "invalid property name"); - goto fail; - } - *pname = name; - return prop_type; -fail1: -fail: - *pname = JS_NULL; - return -1; -} - -typedef struct JSParsePos { - BOOL got_lf; - const uint8_t *ptr; -} JSParsePos; - -static int js_parse_get_pos (JSParseState *s, JSParsePos *sp) { - sp->ptr = s->token.ptr; - sp->got_lf = s->got_lf; - return 0; -} - -static __exception int js_parse_seek_token (JSParseState *s, - const JSParsePos *sp) { - s->buf_ptr = sp->ptr; - s->got_lf = sp->got_lf; - return next_token (s); -} - -/* return TRUE if a regexp literal is allowed after this token */ -static BOOL is_regexp_allowed (int tok) { - switch (tok) { - case TOK_NUMBER: - case TOK_STRING: - case TOK_REGEXP: - case TOK_DEC: - case TOK_INC: - case TOK_NULL: - case TOK_FALSE: - case TOK_TRUE: - case TOK_THIS: - case ')': - case ']': - case '}': /* XXX: regexp may occur after */ - case TOK_IDENT: - return FALSE; - default: - return TRUE; - } -} - -#define SKIP_HAS_SEMI (1 << 0) -#define SKIP_HAS_ASSIGNMENT (1 << 1) - -static BOOL has_lf_in_range (const uint8_t *p1, const uint8_t *p2) { - const uint8_t *tmp; - if (p1 > p2) { - tmp = p1; - p1 = p2; - p2 = tmp; - } - return (memchr (p1, '\n', p2 - p1) != NULL); -} - -/* XXX: improve speed with early bailout */ -/* XXX: no longer works if regexps are present. Could use previous - regexp parsing heuristics to handle most cases */ -static int js_parse_skip_parens_token (JSParseState *s, int *pbits, BOOL no_line_terminator) { - char state[256]; - size_t level = 0; - JSParsePos pos; - int last_tok, tok = TOK_EOF; - int c, tok_len, bits = 0; - const uint8_t *last_token_ptr; - - /* protect from underflow */ - state[level++] = 0; - - js_parse_get_pos (s, &pos); - last_tok = 0; - for (;;) { - switch (s->token.val) { - case '(': - case '[': - case '{': - if (level >= sizeof (state)) goto done; - state[level++] = s->token.val; - break; - case ')': - if (state[--level] != '(') goto done; - break; - case ']': - if (state[--level] != '[') goto done; - break; - case '}': - c = state[--level]; - if (c == '`') { - /* continue the parsing of the template */ - free_token (s, &s->token); - /* Resume TOK_TEMPLATE parsing (s->token.line_num and - * s->token.ptr are OK) */ - s->got_lf = FALSE; - if (js_parse_template_part (s, s->buf_ptr)) goto done; - goto handle_template; - } else if (c != '{') { - goto done; - } - break; - case TOK_TEMPLATE: - handle_template: - if (s->token.u.str.sep != '`') { - /* '${' inside the template : closing '}' and continue - parsing the template */ - if (level >= sizeof (state)) goto done; - state[level++] = '`'; - } - break; - case TOK_EOF: - goto done; - case ';': - if (level == 2) { bits |= SKIP_HAS_SEMI; } - break; - case '=': - bits |= SKIP_HAS_ASSIGNMENT; - break; - - case TOK_DIV_ASSIGN: - tok_len = 2; - goto parse_regexp; - case '/': - tok_len = 1; - parse_regexp: - if (is_regexp_allowed (last_tok)) { - s->buf_ptr -= tok_len; - if (js_parse_regexp (s)) { - /* XXX: should clear the exception */ - goto done; - } - } - break; - } - /* last_tok is only used to recognize regexps */ - if (s->token.val == TOK_IDENT - && (token_is_pseudo_keyword (s, JS_KEY_of) - || token_is_pseudo_keyword (s, JS_KEY_yield))) { - last_tok = TOK_OF; - } else { - last_tok = s->token.val; - } - last_token_ptr = s->token.ptr; - if (next_token (s)) { - /* XXX: should clear the exception generated by next_token() */ - break; - } - if (level <= 1) { - tok = s->token.val; - if (token_is_pseudo_keyword (s, JS_KEY_of)) tok = TOK_OF; - if (no_line_terminator && has_lf_in_range (last_token_ptr, s->token.ptr)) - tok = '\n'; - break; - } - } -done: - if (pbits) { *pbits = bits; } - if (js_parse_seek_token (s, &pos)) return -1; - return tok; -} - -static void set_object_name (JSParseState *s, JSValue name) { - JSFunctionDef *fd = s->cur_func; - int opcode; - - opcode = get_prev_opcode (fd); - if (opcode == OP_set_name) { - /* XXX: should free key after OP_set_name? */ - fd->byte_code.size = fd->last_opcode_pos; - fd->last_opcode_pos = -1; - emit_op (s, OP_set_name); - emit_key (s, name); - } else if (opcode == OP_set_class_name) { - int define_class_pos; - uint32_t cpool_idx; - define_class_pos = fd->last_opcode_pos + 1 - - get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1); - assert (fd->byte_code.buf[define_class_pos] == OP_define_class); - /* Update the cpool index in the bytecode to point to the new name */ - cpool_idx = cpool_add (s, name); - put_u32 (fd->byte_code.buf + define_class_pos + 1, cpool_idx); - fd->last_opcode_pos = -1; - } -} - -static void set_object_name_computed (JSParseState *s) { - JSFunctionDef *fd = s->cur_func; - int opcode; - - opcode = get_prev_opcode (fd); - if (opcode == OP_set_name) { - /* XXX: should free atom after OP_set_name? */ - fd->byte_code.size = fd->last_opcode_pos; - fd->last_opcode_pos = -1; - emit_op (s, OP_set_name_computed); - } else if (opcode == OP_set_class_name) { - int define_class_pos; - define_class_pos = fd->last_opcode_pos + 1 - - get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1); - assert (fd->byte_code.buf[define_class_pos] == OP_define_class); - fd->byte_code.buf[define_class_pos] = OP_define_class_computed; - fd->last_opcode_pos = -1; - } -} - -static __exception int js_parse_object_literal (JSParseState *s) { - JSValue name = JS_NULL; - const uint8_t *start_ptr; - int prop_type; - - if (next_token (s)) goto fail; - /* XXX: add an initial length that will be patched back */ - emit_op (s, OP_object); - - while (s->token.val != '}') { - /* specific case for getter/setter */ - start_ptr = s->token.ptr; - - prop_type = js_parse_property_name (s, &name, TRUE, TRUE); - if (prop_type < 0) goto fail; - - if (prop_type == PROP_TYPE_VAR) { - /* shortcut for x: x */ - emit_op (s, OP_scope_get_var); - emit_key (s, name); - emit_u16 (s, s->cur_func->scope_level); - emit_op (s, OP_define_field); - emit_prop_key (s, name); - } else if (s->token.val == '(') { - JSParseFunctionEnum func_type; - int op_flags; - - func_type = JS_PARSE_FUNC_METHOD; - if (js_parse_function_decl (s, func_type, JS_NULL, start_ptr)) goto fail; - if (JS_IsNull (name)) { - emit_op (s, OP_define_method_computed); - } else { - emit_op (s, OP_define_method); - emit_key (s, name); - } - - op_flags = OP_DEFINE_METHOD_METHOD; - emit_u8 (s, op_flags | OP_DEFINE_METHOD_ENUMERABLE); - } else { - if (JS_IsNull (name)) { - /* must be done before evaluating expr */ - emit_op (s, OP_to_propkey); - } - if (js_parse_expect (s, ':')) goto fail; - if (js_parse_assign_expr (s)) goto fail; - if (JS_IsNull (name)) { - set_object_name_computed (s); - emit_op (s, OP_define_array_el); - emit_op (s, OP_drop); - } else { - set_object_name (s, name); - emit_op (s, OP_define_field); - emit_prop_key (s, name); - } - } - name = JS_NULL; - if (s->token.val != ',') break; - if (next_token (s)) goto fail; - } - if (js_parse_expect (s, '}')) goto fail; - return 0; -fail: - return -1; -} - -/* allow the 'in' binary operator */ -#define PF_IN_ACCEPTED (1 << 0) -/* allow function calls parsing in js_parse_postfix_expr() */ -#define PF_POSTFIX_CALL (1 << 1) -/* allow the exponentiation operator in js_parse_unary() */ -#define PF_POW_ALLOWED (1 << 2) -/* forbid the exponentiation operator in js_parse_unary() */ -#define PF_POW_FORBIDDEN (1 << 3) - -static __exception int js_parse_postfix_expr (JSParseState *s, - int parse_flags); - -static JSFunctionDef * -js_new_function_def (JSContext *ctx, JSFunctionDef *parent, BOOL is_func_expr, const char *filename, const uint8_t *source_ptr, GetLineColCache *get_line_col_cache); -static void emit_return (JSParseState *s, BOOL hasval); - -static __exception int js_parse_left_hand_side_expr (JSParseState *s) { - return js_parse_postfix_expr (s, PF_POSTFIX_CALL); -} - -#define ARRAY_LITERAL_MAX 1024 - -static __exception int js_parse_array_literal (JSParseState *s) { - uint32_t idx = 0; - - if (next_token (s)) return -1; - - while (s->token.val != ']' && idx < ARRAY_LITERAL_MAX) { - if (s->token.val == ',') - return js_parse_error (s, "array holes not allowed"); - - if (js_parse_assign_expr (s)) return -1; - idx++; - - if (s->token.val == ',') { - if (next_token (s)) return -1; - } else if (s->token.val != ']') { - return js_parse_error (s, "expected ',' or ']'"); - } - } - - if (s->token.val != ']') return js_parse_error (s, "array literal too long"); - - emit_op (s, OP_array_from); - emit_u16 (s, idx); - return js_parse_expect (s, ']'); -} - -static __exception int get_lvalue (JSParseState *s, int *popcode, int *pscope, JSValue *pname, int *plabel, int *pdepth, BOOL keep, int tok) { - JSFunctionDef *fd; - int opcode, scope, label, depth; - JSValue name; - - /* we check the last opcode to get the lvalue type */ - fd = s->cur_func; - scope = 0; - name = JS_NULL; - label = -1; - depth = 0; - switch (opcode = get_prev_opcode (fd)) { - case OP_scope_get_var: { - /* Read cpool index and get key from cpool */ - uint32_t idx = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1); - scope = get_u16 (fd->byte_code.buf + fd->last_opcode_pos + 5); - name = fd->cpool[idx]; - if (js_key_equal_str (name, "eval")) { - return js_parse_error (s, "invalid lvalue in strict mode"); - } - if (js_key_equal_str (name, "this") - || js_key_equal_str (name, "new.target")) { - goto invalid_lvalue; - } - /* Variable lvalue: depth=0, no reference objects on stack */ - depth = 0; - break; - } - case OP_get_field: { - /* Read cpool index and get property name from cpool */ - uint32_t idx = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1); - name = fd->cpool[idx]; - depth = 1; - break; - } - case OP_get_array_el: - depth = 2; - break; - default: - invalid_lvalue: - if (tok == TOK_FOR) { - return js_parse_error (s, "invalid for in/of left hand-side"); - } else if (tok == TOK_INC || tok == TOK_DEC) { - return js_parse_error (s, "invalid increment/decrement operand"); - } else if (tok == '[' || tok == '{') { - return js_parse_error (s, "invalid destructuring target"); - } else { - return js_parse_error (s, "invalid assignment left-hand side"); - } - } - /* remove the last opcode */ - fd->byte_code.size = fd->last_opcode_pos; - fd->last_opcode_pos = -1; - - if (keep) { - /* get the value but keep the object/fields on the stack */ - switch (opcode) { - case OP_scope_get_var: - /* For variable lvalues, just re-emit the get to have the value on stack. - No reference objects needed - put_lvalue will emit scope_put_var directly. */ - emit_op (s, OP_scope_get_var); - emit_key (s, name); - emit_u16 (s, scope); - break; - case OP_get_field: - emit_op (s, OP_get_field2); - emit_prop_key (s, name); - break; - case OP_get_array_el: - emit_op (s, OP_get_array_el3); - break; - default: - abort (); - } - } - /* For variable lvalues without keep, nothing needs to be emitted. - put_lvalue will handle the store with just the value on stack. */ - - *popcode = opcode; - *pscope = scope; - /* name is set for OP_get_field, OP_scope_get_var, and JS_NULL for array_el */ - *pname = name; - *plabel = label; - if (pdepth) *pdepth = depth; - return 0; -} - -typedef enum { - PUT_LVALUE_NOKEEP, /* [depth] v -> */ - PUT_LVALUE_NOKEEP_DEPTH, /* [depth] v -> , keep depth (currently - just disable optimizations) */ - PUT_LVALUE_KEEP_TOP, /* [depth] v -> v */ - PUT_LVALUE_KEEP_SECOND, /* [depth] v0 v -> v0 */ - PUT_LVALUE_NOKEEP_BOTTOM, /* v [depth] -> */ -} PutLValueEnum; - -/* name has a live reference. 'is_let' is only used with opcode = - OP_scope_get_var for variable lvalues (no reference objects on stack). */ -static void put_lvalue (JSParseState *s, int opcode, int scope, JSValue name, int label, PutLValueEnum special, BOOL is_let) { - switch (opcode) { - case OP_scope_get_var: - /* depth = 0 for variable lvalues - no reference objects on stack */ - switch (special) { - case PUT_LVALUE_NOKEEP: - case PUT_LVALUE_NOKEEP_DEPTH: - /* val -> (store, nothing left) */ - break; - case PUT_LVALUE_KEEP_TOP: - /* val -> val (dup before store) */ - emit_op (s, OP_dup); - break; - case PUT_LVALUE_KEEP_SECOND: - /* v0 v -> v0 (store v, keep v0) - for postfix ++/-- */ - /* stack: old_val new_val, store new_val, result is old_val */ - break; - case PUT_LVALUE_NOKEEP_BOTTOM: - /* val -> (same as NOKEEP for depth=0) */ - break; - default: - abort (); - } - break; - case OP_get_field: - /* depth = 1 */ - switch (special) { - case PUT_LVALUE_NOKEEP: - case PUT_LVALUE_NOKEEP_DEPTH: - break; - case PUT_LVALUE_KEEP_TOP: - emit_op (s, OP_insert2); /* obj v -> v obj v */ - break; - case PUT_LVALUE_KEEP_SECOND: - emit_op (s, OP_perm3); /* obj v0 v -> v0 obj v */ - break; - case PUT_LVALUE_NOKEEP_BOTTOM: - emit_op (s, OP_swap); - break; - default: - abort (); - } - break; - case OP_get_array_el: - /* depth = 2 */ - switch (special) { - case PUT_LVALUE_NOKEEP: - emit_op (s, OP_nop); /* will trigger optimization */ - break; - case PUT_LVALUE_NOKEEP_DEPTH: - break; - case PUT_LVALUE_KEEP_TOP: - emit_op (s, OP_insert3); /* obj prop v -> v obj prop v */ - break; - case PUT_LVALUE_KEEP_SECOND: - emit_op (s, OP_perm4); /* obj prop v0 v -> v0 obj prop v */ - break; - case PUT_LVALUE_NOKEEP_BOTTOM: - emit_op (s, OP_rot3l); - break; - default: - abort (); - } - break; - default: - break; - } - - switch (opcode) { - case OP_scope_get_var: /* val -> */ - emit_op (s, is_let ? OP_scope_put_var_init : OP_scope_put_var); - emit_key (s, name); /* emit cpool index */ - emit_u16 (s, scope); - break; - case OP_get_field: - emit_op (s, OP_put_field); - emit_prop_key (s, name); /* name has refcount */ - break; - case OP_get_array_el: - emit_op (s, OP_put_array_el); - break; - default: - abort (); - } -} - -static __exception int js_parse_expr_paren (JSParseState *s) { - if (js_parse_expect (s, '(')) return -1; - if (js_parse_expr (s)) return -1; - if (js_parse_expect (s, ')')) return -1; - return 0; -} - -static int js_unsupported_keyword (JSParseState *s, JSValue key) { - char buf[KEY_GET_STR_BUF_SIZE]; - return js_parse_error (s, "unsupported keyword: %s", JS_KeyGetStr (s->ctx, buf, sizeof (buf), key)); -} - -static __exception int js_define_var (JSParseState *s, JSValue name, int tok) { - JSFunctionDef *fd = s->cur_func; - JSVarDefEnum var_def_type; - - if (js_key_equal (name, JS_KEY_eval)) { - return js_parse_error (s, "invalid variable name in strict mode"); - } - if (js_key_equal (name, JS_KEY_let) && tok == TOK_DEF) { - return js_parse_error (s, "invalid lexical variable name"); - } - switch (tok) { - case TOK_DEF: - var_def_type = JS_VAR_DEF_CONST; - break; - case TOK_VAR: - var_def_type = JS_VAR_DEF_LET; - break; - case TOK_DISRUPTION: - var_def_type = JS_VAR_DEF_CATCH; - break; - default: - abort (); - } - if (define_var (s, fd, name, var_def_type) < 0) return -1; - return 0; -} - -static int js_parse_check_duplicate_parameter (JSParseState *s, JSValue name) { - /* Check for duplicate parameter names */ - JSFunctionDef *fd = s->cur_func; - int i; - for (i = 0; i < fd->arg_count; i++) { - if (js_key_equal (fd->args[i].var_name, name)) goto duplicate; - } - for (i = 0; i < fd->var_count; i++) { - if (js_key_equal (fd->vars[i].var_name, name)) goto duplicate; - } - return 0; - -duplicate: - return js_parse_error ( - s, "duplicate parameter names not allowed in this context"); -} - -static JSValue js_parse_destructuring_var (JSParseState *s, int tok, int is_arg) { - JSValue name; - - if (!(s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved) - || js_key_equal (s->token.u.ident.str, JS_KEY_eval)) { - js_parse_error (s, "invalid destructuring target"); - return JS_NULL; - } - name = s->token.u.ident.str; - if (is_arg && js_parse_check_duplicate_parameter (s, name)) goto fail; - if (next_token (s)) goto fail; - - return name; -fail: - return JS_NULL; -} - -/* Return -1 if error, 0 if no initializer, 1 if an initializer is - present at the top level. */ -static int js_parse_destructuring_element (JSParseState *s, int tok, int is_arg, int hasval, BOOL allow_initializer, BOOL export_flag) { - int label_parse, label_assign, label_done, label_lvalue, depth_lvalue; - int start_addr, assign_addr; - JSValue prop_name, var_name; - int opcode, scope, tok1, skip_bits; - BOOL has_initializer; - - label_parse = new_label (s); - label_assign = new_label (s); - - start_addr = s->cur_func->byte_code.size; - if (hasval) { - /* consume value from the stack */ - emit_op (s, OP_dup); - emit_op (s, OP_null); - emit_op (s, OP_strict_eq); - emit_goto (s, OP_if_true, label_parse); - emit_label (s, label_assign); - } else { - emit_goto (s, OP_goto, label_parse); - emit_label (s, label_assign); - /* leave value on the stack */ - emit_op (s, OP_dup); - } - assign_addr = s->cur_func->byte_code.size; - if (s->token.val == '{') { - if (next_token (s)) return -1; - /* XXX: throw an exception if the value cannot be converted to an object */ - while (s->token.val != '}') { - int prop_type; - prop_type = js_parse_property_name (s, &prop_name, FALSE, TRUE); - if (prop_type < 0) return -1; - var_name = JS_NULL; - if (prop_type == PROP_TYPE_IDENT) { - if (next_token (s)) goto prop_error; - if ((s->token.val == '[' || s->token.val == '{') - && ((tok1 = js_parse_skip_parens_token (s, &skip_bits, FALSE)) - == ',' - || tok1 == '=' || tok1 == '}')) { - if (JS_IsNull (prop_name)) { - /* computed property name on stack */ - /* get the computed property from the source object */ - emit_op (s, OP_get_array_el2); - } else { - /* named property */ - /* get the named property from the source object */ - emit_op (s, OP_get_field2); - emit_prop_key (s, prop_name); - } - if (js_parse_destructuring_element (s, tok, is_arg, TRUE, TRUE, export_flag) - < 0) - return -1; - if (s->token.val == '}') break; - /* accept a trailing comma before the '}' */ - if (js_parse_expect (s, ',')) return -1; - continue; - } - if (JS_IsNull (prop_name)) { - emit_op (s, OP_to_propkey); - /* source prop -- source source prop */ - emit_op (s, OP_dup1); - } else { - /* source -- source source */ - emit_op (s, OP_dup); - } - if (tok) { - var_name = js_parse_destructuring_var (s, tok, is_arg); - if (JS_IsNull (var_name)) goto prop_error; - /* no need to make a reference for let/const */ - opcode = OP_scope_get_var; - scope = s->cur_func->scope_level; - label_lvalue = -1; - depth_lvalue = 0; - } else { - if (js_parse_left_hand_side_expr (s)) goto prop_error; - lvalue1: - if (get_lvalue (s, &opcode, &scope, &var_name, &label_lvalue, &depth_lvalue, FALSE, '{')) - goto prop_error; - /* swap ref and lvalue object if any */ - if (JS_IsNull (prop_name)) { - switch (depth_lvalue) { - case 1: - /* source prop x -> x source prop */ - emit_op (s, OP_rot3r); - break; - case 2: - /* source prop x y -> x y source prop */ - emit_op (s, OP_swap2); /* t p2 s p1 */ - break; - case 3: - /* source prop x y z -> x y z source prop */ - emit_op (s, OP_rot5l); - emit_op (s, OP_rot5l); - break; - } - } else { - switch (depth_lvalue) { - case 1: - /* source x -> x source */ - emit_op (s, OP_swap); - break; - case 2: - /* source x y -> x y source */ - emit_op (s, OP_rot3l); - break; - case 3: - /* source x y z -> x y z source */ - emit_op (s, OP_rot4l); - break; - } - } - } - if (JS_IsNull (prop_name)) { - /* computed property name on stack */ - /* XXX: should have OP_get_array_el2x with depth */ - /* source prop -- val */ - emit_op (s, OP_get_array_el); - } else { - /* named property */ - /* XXX: should have OP_get_field2x with depth */ - /* source -- val */ - emit_op (s, OP_get_field); - emit_prop_key (s, prop_name); - } - } else { - /* prop_type = PROP_TYPE_VAR, cannot be a computed property */ - if (is_arg && js_parse_check_duplicate_parameter (s, prop_name)) - goto prop_error; - if (js_key_equal_str (prop_name, "eval")) { - js_parse_error (s, "invalid destructuring target"); - goto prop_error; - } - if (!tok) { - /* generate reference */ - /* source -- source source */ - emit_op (s, OP_dup); - emit_op (s, OP_scope_get_var); - emit_key (s, prop_name); - emit_u16 (s, s->cur_func->scope_level); - goto lvalue1; - } else { - /* no need to make a reference for let/const */ - var_name = prop_name; - opcode = OP_scope_get_var; - scope = s->cur_func->scope_level; - label_lvalue = -1; - depth_lvalue = 0; - - /* source -- source val */ - emit_op (s, OP_get_field2); - emit_prop_key (s, prop_name); - } - } - if (tok) { - if (js_define_var (s, var_name, tok)) goto var_error; - scope = s->cur_func->scope_level; /* XXX: check */ - } - if (s->token.val == '=') { /* handle optional default value */ - int label_hasval; - emit_op (s, OP_dup); - emit_op (s, OP_null); - emit_op (s, OP_strict_eq); - label_hasval = emit_goto (s, OP_if_false, -1); - if (next_token (s)) goto var_error; - emit_op (s, OP_drop); - if (js_parse_assign_expr (s)) goto var_error; - if (opcode == OP_scope_get_var) - set_object_name (s, var_name); - emit_label (s, label_hasval); - } - /* store value into lvalue object */ - put_lvalue (s, opcode, scope, var_name, label_lvalue, PUT_LVALUE_NOKEEP_DEPTH, (tok == TOK_DEF || tok == TOK_VAR)); - if (s->token.val == '}') break; - /* accept a trailing comma before the '}' */ - if (js_parse_expect (s, ',')) return -1; - } - /* drop the source object */ - emit_op (s, OP_drop); - if (next_token (s)) return -1; - } else if (s->token.val == '[') { - return js_parse_error (s, "array destructuring is not supported"); - } else { - return js_parse_error (s, "invalid assignment syntax"); - } - if (s->token.val == '=' && allow_initializer) { - label_done = emit_goto (s, OP_goto, -1); - if (next_token (s)) return -1; - emit_label (s, label_parse); - if (hasval) emit_op (s, OP_drop); - if (js_parse_assign_expr (s)) return -1; - emit_goto (s, OP_goto, label_assign); - emit_label (s, label_done); - has_initializer = TRUE; - } else { - /* normally hasval is true except if - js_parse_skip_parens_token() was wrong in the parsing */ - // assert(hasval); - if (!hasval) { - js_parse_error (s, "too complicated destructuring expression"); - return -1; - } - /* remove test and decrement label ref count */ - memset (s->cur_func->byte_code.buf + start_addr, OP_nop, assign_addr - start_addr); - s->cur_func->label_slots[label_parse].ref_count--; - has_initializer = FALSE; - } - return has_initializer; - -prop_error: -var_error: - return -1; -} - -static void optional_chain_test (JSParseState *s, - int *poptional_chaining_label, - int drop_count) { - int label_next, i; - if (*poptional_chaining_label < 0) *poptional_chaining_label = new_label (s); - /* XXX: could be more efficient with a specific opcode */ - emit_op (s, OP_dup); - emit_op (s, OP_is_null); - label_next = emit_goto (s, OP_if_false, -1); - for (i = 0; i < drop_count; i++) - emit_op (s, OP_drop); - emit_op (s, OP_null); - emit_goto (s, OP_goto, *poptional_chaining_label); - emit_label (s, label_next); -} - -/* allowed parse_flags: PF_POSTFIX_CALL */ -static __exception int js_parse_postfix_expr (JSParseState *s, - int parse_flags) { - int optional_chaining_label; - BOOL accept_lparen = (parse_flags & PF_POSTFIX_CALL) != 0; - const uint8_t *op_token_ptr; - - switch (s->token.val) { - case TOK_NUMBER: { - JSValue val; - val = s->token.u.num.val; - - if (JS_VALUE_GET_TAG (val) == JS_TAG_INT) { - emit_op (s, OP_push_i32); - emit_u32 (s, JS_VALUE_GET_INT (val)); - } else { - if (emit_push_const (s, val) < 0) return -1; - } - } - if (next_token (s)) return -1; - break; - case TOK_TEMPLATE: - if (js_parse_template (s)) return -1; - break; - case TOK_STRING: - if (emit_push_const (s, s->token.u.str.str)) return -1; - if (next_token (s)) return -1; - break; - - case TOK_DIV_ASSIGN: - s->buf_ptr -= 2; - goto parse_regexp; - case '/': - s->buf_ptr--; - parse_regexp: { - JSValue str; - int ret; - if (!s->ctx->compile_regexp) - return js_parse_error (s, "RegExp are not supported"); - /* the previous token is '/' or '/=', so no need to free */ - if (js_parse_regexp (s)) return -1; - ret = emit_push_const (s, s->token.u.regexp.body); - str = s->ctx->compile_regexp (s->ctx, s->token.u.regexp.body, s->token.u.regexp.flags); - if (JS_IsException (str)) { - /* add the line number info */ - int line_num, col_num; - line_num - = get_line_col (&col_num, s->buf_start, s->token.ptr - s->buf_start); - build_backtrace (s->ctx, s->ctx->current_exception, s->filename, line_num + 1, col_num + 1, 0); - return -1; - } - ret = emit_push_const (s, str); - if (ret) return -1; - /* we use a specific opcode to be sure the correct - function is called (otherwise the bytecode would have - to be verified by the RegExp constructor) */ - emit_op (s, OP_regexp); - if (next_token (s)) return -1; - } break; - case '(': - if (js_parse_expr_paren (s)) return -1; - break; - case TOK_FUNCTION: - if (js_parse_function_decl (s, JS_PARSE_FUNC_EXPR, JS_NULL, s->token.ptr)) - return -1; - break; - case TOK_NULL: - if (next_token (s)) return -1; - emit_op (s, OP_null); - break; - case TOK_THIS: - if (next_token (s)) return -1; - emit_op (s, OP_scope_get_var); - emit_key (s, JS_KEY_this); - emit_u16 (s, 0); - break; - case TOK_FALSE: - if (next_token (s)) return -1; - emit_op (s, OP_push_false); - break; - case TOK_TRUE: - if (next_token (s)) return -1; - emit_op (s, OP_push_true); - break; - case TOK_IDENT: { - JSValue name; - const uint8_t *source_ptr; - if (s->token.u.ident.is_reserved) { - return js_parse_error_reserved_identifier (s); - } - source_ptr = s->token.ptr; - name = s->token.u.ident.str; - if (next_token (s)) { - return -1; - } - emit_source_pos (s, source_ptr); - emit_op (s, OP_scope_get_var); - emit_key (s, name); - emit_u16 (s, s->cur_func->scope_level); - } break; - case '{': - case '[': - if (s->token.val == '{') { - if (js_parse_object_literal (s)) return -1; - } else { - if (js_parse_array_literal (s)) return -1; - } - break; - default: - return js_parse_error (s, "unexpected token in expression: '%.*s'", (int)(s->buf_ptr - s->token.ptr), s->token.ptr); - } - - optional_chaining_label = -1; - for (;;) { - JSFunctionDef *fd = s->cur_func; - BOOL has_optional_chain = FALSE; - - if (s->token.val == '(' && accept_lparen) { - int opcode, arg_count, drop_count; - - /* function call */ - parse_func_call: - op_token_ptr = s->token.ptr; - if (next_token (s)) return -1; - - switch (opcode = get_prev_opcode (fd)) { - case OP_get_field: - /* keep the object on the stack */ - fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2; - drop_count = 2; - break; - case OP_get_field_opt_chain: { - int opt_chain_label, next_label; - opt_chain_label - = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1 + 4 + 1); - /* keep the object on the stack */ - fd->byte_code.buf[fd->last_opcode_pos] = OP_get_field2; - fd->byte_code.size = fd->last_opcode_pos + 1 + 4; - next_label = emit_goto (s, OP_goto, -1); - emit_label (s, opt_chain_label); - /* need an additional undefined value for the - case where the optional field does not - exists */ - emit_op (s, OP_null); - emit_label (s, next_label); - drop_count = 2; - opcode = OP_get_field; - } break; - case OP_get_array_el: - /* keep the object on the stack */ - fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2; - drop_count = 2; - break; - case OP_get_array_el_opt_chain: { - int opt_chain_label, next_label; - opt_chain_label - = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1 + 1); - /* keep the object on the stack */ - fd->byte_code.buf[fd->last_opcode_pos] = OP_get_array_el2; - fd->byte_code.size = fd->last_opcode_pos + 1; - next_label = emit_goto (s, OP_goto, -1); - emit_label (s, opt_chain_label); - /* need an additional undefined value for the - case where the optional field does not - exists */ - emit_op (s, OP_null); - emit_label (s, next_label); - drop_count = 2; - opcode = OP_get_array_el; - } break; - case OP_scope_get_var: { - /* verify if function name resolves to a simple - get_loc/get_arg: a function call inside a `with` - statement can resolve to a method call of the - `with` context object - */ - drop_count = 1; - } break; - default: - opcode = OP_invalid; - drop_count = 1; - break; - } - if (has_optional_chain) { - optional_chain_test (s, &optional_chaining_label, drop_count); - } - - /* parse arguments */ - arg_count = 0; - while (s->token.val != ')') { - if (arg_count >= 65535) { - return js_parse_error (s, "Too many call arguments"); - } - if (js_parse_assign_expr (s)) return -1; - arg_count++; - if (s->token.val == ')') break; - /* accept a trailing comma before the ')' */ - if (js_parse_expect (s, ',')) return -1; - } - if (next_token (s)) return -1; - emit_func_call: { - emit_source_pos (s, op_token_ptr); - switch (opcode) { - case OP_get_field: - case OP_get_array_el: - emit_op (s, OP_call_method); - emit_u16 (s, arg_count); - break; - default: - emit_op (s, OP_call); - emit_u16 (s, arg_count); - break; - } - } - } else if (s->token.val == '.') { - op_token_ptr = s->token.ptr; - if (next_token (s)) return -1; - parse_property: - emit_source_pos (s, op_token_ptr); - if (!token_is_ident (s->token.val)) { - return js_parse_error (s, "expecting field name"); - } - if (has_optional_chain) { - optional_chain_test (s, &optional_chaining_label, 1); - } - emit_op (s, OP_get_field); - emit_prop_key (s, s->token.u.ident.str); - if (next_token (s)) return -1; - } else if (s->token.val == '[') { - op_token_ptr = s->token.ptr; - parse_array_access: - if (has_optional_chain) { - optional_chain_test (s, &optional_chaining_label, 1); - } - if (next_token (s)) return -1; - if (js_parse_expr (s)) return -1; - if (js_parse_expect (s, ']')) return -1; - emit_source_pos (s, op_token_ptr); - emit_op (s, OP_get_array_el); - } else { - break; - } - } - if (optional_chaining_label >= 0) { - JSFunctionDef *fd = s->cur_func; - int opcode; - emit_label_raw (s, optional_chaining_label); - /* modify the last opcode so that it is an indicator of an - optional chain */ - opcode = get_prev_opcode (fd); - if (opcode == OP_get_field || opcode == OP_get_array_el) { - if (opcode == OP_get_field) - opcode = OP_get_field_opt_chain; - else - opcode = OP_get_array_el_opt_chain; - fd->byte_code.buf[fd->last_opcode_pos] = opcode; - } else { - fd->last_opcode_pos = -1; - } - } - return 0; -} - -static __exception int js_parse_delete (JSParseState *s) { - JSFunctionDef *fd = s->cur_func; - int opcode; - - if (next_token (s)) return -1; - if (js_parse_unary (s, PF_POW_FORBIDDEN)) return -1; - switch (opcode = get_prev_opcode (fd)) { - case OP_get_field: - case OP_get_field_opt_chain: { - JSValue val; - int ret, opt_chain_label, next_label; - if (opcode == OP_get_field_opt_chain) { - opt_chain_label - = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1 + 4 + 1); - } else { - opt_chain_label = -1; - } - { - /* Read cpool index and get property name from cpool */ - uint32_t idx = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1); - val = fd->cpool[idx]; - } - fd->byte_code.size = fd->last_opcode_pos; - ret = emit_push_const (s, val); - if (ret) return ret; - emit_op (s, OP_delete); - if (opt_chain_label >= 0) { - next_label = emit_goto (s, OP_goto, -1); - emit_label (s, opt_chain_label); - /* if the optional chain is not taken, return 'true' */ - emit_op (s, OP_drop); - emit_op (s, OP_push_true); - emit_label (s, next_label); - } - fd->last_opcode_pos = -1; - } break; - case OP_get_array_el: - fd->byte_code.size = fd->last_opcode_pos; - fd->last_opcode_pos = -1; - emit_op (s, OP_delete); - break; - case OP_get_array_el_opt_chain: { - int opt_chain_label, next_label; - opt_chain_label - = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1 + 1); - fd->byte_code.size = fd->last_opcode_pos; - emit_op (s, OP_delete); - next_label = emit_goto (s, OP_goto, -1); - emit_label (s, opt_chain_label); - /* if the optional chain is not taken, return 'true' */ - emit_op (s, OP_drop); - emit_op (s, OP_push_true); - emit_label (s, next_label); - fd->last_opcode_pos = -1; - } break; - case OP_scope_get_var: { - /* 'delete this': this is not a reference */ - uint32_t idx = get_u32 (fd->byte_code.buf + fd->last_opcode_pos + 1); - JSValue name = fd->cpool[idx]; - if (js_key_equal (name, JS_KEY_this) - || js_key_equal_str (name, "new.target")) - goto ret_true; - - return js_parse_error (s, - "cannot delete a direct reference in strict mode"); - } break; - default: - ret_true: - emit_op (s, OP_drop); - emit_op (s, OP_push_true); - break; - } - return 0; -} - -/* allowed parse_flags: PF_POW_ALLOWED, PF_POW_FORBIDDEN */ -static __exception int js_parse_unary (JSParseState *s, int parse_flags) { - int op; - const uint8_t *op_token_ptr; - - switch (s->token.val) { - case '+': - case '-': - case '!': - case '~': - op_token_ptr = s->token.ptr; - op = s->token.val; - if (next_token (s)) return -1; - if (js_parse_unary (s, PF_POW_FORBIDDEN)) return -1; - switch (op) { - case '-': - emit_source_pos (s, op_token_ptr); - emit_op (s, OP_neg); - break; - case '+': - emit_source_pos (s, op_token_ptr); - emit_op (s, OP_plus); - break; - case '!': - emit_op (s, OP_lnot); - break; - case '~': - emit_source_pos (s, op_token_ptr); - emit_op (s, OP_not); - break; - default: - abort (); - } - parse_flags = 0; - break; - case TOK_DEC: - case TOK_INC: { - int opcode, op, scope, label; - JSValue name; - op = s->token.val; - op_token_ptr = s->token.ptr; - if (next_token (s)) return -1; - if (js_parse_unary (s, 0)) return -1; - if (get_lvalue (s, &opcode, &scope, &name, &label, NULL, TRUE, op)) - return -1; - emit_source_pos (s, op_token_ptr); - emit_op (s, OP_dec + op - TOK_DEC); - put_lvalue (s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, FALSE); - } break; - case TOK_DELETE: - if (js_parse_delete (s)) return -1; - parse_flags = 0; - break; - default: - if (js_parse_postfix_expr (s, PF_POSTFIX_CALL)) return -1; - if (!s->got_lf && (s->token.val == TOK_DEC || s->token.val == TOK_INC)) { - int opcode, op, scope, label; - JSValue name; - op = s->token.val; - op_token_ptr = s->token.ptr; - if (get_lvalue (s, &opcode, &scope, &name, &label, NULL, TRUE, op)) - return -1; - emit_source_pos (s, op_token_ptr); - emit_op (s, OP_post_dec + op - TOK_DEC); - put_lvalue (s, opcode, scope, name, label, PUT_LVALUE_KEEP_SECOND, FALSE); - if (next_token (s)) return -1; - } - break; - } - if (parse_flags & (PF_POW_ALLOWED | PF_POW_FORBIDDEN)) { - if (s->token.val == TOK_POW) { - /* Strict ES7 exponentiation syntax rules: To solve - conficting semantics between different implementations - regarding the precedence of prefix operators and the - postifx exponential, ES7 specifies that -2**2 is a - syntax error. */ - if (parse_flags & PF_POW_FORBIDDEN) { - JS_ThrowSyntaxError (s->ctx, "unparenthesized unary expression can't " - "appear on the left-hand side of '**'"); - return -1; - } - op_token_ptr = s->token.ptr; - if (next_token (s)) return -1; - if (js_parse_unary (s, PF_POW_ALLOWED)) return -1; - emit_source_pos (s, op_token_ptr); - emit_op (s, OP_pow); - } - } - return 0; -} - -/* allowed parse_flags: PF_IN_ACCEPTED */ -static __exception int js_parse_expr_binary (JSParseState *s, int level, int parse_flags) { - int op, opcode; - const uint8_t *op_token_ptr; - - if (level == 0) { - return js_parse_unary (s, PF_POW_ALLOWED); - } else { - if (js_parse_expr_binary (s, level - 1, parse_flags)) return -1; - } - for (;;) { - op = s->token.val; - op_token_ptr = s->token.ptr; - switch (level) { - case 1: - switch (op) { - case '*': - opcode = OP_mul; - break; - case '/': - opcode = OP_div; - break; - case '%': - opcode = OP_mod; - break; - default: - return 0; - } - break; - case 2: - switch (op) { - case '+': - opcode = OP_add; - break; - case '-': - opcode = OP_sub; - break; - default: - return 0; - } - break; - case 3: - switch (op) { - case TOK_SHL: - opcode = OP_shl; - break; - case TOK_SAR: - opcode = OP_sar; - break; - case TOK_SHR: - opcode = OP_shr; - break; - default: - return 0; - } - break; - case 4: - switch (op) { - case '<': - opcode = OP_lt; - break; - case '>': - opcode = OP_gt; - break; - case TOK_LTE: - opcode = OP_lte; - break; - case TOK_GTE: - opcode = OP_gte; - break; - case TOK_IN: - if (parse_flags & PF_IN_ACCEPTED) { - opcode = OP_in; - } else { - return 0; - } - break; - default: - return 0; - } - break; - case 5: - switch (op) { - case TOK_STRICT_EQ: - opcode = OP_strict_eq; - break; - case TOK_STRICT_NEQ: - opcode = OP_strict_neq; - break; - default: - return 0; - } - break; - case 6: - switch (op) { - case '&': - opcode = OP_and; - break; - default: - return 0; - } - break; - case 7: - switch (op) { - case '^': - opcode = OP_xor; - break; - default: - return 0; - } - break; - case 8: - switch (op) { - case '|': - opcode = OP_or; - break; - default: - return 0; - } - break; - default: - abort (); - } - if (next_token (s)) return -1; - if (js_parse_expr_binary (s, level - 1, parse_flags)) return -1; - emit_source_pos (s, op_token_ptr); - emit_op (s, opcode); - } - return 0; -} - -/* allowed parse_flags: PF_IN_ACCEPTED */ -static __exception int js_parse_logical_and_or (JSParseState *s, int op, int parse_flags) { - int label1; - - if (op == TOK_LAND) { - if (js_parse_expr_binary (s, 8, parse_flags)) return -1; - } else { - if (js_parse_logical_and_or (s, TOK_LAND, parse_flags)) return -1; - } - if (s->token.val == op) { - label1 = new_label (s); - - for (;;) { - if (next_token (s)) return -1; - emit_op (s, OP_dup); - emit_goto (s, op == TOK_LAND ? OP_if_false : OP_if_true, label1); - emit_op (s, OP_drop); - - if (op == TOK_LAND) { - if (js_parse_expr_binary (s, 8, parse_flags)) return -1; - } else { - if (js_parse_logical_and_or (s, TOK_LAND, parse_flags)) return -1; - } - if (s->token.val != op) { - break; - } - } - - emit_label (s, label1); - } - return 0; -} - -static __exception int js_parse_coalesce_expr (JSParseState *s, - int parse_flags) { - return js_parse_logical_and_or (s, TOK_LOR, parse_flags); -} - -/* allowed parse_flags: PF_IN_ACCEPTED */ -static __exception int js_parse_cond_expr (JSParseState *s, int parse_flags) { - int label1, label2; - - if (js_parse_coalesce_expr (s, parse_flags)) return -1; - if (s->token.val == '?') { - if (next_token (s)) return -1; - label1 = emit_goto (s, OP_if_false, -1); - - if (js_parse_assign_expr (s)) return -1; - if (js_parse_expect (s, ':')) return -1; - - label2 = emit_goto (s, OP_goto, -1); - - emit_label (s, label1); - - if (js_parse_assign_expr2 (s, parse_flags & PF_IN_ACCEPTED)) return -1; - - emit_label (s, label2); - } - return 0; -} - -/* allowed parse_flags: PF_IN_ACCEPTED */ -static __exception int js_parse_assign_expr2 (JSParseState *s, - int parse_flags) { - int opcode, op, scope, skip_bits; - JSValue name0 = JS_NULL; - JSValue name; - - if (s->token.val == '(' - && js_parse_skip_parens_token (s, NULL, TRUE) == TOK_ARROW) { - return js_parse_function_decl (s, JS_PARSE_FUNC_ARROW, JS_NULL, s->token.ptr); - } else if (s->token.val == TOK_IDENT && peek_token (s, TRUE) == TOK_ARROW) { - return js_parse_function_decl (s, JS_PARSE_FUNC_ARROW, JS_NULL, s->token.ptr); - } else if ((s->token.val == '{' || s->token.val == '[') - && js_parse_skip_parens_token (s, &skip_bits, FALSE) == '=') { - if (js_parse_destructuring_element (s, 0, 0, FALSE, TRUE, FALSE) < 0) - return -1; - return 0; - } - if (s->token.val == TOK_IDENT) { - /* name0 is used to check for OP_set_name pattern, not duplicated */ - name0 = s->token.u.ident.str; - } - if (js_parse_cond_expr (s, parse_flags)) return -1; - - op = s->token.val; - if (op == '=' || (op >= TOK_MUL_ASSIGN && op <= TOK_POW_ASSIGN)) { - int label; - const uint8_t *op_token_ptr; - op_token_ptr = s->token.ptr; - if (next_token (s)) return -1; - if (get_lvalue (s, &opcode, &scope, &name, &label, NULL, (op != '='), op) - < 0) - return -1; - - if (js_parse_assign_expr2 (s, parse_flags)) { - return -1; - } - - if (op == '=') { - if (opcode == OP_scope_get_var && js_key_equal (name, name0)) { - set_object_name (s, name); - } - } else { - static const uint8_t assign_opcodes[] = { - OP_mul, - OP_div, - OP_mod, - OP_add, - OP_sub, - OP_shl, - OP_sar, - OP_shr, - OP_and, - OP_xor, - OP_or, - OP_pow, - }; - op = assign_opcodes[op - TOK_MUL_ASSIGN]; - emit_source_pos (s, op_token_ptr); - emit_op (s, op); - } - put_lvalue (s, opcode, scope, name, label, PUT_LVALUE_KEEP_TOP, FALSE); - } else if (op >= TOK_LAND_ASSIGN && op <= TOK_LOR_ASSIGN) { - int label, label1, depth_lvalue, label2; - - if (next_token (s)) return -1; - if (get_lvalue (s, &opcode, &scope, &name, &label, &depth_lvalue, TRUE, op) - < 0) - return -1; - - emit_op (s, OP_dup); - label1 - = emit_goto (s, op == TOK_LOR_ASSIGN ? OP_if_true : OP_if_false, -1); - emit_op (s, OP_drop); - - if (js_parse_assign_expr2 (s, parse_flags)) { - return -1; - } - - if (opcode == OP_scope_get_var && js_key_equal (name, name0)) { - set_object_name (s, name); - } - - /* For depth=0 (variable lvalues), dup to keep result on stack after put. - For depth>=1, insert to put value below reference objects. */ - switch (depth_lvalue) { - case 0: - emit_op (s, OP_dup); - break; - case 1: - emit_op (s, OP_insert2); - break; - case 2: - emit_op (s, OP_insert3); - break; - case 3: - emit_op (s, OP_insert4); - break; - default: - abort (); - } - - put_lvalue (s, opcode, scope, name, label, PUT_LVALUE_NOKEEP_DEPTH, FALSE); - label2 = emit_goto (s, OP_goto, -1); - - emit_label (s, label1); - - /* remove the lvalue stack entries (none for depth=0 variables) */ - while (depth_lvalue != 0) { - emit_op (s, OP_nip); - depth_lvalue--; - } - - emit_label (s, label2); - } - return 0; -} - -static __exception int js_parse_assign_expr (JSParseState *s) { - return js_parse_assign_expr2 (s, PF_IN_ACCEPTED); -} - -/* allowed parse_flags: PF_IN_ACCEPTED */ -static __exception int js_parse_expr2 (JSParseState *s, int parse_flags) { - BOOL comma = FALSE; - for (;;) { - if (js_parse_assign_expr2 (s, parse_flags)) return -1; - if (comma) { - /* prevent get_lvalue from using the last expression - as an lvalue. This also prevents the conversion of - of get_var to get_ref for method lookup in function - call inside `with` statement. - */ - s->cur_func->last_opcode_pos = -1; - } - if (s->token.val != ',') break; - comma = TRUE; - if (next_token (s)) return -1; - emit_op (s, OP_drop); - } - return 0; -} - -static __exception int js_parse_expr (JSParseState *s) { - return js_parse_expr2 (s, PF_IN_ACCEPTED); -} - -static void push_break_entry (JSFunctionDef *fd, BlockEnv *be, JSValue label_name, int label_break, int label_cont, int drop_count) { - be->prev = fd->top_break; - fd->top_break = be; - be->label_name = label_name; - be->label_break = label_break; - be->label_cont = label_cont; - be->drop_count = drop_count; - be->label_finally = -1; - be->scope_level = fd->scope_level; - be->is_regular_stmt = FALSE; -} - -static void pop_break_entry (JSFunctionDef *fd) { - BlockEnv *be; - be = fd->top_break; - fd->top_break = be->prev; -} - -static __exception int emit_break (JSParseState *s, JSValue name, int is_cont) { - BlockEnv *top; - int i, scope_level; - - scope_level = s->cur_func->scope_level; - top = s->cur_func->top_break; - while (top != NULL) { - close_scopes (s, scope_level, top->scope_level); - scope_level = top->scope_level; - if (is_cont && top->label_cont != -1 - && (JS_IsNull (name) || js_key_equal (top->label_name, name))) { - /* continue stays inside the same block */ - emit_goto (s, OP_goto, top->label_cont); - return 0; - } - if (!is_cont && top->label_break != -1 - && ((JS_IsNull (name) && !top->is_regular_stmt) - || js_key_equal (top->label_name, name))) { - emit_goto (s, OP_goto, top->label_break); - return 0; - } - for (i = 0; i < top->drop_count; i++) - emit_op (s, OP_drop); - if (top->label_finally != -1) { - /* must push dummy value to keep same stack depth */ - emit_op (s, OP_null); - emit_goto (s, OP_gosub, top->label_finally); - emit_op (s, OP_drop); - } - top = top->prev; - } - if (JS_IsNull (name)) { - if (is_cont) - return js_parse_error (s, "continue must be inside loop"); - else - return js_parse_error (s, "break must be inside loop or switch"); - } else { - return js_parse_error (s, "break/continue label not found"); - } -} - -/* execute the finally blocks before return */ -static void emit_return (JSParseState *s, BOOL hasval) { - BlockEnv *top; - - top = s->cur_func->top_break; - while (top != NULL) { - if (top->label_finally != -1) { - if (!hasval) { - emit_op (s, OP_null); - hasval = TRUE; - } - /* Remove the stack elements up to and including the catch - offset. When 'yield' is used in an expression we have - no easy way to count them, so we use this specific - instruction instead. */ - emit_op (s, OP_nip_catch); - /* execute the "finally" block */ - emit_goto (s, OP_gosub, top->label_finally); - } - top = top->prev; - } - - emit_op (s, hasval ? OP_return : OP_return_undef); -} - -#define DECL_MASK_FUNC (1 << 0) /* allow normal function declaration */ -/* ored with DECL_MASK_FUNC if function declarations are allowed with a label - */ -#define DECL_MASK_FUNC_WITH_LABEL (1 << 1) -#define DECL_MASK_OTHER (1 << 2) /* all other declarations */ -#define DECL_MASK_ALL \ - (DECL_MASK_FUNC | DECL_MASK_FUNC_WITH_LABEL | DECL_MASK_OTHER) - -static __exception int js_parse_statement_or_decl (JSParseState *s, - int decl_mask); - -static __exception int js_parse_statement (JSParseState *s) { - return js_parse_statement_or_decl (s, 0); -} - -static __exception int js_parse_block (JSParseState *s) { - if (js_parse_expect (s, '{')) return -1; - if (s->token.val != '}') { - push_scope (s); - for (;;) { - if (js_parse_statement_or_decl (s, DECL_MASK_ALL)) return -1; - if (s->token.val == '}') break; - } - pop_scope (s); - } - if (next_token (s)) return -1; - return 0; -} - -/* allowed parse_flags: PF_IN_ACCEPTED */ -static __exception int js_parse_var (JSParseState *s, int parse_flags, int tok, BOOL export_flag) { - JSFunctionDef *fd = s->cur_func; - JSValue name = JS_NULL; - - for (;;) { - if (s->token.val == TOK_IDENT) { - if (s->token.u.ident.is_reserved) { - return js_parse_error_reserved_identifier (s); - } - name = s->token.u.ident.str; - if (js_key_equal (name, JS_KEY_let) && tok == TOK_DEF) { - js_parse_error (s, "'let' is not a valid lexical identifier"); - goto var_error; - } - if (next_token (s)) goto var_error; - if (js_define_var (s, name, tok)) goto var_error; - - if (s->token.val == '=') { - if (next_token (s)) goto var_error; - - if (js_parse_assign_expr2 (s, parse_flags)) goto var_error; - set_object_name (s, name); - emit_op (s, (tok == TOK_DEF || tok == TOK_VAR) ? OP_scope_put_var_init : OP_scope_put_var); - emit_key (s, name); - emit_u16 (s, fd->scope_level); - } else { - if (tok == TOK_DEF) { - js_parse_error (s, "missing initializer for const variable"); - goto var_error; - } - if (tok == TOK_VAR) { - /* initialize lexical variable upon entering its scope */ - emit_op (s, OP_null); - emit_op (s, OP_scope_put_var_init); - emit_key (s, name); - emit_u16 (s, fd->scope_level); - } - } - } else { - int skip_bits; - if ((s->token.val == '[' || s->token.val == '{') - && js_parse_skip_parens_token (s, &skip_bits, FALSE) == '=') { - emit_op (s, OP_null); - if (js_parse_destructuring_element (s, tok, 0, TRUE, TRUE, export_flag) - < 0) - return -1; - } else { - return js_parse_error (s, "variable name expected"); - } - } - if (s->token.val != ',') break; - if (next_token (s)) return -1; - } - return 0; - -var_error: - return -1; -} - -/* test if the current token is a label. Use simplistic look-ahead scanner */ -static BOOL is_label (JSParseState *s) { - return (s->token.val == TOK_IDENT && !s->token.u.ident.is_reserved - && peek_token (s, FALSE) == ':'); -} - -/* for-in and for-of loops are not supported */ -static __exception int js_parse_for_in_of (JSParseState *s, int label_name) { - return js_parse_error (s, "'for in' and 'for of' loops are not supported"); -} - -static __exception int js_parse_statement_or_decl (JSParseState *s, - int decl_mask) { - JSValue label_name; - int tok; - - /* specific label handling */ - /* XXX: support multiple labels on loop statements */ - label_name = JS_NULL; - if (is_label (s)) { - BlockEnv *be; - - label_name = s->token.u.ident.str; - - for (be = s->cur_func->top_break; be; be = be->prev) { - if (js_key_equal (be->label_name, label_name)) { - js_parse_error (s, "duplicate label name"); - goto fail; - } - } - - if (next_token (s)) goto fail; - if (js_parse_expect (s, ':')) goto fail; - if (s->token.val != TOK_FOR && s->token.val != TOK_DO - && s->token.val != TOK_WHILE) { - /* labelled regular statement */ - int label_break, mask; - BlockEnv break_entry; - - label_break = new_label (s); - push_break_entry (s->cur_func, &break_entry, label_name, label_break, -1, 0); - break_entry.is_regular_stmt = TRUE; - mask = 0; - if (js_parse_statement_or_decl (s, mask)) goto fail; - emit_label (s, label_break); - pop_break_entry (s->cur_func); - goto done; - } - } - - switch (tok = s->token.val) { - case '{': - if (js_parse_block (s)) goto fail; - break; - case TOK_RETURN: { - const uint8_t *op_token_ptr; - op_token_ptr = s->token.ptr; - if (next_token (s)) goto fail; - if (s->token.val != ';' && s->token.val != '}' && !s->got_lf) { - if (js_parse_expr (s)) goto fail; - emit_source_pos (s, op_token_ptr); - emit_return (s, TRUE); - } else { - emit_source_pos (s, op_token_ptr); - emit_return (s, FALSE); - } - if (js_parse_expect_semi (s)) goto fail; - } break; - case TOK_DISRUPT: { - if (next_token (s)) goto fail; - emit_op (s, OP_null); - emit_op (s, OP_throw); - if (js_parse_expect_semi (s)) goto fail; - } break; - case TOK_DEF: - if (!(decl_mask & DECL_MASK_OTHER)) { - js_parse_error ( - s, "lexical declarations can't appear in single-statement context"); - goto fail; - } - /* fall thru */ - case TOK_VAR: - if (!(decl_mask & DECL_MASK_OTHER)) { - js_parse_error ( - s, "lexical declarations can't appear in single-statement context"); - goto fail; - } - if (next_token (s)) goto fail; - if (js_parse_var (s, TRUE, tok, FALSE)) goto fail; - if (js_parse_expect_semi (s)) goto fail; - break; - case TOK_IF: { - int label1, label2, mask; - if (next_token (s)) goto fail; - /* create a new scope for `let f;if(1) function f(){}` */ - push_scope (s); - if (js_parse_expr_paren (s)) goto fail; - label1 = emit_goto (s, OP_if_false, -1); - mask = 0; - - if (js_parse_statement_or_decl (s, mask)) goto fail; - - if (s->token.val == TOK_ELSE) { - label2 = emit_goto (s, OP_goto, -1); - if (next_token (s)) goto fail; - - emit_label (s, label1); - if (js_parse_statement_or_decl (s, mask)) goto fail; - - label1 = label2; - } - emit_label (s, label1); - pop_scope (s); - } break; - case TOK_WHILE: { - int label_cont, label_break; - BlockEnv break_entry; - - label_cont = new_label (s); - label_break = new_label (s); - - push_break_entry (s->cur_func, &break_entry, label_name, label_break, label_cont, 0); - - if (next_token (s)) goto fail; - - emit_label (s, label_cont); - if (js_parse_expr_paren (s)) goto fail; - emit_goto (s, OP_if_false, label_break); - - if (js_parse_statement (s)) goto fail; - emit_goto (s, OP_goto, label_cont); - - emit_label (s, label_break); - - pop_break_entry (s->cur_func); - } break; - case TOK_DO: { - int label_cont, label_break, label1; - BlockEnv break_entry; - - label_cont = new_label (s); - label_break = new_label (s); - label1 = new_label (s); - - push_break_entry (s->cur_func, &break_entry, label_name, label_break, label_cont, 0); - - if (next_token (s)) goto fail; - - emit_label (s, label1); - - if (js_parse_statement (s)) goto fail; - - emit_label (s, label_cont); - if (js_parse_expect (s, TOK_WHILE)) goto fail; - if (js_parse_expr_paren (s)) goto fail; - /* Insert semicolon if missing */ - if (s->token.val == ';') { - if (next_token (s)) goto fail; - } - emit_goto (s, OP_if_true, label1); - - emit_label (s, label_break); - - pop_break_entry (s->cur_func); - } break; - case TOK_FOR: { - int label_cont, label_break, label_body, label_test; - int pos_cont, pos_body, block_scope_level; - BlockEnv break_entry; - int tok, bits; - - if (next_token (s)) goto fail; - - bits = 0; - if (s->token.val == '(') { js_parse_skip_parens_token (s, &bits, FALSE); } - if (js_parse_expect (s, '(')) goto fail; - - if (!(bits & SKIP_HAS_SEMI)) { - /* parse for/in or for/of */ - if (js_parse_for_in_of (s, label_name)) goto fail; - break; - } - block_scope_level = s->cur_func->scope_level; - - /* create scope for the lexical variables declared in the initial, - test and increment expressions */ - push_scope (s); - /* initial expression */ - tok = s->token.val; - if (tok != ';') { - if (tok == TOK_VAR || tok == TOK_DEF) { - if (next_token (s)) goto fail; - if (js_parse_var (s, FALSE, tok, FALSE)) goto fail; - } else { - if (js_parse_expr2 (s, FALSE)) goto fail; - emit_op (s, OP_drop); - } - - /* close the closures before the first iteration */ - close_scopes (s, s->cur_func->scope_level, block_scope_level); - } - if (js_parse_expect (s, ';')) goto fail; - - label_test = new_label (s); - label_cont = new_label (s); - label_body = new_label (s); - label_break = new_label (s); - - push_break_entry (s->cur_func, &break_entry, label_name, label_break, label_cont, 0); - - /* test expression */ - if (s->token.val == ';') { - /* no test expression */ - label_test = label_body; - } else { - emit_label (s, label_test); - if (js_parse_expr (s)) goto fail; - emit_goto (s, OP_if_false, label_break); - } - if (js_parse_expect (s, ';')) goto fail; - - if (s->token.val == ')') { - /* no end expression */ - break_entry.label_cont = label_cont = label_test; - pos_cont = 0; /* avoid warning */ - } else { - /* skip the end expression */ - emit_goto (s, OP_goto, label_body); - - pos_cont = s->cur_func->byte_code.size; - emit_label (s, label_cont); - if (js_parse_expr (s)) goto fail; - emit_op (s, OP_drop); - if (label_test != label_body) emit_goto (s, OP_goto, label_test); - } - if (js_parse_expect (s, ')')) goto fail; - - pos_body = s->cur_func->byte_code.size; - emit_label (s, label_body); - if (js_parse_statement (s)) goto fail; - - /* close the closures before the next iteration */ - /* XXX: check continue case */ - close_scopes (s, s->cur_func->scope_level, block_scope_level); - - if (OPTIMIZE && label_test != label_body && label_cont != label_test) { - /* move the increment code here */ - DynBuf *bc = &s->cur_func->byte_code; - int chunk_size = pos_body - pos_cont; - int offset = bc->size - pos_cont; - int i; - dbuf_realloc (bc, bc->size + chunk_size); - dbuf_put (bc, bc->buf + pos_cont, chunk_size); - memset (bc->buf + pos_cont, OP_nop, chunk_size); - /* increment part ends with a goto */ - s->cur_func->last_opcode_pos = bc->size - 5; - /* relocate labels */ - for (i = label_cont; i < s->cur_func->label_count; i++) { - LabelSlot *ls = &s->cur_func->label_slots[i]; - if (ls->pos >= pos_cont && ls->pos < pos_body) ls->pos += offset; - } - } else { - emit_goto (s, OP_goto, label_cont); - } - - emit_label (s, label_break); - - pop_break_entry (s->cur_func); - pop_scope (s); - } break; - case TOK_BREAK: - case TOK_CONTINUE: { - int is_cont = s->token.val - TOK_BREAK; - JSValue label; - - if (next_token (s)) goto fail; - if (!s->got_lf && s->token.val == TOK_IDENT - && !s->token.u.ident.is_reserved) - label = s->token.u.ident.str; - else - label = JS_NULL; - if (emit_break (s, label, is_cont)) goto fail; - if (!JS_IsNull (label)) { - if (next_token (s)) goto fail; - } - if (js_parse_expect_semi (s)) goto fail; - } break; - case ';': - /* empty statement */ - if (next_token (s)) goto fail; - break; - case TOK_FUNCTION: - /* ES6 Annex B.3.2 and B.3.3 semantics */ - if (!(decl_mask & DECL_MASK_FUNC)) goto func_decl_error; - if (!(decl_mask & DECL_MASK_OTHER) && peek_token (s, FALSE) == '*') - goto func_decl_error; - goto parse_func_var; - case TOK_IDENT: - if (s->token.u.ident.is_reserved) { - js_parse_error_reserved_identifier (s); - goto fail; - } - /* let keyword is no longer supported */ - if (token_is_pseudo_keyword (s, JS_KEY_async) - && peek_token (s, TRUE) == TOK_FUNCTION) { - if (!(decl_mask & DECL_MASK_OTHER)) { - func_decl_error: - js_parse_error ( - s, "function declarations can't appear in single-statement context"); - goto fail; - } - parse_func_var: - if (js_parse_function_decl (s, JS_PARSE_FUNC_VAR, JS_NULL, s->token.ptr)) - goto fail; - break; - } - goto hasexpr; - - case TOK_DEBUGGER: - /* currently no debugger, so just skip the keyword */ - if (next_token (s)) goto fail; - if (js_parse_expect_semi (s)) goto fail; - break; - - case TOK_ENUM: - case TOK_EXPORT: - js_unsupported_keyword (s, s->token.u.ident.str); - goto fail; - - default: - hasexpr: - emit_source_pos (s, s->token.ptr); - if (js_parse_expr (s)) goto fail; - emit_op (s, OP_drop); /* drop the result */ - if (js_parse_expect_semi (s)) goto fail; - break; - } -done: - return 0; -fail: - return -1; -} - -static int add_closure_var (JSContext *ctx, JSFunctionDef *s, BOOL is_local, BOOL is_arg, int var_idx, JSValue var_name, BOOL is_const, BOOL is_lexical, JSVarKindEnum var_kind); - -static __exception int js_parse_source_element (JSParseState *s) { - if (s->token.val == TOK_FUNCTION - || (token_is_pseudo_keyword (s, JS_KEY_async) - && peek_token (s, TRUE) == TOK_FUNCTION)) { - if (js_parse_function_decl (s, JS_PARSE_FUNC_STATEMENT, JS_NULL, s->token.ptr)) - return -1; - } else { - if (js_parse_statement_or_decl (s, DECL_MASK_ALL)) return -1; - } - return 0; -} - -static JSFunctionDef * -js_new_function_def (JSContext *ctx, JSFunctionDef *parent, BOOL is_func_expr, const char *filename, const uint8_t *source_ptr, GetLineColCache *get_line_col_cache) { - JSFunctionDef *fd; - - fd = pjs_mallocz (sizeof (*fd)); - if (!fd) return NULL; - - fd->ctx = ctx; - init_list_head (&fd->child_list); - - /* insert in parent list */ - fd->parent = parent; - fd->parent_cpool_idx = -1; - if (parent) { - list_add_tail (&fd->link, &parent->child_list); - fd->js_mode = parent->js_mode; - fd->parent_scope_level = parent->scope_level; - } - fd->strip_debug = ((ctx->rt->strip_flags & JS_STRIP_DEBUG) != 0); - fd->strip_source - = ((ctx->rt->strip_flags & (JS_STRIP_DEBUG | JS_STRIP_SOURCE)) != 0); - - fd->is_func_expr = is_func_expr; - js_dbuf_init (ctx, &fd->byte_code); - fd->last_opcode_pos = -1; - fd->func_name = JS_NULL; - fd->var_object_idx = -1; - fd->arg_var_object_idx = -1; - fd->func_var_idx = -1; - fd->this_var_idx = -1; - fd->this_active_func_var_idx = -1; - - /* XXX: should distinguish arg, var and var object and body scopes */ - fd->scopes = fd->def_scope_array; - fd->scope_size = countof (fd->def_scope_array); - fd->scope_count = 1; - fd->scopes[0].first = -1; - fd->scopes[0].parent = -1; - fd->scope_level = 0; /* 0: var/arg scope */ - fd->scope_first = -1; - fd->body_scope = -1; - - fd->filename = js_key_new (ctx, filename); - fd->source_pos = source_ptr - get_line_col_cache->buf_start; - fd->get_line_col_cache = get_line_col_cache; - - js_dbuf_init (ctx, &fd->pc2line); - // fd->pc2line_last_line_num = line_num; - // fd->pc2line_last_pc = 0; - fd->last_opcode_source_ptr = source_ptr; - return fd; -} - -static void free_bytecode_atoms (JSRuntime *rt, const uint8_t *bc_buf, int bc_len, BOOL use_short_opcodes) { - int pos, len, op; - const JSOpCode *oi; - - pos = 0; - while (pos < bc_len) { - op = bc_buf[pos]; - if (use_short_opcodes) - oi = &short_opcode_info (op); - else - oi = &opcode_info[op]; - - len = oi->size; - switch (oi->fmt) { - case OP_FMT_key: - case OP_FMT_key_u8: - case OP_FMT_key_u16: - case OP_FMT_key_label_u16: - /* Key operand is now a cpool index; cpool values freed separately */ - break; - default: - break; - } - pos += len; - } -} - -static void js_free_function_def (JSContext *ctx, JSFunctionDef *fd) { - int i; - struct list_head *el, *el1; - - /* free the child functions */ - list_for_each_safe (el, el1, &fd->child_list) { - JSFunctionDef *fd1; - fd1 = list_entry (el, JSFunctionDef, link); - js_free_function_def (ctx, fd1); - } - - free_bytecode_atoms (ctx->rt, fd->byte_code.buf, fd->byte_code.size, fd->use_short_opcodes); - dbuf_free (&fd->byte_code); - pjs_free (fd->jump_slots); - pjs_free (fd->label_slots); - pjs_free (fd->line_number_slots); - - for (i = 0; i < fd->cpool_count; i++) { - } - pjs_free (fd->cpool); - - - for (i = 0; i < fd->var_count; i++) { - } - pjs_free (fd->vars); - for (i = 0; i < fd->arg_count; i++) { - } - pjs_free (fd->args); - - for (i = 0; i < fd->global_var_count; i++) { - } - pjs_free (fd->global_vars); - - for (i = 0; i < fd->closure_var_count; i++) { - JSClosureVar *cv = &fd->closure_var[i]; - (void)cv; - } - pjs_free (fd->closure_var); - - if (fd->scopes != fd->def_scope_array) pjs_free (fd->scopes); - - dbuf_free (&fd->pc2line); - - pjs_free (fd->source); - - if (fd->parent) { - /* remove in parent list */ - list_del (&fd->link); - } - pjs_free (fd); -} - -#ifdef DUMP_BYTECODE -static const char *skip_lines (const char *p, int n) { - while (n-- > 0 && *p) { - while (*p && *p++ != '\n') - continue; - } - return p; -} - -static void print_lines (const char *source, int line, int line1) { - const char *s = source; - const char *p = skip_lines (s, line); - if (*p) { - while (line++ < line1) { - p = skip_lines (s = p, 1); - printf (";; %.*s", (int)(p - s), s); - if (!*p) { - if (p[-1] != '\n') printf ("\n"); - break; - } - } - } -} - -static void dump_byte_code (JSContext *ctx, int pass, const uint8_t *tab, int len, const JSVarDef *args, int arg_count, const JSVarDef *vars, int var_count, const JSClosureVar *closure_var, int closure_var_count, const JSValue *cpool, uint32_t cpool_count, const char *source, const LabelSlot *label_slots, JSFunctionBytecode *b) { - const JSOpCode *oi; - int pos, pos_next, op, size, idx, addr, line, line1, in_source, line_num; - uint8_t *bits = js_mallocz (ctx, len * sizeof (*bits)); - BOOL use_short_opcodes = (b != NULL); - - if (b) { - int col_num; - line_num = find_line_num (ctx, b, -1, &col_num); - } - - /* scan for jump targets */ - for (pos = 0; pos < len; pos = pos_next) { - op = tab[pos]; - if (use_short_opcodes) - oi = &short_opcode_info (op); - else - oi = &opcode_info[op]; - pos_next = pos + oi->size; - if (op < OP_COUNT) { - switch (oi->fmt) { -#if SHORT_OPCODES - case OP_FMT_label8: - pos++; - addr = (int8_t)tab[pos]; - goto has_addr; - case OP_FMT_label16: - pos++; - addr = (int16_t)get_u16 (tab + pos); - goto has_addr; -#endif - case OP_FMT_key_label_u16: - pos += 4; - /* fall thru */ - case OP_FMT_label: - case OP_FMT_label_u16: - pos++; - addr = get_u32 (tab + pos); - goto has_addr; - has_addr: - if (pass == 1) addr = label_slots[addr].pos; - if (pass == 2) addr = label_slots[addr].pos2; - if (pass == 3) addr += pos; - if (addr >= 0 && addr < len) bits[addr] |= 1; - break; - } - } - } - in_source = 0; - if (source) { - /* Always print first line: needed if single line */ - print_lines (source, 0, 1); - in_source = 1; - } - line1 = line = 1; - pos = 0; - while (pos < len) { - op = tab[pos]; - if (source && b) { - int col_num; - if (b) { - line1 = find_line_num (ctx, b, pos, &col_num) - line_num + 1; - } else if (op == OP_line_num) { - /* XXX: no longer works */ - line1 = get_u32 (tab + pos + 1) - line_num + 1; - } - if (line1 > line) { - if (!in_source) printf ("\n"); - in_source = 1; - print_lines (source, line, line1); - line = line1; - // bits[pos] |= 2; - } - } - if (in_source) printf ("\n"); - in_source = 0; - if (op >= OP_COUNT) { - printf ("invalid opcode (0x%02x)\n", op); - pos++; - continue; - } - if (use_short_opcodes) - oi = &short_opcode_info (op); - else - oi = &opcode_info[op]; - size = oi->size; - if (pos + size > len) { - printf ("truncated opcode (0x%02x)\n", op); - break; - } -#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 16) - { - int i, x, x0; - x = x0 = printf ("%5d ", pos); - for (i = 0; i < size; i++) { - if (i == 6) { printf ("\n%*s", x = x0, ""); } - x += printf (" %02X", tab[pos + i]); - } - printf ("%*s", x0 + 20 - x, ""); - } -#endif - if (bits[pos]) { - printf ("%5d: ", pos); - } else { - printf (" "); - } - printf ("%s", oi->name); - pos++; - switch (oi->fmt) { - case OP_FMT_none_int: - printf (" %d", op - OP_push_0); - break; - case OP_FMT_npopx: - printf (" %d", op - OP_call0); - break; - case OP_FMT_u8: - printf (" %u", get_u8 (tab + pos)); - break; - case OP_FMT_i8: - printf (" %d", get_i8 (tab + pos)); - break; - case OP_FMT_u16: - case OP_FMT_npop: - printf (" %u", get_u16 (tab + pos)); - break; - case OP_FMT_npop_u16: - printf (" %u,%u", get_u16 (tab + pos), get_u16 (tab + pos + 2)); - break; - case OP_FMT_i16: - printf (" %d", get_i16 (tab + pos)); - break; - case OP_FMT_i32: - printf (" %d", get_i32 (tab + pos)); - break; - case OP_FMT_u32: - printf (" %u", get_u32 (tab + pos)); - break; -#if SHORT_OPCODES - case OP_FMT_label8: - addr = get_i8 (tab + pos); - goto has_addr1; - case OP_FMT_label16: - addr = get_i16 (tab + pos); - goto has_addr1; -#endif - case OP_FMT_label: - addr = get_u32 (tab + pos); - goto has_addr1; - has_addr1: - if (pass == 1) printf (" %u:%u", addr, label_slots[addr].pos); - if (pass == 2) printf (" %u:%u", addr, label_slots[addr].pos2); - if (pass == 3) printf (" %u", addr + pos); - break; - case OP_FMT_label_u16: - addr = get_u32 (tab + pos); - if (pass == 1) printf (" %u:%u", addr, label_slots[addr].pos); - if (pass == 2) printf (" %u:%u", addr, label_slots[addr].pos2); - if (pass == 3) printf (" %u", addr + pos); - printf (",%u", get_u16 (tab + pos + 4)); - break; -#if SHORT_OPCODES - case OP_FMT_const8: - idx = get_u8 (tab + pos); - goto has_pool_idx; -#endif - case OP_FMT_const: - idx = get_u32 (tab + pos); - goto has_pool_idx; - has_pool_idx: - printf (" %u: ", idx); - if (idx < cpool_count) { - JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[idx], NULL); - } - break; - case OP_FMT_key: - printf (" "); - print_atom (ctx, get_u32 (tab + pos)); - break; - case OP_FMT_key: { - /* Key operand is a cpool index; print the cpool value */ - uint32_t key_idx = get_u32 (tab + pos); - printf (" %u: ", key_idx); - if (key_idx < cpool_count) { - JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[key_idx], NULL); - } - } break; - case OP_FMT_key_u8: - printf (" "); - print_atom (ctx, get_u32 (tab + pos)); - printf (",%d", get_u8 (tab + pos + 4)); - break; - case OP_FMT_key_u16: - printf (" "); - print_atom (ctx, get_u32 (tab + pos)); - printf (",%d", get_u16 (tab + pos + 4)); - break; - case OP_FMT_key_label_u16: - printf (" "); - print_atom (ctx, get_u32 (tab + pos)); - addr = get_u32 (tab + pos + 4); - if (pass == 1) printf (",%u:%u", addr, label_slots[addr].pos); - if (pass == 2) printf (",%u:%u", addr, label_slots[addr].pos2); - if (pass == 3) printf (",%u", addr + pos + 4); - printf (",%u", get_u16 (tab + pos + 8)); - break; - case OP_FMT_none_loc: - idx = (op - OP_get_loc0) % 4; - goto has_loc; - case OP_FMT_loc8: - idx = get_u8 (tab + pos); - goto has_loc; - case OP_FMT_loc: - idx = get_u16 (tab + pos); - has_loc: - printf (" %d: ", idx); - if (idx < var_count) { print_atom (ctx, vars[idx].var_name); } - break; - case OP_FMT_none_arg: - idx = (op - OP_get_arg0) % 4; - goto has_arg; - case OP_FMT_arg: - idx = get_u16 (tab + pos); - has_arg: - printf (" %d: ", idx); - if (idx < arg_count) { print_atom (ctx, args[idx].var_name); } - break; - default: - break; - } - printf ("\n"); - pos += oi->size - 1; - } - if (source) { - if (!in_source) printf ("\n"); - print_lines (source, line, INT32_MAX); - } - js_free (ctx, bits); -} - -static __maybe_unused void dump_pc2line (JSContext *ctx, const uint8_t *buf, int len) { - const uint8_t *p_end, *p; - int pc, v, line_num, col_num, ret; - unsigned int op; - uint32_t val; - - if (len <= 0) return; - - printf ("%5s %5s %5s\n", "PC", "LINE", "COL"); - - p = buf; - p_end = buf + len; - - /* get the function line and column numbers */ - ret = get_leb128 (&val, p, p_end); - if (ret < 0) goto fail; - p += ret; - line_num = val + 1; - - ret = get_leb128 (&val, p, p_end); - if (ret < 0) goto fail; - p += ret; - col_num = val + 1; - - printf ("%5s %5d %5d\n", "-", line_num, col_num); - - pc = 0; - while (p < p_end) { - op = *p++; - if (op == 0) { - ret = get_leb128 (&val, p, p_end); - if (ret < 0) goto fail; - pc += val; - p += ret; - ret = get_sleb128 (&v, p, p_end); - if (ret < 0) goto fail; - p += ret; - line_num += v; - } else { - op -= PC2LINE_OP_FIRST; - pc += (op / PC2LINE_RANGE); - line_num += (op % PC2LINE_RANGE) + PC2LINE_BASE; - } - ret = get_sleb128 (&v, p, p_end); - if (ret < 0) goto fail; - p += ret; - col_num += v; - - printf ("%5d %5d %5d\n", pc, line_num, col_num); - } -fail:; -} - -static __maybe_unused void js_dump_function_bytecode (JSContext *ctx, - JSFunctionBytecode *b) { - int i; - char atom_buf[KEY_GET_STR_BUF_SIZE]; - const char *str; - - if (b->has_debug && !JS_IsNull (b->debug.filename)) { - int line_num, col_num; - str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), b->debug.filename); - line_num = find_line_num (ctx, b, -1, &col_num); - printf ("%s:%d:%d: ", str, line_num, col_num); - } - - str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), b->func_name); - printf ("function: %s%s\n", "", str); - if (b->js_mode) { - printf (" mode:"); - printf (" strict"); - printf ("\n"); - } - if (b->arg_count && b->vardefs) { - printf (" args:"); - for (i = 0; i < b->arg_count; i++) { - printf (" %s", JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), b->vardefs[i].var_name)); - } - printf ("\n"); - } - if (b->var_count && b->vardefs) { - printf (" locals:\n"); - for (i = 0; i < b->var_count; i++) { - JSVarDef *vd = &b->vardefs[b->arg_count + i]; - printf ("%5d: %s %s", i, vd->var_kind == JS_VAR_CATCH ? "catch" : (vd->var_kind == JS_VAR_FUNCTION_DECL || vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) ? "function" - : vd->is_const ? "const" - : vd->is_lexical ? "let" - : "var", - JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), vd->var_name)); - if (vd->scope_level) - printf (" [level:%d next:%d]", vd->scope_level, vd->scope_next); - printf ("\n"); - } - } - if (b->closure_var_count) { - printf (" closure vars:\n"); - for (i = 0; i < b->closure_var_count; i++) { - JSClosureVar *cv = &b->closure_var[i]; - printf ("%5d: %s %s:%s%d %s\n", i, JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), cv->var_name), cv->is_local ? "local" : "parent", cv->is_arg ? "arg" : "loc", cv->var_idx, cv->is_const ? "const" : cv->is_lexical ? "let" - : "var"); - } - } - printf (" stack_size: %d\n", b->stack_size); - printf (" opcodes:\n"); - dump_byte_code (ctx, 3, b->byte_code_buf, b->byte_code_len, b->vardefs, b->arg_count, b->vardefs ? b->vardefs + b->arg_count : NULL, b->var_count, b->closure_var, b->closure_var_count, b->cpool, b->cpool_count, b->has_debug ? b->debug.source : NULL, NULL, b); -#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 32) - if (b->has_debug) - dump_pc2line (ctx, b->debug.pc2line_buf, b->debug.pc2line_len); -#endif - printf ("\n"); -} -#endif - -static int add_closure_var (JSContext *ctx, JSFunctionDef *s, BOOL is_local, BOOL is_arg, int var_idx, JSValue var_name, BOOL is_const, BOOL is_lexical, JSVarKindEnum var_kind) { - JSClosureVar *cv; - - /* the closure variable indexes are currently stored on 16 bits */ - if (s->closure_var_count >= JS_MAX_LOCAL_VARS) { - JS_ThrowInternalError (ctx, "too many closure variables"); - return -1; - } - - if (pjs_resize_array ((void **)&s->closure_var, sizeof (s->closure_var[0]), &s->closure_var_size, s->closure_var_count + 1)) - return -1; - cv = &s->closure_var[s->closure_var_count++]; - cv->is_local = is_local; - cv->is_arg = is_arg; - cv->is_const = is_const; - cv->is_lexical = is_lexical; - cv->var_kind = var_kind; - cv->var_idx = var_idx; - cv->var_name = var_name; - return s->closure_var_count - 1; -} - -/* 'fd' must be a parent of 's'. Create in 's' a closure referencing a - local variable (is_local = TRUE) or a closure (is_local = FALSE) in - 'fd' */ -static int get_closure_var2 (JSContext *ctx, JSFunctionDef *s, JSFunctionDef *fd, BOOL is_local, BOOL is_arg, int var_idx, JSValue var_name, BOOL is_const, BOOL is_lexical, JSVarKindEnum var_kind) { - int i; - - if (fd != s->parent) { - var_idx = get_closure_var2 (ctx, s->parent, fd, is_local, is_arg, var_idx, var_name, is_const, is_lexical, var_kind); - if (var_idx < 0) return -1; - is_local = FALSE; - } - for (i = 0; i < s->closure_var_count; i++) { - JSClosureVar *cv = &s->closure_var[i]; - if (cv->var_idx == var_idx && cv->is_arg == is_arg - && cv->is_local == is_local) - return i; - } - return add_closure_var (ctx, s, is_local, is_arg, var_idx, var_name, is_const, is_lexical, var_kind); -} - -static int get_closure_var (JSContext *ctx, JSFunctionDef *s, JSFunctionDef *fd, BOOL is_arg, int var_idx, JSValue var_name, BOOL is_const, BOOL is_lexical, JSVarKindEnum var_kind) { - return get_closure_var2 (ctx, s, fd, TRUE, is_arg, var_idx, var_name, is_const, is_lexical, var_kind); -} - -/* Compute closure depth and slot for OP_get_up/OP_set_up opcodes. - Follows the closure_var chain to find the actual variable location. - Returns depth (1 = immediate parent) and slot (index in JSFrame.slots). - Returns -1 on error. */ -static int compute_closure_depth_slot(JSFunctionDef *s, int closure_idx, int *out_slot) { - int depth = 1; - JSClosureVar *cv = &s->closure_var[closure_idx]; - JSFunctionDef *fd = s->parent; - - /* Follow chain until we find the actual local variable */ - while (!cv->is_local) { - if (!fd || !fd->parent) return -1; /* Invalid chain */ - cv = &fd->closure_var[cv->var_idx]; - fd = fd->parent; - depth++; - } - - /* Now cv->var_idx is the index in fd's variables/arguments. - Compute slot in JSFrame.slots layout: [args..., vars...] */ - if (cv->is_arg) { - *out_slot = cv->var_idx; - } else { - *out_slot = fd->arg_count + cv->var_idx; - } - return depth; -} - -static int add_var_this (JSContext *ctx, JSFunctionDef *fd) { - return add_var (ctx, fd, JS_KEY_this); -} - -static int resolve_pseudo_var (JSContext *ctx, JSFunctionDef *s, JSValue var_name) { - int var_idx; - - if (!s->has_this_binding) return -1; - - if (js_key_equal (var_name, JS_KEY_this)) { - /* 'this' pseudo variable */ - if (s->this_var_idx < 0) s->this_var_idx = add_var_this (ctx, s); - var_idx = s->this_var_idx; - } else if (js_key_equal (var_name, js_key_new (ctx, "this_active_func"))) { - /* 'this.active_func' pseudo variable */ - if (s->this_active_func_var_idx < 0) - s->this_active_func_var_idx = add_var (ctx, s, var_name); - var_idx = s->this_active_func_var_idx; - } else if (js_key_equal (var_name, js_key_new (ctx, "new_target"))) { - /* 'new.target' not supported - constructors removed */ - var_idx = -1; - } else { - var_idx = -1; - } - return var_idx; -} - -/* test if 'var_name' is in the variable object on the stack. If is it - the case, handle it and jump to 'label_done' */ -static void var_object_test (JSContext *ctx, JSFunctionDef *s, JSValue var_name, int op, DynBuf *bc, int *plabel_done, BOOL is_with) { - int cpool_idx = fd_cpool_add (ctx, s, var_name); - dbuf_putc (bc, op); - dbuf_put_u32 (bc, cpool_idx); -} - -/* return the position of the next opcode */ -static int resolve_scope_var (JSContext *ctx, JSFunctionDef *s, JSValue var_name, int scope_level, int op, DynBuf *bc, uint8_t *bc_buf, LabelSlot *ls, int pos_next) { - int idx, var_idx, is_put; - int label_done; - JSFunctionDef *fd; - JSVarDef *vd; - BOOL is_pseudo_var, is_arg_scope; - - label_done = -1; - - /* XXX: could be simpler to use a specific function to - resolve the pseudo variables */ - is_pseudo_var = (js_key_equal_str (var_name, "home_object") - || js_key_equal_str (var_name, "this_active_func") - || js_key_equal_str (var_name, "new_target") - || js_key_equal (var_name, JS_KEY_this)); - - /* resolve local scoped variables */ - var_idx = -1; - for (idx = s->scopes[scope_level].first; idx >= 0;) { - vd = &s->vars[idx]; - if (js_key_equal (vd->var_name, var_name)) { - if (op == OP_scope_put_var) { - if (vd->is_const) { - int cpool_idx = fd_cpool_add (ctx, s, var_name); - dbuf_putc (bc, OP_throw_error); - dbuf_put_u32 (bc, cpool_idx); - dbuf_putc (bc, JS_THROW_VAR_RO); - goto done; - } - } - var_idx = idx; - break; - } - idx = vd->scope_next; - } - is_arg_scope = (idx == ARG_SCOPE_END); - if (var_idx < 0) { - /* argument scope: variables are not visible but pseudo - variables are visible */ - if (!is_arg_scope) { var_idx = find_var (ctx, s, var_name); } - - if (var_idx < 0 && is_pseudo_var) - var_idx = resolve_pseudo_var (ctx, s, var_name); - - if (var_idx < 0 && s->is_func_expr - && js_key_equal (var_name, s->func_name)) { - /* add a new variable with the function name */ - var_idx = add_func_var (ctx, s, var_name); - } - } - if (var_idx >= 0) { - if (op == OP_scope_put_var - && !(var_idx & ARGUMENT_VAR_OFFSET) && s->vars[var_idx].is_const) { - /* only happens when assigning a function expression name - in strict mode */ - int cpool_idx = fd_cpool_add (ctx, s, var_name); - dbuf_putc (bc, OP_throw_error); - dbuf_put_u32 (bc, cpool_idx); - dbuf_putc (bc, JS_THROW_VAR_RO); - goto done; - } - /* OP_scope_put_var_init is only used to initialize a - lexical variable, so it is never used in a with or var object. It - can be used with a closure (module global variable case). */ - switch (op) { - case OP_scope_get_var_checkthis: - case OP_scope_get_var_undef: - case OP_scope_get_var: - case OP_scope_put_var: - case OP_scope_put_var_init: - is_put = (op == OP_scope_put_var || op == OP_scope_put_var_init); - if (var_idx & ARGUMENT_VAR_OFFSET) { - dbuf_putc (bc, OP_get_arg + is_put); - dbuf_put_u16 (bc, var_idx - ARGUMENT_VAR_OFFSET); - } else { - if (is_put) { - if (s->vars[var_idx].is_lexical) { - if (op == OP_scope_put_var_init) { - /* 'this' can only be initialized once */ - if (js_key_equal (var_name, JS_KEY_this)) - dbuf_putc (bc, OP_put_loc_check_init); - else - dbuf_putc (bc, OP_put_loc); - } else { - dbuf_putc (bc, OP_put_loc_check); - } - } else { - dbuf_putc (bc, OP_put_loc); - } - } else { - if (s->vars[var_idx].is_lexical) { - if (op == OP_scope_get_var_checkthis) { - /* only used for 'this' return in derived class constructors */ - dbuf_putc (bc, OP_get_loc_checkthis); - } else { - dbuf_putc (bc, OP_get_loc_check); - } - } else { - dbuf_putc (bc, OP_get_loc); - } - } - dbuf_put_u16 (bc, var_idx); - } - break; - case OP_scope_delete_var: - dbuf_putc (bc, OP_push_false); - break; - } - goto done; - } - /* check eval object */ - if (!is_arg_scope && s->var_object_idx >= 0 && !is_pseudo_var) { - dbuf_putc (bc, OP_get_loc); - dbuf_put_u16 (bc, s->var_object_idx); - var_object_test (ctx, s, var_name, op, bc, &label_done, 0); - } - /* check eval object in argument scope */ - if (s->arg_var_object_idx >= 0 && !is_pseudo_var) { - dbuf_putc (bc, OP_get_loc); - dbuf_put_u16 (bc, s->arg_var_object_idx); - var_object_test (ctx, s, var_name, op, bc, &label_done, 0); - } - - /* check parent scopes */ - for (fd = s; fd->parent;) { - scope_level = fd->parent_scope_level; - fd = fd->parent; - for (idx = fd->scopes[scope_level].first; idx >= 0;) { - vd = &fd->vars[idx]; - if (js_key_equal (vd->var_name, var_name)) { - if (op == OP_scope_put_var) { - if (vd->is_const) { - int cpool_idx = fd_cpool_add (ctx, s, var_name); - dbuf_putc (bc, OP_throw_error); - dbuf_put_u32 (bc, cpool_idx); - dbuf_putc (bc, JS_THROW_VAR_RO); - goto done; - } - } - var_idx = idx; - break; - } - idx = vd->scope_next; - } - is_arg_scope = (idx == ARG_SCOPE_END); - if (var_idx >= 0) break; - - if (!is_arg_scope) { - var_idx = find_var (ctx, fd, var_name); - if (var_idx >= 0) break; - } - if (is_pseudo_var) { - var_idx = resolve_pseudo_var (ctx, fd, var_name); - if (var_idx >= 0) break; - } - if (fd->is_func_expr && js_key_equal (fd->func_name, var_name)) { - /* add a new variable with the function name */ - var_idx = add_func_var (ctx, fd, var_name); - break; - } - - /* check eval object */ - if (!is_arg_scope && fd->var_object_idx >= 0 && !is_pseudo_var) { - int slot, depth; - vd = &fd->vars[fd->var_object_idx]; - vd->is_captured = 1; - idx = get_closure_var (ctx, s, fd, FALSE, fd->var_object_idx, vd->var_name, FALSE, FALSE, JS_VAR_NORMAL); - depth = compute_closure_depth_slot(s, idx, &slot); - dbuf_putc (bc, OP_get_up); - dbuf_putc (bc, (uint8_t)depth); - dbuf_put_u16 (bc, (uint16_t)slot); - var_object_test (ctx, s, var_name, op, bc, &label_done, 0); - } - - /* check eval object in argument scope */ - if (fd->arg_var_object_idx >= 0 && !is_pseudo_var) { - int slot, depth; - vd = &fd->vars[fd->arg_var_object_idx]; - vd->is_captured = 1; - idx = get_closure_var (ctx, s, fd, FALSE, fd->arg_var_object_idx, vd->var_name, FALSE, FALSE, JS_VAR_NORMAL); - depth = compute_closure_depth_slot(s, idx, &slot); - dbuf_putc (bc, OP_get_up); - dbuf_putc (bc, (uint8_t)depth); - dbuf_put_u16 (bc, (uint16_t)slot); - var_object_test (ctx, s, var_name, op, bc, &label_done, 0); - } - } - - if (var_idx >= 0) { - /* find the corresponding closure variable */ - if (var_idx & ARGUMENT_VAR_OFFSET) { - fd->args[var_idx - ARGUMENT_VAR_OFFSET].is_captured = 1; - idx = get_closure_var (ctx, s, fd, TRUE, var_idx - ARGUMENT_VAR_OFFSET, var_name, FALSE, FALSE, JS_VAR_NORMAL); - } else { - fd->vars[var_idx].is_captured = 1; - idx = get_closure_var ( - ctx, s, fd, FALSE, var_idx, var_name, fd->vars[var_idx].is_const, fd->vars[var_idx].is_lexical, fd->vars[var_idx].var_kind); - } - if (idx >= 0) { - has_idx: - if (op == OP_scope_put_var && s->closure_var[idx].is_const) { - int cpool_idx = fd_cpool_add (ctx, s, var_name); - dbuf_putc (bc, OP_throw_error); - dbuf_put_u32 (bc, cpool_idx); - dbuf_putc (bc, JS_THROW_VAR_RO); - goto done; - } - switch (op) { - case OP_scope_get_var_undef: - case OP_scope_get_var: - case OP_scope_put_var: - case OP_scope_put_var_init: - is_put = (op == OP_scope_put_var || op == OP_scope_put_var_init); - { - /* Use OP_get_up/OP_set_up with (depth, slot) encoding */ - int slot; - int depth = compute_closure_depth_slot(s, idx, &slot); - assert(depth > 0 && depth <= 255 && slot <= 65535); - if (is_put) { - dbuf_putc(bc, OP_set_up); - } else { - dbuf_putc(bc, OP_get_up); - } - dbuf_putc(bc, (uint8_t)depth); - dbuf_put_u16(bc, (uint16_t)slot); - } - break; - case OP_scope_delete_var: - dbuf_putc (bc, OP_push_false); - break; - } - goto done; - } - } - - /* global variable access */ - { - int cpool_idx = fd_cpool_add (ctx, s, var_name); - - switch (op) { - case OP_scope_get_var_undef: - case OP_scope_get_var: - case OP_scope_put_var: - dbuf_putc (bc, OP_get_var_undef + (op - OP_scope_get_var_undef)); - dbuf_put_u32 (bc, cpool_idx); - break; - case OP_scope_put_var_init: - dbuf_putc (bc, OP_put_var_init); - dbuf_put_u32 (bc, cpool_idx); - break; - case OP_scope_delete_var: - dbuf_putc (bc, OP_delete_var); - dbuf_put_u32 (bc, cpool_idx); - break; - } - } -done: - if (label_done >= 0) { - dbuf_putc (bc, OP_label); - dbuf_put_u32 (bc, label_done); - s->label_slots[label_done].pos2 = bc->size; - } - return pos_next; -} - -typedef struct CodeContext { - const uint8_t *bc_buf; /* code buffer */ - int bc_len; /* length of the code buffer */ - int pos; /* position past the matched code pattern */ - int line_num; /* last visited OP_line_num parameter or -1 */ - int op; - int idx; - int label; - int val; -} CodeContext; - -#define M2(op1, op2) ((op1) | ((op2) << 8)) -#define M3(op1, op2, op3) ((op1) | ((op2) << 8) | ((op3) << 16)) -#define M4(op1, op2, op3, op4) \ - ((op1) | ((op2) << 8) | ((op3) << 16) | ((op4) << 24)) - -static BOOL code_match (CodeContext *s, int pos, ...) { - const uint8_t *tab = s->bc_buf; - int op, len, op1, line_num, pos_next; - va_list ap; - BOOL ret = FALSE; - - line_num = -1; - va_start (ap, pos); - - for (;;) { - op1 = va_arg (ap, int); - if (op1 == -1) { - s->pos = pos; - s->line_num = line_num; - ret = TRUE; - break; - } - for (;;) { - if (pos >= s->bc_len) goto done; - op = tab[pos]; - len = opcode_info[op].size; - pos_next = pos + len; - if (pos_next > s->bc_len) goto done; - if (op == OP_line_num) { - line_num = get_u32 (tab + pos + 1); - pos = pos_next; - } else { - break; - } - } - if (op != op1) { - if (op1 == (uint8_t)op1 || !op) break; - if (op != (uint8_t)op1 && op != (uint8_t)(op1 >> 8) - && op != (uint8_t)(op1 >> 16) && op != (uint8_t)(op1 >> 24)) { - break; - } - s->op = op; - } - - pos++; - switch (opcode_info[op].fmt) { - case OP_FMT_loc8: - case OP_FMT_u8: { - int idx = tab[pos]; - int arg = va_arg (ap, int); - if (arg == -1) { - s->idx = idx; - } else { - if (arg != idx) goto done; - } - break; - } - case OP_FMT_u16: - case OP_FMT_npop: - case OP_FMT_loc: - case OP_FMT_arg: { - int idx = get_u16 (tab + pos); - int arg = va_arg (ap, int); - if (arg == -1) { - s->idx = idx; - } else { - if (arg != idx) goto done; - } - break; - } - case OP_FMT_i32: - case OP_FMT_u32: - case OP_FMT_label: - case OP_FMT_const: { - s->label = get_u32 (tab + pos); - break; - } - case OP_FMT_label_u16: { - s->label = get_u32 (tab + pos); - s->val = get_u16 (tab + pos + 4); - break; - } - case OP_FMT_key: { - /* Store cpool index in the label field */ - s->label = get_u32 (tab + pos); - break; - } - case OP_FMT_key_u8: { - s->label = get_u32 (tab + pos); - s->val = get_u8 (tab + pos + 4); - break; - } - case OP_FMT_key_u16: { - s->label = get_u32 (tab + pos); - s->val = get_u16 (tab + pos + 4); - break; - } - case OP_FMT_key_label_u16: { - s->label = get_u32 (tab + pos); - s->label = get_u32 (tab + pos + 4); - s->val = get_u16 (tab + pos + 8); - break; - } - default: - break; - } - pos = pos_next; - } -done: - va_end (ap); - return ret; -} - -static void instantiate_hoisted_definitions (JSContext *ctx, JSFunctionDef *s, DynBuf *bc) { - int i, idx; - - /* add the hoisted functions in arguments and local variables */ - for (i = 0; i < s->arg_count; i++) { - JSVarDef *vd = &s->args[i]; - if (vd->func_pool_idx >= 0) { - dbuf_putc (bc, OP_fclosure); - dbuf_put_u32 (bc, vd->func_pool_idx); - dbuf_putc (bc, OP_put_arg); - dbuf_put_u16 (bc, i); - } - } - for (i = 0; i < s->var_count; i++) { - JSVarDef *vd = &s->vars[i]; - if (vd->scope_level == 0 && vd->func_pool_idx >= 0) { - dbuf_putc (bc, OP_fclosure); - dbuf_put_u32 (bc, vd->func_pool_idx); - dbuf_putc (bc, OP_put_loc); - dbuf_put_u16 (bc, i); - } - } - - /* add the global variables (only happens if s->is_global_var is - true) */ - for (i = 0; i < s->global_var_count; i++) { - JSGlobalVar *hf = &s->global_vars[i]; - int has_closure = 0; - BOOL force_init = hf->force_init; - /* we are in an eval, so the closure contains all the - enclosing variables */ - /* If the outer function has a variable environment, - create a property for the variable there */ - for (idx = 0; idx < s->closure_var_count; idx++) { - JSClosureVar *cv = &s->closure_var[idx]; - if (js_key_equal (cv->var_name, hf->var_name)) { - has_closure = 2; - force_init = FALSE; - break; - } - if (js_key_equal_str (cv->var_name, "_var_") - || js_key_equal_str (cv->var_name, "_arg_var_")) { - int slot, depth = compute_closure_depth_slot(s, idx, &slot); - dbuf_putc (bc, OP_get_up); - dbuf_putc (bc, (uint8_t)depth); - dbuf_put_u16 (bc, (uint16_t)slot); - has_closure = 1; - force_init = TRUE; - break; - } - } - if (!has_closure) { - int flags; - - flags = 0; - if (hf->cpool_idx >= 0 && !hf->is_lexical) { - /* global function definitions need a specific handling */ - dbuf_putc (bc, OP_fclosure); - dbuf_put_u32 (bc, hf->cpool_idx); - - { - int key_idx = fd_cpool_add (ctx, s, hf->var_name); - dbuf_putc (bc, OP_define_func); - dbuf_put_u32 (bc, key_idx); - dbuf_putc (bc, flags); - } - - goto done_global_var; - } else { - if (hf->is_lexical) { flags |= DEFINE_GLOBAL_LEX_VAR; } - int key_idx = fd_cpool_add (ctx, s, hf->var_name); - dbuf_putc (bc, OP_define_var); - dbuf_put_u32 (bc, key_idx); - dbuf_putc (bc, flags); - } - } - if (hf->cpool_idx >= 0 || force_init) { - if (hf->cpool_idx >= 0) { - dbuf_putc (bc, OP_fclosure); - dbuf_put_u32 (bc, hf->cpool_idx); - if (js_key_equal_str (hf->var_name, "_default_")) { - /* set default export function name */ - int name_idx = fd_cpool_add (ctx, s, JS_KEY_default); - dbuf_putc (bc, OP_set_name); - dbuf_put_u32 (bc, name_idx); - } - } else { - dbuf_putc (bc, OP_null); - } - if (has_closure == 2) { - int slot, depth = compute_closure_depth_slot(s, idx, &slot); - dbuf_putc (bc, OP_set_up); - dbuf_putc (bc, (uint8_t)depth); - dbuf_put_u16 (bc, (uint16_t)slot); - } else if (has_closure == 1) { - int key_idx = fd_cpool_add (ctx, s, hf->var_name); - dbuf_putc (bc, OP_define_field); - dbuf_put_u32 (bc, key_idx); - dbuf_putc (bc, OP_drop); - } else { - /* XXX: Check if variable is writable and enumerable */ - int key_idx = fd_cpool_add (ctx, s, hf->var_name); - dbuf_putc (bc, OP_put_var); - dbuf_put_u32 (bc, key_idx); - } - } - done_global_var:; - } - - js_free (ctx, s->global_vars); - s->global_vars = NULL; - s->global_var_count = 0; - s->global_var_size = 0; -} - -static int skip_dead_code (JSFunctionDef *s, const uint8_t *bc_buf, int bc_len, int pos, int *linep) { - int op, len, label; - - for (; pos < bc_len; pos += len) { - op = bc_buf[pos]; - len = opcode_info[op].size; - if (op == OP_line_num) { - *linep = get_u32 (bc_buf + pos + 1); - } else if (op == OP_label) { - label = get_u32 (bc_buf + pos + 1); - if (update_label (s, label, 0) > 0) break; - assert (s->label_slots[label].first_reloc == NULL); - } else { - /* XXX: output a warning for unreachable code? */ - switch (opcode_info[op].fmt) { - case OP_FMT_label: - case OP_FMT_label_u16: - label = get_u32 (bc_buf + pos + 1); - update_label (s, label, -1); - break; - case OP_FMT_key_label_u16: - label = get_u32 (bc_buf + pos + 5); - update_label (s, label, -1); - /* fall thru */ - case OP_FMT_key: - case OP_FMT_key_u8: - case OP_FMT_key_u16: - /* Key operand is a cpool index; cpool values freed separately */ - break; - default: - break; - } - } - } - return pos; -} - -static int get_label_pos (JSFunctionDef *s, int label) { - int i, pos; - for (i = 0; i < 20; i++) { - pos = s->label_slots[label].pos; - for (;;) { - switch (s->byte_code.buf[pos]) { - case OP_line_num: - case OP_label: - pos += 5; - continue; - case OP_goto: - label = get_u32 (s->byte_code.buf + pos + 1); - break; - default: - return pos; - } - break; - } - } - return pos; -} - -/* convert global variable accesses to local variables or closure - variables when necessary */ -static __exception int resolve_variables (JSContext *ctx, JSFunctionDef *s) { - int pos, pos_next, bc_len, op, len, i, idx, line_num; - uint8_t *bc_buf; - JSValue var_name; - int cpool_idx; - DynBuf bc_out; - CodeContext cc; - int scope; - - cc.bc_buf = bc_buf = s->byte_code.buf; - cc.bc_len = bc_len = s->byte_code.size; - js_dbuf_init (ctx, &bc_out); - - /* first pass for runtime checks (must be done before the - variables are created) */ - for (i = 0; i < s->global_var_count; i++) { - JSGlobalVar *hf = &s->global_vars[i]; - int flags; - - /* check if global variable (XXX: simplify) */ - for (idx = 0; idx < s->closure_var_count; idx++) { - JSClosureVar *cv = &s->closure_var[idx]; - if (js_key_equal (cv->var_name, hf->var_name)) { - goto next; - } - if (js_key_equal_str (cv->var_name, "") - || js_key_equal_str (cv->var_name, "")) - goto next; - } - - { - int key_idx = fd_cpool_add (ctx, s, hf->var_name); - dbuf_putc (&bc_out, OP_check_define_var); - dbuf_put_u32 (&bc_out, key_idx); - } - flags = 0; - if (hf->is_lexical) flags |= DEFINE_GLOBAL_LEX_VAR; - if (hf->cpool_idx >= 0) flags |= DEFINE_GLOBAL_FUNC_VAR; - dbuf_putc (&bc_out, flags); - next:; - } - - line_num = 0; /* avoid warning */ - for (pos = 0; pos < bc_len; pos = pos_next) { - op = bc_buf[pos]; - len = opcode_info[op].size; - pos_next = pos + len; - switch (op) { - case OP_line_num: - line_num = get_u32 (bc_buf + pos + 1); - s->line_number_size++; - goto no_change; - - case OP_scope_get_var_checkthis: - case OP_scope_get_var_undef: - case OP_scope_get_var: - case OP_scope_put_var: - case OP_scope_delete_var: - case OP_scope_put_var_init: - cpool_idx = get_u32 (bc_buf + pos + 1); - var_name = s->cpool[cpool_idx]; - scope = get_u16 (bc_buf + pos + 5); - pos_next = resolve_scope_var (ctx, s, var_name, scope, op, &bc_out, NULL, NULL, pos_next); - break; - case OP_gosub: - s->jump_size++; - if (OPTIMIZE) { - /* remove calls to empty finalizers */ - int label; - LabelSlot *ls; - - label = get_u32 (bc_buf + pos + 1); - assert (label >= 0 && label < s->label_count); - ls = &s->label_slots[label]; - if (code_match (&cc, ls->pos, OP_ret, -1)) { - ls->ref_count--; - break; - } - } - goto no_change; - case OP_drop: - if (0) { - /* remove drops before return_undef */ - /* do not perform this optimization in pass2 because - it breaks patterns recognised in resolve_labels */ - int pos1 = pos_next; - int line1 = line_num; - while (code_match (&cc, pos1, OP_drop, -1)) { - if (cc.line_num >= 0) line1 = cc.line_num; - pos1 = cc.pos; - } - if (code_match (&cc, pos1, OP_return_undef, -1)) { - pos_next = pos1; - if (line1 != -1 && line1 != line_num) { - line_num = line1; - s->line_number_size++; - dbuf_putc (&bc_out, OP_line_num); - dbuf_put_u32 (&bc_out, line_num); - } - break; - } - } - goto no_change; - case OP_insert3: - if (OPTIMIZE) { - /* Transformation: insert3 put_array_el drop -> put_array_el */ - if (code_match (&cc, pos_next, OP_put_array_el, OP_drop, -1)) { - dbuf_putc (&bc_out, cc.op); - pos_next = cc.pos; - if (cc.line_num != -1 && cc.line_num != line_num) { - line_num = cc.line_num; - s->line_number_size++; - dbuf_putc (&bc_out, OP_line_num); - dbuf_put_u32 (&bc_out, line_num); - } - break; - } - } - goto no_change; - - case OP_goto: - s->jump_size++; - /* fall thru */ - case OP_tail_call: - case OP_tail_call_method: - case OP_return: - case OP_return_undef: - case OP_throw: - case OP_throw_error: - case OP_ret: - if (OPTIMIZE) { - /* remove dead code */ - int line = -1; - dbuf_put (&bc_out, bc_buf + pos, len); - pos = skip_dead_code (s, bc_buf, bc_len, pos + len, &line); - pos_next = pos; - if (pos < bc_len && line >= 0 && line_num != line) { - line_num = line; - s->line_number_size++; - dbuf_putc (&bc_out, OP_line_num); - dbuf_put_u32 (&bc_out, line_num); - } - break; - } - goto no_change; - - case OP_label: { - int label; - LabelSlot *ls; - - label = get_u32 (bc_buf + pos + 1); - assert (label >= 0 && label < s->label_count); - ls = &s->label_slots[label]; - ls->pos2 = bc_out.size + opcode_info[op].size; - } - goto no_change; - - case OP_enter_scope: { - int scope_idx, scope = get_u16 (bc_buf + pos + 1); - - if (scope == s->body_scope) { - instantiate_hoisted_definitions (ctx, s, &bc_out); - } - - for (scope_idx = s->scopes[scope].first; scope_idx >= 0;) { - JSVarDef *vd = &s->vars[scope_idx]; - if (vd->scope_level != scope) break; - - if (vd->var_kind == JS_VAR_FUNCTION_DECL - || vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) { - /* Initialize lexical variable upon entering scope */ - dbuf_putc (&bc_out, OP_fclosure); - dbuf_put_u32 (&bc_out, vd->func_pool_idx); - dbuf_putc (&bc_out, OP_put_loc); - dbuf_put_u16 (&bc_out, scope_idx); - } else { - /* XXX: should check if variable can be used - before initialization */ - dbuf_putc (&bc_out, OP_set_loc_uninitialized); - dbuf_put_u16 (&bc_out, scope_idx); - } - - scope_idx = vd->scope_next; - } - } break; - - case OP_leave_scope: - /* With outer_frame model, captured variables don't need closing */ - break; - - case OP_set_name: { - /* remove dummy set_name opcodes */ - int name_idx = get_u32 (bc_buf + pos + 1); - if (JS_IsNull (s->cpool[name_idx])) break; - } - goto no_change; - - case OP_if_false: - case OP_if_true: - case OP_catch: - s->jump_size++; - goto no_change; - - case OP_dup: - if (OPTIMIZE) { - /* Transformation: dup if_false(l1) drop, l1: if_false(l2) -> - * if_false(l2) */ - /* Transformation: dup if_true(l1) drop, l1: if_true(l2) -> if_true(l2) - */ - if (code_match (&cc, pos_next, M2 (OP_if_false, OP_if_true), OP_drop, -1)) { - int lab0, lab1, op1, pos1, line1, pos2; - lab0 = lab1 = cc.label; - assert (lab1 >= 0 && lab1 < s->label_count); - op1 = cc.op; - pos1 = cc.pos; - line1 = cc.line_num; - while (code_match (&cc, (pos2 = get_label_pos (s, lab1)), OP_dup, op1, OP_drop, -1)) { - lab1 = cc.label; - } - if (code_match (&cc, pos2, op1, -1)) { - s->jump_size++; - update_label (s, lab0, -1); - update_label (s, cc.label, +1); - dbuf_putc (&bc_out, op1); - dbuf_put_u32 (&bc_out, cc.label); - pos_next = pos1; - if (line1 != -1 && line1 != line_num) { - line_num = line1; - s->line_number_size++; - dbuf_putc (&bc_out, OP_line_num); - dbuf_put_u32 (&bc_out, line_num); - } - break; - } - } - } - goto no_change; - - case OP_nop: - /* remove erased code */ - break; - case OP_set_class_name: - /* only used during parsing */ - break; - - case OP_get_field_opt_chain: /* equivalent to OP_get_field */ - { - int key_idx = get_u32 (bc_buf + pos + 1); - dbuf_putc (&bc_out, OP_get_field); - dbuf_put_u32 (&bc_out, key_idx); - } break; - case OP_get_array_el_opt_chain: /* equivalent to OP_get_array_el */ - dbuf_putc (&bc_out, OP_get_array_el); - break; - - default: - no_change: - dbuf_put (&bc_out, bc_buf + pos, len); - break; - } - } - - /* set the new byte code */ - dbuf_free (&s->byte_code); - s->byte_code = bc_out; - if (dbuf_error (&s->byte_code)) { - JS_ThrowOutOfMemory (ctx); - return -1; - } - return 0; -} - -/* the pc2line table gives a source position for each PC value */ -static void add_pc2line_info (JSFunctionDef *s, uint32_t pc, uint32_t source_pos) { - if (s->line_number_slots != NULL - && s->line_number_count < s->line_number_size - && pc >= s->line_number_last_pc && source_pos != s->line_number_last) { - s->line_number_slots[s->line_number_count].pc = pc; - s->line_number_slots[s->line_number_count].source_pos = source_pos; - s->line_number_count++; - s->line_number_last_pc = pc; - s->line_number_last = source_pos; - } -} - -/* XXX: could use a more compact storage */ -/* XXX: get_line_col_cached() is slow. For more predictable - performance, line/cols could be stored every N source - bytes. Alternatively, get_line_col_cached() could be issued in - emit_source_pos() so that the deltas are more likely to be - small. */ -static void compute_pc2line_info (JSFunctionDef *s) { - if (!s->strip_debug) { - int last_line_num, last_col_num; - uint32_t last_pc = 0; - int i, line_num, col_num; - const uint8_t *buf_start = s->get_line_col_cache->buf_start; - js_dbuf_init (s->ctx, &s->pc2line); - - last_line_num = get_line_col_cached (s->get_line_col_cache, &last_col_num, buf_start + s->source_pos); - dbuf_put_leb128 (&s->pc2line, last_line_num); /* line number minus 1 */ - dbuf_put_leb128 (&s->pc2line, last_col_num); /* column number minus 1 */ - - for (i = 0; i < s->line_number_count; i++) { - uint32_t pc = s->line_number_slots[i].pc; - uint32_t source_pos = s->line_number_slots[i].source_pos; - int diff_pc, diff_line, diff_col; - - if (source_pos == -1) continue; - diff_pc = pc - last_pc; - if (diff_pc < 0) continue; - - line_num = get_line_col_cached (s->get_line_col_cache, &col_num, buf_start + source_pos); - diff_line = line_num - last_line_num; - diff_col = col_num - last_col_num; - if (diff_line == 0 && diff_col == 0) continue; - - if (diff_line >= PC2LINE_BASE && diff_line < PC2LINE_BASE + PC2LINE_RANGE - && diff_pc <= PC2LINE_DIFF_PC_MAX) { - dbuf_putc (&s->pc2line, (diff_line - PC2LINE_BASE) + diff_pc * PC2LINE_RANGE + PC2LINE_OP_FIRST); - } else { - /* longer encoding */ - dbuf_putc (&s->pc2line, 0); - dbuf_put_leb128 (&s->pc2line, diff_pc); - dbuf_put_sleb128 (&s->pc2line, diff_line); - } - dbuf_put_sleb128 (&s->pc2line, diff_col); - - last_pc = pc; - last_line_num = line_num; - last_col_num = col_num; - } - } -} - -static RelocEntry *add_reloc (JSContext *ctx, LabelSlot *ls, uint32_t addr, int size) { - RelocEntry *re; - (void)ctx; - re = pjs_malloc (sizeof (*re)); - if (!re) return NULL; - re->addr = addr; - re->size = size; - re->next = ls->first_reloc; - ls->first_reloc = re; - return re; -} - -static BOOL code_has_label (CodeContext *s, int pos, int label) { - while (pos < s->bc_len) { - int op = s->bc_buf[pos]; - if (op == OP_line_num) { - pos += 5; - continue; - } - if (op == OP_label) { - int lab = get_u32 (s->bc_buf + pos + 1); - if (lab == label) return TRUE; - pos += 5; - continue; - } - if (op == OP_goto) { - int lab = get_u32 (s->bc_buf + pos + 1); - if (lab == label) return TRUE; - } - break; - } - return FALSE; -} - -/* return the target label, following the OP_goto jumps - the first opcode at destination is stored in *pop - */ -static int find_jump_target (JSFunctionDef *s, int label0, int *pop, int *pline) { - int i, pos, op, label; - - label = label0; - update_label (s, label, -1); - for (i = 0; i < 10; i++) { - assert (label >= 0 && label < s->label_count); - pos = s->label_slots[label].pos2; - for (;;) { - switch (op = s->byte_code.buf[pos]) { - case OP_line_num: - if (pline) *pline = get_u32 (s->byte_code.buf + pos + 1); - /* fall thru */ - case OP_label: - pos += opcode_info[op].size; - continue; - case OP_goto: - label = get_u32 (s->byte_code.buf + pos + 1); - break; - case OP_drop: - /* ignore drop opcodes if followed by OP_return_undef */ - while (s->byte_code.buf[++pos] == OP_drop) - continue; - if (s->byte_code.buf[pos] == OP_return_undef) op = OP_return_undef; - /* fall thru */ - default: - goto done; - } - break; - } - } - /* cycle detected, could issue a warning */ - /* XXX: the combination of find_jump_target() and skip_dead_code() - seems incorrect with cyclic labels. See for exemple: - - for (;;) { - l:break l; - l:break l; - l:break l; - l:break l; - } - - Avoiding changing the target is just a workaround and might not - suffice to completely fix the problem. */ - label = label0; -done: - *pop = op; - update_label (s, label, +1); - return label; -} - -static void push_short_int (DynBuf *bc_out, int val) { -#if SHORT_OPCODES - if (val >= -1 && val <= 7) { - dbuf_putc (bc_out, OP_push_0 + val); - return; - } - if (val == (int8_t)val) { - dbuf_putc (bc_out, OP_push_i8); - dbuf_putc (bc_out, val); - return; - } - if (val == (int16_t)val) { - dbuf_putc (bc_out, OP_push_i16); - dbuf_put_u16 (bc_out, val); - return; - } -#endif - dbuf_putc (bc_out, OP_push_i32); - dbuf_put_u32 (bc_out, val); -} - -static void put_short_code (DynBuf *bc_out, int op, int idx) { -#if SHORT_OPCODES - if (idx < 4) { - switch (op) { - case OP_get_loc: - dbuf_putc (bc_out, OP_get_loc0 + idx); - return; - case OP_put_loc: - dbuf_putc (bc_out, OP_put_loc0 + idx); - return; - case OP_set_loc: - dbuf_putc (bc_out, OP_set_loc0 + idx); - return; - case OP_get_arg: - dbuf_putc (bc_out, OP_get_arg0 + idx); - return; - case OP_put_arg: - dbuf_putc (bc_out, OP_put_arg0 + idx); - return; - case OP_set_arg: - dbuf_putc (bc_out, OP_set_arg0 + idx); - return; - case OP_call: - dbuf_putc (bc_out, OP_call0 + idx); - return; - } - } - if (idx < 256) { - switch (op) { - case OP_get_loc: - dbuf_putc (bc_out, OP_get_loc8); - dbuf_putc (bc_out, idx); - return; - case OP_put_loc: - dbuf_putc (bc_out, OP_put_loc8); - dbuf_putc (bc_out, idx); - return; - case OP_set_loc: - dbuf_putc (bc_out, OP_set_loc8); - dbuf_putc (bc_out, idx); - return; - } - } -#endif - dbuf_putc (bc_out, op); - dbuf_put_u16 (bc_out, idx); -} - -/* peephole optimizations and resolve goto/labels */ -static __exception int resolve_labels (JSContext *ctx, JSFunctionDef *s) { - int pos, pos_next, bc_len, op, op1, len, i, line_num; - const uint8_t *bc_buf; - DynBuf bc_out; - LabelSlot *label_slots, *ls; - RelocEntry *re, *re_next; - CodeContext cc; - int label; -#if SHORT_OPCODES - JumpSlot *jp; -#endif - - label_slots = s->label_slots; - - line_num = s->source_pos; - - cc.bc_buf = bc_buf = s->byte_code.buf; - cc.bc_len = bc_len = s->byte_code.size; - js_dbuf_init (ctx, &bc_out); - -#if SHORT_OPCODES - if (s->jump_size) { - s->jump_slots - = pjs_mallocz (sizeof (*s->jump_slots) * s->jump_size); - if (s->jump_slots == NULL) return -1; - } -#endif - /* XXX: Should skip this phase if not generating SHORT_OPCODES */ - if (s->line_number_size && !s->strip_debug) { - s->line_number_slots = pjs_mallocz (sizeof (*s->line_number_slots) * s->line_number_size); - if (s->line_number_slots == NULL) return -1; - s->line_number_last = s->source_pos; - s->line_number_last_pc = 0; - } - - /* initialize the 'this.active_func' variable if needed */ - if (s->this_active_func_var_idx >= 0) { - dbuf_putc (&bc_out, OP_special_object); - dbuf_putc (&bc_out, OP_SPECIAL_OBJECT_THIS_FUNC); - put_short_code (&bc_out, OP_put_loc, s->this_active_func_var_idx); - } - /* initialize the 'this' variable if needed */ - if (s->this_var_idx >= 0) { - dbuf_putc (&bc_out, OP_push_this); - put_short_code (&bc_out, OP_put_loc, s->this_var_idx); - } - /* initialize a reference to the current function if needed */ - if (s->func_var_idx >= 0) { - dbuf_putc (&bc_out, OP_special_object); - dbuf_putc (&bc_out, OP_SPECIAL_OBJECT_THIS_FUNC); - put_short_code (&bc_out, OP_put_loc, s->func_var_idx); - } - /* initialize the variable environment object if needed */ - if (s->var_object_idx >= 0) { - dbuf_putc (&bc_out, OP_special_object); - dbuf_putc (&bc_out, OP_SPECIAL_OBJECT_VAR_OBJECT); - put_short_code (&bc_out, OP_put_loc, s->var_object_idx); - } - if (s->arg_var_object_idx >= 0) { - dbuf_putc (&bc_out, OP_special_object); - dbuf_putc (&bc_out, OP_SPECIAL_OBJECT_VAR_OBJECT); - put_short_code (&bc_out, OP_put_loc, s->arg_var_object_idx); - } - - for (pos = 0; pos < bc_len; pos = pos_next) { - int val; - op = bc_buf[pos]; - len = opcode_info[op].size; - pos_next = pos + len; - switch (op) { - case OP_line_num: - /* line number info (for debug). We put it in a separate - compressed table to reduce memory usage and get better - performance */ - line_num = get_u32 (bc_buf + pos + 1); - break; - - case OP_label: { - label = get_u32 (bc_buf + pos + 1); - assert (label >= 0 && label < s->label_count); - ls = &label_slots[label]; - assert (ls->addr == -1); - ls->addr = bc_out.size; - /* resolve the relocation entries */ - for (re = ls->first_reloc; re != NULL; re = re_next) { - int diff = ls->addr - re->addr; - re_next = re->next; - switch (re->size) { - case 4: - put_u32 (bc_out.buf + re->addr, diff); - break; - case 2: - assert (diff == (int16_t)diff); - put_u16 (bc_out.buf + re->addr, diff); - break; - case 1: - assert (diff == (int8_t)diff); - put_u8 (bc_out.buf + re->addr, diff); - break; - } - pjs_free (re); - } - ls->first_reloc = NULL; - } break; - - case OP_call: - case OP_call_method: { - /* detect and transform tail calls */ - int argc; - argc = get_u16 (bc_buf + pos + 1); - if (code_match (&cc, pos_next, OP_return, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - add_pc2line_info (s, bc_out.size, line_num); - put_short_code (&bc_out, op + 1, argc); - pos_next = skip_dead_code (s, bc_buf, bc_len, cc.pos, &line_num); - break; - } - add_pc2line_info (s, bc_out.size, line_num); - put_short_code (&bc_out, op, argc); - break; - } - goto no_change; - - case OP_return: - case OP_return_undef: - case OP_throw: - case OP_throw_error: - pos_next = skip_dead_code (s, bc_buf, bc_len, pos_next, &line_num); - goto no_change; - - case OP_goto: - label = get_u32 (bc_buf + pos + 1); - has_goto: - if (OPTIMIZE) { - int line1 = -1; - /* Use custom matcher because multiple labels can follow */ - label = find_jump_target (s, label, &op1, &line1); - if (code_has_label (&cc, pos_next, label)) { - /* jump to next instruction: remove jump */ - update_label (s, label, -1); - break; - } - if (op1 == OP_return || op1 == OP_return_undef || op1 == OP_throw) { - /* jump to return/throw: remove jump, append return/throw */ - /* updating the line number obfuscates assembly listing */ - // if (line1 != -1) line_num = line1; - update_label (s, label, -1); - add_pc2line_info (s, bc_out.size, line_num); - dbuf_putc (&bc_out, op1); - pos_next = skip_dead_code (s, bc_buf, bc_len, pos_next, &line_num); - break; - } - /* XXX: should duplicate single instructions followed by goto or return - */ - /* For example, can match one of these followed by return: - push_i32 / push_const / push_atom_value / get_var / - undefined / null / push_false / push_true / get_ref_value / - get_loc / get_arg / get_var_ref - */ - } - goto has_label; - - case OP_gosub: - label = get_u32 (bc_buf + pos + 1); - if (0 && OPTIMIZE) { - label = find_jump_target (s, label, &op1, NULL); - if (op1 == OP_ret) { - update_label (s, label, -1); - /* empty finally clause: remove gosub */ - break; - } - } - goto has_label; - - case OP_catch: - label = get_u32 (bc_buf + pos + 1); - goto has_label; - - case OP_if_true: - case OP_if_false: - label = get_u32 (bc_buf + pos + 1); - if (OPTIMIZE) { - label = find_jump_target (s, label, &op1, NULL); - /* transform if_false/if_true(l1) label(l1) -> drop label(l1) */ - if (code_has_label (&cc, pos_next, label)) { - update_label (s, label, -1); - dbuf_putc (&bc_out, OP_drop); - break; - } - /* transform if_false(l1) goto(l2) label(l1) -> if_false(l2) label(l1) - */ - if (code_match (&cc, pos_next, OP_goto, -1)) { - int pos1 = cc.pos; - int line1 = cc.line_num; - if (code_has_label (&cc, pos1, label)) { - if (line1 != -1) line_num = line1; - pos_next = pos1; - update_label (s, label, -1); - label = cc.label; - op ^= OP_if_true ^ OP_if_false; - } - } - } - has_label: - add_pc2line_info (s, bc_out.size, line_num); - if (op == OP_goto) { - pos_next = skip_dead_code (s, bc_buf, bc_len, pos_next, &line_num); - } - assert (label >= 0 && label < s->label_count); - ls = &label_slots[label]; -#if SHORT_OPCODES - jp = &s->jump_slots[s->jump_count++]; - jp->op = op; - jp->size = 4; - jp->pos = bc_out.size + 1; - jp->label = label; - - if (ls->addr == -1) { - int diff = ls->pos2 - pos - 1; - if (diff < 128 - && (op == OP_if_false || op == OP_if_true || op == OP_goto)) { - jp->size = 1; - jp->op = OP_if_false8 + (op - OP_if_false); - dbuf_putc (&bc_out, OP_if_false8 + (op - OP_if_false)); - dbuf_putc (&bc_out, 0); - if (!add_reloc (ctx, ls, bc_out.size - 1, 1)) goto fail; - break; - } - if (diff < 32768 && op == OP_goto) { - jp->size = 2; - jp->op = OP_goto16; - dbuf_putc (&bc_out, OP_goto16); - dbuf_put_u16 (&bc_out, 0); - if (!add_reloc (ctx, ls, bc_out.size - 2, 2)) goto fail; - break; - } - } else { - int diff = ls->addr - bc_out.size - 1; - if (diff == (int8_t)diff - && (op == OP_if_false || op == OP_if_true || op == OP_goto)) { - jp->size = 1; - jp->op = OP_if_false8 + (op - OP_if_false); - dbuf_putc (&bc_out, OP_if_false8 + (op - OP_if_false)); - dbuf_putc (&bc_out, diff); - break; - } - if (diff == (int16_t)diff && op == OP_goto) { - jp->size = 2; - jp->op = OP_goto16; - dbuf_putc (&bc_out, OP_goto16); - dbuf_put_u16 (&bc_out, diff); - break; - } - } -#endif - dbuf_putc (&bc_out, op); - dbuf_put_u32 (&bc_out, ls->addr - bc_out.size); - if (ls->addr == -1) { - /* unresolved yet: create a new relocation entry */ - if (!add_reloc (ctx, ls, bc_out.size - 4, 4)) goto fail; - } - break; - case OP_drop: - if (OPTIMIZE) { - /* remove useless drops before return */ - if (code_match (&cc, pos_next, OP_return_undef, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - break; - } - } - goto no_change; - - case OP_null: - if (OPTIMIZE) { - /* 1) remove null; drop → (nothing) */ - if (code_match (&cc, pos_next, OP_drop, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - pos_next = cc.pos; - break; - } - /* 2) null; return → return_undef */ - if (code_match (&cc, pos_next, OP_return, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - add_pc2line_info (s, bc_out.size, line_num); - dbuf_putc (&bc_out, OP_return_undef); - pos_next = cc.pos; - break; - } - /* 3) null; if_false/if_true → goto or nop */ - if (code_match (&cc, pos_next, M2 (OP_if_false, OP_if_true), -1)) { - val = 0; - goto has_constant_test; - } -#if SHORT_OPCODES - /* 4a) null strict_eq → is_null */ - if (code_match (&cc, pos_next, OP_strict_eq, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - add_pc2line_info (s, bc_out.size, line_num); - dbuf_putc (&bc_out, OP_is_null); - pos_next = cc.pos; - break; - } - /* 4b) null strict_neq; if_false/if_true → is_null + flipped branch */ - if (code_match (&cc, pos_next, OP_strict_neq, M2 (OP_if_false, OP_if_true), -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - add_pc2line_info (s, bc_out.size, line_num); - dbuf_putc (&bc_out, OP_is_null); - pos_next = cc.pos; - label = cc.label; - op = cc.op ^ OP_if_false ^ OP_if_true; - goto has_label; - } -#endif - } - /* didn’t match any of the above? fall straight into the - OP_push_false / OP_push_true constant‐test code: */ - case OP_push_false: - case OP_push_true: - if (OPTIMIZE) { - val = (op == OP_push_true); - if (code_match (&cc, pos_next, M2 (OP_if_false, OP_if_true), -1)) { - has_constant_test: - if (cc.line_num >= 0) line_num = cc.line_num; - if (val == (cc.op - OP_if_false)) { - /* e.g. null if_false(L) → goto L */ - pos_next = cc.pos; - op = OP_goto; - label = cc.label; - goto has_goto; - } else { - /* e.g. null if_true(L) → nop */ - pos_next = cc.pos; - update_label (s, cc.label, -1); - break; - } - } - } - goto no_change; - - case OP_push_i32: - if (OPTIMIZE) { - /* transform i32(val) neg -> i32(-val) */ - val = get_i32 (bc_buf + pos + 1); - if ((val != INT32_MIN && val != 0) - && code_match (&cc, pos_next, OP_neg, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - if (code_match (&cc, cc.pos, OP_drop, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - } else { - add_pc2line_info (s, bc_out.size, line_num); - push_short_int (&bc_out, -val); - } - pos_next = cc.pos; - break; - } - /* remove push/drop pairs generated by the parser */ - if (code_match (&cc, pos_next, OP_drop, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - pos_next = cc.pos; - break; - } - /* Optimize constant tests: `if (0)`, `if (1)`, `if (!0)`... */ - if (code_match (&cc, pos_next, M2 (OP_if_false, OP_if_true), -1)) { - val = (val != 0); - goto has_constant_test; - } - add_pc2line_info (s, bc_out.size, line_num); - push_short_int (&bc_out, val); - break; - } - goto no_change; - -#if SHORT_OPCODES - case OP_push_const: - case OP_fclosure: - if (OPTIMIZE) { - int idx = get_u32 (bc_buf + pos + 1); - if (idx < 256) { - add_pc2line_info (s, bc_out.size, line_num); - dbuf_putc (&bc_out, OP_push_const8 + op - OP_push_const); - dbuf_putc (&bc_out, idx); - break; - } - } - goto no_change; -#endif - case OP_to_propkey: - if (OPTIMIZE) { - /* remove redundant to_propkey opcodes when storing simple data */ - if (code_match (&cc, pos_next, M2 (OP_get_loc, OP_get_arg), -1, OP_put_array_el, -1) - || code_match (&cc, pos_next, M2 (OP_push_i32, OP_push_const), OP_put_array_el, -1) - || code_match (&cc, pos_next, M4 (OP_null, OP_null, OP_push_true, OP_push_false), OP_put_array_el, -1)) { - break; - } - } - goto no_change; - case OP_insert2: - if (OPTIMIZE) { - /* Transformation: - insert2 put_field(a) drop -> put_field(a) - insert2 put_var_strict(a) drop -> put_var_strict(a) - */ - if (code_match (&cc, pos_next, M2 (OP_put_field, OP_put_var_strict), OP_drop, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - add_pc2line_info (s, bc_out.size, line_num); - dbuf_putc (&bc_out, cc.op); - dbuf_put_u32 (&bc_out, cc.label); - pos_next = cc.pos; - break; - } - } - goto no_change; - - case OP_dup: - if (OPTIMIZE) { - /* Transformation: dup put_x(n) drop -> put_x(n) */ - int op1, line2 = -1; - /* Transformation: dup put_x(n) -> set_x(n) */ - if (code_match (&cc, pos_next, M2 (OP_put_loc, OP_put_arg), -1, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - op1 = cc.op + 1; /* put_x -> set_x */ - pos_next = cc.pos; - if (code_match (&cc, cc.pos, OP_drop, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - op1 -= 1; /* set_x drop -> put_x */ - pos_next = cc.pos; - if (code_match (&cc, cc.pos, op1 - 1, cc.idx, -1)) { - line2 = cc.line_num; /* delay line number update */ - op1 += 1; /* put_x(n) get_x(n) -> set_x(n) */ - pos_next = cc.pos; - } - } - add_pc2line_info (s, bc_out.size, line_num); - put_short_code (&bc_out, op1, cc.idx); - if (line2 >= 0) line_num = line2; - break; - } - } - goto no_change; - - case OP_get_loc: - if (OPTIMIZE) { - /* transformation: - get_loc(n) post_dec put_loc(n) drop -> dec_loc(n) - get_loc(n) post_inc put_loc(n) drop -> inc_loc(n) - get_loc(n) dec dup put_loc(n) drop -> dec_loc(n) - get_loc(n) inc dup put_loc(n) drop -> inc_loc(n) - */ - int idx; - idx = get_u16 (bc_buf + pos + 1); - if (idx >= 256) goto no_change; - if (code_match (&cc, pos_next, M2 (OP_post_dec, OP_post_inc), OP_put_loc, idx, OP_drop, -1) - || code_match (&cc, pos_next, M2 (OP_dec, OP_inc), OP_dup, OP_put_loc, idx, OP_drop, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - add_pc2line_info (s, bc_out.size, line_num); - dbuf_putc (&bc_out, (cc.op == OP_inc || cc.op == OP_post_inc) ? OP_inc_loc : OP_dec_loc); - dbuf_putc (&bc_out, idx); - pos_next = cc.pos; - break; - } - /* transformation: - get_loc(n) push_i32(x) add dup put_loc(n) drop -> push_i32(x) - add_loc(n) - */ - if (code_match (&cc, pos_next, OP_push_i32, OP_add, OP_dup, OP_put_loc, idx, OP_drop, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - add_pc2line_info (s, bc_out.size, line_num); - push_short_int (&bc_out, cc.label); - dbuf_putc (&bc_out, OP_add_loc); - dbuf_putc (&bc_out, idx); - pos_next = cc.pos; - break; - } - /* transformation: - get_loc(n) get_loc(x) add dup put_loc(n) drop -> get_loc(x) add_loc(n) - get_loc(n) get_arg(x) add dup put_loc(n) drop -> get_arg(x) add_loc(n) - */ - if (code_match (&cc, pos_next, M2 (OP_get_loc, OP_get_arg), -1, OP_add, OP_dup, OP_put_loc, idx, OP_drop, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - add_pc2line_info (s, bc_out.size, line_num); - put_short_code (&bc_out, cc.op, cc.idx); - dbuf_putc (&bc_out, OP_add_loc); - dbuf_putc (&bc_out, idx); - pos_next = cc.pos; - break; - } - add_pc2line_info (s, bc_out.size, line_num); - put_short_code (&bc_out, op, idx); - break; - } - goto no_change; -#if SHORT_OPCODES - case OP_get_arg: - if (OPTIMIZE) { - int idx; - idx = get_u16 (bc_buf + pos + 1); - add_pc2line_info (s, bc_out.size, line_num); - put_short_code (&bc_out, op, idx); - break; - } - goto no_change; -#endif - case OP_put_loc: - case OP_put_arg: - if (OPTIMIZE) { - /* transformation: put_x(n) get_x(n) -> set_x(n) */ - int idx; - idx = get_u16 (bc_buf + pos + 1); - if (code_match (&cc, pos_next, op - 1, idx, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - add_pc2line_info (s, bc_out.size, line_num); - put_short_code (&bc_out, op + 1, idx); - pos_next = cc.pos; - break; - } - add_pc2line_info (s, bc_out.size, line_num); - put_short_code (&bc_out, op, idx); - break; - } - goto no_change; - - case OP_post_inc: - case OP_post_dec: - if (OPTIMIZE) { - /* transformation: - post_inc put_x drop -> inc put_x - post_inc perm3 put_field drop -> inc put_field - post_inc perm3 put_var_strict drop -> inc put_var_strict - post_inc perm4 put_array_el drop -> inc put_array_el - */ - int op1, idx; - if (code_match (&cc, pos_next, M2 (OP_put_loc, OP_put_arg), -1, OP_drop, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - op1 = cc.op; - idx = cc.idx; - pos_next = cc.pos; - if (code_match (&cc, cc.pos, op1 - 1, idx, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - op1 += 1; /* put_x(n) get_x(n) -> set_x(n) */ - pos_next = cc.pos; - } - add_pc2line_info (s, bc_out.size, line_num); - dbuf_putc (&bc_out, OP_dec + (op - OP_post_dec)); - put_short_code (&bc_out, op1, idx); - break; - } - if (code_match (&cc, pos_next, OP_perm3, M2 (OP_put_field, OP_put_var_strict), OP_drop, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - add_pc2line_info (s, bc_out.size, line_num); - dbuf_putc (&bc_out, OP_dec + (op - OP_post_dec)); - dbuf_putc (&bc_out, cc.op); - dbuf_put_u32 (&bc_out, cc.label); - pos_next = cc.pos; - break; - } - if (code_match (&cc, pos_next, OP_perm4, OP_put_array_el, OP_drop, -1)) { - if (cc.line_num >= 0) line_num = cc.line_num; - add_pc2line_info (s, bc_out.size, line_num); - dbuf_putc (&bc_out, OP_dec + (op - OP_post_dec)); - dbuf_putc (&bc_out, OP_put_array_el); - pos_next = cc.pos; - break; - } - } - goto no_change; - - default: - no_change: - add_pc2line_info (s, bc_out.size, line_num); - dbuf_put (&bc_out, bc_buf + pos, len); - break; - } - } - - /* check that there were no missing labels */ - for (i = 0; i < s->label_count; i++) { - assert (label_slots[i].first_reloc == NULL); - } -#if SHORT_OPCODES - if (OPTIMIZE) { - /* more jump optimizations */ - int patch_offsets = 0; - for (i = 0, jp = s->jump_slots; i < s->jump_count; i++, jp++) { - LabelSlot *ls; - JumpSlot *jp1; - int j, pos, diff, delta; - - delta = 3; - switch (op = jp->op) { - case OP_goto16: - delta = 1; - /* fall thru */ - case OP_if_false: - case OP_if_true: - case OP_goto: - pos = jp->pos; - diff = s->label_slots[jp->label].addr - pos; - if (diff >= -128 && diff <= 127 + delta) { - // put_u8(bc_out.buf + pos, diff); - jp->size = 1; - if (op == OP_goto16) { - bc_out.buf[pos - 1] = jp->op = OP_goto8; - } else { - bc_out.buf[pos - 1] = jp->op = OP_if_false8 + (op - OP_if_false); - } - goto shrink; - } else if (diff == (int16_t)diff && op == OP_goto) { - // put_u16(bc_out.buf + pos, diff); - jp->size = 2; - delta = 2; - bc_out.buf[pos - 1] = jp->op = OP_goto16; - shrink: - /* XXX: should reduce complexity, using 2 finger copy scheme */ - memmove (bc_out.buf + pos + jp->size, - bc_out.buf + pos + jp->size + delta, - bc_out.size - pos - jp->size - delta); - bc_out.size -= delta; - patch_offsets++; - for (j = 0, ls = s->label_slots; j < s->label_count; j++, ls++) { - if (ls->addr > pos) ls->addr -= delta; - } - for (j = i + 1, jp1 = jp + 1; j < s->jump_count; j++, jp1++) { - if (jp1->pos > pos) jp1->pos -= delta; - } - for (j = 0; j < s->line_number_count; j++) { - if (s->line_number_slots[j].pc > pos) - s->line_number_slots[j].pc -= delta; - } - continue; - } - break; - } - } - if (patch_offsets) { - JumpSlot *jp1; - int j; - for (j = 0, jp1 = s->jump_slots; j < s->jump_count; j++, jp1++) { - int diff1 = s->label_slots[jp1->label].addr - jp1->pos; - switch (jp1->size) { - case 1: - put_u8 (bc_out.buf + jp1->pos, diff1); - break; - case 2: - put_u16 (bc_out.buf + jp1->pos, diff1); - break; - case 4: - put_u32 (bc_out.buf + jp1->pos, diff1); - break; - } - } - } - } - pjs_free (s->jump_slots); - s->jump_slots = NULL; -#endif - pjs_free (s->label_slots); - s->label_slots = NULL; - /* XXX: should delay until copying to runtime bytecode function */ - compute_pc2line_info (s); - pjs_free (s->line_number_slots); - s->line_number_slots = NULL; - /* set the new byte code */ - dbuf_free (&s->byte_code); - s->byte_code = bc_out; - s->use_short_opcodes = TRUE; - if (dbuf_error (&s->byte_code)) { - JS_ThrowOutOfMemory (ctx); - return -1; - } - return 0; -fail: - /* XXX: not safe */ - dbuf_free (&bc_out); - return -1; -} - -/* compute the maximum stack size needed by the function */ - -typedef struct StackSizeState { - int bc_len; - int stack_len_max; - uint16_t *stack_level_tab; - int32_t *catch_pos_tab; - int *pc_stack; - int pc_stack_len; - int pc_stack_size; -} StackSizeState; - -/* 'op' is only used for error indication */ -static __exception int ss_check (JSContext *ctx, StackSizeState *s, int pos, int op, int stack_len, int catch_pos) { - if ((unsigned)pos >= s->bc_len) { - JS_ThrowInternalError (ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos); - return -1; - } - if (stack_len > s->stack_len_max) { - s->stack_len_max = stack_len; - if (s->stack_len_max > JS_STACK_SIZE_MAX) { - JS_ThrowInternalError (ctx, "stack overflow (op=%d, pc=%d)", op, pos); - return -1; - } - } - if (s->stack_level_tab[pos] != 0xffff) { - /* already explored: check that the stack size is consistent */ - if (s->stack_level_tab[pos] != stack_len) { - JS_ThrowInternalError (ctx, "inconsistent stack size: %d %d (pc=%d)", s->stack_level_tab[pos], stack_len, pos); - return -1; - } else if (s->catch_pos_tab[pos] != catch_pos) { - JS_ThrowInternalError (ctx, "inconsistent catch position: %d %d (pc=%d)", s->catch_pos_tab[pos], catch_pos, pos); - return -1; - } else { - return 0; - } - } - - /* mark as explored and store the stack size */ - s->stack_level_tab[pos] = stack_len; - s->catch_pos_tab[pos] = catch_pos; - - /* queue the new PC to explore */ - if (pjs_resize_array ((void **)&s->pc_stack, sizeof (s->pc_stack[0]), &s->pc_stack_size, s->pc_stack_len + 1)) - return -1; - s->pc_stack[s->pc_stack_len++] = pos; - return 0; -} - -static __exception int compute_stack_size (JSContext *ctx, JSFunctionDef *fd, int *pstack_size) { - StackSizeState s_s, *s = &s_s; - int i, diff, n_pop, pos_next, stack_len, pos, op, catch_pos, catch_level; - const JSOpCode *oi; - const uint8_t *bc_buf; - - bc_buf = fd->byte_code.buf; - s->bc_len = fd->byte_code.size; - /* bc_len > 0 */ - s->stack_level_tab - = pjs_malloc (sizeof (s->stack_level_tab[0]) * s->bc_len); - if (!s->stack_level_tab) return -1; - for (i = 0; i < s->bc_len; i++) - s->stack_level_tab[i] = 0xffff; - s->pc_stack = NULL; - s->catch_pos_tab = pjs_malloc (sizeof (s->catch_pos_tab[0]) * s->bc_len); - if (!s->catch_pos_tab) goto fail; - - s->stack_len_max = 0; - s->pc_stack_len = 0; - s->pc_stack_size = 0; - - /* breadth-first graph exploration */ - if (ss_check (ctx, s, 0, OP_invalid, 0, -1)) goto fail; - - while (s->pc_stack_len > 0) { - pos = s->pc_stack[--s->pc_stack_len]; - stack_len = s->stack_level_tab[pos]; - catch_pos = s->catch_pos_tab[pos]; - op = bc_buf[pos]; - if (op == 0 || op >= OP_COUNT) { - JS_ThrowInternalError (ctx, "invalid opcode (op=%d, pc=%d)", op, pos); - goto fail; - } - oi = &short_opcode_info (op); -#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 64) - printf ("%5d: %10s %5d %5d\n", pos, oi->name, stack_len, catch_pos); -#endif - pos_next = pos + oi->size; - if (pos_next > s->bc_len) { - JS_ThrowInternalError (ctx, "bytecode buffer overflow (op=%d, pc=%d)", op, pos); - goto fail; - } - n_pop = oi->n_pop; - /* call pops a variable number of arguments */ - if (oi->fmt == OP_FMT_npop || oi->fmt == OP_FMT_npop_u16) { - n_pop += get_u16 (bc_buf + pos + 1); - } else { -#if SHORT_OPCODES - if (oi->fmt == OP_FMT_npopx) { n_pop += op - OP_call0; } -#endif - } - - if (stack_len < n_pop) { - JS_ThrowInternalError (ctx, "stack underflow (op=%d, pc=%d)", op, pos); - goto fail; - } - stack_len += oi->n_push - n_pop; - if (stack_len > s->stack_len_max) { - s->stack_len_max = stack_len; - if (s->stack_len_max > JS_STACK_SIZE_MAX) { - JS_ThrowInternalError (ctx, "stack overflow (op=%d, pc=%d)", op, pos); - goto fail; - } - } - switch (op) { - case OP_tail_call: - case OP_tail_call_method: - case OP_return: - case OP_return_undef: - case OP_throw: - case OP_throw_error: - case OP_ret: - goto done_insn; - case OP_goto: - diff = get_u32 (bc_buf + pos + 1); - pos_next = pos + 1 + diff; - break; -#if SHORT_OPCODES - case OP_goto16: - diff = (int16_t)get_u16 (bc_buf + pos + 1); - pos_next = pos + 1 + diff; - break; - case OP_goto8: - diff = (int8_t)bc_buf[pos + 1]; - pos_next = pos + 1 + diff; - break; - case OP_if_true8: - case OP_if_false8: - diff = (int8_t)bc_buf[pos + 1]; - if (ss_check (ctx, s, pos + 1 + diff, op, stack_len, catch_pos)) - goto fail; - break; -#endif - case OP_if_true: - case OP_if_false: - diff = get_u32 (bc_buf + pos + 1); - if (ss_check (ctx, s, pos + 1 + diff, op, stack_len, catch_pos)) - goto fail; - break; - case OP_gosub: - diff = get_u32 (bc_buf + pos + 1); - if (ss_check (ctx, s, pos + 1 + diff, op, stack_len + 1, catch_pos)) - goto fail; - break; - case OP_catch: - diff = get_u32 (bc_buf + pos + 1); - if (ss_check (ctx, s, pos + 1 + diff, op, stack_len, catch_pos)) - goto fail; - catch_pos = pos; - break; - /* we assume the catch offset entry is only removed with - some op codes */ - case OP_drop: - catch_level = stack_len; - goto check_catch; - case OP_nip: - catch_level = stack_len - 1; - goto check_catch; - case OP_nip1: - catch_level = stack_len - 1; - check_catch: - if (catch_pos >= 0) { - int level; - level = s->stack_level_tab[catch_pos]; - /* catch_level = stack_level before op_catch is executed ? */ - if (catch_level == level) { catch_pos = s->catch_pos_tab[catch_pos]; } - } - break; - case OP_nip_catch: - if (catch_pos < 0) { - JS_ThrowInternalError (ctx, "nip_catch: no catch op (pc=%d)", pos); - goto fail; - } - stack_len = s->stack_level_tab[catch_pos]; - stack_len++; /* no stack overflow is possible by construction */ - catch_pos = s->catch_pos_tab[catch_pos]; - break; - default: - break; - } - if (ss_check (ctx, s, pos_next, op, stack_len, catch_pos)) goto fail; - done_insn:; - } - pjs_free (s->pc_stack); - pjs_free (s->catch_pos_tab); - pjs_free (s->stack_level_tab); - *pstack_size = s->stack_len_max; - return 0; -fail: - pjs_free (s->pc_stack); - pjs_free (s->catch_pos_tab); - pjs_free (s->stack_level_tab); - *pstack_size = 0; - return -1; -} - -/* create a function object from a function definition. The function - definition is freed. All the child functions are also created. It - must be done this way to resolve all the variables. */ -static JSValue js_create_function (JSContext *ctx, JSFunctionDef *fd) { - JSValue func_obj; - JSFunctionBytecode *b; - struct list_head *el, *el1; - int stack_size, scope, idx; - int function_size, byte_code_offset, cpool_offset; - int closure_var_offset, vardefs_offset; - - /* recompute scope linkage */ - for (scope = 0; scope < fd->scope_count; scope++) { - fd->scopes[scope].first = -1; - } - if (fd->has_parameter_expressions) { - /* special end of variable list marker for the argument scope */ - fd->scopes[ARG_SCOPE_INDEX].first = ARG_SCOPE_END; - } - for (idx = 0; idx < fd->var_count; idx++) { - JSVarDef *vd = &fd->vars[idx]; - vd->scope_next = fd->scopes[vd->scope_level].first; - fd->scopes[vd->scope_level].first = idx; - } - for (scope = 2; scope < fd->scope_count; scope++) { - JSVarScope *sd = &fd->scopes[scope]; - if (sd->first < 0) sd->first = fd->scopes[sd->parent].first; - } - for (idx = 0; idx < fd->var_count; idx++) { - JSVarDef *vd = &fd->vars[idx]; - if (vd->scope_next < 0 && vd->scope_level > 1) { - scope = fd->scopes[vd->scope_level].parent; - vd->scope_next = fd->scopes[scope].first; - } - } - - /* first create all the child functions */ - list_for_each_safe (el, el1, &fd->child_list) { - JSFunctionDef *fd1; - int cpool_idx; - - fd1 = list_entry (el, JSFunctionDef, link); - cpool_idx = fd1->parent_cpool_idx; - func_obj = js_create_function (ctx, fd1); - if (JS_IsException (func_obj)) goto fail; - /* save it in the constant pool */ - assert (cpool_idx >= 0); - fd->cpool[cpool_idx] = func_obj; - } - -#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 4) - if (!fd->strip_debug) { - printf ("pass 1\n"); - dump_byte_code (ctx, 1, fd->byte_code.buf, fd->byte_code.size, fd->args, fd->arg_count, fd->vars, fd->var_count, fd->closure_var, fd->closure_var_count, fd->cpool, fd->cpool_count, fd->source, fd->label_slots, NULL); - printf ("\n"); - } -#endif - - if (resolve_variables (ctx, fd)) goto fail; - -#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 2) - if (!fd->strip_debug) { - printf ("pass 2\n"); - dump_byte_code (ctx, 2, fd->byte_code.buf, fd->byte_code.size, fd->args, fd->arg_count, fd->vars, fd->var_count, fd->closure_var, fd->closure_var_count, fd->cpool, fd->cpool_count, fd->source, fd->label_slots, NULL); - printf ("\n"); - } -#endif - - if (resolve_labels (ctx, fd)) goto fail; - - if (compute_stack_size (ctx, fd, &stack_size) < 0) goto fail; - - if (fd->strip_debug) { - function_size = offsetof (JSFunctionBytecode, debug); - } else { - function_size = sizeof (*b); - } - cpool_offset = function_size; - function_size += fd->cpool_count * sizeof (*fd->cpool); - vardefs_offset = function_size; - if (!fd->strip_debug) { - function_size += (fd->arg_count + fd->var_count) * sizeof (*b->vardefs); - } - closure_var_offset = function_size; - function_size += fd->closure_var_count * sizeof (*fd->closure_var); - byte_code_offset = function_size; - function_size += fd->byte_code.size; - - /* Bytecode is immutable - allocate outside GC heap */ - b = pjs_mallocz (function_size); - if (!b) goto fail; - b->header = objhdr_make (0, OBJ_CODE, false, false, false, false); - - b->byte_code_buf = (void *)((uint8_t *)b + byte_code_offset); - b->byte_code_len = fd->byte_code.size; - memcpy (b->byte_code_buf, fd->byte_code.buf, fd->byte_code.size); - js_free (ctx, fd->byte_code.buf); - fd->byte_code.buf = NULL; - - b->func_name = fd->func_name; - if (fd->arg_count + fd->var_count > 0) { - if (fd->strip_debug) { - /* Strip variable definitions not needed at runtime */ - int i; - for (i = 0; i < fd->var_count; i++) { - } - for (i = 0; i < fd->arg_count; i++) { - } - for (i = 0; i < fd->closure_var_count; i++) { - fd->closure_var[i].var_name = JS_NULL; - } - } else { - b->vardefs = (void *)((uint8_t *)b + vardefs_offset); - memcpy_no_ub (b->vardefs, fd->args, fd->arg_count * sizeof (fd->args[0])); - memcpy_no_ub (b->vardefs + fd->arg_count, fd->vars, fd->var_count * sizeof (fd->vars[0])); - } - b->var_count = fd->var_count; - b->arg_count = fd->arg_count; - b->defined_arg_count = fd->defined_arg_count; - js_free (ctx, fd->args); - js_free (ctx, fd->vars); - } - b->cpool_count = fd->cpool_count; - if (b->cpool_count) { - b->cpool = (void *)((uint8_t *)b + cpool_offset); - memcpy (b->cpool, fd->cpool, b->cpool_count * sizeof (*b->cpool)); - } - js_free (ctx, fd->cpool); - fd->cpool = NULL; - - b->stack_size = stack_size; - - if (fd->strip_debug) { - dbuf_free (&fd->pc2line); // probably useless - } else { - /* XXX: source and pc2line info should be packed at the end of the - JSFunctionBytecode structure, avoiding allocation overhead - */ - b->has_debug = 1; - b->debug.filename = fd->filename; - - // DynBuf pc2line; - // compute_pc2line_info(fd, &pc2line); - // js_free(ctx, fd->line_number_slots) - /* pc2line.buf is allocated by dbuf (system realloc), so just use it directly */ - b->debug.pc2line_buf = fd->pc2line.buf; - fd->pc2line.buf = NULL; /* Transfer ownership */ - b->debug.pc2line_len = fd->pc2line.size; - b->debug.source = fd->source; - b->debug.source_len = fd->source_len; - } - if (fd->scopes != fd->def_scope_array) pjs_free (fd->scopes); - - b->closure_var_count = fd->closure_var_count; - if (b->closure_var_count) { - b->closure_var = (void *)((uint8_t *)b + closure_var_offset); - memcpy (b->closure_var, fd->closure_var, b->closure_var_count * sizeof (*b->closure_var)); - } - pjs_free (fd->closure_var); - fd->closure_var = NULL; - - b->has_prototype = fd->has_prototype; - b->has_simple_parameter_list = fd->has_simple_parameter_list; - b->js_mode = fd->js_mode; - - /* IC removed - shapes no longer used */ - - b->is_direct_or_indirect_eval = FALSE; - -#if defined(DUMP_BYTECODE) && (DUMP_BYTECODE & 1) - if (!fd->strip_debug) { js_dump_function_bytecode (ctx, b); } -#endif - - if (fd->parent) { - /* remove from parent list */ - list_del (&fd->link); - } - - js_free (ctx, fd); - return JS_MKPTR (b); -fail: - js_free_function_def (ctx, fd); - return JS_EXCEPTION; -} - -static __exception int js_parse_directives (JSParseState *s) { - char str[20]; - JSParsePos pos; - BOOL has_semi; - - if (s->token.val != TOK_STRING) return 0; - - js_parse_get_pos (s, &pos); - - while (s->token.val == TOK_STRING) { - /* Copy actual source string representation */ - snprintf (str, sizeof str, "%.*s", (int)(s->buf_ptr - s->token.ptr - 2), s->token.ptr + 1); - - if (next_token (s)) return -1; - - has_semi = FALSE; - switch (s->token.val) { - case ';': - if (next_token (s)) return -1; - has_semi = TRUE; - break; - case '}': - case TOK_EOF: - has_semi = TRUE; - break; - case TOK_NUMBER: - case TOK_STRING: - case TOK_TEMPLATE: - case TOK_IDENT: - case TOK_REGEXP: - case TOK_DEC: - case TOK_INC: - case TOK_NULL: - case TOK_FALSE: - case TOK_TRUE: - case TOK_IF: - case TOK_RETURN: - case TOK_VAR: - case TOK_THIS: - case TOK_DELETE: - case TOK_DO: - case TOK_WHILE: - case TOK_FOR: - case TOK_DISRUPT: - case TOK_FUNCTION: - case TOK_DEBUGGER: - case TOK_DEF: - case TOK_ENUM: - case TOK_EXPORT: - case TOK_IMPORT: - case TOK_INTERFACE: - /* automatic insertion of ';' */ - if (s->got_lf) has_semi = TRUE; - break; - default: - break; - } - if (!has_semi) break; - } - return js_parse_seek_token (s, &pos); -} - -/* return TRUE if the keyword is forbidden only in strict mode */ -static BOOL is_strict_future_keyword (JSValue name) { - BOOL is_strict_reserved = FALSE; - int token = js_keyword_lookup (name, &is_strict_reserved); - return (token >= 0 && is_strict_reserved); -} - -static int js_parse_function_check_names (JSParseState *s, JSFunctionDef *fd, JSValue func_name) { - JSValue name; - int i, idx; - - if (!fd->has_simple_parameter_list && fd->has_use_strict) { - return js_parse_error (s, "\"use strict\" not allowed in function with " - "default or destructuring parameter"); - } - if (js_key_equal (func_name, JS_KEY_eval) - || is_strict_future_keyword (func_name)) { - return js_parse_error (s, "invalid function name in strict code"); - } - for (idx = 0; idx < fd->arg_count; idx++) { - name = fd->args[idx].var_name; - - if (js_key_equal (name, JS_KEY_eval) || is_strict_future_keyword (name)) { - return js_parse_error (s, "invalid argument name in strict code"); - } - } - - for (idx = 0; idx < fd->arg_count; idx++) { - name = fd->args[idx].var_name; - if (!JS_IsNull (name)) { - for (i = 0; i < idx; i++) { - if (js_key_equal (fd->args[i].var_name, name)) goto duplicate; - } - /* Check if argument name duplicates a destructuring parameter */ - /* XXX: should have a flag for such variables */ - for (i = 0; i < fd->var_count; i++) { - if (js_key_equal (fd->vars[i].var_name, name) - && fd->vars[i].scope_level == 0) - goto duplicate; - } - } - } - return 0; - -duplicate: - return js_parse_error ( - s, "duplicate argument names not allowed in this context"); -} - -/* func_name must be JS_NULL for JS_PARSE_FUNC_STATEMENT and - JS_PARSE_FUNC_EXPR, JS_PARSE_FUNC_ARROW and JS_PARSE_FUNC_VAR */ -static __exception int js_parse_function_decl2 (JSParseState *s, - JSParseFunctionEnum func_type, - JSValue func_name, - const uint8_t *ptr, - JSFunctionDef **pfd) { - JSContext *ctx = s->ctx; - JSFunctionDef *fd = s->cur_func; - BOOL is_expr; - int func_idx, lexical_func_idx = -1; - BOOL create_func_var = FALSE; - - is_expr - = (func_type != JS_PARSE_FUNC_STATEMENT && func_type != JS_PARSE_FUNC_VAR); - - if (func_type == JS_PARSE_FUNC_STATEMENT || func_type == JS_PARSE_FUNC_VAR - || func_type == JS_PARSE_FUNC_EXPR) { - if (next_token (s)) return -1; - - if (s->token.val == TOK_IDENT) { - if (s->token.u.ident.is_reserved) - return js_parse_error_reserved_identifier (s); - func_name = s->token.u.ident.str; - if (next_token (s)) { - return -1; - } - } else { - if (func_type != JS_PARSE_FUNC_EXPR) - return js_parse_error (s, "function name expected"); - } - - } else if (func_type != JS_PARSE_FUNC_ARROW) { - /* func_name is already set, nothing to do */ - } - - if (func_type == JS_PARSE_FUNC_VAR) { - /* Create the lexical name here so that the function closure - contains it */ - /* Always create a lexical name, fail if at the same scope as - existing name */ - /* Lexical variable will be initialized upon entering scope */ - lexical_func_idx - = define_var (s, fd, func_name, JS_VAR_DEF_FUNCTION_DECL); - if (lexical_func_idx < 0) { - return -1; - } - } - - fd = js_new_function_def (ctx, fd, is_expr, s->filename, ptr, &s->get_line_col_cache); - if (!fd) { - return -1; - } - if (pfd) *pfd = fd; - s->cur_func = fd; - fd->func_name = func_name; - /* XXX: test !fd->is_generator is always false */ - fd->has_prototype - = (func_type == JS_PARSE_FUNC_STATEMENT || func_type == JS_PARSE_FUNC_VAR - || func_type == JS_PARSE_FUNC_EXPR); - - fd->has_this_binding = (func_type != JS_PARSE_FUNC_ARROW - && func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT); - - /* fd->in_function_body == FALSE prevents yield/await during the parsing - of the arguments in generator/async functions. They are parsed as - regular identifiers for other function kinds. */ - fd->func_type = func_type; - - /* parse arguments */ - fd->has_simple_parameter_list = TRUE; - fd->has_parameter_expressions = FALSE; - if (func_type == JS_PARSE_FUNC_ARROW && s->token.val == TOK_IDENT) { - JSValue name; - if (s->token.u.ident.is_reserved) { - js_parse_error_reserved_identifier (s); - goto fail; - } - name = s->token.u.ident.str; - if (add_arg (ctx, fd, name) < 0) goto fail; - } else if (func_type != JS_PARSE_FUNC_CLASS_STATIC_INIT) { - if (s->token.val == '(') { - int skip_bits; - /* if there is an '=' inside the parameter list, we - consider there is a parameter expression inside */ - js_parse_skip_parens_token (s, &skip_bits, FALSE); - if (skip_bits & SKIP_HAS_ASSIGNMENT) - fd->has_parameter_expressions = TRUE; - if (next_token (s)) goto fail; - } else { - if (js_parse_expect (s, '(')) goto fail; - } - - if (fd->has_parameter_expressions) { - fd->scope_level = -1; /* force no parent scope */ - if (push_scope (s) < 0) return -1; - } - - while (s->token.val != ')') { - JSValue name; - int idx, has_initializer; - - if (s->token.val == '[' || s->token.val == '{') { - fd->has_simple_parameter_list = FALSE; - /* unnamed arg for destructuring */ - idx = add_arg (ctx, fd, JS_NULL); - emit_op (s, OP_get_arg); - emit_u16 (s, idx); - has_initializer - = js_parse_destructuring_element (s, TOK_VAR, 1, TRUE, TRUE, FALSE); - if (has_initializer < 0) goto fail; - } else if (s->token.val == TOK_IDENT) { - if (s->token.u.ident.is_reserved) { - js_parse_error_reserved_identifier (s); - goto fail; - } - name = s->token.u.ident.str; - if (fd->has_parameter_expressions) { - if (js_parse_check_duplicate_parameter (s, name)) goto fail; - if (define_var (s, fd, name, JS_VAR_DEF_LET) < 0) goto fail; - } - idx = add_arg (ctx, fd, name); - if (idx < 0) goto fail; - if (next_token (s)) goto fail; - if (s->token.val == '=') { - int label; - - fd->has_simple_parameter_list = FALSE; - - if (next_token (s)) goto fail; - - label = new_label (s); - emit_op (s, OP_get_arg); - emit_u16 (s, idx); - emit_op (s, OP_dup); - emit_op (s, OP_null); - emit_op (s, OP_strict_eq); - emit_goto (s, OP_if_false, label); - emit_op (s, OP_drop); - if (js_parse_assign_expr (s)) goto fail; - set_object_name (s, name); - emit_op (s, OP_dup); - emit_op (s, OP_put_arg); - emit_u16 (s, idx); - emit_label (s, label); - emit_op (s, OP_scope_put_var_init); - emit_key (s, name); - emit_u16 (s, fd->scope_level); - } else { - if (fd->has_parameter_expressions) { - /* copy the argument to the argument scope */ - emit_op (s, OP_get_arg); - emit_u16 (s, idx); - emit_op (s, OP_scope_put_var_init); - emit_key (s, name); - emit_u16 (s, fd->scope_level); - } - } - } else { - js_parse_error (s, "missing formal parameter"); - goto fail; - } - if (s->token.val == ')') break; - if (js_parse_expect (s, ',')) goto fail; - } - if ((func_type == JS_PARSE_FUNC_GETTER && fd->arg_count != 0) - || (func_type == JS_PARSE_FUNC_SETTER && fd->arg_count != 1)) { - js_parse_error (s, "invalid number of arguments for getter or setter"); - goto fail; - } - if (fd->arg_count > 4) { - js_parse_error (s, "functions cannot have more than 4 parameters"); - goto fail; - } - } - /* Explicit arity: defined_arg_count == arg_count always */ - fd->defined_arg_count = fd->arg_count; - - if (fd->has_parameter_expressions) { - int idx; - - /* Copy the variables in the argument scope to the variable - scope (see FunctionDeclarationInstantiation() in spec). The - normal arguments are already present, so no need to copy - them. */ - idx = fd->scopes[fd->scope_level].first; - while (idx >= 0) { - JSVarDef *vd = &fd->vars[idx]; - if (vd->scope_level != fd->scope_level) break; - if (find_var (ctx, fd, vd->var_name) < 0) { - if (add_var (ctx, fd, vd->var_name) < 0) goto fail; - vd = &fd->vars[idx]; /* fd->vars may have been reallocated */ - emit_op (s, OP_scope_get_var); - emit_key (s, vd->var_name); - emit_u16 (s, fd->scope_level); - emit_op (s, OP_scope_put_var); - emit_key (s, vd->var_name); - emit_u16 (s, 0); - } - idx = vd->scope_next; - } - - /* the argument scope has no parent, hence we don't use pop_scope(s) */ - emit_op (s, OP_leave_scope); - emit_u16 (s, fd->scope_level); - - /* set the variable scope as the current scope */ - fd->scope_level = 0; - fd->scope_first = fd->scopes[fd->scope_level].first; - } - - if (next_token (s)) goto fail; - - /* in generators, yield expression is forbidden during the parsing - of the arguments */ - fd->in_function_body = TRUE; - push_scope (s); /* enter body scope */ - fd->body_scope = fd->scope_level; - - if (s->token.val == TOK_ARROW && func_type == JS_PARSE_FUNC_ARROW) { - if (next_token (s)) goto fail; - - if (s->token.val != '{') { - if (js_parse_function_check_names (s, fd, func_name)) goto fail; - - if (js_parse_assign_expr (s)) goto fail; - - emit_op (s, OP_return); - - if (!fd->strip_source) { - /* save the function source code */ - /* the end of the function source code is after the last - token of the function source stored into s->last_ptr */ - fd->source_len = s->last_ptr - ptr; - fd->source = strndup ((const char *)ptr, fd->source_len); - if (!fd->source) goto fail; - } - goto done; - } - } - - if (js_parse_expect (s, '{')) goto fail; - - if (js_parse_directives (s)) goto fail; - - /* in strict_mode, check function and argument names */ - if (js_parse_function_check_names (s, fd, func_name)) goto fail; - - while (s->token.val != '}') { - if (js_parse_source_element (s)) goto fail; - } - if (!fd->strip_source) { - /* save the function source code */ - fd->source_len = s->buf_ptr - ptr; - fd->source = strndup ((const char *)ptr, fd->source_len); - if (!fd->source) goto fail; - } - - if (next_token (s)) { - /* consume the '}' */ - goto fail; - } - - /* in case there is no return, add one */ - if (js_is_live_code (s)) { emit_return (s, FALSE); } -done: - s->cur_func = fd->parent; - - /* Reparse identifiers after the function is terminated so that - the token is parsed in the englobing function. It could be done - by just using next_token() here for normal functions, but it is - necessary for arrow functions with an expression body. */ - reparse_ident_token (s); - - /* create the function object */ - { - int idx; - JSValue func_name_val = fd->func_name; - - /* the real object will be set at the end of the compilation */ - idx = cpool_add (s, JS_NULL); - fd->parent_cpool_idx = idx; - - if (is_expr) { - /* OP_fclosure creates the function object from the bytecode - and adds the scope information */ - emit_op (s, OP_fclosure); - emit_u32 (s, idx); - if (JS_IsNull (func_name_val)) { - emit_op (s, OP_set_name); - emit_key (s, JS_NULL); - } - } else if (func_type == JS_PARSE_FUNC_VAR) { - emit_op (s, OP_fclosure); - emit_u32 (s, idx); - if (create_func_var) { - if (s->cur_func->is_global_var) { - JSGlobalVar *hf; - /* the global variable must be defined at the start of the - function */ - hf = add_global_var (ctx, s->cur_func, func_name_val); - if (!hf) goto fail; - /* it is considered as defined at the top level - (needed for annex B.3.3.4 and B.3.3.5 - checks) */ - hf->scope_level = 0; - hf->force_init = 1; - /* store directly into global var, bypass lexical scope */ - emit_op (s, OP_dup); - emit_op (s, OP_scope_put_var); - emit_key (s, func_name_val); - emit_u16 (s, 0); - } else { - /* do not call define_var to bypass lexical scope check */ - func_idx = find_var (ctx, s->cur_func, func_name_val); - if (func_idx < 0) { - func_idx = add_var (ctx, s->cur_func, func_name_val); - if (func_idx < 0) goto fail; - } - /* store directly into local var, bypass lexical catch scope */ - emit_op (s, OP_dup); - emit_op (s, OP_scope_put_var); - emit_key (s, func_name_val); - emit_u16 (s, 0); - } - } - if (lexical_func_idx >= 0) { - /* lexical variable will be initialized upon entering scope */ - s->cur_func->vars[lexical_func_idx].func_pool_idx = idx; - emit_op (s, OP_drop); - } else { - /* store function object into its lexical name */ - /* XXX: could use OP_put_loc directly */ - emit_op (s, OP_scope_put_var_init); - emit_key (s, func_name_val); - emit_u16 (s, s->cur_func->scope_level); - } - } else { - if (!s->cur_func->is_global_var) { - int var_idx - = define_var (s, s->cur_func, func_name_val, JS_VAR_DEF_VAR); - - if (var_idx < 0) goto fail; - /* the variable will be assigned at the top of the function */ - if (var_idx & ARGUMENT_VAR_OFFSET) { - s->cur_func->args[var_idx - ARGUMENT_VAR_OFFSET].func_pool_idx = idx; - } else { - s->cur_func->vars[var_idx].func_pool_idx = idx; - } - } else { - JSValue func_var_name; - JSGlobalVar *hf; - if (JS_IsNull (func_name_val)) - func_var_name = JS_KEY_STR (ctx, "default"); /* export default */ - else - func_var_name = func_name_val; - /* the variable will be assigned at the top of the function */ - hf = add_global_var (ctx, s->cur_func, func_var_name); - if (!hf) goto fail; - hf->cpool_idx = idx; - } - } - } - return 0; -fail: - s->cur_func = fd->parent; - js_free_function_def (ctx, fd); - if (pfd) *pfd = NULL; - return -1; -} - -static __exception int js_parse_function_decl (JSParseState *s, - JSParseFunctionEnum func_type, - JSValue func_name, - const uint8_t *ptr) { - return js_parse_function_decl2 (s, func_type, func_name, ptr, NULL); -} - -static __exception int js_parse_program (JSParseState *s) { - JSFunctionDef *fd = s->cur_func; - if (next_token (s)) return -1; - - if (js_parse_directives (s)) return -1; - - /* Global object is immutable - all variables are locals of the eval function */ - fd->is_global_var = FALSE; - - while (s->token.val != TOK_EOF) { - if (js_parse_source_element (s)) return -1; - } - - /* For eval-like semantics: if the last statement was an expression, - return its value instead of null. Expression statements emit OP_drop - to discard the value - remove that and emit OP_return instead. */ - if (get_prev_opcode (fd) == OP_drop) { - fd->byte_code.size = fd->last_opcode_pos; - fd->last_opcode_pos = -1; - emit_return (s, TRUE); - } else { - emit_return (s, FALSE); - } - - return 0; -} - -static void js_parse_init (JSContext *ctx, JSParseState *s, const char *input, size_t input_len, const char *filename) { - memset (s, 0, sizeof (*s)); - s->ctx = ctx; - s->filename = filename; - s->buf_start = s->buf_ptr = (const uint8_t *)input; - s->buf_end = s->buf_ptr + input_len; - s->token.val = ' '; - s->token.ptr = s->buf_ptr; - - s->get_line_col_cache.ptr = s->buf_start; - s->get_line_col_cache.buf_start = s->buf_start; - s->get_line_col_cache.line_num = 0; - s->get_line_col_cache.col_num = 0; -} - -/* Forward declaration */ -static JSValue __JS_CompileInternal (JSContext *ctx, const char *input, size_t input_len, const char *filename); - -/* Compile source code to bytecode without executing. - Returns unlinked bytecode on success, JS_EXCEPTION on error. - 'input' must be zero terminated i.e. input[input_len] = '\0'. */ -JSValue JS_Compile (JSContext *ctx, const char *input, size_t input_len, - const char *filename) { - return __JS_CompileInternal (ctx, input, input_len, filename); -} - -/* Link compiled bytecode with environment and execute. - The env should be a stoned record (or null for no env). - Variables resolve: env first, then global intrinsics. */ -JSValue JS_Integrate (JSContext *ctx, JSValue fun_obj, JSValue env) { - JSValue ret_val; - uint32_t tag; - JSGCRef env_ref, fun_ref; - JSFunctionBytecode *tpl, *linked_bc; - - tag = JS_VALUE_GET_TAG (fun_obj); - /* JSFunctionBytecode uses OBJ_CODE type with JS_TAG_PTR */ - if (tag != JS_TAG_PTR || objhdr_type (*(objhdr_t *)JS_VALUE_GET_PTR (fun_obj)) != OBJ_CODE) { - return JS_ThrowTypeError (ctx, "bytecode function expected"); - } - - /* Protect env and fun_obj from GC during linking */ - JS_AddGCRef (ctx, &env_ref); - env_ref.val = env; - JS_AddGCRef (ctx, &fun_ref); - fun_ref.val = fun_obj; - - /* Link bytecode with environment */ - tpl = (JSFunctionBytecode *)JS_VALUE_GET_PTR (fun_ref.val); - linked_bc = js_link_bytecode (ctx, tpl, env_ref.val); - if (!linked_bc) { - JS_DeleteGCRef (ctx, &fun_ref); - JS_DeleteGCRef (ctx, &env_ref); - return JS_EXCEPTION; - } - JSValue linked = JS_MKPTR (linked_bc); - - /* Update env from GC ref (may have moved) */ - env = env_ref.val; - JS_DeleteGCRef (ctx, &fun_ref); - JS_DeleteGCRef (ctx, &env_ref); - - /* Create closure and set env_record on the function object */ - linked = js_closure (ctx, linked, NULL); - if (JS_IsException (linked)) { - return JS_EXCEPTION; - } - - /* Store env_record on the function for OP_get_env_slot access */ - JSFunction *f = JS_VALUE_GET_FUNCTION (linked); - f->u.func.env_record = env; - - ret_val = JS_Call (ctx, linked, ctx->global_obj, 0, NULL); - - return ret_val; -} - -/* Compile source to bytecode. - 'input' must be zero terminated i.e. input[input_len] = '\0'. */ -static JSValue __JS_CompileInternal (JSContext *ctx, const char *input, size_t input_len, const char *filename) { - JSParseState s1, *s = &s1; - int err; - JSValue fun_obj; - JSFunctionDef *fd; - - js_parse_init (ctx, s, input, input_len, filename); - - fd = js_new_function_def (ctx, NULL, FALSE, filename, s->buf_start, &s->get_line_col_cache); - if (!fd) goto fail1; - s->cur_func = fd; - ctx->current_parse_fd = fd; /* Set GC root for parser's cpool */ - fd->has_this_binding = TRUE; - fd->js_mode = 0; - fd->func_name = JS_KEY__eval_; - - push_scope (s); /* body scope */ - fd->body_scope = fd->scope_level; - - err = js_parse_program (s); - if (err) { - free_token (s, &s->token); - ctx->current_parse_fd = NULL; /* Clear GC root before freeing */ - js_free_function_def (ctx, fd); - goto fail1; - } - - /* create the function object and all the enclosed functions */ - ctx->current_parse_fd = NULL; /* Clear GC root - fd ownership transfers to js_create_function */ - fun_obj = js_create_function (ctx, fd); - if (JS_IsException (fun_obj)) goto fail1; - return fun_obj; -fail1: - return JS_EXCEPTION; -} - -/*******************************************************************/ -/* object list */ - -typedef struct { - JSRecord *obj; - uint32_t hash_next; /* -1 if no next entry */ -} JSRecordListEntry; - -/* XXX: reuse it to optimize weak references */ -typedef struct { - JSRecordListEntry *object_tab; - int object_count; - int object_size; - uint32_t *hash_table; - uint32_t hash_size; -} JSRecordList; - -static void js_object_list_init (JSRecordList *s) { - memset (s, 0, sizeof (*s)); -} - -static uint32_t js_object_list_get_hash (JSRecord *p, uint32_t hash_size) { - return ((uintptr_t)p * 3163) & (hash_size - 1); -} - -static int js_object_list_resize_hash (JSContext *ctx, JSRecordList *s, uint32_t new_hash_size) { - JSRecordListEntry *e; - uint32_t i, h, *new_hash_table; - - new_hash_table = js_malloc (ctx, sizeof (new_hash_table[0]) * new_hash_size); - if (!new_hash_table) return -1; - js_free (ctx, s->hash_table); - s->hash_table = new_hash_table; - s->hash_size = new_hash_size; - - for (i = 0; i < s->hash_size; i++) { - s->hash_table[i] = -1; - } - for (i = 0; i < s->object_count; i++) { - e = &s->object_tab[i]; - h = js_object_list_get_hash (e->obj, s->hash_size); - e->hash_next = s->hash_table[h]; - s->hash_table[h] = i; - } - return 0; -} - -/* the reference count of 'obj' is not modified. Return 0 if OK, -1 if - memory error */ -static int js_object_list_add (JSContext *ctx, JSRecordList *s, JSRecord *obj) { - JSRecordListEntry *e; - uint32_t h, new_hash_size; - - if (js_resize_array (ctx, (void *)&s->object_tab, sizeof (s->object_tab[0]), &s->object_size, s->object_count + 1)) - return -1; - if (unlikely ((s->object_count + 1) >= s->hash_size)) { - new_hash_size = max_uint32 (s->hash_size, 4); - while (new_hash_size <= s->object_count) - new_hash_size *= 2; - if (js_object_list_resize_hash (ctx, s, new_hash_size)) return -1; - } - e = &s->object_tab[s->object_count++]; - h = js_object_list_get_hash (obj, s->hash_size); - e->obj = obj; - e->hash_next = s->hash_table[h]; - s->hash_table[h] = s->object_count - 1; - return 0; -} - -/* return -1 if not present or the object index */ -static int js_object_list_find (JSContext *ctx, JSRecordList *s, JSRecord *obj) { - JSRecordListEntry *e; - uint32_t h, p; - - /* must test empty size because there is no hash table */ - if (s->object_count == 0) return -1; - h = js_object_list_get_hash (obj, s->hash_size); - p = s->hash_table[h]; - while (p != -1) { - e = &s->object_tab[p]; - if (e->obj == obj) return p; - p = e->hash_next; - } - return -1; -} - -static void js_object_list_end (JSContext *ctx, JSRecordList *s) { - js_free (ctx, s->object_tab); - js_free (ctx, s->hash_table); -} - -/*******************************************************************/ -/* binary object writer & reader */ - -typedef enum BCTagEnum { - BC_TAG_NULL = 1, - BC_TAG_BOOL_FALSE, - BC_TAG_BOOL_TRUE, - BC_TAG_INT32, - BC_TAG_FLOAT64, - BC_TAG_STRING, - BC_TAG_OBJECT, - BC_TAG_ARRAY, - BC_TAG_TEMPLATE_OBJECT, - BC_TAG_FUNCTION_BYTECODE, - BC_TAG_MODULE, - BC_TAG_OBJECT_VALUE, - BC_TAG_OBJECT_REFERENCE, -} BCTagEnum; - -#define BC_VERSION 6 - -typedef struct BCWriterState { - JSContext *ctx; - DynBuf dbuf; - BOOL allow_bytecode : 8; - BOOL allow_sab : 8; - BOOL allow_reference : 8; - uint8_t **sab_tab; - int sab_tab_len; - int sab_tab_size; - /* list of referenced objects (used if allow_reference = TRUE) */ - JSRecordList object_list; -} BCWriterState; - -#ifdef DUMP_READ_OBJECT -static const char *const bc_tag_str[] = { - "invalid", - "null", - "false", - "true", - "int32", - "float64", - "string", - "object", - "array", - "template", - "function", - "module", - "ObjectReference", -}; -#endif - -static inline BOOL is_be (void) { - union { - uint16_t a; - uint8_t b; - } u = { 0x100 }; - return u.b; -} - -static void bc_put_u8 (BCWriterState *s, uint8_t v) { - dbuf_putc (&s->dbuf, v); -} - -static void bc_put_u16 (BCWriterState *s, uint16_t v) { - if (is_be ()) v = bswap16 (v); - dbuf_put_u16 (&s->dbuf, v); -} - -static __maybe_unused void bc_put_u32 (BCWriterState *s, uint32_t v) { - if (is_be ()) v = bswap32 (v); - dbuf_put_u32 (&s->dbuf, v); -} - -static void bc_put_u64 (BCWriterState *s, uint64_t v) { - if (is_be ()) v = bswap64 (v); - dbuf_put (&s->dbuf, (uint8_t *)&v, sizeof (v)); -} - -static void bc_put_leb128 (BCWriterState *s, uint32_t v) { - dbuf_put_leb128 (&s->dbuf, v); -} - -static void bc_put_sleb128 (BCWriterState *s, int32_t v) { - dbuf_put_sleb128 (&s->dbuf, v); -} - -static void bc_set_flags (uint32_t *pflags, int *pidx, uint32_t val, int n) { - *pflags = *pflags | (val << *pidx); - *pidx += n; -} - -/* Write a JSValue key (text) to bytecode stream */ -static int bc_put_key (BCWriterState *s, JSValue key) { - /* Handle immediate ASCII strings */ - if (MIST_IsImmediateASCII (key)) { - int len = MIST_GetImmediateASCIILen (key); - bc_put_leb128 (s, (uint32_t)len); - for (int i = 0; i < len; i++) { - bc_put_u8 (s, (uint8_t)MIST_GetImmediateASCIIChar (key, i)); - } - return 0; - } - - /* Handle heap strings */ - if (!JS_IsText (key)) { - /* Not a string - write empty */ - bc_put_leb128 (s, 0); - return 0; - } - - JSText *p = JS_VALUE_GET_STRING (key); - /* Write as UTF-8 */ - uint32_t len = (uint32_t)JSText_len (p); - /* Calculate UTF-8 size */ - size_t utf8_size = 0; - for (uint32_t i = 0; i < len; i++) { - uint32_t c = string_get (p, i); - if (c < 0x80) - utf8_size += 1; - else if (c < 0x800) - utf8_size += 2; - else if (c < 0x10000) - utf8_size += 3; - else - utf8_size += 4; - } - bc_put_leb128 (s, (uint32_t)utf8_size); - for (uint32_t i = 0; i < len; i++) { - uint32_t c = string_get (p, i); - if (c < 0x80) { - bc_put_u8 (s, c); - } else if (c < 0x800) { - bc_put_u8 (s, 0xC0 | (c >> 6)); - bc_put_u8 (s, 0x80 | (c & 0x3F)); - } else if (c < 0x10000) { - bc_put_u8 (s, 0xE0 | (c >> 12)); - bc_put_u8 (s, 0x80 | ((c >> 6) & 0x3F)); - bc_put_u8 (s, 0x80 | (c & 0x3F)); - } else { - bc_put_u8 (s, 0xF0 | (c >> 18)); - bc_put_u8 (s, 0x80 | ((c >> 12) & 0x3F)); - bc_put_u8 (s, 0x80 | ((c >> 6) & 0x3F)); - bc_put_u8 (s, 0x80 | (c & 0x3F)); - } - } - return 0; -} - -static void bc_byte_swap (uint8_t *bc_buf, int bc_len) { - int pos, len, op, fmt; - - pos = 0; - while (pos < bc_len) { - op = bc_buf[pos]; - len = short_opcode_info (op).size; - fmt = short_opcode_info (op).fmt; - switch (fmt) { - case OP_FMT_u16: - case OP_FMT_i16: - case OP_FMT_label16: - case OP_FMT_npop: - case OP_FMT_loc: - case OP_FMT_arg: - put_u16 (bc_buf + pos + 1, bswap16 (get_u16 (bc_buf + pos + 1))); - break; - case OP_FMT_i32: - case OP_FMT_u32: - case OP_FMT_const: - case OP_FMT_label: - case OP_FMT_key: - case OP_FMT_key_u8: - put_u32 (bc_buf + pos + 1, bswap32 (get_u32 (bc_buf + pos + 1))); - break; - case OP_FMT_key_u16: - case OP_FMT_label_u16: - put_u32 (bc_buf + pos + 1, bswap32 (get_u32 (bc_buf + pos + 1))); - put_u16 (bc_buf + pos + 1 + 4, bswap16 (get_u16 (bc_buf + pos + 1 + 4))); - break; - case OP_FMT_key_label_u16: - put_u32 (bc_buf + pos + 1, bswap32 (get_u32 (bc_buf + pos + 1))); - put_u32 (bc_buf + pos + 1 + 4, bswap32 (get_u32 (bc_buf + pos + 1 + 4))); - put_u16 (bc_buf + pos + 1 + 4 + 4, - bswap16 (get_u16 (bc_buf + pos + 1 + 4 + 4))); - break; - case OP_FMT_npop_u16: - put_u16 (bc_buf + pos + 1, bswap16 (get_u16 (bc_buf + pos + 1))); - put_u16 (bc_buf + pos + 1 + 2, bswap16 (get_u16 (bc_buf + pos + 1 + 2))); - break; - default: - break; - } - pos += len; - } -} - -static int JS_WriteFunctionBytecode (BCWriterState *s, const uint8_t *bc_buf1, int bc_len) { - int pos, len, op; - uint8_t *bc_buf; - - bc_buf = js_malloc (s->ctx, bc_len); - if (!bc_buf) return -1; - memcpy (bc_buf, bc_buf1, bc_len); - - pos = 0; - while (pos < bc_len) { - op = bc_buf[pos]; - len = short_opcode_info (op).size; - switch (short_opcode_info (op).fmt) { - case OP_FMT_key: - case OP_FMT_key_u8: - case OP_FMT_key_u16: - case OP_FMT_key_label_u16: - /* Key operand is a cpool index; cpool values serialized separately */ - break; - default: - break; - } - pos += len; - } - - if (is_be ()) bc_byte_swap (bc_buf, bc_len); - - dbuf_put (&s->dbuf, bc_buf, bc_len); - - js_free (s->ctx, bc_buf); - return 0; -} - -static void JS_WriteString (BCWriterState *s, JSText *p) { - int i; - int len = (int)JSText_len (p); - /* UTF-32: write length, then each character as 32-bit value */ - bc_put_leb128 (s, (uint32_t)len); - for (i = 0; i < len; i++) { - uint32_t c = string_get (p, i); - bc_put_u32 (s, c); - } -} - -static int JS_WriteObjectRec (BCWriterState *s, JSValue obj); - -static int JS_WriteObjectTag (BCWriterState *s, JSValue obj) { - JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (obj); - uint32_t mask = (uint32_t)objhdr_cap56 (rec->mist_hdr); - uint32_t i, prop_count; - int pass; - - bc_put_u8 (s, BC_TAG_OBJECT); - prop_count = 0; - - /* Two-pass: count then write */ - for (pass = 0; pass < 2; pass++) { - if (pass == 1) bc_put_leb128 (s, prop_count); - for (i = 1; i <= mask; i++) { - JSValue k = rec->slots[i].key; - if (!rec_key_is_empty (k) && !rec_key_is_tomb (k) && JS_IsText (k)) { - if (pass == 0) { - prop_count++; - } else { - /* Write key as JSValue text directly */ - bc_put_key (s, k); - if (JS_WriteObjectRec (s, rec->slots[i].val)) goto fail; - } - } - } - } - return 0; -fail: - return -1; -} - -static int JS_WriteObjectRec (BCWriterState *s, JSValue obj) { - uint32_t tag; - - if (js_check_stack_overflow (s->ctx, 0)) { - JS_ThrowStackOverflow (s->ctx); - return -1; - } - - tag = JS_VALUE_GET_NORM_TAG (obj); - switch (tag) { - case JS_TAG_NULL: - bc_put_u8 (s, BC_TAG_NULL); - break; - case JS_TAG_BOOL: - bc_put_u8 (s, BC_TAG_BOOL_FALSE + JS_VALUE_GET_INT (obj)); - break; - case JS_TAG_INT: - bc_put_u8 (s, BC_TAG_INT32); - bc_put_sleb128 (s, JS_VALUE_GET_INT (obj)); - break; - case JS_TAG_FLOAT64: { - JSFloat64Union u; - bc_put_u8 (s, BC_TAG_FLOAT64); - u.d = JS_VALUE_GET_FLOAT64 (obj); - bc_put_u64 (s, u.u64); - } break; - case JS_TAG_STRING_IMM: { - /* Immediate ASCII string */ - int len = MIST_GetImmediateASCIILen (obj); - char buf[8]; - for (int i = 0; i < len; i++) - buf[i] = MIST_GetImmediateASCIIChar (obj, i); - JSValue tmp = js_new_string8_len (s->ctx, buf, len); - if (JS_IsException (tmp)) goto fail; - JSText *p = JS_VALUE_GET_STRING (tmp); - bc_put_u8 (s, BC_TAG_STRING); - JS_WriteString (s, p); - } break; - case JS_TAG_PTR: - /* Check if this is a heap string */ - if (JS_IsText (obj)) { - JSText *p = JS_VALUE_GET_STRING (obj); - bc_put_u8 (s, BC_TAG_STRING); - JS_WriteString (s, p); - break; - } - /* Check if this is an intrinsic array */ - if (JS_IsArray (obj)) { - JSArray *arr = JS_VALUE_GET_ARRAY (obj); - uint32_t i; - bc_put_u8 (s, BC_TAG_ARRAY); - bc_put_leb128 (s, arr->len); - for (i = 0; i < arr->len; i++) { - if (JS_WriteObjectRec (s, arr->values[i])) goto fail; - } - } else { - JSRecord *p = JS_VALUE_GET_OBJ (obj); - int ret, idx; - - /* Always use object_list for cycle detection */ - idx = js_object_list_find (s->ctx, &s->object_list, p); - if (idx >= 0) { - if (s->allow_reference) { - bc_put_u8 (s, BC_TAG_OBJECT_REFERENCE); - bc_put_leb128 (s, idx); - break; - } else { - JS_ThrowTypeError (s->ctx, "circular reference"); - goto fail; - } - } - if (js_object_list_add (s->ctx, &s->object_list, p)) goto fail; - switch (REC_GET_CLASS_ID(p)) { - case JS_CLASS_OBJECT: - ret = JS_WriteObjectTag (s, obj); - break; - default: - JS_ThrowTypeError (s->ctx, "unsupported object class"); - ret = -1; - break; - } - if (ret) goto fail; - } - break; - default: - JS_ThrowInternalError (s->ctx, "unsupported tag (%d)", tag); - goto fail; - } - return 0; - -fail: - return -1; -} - -/* create the atom table */ -static int JS_WriteObjectAtoms (BCWriterState *s) { - DynBuf dbuf1; - int atoms_size; - - dbuf1 = s->dbuf; - js_dbuf_init (s->ctx, &s->dbuf); - bc_put_u8 (s, BC_VERSION); - - /* Atoms removed - write empty atom table */ - bc_put_leb128 (s, 0); - /* XXX: should check for OOM in above phase */ - - /* move the atoms at the start */ - /* XXX: could just append dbuf1 data, but it uses more memory if - dbuf1 is larger than dbuf */ - atoms_size = s->dbuf.size; - if (dbuf_realloc (&dbuf1, dbuf1.size + atoms_size)) goto fail; - memmove (dbuf1.buf + atoms_size, dbuf1.buf, dbuf1.size); - memcpy (dbuf1.buf, s->dbuf.buf, atoms_size); - dbuf1.size += atoms_size; - dbuf_free (&s->dbuf); - s->dbuf = dbuf1; - return 0; -fail: - dbuf_free (&dbuf1); - return -1; -} - -uint8_t *JS_WriteObject2 (JSContext *ctx, size_t *psize, JSValue obj, int flags, uint8_t ***psab_tab, size_t *psab_tab_len) { - BCWriterState ss, *s = &ss; - - memset (s, 0, sizeof (*s)); - s->ctx = ctx; - s->allow_bytecode = ((flags & JS_WRITE_OBJ_BYTECODE) != 0); - s->allow_sab = ((flags & JS_WRITE_OBJ_SAB) != 0); - s->allow_reference = ((flags & JS_WRITE_OBJ_REFERENCE) != 0); - js_dbuf_init (ctx, &s->dbuf); - js_object_list_init (&s->object_list); - - if (JS_WriteObjectRec (s, obj)) goto fail; - if (JS_WriteObjectAtoms (s)) goto fail; - js_object_list_end (ctx, &s->object_list); - *psize = s->dbuf.size; - if (psab_tab) *psab_tab = s->sab_tab; - if (psab_tab_len) *psab_tab_len = s->sab_tab_len; - return s->dbuf.buf; -fail: - js_object_list_end (ctx, &s->object_list); - dbuf_free (&s->dbuf); - *psize = 0; - if (psab_tab) *psab_tab = NULL; - if (psab_tab_len) *psab_tab_len = 0; - return NULL; -} - -uint8_t *JS_WriteObject (JSContext *ctx, size_t *psize, JSValue obj, int flags) { - return JS_WriteObject2 (ctx, psize, obj, flags, NULL, NULL); -} - -typedef struct BCReaderState { - JSContext *ctx; - const uint8_t *buf_start, *ptr, *buf_end; - int error_state; - BOOL allow_sab : 8; - BOOL allow_bytecode : 8; - BOOL is_rom_data : 8; - BOOL allow_reference : 8; - /* object references */ - JSRecord **objects; - int objects_count; - int objects_size; - -#ifdef DUMP_READ_OBJECT - const uint8_t *ptr_last; - int level; -#endif -} BCReaderState; - -#ifdef DUMP_READ_OBJECT -static void __attribute__ ((format (printf, 2, 3))) -bc_read_trace (BCReaderState *s, const char *fmt, ...) { - va_list ap; - int i, n, n0; - - if (!s->ptr_last) s->ptr_last = s->buf_start; - - n = n0 = 0; - if (s->ptr > s->ptr_last || s->ptr == s->buf_start) { - n0 = printf ("%04x: ", (int)(s->ptr_last - s->buf_start)); - n += n0; - } - for (i = 0; s->ptr_last < s->ptr; i++) { - if ((i & 7) == 0 && i > 0) { - printf ("\n%*s", n0, ""); - n = n0; - } - n += printf (" %02x", *s->ptr_last++); - } - if (*fmt == '}') s->level--; - if (n < 32 + s->level * 2) { printf ("%*s", 32 + s->level * 2 - n, ""); } - va_start (ap, fmt); - vfprintf (stdout, fmt, ap); - va_end (ap); - if (strchr (fmt, '{')) s->level++; -} -#else -#define bc_read_trace(...) -#endif - -static int bc_read_error_end (BCReaderState *s) { - if (!s->error_state) { - JS_ThrowSyntaxError (s->ctx, "read after the end of the buffer"); - } - return s->error_state = -1; -} - -static int bc_get_u8 (BCReaderState *s, uint8_t *pval) { - if (unlikely (s->buf_end - s->ptr < 1)) { - *pval = 0; /* avoid warning */ - return bc_read_error_end (s); - } - *pval = *s->ptr++; - return 0; -} - -static int bc_get_u16 (BCReaderState *s, uint16_t *pval) { - uint16_t v; - if (unlikely (s->buf_end - s->ptr < 2)) { - *pval = 0; /* avoid warning */ - return bc_read_error_end (s); - } - v = get_u16 (s->ptr); - if (is_be ()) v = bswap16 (v); - *pval = v; - s->ptr += 2; - return 0; -} - -static __maybe_unused int bc_get_u32 (BCReaderState *s, uint32_t *pval) { - uint32_t v; - if (unlikely (s->buf_end - s->ptr < 4)) { - *pval = 0; /* avoid warning */ - return bc_read_error_end (s); - } - v = get_u32 (s->ptr); - if (is_be ()) v = bswap32 (v); - *pval = v; - s->ptr += 4; - return 0; -} - -static int bc_get_u64 (BCReaderState *s, uint64_t *pval) { - uint64_t v; - if (unlikely (s->buf_end - s->ptr < 8)) { - *pval = 0; /* avoid warning */ - return bc_read_error_end (s); - } - v = get_u64 (s->ptr); - if (is_be ()) v = bswap64 (v); - *pval = v; - s->ptr += 8; - return 0; -} - -static int bc_get_leb128 (BCReaderState *s, uint32_t *pval) { - int ret; - ret = get_leb128 (pval, s->ptr, s->buf_end); - if (unlikely (ret < 0)) return bc_read_error_end (s); - s->ptr += ret; - return 0; -} - -static int bc_get_sleb128 (BCReaderState *s, int32_t *pval) { - int ret; - ret = get_sleb128 (pval, s->ptr, s->buf_end); - if (unlikely (ret < 0)) return bc_read_error_end (s); - s->ptr += ret; - return 0; -} - -/* XXX: used to read an `int` with a positive value */ -static int bc_get_leb128_int (BCReaderState *s, int *pval) { - return bc_get_leb128 (s, (uint32_t *)pval); -} - -static int bc_get_leb128_u16 (BCReaderState *s, uint16_t *pval) { - uint32_t val; - if (bc_get_leb128 (s, &val)) { - *pval = 0; - return -1; - } - *pval = val; - return 0; -} - -static int bc_get_buf (BCReaderState *s, uint8_t *buf, uint32_t buf_len) { - if (buf_len != 0) { - if (unlikely (!buf || s->buf_end - s->ptr < buf_len)) - return bc_read_error_end (s); - memcpy (buf, s->ptr, buf_len); - s->ptr += buf_len; - } - return 0; -} - -/* Read a JSValue key (text) from bytecode stream */ -static int bc_get_key (BCReaderState *s, JSValue *pkey) { - uint32_t len; - if (bc_get_leb128 (s, &len)) return -1; - if (len == 0) { - *pkey = JS_KEY_empty; - return 0; - } - /* Read UTF-8 bytes */ - char *buf = alloca (len + 1); - for (uint32_t i = 0; i < len; i++) { - uint8_t byte; - if (bc_get_u8 (s, &byte)) return -1; - buf[i] = (char)byte; - } - buf[len] = '\0'; - /* Create interned key from UTF-8 */ - *pkey = js_key_new_len (s->ctx, buf, len); - return JS_IsNull (*pkey) ? -1 : 0; -} - -static JSText *JS_ReadString (BCReaderState *s) { - uint32_t len; - size_t size; - JSText *p; - uint32_t i; - - if (bc_get_leb128 (s, &len)) return NULL; - /* len is uint32_t, JS_STRING_LEN_MAX is 56 bits, so this always fits */ - p = js_alloc_string (s->ctx, len); - if (!p) { - s->error_state = -1; - return NULL; - } - /* UTF-32: each character is 32-bit */ - size = (size_t)len * sizeof (uint32_t); - if ((s->buf_end - s->ptr) < size) { - bc_read_error_end (s); - /* GC handles cleanup - partial string will be collected */ - return NULL; - } - for (i = 0; i < len; i++) { - uint32_t c = get_u32 (s->ptr); - if (is_be ()) c = bswap32 (c); - string_put (p, i, c); - s->ptr += 4; - } -#ifdef DUMP_READ_OBJECT - JS_DumpString (s->ctx->rt, p); - printf ("\n"); -#endif - return p; -} - -static uint32_t bc_get_flags (uint32_t flags, int *pidx, int n) { - uint32_t val; - /* XXX: this does not work for n == 32 */ - val = (flags >> *pidx) & ((1U << n) - 1); - *pidx += n; - return val; -} - -static int JS_ReadFunctionBytecode (BCReaderState *s, JSFunctionBytecode *b, int byte_code_offset, uint32_t bc_len) { - uint8_t *bc_buf; - int pos, len, op; - - if (s->is_rom_data) { - /* directly use the input buffer */ - if (unlikely (s->buf_end - s->ptr < bc_len)) return bc_read_error_end (s); - bc_buf = (uint8_t *)s->ptr; - s->ptr += bc_len; - } else { - bc_buf = (void *)((uint8_t *)b + byte_code_offset); - if (bc_get_buf (s, bc_buf, bc_len)) return -1; - } - b->byte_code_buf = bc_buf; - - if (is_be ()) bc_byte_swap (bc_buf, bc_len); - - pos = 0; - while (pos < bc_len) { - op = bc_buf[pos]; - len = short_opcode_info (op).size; - switch (short_opcode_info (op).fmt) { - case OP_FMT_key: - case OP_FMT_key_u8: - case OP_FMT_key_u16: - case OP_FMT_key_label_u16: - /* Key operand is a cpool index; cpool values deserialized separately */ - break; - default: - break; - } - pos += len; - } - return 0; -} - -static JSValue JS_ReadObjectRec (BCReaderState *s); - -static int BC_add_object_ref1 (BCReaderState *s, JSRecord *p) { - if (s->allow_reference) { - if (js_resize_array (s->ctx, (void *)&s->objects, sizeof (s->objects[0]), &s->objects_size, s->objects_count + 1)) - return -1; - s->objects[s->objects_count++] = p; - } - return 0; -} - -static int BC_add_object_ref (BCReaderState *s, JSValue obj) { - return BC_add_object_ref1 (s, JS_VALUE_GET_OBJ (obj)); -} - -static JSValue JS_ReadFunctionTag (BCReaderState *s) { - JSContext *ctx = s->ctx; - JSFunctionBytecode bc, *b; - JSValue obj = JS_NULL; - uint16_t v16; - uint8_t v8; - int idx, i, local_count; - int function_size, cpool_offset, byte_code_offset; - int closure_var_offset, vardefs_offset; - - memset (&bc, 0, sizeof (bc)); - bc.header = objhdr_make (0, OBJ_CODE, false, false, false, false); - - if (bc_get_u16 (s, &v16)) goto fail; - idx = 0; - bc.has_prototype = bc_get_flags (v16, &idx, 1); - bc.has_simple_parameter_list = bc_get_flags (v16, &idx, 1); - bc.func_kind = bc_get_flags (v16, &idx, 2); - bc.has_debug = bc_get_flags (v16, &idx, 1); - bc.is_direct_or_indirect_eval = bc_get_flags (v16, &idx, 1); - bc.read_only_bytecode = s->is_rom_data; - if (bc_get_u8 (s, &v8)) goto fail; - bc.js_mode = v8; - if (bc_get_key (s, &bc.func_name)) goto fail; - if (bc_get_leb128_u16 (s, &bc.arg_count)) goto fail; - if (bc_get_leb128_u16 (s, &bc.var_count)) goto fail; - if (bc_get_leb128_u16 (s, &bc.defined_arg_count)) goto fail; - if (bc_get_leb128_u16 (s, &bc.stack_size)) goto fail; - if (bc_get_leb128_int (s, &bc.closure_var_count)) goto fail; - if (bc_get_leb128_int (s, &bc.cpool_count)) goto fail; - if (bc_get_leb128_int (s, &bc.byte_code_len)) goto fail; - if (bc_get_leb128_int (s, &local_count)) goto fail; - - if (bc.has_debug) { - function_size = sizeof (*b); - } else { - function_size = offsetof (JSFunctionBytecode, debug); - } - cpool_offset = function_size; - function_size += bc.cpool_count * sizeof (*bc.cpool); - vardefs_offset = function_size; - function_size += local_count * sizeof (*bc.vardefs); - closure_var_offset = function_size; - function_size += bc.closure_var_count * sizeof (*bc.closure_var); - byte_code_offset = function_size; - if (!bc.read_only_bytecode) { function_size += bc.byte_code_len; } - - b = js_mallocz (ctx, function_size); - if (!b) return JS_EXCEPTION; - - memcpy (b, &bc, offsetof (JSFunctionBytecode, debug)); - b->header = objhdr_make (0, OBJ_CODE, false, false, false, false); - if (local_count != 0) { - b->vardefs = (void *)((uint8_t *)b + vardefs_offset); - } - if (b->closure_var_count != 0) { - b->closure_var = (void *)((uint8_t *)b + closure_var_offset); - } - if (b->cpool_count != 0) { - b->cpool = (void *)((uint8_t *)b + cpool_offset); - } - - obj = JS_MKPTR (b); - -#ifdef DUMP_READ_OBJECT - bc_read_trace (s, "name: "); - print_atom (s->ctx, b->func_name); - printf ("\n"); -#endif - bc_read_trace (s, "args=%d vars=%d defargs=%d closures=%d cpool=%d\n", b->arg_count, b->var_count, b->defined_arg_count, b->closure_var_count, b->cpool_count); - bc_read_trace (s, "stack=%d bclen=%d locals=%d\n", b->stack_size, b->byte_code_len, local_count); - - if (local_count != 0) { - bc_read_trace (s, "vars {\n"); - for (i = 0; i < local_count; i++) { - JSVarDef *vd = &b->vardefs[i]; - if (bc_get_key (s, &vd->var_name)) goto fail; - if (bc_get_leb128_int (s, &vd->scope_level)) goto fail; - if (bc_get_leb128_int (s, &vd->scope_next)) goto fail; - vd->scope_next--; - if (bc_get_u8 (s, &v8)) goto fail; - idx = 0; - vd->var_kind = bc_get_flags (v8, &idx, 4); - vd->is_const = bc_get_flags (v8, &idx, 1); - vd->is_lexical = bc_get_flags (v8, &idx, 1); - vd->is_captured = bc_get_flags (v8, &idx, 1); -#ifdef DUMP_READ_OBJECT - bc_read_trace (s, "name: "); - print_atom (s->ctx, vd->var_name); - printf ("\n"); -#endif - } - bc_read_trace (s, "}\n"); - } - if (b->closure_var_count != 0) { - bc_read_trace (s, "closure vars {\n"); - for (i = 0; i < b->closure_var_count; i++) { - JSClosureVar *cv = &b->closure_var[i]; - int var_idx; - if (bc_get_key (s, &cv->var_name)) goto fail; - if (bc_get_leb128_int (s, &var_idx)) goto fail; - cv->var_idx = var_idx; - if (bc_get_u8 (s, &v8)) goto fail; - idx = 0; - cv->is_local = bc_get_flags (v8, &idx, 1); - cv->is_arg = bc_get_flags (v8, &idx, 1); - cv->is_const = bc_get_flags (v8, &idx, 1); - cv->is_lexical = bc_get_flags (v8, &idx, 1); - cv->var_kind = bc_get_flags (v8, &idx, 4); -#ifdef DUMP_READ_OBJECT - bc_read_trace (s, "name: "); - print_atom (s->ctx, cv->var_name); - printf ("\n"); -#endif - } - bc_read_trace (s, "}\n"); - } - { - bc_read_trace (s, "bytecode {\n"); - if (JS_ReadFunctionBytecode (s, b, byte_code_offset, b->byte_code_len)) - goto fail; - bc_read_trace (s, "}\n"); - } - if (b->has_debug) { - /* read optional debug information */ - bc_read_trace (s, "debug {\n"); - if (bc_get_key (s, &b->debug.filename)) goto fail; -#ifdef DUMP_READ_OBJECT - bc_read_trace (s, "filename: "); - print_atom (s->ctx, b->debug.filename); - printf ("\n"); -#endif - if (bc_get_leb128_int (s, &b->debug.pc2line_len)) goto fail; - if (b->debug.pc2line_len) { - b->debug.pc2line_buf = js_mallocz (ctx, b->debug.pc2line_len); - if (!b->debug.pc2line_buf) goto fail; - if (bc_get_buf (s, b->debug.pc2line_buf, b->debug.pc2line_len)) - goto fail; - } - if (bc_get_leb128_int (s, &b->debug.source_len)) goto fail; - if (b->debug.source_len) { - bc_read_trace (s, "source: %d bytes\n", b->source_len); - b->debug.source = js_mallocz (ctx, b->debug.source_len); - if (!b->debug.source) goto fail; - if (bc_get_buf (s, (uint8_t *)b->debug.source, b->debug.source_len)) - goto fail; - } - bc_read_trace (s, "}\n"); - } - if (b->cpool_count != 0) { - bc_read_trace (s, "cpool {\n"); - for (i = 0; i < b->cpool_count; i++) { - JSValue val; - val = JS_ReadObjectRec (s); - if (JS_IsException (val)) goto fail; - b->cpool[i] = val; - } - bc_read_trace (s, "}\n"); - } - return obj; -fail: - return JS_EXCEPTION; -} - -static JSValue JS_ReadObjectTag (BCReaderState *s) { - JSContext *ctx = s->ctx; - JSValue obj; - uint32_t prop_count, i; - JSValue key; - JSValue val; - int ret; - - obj = JS_NewObject (ctx); - if (BC_add_object_ref (s, obj)) goto fail; - if (bc_get_leb128 (s, &prop_count)) goto fail; - for (i = 0; i < prop_count; i++) { - if (bc_get_key (s, &key)) goto fail; -#ifdef DUMP_READ_OBJECT - bc_read_trace (s, "propname: "); - JS_DumpValue (key); - printf ("\n"); -#endif - val = JS_ReadObjectRec (s); - if (JS_IsException (val)) { - goto fail; - } - ret = JS_SetPropertyInternal (ctx, obj, key, val); - if (ret < 0) goto fail; - } - return obj; -fail: - return JS_EXCEPTION; -} - -static JSValue JS_ReadArray (BCReaderState *s, int tag) { - JSContext *ctx = s->ctx; - JSValue obj; - uint32_t len, i; - JSValue val; - BOOL is_template; - - is_template = (tag == BC_TAG_TEMPLATE_OBJECT); - - if (bc_get_leb128 (s, &len)) return JS_EXCEPTION; - - /* Create intrinsic array with preallocated capacity */ - obj = JS_NewArrayLen (ctx, len); - if (JS_IsException (obj)) return JS_EXCEPTION; - - /* Note: intrinsic arrays don't support object reference tracking - for circular references - they're simpler value containers */ - - /* Read and set each element directly */ - JSArray *arr = JS_VALUE_GET_ARRAY (obj); - for (i = 0; i < len; i++) { - val = JS_ReadObjectRec (s); - if (JS_IsException (val)) goto fail; - /* Replace the null placeholder with the read value */ - arr->values[i] = val; - } - - /* Template objects have additional 'raw' property - not supported for - * intrinsic arrays */ - if (is_template) { - val = JS_ReadObjectRec (s); - if (JS_IsException (val)) goto fail; - /* Skip the raw property for intrinsic arrays */ - } - return obj; -fail: - return JS_EXCEPTION; -} - -static JSValue JS_ReadObjectRec (BCReaderState *s) { - JSContext *ctx = s->ctx; - uint8_t tag; - JSValue obj = JS_NULL; - - if (js_check_stack_overflow (ctx, 0)) return JS_ThrowStackOverflow (ctx); - - if (bc_get_u8 (s, &tag)) return JS_EXCEPTION; - - bc_read_trace (s, "%s {\n", bc_tag_str[tag]); - - switch (tag) { - case BC_TAG_NULL: - obj = JS_NULL; - break; - case BC_TAG_BOOL_FALSE: - case BC_TAG_BOOL_TRUE: - obj = JS_NewBool (ctx, tag - BC_TAG_BOOL_FALSE); - break; - case BC_TAG_INT32: { - int32_t val; - if (bc_get_sleb128 (s, &val)) return JS_EXCEPTION; - bc_read_trace (s, "%d\n", val); - obj = JS_NewInt32 (ctx, val); - } break; - case BC_TAG_FLOAT64: { - JSFloat64Union u; - if (bc_get_u64 (s, &u.u64)) return JS_EXCEPTION; - bc_read_trace (s, "%g\n", u.d); - obj = __JS_NewFloat64 (ctx, u.d); - } break; - case BC_TAG_STRING: { - JSText *p; - p = JS_ReadString (s); - if (!p) return JS_EXCEPTION; - obj = JS_MKPTR (p); - } break; - case BC_TAG_FUNCTION_BYTECODE: - if (!s->allow_bytecode) goto invalid_tag; - obj = JS_ReadFunctionTag (s); - break; - case BC_TAG_OBJECT: - obj = JS_ReadObjectTag (s); - break; - case BC_TAG_ARRAY: - case BC_TAG_TEMPLATE_OBJECT: - obj = JS_ReadArray (s, tag); - break; - case BC_TAG_OBJECT_REFERENCE: { - uint32_t val; - if (!s->allow_reference) - return JS_ThrowSyntaxError (ctx, "object references are not allowed"); - if (bc_get_leb128 (s, &val)) return JS_EXCEPTION; - bc_read_trace (s, "%u\n", val); - if (val >= s->objects_count) { - return JS_ThrowSyntaxError (ctx, "invalid object reference (%u >= %u)", val, s->objects_count); - } - obj = JS_MKPTR (s->objects[val]); - } break; - default: - invalid_tag: - return JS_ThrowSyntaxError (ctx, "invalid tag (tag=%d pos=%u)", tag, (unsigned int)(s->ptr - s->buf_start)); - } - bc_read_trace (s, "}\n"); - return obj; -} - -static int JS_ReadObjectAtoms (BCReaderState *s) { - uint8_t v8; - uint32_t atom_count; - - if (bc_get_u8 (s, &v8)) return -1; - if (v8 != BC_VERSION) { - JS_ThrowSyntaxError (s->ctx, "invalid version (%d expected=%d)", v8, BC_VERSION); - return -1; - } - /* Read atom count - should always be 0 for new bytecode */ - if (bc_get_leb128 (s, &atom_count)) return -1; - bc_read_trace (s, "%d atom indexes\n", atom_count); - if (atom_count != 0) { - JS_ThrowSyntaxError (s->ctx, "legacy atom format not supported"); - return -1; - } - return 0; -} - -static void bc_reader_free (BCReaderState *s) { - js_free (s->ctx, s->objects); -} - -JSValue JS_ReadObject (JSContext *ctx, const uint8_t *buf, size_t buf_len, int flags) { - BCReaderState ss, *s = &ss; - JSValue obj; - - ctx->binary_object_count += 1; - ctx->binary_object_size += buf_len; - - memset (s, 0, sizeof (*s)); - s->ctx = ctx; - s->buf_start = buf; - s->buf_end = buf + buf_len; - s->ptr = buf; - s->allow_bytecode = ((flags & JS_READ_OBJ_BYTECODE) != 0); - s->is_rom_data = ((flags & JS_READ_OBJ_ROM_DATA) != 0); - s->allow_sab = ((flags & JS_READ_OBJ_SAB) != 0); - s->allow_reference = ((flags & JS_READ_OBJ_REFERENCE) != 0); - if (JS_ReadObjectAtoms (s)) { - obj = JS_EXCEPTION; - } else { - obj = JS_ReadObjectRec (s); - } - bc_reader_free (s); - return obj; -} - -/*******************************************************************/ -/* JSCompiledUnit Serialization */ - -/* Magic number for compiled unit files */ -#define COMPILED_UNIT_MAGIC 0x43454C4C /* "CELL" */ -#define COMPILED_UNIT_VERSION 1 - -/* Write a JSCompiledUnit to a byte buffer. - Returns allocated buffer (caller must free), or NULL on error. - The format is: - - magic (4 bytes): "CELL" - - version (1 byte) - - flags (1 byte): has_debug - - local_count (2 bytes) - - stack_size (2 bytes) - - cpool_count (4 bytes) - - byte_code_len (4 bytes) - - cpool values (variable) - - bytecode (byte_code_len bytes) - - debug section (if has_debug) -*/ -uint8_t *JS_WriteCompiledUnit (JSContext *ctx, JSCompiledUnit *unit, size_t *out_len) { - DynBuf dbuf; - dbuf_init (&dbuf); - - /* Magic */ - dbuf_put_u32 (&dbuf, COMPILED_UNIT_MAGIC); - - /* Version */ - dbuf_putc (&dbuf, COMPILED_UNIT_VERSION); - - /* Flags */ - uint8_t flags = 0; - if (unit->has_debug) flags |= 1; - dbuf_putc (&dbuf, flags); - - /* Stack requirements */ - dbuf_put_u16 (&dbuf, unit->local_count); - dbuf_put_u16 (&dbuf, unit->stack_size); - - /* Counts */ - dbuf_put_u32 (&dbuf, unit->cpool_count); - dbuf_put_u32 (&dbuf, unit->byte_code_len); - - /* Write constant pool (simplified - just strings for now) */ - for (int i = 0; i < unit->cpool_count; i++) { - JSValue val = unit->cpool[i]; - uint32_t tag = JS_VALUE_GET_TAG (val); - - if (tag == JS_TAG_INT) { - dbuf_putc (&dbuf, 1); /* type: int */ - int32_t v = JS_VALUE_GET_INT (val); - dbuf_put_u32 (&dbuf, (uint32_t)v); - } else if (tag == JS_TAG_FLOAT64) { - dbuf_putc (&dbuf, 2); /* type: float */ - double d = JS_VALUE_GET_FLOAT64 (val); - dbuf_put (&dbuf, (uint8_t *)&d, sizeof (d)); - } else if (JS_IsText (val)) { - dbuf_putc (&dbuf, 3); /* type: string */ - const char *str = JS_ToCString (ctx, val); - if (str) { - size_t len = strlen (str); - dbuf_put_u32 (&dbuf, (uint32_t)len); - dbuf_put (&dbuf, (uint8_t *)str, len); - JS_FreeCString (ctx, str); - } else { - dbuf_put_u32 (&dbuf, 0); - } - } else { - dbuf_putc (&dbuf, 0); /* type: null/unsupported */ - } - } - - /* Write bytecode */ - dbuf_put (&dbuf, unit->byte_code_buf, unit->byte_code_len); - - /* Write debug section if present */ - if (unit->has_debug) { - /* Filename */ - const char *fname = JS_ToCString (ctx, unit->debug.filename); - if (fname) { - size_t len = strlen (fname); - dbuf_put_u32 (&dbuf, (uint32_t)len); - dbuf_put (&dbuf, (uint8_t *)fname, len); - JS_FreeCString (ctx, fname); - } else { - dbuf_put_u32 (&dbuf, 0); - } - - /* source_len, pc2line_len */ - dbuf_put_u32 (&dbuf, unit->debug.source_len); - dbuf_put_u32 (&dbuf, unit->debug.pc2line_len); - - /* pc2line_buf */ - if (unit->debug.pc2line_len > 0 && unit->debug.pc2line_buf) { - dbuf_put (&dbuf, unit->debug.pc2line_buf, unit->debug.pc2line_len); - } - - /* source */ - if (unit->debug.source_len > 0 && unit->debug.source) { - dbuf_put (&dbuf, (uint8_t *)unit->debug.source, unit->debug.source_len); - } - } - - *out_len = dbuf.size; - return dbuf.buf; -} - -/* Read a JSCompiledUnit from a byte buffer. - Returns unit on success, NULL on error. */ -JSCompiledUnit *JS_ReadCompiledUnit (JSContext *ctx, const uint8_t *buf, size_t buf_len) { - const uint8_t *p = buf; - const uint8_t *end = buf + buf_len; - - if (buf_len < 18) return NULL; /* Minimum header size */ - - /* Check magic */ - uint32_t magic = get_u32 (p); p += 4; - if (magic != COMPILED_UNIT_MAGIC) return NULL; - - /* Version */ - uint8_t version = *p++; - if (version != COMPILED_UNIT_VERSION) return NULL; - - /* Flags */ - uint8_t flags = *p++; - BOOL has_debug = (flags & 1) != 0; - - /* Stack requirements */ - uint16_t local_count = get_u16 (p); p += 2; - uint16_t stack_size = get_u16 (p); p += 2; - - /* Counts */ - uint32_t cpool_count = get_u32 (p); p += 4; - uint32_t byte_code_len = get_u32 (p); p += 4; - - /* Calculate allocation size */ - size_t unit_size; - if (has_debug) { - unit_size = sizeof (JSCompiledUnit); - } else { - unit_size = offsetof (JSCompiledUnit, debug); - } - size_t cpool_offset = unit_size; - unit_size += cpool_count * sizeof (JSValue); - size_t bc_offset = unit_size; - unit_size += byte_code_len; - - /* Allocate unit */ - JSCompiledUnit *unit = pjs_mallocz (unit_size); - if (!unit) return NULL; - - /* Initialize header */ - unit->header = objhdr_make (0, OBJ_CODE, false, false, false, false); - unit->has_debug = has_debug; - unit->read_only_bytecode = 0; - unit->local_count = local_count; - unit->stack_size = stack_size; - unit->cpool_count = cpool_count; - unit->byte_code_len = byte_code_len; - - /* Setup pointers */ - if (cpool_count > 0) { - unit->cpool = (JSValue *)((uint8_t *)unit + cpool_offset); - } else { - unit->cpool = NULL; - } - unit->byte_code_buf = (uint8_t *)unit + bc_offset; - - /* Read constant pool */ - for (uint32_t i = 0; i < cpool_count; i++) { - if (p >= end) goto fail; - uint8_t type = *p++; - - switch (type) { - case 0: /* null */ - unit->cpool[i] = JS_NULL; - break; - case 1: /* int */ - if (p + 4 > end) goto fail; - unit->cpool[i] = JS_NewInt32 (ctx, (int32_t)get_u32 (p)); - p += 4; - break; - case 2: /* float */ - if (p + 8 > end) goto fail; - { - double d; - memcpy (&d, p, sizeof (d)); - unit->cpool[i] = JS_NewFloat64 (ctx, d); - p += 8; - } - break; - case 3: /* string */ - if (p + 4 > end) goto fail; - { - uint32_t len = get_u32 (p); p += 4; - if (p + len > end) goto fail; - unit->cpool[i] = JS_NewStringLen (ctx, (const char *)p, len); - p += len; - } - break; - default: - unit->cpool[i] = JS_NULL; - break; - } - } - - /* Read bytecode */ - if (p + byte_code_len > end) goto fail; - memcpy (unit->byte_code_buf, p, byte_code_len); - p += byte_code_len; - - /* Read debug section if present */ - if (has_debug) { - /* Filename */ - if (p + 4 > end) goto fail; - uint32_t fname_len = get_u32 (p); p += 4; - if (p + fname_len > end) goto fail; - if (fname_len > 0) { - unit->debug.filename = JS_NewStringLen (ctx, (const char *)p, fname_len); - } else { - unit->debug.filename = JS_NULL; - } - p += fname_len; - - /* source_len, pc2line_len */ - if (p + 8 > end) goto fail; - unit->debug.source_len = get_u32 (p); p += 4; - unit->debug.pc2line_len = get_u32 (p); p += 4; - - /* pc2line_buf */ - if (unit->debug.pc2line_len > 0) { - if (p + unit->debug.pc2line_len > end) goto fail; - unit->debug.pc2line_buf = js_malloc (ctx, unit->debug.pc2line_len); - if (!unit->debug.pc2line_buf) goto fail; - memcpy (unit->debug.pc2line_buf, p, unit->debug.pc2line_len); - p += unit->debug.pc2line_len; - } else { - unit->debug.pc2line_buf = NULL; - } - - /* source */ - if (unit->debug.source_len > 0) { - if (p + unit->debug.source_len > end) goto fail; - unit->debug.source = js_malloc (ctx, unit->debug.source_len + 1); - if (!unit->debug.source) goto fail; - memcpy (unit->debug.source, p, unit->debug.source_len); - unit->debug.source[unit->debug.source_len] = '\0'; - p += unit->debug.source_len; - } else { - unit->debug.source = NULL; - } - } - - return unit; - -fail: - pjs_free (unit); - return NULL; -} - -/*******************************************************************/ -/* CellModule Serialization (context-neutral) */ - -/* Free a CellModule and all its contents */ -void cell_module_free (CellModule *mod) { - if (!mod) return; - - /* Free string table */ - if (mod->string_data) pjs_free (mod->string_data); - if (mod->string_offsets) pjs_free (mod->string_offsets); - - /* Free units */ - if (mod->units) { - for (uint32_t i = 0; i < mod->unit_count; i++) { - CellUnit *u = &mod->units[i]; - if (u->constants) pjs_free (u->constants); - if (u->bytecode) pjs_free (u->bytecode); - if (u->upvalues) pjs_free (u->upvalues); - if (u->externals) pjs_free (u->externals); - if (u->pc2line) pjs_free (u->pc2line); - } - pjs_free (mod->units); - } - - /* Free source */ - if (mod->source) pjs_free (mod->source); - - pjs_free (mod); -} - -/* Write a CellModule to a byte buffer (context-neutral format). - Returns allocated buffer (caller must free with pjs_free), or NULL on error. - Format: - - magic (4 bytes): 0x4C4C4543 "CELL" - - version (1 byte) - - flags (1 byte) - - string_count (4 bytes) - - string_data_size (4 bytes) - - string_data (string_data_size bytes) - - string_offsets (string_count * 4 bytes) - - unit_count (4 bytes) - - for each unit: - - const_count (4 bytes) - - for each const: type (1 byte), value (4-8 bytes) - - bytecode_len (4 bytes) - - bytecode (bytecode_len bytes) - - arg_count (2 bytes) - - var_count (2 bytes) - - stack_size (2 bytes) - - upvalue_count (2 bytes) - - for each upvalue: kind (1 byte), index (2 bytes) - - external_count (4 bytes) - - for each external: pc_offset (4), name_sid (4), kind (1) - - pc2line_len (4 bytes) - - pc2line (pc2line_len bytes) - - name_sid (4 bytes) - - source_len (4 bytes) - - source (source_len bytes) -*/ -uint8_t *cell_module_write (CellModule *mod, size_t *out_len) { - DynBuf buf; - dbuf_init (&buf); - - /* Header */ - dbuf_put_u32 (&buf, mod->magic); - dbuf_putc (&buf, mod->version); - dbuf_putc (&buf, mod->flags); - - /* String table */ - dbuf_put_u32 (&buf, mod->string_count); - dbuf_put_u32 (&buf, mod->string_data_size); - if (mod->string_data_size > 0 && mod->string_data) { - dbuf_put (&buf, mod->string_data, mod->string_data_size); - } - if (mod->string_count > 0 && mod->string_offsets) { - for (uint32_t i = 0; i < mod->string_count; i++) { - dbuf_put_u32 (&buf, mod->string_offsets[i]); - } - } - - /* Units */ - dbuf_put_u32 (&buf, mod->unit_count); - for (uint32_t u = 0; u < mod->unit_count; u++) { - CellUnit *unit = &mod->units[u]; - - /* Constants */ - dbuf_put_u32 (&buf, unit->const_count); - for (uint32_t c = 0; c < unit->const_count; c++) { - CellConst *cc = &unit->constants[c]; - dbuf_putc (&buf, cc->type); - switch (cc->type) { - case CELL_CONST_NULL: - break; - case CELL_CONST_INT: - dbuf_put_u32 (&buf, (uint32_t)cc->i32); - break; - case CELL_CONST_FLOAT: { - JSFloat64Union fu; - fu.d = cc->f64; - dbuf_put_u32 (&buf, (uint32_t)(fu.u64 & 0xFFFFFFFF)); - dbuf_put_u32 (&buf, (uint32_t)(fu.u64 >> 32)); - break; - } - case CELL_CONST_STRING: - case CELL_CONST_UNIT: - dbuf_put_u32 (&buf, cc->string_sid); - break; - } - } - - /* Bytecode */ - dbuf_put_u32 (&buf, unit->bytecode_len); - if (unit->bytecode_len > 0 && unit->bytecode) { - dbuf_put (&buf, unit->bytecode, unit->bytecode_len); - } - - /* Stack requirements */ - dbuf_put_u16 (&buf, unit->arg_count); - dbuf_put_u16 (&buf, unit->var_count); - dbuf_put_u16 (&buf, unit->stack_size); - - /* Upvalues */ - dbuf_put_u16 (&buf, unit->upvalue_count); - for (uint16_t i = 0; i < unit->upvalue_count; i++) { - dbuf_putc (&buf, unit->upvalues[i].kind); - dbuf_put_u16 (&buf, unit->upvalues[i].index); - } - - /* Externals */ - dbuf_put_u32 (&buf, unit->external_count); - for (uint32_t i = 0; i < unit->external_count; i++) { - dbuf_put_u32 (&buf, unit->externals[i].pc_offset); - dbuf_put_u32 (&buf, unit->externals[i].name_sid); - dbuf_putc (&buf, unit->externals[i].kind); - } - - /* Debug */ - dbuf_put_u32 (&buf, unit->pc2line_len); - if (unit->pc2line_len > 0 && unit->pc2line) { - dbuf_put (&buf, unit->pc2line, unit->pc2line_len); - } - dbuf_put_u32 (&buf, unit->name_sid); - } - - /* Source */ - dbuf_put_u32 (&buf, mod->source_len); - if (mod->source_len > 0 && mod->source) { - dbuf_put (&buf, (uint8_t *)mod->source, mod->source_len); - } - - if (buf.error) { - dbuf_free (&buf); - *out_len = 0; - return NULL; - } - - *out_len = buf.size; - return buf.buf; -} - -/* Read a CellModule from a byte buffer. - Returns allocated CellModule (caller must free with cell_module_free), or NULL on error. */ -CellModule *cell_module_read (const uint8_t *buf, size_t buf_len) { - const uint8_t *p = buf; - const uint8_t *end = buf + buf_len; - - if (buf_len < 14) return NULL; /* minimum header size */ - - CellModule *mod = pjs_mallocz (sizeof (CellModule)); - if (!mod) return NULL; - - /* Header */ - mod->magic = get_u32 (p); p += 4; - if (mod->magic != CELL_MODULE_MAGIC) goto fail; - mod->version = *p++; - if (mod->version != CELL_MODULE_VERSION) goto fail; - mod->flags = *p++; - - /* String table */ - if (p + 8 > end) goto fail; - mod->string_count = get_u32 (p); p += 4; - mod->string_data_size = get_u32 (p); p += 4; - - if (mod->string_data_size > 0) { - if (p + mod->string_data_size > end) goto fail; - mod->string_data = pjs_malloc (mod->string_data_size); - if (!mod->string_data) goto fail; - memcpy (mod->string_data, p, mod->string_data_size); - p += mod->string_data_size; - } - - if (mod->string_count > 0) { - if (p + mod->string_count * 4 > end) goto fail; - mod->string_offsets = pjs_malloc (mod->string_count * sizeof (uint32_t)); - if (!mod->string_offsets) goto fail; - for (uint32_t i = 0; i < mod->string_count; i++) { - mod->string_offsets[i] = get_u32 (p); p += 4; - } - } - - /* Units */ - if (p + 4 > end) goto fail; - mod->unit_count = get_u32 (p); p += 4; - - if (mod->unit_count > 0) { - mod->units = pjs_mallocz (mod->unit_count * sizeof (CellUnit)); - if (!mod->units) goto fail; - - for (uint32_t u = 0; u < mod->unit_count; u++) { - CellUnit *unit = &mod->units[u]; - - /* Constants */ - if (p + 4 > end) goto fail; - unit->const_count = get_u32 (p); p += 4; - - if (unit->const_count > 0) { - unit->constants = pjs_mallocz (unit->const_count * sizeof (CellConst)); - if (!unit->constants) goto fail; - - for (uint32_t c = 0; c < unit->const_count; c++) { - if (p + 1 > end) goto fail; - unit->constants[c].type = *p++; - switch (unit->constants[c].type) { - case CELL_CONST_NULL: - break; - case CELL_CONST_INT: - if (p + 4 > end) goto fail; - unit->constants[c].i32 = (int32_t)get_u32 (p); p += 4; - break; - case CELL_CONST_FLOAT: { - if (p + 8 > end) goto fail; - JSFloat64Union fu; - fu.u64 = get_u32 (p); - fu.u64 |= ((uint64_t)get_u32 (p + 4)) << 32; - p += 8; - unit->constants[c].f64 = fu.d; - break; - } - case CELL_CONST_STRING: - case CELL_CONST_UNIT: - if (p + 4 > end) goto fail; - unit->constants[c].string_sid = get_u32 (p); p += 4; - break; - } - } - } - - /* Bytecode */ - if (p + 4 > end) goto fail; - unit->bytecode_len = get_u32 (p); p += 4; - - if (unit->bytecode_len > 0) { - if (p + unit->bytecode_len > end) goto fail; - unit->bytecode = pjs_malloc (unit->bytecode_len); - if (!unit->bytecode) goto fail; - memcpy (unit->bytecode, p, unit->bytecode_len); - p += unit->bytecode_len; - } - - /* Stack requirements */ - if (p + 6 > end) goto fail; - unit->arg_count = get_u16 (p); p += 2; - unit->var_count = get_u16 (p); p += 2; - unit->stack_size = get_u16 (p); p += 2; - - /* Upvalues */ - if (p + 2 > end) goto fail; - unit->upvalue_count = get_u16 (p); p += 2; - - if (unit->upvalue_count > 0) { - unit->upvalues = pjs_malloc (unit->upvalue_count * sizeof (CellCapDesc)); - if (!unit->upvalues) goto fail; - for (uint16_t i = 0; i < unit->upvalue_count; i++) { - if (p + 3 > end) goto fail; - unit->upvalues[i].kind = *p++; - unit->upvalues[i].index = get_u16 (p); p += 2; - } - } - - /* Externals */ - if (p + 4 > end) goto fail; - unit->external_count = get_u32 (p); p += 4; - - if (unit->external_count > 0) { - unit->externals = pjs_malloc (unit->external_count * sizeof (CellExternalReloc)); - if (!unit->externals) goto fail; - for (uint32_t i = 0; i < unit->external_count; i++) { - if (p + 9 > end) goto fail; - unit->externals[i].pc_offset = get_u32 (p); p += 4; - unit->externals[i].name_sid = get_u32 (p); p += 4; - unit->externals[i].kind = *p++; - } - } - - /* Debug */ - if (p + 4 > end) goto fail; - unit->pc2line_len = get_u32 (p); p += 4; - - if (unit->pc2line_len > 0) { - if (p + unit->pc2line_len > end) goto fail; - unit->pc2line = pjs_malloc (unit->pc2line_len); - if (!unit->pc2line) goto fail; - memcpy (unit->pc2line, p, unit->pc2line_len); - p += unit->pc2line_len; - } - - if (p + 4 > end) goto fail; - unit->name_sid = get_u32 (p); p += 4; - } - } - - /* Source */ - if (p + 4 > end) goto fail; - mod->source_len = get_u32 (p); p += 4; - - if (mod->source_len > 0) { - if (p + mod->source_len > end) goto fail; - mod->source = pjs_malloc (mod->source_len + 1); - if (!mod->source) goto fail; - memcpy (mod->source, p, mod->source_len); - mod->source[mod->source_len] = '\0'; - p += mod->source_len; - } - - return mod; - -fail: - cell_module_free (mod); - return NULL; -} - -/* Helper: get string from CellModule string table */ -static const char *cell_module_get_string (CellModule *mod, uint32_t sid, uint32_t *out_len) { - if (sid >= mod->string_count) return NULL; - uint32_t offset = mod->string_offsets[sid]; - uint32_t next_offset = (sid + 1 < mod->string_count) - ? mod->string_offsets[sid + 1] - : mod->string_data_size; - *out_len = next_offset - offset; - return (const char *)(mod->string_data + offset); -} - -/* Integrate a CellModule with an environment and execute. - This materializes the string table into the target context's stone arena, - creates runtime bytecode, patches external relocations, and returns the - main unit wrapped as a callable function. - - Parameters: - - ctx: target context - - mod: context-neutral module (ownership NOT transferred) - - env: stoned record for environment (or JS_NULL) - - Returns: callable function value, or JS_EXCEPTION on error. -*/ -JSValue cell_module_integrate (JSContext *ctx, CellModule *mod, JSValue env) { - JSValue *string_table = NULL; - JSFunctionBytecode **units = NULL; - JSValue result = JS_EXCEPTION; - uint32_t i, j; - - if (mod->unit_count == 0) { - JS_ThrowTypeError (ctx, "module has no units"); - return JS_EXCEPTION; - } - - /* Step 1: Materialize string table into context's stone arena */ - if (mod->string_count > 0) { - string_table = pjs_mallocz (mod->string_count * sizeof (JSValue)); - if (!string_table) goto fail; - - for (i = 0; i < mod->string_count; i++) { - uint32_t len; - const char *str = cell_module_get_string (mod, i, &len); - if (!str) { - string_table[i] = JS_NULL; - } else { - /* Intern as a stoned key */ - string_table[i] = js_key_new_len (ctx, str, len); - } - } - } - - /* Step 2: Create JSFunctionBytecode for each unit */ - units = pjs_mallocz (mod->unit_count * sizeof (JSFunctionBytecode *)); - if (!units) goto fail; - - for (i = 0; i < mod->unit_count; i++) { - CellUnit *cu = &mod->units[i]; - - /* Calculate bytecode structure size */ - int function_size = sizeof (JSFunctionBytecode); - int cpool_offset = function_size; - function_size += cu->const_count * sizeof (JSValue); - int byte_code_offset = function_size; - function_size += cu->bytecode_len; - - JSFunctionBytecode *b = pjs_mallocz (function_size); - if (!b) goto fail; - units[i] = b; - - /* Initialize header */ - b->header = objhdr_make (0, OBJ_CODE, false, false, false, false); - b->arg_count = cu->arg_count; - b->var_count = cu->var_count; - b->defined_arg_count = cu->arg_count; /* Same as arg_count for simple functions */ - b->has_simple_parameter_list = 1; /* Assume simple parameter list */ - b->stack_size = cu->stack_size; - b->cpool_count = cu->const_count; - b->byte_code_len = cu->bytecode_len; - - /* Set up pointers */ - b->cpool = (JSValue *)((uint8_t *)b + cpool_offset); - b->byte_code_buf = (uint8_t *)b + byte_code_offset; - - /* Materialize constants */ - for (j = 0; j < cu->const_count; j++) { - CellConst *cc = &cu->constants[j]; - switch (cc->type) { - case CELL_CONST_NULL: - b->cpool[j] = JS_NULL; - break; - case CELL_CONST_INT: - b->cpool[j] = JS_NewInt32 (ctx, cc->i32); - break; - case CELL_CONST_FLOAT: - b->cpool[j] = JS_NewFloat64 (ctx, cc->f64); - break; - case CELL_CONST_STRING: - if (cc->string_sid < mod->string_count) { - b->cpool[j] = string_table[cc->string_sid]; - } else { - b->cpool[j] = JS_NULL; - } - break; - case CELL_CONST_UNIT: - /* Will be patched after all units are created */ - b->cpool[j] = JS_NULL; - break; - } - } - - /* Copy bytecode */ - memcpy (b->byte_code_buf, cu->bytecode, cu->bytecode_len); - - /* Set function name from string table */ - if (cu->name_sid < mod->string_count) { - b->func_name = string_table[cu->name_sid]; - } else { - b->func_name = JS_KEY_empty; - } - } - - /* Step 3: Patch unit references in cpool */ - for (i = 0; i < mod->unit_count; i++) { - CellUnit *cu = &mod->units[i]; - JSFunctionBytecode *b = units[i]; - - for (j = 0; j < cu->const_count; j++) { - if (cu->constants[j].type == CELL_CONST_UNIT) { - uint32_t uid = cu->constants[j].unit_id; - if (uid < mod->unit_count) { - b->cpool[j] = JS_MKPTR (units[uid]); - } - } - } - } - - /* Step 4: Patch external relocations for main unit (unit 0) */ - { - CellUnit *cu = &mod->units[0]; - JSFunctionBytecode *b = units[0]; - uint8_t *bc = b->byte_code_buf; - - /* Get env record if provided */ - JSRecord *env_rec = NULL; - if (!JS_IsNull (env) && JS_IsRecord (env)) { - env_rec = (JSRecord *)JS_VALUE_GET_OBJ (env); - } - - for (j = 0; j < cu->external_count; j++) { - CellExternalReloc *rel = &cu->externals[j]; - uint32_t pc = rel->pc_offset; - uint32_t name_sid = rel->name_sid; - - if (name_sid >= mod->string_count) continue; - JSValue name = string_table[name_sid]; - - if (rel->kind == EXT_GET) { - /* Try env first */ - if (env_rec) { - int slot = rec_find_slot (env_rec, name); - if (slot > 0) { - bc[pc] = OP_get_env_slot; - put_u16 (bc + pc + 1, (uint16_t)slot); - bc[pc + 3] = OP_nop; - bc[pc + 4] = OP_nop; - continue; - } - } - - /* Try global */ - JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); - int slot = rec_find_slot (global, name); - if (slot > 0) { - bc[pc] = OP_get_global_slot; - put_u16 (bc + pc + 1, (uint16_t)slot); - bc[pc + 3] = OP_nop; - bc[pc + 4] = OP_nop; - continue; - } - - /* Link error */ - char buf[64]; - JS_ThrowReferenceError (ctx, "'%s' is not defined", - JS_KeyGetStr (ctx, buf, sizeof (buf), name)); - goto fail; - - } else if (rel->kind == EXT_SET) { - /* Try env first (writable) */ - if (env_rec) { - int slot = rec_find_slot (env_rec, name); - if (slot > 0) { - bc[pc] = OP_set_env_slot; - put_u16 (bc + pc + 1, (uint16_t)slot); - bc[pc + 3] = OP_nop; - bc[pc + 4] = OP_nop; - continue; - } - } - - /* Try global */ - JSRecord *global = (JSRecord *)JS_VALUE_GET_OBJ (ctx->global_obj); - int slot = rec_find_slot (global, name); - if (slot > 0) { - bc[pc] = OP_set_global_slot; - put_u16 (bc + pc + 1, (uint16_t)slot); - bc[pc + 3] = OP_nop; - bc[pc + 4] = OP_nop; - continue; - } - - /* Link error */ - char buf[64]; - JS_ThrowReferenceError (ctx, "cannot assign to '%s' - not found", - JS_KeyGetStr (ctx, buf, sizeof (buf), name)); - goto fail; - } - } - } - - /* Step 5: Create closure from main unit and set env_record */ - { - JSValue linked = JS_MKPTR (units[0]); - linked = js_closure (ctx, linked, NULL); - if (JS_IsException (linked)) goto fail; - - /* Set env_record on the function */ - JSFunction *f = JS_VALUE_GET_FUNCTION (linked); - f->u.func.env_record = env; - - result = linked; - } - - /* Success - don't free units (now owned by result closure) */ - pjs_free (string_table); - pjs_free (units); - return result; - -fail: - /* Free allocated units on failure */ - if (units) { - for (i = 0; i < mod->unit_count; i++) { - if (units[i]) pjs_free (units[i]); - } - pjs_free (units); - } - if (string_table) pjs_free (string_table); - return JS_EXCEPTION; -} - -/*******************************************************************/ -/* JSFunctionBytecode to CellModule conversion */ - -/* Helper structure for building string table */ -typedef struct { - JSValue *strings; /* array of JSValue strings */ - uint32_t count; - uint32_t capacity; -} StringTableBuilder; - -static void stb_init (StringTableBuilder *stb) { - stb->strings = NULL; - stb->count = 0; - stb->capacity = 0; -} - -static void stb_free (StringTableBuilder *stb) { - if (stb->strings) pjs_free (stb->strings); - stb->strings = NULL; - stb->count = 0; - stb->capacity = 0; -} - -/* Add a string to the builder, return its string_id. - If string already exists, return existing id. */ -static uint32_t stb_add (StringTableBuilder *stb, JSValue str) { - /* Check if already present */ - for (uint32_t i = 0; i < stb->count; i++) { - if (stb->strings[i] == str) return i; - } - - /* Add new entry */ - if (stb->count >= stb->capacity) { - uint32_t new_cap = stb->capacity ? stb->capacity * 2 : 16; - JSValue *new_arr = pjs_realloc (stb->strings, new_cap * sizeof (JSValue)); - if (!new_arr) return UINT32_MAX; - stb->strings = new_arr; - stb->capacity = new_cap; - } - - stb->strings[stb->count] = str; - return stb->count++; -} - -/* Helper structure for collecting bytecodes */ -typedef struct { - JSFunctionBytecode **funcs; - uint32_t count; - uint32_t capacity; -} FuncCollector; - -static void fc_init (FuncCollector *fc) { - fc->funcs = NULL; - fc->count = 0; - fc->capacity = 0; -} - -static void fc_free (FuncCollector *fc) { - if (fc->funcs) pjs_free (fc->funcs); - fc->funcs = NULL; - fc->count = 0; - fc->capacity = 0; -} - -/* Add a function to collector, return its unit_id */ -static uint32_t fc_add (FuncCollector *fc, JSFunctionBytecode *b) { - /* Check if already present */ - for (uint32_t i = 0; i < fc->count; i++) { - if (fc->funcs[i] == b) return i; - } - - /* Add new entry */ - if (fc->count >= fc->capacity) { - uint32_t new_cap = fc->capacity ? fc->capacity * 2 : 8; - JSFunctionBytecode **new_arr = pjs_realloc (fc->funcs, new_cap * sizeof (JSFunctionBytecode *)); - if (!new_arr) return UINT32_MAX; - fc->funcs = new_arr; - fc->capacity = new_cap; - } - - fc->funcs[fc->count] = b; - return fc->count++; -} - -/* Recursively collect all functions in bytecode tree */ -static int collect_functions (FuncCollector *fc, JSFunctionBytecode *b) { - uint32_t uid = fc_add (fc, b); - if (uid == UINT32_MAX) return -1; - - /* Scan cpool for nested functions */ - for (int i = 0; i < b->cpool_count; i++) { - JSValue v = b->cpool[i]; - if (JS_VALUE_GET_TAG (v) == JS_TAG_PTR) { - void *ptr = JS_VALUE_GET_PTR (v); - objhdr_t hdr = *(objhdr_t *)ptr; - if (objhdr_type (hdr) == OBJ_CODE) { - /* This is a nested function */ - if (collect_functions (fc, (JSFunctionBytecode *)ptr) < 0) - return -1; - } - } - } - return 0; -} - -/* Collect all strings from a function's cpool and name */ -static int collect_strings (StringTableBuilder *stb, JSFunctionBytecode *b) { - /* Function name */ - if (JS_IsText (b->func_name)) { - if (stb_add (stb, b->func_name) == UINT32_MAX) return -1; - } - - /* Filename */ - if (b->has_debug && JS_IsText (b->debug.filename)) { - if (stb_add (stb, b->debug.filename) == UINT32_MAX) return -1; - } - - /* Cpool strings */ - for (int i = 0; i < b->cpool_count; i++) { - JSValue v = b->cpool[i]; - if (JS_IsText (v)) { - if (stb_add (stb, v) == UINT32_MAX) return -1; - } - } - - /* Variable names (for debugging) */ - if (b->vardefs) { - for (int i = 0; i < b->arg_count + b->var_count; i++) { - if (JS_IsText (b->vardefs[i].var_name)) { - if (stb_add (stb, b->vardefs[i].var_name) == UINT32_MAX) return -1; - } - } - } - - /* Closure variable names */ - if (b->closure_var) { - for (int i = 0; i < b->closure_var_count; i++) { - if (JS_IsText (b->closure_var[i].var_name)) { - if (stb_add (stb, b->closure_var[i].var_name) == UINT32_MAX) return -1; - } - } - } - - return 0; -} - -/* Find string_id for a JSValue string */ -static uint32_t find_string_id (StringTableBuilder *stb, JSValue str) { - for (uint32_t i = 0; i < stb->count; i++) { - if (stb->strings[i] == str) return i; - } - return UINT32_MAX; -} - -/* Find unit_id for a JSFunctionBytecode */ -static uint32_t find_unit_id (FuncCollector *fc, JSFunctionBytecode *b) { - for (uint32_t i = 0; i < fc->count; i++) { - if (fc->funcs[i] == b) return i; - } - return UINT32_MAX; -} - -/* Convert JSFunctionBytecode tree to CellModule. - This extracts all functions, builds a shared string table, - and creates external relocations for unresolved variables. - - Parameters: - - ctx: context (for string conversion) - - main_func: compiled main function bytecode - - Returns: allocated CellModule (caller must free with cell_module_free), or NULL on error. -*/ -CellModule *cell_module_from_bytecode (JSContext *ctx, JSFunctionBytecode *main_func) { - CellModule *mod = NULL; - StringTableBuilder stb; - FuncCollector fc; - DynBuf string_data; - uint32_t i, j; - - stb_init (&stb); - fc_init (&fc); - dbuf_init (&string_data); - - /* Step 1: Collect all functions */ - if (collect_functions (&fc, main_func) < 0) goto fail; - - /* Step 2: Collect all strings from all functions */ - for (i = 0; i < fc.count; i++) { - if (collect_strings (&stb, fc.funcs[i]) < 0) goto fail; - } - - /* Step 3: Allocate module */ - mod = pjs_mallocz (sizeof (CellModule)); - if (!mod) goto fail; - - mod->magic = CELL_MODULE_MAGIC; - mod->version = CELL_MODULE_VERSION; - mod->flags = 0; - - /* Step 4: Build string table data */ - mod->string_count = stb.count; - if (stb.count > 0) { - mod->string_offsets = pjs_malloc (stb.count * sizeof (uint32_t)); - if (!mod->string_offsets) goto fail; - - for (i = 0; i < stb.count; i++) { - mod->string_offsets[i] = string_data.size; - - /* Convert JSValue string to UTF-8 */ - const char *cstr = JS_ToCString (ctx, stb.strings[i]); - if (cstr) { - size_t len = strlen (cstr); - dbuf_put (&string_data, (uint8_t *)cstr, len); - JS_FreeCString (ctx, cstr); - } - } - - if (string_data.error) goto fail; - - mod->string_data_size = string_data.size; - mod->string_data = string_data.buf; - string_data.buf = NULL; /* Transfer ownership */ - } - - /* Step 5: Create units */ - mod->unit_count = fc.count; - mod->units = pjs_mallocz (fc.count * sizeof (CellUnit)); - if (!mod->units) goto fail; - - for (i = 0; i < fc.count; i++) { - JSFunctionBytecode *b = fc.funcs[i]; - CellUnit *cu = &mod->units[i]; - - /* Function name */ - if (JS_IsText (b->func_name)) { - cu->name_sid = find_string_id (&stb, b->func_name); - } else { - cu->name_sid = UINT32_MAX; - } - - /* Stack requirements */ - cu->arg_count = b->arg_count; - cu->var_count = b->var_count; - cu->stack_size = b->stack_size; - - /* Copy bytecode */ - cu->bytecode_len = b->byte_code_len; - if (b->byte_code_len > 0) { - cu->bytecode = pjs_malloc (b->byte_code_len); - if (!cu->bytecode) goto fail; - memcpy (cu->bytecode, b->byte_code_buf, b->byte_code_len); - } - - /* Build constants */ - cu->const_count = b->cpool_count; - if (b->cpool_count > 0) { - cu->constants = pjs_mallocz (b->cpool_count * sizeof (CellConst)); - if (!cu->constants) goto fail; - - for (j = 0; j < (uint32_t)b->cpool_count; j++) { - JSValue v = b->cpool[j]; - CellConst *cc = &cu->constants[j]; - - if (JS_IsNull (v)) { - cc->type = CELL_CONST_NULL; - } else if (JS_VALUE_GET_TAG (v) == JS_TAG_INT) { - cc->type = CELL_CONST_INT; - cc->i32 = JS_VALUE_GET_INT (v); - } else if (JS_VALUE_GET_TAG (v) == JS_TAG_FLOAT64) { - cc->type = CELL_CONST_FLOAT; - cc->f64 = JS_VALUE_GET_FLOAT64 (v); - } else if (JS_IsText (v)) { - cc->type = CELL_CONST_STRING; - cc->string_sid = find_string_id (&stb, v); - } else if (JS_VALUE_GET_TAG (v) == JS_TAG_PTR) { - void *ptr = JS_VALUE_GET_PTR (v); - objhdr_t hdr = *(objhdr_t *)ptr; - if (objhdr_type (hdr) == OBJ_CODE) { - /* Nested function reference */ - cc->type = CELL_CONST_UNIT; - cc->unit_id = find_unit_id (&fc, (JSFunctionBytecode *)ptr); - } else { - cc->type = CELL_CONST_NULL; - } - } else { - cc->type = CELL_CONST_NULL; - } - } - } - - /* Build upvalue descriptors from closure_var */ - cu->upvalue_count = b->closure_var_count; - if (b->closure_var_count > 0 && b->closure_var) { - cu->upvalues = pjs_malloc (b->closure_var_count * sizeof (CellCapDesc)); - if (!cu->upvalues) goto fail; - - for (j = 0; j < (uint32_t)b->closure_var_count; j++) { - JSClosureVar *cv = &b->closure_var[j]; - cu->upvalues[j].kind = cv->is_local ? CAP_FROM_PARENT_LOCAL : CAP_FROM_PARENT_UPVALUE; - cu->upvalues[j].index = cv->var_idx; - } - } - - /* Scan bytecode for external relocations (OP_get_var, OP_put_var, etc.) */ - { - DynBuf relocs; - dbuf_init (&relocs); - - uint8_t *bc = cu->bytecode; - int pos = 0; - while (pos < (int)cu->bytecode_len) { - uint8_t op = bc[pos]; - int len = short_opcode_info (op).size; - - if (op == OP_get_var || op == OP_get_var_undef) { - CellExternalReloc rel; - rel.pc_offset = pos; - uint32_t cpool_idx = get_u32 (bc + pos + 1); - if (cpool_idx < (uint32_t)b->cpool_count && JS_IsText (b->cpool[cpool_idx])) { - rel.name_sid = find_string_id (&stb, b->cpool[cpool_idx]); - } else { - rel.name_sid = UINT32_MAX; - } - rel.kind = EXT_GET; - dbuf_put (&relocs, (uint8_t *)&rel, sizeof (rel)); - } else if (op == OP_put_var || op == OP_put_var_init || op == OP_put_var_strict) { - CellExternalReloc rel; - rel.pc_offset = pos; - uint32_t cpool_idx = get_u32 (bc + pos + 1); - if (cpool_idx < (uint32_t)b->cpool_count && JS_IsText (b->cpool[cpool_idx])) { - rel.name_sid = find_string_id (&stb, b->cpool[cpool_idx]); - } else { - rel.name_sid = UINT32_MAX; - } - rel.kind = EXT_SET; - dbuf_put (&relocs, (uint8_t *)&rel, sizeof (rel)); - } - - pos += len; - } - - if (relocs.size > 0 && !relocs.error) { - cu->external_count = relocs.size / sizeof (CellExternalReloc); - cu->externals = (CellExternalReloc *)relocs.buf; - relocs.buf = NULL; /* Transfer ownership */ - } - dbuf_free (&relocs); - } - - /* Copy debug info */ - if (b->has_debug) { - cu->pc2line_len = b->debug.pc2line_len; - if (b->debug.pc2line_len > 0 && b->debug.pc2line_buf) { - cu->pc2line = pjs_malloc (b->debug.pc2line_len); - if (!cu->pc2line) goto fail; - memcpy (cu->pc2line, b->debug.pc2line_buf, b->debug.pc2line_len); - } - } - } - - /* Step 6: Copy source from main function */ - if (main_func->has_debug && main_func->debug.source_len > 0 && main_func->debug.source) { - mod->source_len = main_func->debug.source_len; - mod->source = pjs_malloc (mod->source_len + 1); - if (!mod->source) goto fail; - memcpy (mod->source, main_func->debug.source, mod->source_len); - mod->source[mod->source_len] = '\0'; - } - - /* Success */ - stb_free (&stb); - fc_free (&fc); - dbuf_free (&string_data); - return mod; - -fail: - stb_free (&stb); - fc_free (&fc); - dbuf_free (&string_data); - if (mod) cell_module_free (mod); - return NULL; -} - -/* Compile source code directly to CellModule (context-neutral format). - This is a convenience function that combines JS_Compile + cell_module_from_bytecode. - - Parameters: - - ctx: context for compilation - - input: source code (must be null-terminated) - - input_len: length of source code - - filename: source filename for debug info - - Returns: allocated CellModule (caller must free with cell_module_free), or NULL on error. -*/ -CellModule *JS_CompileModule (JSContext *ctx, const char *input, size_t input_len, const char *filename) { - JSValue bytecode = JS_Compile (ctx, input, input_len, filename); - if (JS_IsException (bytecode)) { - return NULL; - } - - JSFunctionBytecode *b = JS_VALUE_GET_PTR (bytecode); - CellModule *mod = cell_module_from_bytecode (ctx, b); - - /* Note: bytecode is not freed here - it's still valid and could be used - with JS_Integrate if desired. Caller can free it if not needed. */ - - return mod; -} - -/*******************************************************************/ -/* runtime functions & objects */ - -static int check_function (JSContext *ctx, JSValue obj) { - if (likely (JS_IsFunction (obj))) return 0; - JS_ThrowTypeError (ctx, "not a function"); - return -1; -} - -static int check_exception_free (JSContext *ctx, JSValue obj) { - return JS_IsException (obj); -} - -static JSValue find_key (JSContext *ctx, const char *name) { - /* Create an interned JSValue key from C string */ - return js_key_new (ctx, name); -} - -static int JS_InstantiateFunctionListItem (JSContext *ctx, JSValue *obj_ptr, JSValue key, const JSCFunctionListEntry *e) { - JSValue val; - - switch (e->def_type) { - case JS_DEF_ALIAS: /* using autoinit for aliases is not safe */ - { - JSValue key1 = find_key (ctx, e->u.alias.name); - switch (e->u.alias.base) { - case -1: - val = JS_GetProperty (ctx, *obj_ptr, key1); - break; - case 0: - val = JS_GetProperty (ctx, ctx->global_obj, key1); - break; - default: - abort (); - } - /* key1 is interned, no need to free */ - } break; - case JS_DEF_CFUNC: - val = JS_NewCFunction2 (ctx, e->u.func.cfunc.generic, e->name, e->u.func.length, e->u.func.cproto, e->magic); - break; - case JS_DEF_PROP_INT32: - val = JS_NewInt32 (ctx, e->u.i32); - break; - case JS_DEF_PROP_INT64: - val = JS_NewInt64 (ctx, e->u.i64); - break; - case JS_DEF_PROP_DOUBLE: - val = __JS_NewFloat64 (ctx, e->u.f64); - break; - case JS_DEF_PROP_UNDEFINED: - val = JS_NULL; - break; - case JS_DEF_PROP_STRING: - val = JS_NewAtomString (ctx, e->u.str); - break; - - case JS_DEF_OBJECT: - val = JS_NewObject (ctx); - if (JS_IsException (val)) return -1; - JS_SetPropertyFunctionList (ctx, val, e->u.prop_list.tab, e->u.prop_list.len); - break; - default: - abort (); - } - JS_SetPropertyInternal (ctx, *obj_ptr, key, val); - return 0; -} - -int JS_SetPropertyFunctionList (JSContext *ctx, JSValue obj, const JSCFunctionListEntry *tab, int len) { - int i, ret; - - /* Root obj since allocations in the loop can trigger GC */ - JSGCRef obj_ref; - obj_ref.val = obj; - obj_ref.prev = ctx->last_gc_ref; - ctx->last_gc_ref = &obj_ref; - - for (i = 0; i < len; i++) { - const JSCFunctionListEntry *e = &tab[i]; - JSValue key = find_key (ctx, e->name); - if (JS_IsNull (key)) { - ctx->last_gc_ref = obj_ref.prev; - return -1; - } - ret = JS_InstantiateFunctionListItem (ctx, &obj_ref.val, key, e); - /* key is interned, no need to free */ - if (ret) { - ctx->last_gc_ref = obj_ref.prev; - return -1; - } - } - ctx->last_gc_ref = obj_ref.prev; - return 0; -} - -static __exception int js_get_length32 (JSContext *ctx, uint32_t *pres, JSValue obj) { - int tag = JS_VALUE_GET_TAG (obj); - - /* Fast path for intrinsic arrays */ - if (JS_IsArray (obj)) { - JSArray *arr = JS_VALUE_GET_ARRAY (obj); - *pres = arr->len; - return 0; - } - - if (tag == JS_TAG_FUNCTION) { - JSFunction *fn = JS_VALUE_GET_FUNCTION (obj); - *pres = fn->length; - return 0; - } - - blob *b = js_get_blob (ctx, obj); - if (b) { - *pres = b->length; - return 0; - } - - if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) { - JSText *p = JS_VALUE_GET_STRING (obj); - *pres = JSText_len (p); - return 0; - } - - JSValue len_val; - len_val = JS_GetProperty (ctx, obj, JS_KEY_length); - if (JS_IsException (len_val)) { - *pres = 0; - return -1; - } - return JS_ToUint32 (ctx, pres, len_val); -} - -static __exception int js_get_length64 (JSContext *ctx, int64_t *pres, JSValue obj) { - /* Fast path for intrinsic arrays */ - if (JS_IsArray (obj)) { - JSArray *arr = JS_VALUE_GET_ARRAY (obj); - *pres = arr->len; - return 0; - } - JSValue len_val; - len_val = JS_GetProperty (ctx, obj, JS_KEY_length); - if (JS_IsException (len_val)) { - *pres = 0; - return -1; - } - return JS_ToLength (ctx, pres, len_val); -} - -int JS_GetLength (JSContext *ctx, JSValue obj, int64_t *pres) { - return js_get_length64 (ctx, pres, obj); -} - -static void free_arg_list (JSContext *ctx, JSValue *tab, uint32_t len) { - (void)ctx; - (void)len; - js_free_rt(tab); -} - -/* XXX: should use ValueArray */ -static JSValue *build_arg_list (JSContext *ctx, uint32_t *plen, JSValue *parray_arg) { - uint32_t len, i; - JSValue *tab; - - /* Fast path for intrinsic arrays */ - if (JS_IsArray (*parray_arg)) { - JSArray *arr = JS_VALUE_GET_ARRAY (*parray_arg); - len = arr->len; - if (len > JS_MAX_LOCAL_VARS) { - JS_ThrowRangeError ( - ctx, "too many arguments in function call (only %d allowed)", JS_MAX_LOCAL_VARS); - return NULL; - } - tab = js_mallocz_rt (sizeof (tab[0]) * max_uint32 (1, len)); - if (!tab) return NULL; - arr = JS_VALUE_GET_ARRAY (*parray_arg); - for (i = 0; i < len; i++) { - tab[i] = arr->values[i]; - } - *plen = len; - return tab; - } - - JS_ThrowTypeError (ctx, "not an array"); - return NULL; -} - -/* Error class */ -static JSValue js_error_constructor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv, int magic) { - JSValue obj, msg; - JSValue message, options, proto; - int arg_index; - - /* Use the appropriate error prototype based on magic */ - if (magic < 0) { - proto = ctx->class_proto[JS_CLASS_ERROR]; - } else { - proto = ctx->native_error_proto[magic]; - } - obj = JS_NewObjectProtoClass (ctx, proto, JS_CLASS_ERROR); - if (JS_IsException (obj)) return obj; - arg_index = (magic == JS_AGGREGATE_ERROR); - - message = argv[arg_index++]; - if (!JS_IsNull (message)) { - msg = JS_ToString (ctx, message); - if (unlikely (JS_IsException (msg))) goto exception; - JS_SetPropertyInternal (ctx, obj, JS_KEY_message, msg); - } - - if (arg_index < argc) { - options = argv[arg_index]; - if (JS_IsObject (options)) { - int present = JS_HasProperty (ctx, options, JS_KEY_cause); - if (present < 0) goto exception; - if (present) { - JSValue cause = JS_GetProperty (ctx, options, JS_KEY_cause); - if (JS_IsException (cause)) goto exception; - JS_SetPropertyInternal (ctx, obj, JS_KEY_cause, cause); - } - } - } - - if (magic == JS_AGGREGATE_ERROR) { - /* Require errors to be an array (no iterator support) */ - JSValue error_list; - if (JS_IsArray (argv[0])) { - uint32_t len, i; - if (js_get_length32 (ctx, &len, argv[0])) goto exception; - error_list = JS_NewArray (ctx); - if (JS_IsException (error_list)) goto exception; - for (i = 0; i < len; i++) { - JSValue item = JS_GetPropertyUint32 (ctx, argv[0], i); - if (JS_IsException (item)) { - goto exception; - } - if (JS_SetPropertyUint32 (ctx, error_list, i, item) < 0) { - goto exception; - } - } - } else { - error_list = JS_NewArray (ctx); - if (JS_IsException (error_list)) goto exception; - } - JS_SetPropertyInternal (ctx, obj, JS_KEY_errors, error_list); - } - - /* skip the Error() function in the backtrace */ - build_backtrace (ctx, obj, NULL, 0, 0, JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL); - return obj; -exception: - return JS_EXCEPTION; -} - -static JSValue js_error_toString (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - printf ("E TO STR\n"); - JSValue name, msg; - - if (!JS_IsObject (this_val)) return JS_ThrowTypeErrorNotAnObject (ctx); - name = JS_GetProperty (ctx, this_val, JS_KEY_name); - if (JS_IsNull (name)) - name = JS_KEY_Error; - else - name = JS_ToString (ctx, name); - if (JS_IsException (name)) return JS_EXCEPTION; - - msg = JS_GetProperty (ctx, this_val, JS_KEY_message); - if (JS_IsNull (msg)) - msg = JS_KEY_empty; - else - msg = JS_ToString (ctx, msg); - if (JS_IsException (msg)) { - return JS_EXCEPTION; - } - if (!JS_IsEmptyString (name) && !JS_IsEmptyString (msg)) - name = JS_ConcatString3 (ctx, "", name, ": "); - return JS_ConcatString (ctx, name, msg); -} - -static JSValue js_array_includes (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - JSValue found = js_cell_array_find (ctx, this_val, argc, argv); - if (JS_IsException (found)) return JS_EXCEPTION; - - if (JS_IsNull (found)) return JS_NewBool (ctx, FALSE); - return JS_NewBool (ctx, TRUE); -} - -/* return < 0 if exception or TRUE/FALSE */ -static int js_is_regexp (JSContext *ctx, JSValue obj); - -/* RegExp */ - -static void js_regexp_finalizer (JSRuntime *rt, JSValue val) { - JSRegExp *re = JS_GetOpaque (val, JS_CLASS_REGEXP); - if (re) { - js_free_rt (re->pattern); - js_free_rt (re->bytecode); - js_free_rt (re); - } - (void)rt; -} - -/* create a string containing the RegExp bytecode */ -static JSValue js_compile_regexp (JSContext *ctx, JSValue pattern, JSValue flags) { - const char *str; - int re_flags, mask; - uint8_t *re_bytecode_buf; - size_t i, len; - int re_bytecode_len; - JSValue ret; - char error_msg[64]; - - re_flags = 0; - if (!JS_IsNull (flags)) { - str = JS_ToCStringLen (ctx, &len, flags); - if (!str) return JS_EXCEPTION; - /* XXX: re_flags = LRE_FLAG_OCTAL unless strict mode? */ - for (i = 0; i < len; i++) { - switch (str[i]) { - case 'd': - mask = LRE_FLAG_INDICES; - break; - case 'g': - mask = LRE_FLAG_GLOBAL; - break; - case 'i': - mask = LRE_FLAG_IGNORECASE; - break; - case 'm': - mask = LRE_FLAG_MULTILINE; - break; - case 's': - mask = LRE_FLAG_DOTALL; - break; - case 'u': - mask = LRE_FLAG_UNICODE; - break; - case 'v': - mask = LRE_FLAG_UNICODE_SETS; - break; - case 'y': - mask = LRE_FLAG_STICKY; - break; - default: - goto bad_flags; - } - if ((re_flags & mask) != 0) { - bad_flags: - JS_FreeCString (ctx, str); - goto bad_flags1; - } - re_flags |= mask; - } - JS_FreeCString (ctx, str); - } - - /* 'u' and 'v' cannot be both set */ - if ((re_flags & LRE_FLAG_UNICODE_SETS) && (re_flags & LRE_FLAG_UNICODE)) { - bad_flags1: - return JS_ThrowSyntaxError (ctx, "invalid regular expression flags"); - } - - str = JS_ToCStringLen2 ( - ctx, &len, pattern, !(re_flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS))); - if (!str) return JS_EXCEPTION; - re_bytecode_buf = lre_compile (&re_bytecode_len, error_msg, sizeof (error_msg), str, len, re_flags, ctx); - JS_FreeCString (ctx, str); - if (!re_bytecode_buf) { - JS_ThrowSyntaxError (ctx, "%s", error_msg); - return JS_EXCEPTION; - } - - ret - = js_new_string8_len (ctx, (const char *)re_bytecode_buf, re_bytecode_len); - js_free (ctx, re_bytecode_buf); - return ret; -} - -/* create a RegExp object from a string containing the RegExp bytecode - and the source pattern */ -static JSValue js_regexp_constructor_internal (JSContext *ctx, JSValue pattern, JSValue bc) { - JSValue obj; - JSRecord *p; - JSRegExp *re; - const char *pat_cstr; - size_t pat_len; - int bc_len, i; - - /* sanity check - need strings for pattern and bytecode */ - if (!JS_IsText (bc) || !JS_IsText (pattern)) { - JS_ThrowTypeError (ctx, "string expected"); - fail: - return JS_EXCEPTION; - } - - /* Root pattern and bc across allocating calls */ - JSGCRef pat_ref, bc_ref; - JS_PushGCRef (ctx, &pat_ref); - pat_ref.val = pattern; - JS_PushGCRef (ctx, &bc_ref); - bc_ref.val = bc; - - obj = JS_NewObjectProtoClass (ctx, ctx->class_proto[JS_CLASS_REGEXP], JS_CLASS_REGEXP); - if (JS_IsException (obj)) { - JS_PopGCRef (ctx, &bc_ref); - JS_PopGCRef (ctx, &pat_ref); - goto fail; - } - - /* Root obj across allocating calls */ - JSGCRef obj_ref; - JS_PushGCRef (ctx, &obj_ref); - obj_ref.val = obj; - -#define REGEXP_CLEANUP() do { JS_PopGCRef (ctx, &obj_ref); JS_PopGCRef (ctx, &bc_ref); JS_PopGCRef (ctx, &pat_ref); } while(0) - - /* Allocate JSRegExp off-heap (not on GC heap) so opaque pointer stays valid after GC */ - re = js_malloc_rt (sizeof(JSRegExp)); - if (!re) { REGEXP_CLEANUP (); JS_ThrowOutOfMemory (ctx); goto fail; } - p = JS_VALUE_GET_OBJ (obj_ref.val); - REC_SET_OPAQUE(p, re); - re->pattern = NULL; - re->bytecode = NULL; - - /* Extract pattern as UTF-8 C string */ - pat_cstr = JS_ToCStringLen (ctx, &pat_len, pat_ref.val); - if (!pat_cstr) { REGEXP_CLEANUP (); goto fail; } - re->pattern = js_malloc_rt (pat_len + 1); - if (!re->pattern) { - JS_FreeCString (ctx, pat_cstr); - REGEXP_CLEANUP (); - goto fail; - } - memcpy (re->pattern, pat_cstr, pat_len + 1); - re->pattern_len = (uint32_t)pat_len; - JS_FreeCString (ctx, pat_cstr); - - /* Extract bytecode as raw bytes via string_get (not JS_ToCStringLen - which UTF-8 encodes and would mangle bytes >= 128) */ - bc = bc_ref.val; - if (MIST_IsImmediateASCII (bc)) { - bc_len = MIST_GetImmediateASCIILen (bc); - re->bytecode = js_malloc_rt (bc_len); - if (!re->bytecode) { REGEXP_CLEANUP (); goto fail; } - for (i = 0; i < bc_len; i++) - re->bytecode[i] = (uint8_t)MIST_GetImmediateASCIIChar (bc, i); - } else { - JSText *bc_str = (JSText *)chase (bc_ref.val); - bc_len = (int)JSText_len (bc_str); - re->bytecode = js_malloc_rt (bc_len); - if (!re->bytecode) { REGEXP_CLEANUP (); goto fail; } - for (i = 0; i < bc_len; i++) - re->bytecode[i] = (uint8_t)string_get (bc_str, i); - } - re->bytecode_len = (uint32_t)bc_len; - - { - JSValue key = JS_KEY_STR (ctx, "lastIndex"); - obj = obj_ref.val; /* re-read after JS_KEY_STR allocation */ - JS_SetPropertyInternal (ctx, obj, key, JS_NewInt32 (ctx, 0)); - } - obj = obj_ref.val; - REGEXP_CLEANUP (); - return obj; -} -#undef REGEXP_CLEANUP - -static JSRegExp *js_get_regexp (JSContext *ctx, JSValue obj, BOOL throw_error) { - if (JS_VALUE_GET_TAG (obj) == JS_TAG_PTR) { - JSRecord *p = JS_VALUE_GET_OBJ (obj); - if (REC_GET_CLASS_ID(p) == JS_CLASS_REGEXP) return (JSRegExp *)REC_GET_OPAQUE(p); - } - if (throw_error) { JS_ThrowTypeErrorInvalidClass (ctx, JS_CLASS_REGEXP); } - return NULL; -} - -/* return < 0 if exception or TRUE/FALSE */ -static int js_is_regexp (JSContext *ctx, JSValue obj) { - JSValue m; - - if (!JS_IsObject (obj)) return FALSE; - m = JS_GetPropertyStr (ctx, obj, "Symbol.match"); - if (JS_IsException (m)) return -1; - if (!JS_IsNull (m)) return JS_ToBool (ctx, m); - return js_get_regexp (ctx, obj, FALSE) != NULL; -} - -static JSValue js_regexp_constructor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - JSValue pattern, flags, bc, val; - JSValue pat, flags1; - JSRegExp *re; - int pat_is_regexp; - - pat = argv[0]; - flags1 = argv[1]; - pat_is_regexp = js_is_regexp (ctx, pat); - if (pat_is_regexp < 0) return JS_EXCEPTION; - /* If called with a regexp and no flags, just return a copy */ - if (pat_is_regexp && JS_IsNull (flags1)) { - re = js_get_regexp (ctx, pat, FALSE); - if (re) return pat; - } - re = js_get_regexp (ctx, pat, FALSE); - if (re) { - pattern = JS_NewString (ctx, re->pattern); - if (JS_IsException (pattern)) goto fail; - if (JS_IsNull (flags1)) { - bc = js_new_string8_len (ctx, (const char *)re->bytecode, re->bytecode_len); - if (JS_IsException (bc)) goto fail; - goto no_compilation; - } else { - flags = JS_ToString (ctx, flags1); - if (JS_IsException (flags)) goto fail; - } - } else { - flags = JS_NULL; - if (pat_is_regexp) { - pattern = JS_GetProperty (ctx, pat, JS_KEY_source); - if (JS_IsException (pattern)) goto fail; - if (JS_IsNull (flags1)) { - flags = JS_GetProperty (ctx, pat, JS_KEY_flags); - if (JS_IsException (flags)) goto fail; - } else { - flags = flags1; - } - } else { - pattern = pat; - flags = flags1; - } - if (JS_IsNull (pattern)) { - pattern = JS_KEY_empty; - } else { - val = pattern; - pattern = JS_ToString (ctx, val); - if (JS_IsException (pattern)) goto fail; - } - } - bc = js_compile_regexp (ctx, pattern, flags); - if (JS_IsException (bc)) goto fail; -no_compilation: - return js_regexp_constructor_internal (ctx, pattern, bc); -fail: - return JS_EXCEPTION; -} - -static JSValue js_regexp_compile (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - JSRegExp *re1, *re; - JSValue pattern1, flags1; - JSValue bc, pattern; - const char *pat_cstr; - size_t pat_len; - int bc_len, i; - - re = js_get_regexp (ctx, this_val, TRUE); - if (!re) return JS_EXCEPTION; - pattern1 = argv[0]; - flags1 = argv[1]; - re1 = js_get_regexp (ctx, pattern1, FALSE); - if (re1) { - if (!JS_IsNull (flags1)) - return JS_ThrowTypeError (ctx, "flags must be undefined"); - pattern = JS_NewString (ctx, re1->pattern); - if (JS_IsException (pattern)) goto fail; - bc = js_new_string8_len (ctx, (const char *)re1->bytecode, re1->bytecode_len); - if (JS_IsException (bc)) goto fail; - } else { - bc = JS_NULL; - if (JS_IsNull (pattern1)) - pattern = JS_KEY_empty; - else - pattern = JS_ToString (ctx, pattern1); - if (JS_IsException (pattern)) goto fail; - bc = js_compile_regexp (ctx, pattern, flags1); - if (JS_IsException (bc)) goto fail; - } - /* Free old C buffers */ - js_free_rt (re->pattern); - re->pattern = NULL; - js_free_rt (re->bytecode); - re->bytecode = NULL; - - /* Extract pattern as UTF-8 C string */ - pat_cstr = JS_ToCStringLen (ctx, &pat_len, pattern); - if (!pat_cstr) goto fail; - re->pattern = js_malloc_rt (pat_len + 1); - if (!re->pattern) { - JS_FreeCString (ctx, pat_cstr); - goto fail; - } - memcpy (re->pattern, pat_cstr, pat_len + 1); - re->pattern_len = (uint32_t)pat_len; - JS_FreeCString (ctx, pat_cstr); - - /* Extract bytecode as raw bytes */ - if (MIST_IsImmediateASCII (bc)) { - bc_len = MIST_GetImmediateASCIILen (bc); - re->bytecode = js_malloc_rt (bc_len); - if (!re->bytecode) goto fail; - for (i = 0; i < bc_len; i++) - re->bytecode[i] = (uint8_t)MIST_GetImmediateASCIIChar (bc, i); - } else { - JSText *bc_str = (JSText *)JS_VALUE_GET_PTR (bc); - bc_len = (int)JSText_len (bc_str); - re->bytecode = js_malloc_rt (bc_len); - if (!re->bytecode) goto fail; - for (i = 0; i < bc_len; i++) - re->bytecode[i] = (uint8_t)string_get (bc_str, i); - } - re->bytecode_len = (uint32_t)bc_len; - - { - JSValue key = JS_KEY_STR (ctx, "lastIndex"); - int ret = JS_SetProperty (ctx, this_val, key, JS_NewInt32 (ctx, 0)); - if (ret < 0) return JS_EXCEPTION; - } - return this_val; -fail: - return JS_EXCEPTION; -} - -static JSValue js_regexp_toString (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - JSValue pattern, flags; - - if (!JS_IsObject (this_val)) return JS_ThrowTypeErrorNotAnObject (ctx); - - JSText *b = pretext_init (ctx, 0); - if (!b) return JS_EXCEPTION; - - b = pretext_putc (ctx, b, '/'); - if (!b) return JS_EXCEPTION; - pattern = JS_GetProperty (ctx, this_val, JS_KEY_source); - b = pretext_concat_value (ctx, b, pattern); - if (!b) return JS_EXCEPTION; - b = pretext_putc (ctx, b, '/'); - if (!b) return JS_EXCEPTION; - flags = JS_GetProperty (ctx, this_val, JS_KEY_flags); - b = pretext_concat_value (ctx, b, flags); - if (!b) return JS_EXCEPTION; - return pretext_end (ctx, b); -} - -int lre_check_stack_overflow (void *opaque, size_t alloca_size) { - JSContext *ctx = opaque; - return js_check_stack_overflow (ctx, alloca_size); -} - -int lre_check_timeout (void *opaque) { - JSContext *ctx = opaque; - return (ctx->interrupt_handler - && ctx->interrupt_handler (ctx->rt, ctx->interrupt_opaque)); -} - -void *lre_realloc (void *opaque, void *ptr, size_t size) { - (void)opaque; - /* No JS exception is raised here */ - return js_realloc_rt (ptr, size); -} - -/* Convert UTF-32 JSText to UTF-16 buffer for regex engine. - Returns allocated uint16_t buffer (via js_malloc_rt) that must be freed with js_free_rt. - Sets *out_len to number of uint16 code units. */ -static uint16_t *js_string_to_utf16 (JSContext *ctx, JSText *str, int *out_len) { - int len = (int)JSText_len (str); - /* Worst case: each UTF-32 char becomes 2 UTF-16 surrogates */ - uint16_t *buf = js_malloc_rt (len * 2 * sizeof (uint16_t)); - if (!buf) { JS_ThrowOutOfMemory (ctx); return NULL; } - - int j = 0; - for (int i = 0; i < len; i++) { - uint32_t c = string_get (str, i); - if (c < 0x10000) { - buf[j++] = (uint16_t)c; - } else { - /* Encode as surrogate pair */ - c -= 0x10000; - buf[j++] = (uint16_t)(0xD800 | (c >> 10)); - buf[j++] = (uint16_t)(0xDC00 | (c & 0x3FF)); - } - } - *out_len = j; - return buf; -} - -static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - JSRegExp *re = js_get_regexp (ctx, this_val, TRUE); - JSText *str; - JSGCRef str_ref, this_ref; - JSValue ret, res, val, groups, captures_arr, match0; - uint8_t *re_bytecode; - uint8_t **capture, *str_buf; - uint16_t *utf16_buf = NULL; - int rc, capture_count, shift, i, re_flags; - int utf16_len = 0; - int64_t last_index; - const char *group_name_ptr; - - if (!re) return JS_EXCEPTION; - - /* Root this_val across allocating calls */ - JS_PushGCRef (ctx, &this_ref); - this_ref.val = this_val; - - JS_PushGCRef (ctx, &str_ref); - str_ref.val = JS_ToString (ctx, argv[0]); - if (JS_IsException (str_ref.val)) { - JS_PopGCRef (ctx, &str_ref); - JS_PopGCRef (ctx, &this_ref); - return JS_EXCEPTION; - } - /* Ensure str_val is a heap string for JS_VALUE_GET_STRING */ - if (MIST_IsImmediateASCII (str_ref.val)) { - int imm_len = MIST_GetImmediateASCIILen (str_ref.val); - JSText *hs = js_alloc_string (ctx, imm_len > 0 ? imm_len : 1); - if (!hs) { - JS_PopGCRef (ctx, &str_ref); - JS_PopGCRef (ctx, &this_ref); - return JS_EXCEPTION; - } - for (int ci = 0; ci < imm_len; ci++) - string_put (hs, ci, MIST_GetImmediateASCIIChar (str_ref.val, ci)); - hs->hdr = objhdr_set_cap56 (hs->hdr, imm_len); - hs->length = 0; - hs->hdr = objhdr_set_s (hs->hdr, true); - str_ref.val = JS_MKPTR (hs); - } - - ret = JS_EXCEPTION; - res = JS_NULL; - groups = JS_NULL; - captures_arr = JS_NULL; - match0 = JS_NULL; - capture = NULL; - - val = JS_GetPropertyStr (ctx, this_ref.val, "lastIndex"); - if (JS_IsException (val) || JS_ToLength (ctx, &last_index, val)) - goto fail; - - /* Re-chase re after allocating calls (JS_ToString, js_alloc_string, JS_GetPropertyStr) */ - re = js_get_regexp (ctx, this_ref.val, TRUE); - re_bytecode = re->bytecode; - re_flags = lre_get_flags (re_bytecode); - if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) last_index = 0; - - capture_count = lre_get_capture_count (re_bytecode); - - if (capture_count > 0) { - capture = js_malloc_rt (sizeof (capture[0]) * capture_count * 2); - if (!capture) { JS_ThrowOutOfMemory (ctx); goto fail; } - } - - /* Refresh str after potential GC from js_malloc */ - str = JS_VALUE_GET_STRING (str_ref.val); - - /* Convert UTF-32 string to UTF-16 for regex engine (uses js_malloc_rt, no GC) */ - utf16_buf = js_string_to_utf16 (ctx, str, &utf16_len); - if (!utf16_buf) goto fail; - shift = 1; /* UTF-16 mode */ - str_buf = (uint8_t *)utf16_buf; - - /* Refresh str after potential GC from js_string_to_utf16 */ - str = JS_VALUE_GET_STRING (str_ref.val); - if (last_index > (int)JSText_len (str)) { - rc = 2; - } else { - rc = lre_exec (capture, re_bytecode, str_buf, last_index, (int)JSText_len (str), shift, ctx); - } - - if (rc != 1) { - if (rc >= 0) { - if (rc == 2 || (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY))) { - if (JS_SetPropertyStr (ctx, this_ref.val, "lastIndex", JS_NewInt32 (ctx, 0)) - < 0) - goto fail; - } - ret = JS_NULL; - goto done; - } - if (rc == LRE_RET_TIMEOUT) - JS_ThrowInterrupted (ctx); - else - JS_ThrowInternalError (ctx, "out of memory in regexp execution"); - goto fail; - } - - if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) { - if (JS_SetPropertyStr (ctx, this_ref.val, "lastIndex", JS_NewInt32 (ctx, (capture[1] - str_buf) >> shift)) - < 0) - goto fail; - } - - res = JS_NewObjectProto (ctx, JS_NULL); - if (JS_IsException (res)) goto fail; - - /* Root res, captures_arr, groups, match0 across allocating calls */ - JSGCRef res_ref, cap_ref, grp_ref, m0_ref; - JS_PushGCRef (ctx, &res_ref); - res_ref.val = res; - JS_PushGCRef (ctx, &cap_ref); - cap_ref.val = JS_NULL; - JS_PushGCRef (ctx, &grp_ref); - grp_ref.val = JS_NULL; - JS_PushGCRef (ctx, &m0_ref); - m0_ref.val = JS_NULL; - -#define REGEXP_RESULT_CLEANUP() do { \ - JS_PopGCRef (ctx, &m0_ref); \ - JS_PopGCRef (ctx, &grp_ref); \ - JS_PopGCRef (ctx, &cap_ref); \ - JS_PopGCRef (ctx, &res_ref); \ -} while(0) - - { - int cap_groups = (capture_count > 1) ? (capture_count - 1) : 0; - captures_arr = JS_NewArrayLen (ctx, cap_groups); - if (JS_IsException (captures_arr)) { REGEXP_RESULT_CLEANUP (); goto fail; } - cap_ref.val = captures_arr; - } - - group_name_ptr = lre_get_groupnames (re_bytecode); - if (group_name_ptr) { - groups = JS_NewObjectProto (ctx, JS_NULL); - if (JS_IsException (groups)) { REGEXP_RESULT_CLEANUP (); goto fail; } - grp_ref.val = groups; - } - - { - int match_start = -1; - int match_end = -1; - - for (i = 0; i < capture_count; i++) { - const char *name = NULL; - uint8_t **m = &capture[2 * i]; - int start = -1; - int end = -1; - JSValue s; - - if (group_name_ptr && i > 0) { - if (*group_name_ptr) name = group_name_ptr; - group_name_ptr += strlen (group_name_ptr) + 1; - } - - if (m[0] && m[1]) { - start = (m[0] - str_buf) >> shift; - end = (m[1] - str_buf) >> shift; - } - - s = JS_NULL; - if (start != -1) { - str = JS_VALUE_GET_STRING (str_ref.val); - s = js_sub_string (ctx, str, start, end); - if (JS_IsException (s)) { REGEXP_RESULT_CLEANUP (); goto fail; } - } - - if (i == 0) { - match_start = start; - match_end = end; - match0 = s; - m0_ref.val = match0; - continue; - } - - if (name) { - groups = grp_ref.val; - if (JS_SetPropertyStr (ctx, groups, name, s) < 0) { - REGEXP_RESULT_CLEANUP (); - goto fail; - } - } - - captures_arr = cap_ref.val; - if (JS_SetPropertyUint32 (ctx, captures_arr, (uint32_t)(i - 1), s) < 0) { - REGEXP_RESULT_CLEANUP (); - goto fail; - } - } - - if (match_start < 0) match_start = 0; - if (match_end < match_start) match_end = match_start; - - res = res_ref.val; - if (JS_SetPropertyStr (ctx, res, "index", JS_NewInt32 (ctx, match_start)) - < 0) { - REGEXP_RESULT_CLEANUP (); - goto fail; - } - res = res_ref.val; - if (JS_SetPropertyStr (ctx, res, "end", JS_NewInt32 (ctx, match_end)) < 0) { - REGEXP_RESULT_CLEANUP (); - goto fail; - } - - res = res_ref.val; - match0 = m0_ref.val; - if (JS_SetPropertyStr (ctx, res, "match", match0) < 0) { REGEXP_RESULT_CLEANUP (); goto fail; } - - res = res_ref.val; - captures_arr = cap_ref.val; - if (JS_SetPropertyStr (ctx, res, "captures", captures_arr) < 0) { REGEXP_RESULT_CLEANUP (); goto fail; } - - groups = grp_ref.val; - if (!JS_IsNull (groups)) { - res = res_ref.val; - if (JS_SetPropertyStr (ctx, res, "groups", groups) < 0) { REGEXP_RESULT_CLEANUP (); goto fail; } - } else { - res = res_ref.val; - JS_SetPropertyStr (ctx, res, "groups", JS_NULL); - } - } - - ret = res_ref.val; - REGEXP_RESULT_CLEANUP (); - -done: - JS_PopGCRef (ctx, &str_ref); - JS_PopGCRef (ctx, &this_ref); - js_free_rt (capture); - js_free_rt (utf16_buf); - return ret; - -fail: - JS_PopGCRef (ctx, &str_ref); - JS_PopGCRef (ctx, &this_ref); - js_free_rt (capture); - js_free_rt (utf16_buf); - return JS_EXCEPTION; -} - -static const JSCFunctionListEntry js_regexp_proto_funcs[] = { - JS_CFUNC_DEF ("exec", 1, js_regexp_exec), - JS_CFUNC_DEF ("compile", 2, js_regexp_compile), - JS_CFUNC_DEF ("toString", 0, js_regexp_toString), -}; - -static void JS_AddIntrinsicRegExpCompiler (JSContext *ctx) { - ctx->compile_regexp = js_compile_regexp; -} - -static void JS_AddIntrinsicRegExp (JSContext *ctx) { - JSValue obj; - - JS_AddIntrinsicRegExpCompiler (ctx); - - ctx->class_proto[JS_CLASS_REGEXP] = JS_NewObject (ctx); - JS_SetPropertyFunctionList (ctx, ctx->class_proto[JS_CLASS_REGEXP], js_regexp_proto_funcs, countof (js_regexp_proto_funcs)); - obj = JS_NewCFunction2 (ctx, js_regexp_constructor, "RegExp", 2, JS_CFUNC_generic, 0); - JS_SetPropertyStr (ctx, ctx->global_obj, "RegExp", obj); - ctx->regexp_ctor = obj; -} - -/* JSON */ - -static int json_parse_expect (JSParseState *s, int tok) { - if (s->token.val != tok) { - /* XXX: dump token correctly in all cases */ - return js_parse_error (s, "expecting '%c'", tok); - } - return json_next_token (s); -} - -static JSValue json_parse_value (JSParseState *s) { - JSContext *ctx = s->ctx; - JSValue val = JS_NULL; - int ret; - - switch (s->token.val) { - case '{': { - JSValue prop_val; - JSValue prop_name; - JSGCRef val_ref, tok_ref; - - JS_PushGCRef (ctx, &val_ref); - JS_PushGCRef (ctx, &tok_ref); - - if (json_next_token (s)) { - JS_PopGCRef (ctx, &tok_ref); - JS_PopGCRef (ctx, &val_ref); - goto fail; - } - tok_ref.val = s->token.u.str.str; /* Root the token string before GC */ - - val_ref.val = JS_NewObject (ctx); - if (JS_IsException (val_ref.val)) { - JS_PopGCRef (ctx, &tok_ref); - JS_PopGCRef (ctx, &val_ref); - goto fail; - } - if (s->token.val != '}') { - for (;;) { - if (s->token.val == TOK_STRING) { - prop_name = js_key_from_string (ctx, tok_ref.val); /* Use rooted token */ - if (JS_IsNull (prop_name)) { - JS_PopGCRef (ctx, &tok_ref); - JS_PopGCRef (ctx, &val_ref); - goto fail; - } - } else if (s->ext_json && s->token.val == TOK_IDENT) { - prop_name = tok_ref.val; /* Use rooted ident */ - } else { - js_parse_error (s, "expecting property name"); - JS_PopGCRef (ctx, &tok_ref); - JS_PopGCRef (ctx, &val_ref); - goto fail; - } - if (json_next_token (s)) { - JS_PopGCRef (ctx, &tok_ref); - JS_PopGCRef (ctx, &val_ref); - goto fail; - } - if (json_parse_expect (s, ':')) { - JS_PopGCRef (ctx, &tok_ref); - JS_PopGCRef (ctx, &val_ref); - goto fail; - } - prop_val = json_parse_value (s); - if (JS_IsException (prop_val)) { - JS_PopGCRef (ctx, &tok_ref); - JS_PopGCRef (ctx, &val_ref); - goto fail; - } - ret = JS_SetPropertyInternal (ctx, val_ref.val, prop_name, prop_val); - if (ret < 0) { - JS_PopGCRef (ctx, &tok_ref); - JS_PopGCRef (ctx, &val_ref); - goto fail; - } - - if (s->token.val != ',') break; - if (json_next_token (s)) { - JS_PopGCRef (ctx, &tok_ref); - JS_PopGCRef (ctx, &val_ref); - goto fail; - } - tok_ref.val = s->token.u.str.str; /* Root the new key token */ - if (s->ext_json && s->token.val == '}') break; - } - } - if (json_parse_expect (s, '}')) { - JS_PopGCRef (ctx, &tok_ref); - JS_PopGCRef (ctx, &val_ref); - goto fail; - } - val = val_ref.val; - JS_PopGCRef (ctx, &tok_ref); - JS_PopGCRef (ctx, &val_ref); - } break; - case '[': { - JSValue el; - uint32_t idx; - JSGCRef val_ref; - - JS_PushGCRef (ctx, &val_ref); - - if (json_next_token (s)) { - JS_PopGCRef (ctx, &val_ref); - goto fail; - } - val_ref.val = JS_NewArray (ctx); - if (JS_IsException (val_ref.val)) { - JS_PopGCRef (ctx, &val_ref); - goto fail; - } - if (s->token.val != ']') { - idx = 0; - for (;;) { - el = json_parse_value (s); - if (JS_IsException (el)) { - JS_PopGCRef (ctx, &val_ref); - goto fail; - } - ret = JS_SetPropertyUint32 (ctx, val_ref.val, idx, el); - if (ret < 0) { - JS_PopGCRef (ctx, &val_ref); - goto fail; - } - if (s->token.val != ',') break; - if (json_next_token (s)) { - JS_PopGCRef (ctx, &val_ref); - goto fail; - } - idx++; - if (s->ext_json && s->token.val == ']') break; - } - } - if (json_parse_expect (s, ']')) { - JS_PopGCRef (ctx, &val_ref); - goto fail; - } - val = val_ref.val; - JS_PopGCRef (ctx, &val_ref); - } break; - case TOK_STRING: { - JSGCRef val_ref; - JS_PushGCRef (ctx, &val_ref); - val_ref.val = s->token.u.str.str; - if (json_next_token (s)) { - JS_PopGCRef (ctx, &val_ref); - goto fail; - } - val = val_ref.val; - JS_PopGCRef (ctx, &val_ref); - } break; - case TOK_NUMBER: - val = s->token.u.num.val; - if (json_next_token (s)) goto fail; - break; - case TOK_IDENT: - if (js_key_equal (s->token.u.ident.str, JS_KEY_false) - || js_key_equal (s->token.u.ident.str, JS_KEY_true)) { - val = JS_NewBool (ctx, js_key_equal (s->token.u.ident.str, JS_KEY_true)); - } else if (js_key_equal (s->token.u.ident.str, JS_KEY_null)) { - val = JS_NULL; - } else if (js_key_equal_str (s->token.u.ident.str, "NaN") && s->ext_json) { - /* Note: json5 identifier handling is ambiguous e.g. is - '{ NaN: 1 }' a valid JSON5 production ? */ - val = JS_NewFloat64 (s->ctx, NAN); - } else if (js_key_equal_str (s->token.u.ident.str, "Infinity") - && s->ext_json) { - val = JS_NewFloat64 (s->ctx, INFINITY); - } else { - goto def_token; - } - if (json_next_token (s)) goto fail; - break; - default: - def_token: - if (s->token.val == TOK_EOF) { - js_parse_error (s, "Unexpected end of JSON input"); - } else { - js_parse_error (s, "unexpected token: '%.*s'", (int)(s->buf_ptr - s->token.ptr), s->token.ptr); - } - goto fail; - } - return val; -fail: - return JS_EXCEPTION; -} - -JSValue JS_ParseJSON2 (JSContext *ctx, const char *buf, size_t buf_len, const char *filename, int flags) { - JSParseState s1, *s = &s1; - JSValue val = JS_NULL; - - js_parse_init (ctx, s, buf, buf_len, filename); - s->ext_json = ((flags & JS_PARSE_JSON_EXT) != 0); - if (json_next_token (s)) goto fail; - val = json_parse_value (s); - if (JS_IsException (val)) goto fail; - if (s->token.val != TOK_EOF) { - if (js_parse_error (s, "unexpected data at the end")) goto fail; - } - return val; -fail: - free_token (s, &s->token); - return JS_EXCEPTION; -} - -JSValue JS_ParseJSON (JSContext *ctx, const char *buf, size_t buf_len, const char *filename) { - return JS_ParseJSON2 (ctx, buf, buf_len, filename, 0); -} - -static JSValue internalize_json_property (JSContext *ctx, JSValue holder, JSValue name, JSValue reviver) { - JSValue val, new_el, res; - JSValue args[2]; - int ret, is_array; - uint32_t i, len = 0; - JSValue prop; - - if (js_check_stack_overflow (ctx, 0)) { - return JS_ThrowStackOverflow (ctx); - } - - val = JS_GetProperty (ctx, holder, name); - if (JS_IsException (val)) return val; - is_array = JS_IsArray (val); - if (is_array < 0) goto fail; - if (is_array || JS_IsObject (val)) { - if (is_array) { - if (js_get_length32 (ctx, &len, val)) goto fail; - } else { - /* Object property iteration not yet implemented for JSValue keys */ - len = 0; - } - for (i = 0; i < len; i++) { - /* For arrays, use integer index as key */ - prop = JS_NewInt32 (ctx, i); - new_el = internalize_json_property (ctx, val, prop, reviver); - if (JS_IsException (new_el)) { goto fail; } - if (JS_IsNull (new_el)) { - ret = JS_DeleteProperty (ctx, val, prop); - } else { - ret = JS_SetPropertyInternal (ctx, val, prop, new_el); - } - if (ret < 0) goto fail; - } - } - /* name is already a JSValue, use it directly */ - args[0] = name; - args[1] = val; - res = JS_Call (ctx, reviver, holder, 2, args); - return res; -fail: - return JS_EXCEPTION; -} - -static JSValue js_json_parse (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - JSValue obj, root; - JSValue reviver; - const char *str; - size_t len; - - str = JS_ToCStringLen (ctx, &len, argv[0]); - if (!str) return JS_EXCEPTION; - obj = JS_ParseJSON (ctx, str, len, ""); - JS_FreeCString (ctx, str); - if (JS_IsException (obj)) return obj; - if (argc > 1 && JS_IsFunction (argv[1])) { - reviver = argv[1]; - root = JS_NewObject (ctx); - if (JS_IsException (root)) { - return JS_EXCEPTION; - } - if (JS_SetPropertyInternal (ctx, root, JS_KEY_empty, obj) < 0) { - return JS_EXCEPTION; - } - obj = internalize_json_property (ctx, root, JS_KEY_empty, reviver); - } - return obj; -} - -typedef struct JSONStringifyContext { - JSContext *ctx; - JSValue replacer_func; - JSValue stack; - JSValue property_list; - JSValue gap; - JSValue empty; - JSGCRef b_root; /* GC root for buffer - use JSC_B_GET/SET macros */ -} JSONStringifyContext; - -/* Macros to access the buffer from the rooted JSValue */ -#define JSC_B_GET(jsc) JS_VALUE_GET_STRING((jsc)->b_root.val) -#define JSC_B_SET(jsc, ptr) ((jsc)->b_root.val = JS_MKPTR(ptr)) -#define JSC_B_PUTC(jsc, c) do { \ - JSText *_b = pretext_putc(ctx, JSC_B_GET(jsc), c); \ - if (!_b) goto exception; \ - JSC_B_SET(jsc, _b); \ -} while(0) -#define JSC_B_CONCAT(jsc, v) do { \ - JSText *_b = pretext_concat_value(ctx, JSC_B_GET(jsc), v); \ - if (!_b) goto exception; \ - JSC_B_SET(jsc, _b); \ -} while(0) - -static JSValue js_json_check (JSContext *ctx, JSONStringifyContext *jsc, JSValue holder, JSValue val, JSValue key) { - JSValue v; - JSValue args[2]; - - /* check for object.toJSON method */ - /* ECMA specifies this is done only for Object and BigInt */ - if (JS_IsObject (val)) { - JSValue f = JS_GetProperty (ctx, val, JS_KEY_toJSON); - if (JS_IsException (f)) goto exception; - if (JS_IsFunction (f)) { - v = JS_Call (ctx, f, val, 1, &key); - val = v; - if (JS_IsException (val)) goto exception; - } else { - } - } - - if (!JS_IsNull (jsc->replacer_func)) { - args[0] = key; - args[1] = val; - v = JS_Call (ctx, jsc->replacer_func, holder, 2, args); - val = v; - if (JS_IsException (val)) goto exception; - } - - switch (JS_VALUE_GET_NORM_TAG (val)) { - case JS_TAG_PTR: /* includes arrays (OBJ_ARRAY) via mist_hdr */ - if (JS_IsFunction (val)) break; - /* fall through */ - case JS_TAG_STRING_IMM: - case JS_TAG_INT: - case JS_TAG_FLOAT64: - case JS_TAG_BOOL: - case JS_TAG_NULL: - case JS_TAG_EXCEPTION: - return val; - default: - break; - } - return JS_NULL; - -exception: - return JS_EXCEPTION; -} - -static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue holder, JSValue val, JSValue indent) { - JSValue v; - int64_t i, len; - int ret; - BOOL has_content; - JSGCRef val_ref, indent_ref, indent1_ref, sep_ref, sep1_ref, tab_ref, prop_ref; - - /* Root all values that can be heap pointers and survive across GC points */ - JS_PushGCRef (ctx, &val_ref); - JS_PushGCRef (ctx, &indent_ref); - JS_PushGCRef (ctx, &indent1_ref); - JS_PushGCRef (ctx, &sep_ref); - JS_PushGCRef (ctx, &sep1_ref); - JS_PushGCRef (ctx, &tab_ref); - JS_PushGCRef (ctx, &prop_ref); - - val_ref.val = val; - indent_ref.val = indent; - indent1_ref.val = JS_NULL; - sep_ref.val = JS_NULL; - sep1_ref.val = JS_NULL; - tab_ref.val = JS_NULL; - prop_ref.val = JS_NULL; - - if (js_check_stack_overflow (ctx, 0)) { - JS_ThrowStackOverflow (ctx); - goto exception; - } - - if (JS_IsObject ( - val_ref.val)) { /* includes arrays (OBJ_ARRAY) since they have JS_TAG_PTR */ - v = js_array_includes (ctx, jsc->stack, 1, &val_ref.val); - if (JS_IsException (v)) goto exception; - if (JS_ToBool (ctx, v)) { - JS_ThrowTypeError (ctx, "circular reference"); - goto exception; - } - indent1_ref.val = JS_ConcatString (ctx, indent_ref.val, jsc->gap); - if (JS_IsException (indent1_ref.val)) goto exception; - if (!JS_IsEmptyString (jsc->gap)) { - sep_ref.val = JS_ConcatString3 (ctx, "\n", indent1_ref.val, ""); - if (JS_IsException (sep_ref.val)) goto exception; - sep1_ref.val = js_new_string8 (ctx, " "); - if (JS_IsException (sep1_ref.val)) goto exception; - } else { - sep_ref.val = jsc->empty; - sep1_ref.val = jsc->empty; - } - v = js_cell_push (ctx, jsc->stack, 1, &val_ref.val); - if (check_exception_free (ctx, v)) goto exception; - ret = JS_IsArray (val_ref.val); - if (ret < 0) goto exception; - if (ret) { - if (js_get_length64 (ctx, &len, val_ref.val)) goto exception; - JSC_B_PUTC (jsc, '['); - for (i = 0; i < len; i++) { - if (i > 0) { - JSC_B_PUTC (jsc, ','); - } - JSC_B_CONCAT (jsc, sep_ref.val); - v = JS_GetPropertyInt64 (ctx, val_ref.val, i); - if (JS_IsException (v)) goto exception; - /* XXX: could do this string conversion only when needed */ - prop_ref.val = JS_ToString (ctx, JS_NewInt64 (ctx, i)); - if (JS_IsException (prop_ref.val)) goto exception; - v = js_json_check (ctx, jsc, val_ref.val, v, prop_ref.val); - prop_ref.val = JS_NULL; - if (JS_IsException (v)) goto exception; - if (JS_IsNull (v)) v = JS_NULL; - if (js_json_to_str (ctx, jsc, val_ref.val, v, indent1_ref.val)) goto exception; - } - if (len > 0 && !JS_IsEmptyString (jsc->gap)) { - JSC_B_PUTC (jsc, '\n'); - JSC_B_CONCAT (jsc, indent_ref.val); - } - JSC_B_PUTC (jsc, ']'); - } else { - if (!JS_IsNull (jsc->property_list)) - tab_ref.val = jsc->property_list; - else - tab_ref.val = JS_GetOwnPropertyNames (ctx, val_ref.val); - if (JS_IsException (tab_ref.val)) goto exception; - if (js_get_length64 (ctx, &len, tab_ref.val)) goto exception; - JSC_B_PUTC (jsc, '{'); - has_content = FALSE; - for (i = 0; i < len; i++) { - prop_ref.val = JS_GetPropertyInt64 (ctx, tab_ref.val, i); - if (JS_IsException (prop_ref.val)) goto exception; - v = JS_GetPropertyValue (ctx, val_ref.val, prop_ref.val); - if (JS_IsException (v)) goto exception; - v = js_json_check (ctx, jsc, val_ref.val, v, prop_ref.val); - if (JS_IsException (v)) goto exception; - if (!JS_IsNull (v)) { - if (has_content) { - JSC_B_PUTC (jsc, ','); - } - prop_ref.val = JS_ToQuotedString (ctx, prop_ref.val); - if (JS_IsException (prop_ref.val)) { - goto exception; - } - JSC_B_CONCAT (jsc, sep_ref.val); - JSC_B_CONCAT (jsc, prop_ref.val); - JSC_B_PUTC (jsc, ':'); - JSC_B_CONCAT (jsc, sep1_ref.val); - if (js_json_to_str (ctx, jsc, val_ref.val, v, indent1_ref.val)) goto exception; - has_content = TRUE; - } - } - if (has_content && !JS_IsEmptyString (jsc->gap)) { - JSC_B_PUTC (jsc, '\n'); - JSC_B_CONCAT (jsc, indent_ref.val); - } - JSC_B_PUTC (jsc, '}'); - } - if (check_exception_free (ctx, js_cell_pop (ctx, jsc->stack, 0, NULL))) - goto exception; - goto done; - } - switch (JS_VALUE_GET_NORM_TAG (val_ref.val)) { - case JS_TAG_STRING_IMM: - val_ref.val = JS_ToQuotedString (ctx, val_ref.val); - if (JS_IsException (val_ref.val)) goto exception; - goto concat_value; - case JS_TAG_FLOAT64: - if (!isfinite (JS_VALUE_GET_FLOAT64 (val_ref.val))) { val_ref.val = JS_NULL; } - goto concat_value; - case JS_TAG_INT: - case JS_TAG_BOOL: - case JS_TAG_NULL: - concat_value: { - JSText *_b = pretext_concat_value (ctx, JSC_B_GET (jsc), val_ref.val); - if (!_b) goto exception_ret; - JSC_B_SET (jsc, _b); - goto done; - } - default: - goto done; - } - -done: - JS_PopGCRef (ctx, &prop_ref); - JS_PopGCRef (ctx, &tab_ref); - JS_PopGCRef (ctx, &sep1_ref); - JS_PopGCRef (ctx, &sep_ref); - JS_PopGCRef (ctx, &indent1_ref); - JS_PopGCRef (ctx, &indent_ref); - JS_PopGCRef (ctx, &val_ref); - return 0; - -exception_ret: - JS_PopGCRef (ctx, &prop_ref); - JS_PopGCRef (ctx, &tab_ref); - JS_PopGCRef (ctx, &sep1_ref); - JS_PopGCRef (ctx, &sep_ref); - JS_PopGCRef (ctx, &indent1_ref); - JS_PopGCRef (ctx, &indent_ref); - JS_PopGCRef (ctx, &val_ref); - return -1; - -exception: - JS_PopGCRef (ctx, &prop_ref); - JS_PopGCRef (ctx, &tab_ref); - JS_PopGCRef (ctx, &sep1_ref); - JS_PopGCRef (ctx, &sep_ref); - JS_PopGCRef (ctx, &indent1_ref); - JS_PopGCRef (ctx, &indent_ref); - JS_PopGCRef (ctx, &val_ref); - return -1; -} - -JSValue JS_JSONStringify (JSContext *ctx, JSValue obj, JSValue replacer, JSValue space0) { - JSONStringifyContext jsc_s, *jsc = &jsc_s; - JSValue val, v, space, ret, wrapper; - int res; - int64_t i, j, n; - JSGCRef obj_ref; - - /* Root obj since GC can happen during stringify setup */ - JS_PushGCRef (ctx, &obj_ref); - obj_ref.val = obj; - - jsc->ctx = ctx; - jsc->replacer_func = JS_NULL; - jsc->stack = JS_NULL; - jsc->property_list = JS_NULL; - jsc->gap = JS_NULL; - jsc->empty = JS_KEY_empty; - ret = JS_NULL; - wrapper = JS_NULL; - - /* Root the buffer for GC safety */ - JS_PushGCRef (ctx, &jsc->b_root); - { - JSText *b_init = pretext_init (ctx, 0); - if (!b_init) goto exception; - JSC_B_SET (jsc, b_init); - } - jsc->stack = JS_NewArray (ctx); - if (JS_IsException (jsc->stack)) goto exception; - if (JS_IsFunction (replacer)) { - jsc->replacer_func = replacer; - } else { - res = JS_IsArray (replacer); - if (res < 0) goto exception; - if (res) { - /* XXX: enumeration is not fully correct */ - jsc->property_list = JS_NewArray (ctx); - if (JS_IsException (jsc->property_list)) goto exception; - if (js_get_length64 (ctx, &n, replacer)) goto exception; - for (i = j = 0; i < n; i++) { - JSValue present; - v = JS_GetPropertyInt64 (ctx, replacer, i); - if (JS_IsException (v)) goto exception; - if (JS_IsObject (v)) { - /* Objects are not valid property list items */ - continue; - } else if (JS_IsNumber (v)) { - v = JS_ToString (ctx, v); - if (JS_IsException (v)) goto exception; - } else if (!JS_IsText (v)) { - continue; - } - present - = js_array_includes (ctx, jsc->property_list, 1, (JSValue *)&v); - if (JS_IsException (present)) { - goto exception; - } - if (!JS_ToBool (ctx, present)) { - JS_SetPropertyInt64 (ctx, jsc->property_list, j++, v); - } else { - } - } - } - } - space = space0; - if (JS_IsNumber (space)) { - int n; - if (JS_ToInt32Clamp (ctx, &n, space, 0, 10, 0)) goto exception; - jsc->gap = js_new_string8_len (ctx, " ", n); - } else if (JS_IsText (space)) { - JSText *p = JS_VALUE_GET_STRING (space); - jsc->gap = js_sub_string (ctx, p, 0, min_int ((int)JSText_len (p), 10)); - } else { - jsc->gap = jsc->empty; - } - if (JS_IsException (jsc->gap)) goto exception; - wrapper = JS_NewObject (ctx); - if (JS_IsException (wrapper)) goto exception; - if (JS_SetPropertyInternal (ctx, wrapper, JS_KEY_empty, obj_ref.val) - < 0) - goto exception; - val = obj_ref.val; - - val = js_json_check (ctx, jsc, wrapper, val, jsc->empty); - if (JS_IsException (val)) goto exception; - if (JS_IsNull (val)) { - ret = JS_NULL; - goto done1; - } - if (js_json_to_str (ctx, jsc, wrapper, val, jsc->empty)) goto exception; - - ret = pretext_end (ctx, JSC_B_GET (jsc)); - goto done; - -exception: - ret = JS_EXCEPTION; -done1: -done: - JS_PopGCRef (ctx, &jsc->b_root); - JS_PopGCRef (ctx, &obj_ref); - return ret; -} - -/* ============================================================================ - * Cell Script Native Global Functions - * ============================================================================ - * These functions implement the core Cell script primitives: - * - text: string conversion and manipulation - * - number: number conversion and math utilities - * - array: array creation and manipulation - * - object: object creation and manipulation - * - fn: function utilities - * ============================================================================ - */ - -/* ---------------------------------------------------------------------------- - * number function and sub-functions - * ---------------------------------------------------------------------------- - */ - -/* number(val, format) - convert to number */ -static JSValue js_cell_number (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NULL; - - JSValue val = argv[0]; - int tag = JS_VALUE_GET_TAG (val); - - /* Handle boolean */ - if (tag == JS_TAG_BOOL) { - return JS_NewInt32 (ctx, JS_VALUE_GET_BOOL (val) ? 1 : 0); - } - - /* Handle number - return as-is */ - if (tag == JS_TAG_INT || tag == JS_TAG_FLOAT64) { - return val; - } - - /* Handle string */ - if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) { - const char *str = JS_ToCString (ctx, val); - if (!str) return JS_EXCEPTION; - - JSValue result; - - /* Check for format argument */ - if (argc > 1 && JS_VALUE_GET_TAG (argv[1]) == JS_TAG_INT) { - /* Radix conversion */ - int radix = JS_VALUE_GET_INT (argv[1]); - if (radix < 2 || radix > 36) { - JS_FreeCString (ctx, str); - return JS_NULL; - } - char *endptr; - long long n = strtoll (str, &endptr, radix); - if (endptr == str || *endptr != '\0') { - JS_FreeCString (ctx, str); - return JS_NULL; - } - result = JS_NewInt64 (ctx, n); - } else if (argc > 1 - && (JS_VALUE_GET_TAG (argv[1]) == JS_TAG_STRING - || JS_VALUE_GET_TAG (argv[1]) == JS_TAG_STRING_IMM)) { - /* Format string */ - const char *format = JS_ToCString (ctx, argv[1]); - if (!format) { - JS_FreeCString (ctx, str); - return JS_EXCEPTION; - } - - char *clean = js_malloc (ctx, strlen (str) + 1); - if (!clean) { - JS_FreeCString (ctx, format); - JS_FreeCString (ctx, str); - return JS_EXCEPTION; - } - - const char *p = str; - char *q = clean; - - if (strcmp (format, "u") == 0) { - /* underbar separator */ - while (*p) { - if (*p != '_') *q++ = *p; - p++; - } - } else if (strcmp (format, "d") == 0 || strcmp (format, "l") == 0) { - /* comma separator */ - while (*p) { - if (*p != ',') *q++ = *p; - p++; - } - } else if (strcmp (format, "s") == 0) { - /* space separator */ - while (*p) { - if (*p != ' ') *q++ = *p; - p++; - } - } else if (strcmp (format, "v") == 0) { - /* European style: period separator, comma decimal */ - while (*p) { - if (*p == '.') { - p++; - continue; - } - if (*p == ',') { - *q++ = '.'; - p++; - continue; - } - *q++ = *p++; - } - } else if (strcmp (format, "b") == 0) { - *q = '\0'; - char *endptr; - long long n = strtoll (str, &endptr, 2); - js_free (ctx, clean); - JS_FreeCString (ctx, format); - JS_FreeCString (ctx, str); - if (endptr == str) return JS_NULL; - return JS_NewInt64 (ctx, n); - } else if (strcmp (format, "o") == 0) { - *q = '\0'; - char *endptr; - long long n = strtoll (str, &endptr, 8); - js_free (ctx, clean); - JS_FreeCString (ctx, format); - JS_FreeCString (ctx, str); - if (endptr == str) return JS_NULL; - return JS_NewInt64 (ctx, n); - } else if (strcmp (format, "h") == 0) { - *q = '\0'; - char *endptr; - long long n = strtoll (str, &endptr, 16); - js_free (ctx, clean); - JS_FreeCString (ctx, format); - JS_FreeCString (ctx, str); - if (endptr == str) return JS_NULL; - return JS_NewInt64 (ctx, n); - } else if (strcmp (format, "t") == 0) { - *q = '\0'; - char *endptr; - long long n = strtoll (str, &endptr, 32); - js_free (ctx, clean); - JS_FreeCString (ctx, format); - JS_FreeCString (ctx, str); - if (endptr == str) return JS_NULL; - return JS_NewInt64 (ctx, n); - } else if (strcmp (format, "j") == 0) { - /* JavaScript style prefix */ - js_free (ctx, clean); - JS_FreeCString (ctx, format); - int radix = 10; - const char *start = str; - if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { - radix = 16; - start = str + 2; - } else if (str[0] == '0' && (str[1] == 'o' || str[1] == 'O')) { - radix = 8; - start = str + 2; - } else if (str[0] == '0' && (str[1] == 'b' || str[1] == 'B')) { - radix = 2; - start = str + 2; - } - if (radix != 10) { - char *endptr; - long long n = strtoll (start, &endptr, radix); - JS_FreeCString (ctx, str); - if (endptr == start) return JS_NULL; - return JS_NewInt64 (ctx, n); - } - double d = strtod (str, NULL); - JS_FreeCString (ctx, str); - return JS_NewFloat64 (ctx, d); - } else { - /* Unknown format, just copy */ - strcpy (clean, str); - q = clean + strlen (clean); - } - *q = '\0'; - - double d = strtod (clean, NULL); - js_free (ctx, clean); - JS_FreeCString (ctx, format); - JS_FreeCString (ctx, str); - if (isnan (d)) return JS_NULL; - return JS_NewFloat64 (ctx, d); - } else { - /* Default: parse as decimal */ - char *endptr; - double d = strtod (str, &endptr); - JS_FreeCString (ctx, str); - if (endptr == str || isnan (d)) return JS_NULL; - result = JS_NewFloat64 (ctx, d); - } - - return result; - } - - return JS_NULL; -} - -/* number.whole(n) - truncate to integer */ -static JSValue js_cell_number_whole (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double d; - if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; - return JS_NewFloat64 (ctx, trunc (d)); -} - -/* number.fraction(n) - get fractional part */ -static JSValue js_cell_number_fraction (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double d; - if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; - return JS_NewFloat64 (ctx, d - trunc (d)); -} - -/* number.floor(n, place) - floor with optional decimal place */ -static JSValue js_cell_number_floor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double d; - if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; - if (argc < 2 || JS_IsNull (argv[1])) { - return JS_NewFloat64 (ctx, floor (d)); - } - int place; - if (JS_ToInt32 (ctx, &place, argv[1])) return JS_NULL; - if (place == 0) return JS_NewFloat64 (ctx, floor (d)); - double mult = pow (10, -place); - return JS_NewFloat64 (ctx, floor (d * mult) / mult); -} - -/* number.ceiling(n, place) - ceiling with optional decimal place */ -static JSValue js_cell_number_ceiling (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double d; - if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; - if (argc < 2 || JS_IsNull (argv[1])) { - return JS_NewFloat64 (ctx, ceil (d)); - } - int place; - if (JS_ToInt32 (ctx, &place, argv[1])) return JS_NULL; - if (place == 0) return JS_NewFloat64 (ctx, ceil (d)); - double mult = pow (10, -place); - return JS_NewFloat64 (ctx, ceil (d * mult) / mult); -} - -/* number.abs(n) - absolute value */ -static JSValue js_cell_number_abs (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NULL; - int tag = JS_VALUE_GET_TAG (argv[0]); - if (tag != JS_TAG_INT && tag != JS_TAG_FLOAT64) return JS_NULL; - double d; - if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; - return JS_NewFloat64 (ctx, fabs (d)); -} - -/* number.round(n, place) - round with optional decimal place */ -static JSValue js_cell_number_round (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double d; - if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; - if (argc < 2 || JS_IsNull (argv[1])) { - return JS_NewFloat64 (ctx, round (d)); - } - int place; - if (JS_ToInt32 (ctx, &place, argv[1])) return JS_NULL; - if (place == 0) return JS_NewFloat64 (ctx, round (d)); - double mult = pow (10, -place); - return JS_NewFloat64 (ctx, round (d * mult) / mult); -} - -/* number.sign(n) - return sign (-1, 0, 1) */ -static JSValue js_cell_number_sign (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double d; - if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; - if (d < 0) return JS_NewInt32 (ctx, -1); - if (d > 0) return JS_NewInt32 (ctx, 1); - return JS_NewInt32 (ctx, 0); -} - -/* number.trunc(n, place) - truncate with optional decimal place */ -static JSValue js_cell_number_trunc (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double d; - if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; - if (argc < 2 || JS_IsNull (argv[1])) { - return JS_NewFloat64 (ctx, trunc (d)); - } - int place; - if (JS_ToInt32 (ctx, &place, argv[1])) return JS_NULL; - if (place == 0) return JS_NewFloat64 (ctx, trunc (d)); - double mult = pow (10, -place); - return JS_NewFloat64 (ctx, trunc (d * mult) / mult); -} - -/* number.min(...vals) - minimum value */ -static JSValue js_cell_number_min (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc == 0) return JS_NULL; - double result; - if (JS_ToFloat64 (ctx, &result, argv[0])) return JS_NULL; - for (int i = 1; i < argc; i++) { - double d; - if (JS_ToFloat64 (ctx, &d, argv[i])) return JS_NULL; - if (d < result) result = d; - } - return JS_NewFloat64 (ctx, result); -} - -/* number.max(...vals) - maximum value */ -static JSValue js_cell_number_max (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc == 0) return JS_NULL; - double result; - if (JS_ToFloat64 (ctx, &result, argv[0])) return JS_NULL; - for (int i = 1; i < argc; i++) { - double d; - if (JS_ToFloat64 (ctx, &d, argv[i])) return JS_NULL; - if (d > result) result = d; - } - return JS_NewFloat64 (ctx, result); -} - -/* number.remainder(dividend, divisor) - remainder after division */ -static JSValue js_cell_number_remainder (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2) return JS_NULL; - double dividend, divisor; - if (JS_ToFloat64 (ctx, ÷nd, argv[0])) return JS_NULL; - if (JS_ToFloat64 (ctx, &divisor, argv[1])) return JS_NULL; - if (divisor == 0) return JS_NULL; - return JS_NewFloat64 (ctx, - dividend - (trunc (dividend / divisor) * divisor)); -} - -/* ---------------------------------------------------------------------------- - * text function and sub-functions - * ---------------------------------------------------------------------------- - */ - -/* Helper: convert number to string with radix */ -static JSValue js_cell_number_to_radix_string (JSContext *ctx, double num, int radix) { - if (radix < 2 || radix > 36) return JS_NULL; - - /* For base 10, handle floating point properly */ - if (radix == 10) { - char buf[64]; - /* Check if it's an integer */ - if (trunc (num) == num && num >= -9007199254740991.0 - && num <= 9007199254740991.0) { - snprintf (buf, sizeof (buf), "%.0f", num); - } else { - /* Use %g to get a reasonable representation without trailing zeros */ - snprintf (buf, sizeof (buf), "%.15g", num); - } - return JS_NewString (ctx, buf); - } - - /* For other radixes, use integer conversion */ - static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; - char buf[70]; - int len = 0; - int negative = 0; - int64_t n = (int64_t)trunc (num); - - if (n < 0) { - negative = 1; - n = -n; - } - - if (n == 0) { - buf[len++] = '0'; - } else { - while (n > 0) { - buf[len++] = digits[n % radix]; - n /= radix; - } - } - - if (negative) { buf[len++] = '-'; } - - /* Reverse the string */ - char result[72]; - int j = 0; - for (int i = len - 1; i >= 0; i--) { - result[j++] = buf[i]; - } - result[j] = '\0'; - - return JS_NewString (ctx, result); -} - -/* Helper: add separator every n digits from right */ -static char *add_separator (JSContext *ctx, const char *str, char sep, int n) { - if (n <= 0) { - char *result = js_malloc (ctx, strlen (str) + 1); - if (result) strcpy (result, str); - return result; - } - - int negative = (str[0] == '-'); - const char *start = negative ? str + 1 : str; - - /* Find decimal point */ - const char *decimal = strchr (start, '.'); - int int_len = decimal ? (int)(decimal - start) : (int)strlen (start); - - int num_seps = (int_len - 1) / n; - int result_len = strlen (str) + num_seps + 1; - char *result = js_malloc (ctx, result_len); - if (!result) return NULL; - - char *q = result; - if (negative) *q++ = '-'; - - int count = int_len % n; - if (count == 0) count = n; - - for (int i = 0; i < int_len; i++) { - if (i > 0 && count == 0) { - *q++ = sep; - count = n; - } - *q++ = start[i]; - count--; - } - - if (decimal) { - strcpy (q, decimal); - } else { - *q = '\0'; - } - - return result; -} - -/* Helper: format number with format string */ -static JSValue js_cell_format_number (JSContext *ctx, double num, const char *format) { - int separation = 0; - char style = '\0'; - int places = 0; - int i = 0; - - /* Parse separation digit */ - if (format[i] >= '0' && format[i] <= '9') { - separation = format[i] - '0'; - i++; - } - - /* Parse style letter */ - if (format[i]) { - style = format[i]; - i++; - } else { - return JS_NULL; - } - - /* Parse places digits */ - if (format[i] >= '0' && format[i] <= '9') { - places = format[i] - '0'; - i++; - if (format[i] >= '0' && format[i] <= '9') { - places = places * 10 + (format[i] - '0'); - i++; - } - } - - /* Invalid if more characters */ - if (format[i] != '\0') return JS_NULL; - - char buf[128]; - char *result_str = NULL; - - switch (style) { - case 'e': { - /* Exponential */ - if (places > 0) - snprintf (buf, sizeof (buf), "%.*e", places, num); - else - snprintf (buf, sizeof (buf), "%e", num); - return JS_NewString (ctx, buf); - } - case 'n': { - /* Number - scientific for extreme values */ - if (fabs (num) >= 1e21 || (fabs (num) < 1e-6 && num != 0)) { - snprintf (buf, sizeof (buf), "%e", num); - } else if (places > 0) { - snprintf (buf, sizeof (buf), "%.*f", places, num); - } else { - snprintf (buf, sizeof (buf), "%g", num); - } - return JS_NewString (ctx, buf); - } - case 's': { - /* Space separated */ - if (separation == 0) separation = 3; - snprintf (buf, sizeof (buf), "%.*f", places, num); - result_str = add_separator (ctx, buf, ' ', separation); - if (!result_str) return JS_EXCEPTION; - JSValue ret = JS_NewString (ctx, result_str); - js_free (ctx, result_str); - return ret; - } - case 'u': { - /* Underbar separated */ - snprintf (buf, sizeof (buf), "%.*f", places, num); - if (separation > 0) { - result_str = add_separator (ctx, buf, '_', separation); - if (!result_str) return JS_EXCEPTION; - JSValue ret = JS_NewString (ctx, result_str); - js_free (ctx, result_str); - return ret; - } - return JS_NewString (ctx, buf); - } - case 'd': - case 'l': { - /* Decimal/locale with comma separator */ - if (separation == 0) separation = 3; - if (places == 0 && style == 'd') places = 2; - snprintf (buf, sizeof (buf), "%.*f", places, num); - result_str = add_separator (ctx, buf, ',', separation); - if (!result_str) return JS_EXCEPTION; - JSValue ret = JS_NewString (ctx, result_str); - js_free (ctx, result_str); - return ret; - } - case 'v': { - /* European style: comma decimal, period separator */ - snprintf (buf, sizeof (buf), "%.*f", places, num); - /* Replace . with , */ - for (char *p = buf; *p; p++) { - if (*p == '.') *p = ','; - } - if (separation > 0) { - result_str = add_separator (ctx, buf, '.', separation); - if (!result_str) return JS_EXCEPTION; - JSValue ret = JS_NewString (ctx, result_str); - js_free (ctx, result_str); - return ret; - } - return JS_NewString (ctx, buf); - } - case 'i': { - /* Integer base 10 */ - if (places == 0) places = 1; - int64_t n = (int64_t)trunc (num); - int neg = n < 0; - if (neg) n = -n; - snprintf (buf, sizeof (buf), "%lld", (long long)n); - int len = strlen (buf); - /* Pad with zeros */ - if (len < places) { - memmove (buf + (places - len), buf, len + 1); - memset (buf, '0', places - len); - } - if (separation > 0) { - result_str = add_separator (ctx, buf, '_', separation); - if (!result_str) return JS_EXCEPTION; - if (neg) { - char *final = js_malloc (ctx, strlen (result_str) + 2); - if (!final) { - js_free (ctx, result_str); - return JS_EXCEPTION; - } - final[0] = '-'; - strcpy (final + 1, result_str); - js_free (ctx, result_str); - JSValue ret = JS_NewString (ctx, final); - js_free (ctx, final); - return ret; - } - JSValue ret = JS_NewString (ctx, result_str); - js_free (ctx, result_str); - return ret; - } - if (neg) { - memmove (buf + 1, buf, strlen (buf) + 1); - buf[0] = '-'; - } - return JS_NewString (ctx, buf); - } - case 'b': { - /* Binary */ - if (places == 0) places = 1; - return js_cell_number_to_radix_string (ctx, num, 2); - } - case 'o': { - /* Octal */ - if (places == 0) places = 1; - int64_t n = (int64_t)trunc (num); - snprintf (buf, sizeof (buf), "%llo", (long long)(n < 0 ? -n : n)); - /* Uppercase and pad */ - for (char *p = buf; *p; p++) - *p = toupper (*p); - int len = strlen (buf); - if (len < places) { - memmove (buf + (places - len), buf, len + 1); - memset (buf, '0', places - len); - } - if (n < 0) { - memmove (buf + 1, buf, strlen (buf) + 1); - buf[0] = '-'; - } - return JS_NewString (ctx, buf); - } - case 'h': { - /* Hexadecimal */ - if (places == 0) places = 1; - int64_t n = (int64_t)trunc (num); - snprintf (buf, sizeof (buf), "%llX", (long long)(n < 0 ? -n : n)); - int len = strlen (buf); - if (len < places) { - memmove (buf + (places - len), buf, len + 1); - memset (buf, '0', places - len); - } - if (n < 0) { - memmove (buf + 1, buf, strlen (buf) + 1); - buf[0] = '-'; - } - return JS_NewString (ctx, buf); - } - case 't': { - /* Base32 */ - if (places == 0) places = 1; - return js_cell_number_to_radix_string (ctx, num, 32); - } - } - - return JS_NULL; -} - -/* Forward declaration for blob helper */ -static blob *js_get_blob (JSContext *ctx, JSValue val); - -/* modulo(dividend, divisor) - result has sign of divisor */ -static JSValue js_cell_modulo (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2) return JS_NULL; - - double dividend, divisor; - if (JS_ToFloat64 (ctx, ÷nd, argv[0])) return JS_NULL; - if (JS_ToFloat64 (ctx, &divisor, argv[1])) return JS_NULL; - - /* If either operand is NaN, return null */ - if (isnan (dividend) || isnan (divisor)) return JS_NULL; - - /* If divisor is 0, return null */ - if (divisor == 0) return JS_NULL; - - /* If dividend is 0, return 0 */ - if (dividend == 0) return JS_NewFloat64 (ctx, 0.0); - - /* modulo = dividend - (divisor * floor(dividend / divisor)) */ - double result = dividend - (divisor * floor (dividend / divisor)); - - return JS_NewFloat64 (ctx, result); -} - -/* not(bool) - negate a boolean value */ -static JSValue js_cell_not (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NULL; - - if (!JS_IsBool (argv[0])) return JS_NULL; - - return JS_NewBool (ctx, !JS_ToBool (ctx, argv[0])); -} - -/* neg(number) - negate a number */ -static JSValue js_cell_neg (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NULL; - - double num; - if (JS_ToFloat64 (ctx, &num, argv[0])) return JS_NULL; - - if (isnan (num)) return JS_NULL; - - return JS_NewFloat64 (ctx, -num); -} - -/* character(value) - get character from text or codepoint */ -static JSValue js_cell_character (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NewString (ctx, ""); - - JSValue arg = argv[0]; - int tag = JS_VALUE_GET_TAG (arg); - - /* Handle string - return first character */ - if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) { - if (js_string_value_len (arg) == 0) return JS_NewString (ctx, ""); - return js_sub_string_val (ctx, arg, 0, 1); - } - - /* Handle integer - return character from codepoint */ - if (tag == JS_TAG_INT) { - int32_t val = JS_VALUE_GET_INT (arg); - if (val < 0 || val > 0x10FFFF) return JS_NewString (ctx, ""); - - uint32_t codepoint = (uint32_t)val; - if (codepoint < 0x80) { - char buf[2] = { (char)codepoint, '\0' }; - return JS_NewString (ctx, buf); - } - /* Create single-codepoint UTF-32 string */ - JSText *str = js_alloc_string (ctx, 1); - if (!str) return JS_EXCEPTION; - string_put (str, 0, codepoint); - str->length = 1; - return pretext_end (ctx, str); - } - - /* Handle float - convert to integer if non-negative and within range */ - if (tag == JS_TAG_FLOAT64) { - double d = JS_VALUE_GET_FLOAT64 (arg); - if (isnan (d) || d < 0 || d > 0x10FFFF || d != trunc (d)) - return JS_NewString (ctx, ""); - - uint32_t codepoint = (uint32_t)d; - if (codepoint < 0x80) { - char buf[2] = { (char)codepoint, '\0' }; - return JS_NewString (ctx, buf); - } - /* Create single-codepoint UTF-32 string */ - JSText *str = js_alloc_string (ctx, 1); - if (!str) return JS_EXCEPTION; - string_put (str, 0, codepoint); - str->length = 1; - return pretext_end (ctx, str); - } - - return JS_NewString (ctx, ""); -} - -/* text(arg, format) - main text function */ -static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NULL; - - JSValue arg = argv[0]; - int tag = JS_VALUE_GET_TAG (arg); - - /* Handle string / rope */ - if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) { - JSValue str = JS_ToString (ctx, arg); /* owned + flattens rope */ - if (JS_IsException (str)) return JS_EXCEPTION; - - if (argc == 1) return str; - - if (argc >= 2) { - int tag1 = JS_VALUE_GET_TAG (argv[1]); - if (tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64) { - int len = js_string_value_len (str); - int from, to; - - if (JS_ToInt32 (ctx, &from, argv[1])) - return JS_EXCEPTION; - - if (from < 0) from += len; - if (from < 0) from = 0; - if (from > len) from = len; - - to = len; - if (argc >= 3) { - if (JS_ToInt32 (ctx, &to, argv[2])) - return JS_EXCEPTION; - if (to < 0) to += len; - if (to < 0) to = 0; - if (to > len) to = len; - } - - if (from > to) - return JS_NULL; - - return js_sub_string_val (ctx, str, from, to); - } - } - - return str; - } - - /* Handle blob - convert to text representation */ - blob *bd = js_get_blob (ctx, arg); - if (bd) { - if (!bd->is_stone) - return JS_ThrowTypeError (ctx, "text: blob must be stone"); - - char format = '\0'; - if (argc > 1) { - const char *fmt = JS_ToCString (ctx, argv[1]); - if (!fmt) return JS_EXCEPTION; - format = fmt[0]; - JS_FreeCString (ctx, fmt); - } - - size_t byte_len = (bd->length + 7) / 8; - const uint8_t *data = bd->data; - - if (format == 'h') { - static const char hex[] = "0123456789abcdef"; - char *result = js_malloc (ctx, byte_len * 2 + 1); - if (!result) return JS_EXCEPTION; - for (size_t i = 0; i < byte_len; i++) { - result[i * 2] = hex[(data[i] >> 4) & 0xF]; - result[i * 2 + 1] = hex[data[i] & 0xF]; - } - result[byte_len * 2] = '\0'; - JSValue ret = JS_NewString (ctx, result); - js_free (ctx, result); - return ret; - } else if (format == 'b') { - char *result = js_malloc (ctx, bd->length + 1); - if (!result) return JS_EXCEPTION; - for (size_t i = 0; i < (size_t)bd->length; i++) { - size_t byte_idx = i / 8; - size_t bit_idx = i % 8; - result[i] = (data[byte_idx] & (1u << bit_idx)) ? '1' : '0'; - } - result[bd->length] = '\0'; - JSValue ret = JS_NewString (ctx, result); - js_free (ctx, result); - return ret; - } else if (format == 'o') { - size_t octal_len = ((size_t)bd->length + 2) / 3; - char *result = js_malloc (ctx, octal_len + 1); - if (!result) return JS_EXCEPTION; - for (size_t i = 0; i < octal_len; i++) { - int val = 0; - for (int j = 0; j < 3; j++) { - size_t bit_pos = i * 3 + (size_t)j; - if (bit_pos < (size_t)bd->length) { - size_t byte_idx = bit_pos / 8; - size_t bit_idx = bit_pos % 8; - if (data[byte_idx] & (1u << bit_idx)) val |= (1 << j); - } - } - result[i] = (char)('0' + val); - } - result[octal_len] = '\0'; - JSValue ret = JS_NewString (ctx, result); - js_free (ctx, result); - return ret; - } else if (format == 't') { - static const char b32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; - size_t b32_len = ((size_t)bd->length + 4) / 5; - char *result = js_malloc (ctx, b32_len + 1); - if (!result) return JS_EXCEPTION; - for (size_t i = 0; i < b32_len; i++) { - int val = 0; - for (int j = 0; j < 5; j++) { - size_t bit_pos = i * 5 + (size_t)j; - if (bit_pos < (size_t)bd->length) { - size_t byte_idx = bit_pos / 8; - size_t bit_idx = bit_pos % 8; - if (data[byte_idx] & (1u << bit_idx)) val |= (1 << j); - } - } - result[i] = b32[val & 31]; - } - result[b32_len] = '\0'; - JSValue ret = JS_NewString (ctx, result); - js_free (ctx, result); - return ret; - } else { - if (bd->length % 8 != 0) - return JS_ThrowTypeError (ctx, - "text: blob not byte-aligned for UTF-8"); - return JS_NewStringLen (ctx, (const char *)data, byte_len); - } - } - - /* Handle number */ - if (tag == JS_TAG_INT || tag == JS_TAG_FLOAT64) { - double num; - if (JS_ToFloat64 (ctx, &num, arg)) return JS_EXCEPTION; - - if (argc > 1) { - int tag1 = JS_VALUE_GET_TAG (argv[1]); - if (tag1 == JS_TAG_INT) { - int radix = JS_VALUE_GET_INT (argv[1]); - return js_cell_number_to_radix_string (ctx, num, radix); - } - if (tag1 == JS_TAG_STRING || tag1 == JS_TAG_STRING_IMM) { - const char *format = JS_ToCString (ctx, argv[1]); - if (!format) return JS_EXCEPTION; - JSValue result = js_cell_format_number (ctx, num, format); - JS_FreeCString (ctx, format); - return result; - } - } - - return js_cell_number_to_radix_string (ctx, num, 10); - } - - /* Handle array */ - if (JS_IsArray (arg)) { - int64_t len; - JSGCRef arg_ref; - JS_AddGCRef(ctx, &arg_ref); - arg_ref.val = arg; - if (js_get_length64 (ctx, &len, arg_ref.val)) { - JS_DeleteGCRef(ctx, &arg_ref); - return JS_EXCEPTION; - } - - const char *separator = ""; - BOOL sep_alloc = FALSE; - - if (argc > 1 && JS_VALUE_IS_TEXT (argv[1])) { - separator = JS_ToCString (ctx, argv[1]); - if (!separator) return JS_EXCEPTION; - sep_alloc = TRUE; - } - - JSText *b = pretext_init (ctx, 0); - if (!b) { - if (sep_alloc) JS_FreeCString (ctx, separator); - return JS_EXCEPTION; - } - - /* Root b across allocating calls (JS_GetPropertyInt64, JS_ToString) */ - JSGCRef b_ref; - JS_AddGCRef (ctx, &b_ref); - b_ref.val = JS_MKPTR (b); - - for (int64_t i = 0; i < len; i++) { - if (i > 0 && separator[0]) { - b = (JSText *)chase (b_ref.val); - b = pretext_puts8 (ctx, b, separator); - if (!b) goto array_fail; - b_ref.val = JS_MKPTR (b); - } - - b = (JSText *)chase (b_ref.val); /* re-chase before use */ - JSValue item = JS_GetPropertyInt64 (ctx, arg_ref.val, i); - if (JS_IsException (item)) goto array_fail; - - if (!JS_VALUE_IS_TEXT (item)) { - if (sep_alloc) JS_FreeCString (ctx, separator); - JS_DeleteGCRef (ctx, &b_ref); - JS_DeleteGCRef (ctx, &arg_ref); - return JS_ThrowTypeError (ctx, "text: array element is not a string"); - } - - JSValue item_str = JS_ToString (ctx, item); - if (JS_IsException (item_str)) goto array_fail; - - b = (JSText *)chase (b_ref.val); /* re-chase after JS_ToString */ - b = pretext_concat_value (ctx, b, item_str); - if (!b) goto array_fail; - b_ref.val = JS_MKPTR (b); - } - - b = (JSText *)chase (b_ref.val); - if (sep_alloc) JS_FreeCString (ctx, separator); - JS_DeleteGCRef (ctx, &b_ref); - JS_DeleteGCRef (ctx, &arg_ref); - return pretext_end (ctx, b); - - array_fail: - if (sep_alloc) JS_FreeCString (ctx, separator); - JS_DeleteGCRef (ctx, &b_ref); - JS_DeleteGCRef (ctx, &arg_ref); - return JS_EXCEPTION; - } - - /* Handle function - return source or native stub */ - if (JS_IsFunction (arg)) { - JSFunction *fn = JS_VALUE_GET_FUNCTION (arg); - if (fn->kind == JS_FUNC_KIND_BYTECODE) { - JSFunctionBytecode *b = fn->u.func.function_bytecode; - if (b->has_debug && b->debug.source) - return JS_NewStringLen (ctx, b->debug.source, b->debug.source_len); - } - - const char *pref = "function "; - const char *suff = "() {\n [native code]\n}"; - const char *name = ""; - const char *name_cstr = NULL; - - if (fn->kind == JS_FUNC_KIND_BYTECODE) { - JSFunctionBytecode *fb = fn->u.func.function_bytecode; - name_cstr = JS_ToCString (ctx, fb->func_name); - if (name_cstr) name = name_cstr; - } else if (!JS_IsNull (fn->name)) { - name_cstr = JS_ToCString (ctx, fn->name); - if (name_cstr) name = name_cstr; - } - - size_t plen = strlen (pref); - size_t nlen = strlen (name); - size_t slen = strlen (suff); - - char *result = js_malloc (ctx, plen + nlen + slen + 1); - if (!result) { - if (name_cstr) JS_FreeCString (ctx, name_cstr); - return JS_EXCEPTION; - } - - memcpy (result, pref, plen); - memcpy (result + plen, name, nlen); - memcpy (result + plen + nlen, suff, slen + 1); - - JSValue ret = JS_NewString (ctx, result); - js_free (ctx, result); - if (name_cstr) JS_FreeCString (ctx, name_cstr); - return ret; - } - - return JS_ToString (ctx, arg); - return JS_ThrowInternalError (ctx, "Could not convert to text. Tag is %d", tag); -} - -/* text.lower(str) - convert to lowercase */ -static JSValue js_cell_text_lower (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NULL; - if (!JS_VALUE_IS_TEXT (argv[0])) return JS_NULL; - - /* Handle immediate ASCII - no GC concern */ - if (MIST_IsImmediateASCII (argv[0])) { - int len = MIST_GetImmediateASCIILen (argv[0]); - JSText *b = pretext_init (ctx, len); - if (!b) return JS_EXCEPTION; - for (int i = 0; i < len; i++) { - uint32_t c = MIST_GetImmediateASCIIChar (argv[0], i); - if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a'; - b = pretext_putc (ctx, b, c); - if (!b) return JS_EXCEPTION; - } - return pretext_end (ctx, b); - } - - /* Heap text: must re-chase after GC points */ - int len = (int)JSText_len (JS_VALUE_GET_STRING (argv[0])); - JSText *b = pretext_init (ctx, len); - if (!b) return JS_EXCEPTION; - - for (int i = 0; i < len; i++) { - JSText *p = JS_VALUE_GET_STRING (argv[0]); /* Re-chase each iteration */ - uint32_t c = string_get (p, i); - if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a'; - b = pretext_putc (ctx, b, c); - if (!b) return JS_EXCEPTION; - } - - return pretext_end (ctx, b); -} - -/* text.upper(str) - convert to uppercase */ -static JSValue js_cell_text_upper (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NULL; - if (!JS_VALUE_IS_TEXT (argv[0])) return JS_NULL; - - /* Handle immediate ASCII - no GC concern */ - if (MIST_IsImmediateASCII (argv[0])) { - int len = MIST_GetImmediateASCIILen (argv[0]); - JSText *b = pretext_init (ctx, len); - if (!b) return JS_EXCEPTION; - for (int i = 0; i < len; i++) { - uint32_t c = MIST_GetImmediateASCIIChar (argv[0], i); - if (c >= 'a' && c <= 'z') c = c - 'a' + 'A'; - b = pretext_putc (ctx, b, c); - if (!b) return JS_EXCEPTION; - } - return pretext_end (ctx, b); - } - - /* Heap text: must re-chase after GC points */ - int len = (int)JSText_len (JS_VALUE_GET_STRING (argv[0])); - JSText *b = pretext_init (ctx, len); - if (!b) return JS_EXCEPTION; - - for (int i = 0; i < len; i++) { - JSText *p = JS_VALUE_GET_STRING (argv[0]); /* Re-chase each iteration */ - uint32_t c = string_get (p, i); - if (c >= 'a' && c <= 'z') c = c - 'a' + 'A'; - b = pretext_putc (ctx, b, c); - if (!b) return JS_EXCEPTION; - } - - return pretext_end (ctx, b); -} - -/* text.trim(str, reject) - trim whitespace or custom characters */ -static JSValue js_cell_text_trim (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NULL; - if (!JS_IsText (argv[0])) return JS_NULL; - - JSValue str = argv[0]; - int start = 0; - int end = js_string_value_len (str); - - if (argc > 1 && !JS_IsNull (argv[1])) { - /* Custom trim with reject characters */ - const char *reject = JS_ToCString (ctx, argv[1]); - if (!reject) return JS_EXCEPTION; - size_t reject_len = strlen (reject); - - while (start < end) { - uint32_t c = js_string_value_get (str, start); - int found = 0; - for (size_t i = 0; i < reject_len; i++) { - if (c == (uint8_t)reject[i]) { - found = 1; - break; - } - } - if (!found) break; - start++; - } - while (end > start) { - uint32_t c = js_string_value_get (str, end - 1); - int found = 0; - for (size_t i = 0; i < reject_len; i++) { - if (c == (uint8_t)reject[i]) { - found = 1; - break; - } - } - if (!found) break; - end--; - } - JS_FreeCString (ctx, reject); - } else { - /* Default: trim whitespace */ - while (start < end && lre_is_space (js_string_value_get (str, start))) - start++; - while (end > start && lre_is_space (js_string_value_get (str, end - 1))) - end--; - } - - return js_sub_string_val (ctx, str, start, end); -} - -/* text.codepoint(str) - get first codepoint */ -static JSValue js_cell_text_codepoint (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NULL; - if (!JS_IsText (argv[0])) return JS_NULL; - - /* Handle immediate strings directly */ - if (MIST_IsImmediateASCII (argv[0])) { - int plen = MIST_GetImmediateASCIILen (argv[0]); - if (plen == 0) return JS_NULL; - uint32_t c = MIST_GetImmediateASCIIChar (argv[0], 0); - return JS_NewInt32 (ctx, c); - } - - /* Heap string */ - JSText *p = JS_VALUE_GET_STRING (argv[0]); - int plen = (int)JSText_len (p); - if (plen == 0) { - return JS_NULL; - } - - uint32_t c = string_get (p, 0); - /* Handle surrogate pairs */ - if (c >= 0xD800 && c <= 0xDBFF && plen > 1) { - uint32_t c2 = string_get (p, 1); - if (c2 >= 0xDC00 && c2 <= 0xDFFF) { - c = 0x10000 + ((c - 0xD800) << 10) + (c2 - 0xDC00); - } - } - - return JS_NewInt32 (ctx, c); -} - -/* Helpers (C, not C++). Put these above js_cell_text_replace in the same C - * file. */ - -static JSText *pt_concat_value_to_string_free (JSContext *ctx, JSText *b, JSValue v) { - JSValue s = JS_ToString (ctx, v); - if (JS_IsException (s)) return NULL; - b = pretext_concat_value (ctx, b, s); - return b; -} - -/* Build replacement for a match at `found`. - * - If replacement is a function: call it as (match_text, found) - * - Else if replacement exists: duplicate it - * - Else: empty string - * Returns JS_EXCEPTION on error, JS_NULL if callback returned null, or any - * JSValue. This function CONSUMES match_val if it calls a function (it will - * free it via args cleanup), otherwise it will free match_val before - * returning. - */ -static JSValue make_replacement (JSContext *ctx, int argc, JSValue *argv, int found, JSValue match_val) { - JSValue rep; - - if (argc > 2 && JS_IsFunction (argv[2])) { - JSValue args[2]; - args[0] = match_val; - args[1] = JS_NewInt32 (ctx, found); - rep = JS_Call (ctx, argv[2], JS_NULL, 2, args); - return rep; - } - - - if (argc > 2) return argv[2]; - return JS_KEY_empty; -} - -static int JS_IsRegExp (JSContext *ctx, JSValue v) { - if (!JS_IsObject (v)) return 0; - - JSValue exec = JS_GetPropertyStr (ctx, v, "exec"); - if (JS_IsException (exec)) return -1; - - int ok = JS_IsFunction (exec); - return ok; -} - -/* text.replace(text, target, replacement, limit) - * - * Return a new text in which the target is replaced by the replacement. - * - * target: string (pattern support not implemented here; non-string => null) - * replacement: string or function(match_text, start_pos) -> string|null - * limit: max number of replacements (default unlimited). Limit includes null - * matches. - * - * Empty target semantics: - * Replace at every boundary: before first char, between chars, after last - * char. Example: replace("abc", "", "-") => "-a-b-c-" Boundaries count toward - * limit even if replacement returns null. - */ - -static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2) return JS_NULL; - - if (!JS_IsText (argv[0])) - return JS_NULL; - - int target_is_regex = 0; - { - if (JS_IsText (argv[1])) { - target_is_regex = 0; - } else if (JS_IsObject (argv[1]) && JS_IsRegExp (ctx, argv[1])) { - target_is_regex = 1; - } else { - return JS_NULL; - } - } - - if (!JS_VALUE_IS_TEXT (argv[0])) - return JS_ThrowInternalError (ctx, "Replace must have text in arg0."); - - int len = js_string_value_len (argv[0]); - - int32_t limit = -1; - if (argc > 3 && !JS_IsNull (argv[3])) { - if (JS_ToInt32 (ctx, &limit, argv[3])) { return JS_NULL; } - if (limit < 0) limit = -1; - } - - JSText *b = pretext_init (ctx, len); - if (!b) return JS_EXCEPTION; - - /* Root b across all allocating calls */ - JSGCRef b_ref; - JS_PushGCRef (ctx, &b_ref); - b_ref.val = JS_MKPTR (b); - -/* Macro to re-chase b from GC ref before use */ -#define B_RECHASE() b = (JSText *)chase (b_ref.val) - -/* Macro to update b_ref after b changes */ -#define B_UPDATE(new_b) do { b = (new_b); b_ref.val = JS_MKPTR (b); } while(0) -#define B_CLEANUP() JS_PopGCRef (ctx, &b_ref) - - if (!target_is_regex) { - if (!JS_VALUE_IS_TEXT (argv[1])) { - B_CLEANUP (); - return JS_ThrowInternalError ( - ctx, "Second arg of replace must be pattern or text."); - } - - int t_len = js_string_value_len (argv[1]); - - if (t_len == 0) { - int32_t count = 0; - - for (int boundary = 0; boundary <= len; boundary++) { - if (limit >= 0 && count >= limit) break; - - JSValue match = JS_KEY_empty; - if (JS_IsException (match)) { B_CLEANUP (); goto fail_str_target; } - - JSValue rep = make_replacement (ctx, argc, argv, boundary, match); - if (JS_IsException (rep)) { B_CLEANUP (); goto fail_str_target; } - - count++; - - if (!JS_IsNull (rep)) { - B_RECHASE (); - B_UPDATE (pt_concat_value_to_string_free (ctx, b, rep)); - if (!b) { B_CLEANUP (); goto fail_str_target; } - } - - if (boundary < len) { - JSValue ch = js_sub_string_val (ctx, argv[0], boundary, boundary + 1); - if (JS_IsException (ch)) { B_CLEANUP (); goto fail_str_target; } - B_RECHASE (); - B_UPDATE (pretext_concat_value (ctx, b, ch)); - if (!b) { B_CLEANUP (); goto fail_str_target; } - } - } - - B_RECHASE (); - B_CLEANUP (); - return pretext_end (ctx, b); - } - - int pos = 0; - int32_t count = 0; - - while (pos <= len - t_len && (limit < 0 || count < limit)) { - int found = -1; - - /* Search for pattern using character-by-character comparison */ - for (int i = pos; i <= len - t_len; i++) { - int match = 1; - for (int j = 0; j < t_len; j++) { - if (js_string_value_get (argv[0], i + j) != js_string_value_get (argv[1], j)) { - match = 0; - break; - } - } - if (match) { - found = i; - break; - } - } - if (found < 0) break; - - if (found > pos) { - int sub_len = found - pos; - JSText *sub_str = js_alloc_string (ctx, sub_len); - if (!sub_str) { B_CLEANUP (); goto fail_str_target; } - for (int i = 0; i < sub_len; i++) { - string_put (sub_str, i, js_string_value_get (argv[0], pos + i)); - } - sub_str->length = sub_len; - JSValue sub = pretext_end (ctx, sub_str); - if (JS_IsException (sub)) { B_CLEANUP (); goto fail_str_target; } - B_RECHASE (); - B_UPDATE (pretext_concat_value (ctx, b, sub)); - if (!b) { B_CLEANUP (); goto fail_str_target; } - } - - /* Build match substring manually */ - JSText *match_str = js_alloc_string (ctx, t_len); - if (!match_str) { B_CLEANUP (); goto fail_str_target; } - for (int i = 0; i < t_len; i++) { - string_put (match_str, i, js_string_value_get (argv[0], found + i)); - } - match_str->length = t_len; - JSValue match = pretext_end (ctx, match_str); - if (JS_IsException (match)) { B_CLEANUP (); goto fail_str_target; } - - JSValue rep = make_replacement (ctx, argc, argv, found, match); - if (JS_IsException (rep)) { B_CLEANUP (); goto fail_str_target; } - - count++; - - if (!JS_IsNull (rep)) { - B_RECHASE (); - B_UPDATE (pt_concat_value_to_string_free (ctx, b, rep)); - if (!b) { B_CLEANUP (); goto fail_str_target; } - } - - pos = found + t_len; - } - - if (pos < len) { - int sub_len = len - pos; - JSText *sub_str = js_alloc_string (ctx, sub_len); - if (!sub_str) { B_CLEANUP (); goto fail_str_target; } - for (int i = 0; i < sub_len; i++) { - string_put (sub_str, i, js_string_value_get (argv[0], pos + i)); - } - sub_str->length = sub_len; - JSValue sub = pretext_end (ctx, sub_str); - if (JS_IsException (sub)) { B_CLEANUP (); goto fail_str_target; } - B_RECHASE (); - B_UPDATE (pretext_concat_value (ctx, b, sub)); - if (!b) { B_CLEANUP (); goto fail_str_target; } - } - - B_RECHASE (); - B_CLEANUP (); - return pretext_end (ctx, b); - - fail_str_target: - return JS_EXCEPTION; - } - - /* Regex target - root rx across allocating calls */ - JSGCRef rx_ref; - JS_PushGCRef (ctx, &rx_ref); - rx_ref.val = argv[1]; - -#define RX_CLEANUP() do { JS_PopGCRef (ctx, &rx_ref); B_CLEANUP (); } while(0) -#define RX_VAL (rx_ref.val) - - JSValue orig_last_index = JS_GetPropertyStr (ctx, RX_VAL, "lastIndex"); - if (JS_IsException (orig_last_index)) { RX_CLEANUP (); goto fail_rx; } - int have_orig_last_index = 1; - - int pos = 0; - int32_t count = 0; - - while (pos <= len && (limit < 0 || count < limit)) { - if (JS_SetPropertyStr (ctx, RX_VAL, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) { - RX_CLEANUP (); goto fail_rx; - } - - JSValue sub_str = js_sub_string_val (ctx, argv[0], pos, len); - if (JS_IsException (sub_str)) { RX_CLEANUP (); goto fail_rx; } - - JSValue exec_res - = JS_Invoke (ctx, RX_VAL, JS_KEY_exec, 1, (JSValue *)&sub_str); - if (JS_IsException (exec_res)) { RX_CLEANUP (); goto fail_rx; } - - if (JS_IsNull (exec_res)) { - break; - } - - JSValue idx_val = JS_GetPropertyStr (ctx, exec_res, "index"); - if (JS_IsException (idx_val)) { RX_CLEANUP (); goto fail_rx; } - - int32_t local_index = 0; - if (JS_ToInt32 (ctx, &local_index, idx_val)) { RX_CLEANUP (); goto fail_rx; } - - if (local_index < 0) local_index = 0; - int found = pos + local_index; - if (found < pos) found = pos; - if (found > len) { - break; - } - - JSValue match = JS_GetPropertyStr (ctx, exec_res, "match"); - if (JS_IsException (match)) { RX_CLEANUP (); goto fail_rx; } - - JSValue end_val = JS_GetPropertyStr (ctx, exec_res, "end"); - if (JS_IsException (end_val)) { RX_CLEANUP (); goto fail_rx; } - - int32_t end = 0; - if (JS_ToInt32 (ctx, &end, end_val)) { RX_CLEANUP (); goto fail_rx; } - - int match_len = end - local_index; - if (match_len < 0) match_len = 0; - - if (found > pos) { - JSValue prefix = js_sub_string_val (ctx, argv[0], pos, found); - if (JS_IsException (prefix)) { RX_CLEANUP (); goto fail_rx; } - B_RECHASE (); - B_UPDATE (pretext_concat_value (ctx, b, prefix)); - if (!b) { RX_CLEANUP (); goto fail_rx; } - } - - JSValue rep = make_replacement (ctx, argc, argv, found, match); - if (JS_IsException (rep)) { RX_CLEANUP (); goto fail_rx; } - - count++; - - if (!JS_IsNull (rep)) { - B_RECHASE (); - B_UPDATE (pt_concat_value_to_string_free (ctx, b, rep)); - if (!b) { RX_CLEANUP (); goto fail_rx; } - } - - pos = found + match_len; - if (match_len == 0) { - if (pos < len) - pos++; - else - break; - } - } - - if (pos < len) { - JSValue tail = js_sub_string_val (ctx, argv[0], pos, len); - if (JS_IsException (tail)) { RX_CLEANUP (); goto fail_rx; } - B_RECHASE (); - B_UPDATE (pretext_concat_value (ctx, b, tail)); - if (!b) { RX_CLEANUP (); goto fail_rx; } - } - - if (have_orig_last_index) - JS_SetPropertyStr (ctx, RX_VAL, "lastIndex", orig_last_index); - - B_RECHASE (); - RX_CLEANUP (); - return pretext_end (ctx, b); - -fail_rx: - if (!JS_IsNull (orig_last_index) && !JS_IsException (orig_last_index)) { - JS_SetPropertyStr (ctx, argv[1], "lastIndex", orig_last_index); - } else { - } - return JS_EXCEPTION; -} - -#undef RX_CLEANUP -#undef RX_VAL -#undef B_RECHASE -#undef B_UPDATE -#undef B_CLEANUP - -/* text.search(str, target, from) - find substring or regex match */ -static JSValue js_cell_text_search (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2) return JS_NULL; - - if (!JS_IsText (argv[0])) return JS_NULL; - - int target_is_regex = 0; - if (JS_IsText (argv[1])) { - target_is_regex = 0; - } else if (JS_IsObject (argv[1]) && JS_IsRegExp (ctx, argv[1])) { - target_is_regex = 1; - } else { - return JS_NULL; - } - - JSValue str = argv[0]; - int len = js_string_value_len (str); - - int from = 0; - if (argc > 2 && !JS_IsNull (argv[2])) { - if (JS_ToInt32 (ctx, &from, argv[2])) { - return JS_NULL; - } - if (from < 0) from += len; - if (from < 0) from = 0; - } - if (from > len) { - return JS_NULL; - } - - if (!target_is_regex) { - JSValue target = argv[1]; - int t_len = js_string_value_len (target); - - int result = -1; - if (len >= t_len) { - for (int i = from; i <= len - t_len; i++) { - int match = 1; - for (int j = 0; j < t_len; j++) { - if (js_string_value_get (str, i + j) != js_string_value_get (target, j)) { - match = 0; - break; - } - } - if (match) { - result = i; - break; - } - } - } - - if (result == -1) return JS_NULL; - return JS_NewInt32 (ctx, result); - } - - /* Regex target - root rx and str across allocating calls */ - JSGCRef rx_ref, str_ref; - JS_PushGCRef (ctx, &rx_ref); - rx_ref.val = argv[1]; - JS_PushGCRef (ctx, &str_ref); - str_ref.val = str; - -#define SEARCH_CLEANUP() do { JS_PopGCRef (ctx, &str_ref); JS_PopGCRef (ctx, &rx_ref); } while(0) - - JSValue orig_last_index = JS_GetPropertyStr (ctx, rx_ref.val, "lastIndex"); - if (JS_IsException (orig_last_index)) { - SEARCH_CLEANUP (); - return JS_EXCEPTION; - } - int have_orig_last_index = 1; - - if (JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) - goto fail_rx_search; - - JSValue sub_str = js_sub_string_val (ctx, str_ref.val, from, len); - if (JS_IsException (sub_str)) goto fail_rx_search; - - JSValue exec_res = JS_Invoke (ctx, rx_ref.val, JS_KEY_exec, 1, (JSValue *)&sub_str); - if (JS_IsException (exec_res)) goto fail_rx_search; - - if (JS_IsNull (exec_res)) { - if (have_orig_last_index) - JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); - SEARCH_CLEANUP (); - return JS_NULL; - } - - JSValue idx_val = JS_GetPropertyStr (ctx, exec_res, "index"); - if (JS_IsException (idx_val)) { - goto fail_rx_search; - } - - int32_t local_index = 0; - if (JS_ToInt32 (ctx, &local_index, idx_val)) { - goto fail_rx_search; - } - - if (local_index < 0) local_index = 0; - - if (have_orig_last_index) - JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); - - SEARCH_CLEANUP (); - return JS_NewInt32 (ctx, from + local_index); - -fail_rx_search: - if (!JS_IsNull (orig_last_index) && !JS_IsException (orig_last_index)) { - JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); - } - SEARCH_CLEANUP (); - return JS_EXCEPTION; -} -#undef SEARCH_CLEANUP -static inline uint32_t js_str_get (JSText *s, int idx) { - return string_get (s, idx); -} - -static int js_str_find_range (JSText *hay, int from, int to, JSText *needle) { - int nlen = (int)JSText_len (needle); - int hlen = (int)JSText_len (hay); - - if (from < 0) from = 0; - if (to < 0) to = 0; - if (to > hlen) to = hlen; - if (from > to) return -1; - - if (nlen == 0) return from; - if (nlen > (to - from)) return -1; - - int limit = to - nlen; - for (int i = from; i <= limit; i++) { - int j = 0; - for (; j < nlen; j++) { - if (js_str_get (hay, i + j) != js_str_get (needle, j)) break; - } - if (j == nlen) return i; - } - return -1; -} - -/* text_extract(text, pattern, from?, to?) - return array of matches or null - - literal pattern: [match] - - regexp pattern: [full_match, cap1, cap2, ...] -*/ -static JSValue js_cell_text_extract (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2) return JS_NULL; - - if (!JS_IsText (argv[0])) - return JS_NULL; - - JSValue str = argv[0]; - int len = js_string_value_len (str); - - int from = 0; - if (argc >= 3 && !JS_IsNull (argv[2])) { - if (JS_ToInt32 (ctx, &from, argv[2])) return JS_EXCEPTION; - if (from < 0) from += len; - if (from < 0) from = 0; - if (from > len) from = len; - } - - int to = len; - if (argc >= 4 && !JS_IsNull (argv[3])) { - if (JS_ToInt32 (ctx, &to, argv[3])) return JS_EXCEPTION; - if (to < 0) to += len; - if (to < 0) to = 0; - if (to > len) to = len; - } - - if (from > to) return JS_NULL; - - /* RegExp path: convert new exec result record -> classic array */ - if (JS_IsObject (argv[1]) && JS_IsRegExp (ctx, argv[1])) { - /* Root rx, str, out across allocating calls */ - JSGCRef rx_ref, str_ref, out_ref; - JS_PushGCRef (ctx, &rx_ref); - rx_ref.val = argv[1]; - JS_PushGCRef (ctx, &str_ref); - str_ref.val = str; - JS_PushGCRef (ctx, &out_ref); - out_ref.val = JS_NULL; - -#define EXT_CLEANUP() do { JS_PopGCRef (ctx, &out_ref); JS_PopGCRef (ctx, &str_ref); JS_PopGCRef (ctx, &rx_ref); } while(0) - - JSValue orig_last_index = JS_GetPropertyStr (ctx, rx_ref.val, "lastIndex"); - if (JS_IsException (orig_last_index)) { EXT_CLEANUP (); return JS_EXCEPTION; } - - if (JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) - goto fail_rx; - - JSValue sub_str; - if (from == 0 && to == len) { - sub_str = str_ref.val; - } else { - sub_str = js_sub_string_val (ctx, str_ref.val, from, to); - if (JS_IsException (sub_str)) goto fail_rx; - } - - JSGCRef exec_ref; - JS_PushGCRef (ctx, &exec_ref); - exec_ref.val = JS_Invoke (ctx, rx_ref.val, JS_KEY_exec, 1, (JSValue *)&sub_str); - if (JS_IsException (exec_ref.val)) { JS_PopGCRef (ctx, &exec_ref); goto fail_rx; } - - if (JS_IsNull (exec_ref.val)) { - JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); - JS_PopGCRef (ctx, &exec_ref); - EXT_CLEANUP (); - return JS_NULL; - } - - /* Build result array */ - JSValue out = JS_NewArray (ctx); - if (JS_IsException (out)) { - JS_PopGCRef (ctx, &exec_ref); - goto fail_rx; - } - out_ref.val = out; - - /* out[0] = exec_res.match */ - JSValue match0 = JS_GetPropertyStr (ctx, exec_ref.val, "match"); - if (JS_IsException (match0)) { - JS_PopGCRef (ctx, &exec_ref); - goto fail_rx; - } - out = out_ref.val; - if (JS_SetPropertyUint32 (ctx, out, 0, match0) < 0) { - JS_PopGCRef (ctx, &exec_ref); - goto fail_rx; - } - - /* Append capture groups from exec_res.captures */ - JSValue caps = JS_GetPropertyStr (ctx, exec_ref.val, "captures"); - JS_PopGCRef (ctx, &exec_ref); /* exec_ref no longer needed */ - if (!JS_IsException (caps) && JS_IsArray (caps)) { - int64_t caps_len = 0; - if (js_get_length64 (ctx, &caps_len, caps) == 0 && caps_len > 0) { - for (int64_t i = 0; i < caps_len; i++) { - JSValue cap = JS_GetPropertyInt64 (ctx, caps, i); - if (JS_IsException (cap)) { - goto fail_rx; - } - out = out_ref.val; - if (JS_SetPropertyInt64 (ctx, out, i + 1, cap) < 0) { - goto fail_rx; - } - } - } - } - - JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); - out = out_ref.val; - EXT_CLEANUP (); - return out; - - fail_rx: - if (!JS_IsNull (orig_last_index) && !JS_IsException (orig_last_index)) { - JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); - } - EXT_CLEANUP (); - return JS_EXCEPTION; - } -#undef EXT_CLEANUP - - /* Literal text path */ - JSValue needle_val = JS_ToString (ctx, argv[1]); - if (JS_IsException (needle_val)) return JS_EXCEPTION; - str = argv[0]; /* refresh after potential GC */ - - int needle_len = js_string_value_len (needle_val); - - /* Find needle in str[from..to) */ - int pos = -1; - if (needle_len == 0) { - pos = from; - } else if (needle_len <= (to - from)) { - int limit = to - needle_len; - for (int i = from; i <= limit; i++) { - int j = 0; - for (; j < needle_len; j++) { - if (js_string_value_get (str, i + j) != js_string_value_get (needle_val, j)) - break; - } - if (j == needle_len) { pos = i; break; } - } - } - - if (pos < 0) return JS_NULL; - - JSGCRef arr_ref; - JS_PushGCRef (ctx, &arr_ref); - arr_ref.val = JS_NewArrayLen (ctx, 1); - if (JS_IsException (arr_ref.val)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } - str = argv[0]; /* refresh after potential GC */ - - JSValue match = js_sub_string_val (ctx, str, pos, pos + needle_len); - if (JS_IsException (match)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } - - JSValue arr = arr_ref.val; - JS_PopGCRef (ctx, &arr_ref); - - if (JS_SetPropertyUint32 (ctx, arr, 0, match) < 0) - return JS_EXCEPTION; - - return arr; -} - -/* format(text, collection, transformer) - string interpolation - * Finds {name} or {name:format} patterns and substitutes from collection. - * Collection can be array (index by number) or record (index by key). - * Transformer can be function(value, format) or record of functions. - */ -static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2) return JS_NULL; - if (!JS_IsText (argv[0])) return JS_NULL; - - JSValue text_val = argv[0]; - JSValue collection = argv[1]; - JSValue transformer = argc > 2 ? argv[2] : JS_NULL; - - int is_array = JS_IsArray (collection); - int is_record = JS_IsRecord (collection); - if (!is_array && !is_record) return JS_NULL; - - int len = js_string_value_len (text_val); - - /* Root text_val, collection, transformer BEFORE any allocation */ - JSGCRef res_ref, text_ref, coll_ref, xform_ref; - JS_PushGCRef (ctx, &res_ref); - JS_PushGCRef (ctx, &text_ref); - JS_PushGCRef (ctx, &coll_ref); - JS_PushGCRef (ctx, &xform_ref); - res_ref.val = JS_NULL; - text_ref.val = text_val; - coll_ref.val = collection; - xform_ref.val = transformer; - -#define FMT_CLEANUP() do { \ - JS_PopGCRef (ctx, &xform_ref); \ - JS_PopGCRef (ctx, &coll_ref); \ - JS_PopGCRef (ctx, &text_ref); \ - JS_PopGCRef (ctx, &res_ref); \ -} while(0) - - JSText *result = pretext_init (ctx, len); - if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } - res_ref.val = JS_MKPTR (result); - - int pos = 0; - while (pos < len) { - text_val = text_ref.val; - /* Find next '{' */ - int brace_start = -1; - for (int i = pos; i < len; i++) { - if (js_string_value_get (text_val, i) == '{') { - brace_start = i; - break; - } - } - - if (brace_start < 0) { - /* No more braces, copy rest of string */ - JSValue tail = js_sub_string_val (ctx, text_ref.val, pos, len); - if (JS_IsException (tail)) { FMT_CLEANUP(); return JS_EXCEPTION; } - result = (JSText *)chase (res_ref.val); - result = pretext_concat_value (ctx, result, tail); - if (result) res_ref.val = JS_MKPTR (result); - break; - } - - /* Copy text before brace */ - if (brace_start > pos) { - JSValue prefix = js_sub_string_val (ctx, text_ref.val, pos, brace_start); - if (JS_IsException (prefix)) { FMT_CLEANUP(); return JS_EXCEPTION; } - result = (JSText *)chase (res_ref.val); - result = pretext_concat_value (ctx, result, prefix); - if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } - res_ref.val = JS_MKPTR (result); - } - - /* Find closing '}' */ - text_val = text_ref.val; - int brace_end = -1; - for (int i = brace_start + 1; i < len; i++) { - if (js_string_value_get (text_val, i) == '}') { - brace_end = i; - break; - } - } - - if (brace_end < 0) { - /* No closing brace, copy '{' and continue */ - JSValue ch = js_sub_string_val (ctx, text_ref.val, brace_start, brace_start + 1); - if (JS_IsException (ch)) { FMT_CLEANUP(); return JS_EXCEPTION; } - result = (JSText *)chase (res_ref.val); - result = pretext_concat_value (ctx, result, ch); - if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } - res_ref.val = JS_MKPTR (result); - pos = brace_start + 1; - continue; - } - - /* Extract content between braces */ - JSValue middle = js_sub_string_val (ctx, text_ref.val, brace_start + 1, brace_end); - if (JS_IsException (middle)) { FMT_CLEANUP(); return JS_EXCEPTION; } - - /* Split on ':' to get name and format_spec */ - int middle_len = js_string_value_len (middle); - - int colon_pos = -1; - for (int i = 0; i < middle_len; i++) { - if (js_string_value_get (middle, i) == ':') { - colon_pos = i; - break; - } - } - - JSValue name_val, format_spec; - if (colon_pos >= 0) { - name_val = js_sub_string_val (ctx, middle, 0, colon_pos); - format_spec = js_sub_string_val (ctx, middle, colon_pos + 1, middle_len); - } else { - name_val = middle; - format_spec = JS_KEY_empty; - } - - /* Get value from collection — protect with GCRef since JS_Call below can trigger GC */ - JSGCRef cv_ref; - JS_PushGCRef (ctx, &cv_ref); - cv_ref.val = JS_NULL; - if (is_array) { - int name_len = js_string_value_len (name_val); - int32_t idx = 0; - int valid = (name_len > 0); - for (int ni = 0; ni < name_len && valid; ni++) { - uint32_t ch = js_string_value_get (name_val, ni); - if (ch >= '0' && ch <= '9') - idx = idx * 10 + (ch - '0'); - else - valid = 0; - } - if (valid && idx >= 0) { - cv_ref.val = JS_GetPropertyUint32 (ctx, coll_ref.val, (uint32_t)idx); - } - } else { - cv_ref.val = JS_GetProperty (ctx, coll_ref.val, name_val); - } - - /* Try to get substitution */ - JSValue substitution = JS_NULL; - int made_substitution = 0; - - if (!JS_IsNull (xform_ref.val)) { - if (JS_IsFunction (xform_ref.val)) { - JSValue args[2] = { cv_ref.val, format_spec }; - JSValue result_val = JS_Call (ctx, xform_ref.val, JS_NULL, 2, args); - if (JS_IsText (result_val)) { - substitution = result_val; - made_substitution = 1; - } - } else if (JS_IsRecord (xform_ref.val)) { - JSValue func = JS_GetProperty (ctx, xform_ref.val, format_spec); - if (JS_IsFunction (func)) { - JSValue result_val = JS_Call (ctx, func, JS_NULL, 1, &cv_ref.val); - if (JS_IsText (result_val)) { - substitution = result_val; - made_substitution = 1; - } - } - } - } - - if (!made_substitution && JS_IsNumber (cv_ref.val) && !JS_IsNull (format_spec)) { - JSValue text_method = JS_GetPropertyStr (ctx, cv_ref.val, "text"); - if (JS_IsFunction (text_method)) { - JSValue result_val = JS_Call (ctx, text_method, cv_ref.val, 1, &format_spec); - if (JS_IsText (result_val)) { - substitution = result_val; - made_substitution = 1; - } - } - } - - if (!made_substitution && !JS_IsNull (cv_ref.val)) { - JSValue conv_text_val = JS_ToString (ctx, cv_ref.val); - if (JS_IsText (conv_text_val)) { - substitution = conv_text_val; - made_substitution = 1; - } - } - JS_PopGCRef (ctx, &cv_ref); - - result = (JSText *)chase (res_ref.val); - if (made_substitution) { - result = pretext_concat_value (ctx, result, substitution); - if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } - res_ref.val = JS_MKPTR (result); - } else { - JSValue orig = js_sub_string_val (ctx, text_ref.val, brace_start, brace_end + 1); - if (JS_IsException (orig)) { FMT_CLEANUP(); return JS_EXCEPTION; } - result = (JSText *)chase (res_ref.val); - result = pretext_concat_value (ctx, result, orig); - if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } - res_ref.val = JS_MKPTR (result); - } - - pos = brace_end + 1; - } - - result = (JSText *)chase (res_ref.val); - FMT_CLEANUP(); -#undef FMT_CLEANUP - return pretext_end (ctx, result); -} - -/* print(args...) - print arguments to stdout */ -static JSValue js_print (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - for (int i = 0; i < argc; i++) { - const char *str = JS_ToCString (ctx, argv[i]); - if (str) { - fputs (str, stdout); - JS_FreeCString (ctx, str); - } - if (i < argc - 1) fputc (' ', stdout); - } - fputc ('\n', stdout); - return JS_NULL; -} - -static JSValue js_stacktrace (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - cJSON *stack = JS_GetStack(ctx); - if (stack) { - int n = cJSON_GetArraySize(stack); - for (int i = 0; i < n; i++) { - cJSON *fr = cJSON_GetArrayItem(stack, i); - const char *fn = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fr, "function")); - const char *file = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fr, "file")); - int line = (int)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(fr, "line")); - int col = (int)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(fr, "column")); - printf(" at %s (%s:%d:%d)\n", fn ? fn : "", file ? file : "", line, col); - } - cJSON_Delete(stack); - } - return JS_NULL; -} - -/* ---------------------------------------------------------------------------- - * Bytecode dump function (always available, for debugging) - * ---------------------------------------------------------------------------- - */ - -/* Opcode names for bytecode dump */ -static const char *dump_opcode_names[] = { -#define FMT(f) -#define DEF(id, size, n_pop, n_push, f) #id, -#define def(id, size, n_pop, n_push, f) -#include "quickjs-opcode.h" -#undef def -#undef DEF -#undef FMT -}; - -static void dump_bytecode_opcodes (JSContext *ctx, JSFunctionBytecode *b) { - const uint8_t *tab = b->byte_code_buf; - int len = b->byte_code_len; - const JSValue *cpool = b->cpool; - uint32_t cpool_count = b->cpool_count; - const JSVarDef *vars = b->vardefs ? b->vardefs + b->arg_count : NULL; - int var_count = b->var_count; - int pos = 0; - - while (pos < len) { - int op = tab[pos]; - if (op >= OP_COUNT) { - printf (" %5d: \n", pos, op); - pos++; - continue; - } - const JSOpCode *oi = &short_opcode_info (op); - int size = oi->size; - if (pos + size > len) { - printf (" %5d: \n", pos, op); - break; - } - - printf (" %5d: %s", pos, dump_opcode_names[op]); - pos++; - - switch (oi->fmt) { - case OP_FMT_none_int: - printf (" %d", op - OP_push_0); - break; - case OP_FMT_npopx: - printf (" %d", op - OP_call0); - break; - case OP_FMT_u8: - printf (" %u", get_u8 (tab + pos)); - break; - case OP_FMT_i8: - printf (" %d", get_i8 (tab + pos)); - break; - case OP_FMT_u16: - case OP_FMT_npop: - printf (" %u", get_u16 (tab + pos)); - break; - case OP_FMT_npop_u16: - printf (" %u,%u", get_u16 (tab + pos), get_u16 (tab + pos + 2)); - break; - case OP_FMT_i16: - printf (" %d", get_i16 (tab + pos)); - break; - case OP_FMT_i32: - printf (" %d", get_i32 (tab + pos)); - break; - case OP_FMT_u32: - printf (" %u", get_u32 (tab + pos)); - break; - case OP_FMT_label8: - printf (" ->%d", pos + get_i8 (tab + pos)); - break; - case OP_FMT_label16: - printf (" ->%d", pos + get_i16 (tab + pos)); - break; - case OP_FMT_label: - printf (" ->%u", pos + get_u32 (tab + pos)); - break; - case OP_FMT_label_u16: - printf (" ->%u,%u", pos + get_u32 (tab + pos), get_u16 (tab + pos + 4)); - break; - case OP_FMT_const8: { - uint32_t idx = get_u8 (tab + pos); - printf (" [%u]", idx); - if (idx < cpool_count) { - printf (": "); - JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[idx], NULL); - } - break; - } - case OP_FMT_const: { - uint32_t idx = get_u32 (tab + pos); - printf (" [%u]", idx); - if (idx < cpool_count) { - printf (": "); - JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[idx], NULL); - } - break; - } - case OP_FMT_key: { - uint32_t idx = get_u32 (tab + pos); - printf (" [%u]", idx); - if (idx < cpool_count) { - printf (": "); - JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[idx], NULL); - } - break; - } - case OP_FMT_key_u8: { - uint32_t idx = get_u32 (tab + pos); - printf (" [%u],%d", idx, get_u8 (tab + pos + 4)); - if (idx < cpool_count) { - printf (": "); - JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[idx], NULL); - } - break; - } - case OP_FMT_key_u16: { - uint32_t idx = get_u32 (tab + pos); - printf (" [%u],%d", idx, get_u16 (tab + pos + 4)); - if (idx < cpool_count) { - printf (": "); - JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[idx], NULL); - } - break; - } - case OP_FMT_none_loc: - printf (" loc%d", (op - OP_get_loc0) % 4); - break; - case OP_FMT_loc8: { - int idx = get_u8 (tab + pos); - printf (" loc%d", idx); - if (vars && idx < var_count) { - char buf[KEY_GET_STR_BUF_SIZE]; - printf (": %s", JS_KeyGetStr (ctx, buf, sizeof(buf), vars[idx].var_name)); - } - break; - } - case OP_FMT_loc: { - int idx = get_u16 (tab + pos); - printf (" loc%d", idx); - if (vars && idx < var_count) { - char buf[KEY_GET_STR_BUF_SIZE]; - printf (": %s", JS_KeyGetStr (ctx, buf, sizeof(buf), vars[idx].var_name)); - } - break; - } - case OP_FMT_none_arg: - printf (" arg%d", (op - OP_get_arg0) % 4); - break; - case OP_FMT_arg: - printf (" arg%d", get_u16 (tab + pos)); - break; - default: - break; - } - printf ("\n"); - pos += size - 1; /* -1 because we already incremented pos after reading opcode */ - } -} - -void JS_DumpFunctionBytecode (JSContext *ctx, JSValue func_val) { - JSFunctionBytecode *b = NULL; - - if (!JS_IsPtr (func_val)) { - printf ("JS_DumpFunctionBytecode: not a pointer value\n"); - return; - } - - /* Get the object header to check type */ - void *ptr = JS_VALUE_GET_PTR (func_val); - objhdr_t hdr = *(objhdr_t *)ptr; - uint8_t type = objhdr_type (hdr); - - if (type == OBJ_FUNCTION) { - /* It's a JSFunction - extract bytecode */ - JSFunction *fn = (JSFunction *)ptr; - if (fn->kind != JS_FUNC_KIND_BYTECODE) { - printf ("JS_DumpFunctionBytecode: not a bytecode function (kind=%d)\n", fn->kind); - return; - } - b = fn->u.func.function_bytecode; - } else if (type == OBJ_CODE) { - /* It's raw bytecode from js_create_function */ - b = (JSFunctionBytecode *)ptr; - } else { - printf ("JS_DumpFunctionBytecode: not a function or bytecode (type=%d)\n", type); - return; - } - - if (!b) { - printf ("JS_DumpFunctionBytecode: no bytecode\n"); - return; - } - - char buf[KEY_GET_STR_BUF_SIZE]; - - printf ("=== Bytecode Dump ===\n"); - - /* Function name */ - const char *fname = JS_KeyGetStr (ctx, buf, sizeof(buf), b->func_name); - printf ("Function: %s\n", fname ? fname : ""); - - /* Debug info */ - if (b->has_debug && !JS_IsNull (b->debug.filename)) { - printf ("File: %s\n", JS_KeyGetStr (ctx, buf, sizeof(buf), b->debug.filename)); - } - - /* Basic stats */ - printf ("Args: %d, Vars: %d, Stack: %d\n", b->arg_count, b->var_count, b->stack_size); - printf ("Bytecode length: %d bytes\n", b->byte_code_len); - - /* Arguments */ - if (b->arg_count > 0 && b->vardefs) { - printf ("\nArguments:\n"); - for (int i = 0; i < b->arg_count; i++) { - printf (" %d: %s\n", i, JS_KeyGetStr (ctx, buf, sizeof(buf), b->vardefs[i].var_name)); - } - } - - /* Local variables */ - if (b->var_count > 0 && b->vardefs) { - printf ("\nLocal variables:\n"); - for (int i = 0; i < b->var_count; i++) { - JSVarDef *vd = &b->vardefs[b->arg_count + i]; - const char *kind = vd->is_const ? "const" : vd->is_lexical ? "let" : "var"; - printf (" %d: %s %s", i, kind, JS_KeyGetStr (ctx, buf, sizeof(buf), vd->var_name)); - if (vd->scope_level) printf (" [scope:%d]", vd->scope_level); - printf ("\n"); - } - } - - /* Closure variables */ - if (b->closure_var_count > 0) { - printf ("\nClosure variables:\n"); - for (int i = 0; i < b->closure_var_count; i++) { - JSClosureVar *cv = &b->closure_var[i]; - printf (" %d: %s (%s:%s%d)\n", i, - JS_KeyGetStr (ctx, buf, sizeof(buf), cv->var_name), - cv->is_local ? "local" : "parent", - cv->is_arg ? "arg" : "loc", - cv->var_idx); - } - } - - /* Constant pool */ - if (b->cpool_count > 0) { - printf ("\nConstant pool (%d entries):\n", b->cpool_count); - for (uint32_t i = 0; i < b->cpool_count; i++) { - printf (" [%u]: ", i); - JS_PrintValue (ctx, js_dump_value_write, stdout, b->cpool[i], NULL); - printf ("\n"); - } - } - - /* Bytecode instructions */ - printf ("\nBytecode:\n"); - dump_bytecode_opcodes (ctx, b); - - printf ("=== End Bytecode Dump ===\n"); -} - -/* ---------------------------------------------------------------------------- - * array function and sub-functions - * ---------------------------------------------------------------------------- - */ - -/* array(arg, arg2, arg3, arg4) - main array function */ -static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - (void)this_val; - if (argc < 1) return JS_NULL; - - JSValue arg = argv[0]; - - /* array(number) - create array of size */ - /* array(number, initial_value) - create array with initial values */ - if (JS_IsNumber (arg)) { - if (!JS_IsInteger (arg)) - return JS_ThrowTypeError (ctx, "Array expected an integer."); - - int len = JS_VALUE_GET_INT (arg); - if (len < 0) return JS_NULL; - - JSGCRef result_ref; - JSValue result = JS_NewArrayLen (ctx, len); - if (JS_IsException (result)) return result; - - if (argc > 1 && JS_IsFunction (argv[1])) { - /* Fill with function results - GC-safe */ - JSGCRef func_ref; - JS_PushGCRef (ctx, &func_ref); - func_ref.val = argv[1]; - int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func_ref.val))->length; - - if (arity >= 1) { - for (int i = 0; i < len; i++) { - JSValue idx_arg = JS_NewInt32 (ctx, i); - JS_PUSH_VALUE (ctx, result); - JSValue val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &idx_arg, 0); - JS_POP_VALUE (ctx, result); - if (JS_IsException (val)) { JS_PopGCRef (ctx, &func_ref); return JS_EXCEPTION; } - JSArray *out = JS_VALUE_GET_ARRAY (result); /* re-chase after call */ - out->values[i] = val; - } - } else { - for (int i = 0; i < len; i++) { - JS_PUSH_VALUE (ctx, result); - JSValue val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 0, NULL, 0); - JS_POP_VALUE (ctx, result); - if (JS_IsException (val)) { JS_PopGCRef (ctx, &func_ref); return JS_EXCEPTION; } - JSArray *out = JS_VALUE_GET_ARRAY (result); /* re-chase after call */ - out->values[i] = val; - } - } - JS_PopGCRef (ctx, &func_ref); - } else if (argc > 1) { - /* Fill with value */ - JSArray *out = JS_VALUE_GET_ARRAY (result); - for (int i = 0; i < len; i++) - out->values[i] = argv[1]; - } - return result; - } - - /* array(array) - copy */ - /* array(array, function) - map */ - /* array(array, another_array) - concat */ - /* array(array, from, to) - slice */ - if (JS_IsArray (arg)) { - /* Root input array and arg1 for GC safety in this section */ - JSGCRef arg0_ref, arg1_ref; - JS_PushGCRef (ctx, &arg0_ref); - JS_PushGCRef (ctx, &arg1_ref); - arg0_ref.val = argv[0]; - arg1_ref.val = argc > 1 ? argv[1] : JS_NULL; - - JSArray *arr = JS_VALUE_GET_ARRAY (arg0_ref.val); - int len = arr->len; - - if (argc < 2 || JS_IsNull (argv[1])) { - /* Copy */ - JSValue result = JS_NewArrayLen (ctx, len); - if (JS_IsException (result)) { - JS_PopGCRef (ctx, &arg1_ref); - JS_PopGCRef (ctx, &arg0_ref); - return result; - } - arr = JS_VALUE_GET_ARRAY (arg0_ref.val); /* re-chase after allocation */ - JSArray *out = JS_VALUE_GET_ARRAY (result); - for (int i = 0; i < len; i++) { - out->values[i] = arr->values[i]; - } - out->len = len; - JS_PopGCRef (ctx, &arg1_ref); - JS_PopGCRef (ctx, &arg0_ref); - return result; - } - - if (JS_IsFunction (arg1_ref.val)) { - /* Map - GC-safe: root result throughout, use rooted refs for func and array */ - int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (arg1_ref.val))->length; - - int reverse = argc > 2 && JS_ToBool (ctx, argv[2]); - JSValue exit_val = argc > 3 ? argv[3] : JS_NULL; - - JSGCRef result_ref; - JS_PushGCRef (ctx, &result_ref); /* Push first - sets val to JS_NULL */ - result_ref.val = JS_NewArray (ctx); /* Then assign */ - if (JS_IsException (result_ref.val)) { - JS_PopGCRef (ctx, &result_ref); - JS_PopGCRef (ctx, &arg1_ref); - JS_PopGCRef (ctx, &arg0_ref); - return result_ref.val; - } - - if (arity >= 2) { - if (reverse) { - for (int i = len - 1; i >= 0; i--) { - /* Re-chase input array each iteration */ - arr = JS_VALUE_GET_ARRAY (arg0_ref.val); - if (i >= (int)arr->len) continue; /* array may have shrunk */ - JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, i) }; - JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 2, args, 0); - if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } - if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break; - if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } - } - } else { - for (int i = 0; i < len; i++) { - arr = JS_VALUE_GET_ARRAY (arg0_ref.val); - if (i >= (int)arr->len) break; - JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, i) }; - JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 2, args, 0); - if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } - if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break; - if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } - } - } - } else { - if (reverse) { - for (int i = len - 1; i >= 0; i--) { - arr = JS_VALUE_GET_ARRAY (arg0_ref.val); - if (i >= (int)arr->len) continue; - JSValue item = arr->values[i]; - JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 1, &item, 0); - if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } - if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break; - if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } - } - } else { - for (int i = 0; i < len; i++) { - arr = JS_VALUE_GET_ARRAY (arg0_ref.val); - if (i >= (int)arr->len) break; - JSValue item = arr->values[i]; - JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 1, &item, 0); - if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } - if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break; - if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } - } - } - } - JSValue result = result_ref.val; - JS_PopGCRef (ctx, &result_ref); - JS_PopGCRef (ctx, &arg1_ref); - JS_PopGCRef (ctx, &arg0_ref); - return result; - } - - if (JS_IsArray (arg1_ref.val)) { - /* Concat */ - JSArray *arr2 = JS_VALUE_GET_ARRAY (arg1_ref.val); - int len2 = arr2->len; - - JSValue result = JS_NewArrayLen (ctx, len + len2); - if (JS_IsException (result)) { - JS_PopGCRef (ctx, &arg1_ref); - JS_PopGCRef (ctx, &arg0_ref); - return result; - } - /* Re-chase arrays after allocation */ - arr = JS_VALUE_GET_ARRAY (arg0_ref.val); - arr2 = JS_VALUE_GET_ARRAY (arg1_ref.val); - JSArray *out = JS_VALUE_GET_ARRAY (result); - - for (int i = 0; i < len; i++) { - out->values[i] = arr->values[i]; - } - for (int i = 0; i < len2; i++) { - out->values[len + i] = arr2->values[i]; - } - out->len = len + len2; - JS_PopGCRef (ctx, &arg1_ref); - JS_PopGCRef (ctx, &arg0_ref); - return result; - } - - if (JS_IsNumber (argv[1])) { - /* Slice */ - if (!JS_IsInteger (argv[1])) { - JS_PopGCRef (ctx, &arg1_ref); - JS_PopGCRef (ctx, &arg0_ref); - return JS_NULL; - } - int from = JS_VALUE_GET_INT (argv[1]); - int to; - if (argc > 2 && !JS_IsNull (argv[2])) { - if (!JS_IsNumber (argv[2]) || !JS_IsInteger (argv[2])) { - JS_PopGCRef (ctx, &arg1_ref); - JS_PopGCRef (ctx, &arg0_ref); - return JS_NULL; - } - to = JS_VALUE_GET_INT (argv[2]); - } else { - to = len; - } - - if (from < 0) from += len; - if (to < 0) to += len; - if (from < 0 || from > len || to < 0 || to > len || from > to) { - JS_PopGCRef (ctx, &arg1_ref); - JS_PopGCRef (ctx, &arg0_ref); - return JS_NULL; - } - - int slice_len = to - from; - JSValue result = JS_NewArrayLen (ctx, slice_len); - if (JS_IsException (result)) { - JS_PopGCRef (ctx, &arg1_ref); - JS_PopGCRef (ctx, &arg0_ref); - return result; - } - /* Re-chase arrays after allocation */ - arr = JS_VALUE_GET_ARRAY (arg0_ref.val); - JSArray *out = JS_VALUE_GET_ARRAY (result); - - for (int i = 0; i < slice_len; i++) { - out->values[i] = arr->values[from + i]; - } - out->len = slice_len; - JS_PopGCRef (ctx, &arg1_ref); - JS_PopGCRef (ctx, &arg0_ref); - return result; - } - - JS_PopGCRef (ctx, &arg1_ref); - JS_PopGCRef (ctx, &arg0_ref); - return JS_NULL; - } - - /* array(object) - keys */ - if (JS_IsRecord (arg)) { - /* Return object keys */ - return JS_GetOwnPropertyNames (ctx, arg); - } - - /* array(text) - split into characters */ - /* array(text, separator) - split by separator */ - /* array(text, length) - dice into chunks */ - if (JS_VALUE_IS_TEXT (arg)) { - int len = js_string_value_len (arg); - - if (argc < 2 || JS_IsNull (argv[1])) { - /* Split into characters */ - JSValue result = JS_NewArrayLen (ctx, len); - if (JS_IsException (result)) { return result; } - JSArray *out = JS_VALUE_GET_ARRAY (result); - for (int i = 0; i < len; i++) { - JSValue ch = js_sub_string_val (ctx, arg, i, i + 1); - if (JS_IsException (ch)) { - return JS_EXCEPTION; - } - out->values[i] = ch; - } - out->len = len; - return result; - } - - if (JS_VALUE_IS_TEXT (argv[1])) { - /* Split by separator */ - const char *cstr = JS_ToCString (ctx, arg); - const char *sep = JS_ToCString (ctx, argv[1]); - if (!cstr || !sep) { - if (cstr) JS_FreeCString (ctx, cstr); - if (sep) JS_FreeCString (ctx, sep); - return JS_EXCEPTION; - } - - size_t sep_len = strlen (sep); - - /* Count the number of parts first */ - int64_t count = 0; - if (sep_len == 0) { - count = len; - } else { - const char *pos = cstr; - const char *found; - count = 1; - while ((found = strstr (pos, sep)) != NULL) { - count++; - pos = found + sep_len; - } - } - - JSValue result = JS_NewArrayLen (ctx, count); - if (JS_IsException (result)) { - JS_FreeCString (ctx, cstr); - JS_FreeCString (ctx, sep); - return result; - } - - int64_t idx = 0; - const char *pos = cstr; - const char *found; - - if (sep_len == 0) { - for (int i = 0; i < len; i++) { - JSValue ch = js_sub_string_val (ctx, arg, i, i + 1); - JS_SetPropertyInt64 (ctx, result, idx++, ch); - } - } else { - while ((found = strstr (pos, sep)) != NULL) { - JSValue part = JS_NewStringLen (ctx, pos, found - pos); - JS_SetPropertyInt64 (ctx, result, idx++, part); - pos = found + sep_len; - } - JSValue part = JS_NewString (ctx, pos); - JS_SetPropertyInt64 (ctx, result, idx++, part); - } - - JS_FreeCString (ctx, cstr); - JS_FreeCString (ctx, sep); - return result; - } - - if (JS_IsObject (argv[1]) && JS_IsRegExp (ctx, argv[1])) { - /* Split by regex (manual "global" iteration; ignore g flag semantics) */ - /* Root rx, result, arg across allocating calls */ - JSGCRef rx_ref, res_ref, arg_ref; - JS_PushGCRef (ctx, &rx_ref); - rx_ref.val = argv[1]; - JS_PushGCRef (ctx, &res_ref); - res_ref.val = JS_NULL; - JS_PushGCRef (ctx, &arg_ref); - arg_ref.val = arg; - -#define RXS_CLEANUP() do { JS_PopGCRef (ctx, &arg_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &rx_ref); } while(0) - - JSValue result = JS_NewArray (ctx); - if (JS_IsException (result)) { RXS_CLEANUP (); return result; } - res_ref.val = result; - - /* Save & restore lastIndex to avoid mutating caller-visible state */ - JSValue orig_last_index = JS_GetPropertyStr (ctx, rx_ref.val, "lastIndex"); - if (JS_IsException (orig_last_index)) { - RXS_CLEANUP (); - return JS_EXCEPTION; - } - - int pos = 0; - int64_t out_idx = 0; - - while (pos <= len) { - if (JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) - goto fail_rx_split; - - JSValue sub_str = js_sub_string_val (ctx, arg_ref.val, pos, len); - if (JS_IsException (sub_str)) goto fail_rx_split; - - JSValue exec_res - = JS_Invoke (ctx, rx_ref.val, JS_KEY_exec, 1, (JSValue *)&sub_str); - if (JS_IsException (exec_res)) goto fail_rx_split; - - if (JS_IsNull (exec_res)) { - JSValue tail = js_sub_string_val (ctx, arg_ref.val, pos, len); - if (JS_IsException (tail)) goto fail_rx_split; - result = res_ref.val; - if (JS_ArrayPush (ctx, &result, tail) < 0) { res_ref.val = result; goto fail_rx_split; } - res_ref.val = result; - break; - } - - JSValue idx_val = JS_GetPropertyStr (ctx, exec_res, "index"); - if (JS_IsException (idx_val)) goto fail_rx_split; - - int32_t local_index = 0; - if (JS_ToInt32 (ctx, &local_index, idx_val)) goto fail_rx_split; - if (local_index < 0) local_index = 0; - - int found = pos + local_index; - if (found < pos) found = pos; - if (found > len) { - JSValue tail = js_sub_string_val (ctx, arg_ref.val, pos, len); - if (JS_IsException (tail)) goto fail_rx_split; - result = res_ref.val; - JS_SetPropertyInt64 (ctx, result, out_idx++, tail); - break; - } - - JSValue end_val = JS_GetPropertyStr (ctx, exec_res, "end"); - if (JS_IsException (end_val)) goto fail_rx_split; - - int32_t end = 0; - if (JS_ToInt32 (ctx, &end, end_val)) goto fail_rx_split; - - int match_len = end - local_index; - if (match_len < 0) match_len = 0; - - JSValue part = js_sub_string_val (ctx, arg_ref.val, pos, found); - if (JS_IsException (part)) goto fail_rx_split; - result = res_ref.val; - if (JS_ArrayPush (ctx, &result, part) < 0) { res_ref.val = result; goto fail_rx_split; } - res_ref.val = result; - - pos = found + match_len; - if (match_len == 0) { - if (found >= len) { - JSValue empty = JS_NewStringLen (ctx, "", 0); - if (JS_IsException (empty)) goto fail_rx_split; - result = res_ref.val; - if (JS_ArrayPush (ctx, &result, empty) < 0) { res_ref.val = result; goto fail_rx_split; } - res_ref.val = result; - break; - } - pos = found + 1; - } - } - - JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); - result = res_ref.val; - RXS_CLEANUP (); - return result; - - fail_rx_split: - if (!JS_IsException (orig_last_index)) { - JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); - } - RXS_CLEANUP (); - return JS_EXCEPTION; - } -#undef RXS_CLEANUP - - if (JS_VALUE_IS_NUMBER (argv[1])) { - /* Dice into chunks */ - int chunk_len; - if (JS_ToInt32 (ctx, &chunk_len, argv[1])) - return JS_NULL; - if (chunk_len <= 0) - return JS_NULL; - - int64_t count = (len + chunk_len - 1) / chunk_len; - JSValue result = JS_NewArrayLen (ctx, count); - if (JS_IsException (result)) - return result; - - int64_t idx = 0; - for (int i = 0; i < len; i += chunk_len) { - int end = i + chunk_len; - if (end > len) end = len; - JSValue chunk = js_sub_string_val (ctx, arg, i, end); - if (JS_IsException (chunk)) - return JS_EXCEPTION; - JS_SetPropertyInt64 (ctx, result, idx++, chunk); - } - - return result; - } - - return JS_NULL; - } - - return JS_NULL; -} - -/* array.reduce(arr, fn, initial, reverse) */ -/* GC-safe reduce: re-chase array after each call */ -static JSValue js_cell_array_reduce (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2) return JS_NULL; - if (!JS_IsArray (argv[0])) return JS_NULL; - if (!JS_IsFunction (argv[1])) return JS_NULL; - - /* GC-safe: root argv[0] and argv[1] for the duration of this function */ - JSGCRef arr_ref, func_ref; - JS_PushGCRef (ctx, &arr_ref); - JS_PushGCRef (ctx, &func_ref); - arr_ref.val = argv[0]; - func_ref.val = argv[1]; - - JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); - word_t len = arr->len; - - int reverse = argc > 3 && JS_ToBool (ctx, argv[3]); - JSGCRef acc_ref; - JSValue acc; - - if (argc < 3 || JS_IsNull (argv[2])) { - if (len == 0) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } - if (len == 1) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return arr->values[0]; } - - if (reverse) { - acc = arr->values[len - 1]; - for (word_t i = len - 1; i > 0; i--) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); - if (i - 1 >= arr->len) continue; - JSValue args[2] = { acc, arr->values[i - 1] }; - JS_PUSH_VALUE (ctx, acc); - JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); - JS_POP_VALUE (ctx, acc); - if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } - acc = new_acc; - } - } else { - acc = arr->values[0]; - for (word_t i = 1; i < len; i++) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); - if (i >= arr->len) break; - JSValue args[2] = { acc, arr->values[i] }; - JS_PUSH_VALUE (ctx, acc); - JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); - JS_POP_VALUE (ctx, acc); - if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } - acc = new_acc; - } - } - } else { - if (len == 0) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return argv[2]; } - acc = argv[2]; - - if (reverse) { - for (word_t i = len; i > 0; i--) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); - if (i - 1 >= arr->len) continue; - JSValue args[2] = { acc, arr->values[i - 1] }; - JS_PUSH_VALUE (ctx, acc); - JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); - JS_POP_VALUE (ctx, acc); - if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } - acc = new_acc; - } - } else { - for (word_t i = 0; i < len; i++) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); - if (i >= arr->len) break; - JSValue args[2] = { acc, arr->values[i] }; - JS_PUSH_VALUE (ctx, acc); - JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); - JS_POP_VALUE (ctx, acc); - if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } - acc = new_acc; - } - } - } - - JS_PopGCRef (ctx, &func_ref); - JS_PopGCRef (ctx, &arr_ref); - return acc; -} - -/* array.for(arr, fn, reverse, exit) - GC-safe */ -static JSValue js_cell_array_for (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2) return JS_NULL; - if (!JS_IsArray (argv[0])) return JS_NULL; - if (!JS_IsFunction (argv[1])) return JS_NULL; - - /* GC-safe: root argv[0] and argv[1] */ - JSGCRef arr_ref, func_ref; - JS_PushGCRef (ctx, &arr_ref); - JS_PushGCRef (ctx, &func_ref); - arr_ref.val = argv[0]; - func_ref.val = argv[1]; - - JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); - - word_t len = arr->len; - if (len == 0) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } - - int reverse = argc > 2 && JS_ToBool (ctx, argv[2]); - JSValue exit_val = argc > 3 ? argv[3] : JS_NULL; - - /* Determine function arity */ - int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func_ref.val))->length; - - if (reverse) { - for (word_t i = len; i > 0; i--) { - /* Re-chase array each iteration */ - arr = JS_VALUE_GET_ARRAY (arr_ref.val); - if (i - 1 >= arr->len) continue; - JSValue result; - if (arity == 1) { - JSValue item = arr->values[i - 1]; - result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); - } else { - JSValue args[2]; - args[0] = arr->values[i - 1]; - args[1] = JS_NewInt32 (ctx, (int32_t)(i - 1)); - result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); - } - - if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } - if (!JS_IsNull (exit_val) && js_strict_eq (ctx, result, exit_val)) { - JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); - return result; - } - } - } else { - for (word_t i = 0; i < len; i++) { - /* Re-chase array each iteration */ - arr = JS_VALUE_GET_ARRAY (arr_ref.val); - if (i >= arr->len) break; - JSValue result; - if (arity == 1) { - JSValue item = arr->values[i]; - result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); - } else { - JSValue args[2]; - args[0] = arr->values[i]; - args[1] = JS_NewInt32 (ctx, (int32_t)i); - result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); - } - - if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } - if (!JS_IsNull (exit_val) && js_strict_eq (ctx, result, exit_val)) { - JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); - return result; - } - } - } - - JS_PopGCRef (ctx, &func_ref); - JS_PopGCRef (ctx, &arr_ref); - return JS_NULL; -} - -/* array.find(arr, fn, reverse, from) */ -/* array.find(arr, fn, reverse, from) - GC-safe */ -static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2) return JS_NULL; - if (!JS_IsArray (argv[0])) return JS_NULL; - - /* GC-safe: root argv[0] */ - JSGCRef arr_ref; - JS_PushGCRef (ctx, &arr_ref); - arr_ref.val = argv[0]; - - JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); - word_t len = arr->len; - - int reverse = argc > 2 && JS_ToBool (ctx, argv[2]); - int32_t from; - if (argc > 3 && !JS_IsNull (argv[3])) { - if (JS_ToInt32 (ctx, &from, argv[3])) { JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } - } else { - from = reverse ? (int32_t)(len - 1) : 0; - } - - if (!JS_IsFunction (argv[1])) { - /* Compare exactly - no GC concerns since no calls */ - JSValue target = argv[1]; - if (reverse) { - for (int32_t i = from; i >= 0; i--) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); - if ((word_t)i >= arr->len) continue; - if (js_strict_eq (ctx, arr->values[i], target)) { - JS_PopGCRef (ctx, &arr_ref); - return JS_NewInt32 (ctx, i); - } - } - } else { - for (word_t i = (word_t)from; i < len; i++) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); - if (i >= arr->len) break; - if (js_strict_eq (ctx, arr->values[i], target)) { - JS_PopGCRef (ctx, &arr_ref); - return JS_NewInt32 (ctx, (int32_t)i); - } - } - } - JS_PopGCRef (ctx, &arr_ref); - return JS_NULL; - } - - /* Use function predicate - must re-chase after each call */ - JSGCRef func_ref; - JS_PushGCRef (ctx, &func_ref); - func_ref.val = argv[1]; - int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func_ref.val))->length; - - if (arity == 2) { - if (reverse) { - for (int32_t i = from; i >= 0; i--) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); - if ((word_t)i >= arr->len) continue; - JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, i) }; - JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); - if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } - if (JS_ToBool (ctx, result)) { - JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); - return JS_NewInt32 (ctx, i); - } - } - } else { - for (word_t i = (word_t)from; i < len; i++) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); - if (i >= arr->len) break; - JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, (int32_t)i) }; - JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); - if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } - if (JS_ToBool (ctx, result)) { - JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); - return JS_NewInt32 (ctx, (int32_t)i); - } - } - } - } else { - if (reverse) { - for (int32_t i = from; i >= 0; i--) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); - if ((word_t)i >= arr->len) continue; - JSValue item = arr->values[i]; - JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); - if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } - if (JS_ToBool (ctx, result)) { - JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); - return JS_NewInt32 (ctx, i); - } - } - } else { - for (word_t i = (word_t)from; i < len; i++) { - arr = JS_VALUE_GET_ARRAY (arr_ref.val); - if (i >= arr->len) break; - JSValue item = arr->values[i]; - JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); - if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } - if (JS_ToBool (ctx, result)) { - JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); - return JS_NewInt32 (ctx, (int32_t)i); - } - } - } - } - - JS_PopGCRef (ctx, &func_ref); - JS_PopGCRef (ctx, &arr_ref); - return JS_NULL; -} - -/* array.filter(arr, fn) - GC-safe */ -static JSValue js_cell_array_filter (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2) return JS_NULL; - if (!JS_IsArray (argv[0])) return JS_NULL; - if (!JS_IsFunction (argv[1])) return JS_NULL; - - /* Protect input array and function throughout the loop */ - JSGCRef input_ref, func_ref, result_ref; - JS_PushGCRef (ctx, &input_ref); - JS_PushGCRef (ctx, &func_ref); - JS_PushGCRef (ctx, &result_ref); - input_ref.val = argv[0]; - func_ref.val = argv[1]; - - JSArray *arr = JS_VALUE_GET_ARRAY (input_ref.val); - word_t len = arr->len; - - result_ref.val = JS_NewArray (ctx); - if (JS_IsException (result_ref.val)) { - JS_PopGCRef (ctx, &result_ref); - JS_PopGCRef (ctx, &func_ref); - JS_PopGCRef (ctx, &input_ref); - return JS_EXCEPTION; - } - - int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func_ref.val))->length; - - for (word_t i = 0; i < len; i++) { - /* Re-chase input array each iteration (it may have moved) */ - arr = JS_VALUE_GET_ARRAY (input_ref.val); - if (i >= arr->len) break; - JSValue item = arr->values[i]; - - JSValue val; - if (arity >= 2) { - JSValue args[2] = { item, JS_NewInt32 (ctx, (int32_t)i) }; - val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); - } else if (arity == 1) { - val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); - } else { - val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 0, NULL, 0); - } - - if (JS_IsException (val)) { - JS_PopGCRef (ctx, &result_ref); - JS_PopGCRef (ctx, &func_ref); - JS_PopGCRef (ctx, &input_ref); - return JS_EXCEPTION; - } - - if (JS_VALUE_GET_TAG (val) == JS_TAG_BOOL) { - if (JS_VALUE_GET_BOOL (val)) { - /* Re-read item after the call (GC may have moved the input array) */ - arr = JS_VALUE_GET_ARRAY (input_ref.val); - if (i < arr->len) { - item = arr->values[i]; - if (js_intrinsic_array_push (ctx, &result_ref.val, item) < 0) { - JS_PopGCRef (ctx, &result_ref); - JS_PopGCRef (ctx, &func_ref); - JS_PopGCRef (ctx, &input_ref); - return JS_EXCEPTION; - } - } - } - } else { - JS_PopGCRef (ctx, &result_ref); - JS_PopGCRef (ctx, &func_ref); - JS_PopGCRef (ctx, &input_ref); - return JS_NULL; - } - } - - JSValue result = result_ref.val; - JS_PopGCRef (ctx, &result_ref); - JS_PopGCRef (ctx, &func_ref); - JS_PopGCRef (ctx, &input_ref); - return result; -} - -/* array.sort(arr, select) - GC-safe */ -static JSValue js_cell_array_sort (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - (void)this_val; - if (argc < 1) return JS_NULL; - if (!JS_IsArray (argv[0])) return JS_NULL; - - /* GC-safe: root argv[0] */ - JSGCRef arr_ref; - JS_PushGCRef (ctx, &arr_ref); - arr_ref.val = argv[0]; - - JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); - word_t len = arr->len; - - JSValue result = JS_NewArrayLen (ctx, len); - if (JS_IsException (result)) { JS_PopGCRef (ctx, &arr_ref); return result; } - - if (len == 0) { JS_PopGCRef (ctx, &arr_ref); return result; } - - /* Root result across allocating calls */ - JSGCRef result_ref; - JS_PushGCRef (ctx, &result_ref); - result_ref.val = result; - - /* Re-chase arr after allocation */ - arr = JS_VALUE_GET_ARRAY (arr_ref.val); - len = arr->len; - - /* Use alloca for temporary working arrays - they won't move during GC */ - JSValue *items = alloca (sizeof (JSValue) * len); - double *keys = alloca (sizeof (double) * len); - char **str_keys = NULL; - int is_string = 0; - - /* Extract items and keys - re-chase arrays as needed */ - for (word_t i = 0; i < len; i++) { - /* Re-chase input and key arrays each iteration */ - arr = JS_VALUE_GET_ARRAY (arr_ref.val); - if (i >= arr->len) break; - items[i] = arr->values[i]; - - JSValue key; - if (argc < 2 || JS_IsNull (argv[1])) { - key = items[i]; - } else if (JS_VALUE_GET_TAG (argv[1]) == JS_TAG_INT) { - /* Numeric index - use for nested arrays */ - int32_t idx; - JS_ToInt32 (ctx, &idx, argv[1]); - if (JS_IsArray (items[i])) { - JSArray *nested = JS_VALUE_GET_ARRAY (items[i]); - if (idx >= 0 && (word_t)idx < nested->len) - key = nested->values[idx]; - else - key = JS_NULL; - } else { - key = JS_GetPropertyInt64 (ctx, items[i], idx); - /* Re-read items[i] after potential GC */ - arr = JS_VALUE_GET_ARRAY (arr_ref.val); - items[i] = arr->values[i]; - } - } else if (JS_VALUE_GET_TAG (argv[1]) == JS_TAG_STRING - || JS_VALUE_GET_TAG (argv[1]) == JS_TAG_STRING_IMM) { - JSValue prop_key = js_key_from_string (ctx, argv[1]); - /* Re-read items[i] after allocation (js_key_from_string can trigger GC) */ - arr = JS_VALUE_GET_ARRAY (arr_ref.val); - items[i] = arr->values[i]; - key = JS_GetProperty (ctx, items[i], prop_key); - } else if (argc >= 2 && JS_IsArray (argv[1])) { - /* Re-chase key array */ - JSArray *key_arr = JS_VALUE_GET_ARRAY (argv[1]); - if (i < key_arr->len) - key = key_arr->values[i]; - else - key = JS_NULL; - } else { - key = items[i]; - } - - if (JS_IsException (key)) { - if (str_keys) { - for (word_t j = 0; j < i; j++) - JS_FreeCString (ctx, str_keys[j]); - js_free (ctx, str_keys); - } - JS_PopGCRef (ctx, &result_ref); - JS_PopGCRef (ctx, &arr_ref); - return JS_EXCEPTION; - } - - int key_tag = JS_VALUE_GET_TAG (key); - if (key_tag == JS_TAG_INT || key_tag == JS_TAG_FLOAT64 || key_tag == JS_TAG_SHORT_FLOAT) { - JS_ToFloat64 (ctx, &keys[i], key); - if (i == 0) is_string = 0; - } else if (key_tag == JS_TAG_STRING || key_tag == JS_TAG_STRING_IMM) { - if (i == 0) { - is_string = 1; - str_keys = alloca (sizeof (char *) * len); - } - if (is_string) { str_keys[i] = (char *)JS_ToCString (ctx, key); } - } else { - if (str_keys) { - for (word_t j = 0; j < i; j++) - JS_FreeCString (ctx, str_keys[j]); - } - JS_PopGCRef (ctx, &result_ref); - JS_PopGCRef (ctx, &arr_ref); - return JS_NULL; - } - } - - /* Re-read all items from GC-safe source after key extraction (GC may have moved them) */ - arr = JS_VALUE_GET_ARRAY (arr_ref.val); - for (word_t i = 0; i < len && i < arr->len; i++) - items[i] = arr->values[i]; - - /* Create index array using alloca */ - int *indices = alloca (sizeof (int) * len); - for (word_t i = 0; i < len; i++) - indices[i] = (int)i; - - /* Simple insertion sort (stable) */ - for (word_t i = 1; i < len; i++) { - int temp = indices[i]; - int j = (int)i - 1; - while (j >= 0) { - int cmp; - if (is_string) { - cmp = strcmp (str_keys[indices[j]], str_keys[temp]); - } else { - double a = keys[indices[j]], b = keys[temp]; - cmp = (a > b) - (a < b); - } - if (cmp <= 0) break; - indices[j + 1] = indices[j]; - j--; - } - indices[j + 1] = temp; - } - - /* Build sorted array directly into output - re-chase result */ - JSArray *out = JS_VALUE_GET_ARRAY (result_ref.val); - for (word_t i = 0; i < len; i++) { - out->values[i] = items[indices[i]]; - } - out->len = len; - - /* Cleanup string keys only (alloca frees automatically) */ - if (str_keys) { - for (word_t i = 0; i < len; i++) - JS_FreeCString (ctx, str_keys[i]); - } - - result = result_ref.val; - JS_PopGCRef (ctx, &result_ref); - JS_PopGCRef (ctx, &arr_ref); - return result; -} - -/* ---------------------------------------------------------------------------- - * object function and sub-functions - * ---------------------------------------------------------------------------- - */ - -/* object(arg, arg2) - main object function */ -static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NULL; - - JSValue arg = argv[0]; - - /* object(object) - shallow mutable copy */ - if (JS_IsObject (arg) && !JS_IsArray (arg) && !JS_IsFunction (arg)) { - if (argc < 2 || JS_IsNull (argv[1])) { - /* Shallow copy - root arg, result, keys across allocating calls */ - JSGCRef arg_ref, res_ref, keys_ref; - JS_PushGCRef (ctx, &arg_ref); - arg_ref.val = arg; - JS_PushGCRef (ctx, &res_ref); - res_ref.val = JS_NewObject (ctx); - JS_PushGCRef (ctx, &keys_ref); - keys_ref.val = JS_NULL; - -#define OBJ_COPY_CLEANUP() do { JS_PopGCRef (ctx, &keys_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &arg_ref); } while(0) - - if (JS_IsException (res_ref.val)) { OBJ_COPY_CLEANUP (); return JS_EXCEPTION; } - - keys_ref.val = JS_GetOwnPropertyNames (ctx, arg_ref.val); - if (JS_IsException (keys_ref.val)) { - OBJ_COPY_CLEANUP (); - return JS_EXCEPTION; - } - uint32_t len; - if (js_get_length32 (ctx, &len, keys_ref.val)) { - OBJ_COPY_CLEANUP (); - return JS_EXCEPTION; - } - - for (uint32_t i = 0; i < len; i++) { - JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); - JSValue val = JS_GetProperty (ctx, arg_ref.val, key); - if (JS_IsException (val)) { - OBJ_COPY_CLEANUP (); - return JS_EXCEPTION; - } - JS_SetProperty (ctx, res_ref.val, key, val); - } - JSValue result = res_ref.val; - OBJ_COPY_CLEANUP (); - return result; - } -#undef OBJ_COPY_CLEANUP - - /* object(object, another_object) - combine */ - if (JS_IsObject (argv[1]) && !JS_IsArray (argv[1])) { - JSGCRef arg_ref, arg2_ref, res_ref, keys_ref; - JS_PushGCRef (ctx, &arg_ref); - arg_ref.val = arg; - JS_PushGCRef (ctx, &arg2_ref); - arg2_ref.val = argv[1]; - JS_PushGCRef (ctx, &res_ref); - res_ref.val = JS_NewObject (ctx); - JS_PushGCRef (ctx, &keys_ref); - keys_ref.val = JS_NULL; - -#define OBJ_COMBINE_CLEANUP() do { JS_PopGCRef (ctx, &keys_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &arg2_ref); JS_PopGCRef (ctx, &arg_ref); } while(0) - - if (JS_IsException (res_ref.val)) { OBJ_COMBINE_CLEANUP (); return JS_EXCEPTION; } - - /* Copy from first object */ - keys_ref.val = JS_GetOwnPropertyNames (ctx, arg_ref.val); - if (JS_IsException (keys_ref.val)) { - OBJ_COMBINE_CLEANUP (); - return JS_EXCEPTION; - } - uint32_t len; - if (js_get_length32 (ctx, &len, keys_ref.val)) { - OBJ_COMBINE_CLEANUP (); - return JS_EXCEPTION; - } - for (uint32_t i = 0; i < len; i++) { - JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); - JSValue val = JS_GetProperty (ctx, arg_ref.val, key); - if (JS_IsException (val)) { - OBJ_COMBINE_CLEANUP (); - return JS_EXCEPTION; - } - JS_SetProperty (ctx, res_ref.val, key, val); - } - - /* Copy from second object */ - keys_ref.val = JS_GetOwnPropertyNames (ctx, arg2_ref.val); - if (JS_IsException (keys_ref.val)) { - OBJ_COMBINE_CLEANUP (); - return JS_EXCEPTION; - } - if (js_get_length32 (ctx, &len, keys_ref.val)) { - OBJ_COMBINE_CLEANUP (); - return JS_EXCEPTION; - } - for (uint32_t i = 0; i < len; i++) { - JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); - JSValue val = JS_GetProperty (ctx, arg2_ref.val, key); - if (JS_IsException (val)) { - OBJ_COMBINE_CLEANUP (); - return JS_EXCEPTION; - } - JS_SetProperty (ctx, res_ref.val, key, val); - } - JSValue result = res_ref.val; - OBJ_COMBINE_CLEANUP (); - return result; - } -#undef OBJ_COMBINE_CLEANUP - - /* object(object, array_of_keys) - select */ - if (JS_IsArray (argv[1])) { - JSGCRef arg_ref, res_ref, karr_ref; - JS_PushGCRef (ctx, &arg_ref); - arg_ref.val = arg; - JS_PushGCRef (ctx, &res_ref); - res_ref.val = JS_NewObject (ctx); - JS_PushGCRef (ctx, &karr_ref); - karr_ref.val = argv[1]; - -#define OBJ_SEL_CLEANUP() do { JS_PopGCRef (ctx, &karr_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &arg_ref); } while(0) - - if (JS_IsException (res_ref.val)) { OBJ_SEL_CLEANUP (); return JS_EXCEPTION; } - - JSArray *keys = JS_VALUE_GET_ARRAY (karr_ref.val); - int len = keys->len; - - for (int i = 0; i < len; i++) { - keys = JS_VALUE_GET_ARRAY (karr_ref.val); /* re-chase each iteration */ - if (i >= (int)keys->len) break; - JSValue key = keys->values[i]; - if (JS_IsText (key)) { - JSValue prop_key = js_key_from_string (ctx, key); - int has = JS_HasProperty (ctx, arg_ref.val, prop_key); - if (has > 0) { - JSValue val = JS_GetProperty (ctx, arg_ref.val, prop_key); - if (!JS_IsException (val)) { - JS_SetProperty (ctx, res_ref.val, prop_key, val); - } - } - } - } - JSValue result = res_ref.val; - OBJ_SEL_CLEANUP (); - return result; - } -#undef OBJ_SEL_CLEANUP - } - - /* object(array_of_keys) - set with true values */ - /* object(array_of_keys, value) - value set */ - /* object(array_of_keys, function) - functional value set */ - if (JS_IsArray (arg)) { - JSArray *keys = JS_VALUE_GET_ARRAY (arg); - int len = keys->len; - - int is_func = argc >= 2 && JS_IsFunction (argv[1]); - - /* Root keys array and func/value BEFORE JS_NewObject which may trigger GC. - argv[] is on the C stack and is NOT a GC root, so after any allocation - that triggers GC, argv[] values become dangling pointers. */ - JSGCRef keys_ref, func_ref, result_ref; - JS_PushGCRef (ctx, &keys_ref); - keys_ref.val = arg; /* use already-read arg, not argv[0] */ - JS_PushGCRef (ctx, &func_ref); - func_ref.val = argc >= 2 ? argv[1] : JS_NULL; - JS_PushGCRef (ctx, &result_ref); - result_ref.val = JS_NULL; - - JSValue result = JS_NewObject (ctx); - if (JS_IsException (result)) { - JS_PopGCRef (ctx, &result_ref); - JS_PopGCRef (ctx, &func_ref); - JS_PopGCRef (ctx, &keys_ref); - return result; - } - result_ref.val = result; - - for (int i = 0; i < len; i++) { - keys = JS_VALUE_GET_ARRAY (keys_ref.val); - if (i >= (int)keys->len) break; - JSValue key = keys->values[i]; - if (JS_IsText (key)) { - /* Use JSValue key directly - create interned key */ - JSValue prop_key = js_key_from_string (ctx, key); - JSValue val; - if (argc < 2 || JS_IsNull (func_ref.val)) { - val = JS_TRUE; - } else if (is_func) { - JSValue arg_key = key; - val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &arg_key, 0); - if (JS_IsException (val)) { - JS_PopGCRef (ctx, &result_ref); - JS_PopGCRef (ctx, &func_ref); - JS_PopGCRef (ctx, &keys_ref); - return JS_EXCEPTION; - } - } else { - val = func_ref.val; - } - JS_SetProperty (ctx, result_ref.val, prop_key, val); - /* prop_key is interned, no need to free */ - } - } - result = JS_PopGCRef (ctx, &result_ref); - JS_PopGCRef (ctx, &func_ref); - JS_PopGCRef (ctx, &keys_ref); - return result; - } - - return JS_NULL; -} - -/* ---------------------------------------------------------------------------- - * fn function and sub-functions - * ---------------------------------------------------------------------------- - */ - -/* fn.apply(func, args) - arity is enforced in JS_CallInternal */ -static JSValue js_cell_fn_apply (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NULL; - if (!JS_IsFunction (argv[0])) return argv[0]; - - JSGCRef func_ref, args_ref; - JS_PushGCRef (ctx, &func_ref); - JS_PushGCRef (ctx, &args_ref); - func_ref.val = argv[0]; - args_ref.val = argc >= 2 ? argv[1] : JS_NULL; - - if (argc < 2) { - JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 0, NULL, 0); - JS_PopGCRef (ctx, &args_ref); - JS_PopGCRef (ctx, &func_ref); - return result; - } - - if (!JS_IsArray (args_ref.val)) { - /* Wrap single value in array */ - JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &args_ref.val, 0); - JS_PopGCRef (ctx, &args_ref); - JS_PopGCRef (ctx, &func_ref); - return result; - } - - JSArray *arr = JS_VALUE_GET_ARRAY (args_ref.val); - int len = arr->len; - - if (len == 0) { - JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 0, NULL, 0); - JS_PopGCRef (ctx, &args_ref); - JS_PopGCRef (ctx, &func_ref); - return result; - } - - JSValue *args = js_malloc (ctx, sizeof (JSValue) * len); - if (!args) { - JS_PopGCRef (ctx, &args_ref); - JS_PopGCRef (ctx, &func_ref); - return JS_EXCEPTION; - } - arr = JS_VALUE_GET_ARRAY (args_ref.val); /* re-chase after malloc via rooted ref */ - - for (int i = 0; i < len; i++) { - args[i] = arr->values[i]; - } - - JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, len, args, 0); - - js_free (ctx, args); - - JS_PopGCRef (ctx, &args_ref); - JS_PopGCRef (ctx, &func_ref); - return result; -} - -/* ============================================================================ - * Blob Intrinsic Type - * ============================================================================ - */ - -/* Helper to check if JSValue is a blob */ -static blob *js_get_blob (JSContext *ctx, JSValue val) { - /* Must be a record, not an array or other object type */ - if (!JS_IsRecord(val)) return NULL; - JSRecord *p = JS_VALUE_GET_OBJ (val); - if (REC_GET_CLASS_ID(p) != JS_CLASS_BLOB) return NULL; - return REC_GET_OPAQUE(p); -} - -/* Helper to create a new blob JSValue */ -static JSValue js_new_blob (JSContext *ctx, blob *b) { - JSValue obj = JS_NewObjectClass (ctx, JS_CLASS_BLOB); - if (JS_IsException (obj)) { - blob_destroy (b); - return obj; - } - JS_SetOpaque (obj, b); - return obj; -} - -/* Blob finalizer */ -static void js_blob_finalizer (JSRuntime *rt, JSValue val) { - blob *b = JS_GetOpaque (val, JS_CLASS_BLOB); - if (b) blob_destroy (b); -} - -/* blob() constructor */ -static JSValue js_blob_constructor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - blob *bd = NULL; - - /* blob() - empty blob */ - if (argc == 0) { - bd = blob_new (0); - } - /* blob(capacity) - blob with initial capacity in bits */ - else if (argc == 1 && JS_IsNumber (argv[0])) { - int64_t capacity_bits; - if (JS_ToInt64 (ctx, &capacity_bits, argv[0]) < 0) return JS_EXCEPTION; - if (capacity_bits < 0) capacity_bits = 0; - bd = blob_new ((size_t)capacity_bits); - } - /* blob(length, logical/random) - blob with fill or random */ - else if (argc == 2 && JS_IsNumber (argv[0])) { - int64_t length_bits; - if (JS_ToInt64 (ctx, &length_bits, argv[0]) < 0) return JS_EXCEPTION; - if (length_bits < 0) length_bits = 0; - - if (JS_IsBool (argv[1])) { - int is_one = JS_ToBool (ctx, argv[1]); - bd = blob_new_with_fill ((size_t)length_bits, is_one); - } else if (JS_IsFunction (argv[1])) { - /* Random function provided */ - size_t bytes = (length_bits + 7) / 8; - bd = blob_new ((size_t)length_bits); - if (bd) { - bd->length = length_bits; - memset (bd->data, 0, bytes); - - size_t bits_written = 0; - while (bits_written < (size_t)length_bits) { - JSValue randval = JS_Call (ctx, argv[1], JS_NULL, 0, NULL); - if (JS_IsException (randval)) { - blob_destroy (bd); - return JS_EXCEPTION; - } - - int64_t fitval; - JS_ToInt64 (ctx, &fitval, randval); - - size_t bits_to_use = length_bits - bits_written; - if (bits_to_use > 52) bits_to_use = 52; - - for (size_t j = 0; j < bits_to_use; j++) { - size_t bit_pos = bits_written + j; - size_t byte_idx = bit_pos / 8; - size_t bit_idx = bit_pos % 8; - - if (fitval & (1LL << j)) - bd->data[byte_idx] |= (uint8_t)(1 << bit_idx); - else - bd->data[byte_idx] &= (uint8_t)~(1 << bit_idx); - } - bits_written += bits_to_use; - } - } - } else { - return JS_ThrowTypeError ( - ctx, "Second argument must be boolean or random function"); - } - } - /* blob(blob, from, to) - copy from another blob */ - else if (argc >= 1 && JS_IsObject (argv[0])) { - blob *src = js_get_blob (ctx, argv[0]); - if (!src) - return JS_ThrowTypeError (ctx, - "blob constructor: argument 1 not a blob"); - int64_t from = 0, to = (int64_t)src->length; - if (argc >= 2 && JS_IsNumber (argv[1])) { - JS_ToInt64 (ctx, &from, argv[1]); - if (from < 0) from = 0; - } - if (argc >= 3 && JS_IsNumber (argv[2])) { - JS_ToInt64 (ctx, &to, argv[2]); - if (to < from) to = from; - if (to > (int64_t)src->length) to = (int64_t)src->length; - } - bd = blob_new_from_blob (src, (size_t)from, (size_t)to); - } - /* blob(text) - create blob from UTF-8 string */ - else if (argc == 1 - && (JS_VALUE_GET_TAG (argv[0]) == JS_TAG_STRING - || JS_VALUE_GET_TAG (argv[0]) == JS_TAG_STRING_IMM)) { - const char *str = JS_ToCString (ctx, argv[0]); - if (!str) return JS_EXCEPTION; - size_t len = strlen (str); - bd = blob_new (len * 8); - if (bd) { - memcpy (bd->data, str, len); - bd->length = len * 8; - } - JS_FreeCString (ctx, str); - } else { - return JS_ThrowTypeError (ctx, "blob constructor: invalid arguments"); - } - - if (!bd) return JS_ThrowOutOfMemory (ctx); - - return js_new_blob (ctx, bd); -} - -/* blob.write_bit(logical) */ -static JSValue js_blob_write_bit (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) - return JS_ThrowTypeError (ctx, "write_bit(logical) requires 1 argument"); - blob *bd = js_get_blob (ctx, this_val); - if (!bd) return JS_ThrowTypeError (ctx, "write_bit: not called on a blob"); - - int bit_val; - if (JS_IsNumber (argv[0])) { - int32_t num; - JS_ToInt32 (ctx, &num, argv[0]); - if (num != 0 && num != 1) - return JS_ThrowTypeError ( - ctx, "write_bit: value must be true, false, 0, or 1"); - bit_val = num; - } else { - bit_val = JS_ToBool (ctx, argv[0]); - } - - if (blob_write_bit (bd, bit_val) < 0) - return JS_ThrowTypeError (ctx, - "write_bit: cannot write (maybe stone or OOM)"); - return JS_NULL; -} - -/* blob.write_blob(second_blob) */ -static JSValue js_blob_write_blob (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) - return JS_ThrowTypeError (ctx, - "write_blob(second_blob) requires 1 argument"); - blob *bd = js_get_blob (ctx, this_val); - if (!bd) return JS_ThrowTypeError (ctx, "write_blob: not called on a blob"); - blob *second = js_get_blob (ctx, argv[0]); - if (!second) - return JS_ThrowTypeError (ctx, "write_blob: argument must be a blob"); - - if (blob_write_blob (bd, second) < 0) - return JS_ThrowTypeError (ctx, - "write_blob: cannot write to stone blob or OOM"); - - return JS_NULL; -} - -/* blob.write_number(number) - write dec64 */ -static JSValue js_blob_write_number (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) - return JS_ThrowTypeError (ctx, "write_number(number) requires 1 argument"); - blob *bd = js_get_blob (ctx, this_val); - if (!bd) - return JS_ThrowTypeError (ctx, "write_number: not called on a blob"); - - double d; - if (JS_ToFloat64 (ctx, &d, argv[0]) < 0) return JS_EXCEPTION; - - if (blob_write_dec64 (bd, d) < 0) - return JS_ThrowTypeError ( - ctx, "write_number: cannot write to stone blob or OOM"); - - return JS_NULL; -} - -/* blob.write_fit(value, len) */ -static JSValue js_blob_write_fit (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2) - return JS_ThrowTypeError (ctx, - "write_fit(value, len) requires 2 arguments"); - - blob *bd = js_get_blob (ctx, this_val); - if (!bd) return JS_ThrowTypeError (ctx, "write_fit: not called on a blob"); - - int64_t value; - int32_t len; - - if (JS_ToInt64 (ctx, &value, argv[0]) < 0) return JS_EXCEPTION; - if (JS_ToInt32 (ctx, &len, argv[1]) < 0) return JS_EXCEPTION; - - if (blob_write_fit (bd, value, len) < 0) - return JS_ThrowTypeError (ctx, - "write_fit: value doesn't fit or stone blob"); - - return JS_NULL; -} - -/* blob.write_text(text) */ -static JSValue js_blob_write_text (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) - return JS_ThrowTypeError (ctx, "write_text(text) requires 1 argument"); - - blob *bd = js_get_blob (ctx, this_val); - if (!bd) return JS_ThrowTypeError (ctx, "write_text: not called on a blob"); - - const char *str = JS_ToCString (ctx, argv[0]); - if (!str) return JS_EXCEPTION; - - if (blob_write_text (bd, str) < 0) { - JS_FreeCString (ctx, str); - return JS_ThrowTypeError (ctx, - "write_text: cannot write to stone blob or OOM"); - } - - JS_FreeCString (ctx, str); - return JS_NULL; -} - -/* blob.write_pad(block_size) */ -static JSValue js_blob_write_pad (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) - return JS_ThrowTypeError (ctx, - "write_pad(block_size) requires 1 argument"); - - blob *bd = js_get_blob (ctx, this_val); - if (!bd) return JS_ThrowTypeError (ctx, "write_pad: not called on a blob"); - - int32_t block_size; - if (JS_ToInt32 (ctx, &block_size, argv[0]) < 0) return JS_EXCEPTION; - - if (blob_write_pad (bd, block_size) < 0) - return JS_ThrowTypeError (ctx, "write_pad: cannot write"); - - return JS_NULL; -} - -/* blob.w16(value) - write 16-bit value */ -static JSValue js_blob_w16 (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) - return JS_ThrowTypeError (ctx, "w16(value) requires 1 argument"); - - blob *bd = js_get_blob (ctx, this_val); - if (!bd) return JS_ThrowTypeError (ctx, "w16: not called on a blob"); - - int32_t value; - if (JS_ToInt32 (ctx, &value, argv[0]) < 0) return JS_EXCEPTION; - - int16_t short_val = (int16_t)value; - if (blob_write_bytes (bd, &short_val, sizeof (int16_t)) < 0) - return JS_ThrowTypeError (ctx, "w16: cannot write"); - - return JS_NULL; -} - -/* blob.w32(value) - write 32-bit value */ -static JSValue js_blob_w32 (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) - return JS_ThrowTypeError (ctx, "w32(value) requires 1 argument"); - - blob *bd = js_get_blob (ctx, this_val); - if (!bd) return JS_ThrowTypeError (ctx, "w32: not called on a blob"); - - int32_t value; - if (JS_ToInt32 (ctx, &value, argv[0]) < 0) return JS_EXCEPTION; - - if (blob_write_bytes (bd, &value, sizeof (int32_t)) < 0) - return JS_ThrowTypeError (ctx, "w32: cannot write"); - - return JS_NULL; -} - -/* blob.wf(value) - write float */ -static JSValue js_blob_wf (JSContext *ctx, JSValue this_val, JSValue arg0) { - if (JS_IsNull (arg0)) - return JS_ThrowTypeError (ctx, "wf(value) requires 1 argument"); - - blob *bd = js_get_blob (ctx, this_val); - if (!bd) return JS_ThrowTypeError (ctx, "wf: not called on a blob"); - - float f; - double d; - if (JS_ToFloat64 (ctx, &d, arg0) < 0) return JS_EXCEPTION; - f = d; - - if (blob_write_bytes (bd, &f, sizeof (f)) < 0) - return JS_ThrowTypeError (ctx, "wf: cannot write"); - - return JS_NULL; -} - -/* blob.read_logical(from) */ -static JSValue js_blob_read_logical (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) - return JS_ThrowTypeError (ctx, "read_logical(from) requires 1 argument"); - blob *bd = js_get_blob (ctx, this_val); - if (!bd) - return JS_ThrowTypeError (ctx, "read_logical: not called on a blob"); - int64_t pos; - if (JS_ToInt64 (ctx, &pos, argv[0]) < 0) - return JS_ThrowInternalError (ctx, "must provide a positive bit"); - if (pos < 0) - return JS_ThrowRangeError (ctx, - "read_logical: position must be non-negative"); - int bit_val; - if (blob_read_bit (bd, (size_t)pos, &bit_val) < 0) - return JS_ThrowTypeError (ctx, "read_logical: blob must be stone"); - return JS_NewBool (ctx, bit_val); -} - -/* blob.read_blob(from, to) */ -static JSValue js_blob_read_blob (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - blob *bd = js_get_blob (ctx, this_val); - if (!bd) return JS_ThrowTypeError (ctx, "read_blob: not called on a blob"); - - if (!bd->is_stone) - return JS_ThrowTypeError (ctx, "read_blob: blob must be stone"); - - int64_t from = 0; - int64_t to = bd->length; - - if (argc >= 1) { - if (JS_ToInt64 (ctx, &from, argv[0]) < 0) return JS_EXCEPTION; - if (from < 0) from = 0; - } - if (argc >= 2) { - if (JS_ToInt64 (ctx, &to, argv[1]) < 0) return JS_EXCEPTION; - if (to > (int64_t)bd->length) to = bd->length; - } - - blob *new_bd = blob_read_blob (bd, from, to); - if (!new_bd) return JS_ThrowOutOfMemory (ctx); - - return js_new_blob (ctx, new_bd); -} - -/* blob.read_number(from) */ -static JSValue js_blob_read_number (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) - return JS_ThrowTypeError (ctx, "read_number(from) requires 1 argument"); - blob *bd = js_get_blob (ctx, this_val); - if (!bd) return JS_ThrowTypeError (ctx, "read_number: not called on a blob"); - - if (!bd->is_stone) - return JS_ThrowTypeError (ctx, "read_number: blob must be stone"); - - double from; - if (JS_ToFloat64 (ctx, &from, argv[0]) < 0) return JS_EXCEPTION; - - if (from < 0) - return JS_ThrowRangeError (ctx, - "read_number: position must be non-negative"); - - double d; - if (blob_read_dec64 (bd, from, &d) < 0) - return JS_ThrowRangeError (ctx, "read_number: out of range"); - - return JS_NewFloat64 (ctx, d); -} - -/* blob.read_fit(from, len) */ -static JSValue js_blob_read_fit (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2) - return JS_ThrowTypeError (ctx, "read_fit(from, len) requires 2 arguments"); - - blob *bd = js_get_blob (ctx, this_val); - if (!bd) return JS_ThrowTypeError (ctx, "read_fit: not called on a blob"); - - if (!bd->is_stone) - return JS_ThrowTypeError (ctx, "read_fit: blob must be stone"); - - int64_t from; - int32_t len; - - if (JS_ToInt64 (ctx, &from, argv[0]) < 0) return JS_EXCEPTION; - if (JS_ToInt32 (ctx, &len, argv[1]) < 0) return JS_EXCEPTION; - - if (from < 0) - return JS_ThrowRangeError (ctx, "read_fit: position must be non-negative"); - - int64_t value; - if (blob_read_fit (bd, from, len, &value) < 0) - return JS_ThrowRangeError (ctx, - "read_fit: out of range or invalid length"); - - return JS_NewInt64 (ctx, value); -} - -/* blob.read_text(from) */ -static JSValue js_blob_read_text (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - blob *bd = js_get_blob (ctx, this_val); - if (!bd) return JS_ThrowTypeError (ctx, "read_text: not called on a blob"); - - if (!bd->is_stone) - return JS_ThrowTypeError (ctx, "read_text: blob must be stone"); - - int64_t from = 0; - if (argc >= 1) { - if (JS_ToInt64 (ctx, &from, argv[0]) < 0) return JS_EXCEPTION; - } - - char *text; - size_t bits_read; - if (blob_read_text (bd, from, &text, &bits_read) < 0) - return JS_ThrowRangeError (ctx, - "read_text: out of range or invalid encoding"); - - JSValue result = JS_NewString (ctx, text); - /* Note: blob_read_text uses system malloc, so we use sys_free */ - sys_free (text); - - return result; -} - -/* blob.pad?(from, block_size) */ -static JSValue js_blob_pad_q (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2) - return JS_ThrowTypeError (ctx, - "pad?(from, block_size) requires 2 arguments"); - blob *bd = js_get_blob (ctx, this_val); - if (!bd) return JS_ThrowTypeError (ctx, "pad?: not called on a blob"); - - if (!bd->is_stone) - return JS_ThrowTypeError (ctx, "pad?: blob must be stone"); - - int64_t from; - int32_t block_size; - if (JS_ToInt64 (ctx, &from, argv[0]) < 0) return JS_EXCEPTION; - if (JS_ToInt32 (ctx, &block_size, argv[1]) < 0) return JS_EXCEPTION; - - return JS_NewBool (ctx, blob_pad_check (bd, from, block_size)); -} - -static const JSCFunctionListEntry js_blob_proto_funcs[] = { - /* Write methods */ - JS_CFUNC_DEF ("write_bit", 1, js_blob_write_bit), - JS_CFUNC_DEF ("write_blob", 1, js_blob_write_blob), - JS_CFUNC_DEF ("write_number", 1, js_blob_write_number), - JS_CFUNC_DEF ("write_fit", 2, js_blob_write_fit), - JS_CFUNC_DEF ("write_text", 1, js_blob_write_text), - JS_CFUNC_DEF ("write_pad", 1, js_blob_write_pad), - JS_CFUNC1_DEF ("wf", js_blob_wf), - JS_CFUNC_DEF ("w16", 1, js_blob_w16), - JS_CFUNC_DEF ("w32", 1, js_blob_w32), - - /* Read methods */ - JS_CFUNC_DEF ("read_logical", 1, js_blob_read_logical), - JS_CFUNC_DEF ("read_blob", 2, js_blob_read_blob), - JS_CFUNC_DEF ("read_number", 1, js_blob_read_number), - JS_CFUNC_DEF ("read_fit", 2, js_blob_read_fit), - JS_CFUNC_DEF ("read_text", 1, js_blob_read_text), - JS_CFUNC_DEF ("pad?", 2, js_blob_pad_q), -}; - -/* ============================================================================ - * Blob external API functions (called from other files via cell.h) - * ============================================================================ - */ - -/* Initialize blob - called during context setup (but we do it in - * JS_AddIntrinsicBaseObjects now) */ -JSValue js_blob_use (JSContext *js) { - return JS_GetPropertyStr (js, js->global_obj, "blob"); -} - -/* Create a new blob from raw data, stone it, and return as JSValue */ -JSValue js_new_blob_stoned_copy (JSContext *js, void *data, size_t bytes) { - blob *b = blob_new (bytes * 8); - if (!b) return JS_ThrowOutOfMemory (js); - memcpy (b->data, data, bytes); - b->length = bytes * 8; - blob_make_stone (b); - return js_new_blob (js, b); -} - -/* Get raw data pointer from a blob (must be stone) - returns byte count */ -void *js_get_blob_data (JSContext *js, size_t *size, JSValue v) { - blob *b = js_get_blob (js, v); - *size = (b->length + 7) / 8; - if (!b) { - JS_ThrowReferenceError (js, "get_blob_data: not called on a blob"); - return NULL; - } - - if (!b->is_stone) { - JS_ThrowReferenceError (js, - "attempted to read data from a non-stone blob"); - return NULL; - } - - if (b->length % 8 != 0) { - JS_ThrowReferenceError ( - js, - "attempted to read data from a non-byte aligned blob [length is %zu]", - b->length); - return NULL; - } - - return b->data; -} - -/* Get raw data pointer from a blob (must be stone) - returns bit count */ -void *js_get_blob_data_bits (JSContext *js, size_t *bits, JSValue v) { - blob *b = js_get_blob (js, v); - if (!b) { - JS_ThrowReferenceError (js, "get_blob_data_bits: not called on a blob"); - return NULL; - } - if (!b->is_stone) { - JS_ThrowReferenceError (js, - "attempted to read data from a non-stone blob"); - return NULL; - } - - if (!b->data) { - JS_ThrowReferenceError (js, "attempted to read data from an empty blob"); - return NULL; - } - - if (b->length % 8 != 0) { - JS_ThrowReferenceError ( - js, "attempted to read data from a non-byte aligned blob"); - return NULL; - } - - if (b->length == 0) { - JS_ThrowReferenceError (js, "attempted to read data from an empty blob"); - return NULL; - } - - *bits = b->length; - return b->data; -} - -/* Check if a value is a blob */ -int js_is_blob (JSContext *js, JSValue v) { - return js_get_blob (js, v) != NULL; -} - -/* ============================================================================ - * eval() function - compile and execute code with environment - * ============================================================================ - */ - -/* eval(text, env) - evaluate code with optional environment record - * text: string to compile and execute - * env: optional stone record for variable bindings (checked first before intrinsics) - */ -static JSValue js_cell_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - const char *str; - size_t len; - JSValue env = JS_NULL; - JSValue result; - JSGCRef env_ref; - - if (argc < 1 || !JS_IsText (argv[0])) { - return JS_ThrowTypeError (ctx, "eval requires a text argument"); - } - - /* Get optional environment record (must be stone if provided) */ - if (argc > 1 && !JS_IsNull (argv[1])) { - if (!JS_IsRecord (argv[1])) { - return JS_ThrowTypeError (ctx, "eval environment must be an object"); - } - JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (argv[1]); - if (!objhdr_s (rec->mist_hdr)) { - return JS_ThrowTypeError (ctx, "eval environment must be stoned"); - } - env = argv[1]; - } - - /* Protect env from GC during compilation */ - JS_AddGCRef (ctx, &env_ref); - env_ref.val = env; - - /* Get text string */ - str = JS_ToCStringLen (ctx, &len, argv[0]); - if (!str) { - JS_DeleteGCRef (ctx, &env_ref); - return JS_EXCEPTION; - } - - /* Compile the text */ - JSValue fun = JS_Compile (ctx, str, len, ""); - JS_FreeCString (ctx, str); - if (JS_IsException (fun)) { - JS_DeleteGCRef (ctx, &env_ref); - return fun; - } - - /* Update env from GC ref (may have moved) */ - env = env_ref.val; - JS_DeleteGCRef (ctx, &env_ref); - - /* Integrate with environment */ - result = JS_Integrate (ctx, fun, env); - return result; -} - -/* ============================================================================ - * mach_eval() function - compile and execute via MACH VM - * ============================================================================ - */ - -/* mach_eval(name, source) - parse to AST and run through MACH VM */ -static JSValue js_mach_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2 || !JS_IsText (argv[0]) || !JS_IsText (argv[1])) - return JS_ThrowTypeError (ctx, "mach_eval requires (name, source) text arguments"); - - const char *name = JS_ToCString (ctx, argv[0]); - if (!name) return JS_EXCEPTION; - - const char *source = JS_ToCString (ctx, argv[1]); - if (!source) { - JS_FreeCString (ctx, name); - return JS_EXCEPTION; - } - - cJSON *ast = JS_ASTTree (source, strlen (source), name); - JS_FreeCString (ctx, source); - - if (!ast) { - JS_FreeCString (ctx, name); - return JS_ThrowSyntaxError (ctx, "mach_eval: failed to parse AST"); - } - - JSValue result = JS_RunMachTree (ctx, ast, JS_NULL); - cJSON_Delete (ast); - JS_FreeCString (ctx, name); - return result; -} - -/* ============================================================================ - * stone() function - deep freeze with blob support - * ============================================================================ - */ - -/* stone(object) - deep freeze an object */ -static JSValue js_cell_stone (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NULL; - - JSValue obj = argv[0]; - - blob *bd = js_get_blob (ctx, obj); - if (bd) { - bd->is_stone = true; - return obj; - } - - if (JS_IsObject (obj)) { - JSRecord *rec = JS_VALUE_GET_RECORD (obj); - obj_set_stone (rec); - return obj; - } - - if (JS_IsArray (obj)) { - JSArray *arr = JS_VALUE_GET_ARRAY (obj); - arr->mist_hdr = objhdr_set_s (arr->mist_hdr, true); - return obj; - } - - return JS_NULL; -} - -/* ============================================================================ - * reverse() function - reverse an array - * ============================================================================ - */ - -static JSValue js_cell_reverse (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NULL; - - JSValue value = argv[0]; - - /* Handle arrays */ - if (JS_IsArray (value)) { - /* GC-safe: root argv[0] */ - JSGCRef arr_ref; - JS_PushGCRef (ctx, &arr_ref); - arr_ref.val = argv[0]; - - JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); - int len = arr->len; - - JSValue result = JS_NewArrayLen (ctx, len); - if (JS_IsException (result)) { JS_PopGCRef (ctx, &arr_ref); return result; } - arr = JS_VALUE_GET_ARRAY (arr_ref.val); /* re-chase after allocation */ - JSArray *out = JS_VALUE_GET_ARRAY (result); - for (int i = len - 1, j = 0; i >= 0; i--, j++) { - out->values[j] = arr->values[i]; - } - out->len = len; - JS_PopGCRef (ctx, &arr_ref); - return result; - } - - /* Handle strings */ - if (JS_IsText (value)) { - int len = js_string_value_len (value); - if (len == 0) return JS_NewString (ctx, ""); - JSText *str = js_alloc_string (ctx, len); - if (!str) return JS_EXCEPTION; - for (int i = 0; i < len; i++) { - string_put (str, i, js_string_value_get (value, len - 1 - i)); - } - str->length = len; - return pretext_end (ctx, str); - } - - /* Handle blobs */ - blob *bd = js_get_blob (ctx, value); - if (bd) { - /* Blobs need proper blob reversal support - return null for now */ - return JS_NULL; - } - - return JS_NULL; -} - -/* ============================================================================ - * proto() function - get prototype of an object - * ============================================================================ - */ - -static JSValue js_cell_pop (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NULL; - - JSValue obj = argv[0]; - if (!JS_IsArray (obj)) return JS_NULL; - - JSArray *arr = JS_VALUE_GET_ARRAY (obj); - - if (arr->len == 0) return JS_NULL; - - /* Transfer ownership: take value without dup, clear slot, decrement len */ - JSValue last = arr->values[arr->len - 1]; - arr->values[arr->len - 1] = JS_NULL; - arr->len--; - return last; -} - -JSValue JS_Stone (JSContext *ctx, JSValue this_val) { - return js_cell_stone (ctx, this_val, 1, &this_val); -} - -/* GC-safe push: takes pointer to array JSValue, updates it if array grows. - Returns 0 on success, -1 on error. */ -int JS_ArrayPush (JSContext *ctx, JSValue *arr_ptr, JSValue val) { - if (!JS_IsArray (*arr_ptr)) { - JS_ThrowTypeError (ctx, "not an array"); - return -1; - } - return js_intrinsic_array_push (ctx, arr_ptr, val); -} - -JSValue JS_ArrayPop (JSContext *ctx, JSValue obj) { - if (!JS_IsArray (obj)) return JS_ThrowTypeError (ctx, "not an array"); - return js_cell_pop (ctx, JS_NULL, 1, &obj); -} - -/* C API: array(arg0, arg1, arg2, arg3) - - array(number) or array(number, fill_value_or_fn) - create array - - array(array) - copy - - array(array, fn, reverse, exit) - map - - array(array, array2) - concat - - array(array, from, to) - slice - - array(object) - keys - - array(text) or array(text, sep_or_len) - split */ -JSValue JS_Array (JSContext *ctx, JSValue arg0, JSValue arg1, JSValue arg2, JSValue arg3) { - JSValue argv[4] = { arg0, arg1, arg2, arg3 }; - int argc = 4; - if (JS_IsNull (arg3)) argc = 3; - if (JS_IsNull (arg2)) argc = 2; - if (JS_IsNull (arg1)) argc = 1; - return js_cell_array (ctx, JS_NULL, argc, argv); -} - -/* C API: filter(arr, fn) - returns new filtered array */ -JSValue JS_ArrayFilter (JSContext *ctx, JSValue arr, JSValue fn) { - JSValue argv[2] = { arr, fn }; - return js_cell_array_filter (ctx, JS_NULL, 2, argv); -} - -/* C API: sort(arr, selector) - returns new sorted array */ -JSValue JS_ArraySort (JSContext *ctx, JSValue arr, JSValue selector) { - JSValue argv[2] = { arr, selector }; - return js_cell_array_sort (ctx, JS_NULL, 2, argv); -} - -/* C API: find(arr, target_or_fn, reverse, from) - returns index or null */ -JSValue JS_ArrayFind (JSContext *ctx, JSValue arr, JSValue target_or_fn, JSValue reverse, JSValue from) { - JSValue argv[4] = { arr, target_or_fn, reverse, from }; - int argc = 4; - if (JS_IsNull (from)) argc = 3; - if (JS_IsNull (reverse)) argc = 2; - return js_cell_array_find (ctx, JS_NULL, argc, argv); -} - -/* C API: arrfor(arr, fn, reverse, exit) - iterate array */ -JSValue JS_ArrFor (JSContext *ctx, JSValue arr, JSValue fn, JSValue reverse, JSValue exit_val) { - JSValue argv[4] = { arr, fn, reverse, exit_val }; - int argc = 4; - if (JS_IsNull (exit_val)) argc = 3; - if (JS_IsNull (reverse)) argc = 2; - return js_cell_array_for (ctx, JS_NULL, argc, argv); -} - -/* C API: reduce(arr, fn, initial, reverse) - reduce array */ -JSValue JS_ArrayReduce (JSContext *ctx, JSValue arr, JSValue fn, JSValue initial, JSValue reverse) { - JSValue argv[4] = { arr, fn, initial, reverse }; - int argc = 4; - if (JS_IsNull (reverse)) argc = 3; - if (JS_IsNull (initial)) argc = 2; - return js_cell_array_reduce (ctx, JS_NULL, argc, argv); -} - -/* ============================================================ - C API Wrappers for Cell Intrinsic Functions - ============================================================ */ - -/* C API: stone(val) - make value immutable */ -JSValue JS_CellStone (JSContext *ctx, JSValue val) { - return js_cell_stone (ctx, JS_NULL, 1, &val); -} - -/* C API: length(val) - get length of array/text/blob */ -JSValue JS_CellLength (JSContext *ctx, JSValue val) { - return js_cell_length (ctx, JS_NULL, 1, &val); -} - -/* C API: reverse(val) - reverse array or text */ -JSValue JS_CellReverse (JSContext *ctx, JSValue val) { - return js_cell_reverse (ctx, JS_NULL, 1, &val); -} - -/* C API: proto(obj) - get prototype */ -JSValue JS_CellProto (JSContext *ctx, JSValue obj) { - return js_cell_proto (ctx, JS_NULL, 1, &obj); -} - -/* C API: splat(val) - convert to array */ -JSValue JS_CellSplat (JSContext *ctx, JSValue val) { - return js_cell_splat (ctx, JS_NULL, 1, &val); -} - -/* C API: meme(obj, deep) - clone object */ -JSValue JS_CellMeme (JSContext *ctx, JSValue obj, JSValue deep) { - JSValue argv[2] = { obj, deep }; - int argc = JS_IsNull (deep) ? 1 : 2; - return js_cell_meme (ctx, JS_NULL, argc, argv); -} - -/* C API: apply(fn, args) - apply function to array of args */ -JSValue JS_CellApply (JSContext *ctx, JSValue fn, JSValue args) { - JSValue argv[2] = { fn, args }; - return js_cell_fn_apply (ctx, JS_NULL, 2, argv); -} - -/* C API: call(fn, this, args...) - call function */ -JSValue JS_CellCall (JSContext *ctx, JSValue fn, JSValue this_val, JSValue args) { - JSValue argv[3] = { fn, this_val, args }; - int argc = JS_IsNull (args) ? 2 : 3; - return js_cell_call (ctx, JS_NULL, argc, argv); -} - -/* C API: modulo(a, b) - modulo operation */ -JSValue JS_CellModulo (JSContext *ctx, JSValue a, JSValue b) { - JSValue argv[2] = { a, b }; - return js_cell_modulo (ctx, JS_NULL, 2, argv); -} - -/* C API: neg(val) - negate number */ -JSValue JS_CellNeg (JSContext *ctx, JSValue val) { - return js_cell_neg (ctx, JS_NULL, 1, &val); -} - -/* C API: not(val) - logical not */ -JSValue JS_CellNot (JSContext *ctx, JSValue val) { - return js_cell_not (ctx, JS_NULL, 1, &val); -} - -/* Text functions */ - -/* C API: text(val) - convert to text */ -JSValue JS_CellText (JSContext *ctx, JSValue val) { - return js_cell_text (ctx, JS_NULL, 1, &val); -} - -/* C API: lower(text) - convert to lowercase */ -JSValue JS_CellLower (JSContext *ctx, JSValue text) { - return js_cell_text_lower (ctx, JS_NULL, 1, &text); -} - -/* C API: upper(text) - convert to uppercase */ -JSValue JS_CellUpper (JSContext *ctx, JSValue text) { - return js_cell_text_upper (ctx, JS_NULL, 1, &text); -} - -/* C API: trim(text, chars) - trim whitespace or specified chars */ -JSValue JS_CellTrim (JSContext *ctx, JSValue text, JSValue chars) { - JSValue argv[2] = { text, chars }; - int argc = JS_IsNull (chars) ? 1 : 2; - return js_cell_text_trim (ctx, JS_NULL, argc, argv); -} - -/* C API: codepoint(text, idx) - get codepoint at index */ -JSValue JS_CellCodepoint (JSContext *ctx, JSValue text, JSValue idx) { - JSValue argv[2] = { text, idx }; - int argc = JS_IsNull (idx) ? 1 : 2; - return js_cell_text_codepoint (ctx, JS_NULL, argc, argv); -} - -/* C API: replace(text, pattern, replacement) - replace in text */ -JSValue JS_CellReplace (JSContext *ctx, JSValue text, JSValue pattern, JSValue replacement) { - JSValue argv[3] = { text, pattern, replacement }; - return js_cell_text_replace (ctx, JS_NULL, 3, argv); -} - -/* C API: search(text, pattern, from) - search in text */ -JSValue JS_CellSearch (JSContext *ctx, JSValue text, JSValue pattern, JSValue from) { - JSValue argv[3] = { text, pattern, from }; - int argc = JS_IsNull (from) ? 2 : 3; - return js_cell_text_search (ctx, JS_NULL, argc, argv); -} - -/* C API: extract(text, from, to) - extract substring - Internally, js_cell_text_extract expects (text, pattern, from, to) - but for simple substring extraction we don't need a pattern */ -JSValue JS_CellExtract (JSContext *ctx, JSValue text, JSValue from, JSValue to) { - if (!JS_IsText (text)) return JS_NULL; - - JSGCRef text_ref; - JS_PushGCRef (ctx, &text_ref); - text_ref.val = text; - - int len = js_string_value_len (text_ref.val); - - int from_idx = 0; - int to_idx = len; - - if (!JS_IsNull (from)) { - if (JS_ToInt32 (ctx, &from_idx, from)) { - JS_PopGCRef (ctx, &text_ref); - return JS_EXCEPTION; - } - if (from_idx < 0) from_idx += len; - if (from_idx < 0) from_idx = 0; - if (from_idx > len) from_idx = len; - } - - if (!JS_IsNull (to)) { - if (JS_ToInt32 (ctx, &to_idx, to)) { - JS_PopGCRef (ctx, &text_ref); - return JS_EXCEPTION; - } - if (to_idx < 0) to_idx += len; - if (to_idx < 0) to_idx = 0; - if (to_idx > len) to_idx = len; - } - - if (from_idx > to_idx) { - JS_PopGCRef (ctx, &text_ref); - return JS_NULL; - } - if (from_idx == to_idx) { - JS_PopGCRef (ctx, &text_ref); - return JS_NewString (ctx, ""); - } - - /* Create result string */ - int result_len = to_idx - from_idx; - JSText *str = js_alloc_string (ctx, result_len); - if (!str) { - JS_PopGCRef (ctx, &text_ref); - return JS_EXCEPTION; - } - for (int i = 0; i < result_len; i++) { - string_put (str, i, js_string_value_get (text_ref.val, from_idx + i)); - } - str->length = result_len; - JS_PopGCRef (ctx, &text_ref); - return pretext_end (ctx, str); -} - -/* C API: character(codepoint) - create single character text */ -JSValue JS_CellCharacter (JSContext *ctx, JSValue codepoint) { - return js_cell_character (ctx, JS_NULL, 1, &codepoint); -} - -/* Number functions */ - -/* C API: number(val) - convert to number */ -JSValue JS_CellNumber (JSContext *ctx, JSValue val) { - return js_cell_number (ctx, JS_NULL, 1, &val); -} - -/* C API: abs(num) - absolute value */ -JSValue JS_CellAbs (JSContext *ctx, JSValue num) { - return js_cell_number_abs (ctx, JS_NULL, 1, &num); -} - -/* C API: sign(num) - sign of number (-1, 0, 1) */ -JSValue JS_CellSign (JSContext *ctx, JSValue num) { - return js_cell_number_sign (ctx, JS_NULL, 1, &num); -} - -/* C API: floor(num) - floor */ -JSValue JS_CellFloor (JSContext *ctx, JSValue num) { - return js_cell_number_floor (ctx, JS_NULL, 1, &num); -} - -/* C API: ceiling(num) - ceiling */ -JSValue JS_CellCeiling (JSContext *ctx, JSValue num) { - return js_cell_number_ceiling (ctx, JS_NULL, 1, &num); -} - -/* C API: round(num) - round to nearest integer */ -JSValue JS_CellRound (JSContext *ctx, JSValue num) { - return js_cell_number_round (ctx, JS_NULL, 1, &num); -} - -/* C API: trunc(num) - truncate towards zero */ -JSValue JS_CellTrunc (JSContext *ctx, JSValue num) { - return js_cell_number_trunc (ctx, JS_NULL, 1, &num); -} - -/* C API: whole(num) - integer part */ -JSValue JS_CellWhole (JSContext *ctx, JSValue num) { - return js_cell_number_whole (ctx, JS_NULL, 1, &num); -} - -/* C API: fraction(num) - fractional part */ -JSValue JS_CellFraction (JSContext *ctx, JSValue num) { - return js_cell_number_fraction (ctx, JS_NULL, 1, &num); -} - -/* C API: min(a, b) - minimum of two numbers */ -JSValue JS_CellMin (JSContext *ctx, JSValue a, JSValue b) { - JSValue argv[2] = { a, b }; - return js_cell_number_min (ctx, JS_NULL, 2, argv); -} - -/* C API: max(a, b) - maximum of two numbers */ -JSValue JS_CellMax (JSContext *ctx, JSValue a, JSValue b) { - JSValue argv[2] = { a, b }; - return js_cell_number_max (ctx, JS_NULL, 2, argv); -} - -/* C API: remainder(a, b) - remainder after division */ -JSValue JS_CellRemainder (JSContext *ctx, JSValue a, JSValue b) { - JSValue argv[2] = { a, b }; - return js_cell_number_remainder (ctx, JS_NULL, 2, argv); -} - -/* Object functions */ - -/* C API: object(proto, props) - create object */ -JSValue JS_CellObject (JSContext *ctx, JSValue proto, JSValue props) { - JSValue argv[2] = { proto, props }; - int argc = JS_IsNull (props) ? 1 : 2; - if (JS_IsNull (proto)) argc = 0; - return js_cell_object (ctx, JS_NULL, argc, argv); -} - -/* C API: format(text, collection, transformer) - string interpolation */ -JSValue JS_CellFormat (JSContext *ctx, JSValue text, JSValue collection, JSValue transformer) { - JSValue argv[3] = { text, collection, transformer }; - int argc = JS_IsNull (transformer) ? 2 : 3; - return js_cell_text_format (ctx, JS_NULL, argc, argv); -} - -/* ============================================================ - Helper Functions for C API - ============================================================ */ - -/* Create an array from a list of JSValues */ -JSValue JS_NewArrayFrom (JSContext *ctx, int count, JSValue *values) { - JSGCRef arr_ref; - JS_PushGCRef (ctx, &arr_ref); - arr_ref.val = JS_NewArray (ctx); - if (JS_IsException (arr_ref.val)) { - JS_PopGCRef (ctx, &arr_ref); - return JS_EXCEPTION; - } - for (int i = 0; i < count; i++) { - if (JS_ArrayPush (ctx, &arr_ref.val, values[i]) < 0) { - JS_PopGCRef (ctx, &arr_ref); - return JS_EXCEPTION; - } - } - JSValue result = arr_ref.val; - JS_PopGCRef (ctx, &arr_ref); - return result; -} - -/* Print a JSValue text to stdout */ -void JS_PrintText (JSContext *ctx, JSValue val) { - if (!JS_IsText (val)) { - /* Try to convert to string first */ - val = JS_ToString (ctx, val); - if (JS_IsException (val) || !JS_IsText (val)) { - printf ("[non-text value]"); - return; - } - } - const char *str = JS_ToCString (ctx, val); - if (str) { - printf ("%s", str); - JS_FreeCString (ctx, str); - } -} - -/* Print a JSValue text to stdout with newline */ -void JS_PrintTextLn (JSContext *ctx, JSValue val) { - JS_PrintText (ctx, val); - printf ("\n"); -} - -/* Format and print - convenience function */ -void JS_PrintFormatted (JSContext *ctx, const char *fmt, int count, JSValue *values) { - JSValue fmt_str = JS_NewString (ctx, fmt); - JSValue arr = JS_NewArrayFrom (ctx, count, values); - JSValue result = JS_CellFormat (ctx, fmt_str, arr, JS_NULL); - JS_PrintText (ctx, result); -} - -static JSValue js_cell_push (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NULL; - - if (!JS_IsArray (argv[0])) return JS_NULL; - - JSGCRef arr_ref; - JS_PushGCRef (ctx, &arr_ref); - arr_ref.val = argv[0]; - - for (int i = 1; i < argc; i++) { - if (js_intrinsic_array_push (ctx, &arr_ref.val, argv[i]) < 0) { - JS_PopGCRef (ctx, &arr_ref); - return JS_EXCEPTION; - } - } - - argv[0] = arr_ref.val; - JS_PopGCRef (ctx, &arr_ref); - return JS_NULL; -} - -static JSValue js_cell_proto (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - (void)this_val; - if (argc < 1) return JS_NULL; - - JSValue obj = argv[0]; - - if (JS_IsArray (obj)) { - return JS_ThrowTypeError (ctx, "arrays do not have prototypes"); - } - - if (!JS_IsObject (obj)) return JS_NULL; - - JSValue proto = JS_GetPrototype (ctx, obj); - if (JS_IsException (proto)) return JS_NULL; - - /* If prototype is Object.prototype, return null */ - if (JS_IsObject (proto)) { - JSValue obj_proto = ctx->class_proto[JS_CLASS_OBJECT]; - if (JS_IsObject (obj_proto) - && JS_VALUE_GET_OBJ (proto) == JS_VALUE_GET_OBJ (obj_proto)) { - return JS_NULL; - } - } - - return proto; -} - -static JSValue js_cell_meme (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - JSValue proto = JS_NULL; - if (argc > 0 && !JS_IsNull (argv[0])) proto = argv[0]; - - JSValue result = JS_NewObjectProto (ctx, proto); - if (JS_IsException (result)) return result; - - if (argc < 2) return result; - - /* Root result across allocating calls */ - JSGCRef result_ref; - JS_PushGCRef (ctx, &result_ref); - result_ref.val = result; - -/* Helper function to apply a single mixin */ -#define APPLY_MIXIN(mix_val) \ - do { \ - if (!JS_IsObject (mix_val) || JS_IsNull (mix_val) || JS_IsArray (mix_val)) \ - break; \ - JSGCRef _mix_ref; \ - JS_PushGCRef (ctx, &_mix_ref); \ - _mix_ref.val = mix_val; \ - JSValue _keys = JS_GetOwnPropertyNames (ctx, _mix_ref.val); \ - if (JS_IsException (_keys)) { \ - JS_PopGCRef (ctx, &_mix_ref); \ - JS_PopGCRef (ctx, &result_ref); \ - return JS_EXCEPTION; \ - } \ - uint32_t _len; \ - if (js_get_length32 (ctx, &_len, _keys)) { \ - JS_PopGCRef (ctx, &_mix_ref); \ - JS_PopGCRef (ctx, &result_ref); \ - return JS_EXCEPTION; \ - } \ - for (uint32_t j = 0; j < _len; j++) { \ - JSValue _key = JS_GetPropertyUint32 (ctx, _keys, j); \ - JSValue val = JS_GetProperty (ctx, _mix_ref.val, _key); \ - if (JS_IsException (val)) { \ - JS_PopGCRef (ctx, &_mix_ref); \ - JS_PopGCRef (ctx, &result_ref); \ - return JS_EXCEPTION; \ - } \ - JS_SetProperty (ctx, result_ref.val, _key, val); \ - } \ - JS_PopGCRef (ctx, &_mix_ref); \ - } while (0) - - /* Process all arguments starting from argv[1] as mixins */ - for (int i = 1; i < argc; i++) { - JSValue mixins = argv[i]; - - if (JS_IsArray (mixins)) { - /* Array of mixins - root the array across calls */ - JSGCRef mixins_ref; - JS_PushGCRef (ctx, &mixins_ref); - mixins_ref.val = mixins; - int64_t len; - if (js_get_length64 (ctx, &len, mixins_ref.val)) { - JS_PopGCRef (ctx, &mixins_ref); - JS_PopGCRef (ctx, &result_ref); - return JS_EXCEPTION; - } - - for (int64_t j = 0; j < len; j++) { - JSValue mix = JS_GetPropertyInt64 (ctx, mixins_ref.val, j); - if (JS_IsException (mix)) { - JS_PopGCRef (ctx, &mixins_ref); - JS_PopGCRef (ctx, &result_ref); - return JS_EXCEPTION; - } - APPLY_MIXIN (mix); - } - JS_PopGCRef (ctx, &mixins_ref); - } else if (JS_IsObject (mixins) && !JS_IsNull (mixins)) { - /* Single mixin object */ - APPLY_MIXIN (mixins); - } - } - -#undef APPLY_MIXIN - - result = result_ref.val; - JS_PopGCRef (ctx, &result_ref); - return result; -} - -/* ============================================================================ - * splat() function - flatten object with prototype chain - * ============================================================================ - */ - -static JSValue js_cell_splat (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NULL; - - JSValue obj = argv[0]; - if (!JS_IsObject (obj) || JS_IsNull (obj)) return JS_NULL; - - /* Root obj, result, current, keys across allocating calls */ - JSGCRef obj_ref, res_ref, cur_ref, keys_ref; - JS_PushGCRef (ctx, &obj_ref); - obj_ref.val = obj; - JS_PushGCRef (ctx, &res_ref); - res_ref.val = JS_NewObject (ctx); - JS_PushGCRef (ctx, &cur_ref); - cur_ref.val = obj_ref.val; /* use rooted value, not stale local */ - JS_PushGCRef (ctx, &keys_ref); - keys_ref.val = JS_NULL; - -#define SPLAT_CLEANUP() do { JS_PopGCRef (ctx, &keys_ref); JS_PopGCRef (ctx, &cur_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &obj_ref); } while(0) - - if (JS_IsException (res_ref.val)) { SPLAT_CLEANUP (); return JS_EXCEPTION; } - - /* Walk prototype chain and collect text keys */ - while (!JS_IsNull (cur_ref.val)) { - keys_ref.val = JS_GetOwnPropertyNames (ctx, cur_ref.val); - if (JS_IsException (keys_ref.val)) { - SPLAT_CLEANUP (); - return JS_EXCEPTION; - } - uint32_t len; - if (js_get_length32 (ctx, &len, keys_ref.val)) { - SPLAT_CLEANUP (); - return JS_EXCEPTION; - } - - for (uint32_t i = 0; i < len; i++) { - JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); - int has = JS_HasProperty (ctx, res_ref.val, key); - if (has < 0) { - SPLAT_CLEANUP (); - return JS_EXCEPTION; - } - if (!has) { - JSValue val = JS_GetProperty (ctx, cur_ref.val, key); - if (JS_IsException (val)) { - SPLAT_CLEANUP (); - return JS_EXCEPTION; - } - int tag = JS_VALUE_GET_TAG (val); - if (JS_IsObject (val) || JS_IsNumber (val) || tag == JS_TAG_STRING - || tag == JS_TAG_STRING_IMM || tag == JS_TAG_BOOL) { - JS_SetProperty (ctx, res_ref.val, key, val); - } - } - } - - cur_ref.val = JS_GetPrototype (ctx, cur_ref.val); - } - - /* Call to_data if present */ - JSValue to_data = JS_GetPropertyStr (ctx, obj_ref.val, "to_data"); - if (JS_IsFunction (to_data)) { - JSValue args[1] = { res_ref.val }; - JSValue extra = JS_Call (ctx, to_data, obj_ref.val, 1, args); - if (!JS_IsException (extra) && JS_IsObject (extra)) { - keys_ref.val = JS_GetOwnPropertyNames (ctx, extra); - if (!JS_IsException (keys_ref.val)) { - uint32_t len; - if (!js_get_length32 (ctx, &len, keys_ref.val)) { - for (uint32_t i = 0; i < len; i++) { - JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); - JSValue val = JS_GetProperty (ctx, extra, key); - JS_SetProperty (ctx, res_ref.val, key, val); - } - } - } - } - } - - JSValue result = res_ref.val; - SPLAT_CLEANUP (); - return result; -} -#undef SPLAT_CLEANUP - -/* ============================================================================ - * length() function - * ============================================================================ - */ - -static JSValue js_cell_length (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NULL; - - JSValue val = argv[0]; - - /* null returns null */ - if (JS_IsNull (val)) return JS_NULL; - - /* Functions return arity (accessed directly, not via properties) */ - if (JS_IsFunction (val)) { - JSFunction *f = JS_VALUE_GET_FUNCTION (val); - return JS_NewInt32 (ctx, f->length); - } - - int tag = JS_VALUE_GET_TAG (val); - - /* Strings return codepoint count */ - if (tag == JS_TAG_STRING_IMM) { - return JS_NewInt32 (ctx, MIST_GetImmediateASCIILen (val)); - } - if (tag == JS_TAG_STRING) { - JSText *p = JS_VALUE_GET_STRING (val); - return JS_NewInt32 (ctx, (int)JSText_len (p)); - } - - /* Check for blob */ - blob *bd = js_get_blob (ctx, val); - if (bd) return JS_NewInt64 (ctx, bd->length); - - /* Arrays return element count */ - if (JS_IsArray (val)) { - JSArray *arr = JS_VALUE_GET_ARRAY (val); - return JS_NewInt32 (ctx, arr->len); - } - - /* Objects with length property */ - if (JS_IsObject (val)) { - JSValue len = JS_GetPropertyStr (ctx, val, "length"); - if (!JS_IsException (len) && !JS_IsNull (len)) { - if (JS_IsFunction (len)) { - JSValue result = JS_Call (ctx, len, val, 0, NULL); - return result; - } - if (JS_VALUE_IS_NUMBER (len)) return len; - } else if (JS_IsException (len)) { - return len; - } - } - - return JS_NULL; -} - -/* ============================================================================ - * call() function - call a function with explicit this and arguments - * ============================================================================ - */ - -/* call(func, this_val, args_array) */ -static JSValue js_cell_call (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) - return JS_ThrowTypeError (ctx, "call requires a function argument"); - - JSGCRef func_ref, this_ref, args_ref; - JS_PushGCRef (ctx, &func_ref); - JS_PushGCRef (ctx, &this_ref); - JS_PushGCRef (ctx, &args_ref); - func_ref.val = argv[0]; - this_ref.val = argc >= 2 ? argv[1] : JS_NULL; - args_ref.val = argc >= 3 ? argv[2] : JS_NULL; - - if (!JS_IsFunction (func_ref.val)) { - JS_PopGCRef (ctx, &args_ref); - JS_PopGCRef (ctx, &this_ref); - JS_PopGCRef (ctx, &func_ref); - return JS_ThrowTypeError (ctx, "first argument must be a function"); - } - - if (argc < 3 || JS_IsNull (args_ref.val)) { - JSValue ret = JS_CallInternal (ctx, func_ref.val, this_ref.val, 0, NULL, 0); - JS_PopGCRef (ctx, &args_ref); - JS_PopGCRef (ctx, &this_ref); - JS_PopGCRef (ctx, &func_ref); - return ret; - } - - if (!JS_IsArray (args_ref.val)) { - JS_PopGCRef (ctx, &args_ref); - JS_PopGCRef (ctx, &this_ref); - JS_PopGCRef (ctx, &func_ref); - return JS_ThrowTypeError (ctx, "third argument must be an array"); - } - - uint32_t len; - JSValue *tab = build_arg_list (ctx, &len, &args_ref.val); - if (!tab) { - JS_PopGCRef (ctx, &args_ref); - JS_PopGCRef (ctx, &this_ref); - JS_PopGCRef (ctx, &func_ref); - return JS_EXCEPTION; - } - - JSValue ret = JS_CallInternal (ctx, func_ref.val, this_ref.val, len, tab, 0); - free_arg_list (ctx, tab, len); - JS_PopGCRef (ctx, &args_ref); - JS_PopGCRef (ctx, &this_ref); - JS_PopGCRef (ctx, &func_ref); - return ret; -} - -/* ============================================================================ - * is_* type checking functions - * ============================================================================ - */ - -/* is_array(val) */ -static JSValue js_cell_is_array (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_FALSE; - return JS_NewBool (ctx, JS_IsArray (argv[0])); -} - -/* is_blob(val) */ -static JSValue js_cell_is_blob (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_FALSE; - return JS_NewBool (ctx, js_get_blob (ctx, argv[0]) != NULL); -} - -/* is_data(val) - check if object is a plain object (data record) */ -static JSValue js_cell_is_data (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_FALSE; - JSValue val = argv[0]; - if (!JS_IsObject (val)) return JS_FALSE; - if (JS_IsArray (val)) return JS_FALSE; - if (JS_IsFunction (val)) return JS_FALSE; - if (js_get_blob (ctx, val)) return JS_FALSE; - /* Check if it's a plain object (prototype is Object.prototype or null) */ - return JS_TRUE; -} - -/* is_function(val) */ -static JSValue js_cell_is_function (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_FALSE; - return JS_NewBool (ctx, JS_IsFunction (argv[0])); -} - -/* is_logical(val) - check if value is a boolean (true or false) */ -static JSValue js_cell_is_logical (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_FALSE; - return JS_NewBool (ctx, JS_VALUE_GET_TAG (argv[0]) == JS_TAG_BOOL); -} - -/* is_integer(val) */ -static JSValue js_cell_is_integer (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_FALSE; - JSValue val = argv[0]; - int tag = JS_VALUE_GET_TAG (val); - if (tag == JS_TAG_INT) return JS_TRUE; - if (tag == JS_TAG_FLOAT64) { - double d = JS_VALUE_GET_FLOAT64 (val); - return JS_NewBool (ctx, isfinite (d) && trunc (d) == d); - } - return JS_FALSE; -} - -/* is_null(val) */ -static JSValue js_cell_is_null (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_FALSE; - return JS_NewBool (ctx, JS_IsNull (argv[0])); -} - -/* is_number(val) */ -static JSValue js_cell_is_number (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_FALSE; - return JS_NewBool (ctx, JS_IsNumber (argv[0])); -} - -/* is_object(val) - true for non-array, non-null objects */ -static JSValue js_cell_is_object (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_FALSE; - JSValue val = argv[0]; - if (!JS_IsObject (val)) return JS_FALSE; - if (JS_IsArray (val)) return JS_FALSE; - return JS_TRUE; -} - -/* is_stone(val) - check if value is immutable */ -static JSValue js_cell_is_stone (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - (void)this_val; - if (argc < 1) return JS_FALSE; - - return JS_NewBool (ctx, JS_IsStone (argv[0])); -} - -/* is_text(val) */ -static JSValue js_cell_is_text (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_FALSE; - int tag = JS_VALUE_GET_TAG (argv[0]); - return JS_NewBool (ctx, tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM); -} - -/* is_proto(val, master) - check if val has master in prototype chain */ -static JSValue js_cell_is_proto (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2) return JS_FALSE; - JSValue val = argv[0]; - JSValue master = argv[1]; - - if (!JS_IsObject (val) || JS_IsNull (master)) return JS_FALSE; - - /* Walk prototype chain */ - JSValue proto = JS_GetPrototype (ctx, val); - while (!JS_IsNull (proto) && !JS_IsException (proto)) { - /* If master is a function with prototype property, check that */ - if (JS_IsFunction (master)) { - JSValue master_proto = JS_GetPropertyStr (ctx, master, "prototype"); - if (!JS_IsException (master_proto) && !JS_IsNull (master_proto)) { - JSRecord *p1 = JS_VALUE_GET_OBJ (proto); - JSRecord *p2 = JS_VALUE_GET_OBJ (master_proto); - if (p1 == p2) { - return JS_TRUE; - } - } else if (!JS_IsException (master_proto)) { - } - } - /* Also check if proto == master directly */ - if (JS_IsObject (master)) { - JSRecord *p1 = JS_VALUE_GET_OBJ (proto); - JSRecord *p2 = JS_VALUE_GET_OBJ (master); - if (p1 == p2) { - return JS_TRUE; - } - } - - JSValue next = JS_GetPrototype (ctx, proto); - proto = next; - } - if (JS_IsException (proto)) return proto; - return JS_FALSE; -} - -static const char *const native_error_name[JS_NATIVE_ERROR_COUNT] = { - "EvalError", - "RangeError", - "ReferenceError", - "SyntaxError", - "TypeError", - "URIError", - "InternalError", - "AggregateError", -}; - -/* Minimum amount of objects to be able to compile code and display - error messages. No JSAtom should be allocated by this function. */ -static void JS_AddIntrinsicBasicObjects (JSContext *ctx) { - JSGCRef proto_ref; - int i; - - JS_PushGCRef (ctx, &proto_ref); - - ctx->class_proto[JS_CLASS_OBJECT] = JS_NewObjectProto (ctx, JS_NULL); - - ctx->class_proto[JS_CLASS_ERROR] = JS_NewObject (ctx); - - for (i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { - proto_ref.val = JS_NewObjectProto (ctx, ctx->class_proto[JS_CLASS_ERROR]); - JS_SetPropertyInternal (ctx, proto_ref.val, JS_KEY_name, JS_NewAtomString (ctx, native_error_name[i])); - JS_SetPropertyInternal (ctx, proto_ref.val, JS_KEY_message, JS_KEY_empty); - ctx->native_error_proto[i] = proto_ref.val; - } - - JS_PopGCRef (ctx, &proto_ref); -} - -/* logical(val) — false for 0/false/"false"/null, true for 1/true/"true", null otherwise */ -static JSValue js_cell_logical(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) return JS_NULL; - JSValue v = argv[0]; - if (JS_IsNull(v) || (JS_IsInt(v) && JS_VALUE_GET_INT(v) == 0) || - (JS_IsBool(v) && !JS_VALUE_GET_BOOL(v))) return JS_FALSE; - if ((JS_IsInt(v) && JS_VALUE_GET_INT(v) == 1) || - (JS_IsBool(v) && JS_VALUE_GET_BOOL(v))) return JS_TRUE; - if (JS_IsText(v)) { - char buf[8]; - JS_KeyGetStr(ctx, buf, sizeof(buf), v); - if (strcmp(buf, "false") == 0) return JS_FALSE; - if (strcmp(buf, "true") == 0) return JS_TRUE; - } - return JS_NULL; -} - -/* starts_with(str, prefix) — search(str, prefix) == 0 */ -static JSValue js_cell_starts_with(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2) return JS_NULL; - JSValue args[3] = { argv[0], argv[1], JS_NULL }; - JSValue pos = js_cell_text_search(ctx, JS_NULL, 2, args); - if (JS_IsInt(pos) && JS_VALUE_GET_INT(pos) == 0) return JS_TRUE; - return JS_FALSE; -} - -/* ends_with(str, suffix) — search(str, suffix, -length(suffix)) != null */ -static JSValue js_cell_ends_with(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2) return JS_NULL; - JSValue len_val = js_cell_length(ctx, JS_NULL, 1, &argv[1]); - int slen = JS_IsInt(len_val) ? JS_VALUE_GET_INT(len_val) : 0; - JSValue offset = JS_NewInt32(ctx, -slen); - JSValue args[3] = { argv[0], argv[1], offset }; - JSValue pos = js_cell_text_search(ctx, JS_NULL, 3, args); - if (!JS_IsNull(pos)) return JS_TRUE; - return JS_FALSE; -} - -/* every(arr, pred) — find(arr, x => !pred(x)) == null */ -static JSValue js_cell_every(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2) return JS_NULL; - if (!JS_IsArray(argv[0]) || !JS_IsFunction(argv[1])) return JS_NULL; - JSGCRef arr_ref, fn_ref; - JS_PushGCRef(ctx, &arr_ref); - JS_PushGCRef(ctx, &fn_ref); - arr_ref.val = argv[0]; - fn_ref.val = argv[1]; - JSArray *arr = JS_VALUE_GET_ARRAY(arr_ref.val); - for (int i = 0; i < arr->len; i++) { - JSValue elem = arr->values[i]; - JSValue r = JS_CallInternal(ctx, fn_ref.val, JS_NULL, 1, &elem, 0); - arr = JS_VALUE_GET_ARRAY(arr_ref.val); - if (JS_IsException(r)) { - JS_PopGCRef(ctx, &fn_ref); - JS_PopGCRef(ctx, &arr_ref); - return r; - } - if (!JS_ToBool(ctx, r)) { - JS_PopGCRef(ctx, &fn_ref); - JS_PopGCRef(ctx, &arr_ref); - return JS_FALSE; - } - } - JS_PopGCRef(ctx, &fn_ref); - JS_PopGCRef(ctx, &arr_ref); - return JS_TRUE; -} - -/* some(arr, pred) — find(arr, pred) != null */ -static JSValue js_cell_some(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 2) return JS_NULL; - JSValue r = js_cell_array_find(ctx, JS_NULL, argc, argv); - if (JS_IsException(r)) return r; - if (!JS_IsNull(r)) return JS_TRUE; - return JS_FALSE; -} - -/* GC-SAFE: Helper to set a global function. Creates function first, then reads - ctx->global_obj to ensure it's not stale if GC ran during function creation. */ -static void js_set_global_cfunc(JSContext *ctx, const char *name, JSCFunction *func, int length) { - JSGCRef ref; - JS_PushGCRef(ctx, &ref); - ref.val = JS_NewCFunction(ctx, func, name, length); - JS_SetPropertyStr(ctx, ctx->global_obj, name, ref.val); - JS_PopGCRef(ctx, &ref); -} - -static void JS_AddIntrinsicBaseObjects (JSContext *ctx) { - JSValue obj1; - - ctx->throw_type_error = JS_NewCFunction (ctx, js_throw_type_error, NULL, 0); - ctx->global_obj = JS_NewObject (ctx); - - /* Error */ - obj1 = JS_NewCFunctionMagic (ctx, js_error_constructor, "Error", 1, JS_CFUNC_generic_magic, -1); - JS_SetPropertyStr (ctx, ctx->global_obj, "Error", obj1); - - #define REGISTER_ERROR(idx, name) do { \ - JSValue func_obj = JS_NewCFunctionMagic(ctx, js_error_constructor, name, 1 + ((idx) == JS_AGGREGATE_ERROR), JS_CFUNC_generic_magic, (idx)); \ - JS_SetPropertyStr(ctx, ctx->global_obj, name, func_obj); \ - } while(0) - REGISTER_ERROR(0, "EvalError"); - REGISTER_ERROR(1, "RangeError"); - REGISTER_ERROR(2, "ReferenceError"); - REGISTER_ERROR(3, "SyntaxError"); - REGISTER_ERROR(4, "TypeError"); - REGISTER_ERROR(5, "URIError"); - REGISTER_ERROR(6, "InternalError"); - REGISTER_ERROR(7, "AggregateError"); - #undef REGISTER_ERROR - - /* Cell Script global functions: text, number, array, object, fn */ - { - JSValue text_func = JS_NewCFunction (ctx, js_cell_text, "text", 3); - JS_SetPropertyStr (ctx, ctx->global_obj, "text", text_func); - - JSValue number_func = JS_NewCFunction (ctx, js_cell_number, "number", 2); - JS_SetPropertyStr (ctx, ctx->global_obj, "number", number_func); - - JSValue array_func = JS_NewCFunction (ctx, js_cell_array, "array", 4); - JS_SetPropertyStr (ctx, ctx->global_obj, "array", array_func); - - JSValue object_func = JS_NewCFunction (ctx, js_cell_object, "object", 2); - JS_SetPropertyStr (ctx, ctx->global_obj, "object", object_func); - - /* Blob intrinsic type */ - { - JSClassDef blob_class = { - .class_name = "blob", - .finalizer = js_blob_finalizer, - }; - JS_NewClass (ctx, JS_CLASS_BLOB, &blob_class); - ctx->class_proto[JS_CLASS_BLOB] = JS_NewObject (ctx); - JS_SetPropertyFunctionList (ctx, ctx->class_proto[JS_CLASS_BLOB], js_blob_proto_funcs, countof (js_blob_proto_funcs)); - - JSValue blob_ctor = JS_NewCFunction2 (ctx, js_blob_constructor, "blob", 3, JS_CFUNC_generic, 0); - JS_SetPropertyStr (ctx, ctx->global_obj, "blob", blob_ctor); - } - - /* Core functions - using GC-safe helper */ - js_set_global_cfunc(ctx, "eval", js_cell_eval, 2); - js_set_global_cfunc(ctx, "mach_eval", js_mach_eval, 2); - js_set_global_cfunc(ctx, "stone", js_cell_stone, 1); - js_set_global_cfunc(ctx, "length", js_cell_length, 1); - js_set_global_cfunc(ctx, "call", js_cell_call, 3); - - /* is_* type checking functions */ - js_set_global_cfunc(ctx, "is_array", js_cell_is_array, 1); - js_set_global_cfunc(ctx, "is_blob", js_cell_is_blob, 1); - js_set_global_cfunc(ctx, "is_data", js_cell_is_data, 1); - js_set_global_cfunc(ctx, "is_function", js_cell_is_function, 1); - js_set_global_cfunc(ctx, "is_logical", js_cell_is_logical, 1); - js_set_global_cfunc(ctx, "is_integer", js_cell_is_integer, 1); - js_set_global_cfunc(ctx, "is_null", js_cell_is_null, 1); - js_set_global_cfunc(ctx, "is_number", js_cell_is_number, 1); - js_set_global_cfunc(ctx, "is_object", js_cell_is_object, 1); - js_set_global_cfunc(ctx, "is_stone", js_cell_is_stone, 1); - js_set_global_cfunc(ctx, "is_text", js_cell_is_text, 1); - js_set_global_cfunc(ctx, "is_proto", js_cell_is_proto, 2); - - /* Utility functions */ - js_set_global_cfunc(ctx, "apply", js_cell_fn_apply, 2); - js_set_global_cfunc(ctx, "replace", js_cell_text_replace, 4); - js_set_global_cfunc(ctx, "lower", js_cell_text_lower, 1); - js_set_global_cfunc(ctx, "upper", js_cell_text_upper, 1); - js_set_global_cfunc(ctx, "trim", js_cell_text_trim, 2); - js_set_global_cfunc(ctx, "codepoint", js_cell_text_codepoint, 1); - js_set_global_cfunc(ctx, "search", js_cell_text_search, 3); - js_set_global_cfunc(ctx, "extract", js_cell_text_extract, 4); - js_set_global_cfunc(ctx, "format", js_cell_text_format, 3); - js_set_global_cfunc(ctx, "reduce", js_cell_array_reduce, 4); - js_set_global_cfunc(ctx, "arrfor", js_cell_array_for, 4); - js_set_global_cfunc(ctx, "find", js_cell_array_find, 4); - js_set_global_cfunc(ctx, "filter", js_cell_array_filter, 2); - js_set_global_cfunc(ctx, "sort", js_cell_array_sort, 2); - - /* Number utility functions */ - js_set_global_cfunc(ctx, "whole", js_cell_number_whole, 1); - js_set_global_cfunc(ctx, "fraction", js_cell_number_fraction, 1); - js_set_global_cfunc(ctx, "floor", js_cell_number_floor, 2); - js_set_global_cfunc(ctx, "ceiling", js_cell_number_ceiling, 2); - js_set_global_cfunc(ctx, "abs", js_cell_number_abs, 1); - js_set_global_cfunc(ctx, "round", js_cell_number_round, 2); - js_set_global_cfunc(ctx, "sign", js_cell_number_sign, 1); - js_set_global_cfunc(ctx, "trunc", js_cell_number_trunc, 2); - js_set_global_cfunc(ctx, "min", js_cell_number_min, 2); - js_set_global_cfunc(ctx, "max", js_cell_number_max, 2); - js_set_global_cfunc(ctx, "remainder", js_cell_number_remainder, 2); - js_set_global_cfunc(ctx, "character", js_cell_character, 2); - js_set_global_cfunc(ctx, "modulo", js_cell_modulo, 2); - js_set_global_cfunc(ctx, "neg", js_cell_neg, 1); - js_set_global_cfunc(ctx, "not", js_cell_not, 1); - js_set_global_cfunc(ctx, "reverse", js_cell_reverse, 1); - js_set_global_cfunc(ctx, "proto", js_cell_proto, 1); - js_set_global_cfunc(ctx, "splat", js_cell_splat, 1); - - /* pi - mathematical constant (no GC concern for immediate float) */ - JS_SetPropertyStr(ctx, ctx->global_obj, "pi", - JS_NewFloat64(ctx, 3.14159265358979323846264338327950288419716939937510)); - - js_set_global_cfunc(ctx, "push", js_cell_push, 2); - js_set_global_cfunc(ctx, "pop", js_cell_pop, 1); - js_set_global_cfunc(ctx, "meme", js_cell_meme, 2); - - /* Engine builtins (normally from engine.cm, needed for --mach-run) */ - js_set_global_cfunc(ctx, "logical", js_cell_logical, 1); - js_set_global_cfunc(ctx, "starts_with", js_cell_starts_with, 2); - js_set_global_cfunc(ctx, "ends_with", js_cell_ends_with, 2); - js_set_global_cfunc(ctx, "every", js_cell_every, 2); - js_set_global_cfunc(ctx, "some", js_cell_some, 2); - - /* fn record with apply property */ - { - JSGCRef fn_ref; - JS_PushGCRef(ctx, &fn_ref); - fn_ref.val = JS_NewObject(ctx); - JSValue apply_fn = JS_NewCFunction(ctx, js_cell_fn_apply, "apply", 2); - JS_SetPropertyStr(ctx, fn_ref.val, "apply", apply_fn); - JS_SetPropertyStr(ctx, ctx->global_obj, "fn", fn_ref.val); - JS_PopGCRef(ctx, &fn_ref); - } - - /* I/O functions */ - js_set_global_cfunc(ctx, "print", js_print, -1); /* variadic: length < 0 means no arg limit */ - js_set_global_cfunc(ctx, "stacktrace", js_stacktrace, 0); - } -} - -#define STRLEN(s) (sizeof (s) / sizeof (s[0])) -#define CSTR "" - -static inline void key_to_buf (JSContext *ctx, JSValue key, char *dst, int cap, const char *fallback) { - if (JS_IsNull (key)) { - strncpy (dst, fallback, cap); - dst[cap - 1] = 0; - return; - } - JS_KeyGetStr (ctx, dst, cap, key); - if (dst[0] == 0) { - strncpy (dst, fallback, cap); - dst[cap - 1] = 0; - } -} - -void js_debug_info (JSContext *js, JSValue fn, js_debug *dbg) { - *dbg = (js_debug){ 0 }; - - if (!JS_IsFunction (fn)) return; - - JSFunction *f = JS_VALUE_GET_FUNCTION (fn); - dbg->unique = (int)(uintptr_t)f; - - JSValue name_key = JS_NULL; - if (!JS_IsNull (f->name)) - name_key = f->name; - else if (f->kind == JS_FUNC_KIND_BYTECODE && f->u.func.function_bytecode) - name_key = f->u.func.function_bytecode->func_name; - - key_to_buf (js, name_key, dbg->name, sizeof (dbg->name), ""); - - if (f->kind == JS_FUNC_KIND_BYTECODE) { - JSFunctionBytecode *b = f->u.func.function_bytecode; - key_to_buf (js, b->debug.filename, dbg->filename, sizeof (dbg->filename), "unknown"); - dbg->what = "JS"; - dbg->closure_n = b->closure_var_count; - dbg->param_n = b->arg_count; - dbg->vararg = 1; - dbg->source = (const uint8_t *)b->debug.source; - dbg->srclen = b->debug.source_len; - dbg->line = 0; /* see below */ - return; - } - - if (f->kind == JS_FUNC_KIND_C || f->kind == JS_FUNC_KIND_C_DATA) { - strncpy (dbg->filename, "", sizeof (dbg->filename)); - dbg->filename[sizeof (dbg->filename) - 1] = 0; - dbg->what = "C"; - dbg->param_n = f->length; - dbg->vararg = 1; - dbg->line = 0; - dbg->source = (const uint8_t *)CSTR; - dbg->srclen = STRLEN (CSTR); - } -} - -void js_debug_sethook (JSContext *ctx, js_hook hook, int type, void *user) { - ctx->trace_hook = hook; - ctx->trace_type = type; - ctx->trace_data = user; -} - -uint32_t js_debugger_stack_depth (JSContext *ctx) { - uint32_t stack_index = 0; - JSStackFrame *sf = ctx->current_stack_frame; - while (sf != NULL) { - sf = sf->prev_frame; - stack_index++; - } - return stack_index; -} - -JSValue js_debugger_backtrace_fns (JSContext *ctx, const uint8_t *cur_pc) { - JSValue ret = JS_NewArray (ctx); - JSStackFrame *sf; - uint32_t stack_index = 0; - - for (sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) { - uint32_t id = stack_index++; - JS_SetPropertyUint32 (ctx, ret, id, sf->cur_func); - } - return ret; -} - -JSValue js_debugger_build_backtrace (JSContext *ctx, const uint8_t *cur_pc) { - JSStackFrame *sf; - const char *func_name_str; - JSFunction *f; - JSValue ret = JS_NewArray (ctx); - uint32_t stack_index = 0; - - for (sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) { - JSValue current_frame = JS_NewObject (ctx); - - uint32_t id = stack_index++; - JS_SetPropertyStr (ctx, current_frame, "id", JS_NewUint32 (ctx, id)); - - func_name_str = get_func_name (ctx, sf->cur_func); - if (!func_name_str || func_name_str[0] == '\0') - JS_SetPropertyStr (ctx, current_frame, "name", JS_NewString (ctx, "")); - else - JS_SetPropertyStr (ctx, current_frame, "name", JS_NewString (ctx, func_name_str)); - JS_FreeCString (ctx, func_name_str); - - if (JS_VALUE_GET_TAG (sf->cur_func) == JS_TAG_FUNCTION) { - f = JS_VALUE_GET_FUNCTION (sf->cur_func); - if (f->kind == JS_FUNC_KIND_BYTECODE) { - JSFunctionBytecode *b; - int line_num1; - - b = f->u.func.function_bytecode; - if (b->has_debug) { - const uint8_t *pc = sf != ctx->current_stack_frame || !cur_pc - ? sf->cur_pc - : cur_pc; - int col_num; - line_num1 - = find_line_num (ctx, b, pc - b->byte_code_buf - 1, &col_num); - JS_SetPropertyStr (ctx, current_frame, "filename", b->debug.filename); - if (line_num1 != -1) - JS_SetPropertyStr (ctx, current_frame, "line", JS_NewUint32 (ctx, line_num1)); - } - } else { - JS_SetPropertyStr (ctx, current_frame, "name", JS_NewString (ctx, "(native)")); - } - } else { - JS_SetPropertyStr (ctx, current_frame, "name", JS_NewString (ctx, "(native)")); - } - JS_SetPropertyUint32 (ctx, ret, id, current_frame); - } - return ret; -} - -JSValue js_debugger_fn_info (JSContext *ctx, JSValue fn) { - JSValue ret = JS_NewObject (ctx); - if (!js_is_bytecode_function (fn)) goto done; - - JSFunction *f = JS_VALUE_GET_FUNCTION (fn); - JSFunctionBytecode *b = f->u.func.function_bytecode; - char atom_buf[KEY_GET_STR_BUF_SIZE]; - const char *str; - int i; - - // Function name - if (!JS_IsNull (b->func_name)) { - str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), b->func_name); - JS_SetPropertyStr (ctx, ret, "name", JS_NewString (ctx, str)); - } - - // File location info - if (b->has_debug && !JS_IsNull (b->debug.filename)) { - int line_num, col_num; - str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), b->debug.filename); - line_num = find_line_num (ctx, b, -1, &col_num); - JS_SetPropertyStr (ctx, ret, "filename", JS_NewString (ctx, str)); - JS_SetPropertyStr (ctx, ret, "line", JS_NewInt32 (ctx, line_num)); - JS_SetPropertyStr (ctx, ret, "column", JS_NewInt32 (ctx, col_num)); - } - - // Arguments - if (b->arg_count && b->vardefs) { - JSValue args_array = JS_NewArray (ctx); - for (i = 0; i < b->arg_count; i++) { - str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), b->vardefs[i].var_name); - JS_SetPropertyUint32 (ctx, args_array, i, JS_NewString (ctx, str)); - } - JS_SetPropertyStr (ctx, ret, "args", args_array); - } - - // Local variables - if (b->var_count && b->vardefs) { - JSValue locals_array = JS_NewArray (ctx); - for (i = 0; i < b->var_count; i++) { - JSVarDef *vd = &b->vardefs[b->arg_count + i]; - JSValue local_obj = JS_NewObject (ctx); - - str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), vd->var_name); - JS_SetPropertyStr (ctx, local_obj, "name", JS_NewString (ctx, str)); - - const char *var_type = vd->var_kind == JS_VAR_CATCH ? "catch" - : (vd->var_kind == JS_VAR_FUNCTION_DECL - || vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) - ? "function" - : vd->is_const ? "const" - : vd->is_lexical ? "let" - : "var"; - JS_SetPropertyStr (ctx, local_obj, "type", JS_NewString (ctx, var_type)); - JS_SetPropertyStr (ctx, local_obj, "index", JS_NewInt32 (ctx, i)); - - if (vd->scope_level) { - JS_SetPropertyStr (ctx, local_obj, "scope_level", JS_NewInt32 (ctx, vd->scope_level)); - JS_SetPropertyStr (ctx, local_obj, "scope_next", JS_NewInt32 (ctx, vd->scope_next)); - } - - JS_SetPropertyUint32 (ctx, locals_array, i, local_obj); - } - JS_SetPropertyStr (ctx, ret, "locals", locals_array); - } - - // Closure variables - if (b->closure_var_count) { - JSValue closure_array = JS_NewArray (ctx); - for (i = 0; i < b->closure_var_count; i++) { - JSClosureVar *cv = &b->closure_var[i]; - JSValue closure_obj = JS_NewObject (ctx); - - str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), cv->var_name); - JS_SetPropertyStr (ctx, closure_obj, "name", JS_NewString (ctx, str)); - JS_SetPropertyStr (ctx, closure_obj, "is_local", JS_NewBool (ctx, cv->is_local)); - JS_SetPropertyStr (ctx, closure_obj, "is_arg", JS_NewBool (ctx, cv->is_arg)); - JS_SetPropertyStr (ctx, closure_obj, "var_idx", JS_NewInt32 (ctx, cv->var_idx)); - - const char *var_type = cv->is_const ? "const" - : cv->is_lexical ? "let" - : "var"; - JS_SetPropertyStr (ctx, closure_obj, "type", JS_NewString (ctx, var_type)); - - JS_SetPropertyUint32 (ctx, closure_array, i, closure_obj); - } - JS_SetPropertyStr (ctx, ret, "closure_vars", closure_array); - } - - // Stack size - JS_SetPropertyStr (ctx, ret, "stack_size", JS_NewInt32 (ctx, b->stack_size)); - -done: - return ret; -} - -// Opcode names array for debugger -static const char *opcode_names[] = { -#define FMT(f) -#define DEF(id, size, n_pop, n_push, f) #id, -#define def(id, size, n_pop, n_push, f) -#include "quickjs-opcode.h" -#undef def -#undef DEF -#undef FMT -}; - -JSValue js_debugger_fn_bytecode (JSContext *ctx, JSValue fn) { - if (!js_is_bytecode_function (fn)) return JS_NULL; - - JSFunction *f = JS_VALUE_GET_FUNCTION (fn); - JSFunctionBytecode *b = f->u.func.function_bytecode; - JSValue ret = JS_NewArray (ctx); - - const uint8_t *tab = b->byte_code_buf; - int len = b->byte_code_len; - int pos = 0; - int idx = 0; - BOOL use_short_opcodes = TRUE; - char opcode_str[256]; - - while (pos < len) { - int op = tab[pos]; - const JSOpCode *oi; - - if (use_short_opcodes) - oi = &short_opcode_info (op); - else - oi = &opcode_info[op]; - - int size = oi->size; - if (pos + size > len) { break; } - - if (op >= sizeof (opcode_names) / sizeof (opcode_names[0])) { - snprintf (opcode_str, sizeof (opcode_str), "unknown"); - } else { - const char *opcode_name = opcode_names[op]; - snprintf (opcode_str, sizeof (opcode_str), "%s", opcode_name); - - // Add arguments based on opcode format - int arg_pos = pos + 1; - switch (oi->fmt) { - case OP_FMT_none_int: - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %d", - op - OP_push_0); - break; - case OP_FMT_npopx: - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %d", - op - OP_call0); - break; - case OP_FMT_u8: - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u", - get_u8 (tab + arg_pos)); - break; - case OP_FMT_i8: - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %d", - get_i8 (tab + arg_pos)); - break; - case OP_FMT_u16: - case OP_FMT_npop: - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u", - get_u16 (tab + arg_pos)); - break; - case OP_FMT_npop_u16: - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u %u", - get_u16 (tab + arg_pos), - get_u16 (tab + arg_pos + 2)); - break; - case OP_FMT_i16: - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %d", - get_i16 (tab + arg_pos)); - break; - case OP_FMT_i32: - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %d", - get_i32 (tab + arg_pos)); - break; - case OP_FMT_u32: - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u", - get_u32 (tab + arg_pos)); - break; -#if SHORT_OPCODES - case OP_FMT_label8: - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u", - get_i8 (tab + arg_pos) + arg_pos); - break; - case OP_FMT_label16: - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u", - get_i16 (tab + arg_pos) + arg_pos); - break; - case OP_FMT_const8: - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u", - get_u8 (tab + arg_pos)); - break; -#endif - case OP_FMT_const: - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u", - get_u32 (tab + arg_pos)); - break; - case OP_FMT_label: - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u", - get_u32 (tab + arg_pos) + arg_pos); - break; - case OP_FMT_label_u16: - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u %u", - get_u32 (tab + arg_pos) + arg_pos, - get_u16 (tab + arg_pos + 4)); - break; - case OP_FMT_key: { - /* Key operand is a cpool index */ - uint32_t key_idx = get_u32 (tab + arg_pos); - if (key_idx < b->cpool_count) { - JSValue key = b->cpool[key_idx]; - const char *key_str = JS_ToCString (ctx, key); - if (key_str) { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " key:%s", - key_str); - JS_FreeCString (ctx, key_str); - } else { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " key[%u]", - key_idx); - } - } else { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " key[%u]", - key_idx); - } - } break; - case OP_FMT_key_u8: { - uint32_t cpool_idx = get_u32 (tab + arg_pos); - const char *key_str = NULL; - if (cpool_idx < b->cpool_count) - key_str = JS_ToCString (ctx, b->cpool[cpool_idx]); - if (key_str) { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %s %d", - key_str, - get_u8 (tab + arg_pos + 4)); - JS_FreeCString (ctx, key_str); - } else { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " cpool[%u] %d", - cpool_idx, - get_u8 (tab + arg_pos + 4)); - } - } break; - case OP_FMT_key_u16: { - uint32_t cpool_idx = get_u32 (tab + arg_pos); - const char *key_str = NULL; - if (cpool_idx < b->cpool_count) - key_str = JS_ToCString (ctx, b->cpool[cpool_idx]); - if (key_str) { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %s %d", - key_str, - get_u16 (tab + arg_pos + 4)); - JS_FreeCString (ctx, key_str); - } else { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " cpool[%u] %d", - cpool_idx, - get_u16 (tab + arg_pos + 4)); - } - } break; - case OP_FMT_key_label_u16: { - uint32_t cpool_idx = get_u32 (tab + arg_pos); - int addr = get_u32 (tab + arg_pos + 4); - int extra = get_u16 (tab + arg_pos + 8); - const char *key_str = NULL; - if (cpool_idx < b->cpool_count) - key_str = JS_ToCString (ctx, b->cpool[cpool_idx]); - if (key_str) { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %s %u %u", - key_str, - addr + arg_pos + 4, - extra); - JS_FreeCString (ctx, key_str); - } else { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " cpool[%u] %u %u", - cpool_idx, - addr + arg_pos + 4, - extra); - } - } break; - case OP_FMT_none_loc: { - int idx = (op - OP_get_loc0) % 4; - if (idx < b->var_count) { - const char *var_name - = JS_ToCString (ctx, b->vardefs[idx].var_name); - if (var_name) { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %d: %s", - idx, - var_name); - JS_FreeCString (ctx, var_name); - } else { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %d", - idx); - } - } else { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %d", - idx); - } - } break; - case OP_FMT_loc8: { - int idx = get_u8 (tab + arg_pos); - if (idx < b->var_count) { - const char *var_name - = JS_ToCString (ctx, b->vardefs[idx].var_name); - if (var_name) { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u: %s", - idx, - var_name); - JS_FreeCString (ctx, var_name); - } else { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u", - idx); - } - } else { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u", - idx); - } - } break; - case OP_FMT_loc: { - int idx = get_u16 (tab + arg_pos); - if (idx < b->var_count) { - const char *var_name - = JS_ToCString (ctx, b->vardefs[idx].var_name); - if (var_name) { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u: %s", - idx, - var_name); - JS_FreeCString (ctx, var_name); - } else { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u", - idx); - } - } else { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u", - idx); - } - } break; - case OP_FMT_none_arg: { - int idx = (op - OP_get_arg0) % 4; - if (idx < b->arg_count) { - const char *arg_name - = JS_ToCString (ctx, b->vardefs[idx].var_name); - if (arg_name) { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %d: %s", - idx, - arg_name); - JS_FreeCString (ctx, arg_name); - } else { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %d", - idx); - } - } else { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %d", - idx); - } - } break; - case OP_FMT_arg: { - int idx = get_u16 (tab + arg_pos); - if (idx < b->arg_count) { - const char *arg_name - = JS_ToCString (ctx, b->vardefs[idx].var_name); - if (arg_name) { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u: %s", - idx, - arg_name); - JS_FreeCString (ctx, arg_name); - } else { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u", - idx); - } - } else { - snprintf (opcode_str + strlen (opcode_str), - sizeof (opcode_str) - strlen (opcode_str), - " %u", - idx); - } - } break; - default: - break; - } - } - - JSValue js_opcode_str = JS_NewString (ctx, opcode_str); - JS_SetPropertyUint32 (ctx, ret, idx++, js_opcode_str); - - pos += size; - } - - return ret; -} - -JSValue js_debugger_local_variables (JSContext *ctx, int stack_index) { - JSValue ret = JS_NewObject (ctx); - - JSStackFrame *sf; - int cur_index = 0; - - for (sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) { - // this val is one frame up - if (cur_index == stack_index - 1) { - if (js_is_bytecode_function (sf->cur_func)) { - JSFunction *f = JS_VALUE_GET_FUNCTION (sf->cur_func); - JSFunctionBytecode *b = f->u.func.function_bytecode; - - JSValue this_obj = sf->var_buf[b->var_count]; - // only provide a this if it is not the global object. - if (JS_VALUE_GET_OBJ (this_obj) != JS_VALUE_GET_OBJ (ctx->global_obj)) - JS_SetPropertyStr (ctx, ret, "this", this_obj); - } - } - - if (cur_index < stack_index) { - cur_index++; - continue; - } - - if (!js_is_bytecode_function (sf->cur_func)) goto done; - JSFunction *f = JS_VALUE_GET_FUNCTION (sf->cur_func); - JSFunctionBytecode *b = f->u.func.function_bytecode; - - for (uint32_t i = 0; i < b->arg_count + b->var_count; i++) { - JSValue var_val; - if (i < b->arg_count) - var_val = sf->arg_buf[i]; - else - var_val = sf->var_buf[i - b->arg_count]; - - if (JS_IsUninitialized (var_val)) continue; - - JSVarDef *vd = b->vardefs + i; - JS_SetProperty (ctx, ret, vd->var_name, var_val); - } - - break; - } - -done: - return ret; -} - -void js_debugger_set_closure_variable (JSContext *ctx, JSValue fn, JSValue var_name, JSValue val) { - /* TODO: Reimplement using outer_frame mechanism if debugging is needed */ - (void)ctx; (void)fn; (void)var_name; (void)val; -} - -JSValue js_debugger_closure_variables (JSContext *ctx, JSValue fn) { - /* TODO: Reimplement using outer_frame mechanism if debugging is needed */ - (void)fn; - return JS_NewObject (ctx); -} - -void *js_debugger_val_address (JSContext *ctx, JSValue val) { - return JS_VALUE_GET_PTR (val); -} - -/* ============================================================================ - * Cell Script Module: json - * Provides json.encode() and json.decode() using pure C implementation - * ============================================================================ - */ - -static JSValue js_cell_json_encode (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) - return JS_ThrowTypeError (ctx, "json.encode requires at least 1 argument"); - - JSValue replacer = argc > 1 ? argv[1] : JS_NULL; - JSValue space = argc > 2 ? argv[2] : JS_NewInt32 (ctx, 1); - JSValue result = JS_JSONStringify (ctx, argv[0], replacer, space); - return result; -} - -static JSValue js_cell_json_decode (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - if (argc < 1) - return JS_ThrowTypeError (ctx, "json.decode requires at least 1 argument"); - - if (!JS_IsText (argv[0])) { - JSValue err = JS_NewError (ctx); - JS_SetPropertyStr ( - ctx, err, "message", JS_NewString (ctx, "couldn't parse text: not a string")); - return JS_Throw (ctx, err); - } - - const char *str = JS_ToCString (ctx, argv[0]); - if (!str) return JS_EXCEPTION; - - size_t len = strlen (str); - JSValue result = JS_ParseJSON (ctx, str, len, ""); - JS_FreeCString (ctx, str); - - /* Apply reviver if provided */ - if (argc > 1 && JS_IsFunction (argv[1]) && !JS_IsException (result)) { - /* Create wrapper object to pass to reviver */ - JSValue wrapper = JS_NewObject (ctx); - JS_SetPropertyStr (ctx, wrapper, "", result); - - JSValue holder = wrapper; - JSValue key = JS_KEY_empty; - JSValue args[2] = { key, JS_GetProperty (ctx, holder, key) }; - JSValue final = JS_Call (ctx, argv[1], holder, 2, args); - result = final; - } - - return result; -} - -static const JSCFunctionListEntry js_cell_json_funcs[] = { - JS_CFUNC_DEF ("encode", 1, js_cell_json_encode), - JS_CFUNC_DEF ("decode", 1, js_cell_json_decode), -}; - -JSValue js_json_use (JSContext *ctx) { - JSGCRef obj_ref; - JS_PushGCRef (ctx, &obj_ref); - obj_ref.val = JS_NewObject (ctx); - JS_SetPropertyFunctionList (ctx, obj_ref.val, js_cell_json_funcs, countof (js_cell_json_funcs)); - JSValue result = obj_ref.val; - JS_PopGCRef (ctx, &obj_ref); - return result; -} - -/* ============================================================================ - * Cell Script Module: nota - * Provides nota.encode() and nota.decode() for NOTA binary serialization - * ============================================================================ - */ - -static int nota_get_arr_len (JSContext *ctx, JSValue arr) { - int64_t len; - JS_GetLength (ctx, arr, &len); - return (int)len; -} - -typedef struct NotaEncodeContext { - JSContext *ctx; - JSGCRef *visitedStack_ref; /* pointer to GC-rooted ref */ - NotaBuffer nb; - int cycle; - JSGCRef *replacer_ref; /* pointer to GC-rooted ref */ -} NotaEncodeContext; - -static void nota_stack_push (NotaEncodeContext *enc, JSValueConst val) { - JSContext *ctx = enc->ctx; - int len = nota_get_arr_len (ctx, enc->visitedStack_ref->val); - JS_SetPropertyInt64 (ctx, enc->visitedStack_ref->val, len, JS_DupValue (ctx, val)); -} - -static void nota_stack_pop (NotaEncodeContext *enc) { - JSContext *ctx = enc->ctx; - int len = nota_get_arr_len (ctx, enc->visitedStack_ref->val); - JS_SetPropertyStr (ctx, enc->visitedStack_ref->val, "length", JS_NewUint32 (ctx, len - 1)); -} - -static int nota_stack_has (NotaEncodeContext *enc, JSValueConst val) { - JSContext *ctx = enc->ctx; - int len = nota_get_arr_len (ctx, enc->visitedStack_ref->val); - for (int i = 0; i < len; i++) { - JSValue elem = JS_GetPropertyUint32 (ctx, enc->visitedStack_ref->val, i); - if (JS_IsObject (elem) && JS_IsObject (val)) { - if (JS_StrictEq (ctx, elem, val)) { - JS_FreeValue (ctx, elem); - return 1; - } - } - JS_FreeValue (ctx, elem); - } - return 0; -} - -static JSValue nota_apply_replacer (NotaEncodeContext *enc, JSValueConst holder, JSValueConst key, JSValueConst val) { - if (!enc->replacer_ref || JS_IsNull (enc->replacer_ref->val)) return JS_DupValue (enc->ctx, val); - - JSValue args[2] = { JS_DupValue (enc->ctx, key), JS_DupValue (enc->ctx, val) }; - JSValue result = JS_Call (enc->ctx, enc->replacer_ref->val, holder, 2, args); - JS_FreeValue (enc->ctx, args[0]); - JS_FreeValue (enc->ctx, args[1]); - - if (JS_IsException (result)) return JS_DupValue (enc->ctx, val); - return result; -} - -static char *js_do_nota_decode (JSContext *js, JSValue *tmp, char *nota, JSValue holder, JSValue key, JSValue reviver) { - int type = nota_type (nota); - JSValue ret2; - long long n; - double d; - int b; - char *str; - uint8_t *blob; - - switch (type) { - case NOTA_BLOB: - nota = nota_read_blob (&n, (char **)&blob, nota); - *tmp = js_new_blob_stoned_copy (js, blob, n); - sys_free (blob); - break; - case NOTA_TEXT: - nota = nota_read_text (&str, nota); - *tmp = JS_NewString (js, str); - sys_free (str); - break; - case NOTA_ARR: - nota = nota_read_array (&n, nota); - *tmp = JS_NewArray (js); - for (int i = 0; i < n; i++) { - nota = js_do_nota_decode (js, &ret2, nota, *tmp, JS_NewInt32 (js, i), reviver); - JS_SetPropertyInt64 (js, *tmp, i, ret2); - } - break; - case NOTA_REC: - nota = nota_read_record (&n, nota); - *tmp = JS_NewObject (js); - for (int i = 0; i < n; i++) { - nota = nota_read_text (&str, nota); - JSValue prop_key = JS_NewString (js, str); - nota = js_do_nota_decode (js, &ret2, nota, *tmp, prop_key, reviver); - JS_SetPropertyStr (js, *tmp, str, ret2); - JS_FreeValue (js, prop_key); - sys_free (str); - } - break; - case NOTA_INT: - nota = nota_read_int (&n, nota); - *tmp = JS_NewInt64 (js, n); - break; - case NOTA_SYM: - nota = nota_read_sym (&b, nota); - if (b == NOTA_PRIVATE) { - JSValue inner; - nota = js_do_nota_decode (js, &inner, nota, holder, JS_NULL, reviver); - JSValue obj = JS_NewObject (js); - *tmp = obj; - } else { - switch (b) { - case NOTA_NULL: *tmp = JS_NULL; break; - case NOTA_FALSE: *tmp = JS_NewBool (js, 0); break; - case NOTA_TRUE: *tmp = JS_NewBool (js, 1); break; - default: *tmp = JS_NULL; break; - } - } - break; - default: - case NOTA_FLOAT: - nota = nota_read_float (&d, nota); - *tmp = JS_NewFloat64 (js, d); - break; - } - - if (!JS_IsNull (reviver)) { - JSValue args[2] = { JS_DupValue (js, key), JS_DupValue (js, *tmp) }; - JSValue revived = JS_Call (js, reviver, holder, 2, args); - JS_FreeValue (js, args[0]); - JS_FreeValue (js, args[1]); - if (!JS_IsException (revived)) { - JS_FreeValue (js, *tmp); - *tmp = revived; - } else { - JS_FreeValue (js, revived); - } - } - - return nota; -} - -static void nota_encode_value (NotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValueConst key) { - JSContext *ctx = enc->ctx; - JSGCRef replaced_ref, keys_ref, elem_ref, prop_ref; - JS_PushGCRef (ctx, &replaced_ref); - replaced_ref.val = nota_apply_replacer (enc, holder, key, val); - int tag = JS_VALUE_GET_TAG (replaced_ref.val); - - switch (tag) { - case JS_TAG_INT: - case JS_TAG_FLOAT64: { - double d; - JS_ToFloat64 (ctx, &d, replaced_ref.val); - nota_write_number (&enc->nb, d); - break; - } - case JS_TAG_STRING: { - const char *str = JS_ToCString (ctx, replaced_ref.val); - nota_write_text (&enc->nb, str); - JS_FreeCString (ctx, str); - break; - } - case JS_TAG_BOOL: - if (JS_VALUE_GET_BOOL (replaced_ref.val)) nota_write_sym (&enc->nb, NOTA_TRUE); - else nota_write_sym (&enc->nb, NOTA_FALSE); - break; - case JS_TAG_NULL: - nota_write_sym (&enc->nb, NOTA_NULL); - break; - case JS_TAG_PTR: { - if (js_is_blob (ctx, replaced_ref.val)) { - size_t buf_len; - void *buf_data = js_get_blob_data (ctx, &buf_len, replaced_ref.val); - if (buf_data == (void *)-1) { - JS_PopGCRef (ctx, &replaced_ref); - return; - } - nota_write_blob (&enc->nb, (unsigned long long)buf_len * 8, (const char *)buf_data); - break; - } - - if (JS_IsArray (replaced_ref.val)) { - if (nota_stack_has (enc, replaced_ref.val)) { - enc->cycle = 1; - break; - } - nota_stack_push (enc, replaced_ref.val); - int arr_len = nota_get_arr_len (ctx, replaced_ref.val); - nota_write_array (&enc->nb, arr_len); - JS_PushGCRef (ctx, &elem_ref); - for (int i = 0; i < arr_len; i++) { - elem_ref.val = JS_GetPropertyUint32 (ctx, replaced_ref.val, i); - JSValue elem_key = JS_NewInt32 (ctx, i); - nota_encode_value (enc, elem_ref.val, replaced_ref.val, elem_key); - } - JS_PopGCRef (ctx, &elem_ref); - nota_stack_pop (enc); - break; - } - - JSValue adata = JS_NULL; - if (!JS_IsNull (adata)) { - nota_write_sym (&enc->nb, NOTA_PRIVATE); - nota_encode_value (enc, adata, replaced_ref.val, JS_NULL); - break; - } - if (nota_stack_has (enc, replaced_ref.val)) { - enc->cycle = 1; - break; - } - nota_stack_push (enc, replaced_ref.val); - - JSValue to_json = JS_GetPropertyStr (ctx, replaced_ref.val, "toJSON"); - if (JS_IsFunction (to_json)) { - JSValue result = JS_Call (ctx, to_json, replaced_ref.val, 0, NULL); - if (!JS_IsException (result)) { - nota_encode_value (enc, result, holder, key); - } else { - nota_write_sym (&enc->nb, NOTA_NULL); - } - nota_stack_pop (enc); - break; - } - - JS_PushGCRef (ctx, &keys_ref); - keys_ref.val = JS_GetOwnPropertyNames (ctx, replaced_ref.val); - if (JS_IsException (keys_ref.val)) { - nota_write_sym (&enc->nb, NOTA_NULL); - nota_stack_pop (enc); - JS_PopGCRef (ctx, &keys_ref); - break; - } - int64_t plen64; - if (JS_GetLength (ctx, keys_ref.val, &plen64) < 0) { - nota_write_sym (&enc->nb, NOTA_NULL); - nota_stack_pop (enc); - JS_PopGCRef (ctx, &keys_ref); - break; - } - uint32_t plen = (uint32_t)plen64; - - JS_PushGCRef (ctx, &prop_ref); - JS_PushGCRef (ctx, &elem_ref); - uint32_t non_function_count = 0; - for (uint32_t i = 0; i < plen; i++) { - elem_ref.val = JS_GetPropertyUint32 (ctx, keys_ref.val, i); - prop_ref.val = JS_GetProperty (ctx, replaced_ref.val, elem_ref.val); - if (!JS_IsFunction (prop_ref.val)) non_function_count++; - } - - nota_write_record (&enc->nb, non_function_count); - for (uint32_t i = 0; i < plen; i++) { - elem_ref.val = JS_GetPropertyUint32 (ctx, keys_ref.val, i); - prop_ref.val = JS_GetProperty (ctx, replaced_ref.val, elem_ref.val); - if (!JS_IsFunction (prop_ref.val)) { - const char *prop_name = JS_ToCString (ctx, elem_ref.val); - nota_write_text (&enc->nb, prop_name ? prop_name : ""); - nota_encode_value (enc, prop_ref.val, replaced_ref.val, elem_ref.val); - JS_FreeCString (ctx, prop_name); - } - } - JS_PopGCRef (ctx, &elem_ref); - JS_PopGCRef (ctx, &prop_ref); - JS_PopGCRef (ctx, &keys_ref); - nota_stack_pop (enc); - break; - } - default: - nota_write_sym (&enc->nb, NOTA_NULL); - break; - } - JS_PopGCRef (ctx, &replaced_ref); -} - -void *value2nota (JSContext *ctx, JSValue v) { - JSGCRef val_ref, stack_ref, key_ref; - JS_PushGCRef (ctx, &val_ref); - JS_PushGCRef (ctx, &stack_ref); - JS_PushGCRef (ctx, &key_ref); - val_ref.val = v; - - NotaEncodeContext enc_s, *enc = &enc_s; - enc->ctx = ctx; - stack_ref.val = JS_NewArray (ctx); - enc->visitedStack_ref = &stack_ref; - enc->cycle = 0; - enc->replacer_ref = NULL; - - nota_buffer_init (&enc->nb, 128); - key_ref.val = JS_NewString (ctx, ""); - nota_encode_value (enc, val_ref.val, JS_NULL, key_ref.val); - - if (enc->cycle) { - JS_PopGCRef (ctx, &key_ref); - JS_PopGCRef (ctx, &stack_ref); - JS_PopGCRef (ctx, &val_ref); - nota_buffer_free (&enc->nb); - return NULL; - } - - JS_PopGCRef (ctx, &key_ref); - JS_PopGCRef (ctx, &stack_ref); - JS_PopGCRef (ctx, &val_ref); - void *data_ptr = enc->nb.data; - enc->nb.data = NULL; - nota_buffer_free (&enc->nb); - return data_ptr; -} - -JSValue nota2value (JSContext *js, void *nota) { - if (!nota) return JS_NULL; - JSGCRef holder_ref, key_ref, ret_ref; - JS_PushGCRef (js, &holder_ref); - JS_PushGCRef (js, &key_ref); - JS_PushGCRef (js, &ret_ref); - holder_ref.val = JS_NewObject (js); - key_ref.val = JS_NewString (js, ""); - ret_ref.val = JS_NULL; - js_do_nota_decode (js, &ret_ref.val, nota, holder_ref.val, key_ref.val, JS_NULL); - JSValue result = ret_ref.val; - JS_PopGCRef (js, &ret_ref); - JS_PopGCRef (js, &key_ref); - JS_PopGCRef (js, &holder_ref); - return result; -} - -static JSValue js_nota_encode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - if (argc < 1) return JS_ThrowTypeError (ctx, "nota.encode requires at least 1 argument"); - - JSGCRef val_ref, stack_ref, replacer_ref, key_ref; - JS_PushGCRef (ctx, &val_ref); - JS_PushGCRef (ctx, &stack_ref); - JS_PushGCRef (ctx, &replacer_ref); - JS_PushGCRef (ctx, &key_ref); - val_ref.val = argv[0]; - stack_ref.val = JS_NewArray (ctx); - replacer_ref.val = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL; - - NotaEncodeContext enc_s, *enc = &enc_s; - enc->ctx = ctx; - enc->visitedStack_ref = &stack_ref; - enc->cycle = 0; - enc->replacer_ref = &replacer_ref; - - nota_buffer_init (&enc->nb, 128); - key_ref.val = JS_NewString (ctx, ""); - nota_encode_value (enc, val_ref.val, JS_NULL, key_ref.val); - - JSValue ret; - if (enc->cycle) { - nota_buffer_free (&enc->nb); - ret = JS_ThrowReferenceError (ctx, "Tried to encode something to nota with a cycle."); - } else { - size_t total_len = enc->nb.size; - void *data_ptr = enc->nb.data; - ret = js_new_blob_stoned_copy (ctx, (uint8_t *)data_ptr, total_len); - nota_buffer_free (&enc->nb); - } - - JS_PopGCRef (ctx, &key_ref); - JS_PopGCRef (ctx, &replacer_ref); - JS_PopGCRef (ctx, &stack_ref); - JS_PopGCRef (ctx, &val_ref); - return ret; -} - -static JSValue js_nota_decode (JSContext *js, JSValueConst self, int argc, JSValueConst *argv) { - if (argc < 1) return JS_NULL; - - size_t len; - unsigned char *nota = js_get_blob_data (js, &len, argv[0]); - if (nota == (unsigned char *)-1) return JS_EXCEPTION; - if (!nota) return JS_NULL; - - JSGCRef holder_ref, key_ref, ret_ref, reviver_ref; - JS_PushGCRef (js, &holder_ref); - JS_PushGCRef (js, &key_ref); - JS_PushGCRef (js, &ret_ref); - JS_PushGCRef (js, &reviver_ref); - - reviver_ref.val = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL; - holder_ref.val = JS_NewObject (js); - key_ref.val = JS_NewString (js, ""); - ret_ref.val = JS_NULL; - - js_do_nota_decode (js, &ret_ref.val, (char *)nota, holder_ref.val, key_ref.val, reviver_ref.val); - - JSValue result = ret_ref.val; - JS_PopGCRef (js, &reviver_ref); - JS_PopGCRef (js, &ret_ref); - JS_PopGCRef (js, &key_ref); - JS_PopGCRef (js, &holder_ref); - return result; -} - -static const JSCFunctionListEntry js_nota_funcs[] = { - JS_CFUNC_DEF ("encode", 1, js_nota_encode), - JS_CFUNC_DEF ("decode", 1, js_nota_decode), -}; - -JSValue js_nota_use (JSContext *js) { - JSGCRef export_ref; - JS_PushGCRef (js, &export_ref); - export_ref.val = JS_NewObject (js); - JS_SetPropertyFunctionList (js, export_ref.val, js_nota_funcs, sizeof (js_nota_funcs) / sizeof (JSCFunctionListEntry)); - JSValue result = export_ref.val; - JS_PopGCRef (js, &export_ref); - return result; -} - -/* ============================================================================ - * Cell Script Module: wota - * Provides wota.encode() and wota.decode() for WOTA binary serialization - * ============================================================================ - */ - -typedef struct ObjectRef { - void *ptr; - struct ObjectRef *next; -} ObjectRef; - -typedef struct WotaEncodeContext { - JSContext *ctx; - ObjectRef *visited_stack; - WotaBuffer wb; - int cycle; - JSValue replacer; -} WotaEncodeContext; - -static void wota_stack_push (WotaEncodeContext *enc, JSValueConst val) { - (void)enc; (void)val; - /* Cycle detection disabled for performance */ -} - -static void wota_stack_pop (WotaEncodeContext *enc) { - if (!enc->visited_stack) return; - - ObjectRef *top = enc->visited_stack; - enc->visited_stack = top->next; - sys_free (top); -} - -static int wota_stack_has (WotaEncodeContext *enc, JSValueConst val) { - (void)enc; (void)val; - return 0; - /* Cycle detection disabled for performance */ -} - -static void wota_stack_free (WotaEncodeContext *enc) { - while (enc->visited_stack) { - wota_stack_pop (enc); - } -} - -static JSValue wota_apply_replacer (WotaEncodeContext *enc, JSValueConst holder, JSValue key, JSValueConst val) { - if (JS_IsNull (enc->replacer)) return JS_DupValue (enc->ctx, val); - JSValue key_val = JS_IsNull (key) ? JS_NULL : JS_DupValue (enc->ctx, key); - JSValue args[2] = { key_val, JS_DupValue (enc->ctx, val) }; - JSValue result = JS_Call (enc->ctx, enc->replacer, holder, 2, args); - JS_FreeValue (enc->ctx, args[0]); - JS_FreeValue (enc->ctx, args[1]); - if (JS_IsException (result)) return JS_DupValue (enc->ctx, val); - return result; -} - -static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValue key); - -static void encode_object_properties (WotaEncodeContext *enc, JSValueConst val, JSValueConst holder) { - JSContext *ctx = enc->ctx; - - /* Root the input value to protect it during property enumeration */ - JSGCRef val_ref, keys_ref; - JS_PushGCRef (ctx, &val_ref); - JS_PushGCRef (ctx, &keys_ref); - val_ref.val = JS_DupValue (ctx, val); - - keys_ref.val = JS_GetOwnPropertyNames (ctx, val_ref.val); - if (JS_IsException (keys_ref.val)) { - wota_write_sym (&enc->wb, WOTA_NULL); - JS_FreeValue (ctx, val_ref.val); - JS_PopGCRef (ctx, &keys_ref); - JS_PopGCRef (ctx, &val_ref); - return; - } - int64_t plen64; - if (JS_GetLength (ctx, keys_ref.val, &plen64) < 0) { - JS_FreeValue (ctx, keys_ref.val); - JS_FreeValue (ctx, val_ref.val); - wota_write_sym (&enc->wb, WOTA_NULL); - JS_PopGCRef (ctx, &keys_ref); - JS_PopGCRef (ctx, &val_ref); - return; - } - uint32_t plen = (uint32_t)plen64; - uint32_t non_function_count = 0; - - /* Allocate GC-rooted arrays for props and keys */ - JSGCRef *prop_refs = sys_malloc (sizeof (JSGCRef) * plen); - JSGCRef *key_refs = sys_malloc (sizeof (JSGCRef) * plen); - for (uint32_t i = 0; i < plen; i++) { - JS_PushGCRef (ctx, &prop_refs[i]); - JS_PushGCRef (ctx, &key_refs[i]); - prop_refs[i].val = JS_NULL; - key_refs[i].val = JS_NULL; - } - - for (uint32_t i = 0; i < plen; i++) { - JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); - JSValue prop_val = JS_GetProperty (ctx, val_ref.val, key); - if (!JS_IsFunction (prop_val)) { - key_refs[non_function_count].val = key; - prop_refs[non_function_count].val = prop_val; - non_function_count++; - } else { - JS_FreeValue (ctx, prop_val); - JS_FreeValue (ctx, key); - } - } - JS_FreeValue (ctx, keys_ref.val); - wota_write_record (&enc->wb, non_function_count); - for (uint32_t i = 0; i < non_function_count; i++) { - size_t klen; - const char *prop_name = JS_ToCStringLen (ctx, &klen, key_refs[i].val); - wota_write_text_len (&enc->wb, prop_name ? prop_name : "", prop_name ? klen : 0); - wota_encode_value (enc, prop_refs[i].val, val_ref.val, key_refs[i].val); - JS_FreeCString (ctx, prop_name); - JS_FreeValue (ctx, prop_refs[i].val); - JS_FreeValue (ctx, key_refs[i].val); - } - /* Pop all GC refs in reverse order */ - for (int i = plen - 1; i >= 0; i--) { - JS_PopGCRef (ctx, &key_refs[i]); - JS_PopGCRef (ctx, &prop_refs[i]); - } - sys_free (prop_refs); - sys_free (key_refs); - JS_FreeValue (ctx, val_ref.val); - JS_PopGCRef (ctx, &keys_ref); - JS_PopGCRef (ctx, &val_ref); -} - -static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValue key) { - JSContext *ctx = enc->ctx; - JSValue replaced; - if (!JS_IsNull (enc->replacer) && !JS_IsNull (key)) - replaced = wota_apply_replacer (enc, holder, key, val); - else - replaced = JS_DupValue (enc->ctx, val); - - int tag = JS_VALUE_GET_TAG (replaced); - switch (tag) { - case JS_TAG_INT: { - int32_t d; - JS_ToInt32 (ctx, &d, replaced); - wota_write_int_word (&enc->wb, d); - break; - } - case JS_TAG_FLOAT64: { - double d; - if (JS_ToFloat64 (ctx, &d, replaced) < 0) { - wota_write_sym (&enc->wb, WOTA_NULL); - break; - } - wota_write_float_word (&enc->wb, d); - break; - } - case JS_TAG_STRING: { - size_t plen; - const char *str = JS_ToCStringLen (ctx, &plen, replaced); - wota_write_text_len (&enc->wb, str ? str : "", str ? plen : 0); - JS_FreeCString (ctx, str); - break; - } - case JS_TAG_BOOL: - wota_write_sym (&enc->wb, JS_VALUE_GET_BOOL (replaced) ? WOTA_TRUE : WOTA_FALSE); - break; - case JS_TAG_NULL: - wota_write_sym (&enc->wb, WOTA_NULL); - break; - case JS_TAG_PTR: { - if (js_is_blob (ctx, replaced)) { - size_t buf_len; - void *buf_data = js_get_blob_data (ctx, &buf_len, replaced); - if (buf_data == (void *)-1) { - JS_FreeValue (ctx, replaced); - return; - } - if (buf_len == 0) { - wota_write_blob (&enc->wb, 0, ""); - } else { - wota_write_blob (&enc->wb, (unsigned long long)buf_len * 8, (const char *)buf_data); - } - break; - } - if (JS_IsArray (replaced)) { - if (wota_stack_has (enc, replaced)) { - enc->cycle = 1; - break; - } - wota_stack_push (enc, replaced); - int64_t arr_len; - JS_GetLength (ctx, replaced, &arr_len); - wota_write_array (&enc->wb, arr_len); - for (int64_t i = 0; i < arr_len; i++) { - JSValue elem_val = JS_GetPropertyUint32 (ctx, replaced, i); - wota_encode_value (enc, elem_val, replaced, JS_NewInt32 (ctx, (int32_t)i)); - JS_FreeValue (ctx, elem_val); - } - wota_stack_pop (enc); - break; - } - JSValue adata = JS_NULL; - if (!JS_IsNull (adata)) { - wota_write_sym (&enc->wb, WOTA_PRIVATE); - wota_encode_value (enc, adata, replaced, JS_NULL); - JS_FreeValue (ctx, adata); - break; - } - JS_FreeValue (ctx, adata); - if (wota_stack_has (enc, replaced)) { - enc->cycle = 1; - break; - } - wota_stack_push (enc, replaced); - JSValue to_json = JS_GetPropertyStr (ctx, replaced, "toJSON"); - if (JS_IsFunction (to_json)) { - JSValue result = JS_Call (ctx, to_json, replaced, 0, NULL); - JS_FreeValue (ctx, to_json); - if (!JS_IsException (result)) { - wota_encode_value (enc, result, holder, key); - JS_FreeValue (ctx, result); - } else - wota_write_sym (&enc->wb, WOTA_NULL); - wota_stack_pop (enc); - break; - } - JS_FreeValue (ctx, to_json); - encode_object_properties (enc, replaced, holder); - wota_stack_pop (enc); - break; - } - default: - wota_write_sym (&enc->wb, WOTA_NULL); - break; - } - JS_FreeValue (ctx, replaced); -} - -static char *decode_wota_value (JSContext *ctx, char *data_ptr, JSValue *out_val, JSValue holder, JSValue key, JSValue reviver) { - uint64_t first_word = *(uint64_t *)data_ptr; - int type = (int)(first_word & 0xffU); - switch (type) { - case WOTA_INT: { - long long val; - data_ptr = wota_read_int (&val, data_ptr); - *out_val = JS_NewInt64 (ctx, val); - break; - } - case WOTA_FLOAT: { - double d; - data_ptr = wota_read_float (&d, data_ptr); - *out_val = JS_NewFloat64 (ctx, d); - break; - } - case WOTA_SYM: { - int scode; - data_ptr = wota_read_sym (&scode, data_ptr); - if (scode == WOTA_PRIVATE) { - JSValue inner = JS_NULL; - data_ptr = decode_wota_value (ctx, data_ptr, &inner, holder, JS_NULL, reviver); - JSValue obj = JS_NewObject (ctx); - *out_val = obj; - } else if (scode == WOTA_NULL) *out_val = JS_NULL; - else if (scode == WOTA_FALSE) *out_val = JS_NewBool (ctx, 0); - else if (scode == WOTA_TRUE) *out_val = JS_NewBool (ctx, 1); - else *out_val = JS_NULL; - break; - } - case WOTA_BLOB: { - long long blen; - char *bdata = NULL; - data_ptr = wota_read_blob (&blen, &bdata, data_ptr); - *out_val = bdata ? js_new_blob_stoned_copy (ctx, (uint8_t *)bdata, (size_t)blen) : js_new_blob_stoned_copy (ctx, NULL, 0); - if (bdata) sys_free (bdata); - break; - } - case WOTA_TEXT: { - char *utf8 = NULL; - data_ptr = wota_read_text (&utf8, data_ptr); - *out_val = JS_NewString (ctx, utf8 ? utf8 : ""); - if (utf8) sys_free (utf8); - break; - } - case WOTA_ARR: { - long long c; - data_ptr = wota_read_array (&c, data_ptr); - JSGCRef arr_ref; - JS_PushGCRef (ctx, &arr_ref); - arr_ref.val = JS_NewArrayLen (ctx, c); - for (long long i = 0; i < c; i++) { - JSGCRef elem_ref; - JS_PushGCRef (ctx, &elem_ref); - elem_ref.val = JS_NULL; - JSValue idx_key = JS_NewInt32 (ctx, (int32_t)i); - data_ptr = decode_wota_value (ctx, data_ptr, &elem_ref.val, arr_ref.val, idx_key, reviver); - JS_SetPropertyUint32 (ctx, arr_ref.val, i, elem_ref.val); - JS_PopGCRef (ctx, &elem_ref); - } - *out_val = arr_ref.val; - JS_PopGCRef (ctx, &arr_ref); - break; - } - case WOTA_REC: { - long long c; - data_ptr = wota_read_record (&c, data_ptr); - JSGCRef obj_ref; - JS_PushGCRef (ctx, &obj_ref); - obj_ref.val = JS_NewObject (ctx); - for (long long i = 0; i < c; i++) { - char *tkey = NULL; - size_t key_len; - data_ptr = wota_read_text_len (&key_len, &tkey, data_ptr); - if (!tkey) continue; - JSGCRef prop_key_ref, sub_val_ref; - JS_PushGCRef (ctx, &prop_key_ref); - JS_PushGCRef (ctx, &sub_val_ref); - prop_key_ref.val = JS_NewStringLen (ctx, tkey, key_len); - sub_val_ref.val = JS_NULL; - data_ptr = decode_wota_value (ctx, data_ptr, &sub_val_ref.val, obj_ref.val, prop_key_ref.val, reviver); - JS_SetProperty (ctx, obj_ref.val, prop_key_ref.val, sub_val_ref.val); - JS_FreeValue (ctx, prop_key_ref.val); - JS_PopGCRef (ctx, &sub_val_ref); - JS_PopGCRef (ctx, &prop_key_ref); - sys_free (tkey); - } - *out_val = obj_ref.val; - JS_PopGCRef (ctx, &obj_ref); - break; - } - default: - data_ptr += 8; - *out_val = JS_NULL; - break; - } - if (!JS_IsNull (reviver)) { - JSValue key_val = JS_IsNull (key) ? JS_NULL : JS_DupValue (ctx, key); - JSValue args[2] = { key_val, JS_DupValue (ctx, *out_val) }; - JSValue revived = JS_Call (ctx, reviver, holder, 2, args); - JS_FreeValue (ctx, args[0]); - JS_FreeValue (ctx, args[1]); - if (!JS_IsException (revived)) { - JS_FreeValue (ctx, *out_val); - *out_val = revived; - } else - JS_FreeValue (ctx, revived); - } - return data_ptr; -} - -void *value2wota (JSContext *ctx, JSValue v, JSValue replacer, size_t *bytes) { - JSGCRef val_ref, rep_ref; - JS_PushGCRef (ctx, &val_ref); - JS_PushGCRef (ctx, &rep_ref); - val_ref.val = v; - rep_ref.val = replacer; - - WotaEncodeContext enc_s, *enc = &enc_s; - - enc->ctx = ctx; - enc->visited_stack = NULL; - enc->cycle = 0; - enc->replacer = rep_ref.val; - wota_buffer_init (&enc->wb, 16); - wota_encode_value (enc, val_ref.val, JS_NULL, JS_NULL); - if (enc->cycle) { - wota_stack_free (enc); - wota_buffer_free (&enc->wb); - JS_PopGCRef (ctx, &rep_ref); - JS_PopGCRef (ctx, &val_ref); - return NULL; - } - wota_stack_free (enc); - size_t total_bytes = enc->wb.size * sizeof (uint64_t); - void *wota = sys_realloc (enc->wb.data, total_bytes); - if (bytes) *bytes = total_bytes; - JS_PopGCRef (ctx, &rep_ref); - JS_PopGCRef (ctx, &val_ref); - return wota; -} - -JSValue wota2value (JSContext *ctx, void *wota) { - JSGCRef holder_ref, result_ref; - JS_PushGCRef (ctx, &holder_ref); - JS_PushGCRef (ctx, &result_ref); - result_ref.val = JS_NULL; - holder_ref.val = JS_NewObject (ctx); - decode_wota_value (ctx, wota, &result_ref.val, holder_ref.val, JS_NULL, JS_NULL); - JSValue result = result_ref.val; - JS_PopGCRef (ctx, &result_ref); - JS_PopGCRef (ctx, &holder_ref); - return result; -} - -static JSValue js_wota_encode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - if (argc < 1) return JS_ThrowTypeError (ctx, "wota.encode requires at least 1 argument"); - size_t total_bytes; - void *wota = value2wota (ctx, argv[0], JS_IsFunction (argv[1]) ? argv[1] : JS_NULL, &total_bytes); - JSValue ret = js_new_blob_stoned_copy (ctx, wota, total_bytes); - sys_free (wota); - return ret; -} - -static JSValue js_wota_decode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - if (argc < 1) return JS_NULL; - size_t len; - uint8_t *buf = js_get_blob_data (ctx, &len, argv[0]); - if (buf == (uint8_t *)-1) return JS_EXCEPTION; - if (!buf || len == 0) return JS_ThrowTypeError (ctx, "No blob data present"); - JSValue reviver = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL; - char *data_ptr = (char *)buf; - JSValue result = JS_NULL; - JSValue holder = JS_NewObject (ctx); - JSValue empty_key = JS_NewString (ctx, ""); - decode_wota_value (ctx, data_ptr, &result, holder, empty_key, reviver); - JS_FreeValue (ctx, empty_key); - JS_FreeValue (ctx, holder); - return result; -} - -static const JSCFunctionListEntry js_wota_funcs[] = { - JS_CFUNC_DEF ("encode", 2, js_wota_encode), - JS_CFUNC_DEF ("decode", 2, js_wota_decode), -}; - -JSValue js_wota_use (JSContext *ctx) { - JSGCRef exports_ref; - JS_PushGCRef (ctx, &exports_ref); - exports_ref.val = JS_NewObject (ctx); - JS_SetPropertyFunctionList (ctx, exports_ref.val, js_wota_funcs, sizeof (js_wota_funcs) / sizeof (js_wota_funcs[0])); - JSValue result = exports_ref.val; - JS_PopGCRef (ctx, &exports_ref); - return result; -} - -static JSValue js_math_e (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double power = 1.0; - if (argc > 0 && !JS_IsNull (argv[0])) { - if (JS_ToFloat64 (ctx, &power, argv[0]) < 0) return JS_EXCEPTION; - } - return JS_NewFloat64 (ctx, exp (power)); -} - -/* ============================================================================ - * Cell Script Module: math/radians - * Provides trigonometric and math functions using radians - * ============================================================================ - */ - -static JSValue js_math_rad_arc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, acos (x)); -} - -static JSValue js_math_rad_arc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, asin (x)); -} - -static JSValue js_math_rad_arc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, atan (x)); -} - -static JSValue js_math_rad_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, cos (x)); -} - -static JSValue js_math_rad_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, sin (x)); -} - -static JSValue js_math_rad_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, tan (x)); -} - -static JSValue js_math_ln (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, log (x)); -} - -static JSValue js_math_log10 (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, log10 (x)); -} - -static JSValue js_math_log2 (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, log2 (x)); -} - -static JSValue js_math_power (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x, y; - if (argc < 2) return JS_NULL; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - if (JS_ToFloat64 (ctx, &y, argv[1]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, pow (x, y)); -} - -static JSValue js_math_root (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x, n; - if (argc < 2) return JS_NULL; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - if (JS_ToFloat64 (ctx, &n, argv[1]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, pow (x, 1.0 / n)); -} - -static JSValue js_math_sqrt (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, sqrt (x)); -} - -static const JSCFunctionListEntry js_math_radians_funcs[] - = { JS_CFUNC_DEF ("arc_cosine", 1, js_math_rad_arc_cosine), - JS_CFUNC_DEF ("arc_sine", 1, js_math_rad_arc_sine), - JS_CFUNC_DEF ("arc_tangent", 1, js_math_rad_arc_tangent), - JS_CFUNC_DEF ("cosine", 1, js_math_rad_cosine), - JS_CFUNC_DEF ("sine", 1, js_math_rad_sine), - JS_CFUNC_DEF ("tangent", 1, js_math_rad_tangent), - JS_CFUNC_DEF ("ln", 1, js_math_ln), - JS_CFUNC_DEF ("log", 1, js_math_log10), - JS_CFUNC_DEF ("log2", 1, js_math_log2), - JS_CFUNC_DEF ("power", 2, js_math_power), - JS_CFUNC_DEF ("root", 2, js_math_root), - JS_CFUNC_DEF ("sqrt", 1, js_math_sqrt), - JS_CFUNC_DEF ("e", 1, js_math_e) }; - -JSValue js_math_radians_use (JSContext *ctx) { - JSValue obj = JS_NewObject (ctx); - JS_SetPropertyFunctionList (ctx, obj, js_math_radians_funcs, countof (js_math_radians_funcs)); - return obj; -} - -/* ============================================================================ - * Cell Script Module: math/degrees - * Provides trigonometric and math functions using degrees - * ============================================================================ - */ - -#define DEG2RAD (3.14159265358979323846264338327950288419716939937510 / 180.0) -#define RAD2DEG (180.0 / 3.14159265358979323846264338327950288419716939937510) - -static JSValue js_math_deg_arc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, acos (x) * RAD2DEG); -} - -static JSValue js_math_deg_arc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, asin (x) * RAD2DEG); -} - -static JSValue js_math_deg_arc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, atan (x) * RAD2DEG); -} - -static JSValue js_math_deg_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, cos (x * DEG2RAD)); -} - -static JSValue js_math_deg_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, sin (x * DEG2RAD)); -} - -static JSValue js_math_deg_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, tan (x * DEG2RAD)); -} - -static const JSCFunctionListEntry js_math_degrees_funcs[] - = { JS_CFUNC_DEF ("arc_cosine", 1, js_math_deg_arc_cosine), - JS_CFUNC_DEF ("arc_sine", 1, js_math_deg_arc_sine), - JS_CFUNC_DEF ("arc_tangent", 1, js_math_deg_arc_tangent), - JS_CFUNC_DEF ("cosine", 1, js_math_deg_cosine), - JS_CFUNC_DEF ("sine", 1, js_math_deg_sine), - JS_CFUNC_DEF ("tangent", 1, js_math_deg_tangent), - JS_CFUNC_DEF ("ln", 1, js_math_ln), - JS_CFUNC_DEF ("log", 1, js_math_log10), - JS_CFUNC_DEF ("log2", 1, js_math_log2), - JS_CFUNC_DEF ("power", 2, js_math_power), - JS_CFUNC_DEF ("root", 2, js_math_root), - JS_CFUNC_DEF ("sqrt", 1, js_math_sqrt), - JS_CFUNC_DEF ("e", 1, js_math_e) }; - -JSValue js_math_degrees_use (JSContext *ctx) { - JSValue obj = JS_NewObject (ctx); - JS_SetPropertyFunctionList (ctx, obj, js_math_degrees_funcs, countof (js_math_degrees_funcs)); - return obj; -} - -/* ============================================================================ - * Cell Script Module: math/cycles - * Provides trigonometric and math functions using cycles (0-1 = full rotation) - * ============================================================================ - */ - -#define TWOPI (2.0 * 3.14159265358979323846264338327950288419716939937510) - -static JSValue js_math_cyc_arc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, acos (x) / TWOPI); -} - -static JSValue js_math_cyc_arc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, asin (x) / TWOPI); -} - -static JSValue js_math_cyc_arc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, atan (x) / TWOPI); -} - -static JSValue js_math_cyc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, cos (x * TWOPI)); -} - -static JSValue js_math_cyc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, sin (x * TWOPI)); -} - -static JSValue js_math_cyc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { - double x; - if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; - return JS_NewFloat64 (ctx, tan (x * TWOPI)); -} - -static const JSCFunctionListEntry js_math_cycles_funcs[] - = { JS_CFUNC_DEF ("arc_cosine", 1, js_math_cyc_arc_cosine), - JS_CFUNC_DEF ("arc_sine", 1, js_math_cyc_arc_sine), - JS_CFUNC_DEF ("arc_tangent", 1, js_math_cyc_arc_tangent), - JS_CFUNC_DEF ("cosine", 1, js_math_cyc_cosine), - JS_CFUNC_DEF ("sine", 1, js_math_cyc_sine), - JS_CFUNC_DEF ("tangent", 1, js_math_cyc_tangent), - JS_CFUNC_DEF ("ln", 1, js_math_ln), - JS_CFUNC_DEF ("log", 1, js_math_log10), - JS_CFUNC_DEF ("log2", 1, js_math_log2), - JS_CFUNC_DEF ("power", 2, js_math_power), - JS_CFUNC_DEF ("root", 2, js_math_root), - JS_CFUNC_DEF ("sqrt", 1, js_math_sqrt), - JS_CFUNC_DEF ("e", 1, js_math_e) }; - -JSValue js_math_cycles_use (JSContext *ctx) { - JSValue obj = JS_NewObject (ctx); - JS_SetPropertyFunctionList (ctx, obj, js_math_cycles_funcs, countof (js_math_cycles_funcs)); - return obj; -} - -/* ============================================================ - AST JSON Output Implementation - ============================================================ */ - -typedef struct ASTParseState { - const char *filename; - const uint8_t *buf_start; - const uint8_t *buf_ptr; - const uint8_t *buf_end; - const uint8_t *token_ptr; - int token_val; - BOOL got_lf; - int function_nr; - cJSON *errors; /* array of error objects */ - int has_error; - int error_count; - int in_disruption; - char *decoded_str; /* allocated buffer for decoded string escapes */ - GetLineColCache lc_cache; - union { - struct { - const char *str; - size_t len; - } str; - struct { - double val; - } num; - struct { - const char *str; - size_t len; - BOOL has_escape; - BOOL is_reserved; - } ident; - struct { - const char *body; - size_t body_len; - const char *flags; - size_t flags_len; - } regexp; - } token_u; -} ASTParseState; - -/* Add a length-delimited string to a cJSON object (source pointers aren't null-terminated) */ -static void cjson_add_strn (cJSON *obj, const char *key, const char *str, size_t len) { - char buf[256]; - char *tmp = (len < sizeof (buf)) ? buf : sys_malloc (len + 1); - memcpy (tmp, str, len); - tmp[len] = '\0'; - cJSON_AddStringToObject (obj, key, tmp); - if (tmp != buf) sys_free (tmp); -} - -/* Compare a length-delimited token string against a null-terminated literal */ -static inline BOOL tok_eq (const char *str, size_t len, const char *lit) { - size_t ll = strlen (lit); - return len == ll && memcmp (str, lit, ll) == 0; -} - -static cJSON *ast_parse_expr (ASTParseState *s); -static cJSON *ast_parse_assign_expr (ASTParseState *s); -static cJSON *ast_parse_statement (ASTParseState *s); -static void ast_sync_to_statement (ASTParseState *s); -static cJSON *ast_parse_block_statements (ASTParseState *s); -static cJSON *ast_parse_function_inner (ASTParseState *s, BOOL is_expr); -static cJSON *ast_parse_arrow_function (ASTParseState *s); - -/* Check if we're looking at an arrow function starting with '(' */ -static BOOL ast_is_arrow_function (ASTParseState *s) { - if (s->token_val != '(') return FALSE; - const uint8_t *p = s->buf_ptr; - int depth = 1; - while (p < s->buf_end && depth > 0) { - uint8_t c = *p++; - if (c == '(') depth++; - else if (c == ')') depth--; - else if (c == '"' || c == '\'' || c == '`') { - /* Skip string */ - uint8_t quote = c; - while (p < s->buf_end && *p != quote) { - if (*p == '\\' && p + 1 < s->buf_end) p++; - p++; - } - if (p < s->buf_end) p++; - } - } - /* Skip whitespace */ - while (p < s->buf_end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++; - /* Check for => */ - return (p + 1 < s->buf_end && p[0] == '=' && p[1] == '>'); -} - -static void ast_free_token (ASTParseState *s) { - if (s->decoded_str) { - sys_free (s->decoded_str); - s->decoded_str = NULL; - } -} - -static void ast_get_line_col (ASTParseState *s, const uint8_t *ptr, int *line, int *col) { - *line = get_line_col_cached (&s->lc_cache, col, ptr); -} - -static cJSON *ast_node (ASTParseState *s, const char *kind, const uint8_t *start_ptr) { - cJSON *node = cJSON_CreateObject (); - cJSON_AddStringToObject (node, "kind", kind); - int at = (int)(start_ptr - s->buf_start); - int from_row, from_col; - ast_get_line_col (s, start_ptr, &from_row, &from_col); - cJSON_AddNumberToObject (node, "at", at); - cJSON_AddNumberToObject (node, "from_row", from_row); - cJSON_AddNumberToObject (node, "from_column", from_col); - return node; -} - -static void ast_node_end (ASTParseState *s, cJSON *node, const uint8_t *end_ptr) { - int to_row, to_col; - ast_get_line_col (s, end_ptr, &to_row, &to_col); - cJSON_AddNumberToObject (node, "to_row", to_row); - cJSON_AddNumberToObject (node, "to_column", to_col); -} - -static void ast_error (ASTParseState *s, const uint8_t *ptr, const char *fmt, ...) { - if (s->error_count >= 5) return; - s->error_count++; - - va_list ap; - char buf[256]; - int line, col; - - va_start (ap, fmt); - vsnprintf (buf, sizeof(buf), fmt, ap); - va_end (ap); - - ast_get_line_col (s, ptr, &line, &col); - - cJSON *err = cJSON_CreateObject (); - cJSON_AddStringToObject (err, "message", buf); - cJSON_AddNumberToObject (err, "line", line + 1); /* 1-based for user display */ - cJSON_AddNumberToObject (err, "column", col + 1); - cJSON_AddNumberToObject (err, "offset", (int)(ptr - s->buf_start)); - - if (!s->errors) { - s->errors = cJSON_CreateArray (); - } - cJSON_AddItemToArray (s->errors, err); - s->has_error = 1; -} - -/* Decode escape sequences in a string literal into dst. Returns decoded length. */ -static int ast_decode_string (const uint8_t *src, int len, char *dst) { - const uint8_t *end = src + len; - char *out = dst; - while (src < end) { - if (*src == '\\' && src + 1 < end) { - src++; - switch (*src) { - case 'n': *out++ = '\n'; src++; break; - case 't': *out++ = '\t'; src++; break; - case 'r': *out++ = '\r'; src++; break; - case '\\': *out++ = '\\'; src++; break; - case '\'': *out++ = '\''; src++; break; - case '\"': *out++ = '\"'; src++; break; - case '0': *out++ = '\0'; src++; break; - case 'b': *out++ = '\b'; src++; break; - case 'f': *out++ = '\f'; src++; break; - case 'v': *out++ = '\v'; src++; break; - case 'u': { - src++; - unsigned int cp = 0; - for (int i = 0; i < 4 && src < end; i++, src++) { - cp <<= 4; - if (*src >= '0' && *src <= '9') cp |= *src - '0'; - else if (*src >= 'a' && *src <= 'f') cp |= *src - 'a' + 10; - else if (*src >= 'A' && *src <= 'F') cp |= *src - 'A' + 10; - else break; - } - out += unicode_to_utf8 ((uint8_t *)out, cp); - } break; - default: *out++ = *src++; break; - } - } else { - *out++ = *src++; - } - } - return out - dst; -} - -static int ast_next_token (ASTParseState *s) { - const uint8_t *p; - int c; - BOOL ident_has_escape; - - ast_free_token (s); - p = s->buf_ptr; - s->got_lf = FALSE; - -redo: - s->token_ptr = p; - c = *p; - switch (c) { - case 0: - if (p >= s->buf_end) { - s->token_val = TOK_EOF; - } else { - goto def_token; - } - break; - case '`': { - const uint8_t *start = p; - p++; - while (p < s->buf_end && *p != '`') { - if (*p == '\\' && p + 1 < s->buf_end) { p += 2; continue; } - if (*p == '$' && p + 1 < s->buf_end && p[1] == '{') { - p += 2; - int depth = 1; - while (p < s->buf_end && depth > 0) { - if (*p == '{') { depth++; p++; } - else if (*p == '}') { depth--; p++; } - else if (*p == '\'' || *p == '"' || *p == '`') { - int q = *p; p++; - while (p < s->buf_end && *p != q) { - if (*p == '\\' && p + 1 < s->buf_end) p++; - p++; - } - if (p < s->buf_end) p++; - } else { p++; } - } - continue; - } - p++; - } - if (p >= s->buf_end) { - ast_error (s, start, "unterminated template literal"); - s->buf_ptr = p; - goto redo; - } - p++; - s->token_val = TOK_TEMPLATE; - { - const uint8_t *raw = start + 1; - int raw_len = p - start - 2; - BOOL has_escape = FALSE; - for (int i = 0; i < raw_len; i++) { - if (raw[i] == '\\') { has_escape = TRUE; break; } - } - if (has_escape) { - char *buf = sys_malloc (raw_len * 4 + 1); - int decoded_len = ast_decode_string (raw, raw_len, buf); - s->decoded_str = buf; - s->token_u.str.str = buf; - s->token_u.str.len = decoded_len; - } else { - s->token_u.str.str = (const char *)raw; - s->token_u.str.len = raw_len; - } - } - } break; - case '\'': - case '\"': { - const uint8_t *start = p; - int quote = c; - p++; - while (p < s->buf_end && *p != quote) { - if (*p == '\\' && p + 1 < s->buf_end) p++; - p++; - } - if (p >= s->buf_end) { - ast_error (s, start, "unterminated string literal"); - s->buf_ptr = p; - goto redo; - } - p++; - /* Store the string content without quotes, decoding escape sequences */ - s->token_val = TOK_STRING; - { - const uint8_t *raw = start + 1; - int raw_len = p - start - 2; - /* Check if any escape sequences need decoding */ - BOOL has_escape = FALSE; - for (int i = 0; i < raw_len; i++) { - if (raw[i] == '\\') { has_escape = TRUE; break; } - } - if (has_escape) { - char *buf = sys_malloc (raw_len * 4 + 1); - int decoded_len = ast_decode_string (raw, raw_len, buf); - s->decoded_str = buf; - s->token_u.str.str = buf; - s->token_u.str.len = decoded_len; - } else { - s->token_u.str.str = (const char *)raw; - s->token_u.str.len = raw_len; - } - } - } break; - case '\r': - if (p[1] == '\n') p++; - /* fall through */ - case '\n': - p++; - s->got_lf = TRUE; - goto redo; - case '\f': - case '\v': - case ' ': - case '\t': - p++; - goto redo; - case '/': - if (p[1] == '*') { - const uint8_t *comment_start = p; - p += 2; - BOOL found_end = FALSE; - while (p < s->buf_end) { - if (p[0] == '*' && p + 1 < s->buf_end && p[1] == '/') { - p += 2; - found_end = TRUE; - break; - } - if (*p == '\n' || *p == '\r') s->got_lf = TRUE; - p++; - } - if (!found_end) - ast_error (s, comment_start, "unterminated block comment"); - goto redo; - } else if (p[1] == '/') { - p += 2; - while (p < s->buf_end && *p != '\n' && *p != '\r') p++; - goto redo; - } else if (p[1] == '=') { - p += 2; - s->token_val = TOK_DIV_ASSIGN; - } else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } else { - p++; - s->token_val = c; - } - break; - case '\\': - goto def_token; - case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': - case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': - case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': - case 'v': case 'w': case 'x': case 'y': case 'z': - case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': - case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': - case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': - case 'V': case 'W': case 'X': case 'Y': case 'Z': - case '_': case '$': { - const uint8_t *start = p; - ident_has_escape = FALSE; - p++; - while (p < s->buf_end) { - c = *p; - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9') || c == '_' || c == '$' || - c == '?' || c == '!') { - p++; - } else if (c >= 0x80) { - /* unicode identifier */ - p++; - while (p < s->buf_end && (*p & 0xc0) == 0x80) p++; - } else { - break; - } - } - size_t len = p - start; - s->token_u.ident.str = (const char *)start; - s->token_u.ident.len = len; - s->token_u.ident.has_escape = ident_has_escape; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - - /* Check for keywords */ - if (len == 2 && !memcmp (start, "if", 2)) s->token_val = TOK_IF; - else if (len == 2 && !memcmp (start, "in", 2)) s->token_val = TOK_IN; - else if (len == 2 && !memcmp (start, "do", 2)) s->token_val = TOK_DO; - else if (len == 2 && !memcmp (start, "go", 2)) s->token_val = TOK_GO; - else if (len == 3 && !memcmp (start, "var", 3)) s->token_val = TOK_VAR; - else if (len == 3 && !memcmp (start, "def", 3)) s->token_val = TOK_DEF; - else if (len == 3 && !memcmp (start, "for", 3)) s->token_val = TOK_FOR; - else if (len == 4 && !memcmp (start, "else", 4)) s->token_val = TOK_ELSE; - else if (len == 4 && !memcmp (start, "this", 4)) s->token_val = TOK_THIS; - else if (len == 4 && !memcmp (start, "null", 4)) s->token_val = TOK_NULL; - else if (len == 4 && !memcmp (start, "true", 4)) s->token_val = TOK_TRUE; - else if (len == 5 && !memcmp (start, "false", 5)) s->token_val = TOK_FALSE; - else if (len == 5 && !memcmp (start, "while", 5)) s->token_val = TOK_WHILE; - else if (len == 5 && !memcmp (start, "break", 5)) s->token_val = TOK_BREAK; - else if (len == 6 && !memcmp (start, "return", 6)) s->token_val = TOK_RETURN; - else if (len == 6 && !memcmp (start, "delete", 6)) s->token_val = TOK_DELETE; - else if (len == 7 && !memcmp (start, "disrupt", 7)) s->token_val = TOK_DISRUPT; - else if (len == 8 && !memcmp (start, "function", 8)) s->token_val = TOK_FUNCTION; - else if (len == 8 && !memcmp (start, "continue", 8)) s->token_val = TOK_CONTINUE; - else if (len == 10 && !memcmp (start, "disruption", 10)) s->token_val = TOK_DISRUPTION; - } break; - case '.': - if (p[1] >= '0' && p[1] <= '9') { - goto parse_number; - } else { - goto def_token; - } - break; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - parse_number: { - const uint8_t *start = p; - BOOL is_float = FALSE; - /* hex */ - if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { - p += 2; - const uint8_t *digits_start = p; - while (p < s->buf_end && ((c = *p, (c >= '0' && c <= '9') || - (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || c == '_'))) p++; - if (p == digits_start) - ast_error (s, start, "malformed hex number: no digits after '0x'"); - } else if (p[0] == '0' && (p[1] == 'b' || p[1] == 'B')) { - p += 2; - const uint8_t *digits_start = p; - while (p < s->buf_end && (*p == '0' || *p == '1' || *p == '_')) p++; - if (p == digits_start) - ast_error (s, start, "malformed binary number: no digits after '0b'"); - } else if (p[0] == '0' && (p[1] == 'o' || p[1] == 'O')) { - p += 2; - const uint8_t *digits_start = p; - while (p < s->buf_end && (*p >= '0' && *p <= '7')) p++; - if (p == digits_start) - ast_error (s, start, "malformed octal number: no digits after '0o'"); - } else { - while (p < s->buf_end && ((*p >= '0' && *p <= '9') || *p == '_')) p++; - if (p < s->buf_end && *p == '.') { - is_float = TRUE; - p++; - while (p < s->buf_end && ((*p >= '0' && *p <= '9') || *p == '_')) p++; - } - if (p < s->buf_end && (*p == 'e' || *p == 'E')) { - is_float = TRUE; - p++; - if (p < s->buf_end && (*p == '+' || *p == '-')) p++; - const uint8_t *exp_start = p; - while (p < s->buf_end && (*p >= '0' && *p <= '9')) p++; - if (p == exp_start) - ast_error (s, start, "malformed number: no digits after exponent"); - } - } - s->token_val = TOK_NUMBER; - /* Parse the number value */ - char *numstr = sys_malloc (p - start + 1); - memcpy (numstr, start, p - start); - numstr[p - start] = '\0'; - double val = strtod (numstr, NULL); - sys_free (numstr); - s->token_u.num.val = val; - } break; - case '*': - if (p[1] == '=') { p += 2; s->token_val = TOK_MUL_ASSIGN; } - else if (p[1] == '*') { - if (p[2] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 3; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 3; - } - else if (p[2] == '=') { p += 3; s->token_val = TOK_POW_ASSIGN; } - else { p += 2; s->token_val = TOK_POW; } - } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '%': - if (p[1] == '=') { p += 2; s->token_val = TOK_MOD_ASSIGN; } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '+': - if (p[1] == '=') { p += 2; s->token_val = TOK_PLUS_ASSIGN; } - else if (p[1] == '+') { p += 2; s->token_val = TOK_INC; } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '-': - if (p[1] == '=') { p += 2; s->token_val = TOK_MINUS_ASSIGN; } - else if (p[1] == '-') { p += 2; s->token_val = TOK_DEC; } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '<': - if (p[1] == '=' && p[2] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 3; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 3; - } - else if (p[1] == '=') { p += 2; s->token_val = TOK_LTE; } - else if (p[1] == '<') { - if (p[2] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 3; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 3; - } - else if (p[2] == '=') { p += 3; s->token_val = TOK_SHL_ASSIGN; } - else { p += 2; s->token_val = TOK_SHL; } - } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '>': - if (p[1] == '=' && p[2] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 3; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 3; - } - else if (p[1] == '=') { p += 2; s->token_val = TOK_GTE; } - else if (p[1] == '>') { - if (p[2] == '>') { - if (p[3] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 4; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 4; - } - else if (p[3] == '=') { p += 4; s->token_val = TOK_SHR_ASSIGN; } - else { p += 3; s->token_val = TOK_SHR; } - } - else if (p[2] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 3; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 3; - } - else if (p[2] == '=') { p += 3; s->token_val = TOK_SAR_ASSIGN; } - else { p += 2; s->token_val = TOK_SAR; } - } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '=': - if (p[1] == '=') { - if (p[2] == '=') { p += 3; s->token_val = TOK_STRICT_EQ; } - else { p += 2; s->token_val = TOK_EQ; } - } else if (p[1] == '>') { p += 2; s->token_val = TOK_ARROW; } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '!': - if (p[1] == '=') { - if (p[2] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 3; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 3; - } - else if (p[2] == '=') { p += 3; s->token_val = TOK_STRICT_NEQ; } - else { p += 2; s->token_val = TOK_NEQ; } - } else { goto def_token; } - break; - case '&': - if (p[1] == '&') { - if (p[2] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 3; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 3; - } - else if (p[2] == '=') { p += 3; s->token_val = TOK_LAND_ASSIGN; } - else { p += 2; s->token_val = TOK_LAND; } - } - else if (p[1] == '=') { p += 2; s->token_val = TOK_AND_ASSIGN; } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '|': - if (p[1] == '|') { - if (p[2] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 3; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 3; - } - else if (p[2] == '=') { p += 3; s->token_val = TOK_LOR_ASSIGN; } - else { p += 2; s->token_val = TOK_LOR; } - } - else if (p[1] == '=') { p += 2; s->token_val = TOK_OR_ASSIGN; } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '^': - if (p[1] == '=') { p += 2; s->token_val = TOK_XOR_ASSIGN; } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '[': - if (p[1] == ']' && p[2] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 3; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 3; - } - else { goto def_token; } - break; - case '~': - if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '?': - goto def_token; - default: - def_token: - p++; - s->token_val = c; - break; - } - s->buf_ptr = p; - return 0; -} - -/* Tokenizer function that does NOT skip whitespace/comments - emits them as tokens */ -static int tokenize_next (ASTParseState *s) { - const uint8_t *p; - int c; - BOOL ident_has_escape; - - ast_free_token (s); - p = s->buf_ptr; - s->got_lf = FALSE; - - s->token_ptr = p; - c = *p; - switch (c) { - case 0: - if (p >= s->buf_end) { - s->token_val = TOK_EOF; - } else { - goto def_token; - } - break; - case '`': { - const uint8_t *start = p; - p++; - while (p < s->buf_end && *p != '`') { - if (*p == '\\' && p + 1 < s->buf_end) { p += 2; continue; } - if (*p == '$' && p + 1 < s->buf_end && p[1] == '{') { - p += 2; - int depth = 1; - while (p < s->buf_end && depth > 0) { - if (*p == '{') { depth++; p++; } - else if (*p == '}') { depth--; p++; } - else if (*p == '\'' || *p == '"' || *p == '`') { - int q = *p; p++; - while (p < s->buf_end && *p != q) { - if (*p == '\\' && p + 1 < s->buf_end) p++; - p++; - } - if (p < s->buf_end) p++; - } else { p++; } - } - continue; - } - p++; - } - if (p >= s->buf_end) { - ast_error (s, start, "unterminated template literal"); - s->token_val = TOK_ERROR; - s->token_u.str.str = (const char *)(start + 1); - s->token_u.str.len = p - start - 1; - } else { - p++; - s->token_val = TOK_TEMPLATE; - s->token_u.str.str = (const char *)(start + 1); - s->token_u.str.len = p - start - 2; - } - } break; - case '\'': - case '\"': { - const uint8_t *start = p; - int quote = c; - p++; - while (p < s->buf_end && *p != quote) { - if (*p == '\\' && p + 1 < s->buf_end) p++; - p++; - } - if (p >= s->buf_end) { - ast_error (s, start, "unterminated string literal"); - s->token_val = TOK_ERROR; - s->token_u.str.str = (const char *)(start + 1); - s->token_u.str.len = p - start - 1; - } else { - p++; - s->token_val = TOK_STRING; - s->token_u.str.str = (const char *)(start + 1); - s->token_u.str.len = p - start - 2; - } - } break; - case '\r': - if (p[1] == '\n') p++; - /* fall through */ - case '\n': - p++; - s->got_lf = TRUE; - s->token_val = TOK_NEWLINE; - break; - case '\f': - case '\v': - case ' ': - case '\t': { - /* Collect consecutive whitespace (excluding newlines) */ - while (p < s->buf_end && (*p == ' ' || *p == '\t' || *p == '\f' || *p == '\v')) p++; - s->token_val = TOK_SPACE; - } break; - case '/': - if (p[1] == '*') { - /* Multi-line comment */ - const uint8_t *comment_start = p; - p += 2; - BOOL found_end = FALSE; - while (p < s->buf_end) { - if (p[0] == '*' && p + 1 < s->buf_end && p[1] == '/') { - p += 2; - found_end = TRUE; - break; - } - if (*p == '\n' || *p == '\r') s->got_lf = TRUE; - p++; - } - if (!found_end) { - ast_error (s, comment_start, "unterminated block comment"); - s->token_val = TOK_ERROR; - } else { - s->token_val = TOK_COMMENT; - } - } else if (p[1] == '/') { - /* Single-line comment */ - p += 2; - while (p < s->buf_end && *p != '\n' && *p != '\r') p++; - s->token_val = TOK_COMMENT; - } else if (p[1] == '=') { - p += 2; - s->token_val = TOK_DIV_ASSIGN; - } else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } else { - p++; - s->token_val = c; - } - break; - case '\\': - goto def_token; - case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': - case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': - case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': - case 'v': case 'w': case 'x': case 'y': case 'z': - case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': - case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': - case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': - case 'V': case 'W': case 'X': case 'Y': case 'Z': - case '_': case '$': { - const uint8_t *start = p; - ident_has_escape = FALSE; - p++; - while (p < s->buf_end) { - c = *p; - if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9') || c == '_' || c == '$' || - c == '?' || c == '!') { - p++; - } else if (c >= 0x80) { - p++; - while (p < s->buf_end && (*p & 0xc0) == 0x80) p++; - } else { - break; - } - } - size_t len = p - start; - s->token_u.ident.str = (const char *)start; - s->token_u.ident.len = len; - s->token_u.ident.has_escape = ident_has_escape; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - - /* Check for keywords */ - if (len == 2 && !memcmp (start, "if", 2)) s->token_val = TOK_IF; - else if (len == 2 && !memcmp (start, "in", 2)) s->token_val = TOK_IN; - else if (len == 2 && !memcmp (start, "do", 2)) s->token_val = TOK_DO; - else if (len == 2 && !memcmp (start, "go", 2)) s->token_val = TOK_GO; - else if (len == 3 && !memcmp (start, "var", 3)) s->token_val = TOK_VAR; - else if (len == 3 && !memcmp (start, "def", 3)) s->token_val = TOK_DEF; - else if (len == 3 && !memcmp (start, "for", 3)) s->token_val = TOK_FOR; - else if (len == 4 && !memcmp (start, "else", 4)) s->token_val = TOK_ELSE; - else if (len == 4 && !memcmp (start, "this", 4)) s->token_val = TOK_THIS; - else if (len == 4 && !memcmp (start, "null", 4)) s->token_val = TOK_NULL; - else if (len == 4 && !memcmp (start, "true", 4)) s->token_val = TOK_TRUE; - else if (len == 5 && !memcmp (start, "false", 5)) s->token_val = TOK_FALSE; - else if (len == 5 && !memcmp (start, "while", 5)) s->token_val = TOK_WHILE; - else if (len == 5 && !memcmp (start, "break", 5)) s->token_val = TOK_BREAK; - else if (len == 6 && !memcmp (start, "return", 6)) s->token_val = TOK_RETURN; - else if (len == 6 && !memcmp (start, "delete", 6)) s->token_val = TOK_DELETE; - else if (len == 7 && !memcmp (start, "disrupt", 7)) s->token_val = TOK_DISRUPT; - else if (len == 8 && !memcmp (start, "function", 8)) s->token_val = TOK_FUNCTION; - else if (len == 8 && !memcmp (start, "continue", 8)) s->token_val = TOK_CONTINUE; - else if (len == 10 && !memcmp (start, "disruption", 10)) s->token_val = TOK_DISRUPTION; - } break; - case '.': - if (p[1] >= '0' && p[1] <= '9') { - goto tokenize_number; - } else { - goto def_token; - } - break; - case '0': case '1': case '2': case '3': case '4': - case '5': case '6': case '7': case '8': case '9': - tokenize_number: { - const uint8_t *start = p; - BOOL is_float = FALSE; - if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { - p += 2; - const uint8_t *digits_start = p; - while (p < s->buf_end && ((c = *p, (c >= '0' && c <= '9') || - (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || c == '_'))) p++; - if (p == digits_start) - ast_error (s, start, "malformed hex number: no digits after '0x'"); - } else if (p[0] == '0' && (p[1] == 'b' || p[1] == 'B')) { - p += 2; - const uint8_t *digits_start = p; - while (p < s->buf_end && (*p == '0' || *p == '1' || *p == '_')) p++; - if (p == digits_start) - ast_error (s, start, "malformed binary number: no digits after '0b'"); - } else if (p[0] == '0' && (p[1] == 'o' || p[1] == 'O')) { - p += 2; - const uint8_t *digits_start = p; - while (p < s->buf_end && (*p >= '0' && *p <= '7')) p++; - if (p == digits_start) - ast_error (s, start, "malformed octal number: no digits after '0o'"); - } else { - while (p < s->buf_end && ((*p >= '0' && *p <= '9') || *p == '_')) p++; - if (p < s->buf_end && *p == '.') { - is_float = TRUE; - p++; - while (p < s->buf_end && ((*p >= '0' && *p <= '9') || *p == '_')) p++; - } - if (p < s->buf_end && (*p == 'e' || *p == 'E')) { - is_float = TRUE; - p++; - if (p < s->buf_end && (*p == '+' || *p == '-')) p++; - const uint8_t *exp_start = p; - while (p < s->buf_end && (*p >= '0' && *p <= '9')) p++; - if (p == exp_start) - ast_error (s, start, "malformed number: no digits after exponent"); - } - } - (void)is_float; - s->token_val = TOK_NUMBER; - char *numstr = sys_malloc (p - start + 1); - memcpy (numstr, start, p - start); - numstr[p - start] = '\0'; - double val = strtod (numstr, NULL); - sys_free (numstr); - s->token_u.num.val = val; - } break; - case '*': - if (p[1] == '=') { p += 2; s->token_val = TOK_MUL_ASSIGN; } - else if (p[1] == '*') { - if (p[2] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 3; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 3; - } - else if (p[2] == '=') { p += 3; s->token_val = TOK_POW_ASSIGN; } - else { p += 2; s->token_val = TOK_POW; } - } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '%': - if (p[1] == '=') { p += 2; s->token_val = TOK_MOD_ASSIGN; } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '+': - if (p[1] == '=') { p += 2; s->token_val = TOK_PLUS_ASSIGN; } - else if (p[1] == '+') { p += 2; s->token_val = TOK_INC; } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '-': - if (p[1] == '=') { p += 2; s->token_val = TOK_MINUS_ASSIGN; } - else if (p[1] == '-') { p += 2; s->token_val = TOK_DEC; } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '<': - if (p[1] == '=' && p[2] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 3; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 3; - } - else if (p[1] == '=') { p += 2; s->token_val = TOK_LTE; } - else if (p[1] == '<') { - if (p[2] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 3; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 3; - } - else if (p[2] == '=') { p += 3; s->token_val = TOK_SHL_ASSIGN; } - else { p += 2; s->token_val = TOK_SHL; } - } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '>': - if (p[1] == '=' && p[2] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 3; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 3; - } - else if (p[1] == '=') { p += 2; s->token_val = TOK_GTE; } - else if (p[1] == '>') { - if (p[2] == '>') { - if (p[3] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 4; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 4; - } - else if (p[3] == '=') { p += 4; s->token_val = TOK_SHR_ASSIGN; } - else { p += 3; s->token_val = TOK_SHR; } - } - else if (p[2] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 3; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 3; - } - else if (p[2] == '=') { p += 3; s->token_val = TOK_SAR_ASSIGN; } - else { p += 2; s->token_val = TOK_SAR; } - } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '=': - if (p[1] == '=') { - if (p[2] == '=') { p += 3; s->token_val = TOK_STRICT_EQ; } - else { p += 2; s->token_val = TOK_EQ; } - } else if (p[1] == '>') { p += 2; s->token_val = TOK_ARROW; } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '!': - if (p[1] == '=') { - if (p[2] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 3; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 3; - } - else if (p[2] == '=') { p += 3; s->token_val = TOK_STRICT_NEQ; } - else { p += 2; s->token_val = TOK_NEQ; } - } else { goto def_token; } - break; - case '&': - if (p[1] == '&') { - if (p[2] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 3; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 3; - } - else if (p[2] == '=') { p += 3; s->token_val = TOK_LAND_ASSIGN; } - else { p += 2; s->token_val = TOK_LAND; } - } - else if (p[1] == '=') { p += 2; s->token_val = TOK_AND_ASSIGN; } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '|': - if (p[1] == '|') { - if (p[2] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 3; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 3; - } - else if (p[2] == '=') { p += 3; s->token_val = TOK_LOR_ASSIGN; } - else { p += 2; s->token_val = TOK_LOR; } - } - else if (p[1] == '=') { p += 2; s->token_val = TOK_OR_ASSIGN; } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '^': - if (p[1] == '=') { p += 2; s->token_val = TOK_XOR_ASSIGN; } - else if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '[': - if (p[1] == ']' && p[2] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 3; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 3; - } - else { goto def_token; } - break; - case '~': - if (p[1] == '!') { - s->token_u.ident.str = (const char *)p; - s->token_u.ident.len = 2; - s->token_u.ident.has_escape = FALSE; - s->token_u.ident.is_reserved = FALSE; - s->token_val = TOK_IDENT; - p += 2; - } - else { goto def_token; } - break; - case '?': - goto def_token; - default: - def_token: - p++; - s->token_val = c; - break; - } - s->buf_ptr = p; - return 0; -} - -static cJSON *ast_parse_primary (ASTParseState *s) { - const uint8_t *start = s->token_ptr; - cJSON *node = NULL; - - switch (s->token_val) { - case TOK_NUMBER: { - node = ast_node (s, "number", start); - double d = s->token_u.num.val; - /* Store original text representation */ - size_t len = s->buf_ptr - start; - char *text = sys_malloc (len + 1); - memcpy (text, start, len); - text[len] = '\0'; - cJSON_AddStringToObject (node, "value", text); - cJSON_AddNumberToObject (node, "number", d); - sys_free (text); - ast_node_end (s, node, s->buf_ptr); - ast_next_token (s); - } break; - - case TOK_STRING: { - node = ast_node (s, "text", start); - cjson_add_strn (node, "value", s->token_u.str.str, s->token_u.str.len); - ast_node_end (s, node, s->buf_ptr); - ast_next_token (s); - } break; - - case TOK_TEMPLATE: { - const uint8_t *tmpl_start = start + 1; - const uint8_t *tmpl_end = s->buf_ptr - 1; - const uint8_t *saved_end = s->buf_ptr; - - /* Quick scan for ${ */ - BOOL has_expr = FALSE; - for (const uint8_t *sc = tmpl_start; sc < tmpl_end; sc++) { - if (*sc == '\\' && sc + 1 < tmpl_end) { sc++; continue; } - if (*sc == '$' && sc + 1 < tmpl_end && sc[1] == '{') { - has_expr = TRUE; break; - } - } - - if (!has_expr) { - /* Simple template — unchanged behavior */ - node = ast_node (s, "text", start); - cjson_add_strn (node, "value", s->token_u.str.str, s->token_u.str.len); - ast_node_end (s, node, s->buf_ptr); - ast_next_token (s); - } else { - node = ast_node (s, "text literal", start); - cJSON *list = cJSON_AddArrayToObject (node, "list"); - - /* Build format string with {N} placeholders */ - int cap = 256; - char *fmt = sys_malloc (cap); - int len = 0; - int idx = 0; - const uint8_t *p = tmpl_start; - - while (p < tmpl_end) { - if (*p == '\\' && p + 1 < tmpl_end) { - p++; /* skip backslash */ - if (len + 8 >= cap) { cap *= 2; fmt = sys_realloc (fmt, cap); } - switch (*p) { - case 'n': fmt[len++] = '\n'; p++; break; - case 't': fmt[len++] = '\t'; p++; break; - case 'r': fmt[len++] = '\r'; p++; break; - case '\\': fmt[len++] = '\\'; p++; break; - case '`': fmt[len++] = '`'; p++; break; - case '$': fmt[len++] = '$'; p++; break; - case '0': fmt[len++] = '\0'; p++; break; - case 'u': { - p++; - unsigned int cp = 0; - for (int i = 0; i < 4 && p < tmpl_end; i++, p++) { - cp <<= 4; - if (*p >= '0' && *p <= '9') cp |= *p - '0'; - else if (*p >= 'a' && *p <= 'f') cp |= *p - 'a' + 10; - else if (*p >= 'A' && *p <= 'F') cp |= *p - 'A' + 10; - else break; - } - len += unicode_to_utf8 ((uint8_t *)fmt + len, cp); - } break; - default: fmt[len++] = *p++; break; - } - continue; - } - if (*p == '$' && p + 1 < tmpl_end && p[1] == '{') { - /* Add {N} placeholder */ - if (len + 12 >= cap) { cap *= 2; fmt = sys_realloc (fmt, cap); } - len += snprintf (fmt + len, cap - len, "{%d}", idx++); - p += 2; /* skip ${ */ - - /* Parse expression: redirect buf_ptr, tokenize, parse */ - s->buf_ptr = p; - ast_next_token (s); - cJSON *expr = ast_parse_assign_expr (s); - if (expr) cJSON_AddItemToArray (list, expr); - - /* After expression, token should be '}' */ - if (s->token_val == '}') { - p = s->buf_ptr; - } else { - ast_error (s, p, "expected '}' after template expression"); - p = s->buf_ptr; - } - continue; - } - if (len + 1 >= cap) { cap *= 2; fmt = sys_realloc (fmt, cap); } - fmt[len++] = *p++; - } - fmt[len] = '\0'; - - cJSON_AddStringToObject (node, "value", fmt); - sys_free (fmt); - - s->buf_ptr = saved_end; - ast_node_end (s, node, saved_end); - ast_next_token (s); - } - } break; - - case TOK_IDENT: { - /* Check for single-param arrow function: x => ... */ - const uint8_t *p = s->buf_ptr; - while (p < s->buf_end && (*p == ' ' || *p == '\t')) p++; - if (p + 1 < s->buf_end && p[0] == '=' && p[1] == '>') { - node = ast_parse_arrow_function (s); - } else { - node = ast_node (s, "name", start); - cjson_add_strn (node, "name", s->token_u.ident.str, s->token_u.ident.len); - ast_node_end (s, node, s->buf_ptr); - ast_next_token (s); - } - } break; - - case TOK_NULL: - node = ast_node (s, "null", start); - ast_node_end (s, node, s->buf_ptr); - ast_next_token (s); - break; - - case TOK_TRUE: - node = ast_node (s, "true", start); - ast_node_end (s, node, s->buf_ptr); - ast_next_token (s); - break; - - case TOK_FALSE: - node = ast_node (s, "false", start); - ast_node_end (s, node, s->buf_ptr); - ast_next_token (s); - break; - - case TOK_THIS: - node = ast_node (s, "this", start); - ast_node_end (s, node, s->buf_ptr); - ast_next_token (s); - break; - - case '[': { - node = ast_node (s, "array", start); - cJSON *list = cJSON_AddArrayToObject (node, "list"); - ast_next_token (s); - while (s->token_val != ']' && s->token_val != TOK_EOF) { - cJSON *elem = ast_parse_assign_expr (s); - if (elem) cJSON_AddItemToArray (list, elem); - if (s->token_val == ',') ast_next_token (s); - else break; - } - ast_node_end (s, node, s->buf_ptr); - if (s->token_val == ']') { - ast_next_token (s); - } else if (s->token_val == TOK_EOF) { - ast_error (s, s->token_ptr, "unterminated array literal, expected ']'"); - } - } break; - - case '{': { - node = ast_node (s, "record", start); - cJSON *list = cJSON_AddArrayToObject (node, "list"); - ast_next_token (s); - while (s->token_val != '}' && s->token_val != TOK_EOF) { - cJSON *pair = cJSON_CreateObject (); - /* property name */ - int is_ident = (s->token_val == TOK_IDENT); - int is_keyword = (s->token_val >= TOK_FIRST_KEYWORD && s->token_val <= TOK_LAST_KEYWORD); - if (is_ident || is_keyword || s->token_val == TOK_STRING || s->token_val == TOK_NUMBER) { - cJSON *left; - if (is_keyword) { - left = ast_node (s, "name", s->token_ptr); - cjson_add_strn (left, "name", s->token_u.ident.str, s->token_u.ident.len); - ast_node_end (s, left, s->buf_ptr); - ast_next_token (s); - } else { - left = ast_parse_primary (s); - } - cJSON_AddItemToObject (pair, "left", left); - } else if (s->token_val == '[') { - /* computed property */ - ast_next_token (s); - cJSON *left = ast_parse_assign_expr (s); - cJSON_AddItemToObject (pair, "left", left); - if (s->token_val == ']') { - ast_next_token (s); - } else { - ast_error (s, s->token_ptr, "expected ']' after computed property"); - } - } else { - cJSON_Delete (pair); - ast_error (s, s->token_ptr, "expected property name in object literal"); - break; - } - /* colon and value */ - if (s->token_val == ':') { - ast_next_token (s); - cJSON *right = ast_parse_assign_expr (s); - cJSON_AddItemToObject (pair, "right", right); - } else if (s->token_val == '(') { - /* Method shorthand: init() {} => init: function init() {} */ - const uint8_t *fn_start = s->token_ptr; - cJSON *fn = ast_node (s, "function", fn_start); - - /* Set method name from property key */ - cJSON *left = cJSON_GetObjectItemCaseSensitive (pair, "left"); - cJSON *name_item = cJSON_GetObjectItemCaseSensitive (left, "name"); - if (name_item) - cJSON_AddStringToObject (fn, "name", name_item->valuestring); - - /* Parse parameters */ - cJSON *params = cJSON_AddArrayToObject (fn, "list"); - ast_next_token (s); /* skip '(' */ - while (s->token_val != ')' && s->token_val != TOK_EOF) { - if (s->token_val == TOK_IDENT) { - cJSON *param = ast_node (s, "name", s->token_ptr); - cjson_add_strn (param, "name", s->token_u.ident.str, s->token_u.ident.len); - ast_node_end (s, param, s->buf_ptr); - ast_next_token (s); - if (s->token_val == '=' || s->token_val == '|') { - ast_next_token (s); - cJSON *default_val = ast_parse_expr (s); - cJSON_AddItemToObject (param, "expression", default_val); - } - cJSON_AddItemToArray (params, param); - } else { - ast_error (s, s->token_ptr, "expected parameter name"); - break; - } - if (s->token_val == ',') ast_next_token (s); - else break; - } - if (s->token_val == ')') ast_next_token (s); - else if (s->token_val == TOK_EOF) - ast_error (s, s->token_ptr, "unterminated method parameter list"); - - if (cJSON_GetArraySize (params) > 4) - ast_error (s, s->token_ptr, "functions cannot have more than 4 parameters"); - - /* Parse body */ - if (s->token_val == '{') { - ast_next_token (s); - cJSON *stmts = ast_parse_block_statements (s); - cJSON_AddItemToObject (fn, "statements", stmts); - if (s->token_val == '}') ast_next_token (s); - else if (s->token_val == TOK_EOF) - ast_error (s, s->token_ptr, "unterminated method body"); - } else { - ast_error (s, s->token_ptr, "expected '{' for method body"); - } - - cJSON_AddNumberToObject (fn, "function_nr", s->function_nr++); - ast_node_end (s, fn, s->buf_ptr); - cJSON_AddItemToObject (pair, "right", fn); - } else if (!(is_ident && (s->token_val == ',' || s->token_val == '}'))) { - ast_error (s, s->token_ptr, "expected ':' after property name"); - } - cJSON_AddItemToArray (list, pair); - if (s->token_val == ',') ast_next_token (s); - else break; - } - ast_node_end (s, node, s->buf_ptr); - if (s->token_val == '}') { - ast_next_token (s); - } else if (s->token_val == TOK_EOF) { - ast_error (s, s->token_ptr, "unterminated object literal, expected '}'"); - } - } break; - - case '(': { - /* Check for arrow function: () => ..., (a, b) => ... */ - if (ast_is_arrow_function (s)) { - node = ast_parse_arrow_function (s); - } else { - ast_next_token (s); - node = ast_parse_expr (s); - if (s->token_val == ')') { - ast_next_token (s); - } else if (s->token_val == TOK_EOF) { - ast_error (s, s->token_ptr, "unterminated parenthesized expression, expected ')'"); - } else { - ast_error (s, s->token_ptr, "expected ')' after expression"); - } - } - } break; - - case TOK_FUNCTION: { - node = ast_parse_function_inner (s, TRUE); - } break; - - case '/': { - /* Regex literal - when / appears in primary position, it's a regex */ - node = ast_node (s, "regexp", start); - const uint8_t *p = s->token_ptr + 1; /* skip opening / */ - const uint8_t *pattern_start = p; - - /* Parse pattern - find closing / (not escaped) */ - while (p < s->buf_end && *p != '/') { - if (*p == '\\' && p + 1 < s->buf_end) { - p += 2; /* skip escape sequence */ - } else if (*p == '\n' || *p == '\r') { - ast_error (s, p, "unterminated regex literal"); - break; - } else { - p++; - } - } - size_t pattern_len = p - pattern_start; - if (p < s->buf_end) p++; /* skip closing / */ - - /* Parse flags */ - const uint8_t *flags_start = p; - while (p < s->buf_end && ((*p >= 'a' && *p <= 'z') || (*p >= 'A' && *p <= 'Z'))) { - p++; - } - size_t flags_len = p - flags_start; - - char *pattern = sys_malloc (pattern_len + 1); - memcpy (pattern, pattern_start, pattern_len); - pattern[pattern_len] = '\0'; - cJSON_AddStringToObject (node, "pattern", pattern); - sys_free (pattern); - - if (flags_len > 0) { - char *flags = sys_malloc (flags_len + 1); - memcpy (flags, flags_start, flags_len); - flags[flags_len] = '\0'; - cJSON_AddStringToObject (node, "flags", flags); - sys_free (flags); - } - - s->buf_ptr = p; - ast_node_end (s, node, s->buf_ptr); - ast_next_token (s); - } break; - - default: - /* Report syntax error with token info */ - if (s->token_val >= 32 && s->token_val < 127) { - ast_error (s, start, "unexpected '%c' in expression", s->token_val); - } else if (s->token_val == TOK_EOF) { - ast_error (s, start, "unexpected end of input"); - } else { - ast_error (s, start, "unexpected token (keyword or operator) where expression expected"); - } - ast_next_token (s); - return NULL; - } - - return node; -} - -static cJSON *ast_parse_postfix (ASTParseState *s) { - cJSON *node = ast_parse_primary (s); - if (!node) return NULL; - - for (;;) { - const uint8_t *start = s->token_ptr; - if (s->token_val == '.') { - ast_next_token (s); - cJSON *new_node = ast_node (s, ".", start); - cJSON_AddItemToObject (new_node, "left", node); - if (s->token_val == TOK_IDENT - || (s->token_val >= TOK_FIRST_KEYWORD && s->token_val <= TOK_LAST_KEYWORD)) { - cjson_add_strn (new_node, "right", s->token_u.ident.str, s->token_u.ident.len); - ast_next_token (s); - } else { - ast_error (s, s->token_ptr, "expected property name after '.'"); - } - ast_node_end (s, new_node, s->buf_ptr); - node = new_node; - } else if (s->token_val == '[') { - ast_next_token (s); - cJSON *new_node = ast_node (s, "[", start); - cJSON_AddItemToObject (new_node, "left", node); - if (s->token_val == ']') { - ast_next_token (s); - } else { - cJSON *index = ast_parse_assign_expr (s); - cJSON_AddItemToObject (new_node, "right", index); - if (s->token_val == ']') ast_next_token (s); - else ast_error (s, s->token_ptr, "expected ']'"); - } - ast_node_end (s, new_node, s->buf_ptr); - node = new_node; - } else if (s->token_val == '(') { - ast_next_token (s); - cJSON *new_node = ast_node (s, "(", start); - cJSON_AddItemToObject (new_node, "expression", node); - cJSON *list = cJSON_AddArrayToObject (new_node, "list"); - while (s->token_val != ')' && s->token_val != TOK_EOF) { - cJSON *arg = ast_parse_assign_expr (s); - if (arg) cJSON_AddItemToArray (list, arg); - if (s->token_val == ',') ast_next_token (s); - else break; - } - if (s->token_val == ')') ast_next_token (s); - else ast_error (s, s->token_ptr, "unterminated argument list, expected ')'"); - ast_node_end (s, new_node, s->buf_ptr); - node = new_node; - } else if (s->token_val == TOK_INC) { - cJSON *new_node = ast_node (s, "++", start); - cJSON_AddItemToObject (new_node, "expression", node); - cJSON_AddBoolToObject (new_node, "postfix", 1); - ast_next_token (s); - ast_node_end (s, new_node, s->buf_ptr); - node = new_node; - } else if (s->token_val == TOK_DEC) { - cJSON *new_node = ast_node (s, "--", start); - cJSON_AddItemToObject (new_node, "expression", node); - cJSON_AddBoolToObject (new_node, "postfix", 1); - ast_next_token (s); - ast_node_end (s, new_node, s->buf_ptr); - node = new_node; - } else { - break; - } - } - return node; -} - -static cJSON *ast_parse_unary (ASTParseState *s) { - const uint8_t *start = s->token_ptr; - - switch (s->token_val) { - case '!': { - ast_next_token (s); - cJSON *node = ast_node (s, "!", start); - cJSON *expr = ast_parse_unary (s); - cJSON_AddItemToObject (node, "expression", expr); - ast_node_end (s, node, s->buf_ptr); - return node; - } - case '~': { - ast_next_token (s); - cJSON *node = ast_node (s, "~", start); - cJSON *expr = ast_parse_unary (s); - cJSON_AddItemToObject (node, "expression", expr); - ast_node_end (s, node, s->buf_ptr); - return node; - } - case '+': { - ast_next_token (s); - cJSON *node = ast_node (s, "+unary", start); - cJSON *expr = ast_parse_unary (s); - cJSON_AddItemToObject (node, "expression", expr); - ast_node_end (s, node, s->buf_ptr); - return node; - } - case '-': { - ast_next_token (s); - cJSON *node = ast_node (s, "-unary", start); - cJSON *expr = ast_parse_unary (s); - cJSON_AddItemToObject (node, "expression", expr); - ast_node_end (s, node, s->buf_ptr); - return node; - } - case TOK_INC: { - ast_next_token (s); - cJSON *node = ast_node (s, "++", start); - cJSON *expr = ast_parse_unary (s); - cJSON_AddItemToObject (node, "expression", expr); - cJSON_AddBoolToObject (node, "postfix", 0); - ast_node_end (s, node, s->buf_ptr); - return node; - } - case TOK_DEC: { - ast_next_token (s); - cJSON *node = ast_node (s, "--", start); - cJSON *expr = ast_parse_unary (s); - cJSON_AddItemToObject (node, "expression", expr); - cJSON_AddBoolToObject (node, "postfix", 0); - ast_node_end (s, node, s->buf_ptr); - return node; - } - case TOK_DELETE: { - ast_next_token (s); - cJSON *node = ast_node (s, "delete", start); - cJSON *expr = ast_parse_unary (s); - cJSON_AddItemToObject (node, "expression", expr); - ast_node_end (s, node, s->buf_ptr); - return node; - } - default: - return ast_parse_postfix (s); - } -} - -/* Binary operator precedence levels */ -typedef struct { - int token; - const char *kind; - int prec; -} ASTBinOp; - -static const ASTBinOp ast_binops[] = { - { TOK_POW, "**", 14 }, - { '*', "*", 13 }, - { '/', "/", 13 }, - { '%', "%", 13 }, - { '+', "+", 12 }, - { '-', "-", 12 }, - { TOK_SHL, "<<", 11 }, - { TOK_SAR, ">>", 11 }, - { TOK_SHR, ">>>", 11 }, - { '<', "<", 10 }, - { '>', ">", 10 }, - { TOK_LTE, "<=", 10 }, - { TOK_GTE, ">=", 10 }, - { TOK_IN, "in", 10 }, - { TOK_EQ, "==", 9 }, - { TOK_NEQ, "!=", 9 }, - { TOK_STRICT_EQ, "===", 9 }, - { TOK_STRICT_NEQ, "!==", 9 }, - { '&', "&", 8 }, - { '^', "^", 7 }, - { '|', "|", 6 }, - { TOK_LAND, "&&", 5 }, - { TOK_LOR, "||", 4 }, - { 0, NULL, 0 } -}; - -static const ASTBinOp *ast_get_binop (int token) { - for (int i = 0; ast_binops[i].kind; i++) { - if (ast_binops[i].token == token) return &ast_binops[i]; - } - return NULL; -} - -static cJSON *ast_parse_binary (ASTParseState *s, int min_prec) { - cJSON *left = ast_parse_unary (s); - if (!left) return NULL; - - for (;;) { - const uint8_t *start = s->token_ptr; - const ASTBinOp *op = ast_get_binop (s->token_val); - if (!op || op->prec < min_prec) break; - - ast_next_token (s); - - /* Right associativity for ** */ - int next_prec = (op->prec == 14) ? op->prec : op->prec + 1; - cJSON *right = ast_parse_binary (s, next_prec); - - cJSON *node = ast_node (s, op->kind, start); - cJSON_AddItemToObject (node, "left", left); - cJSON_AddItemToObject (node, "right", right); - ast_node_end (s, node, s->buf_ptr); - left = node; - } - return left; -} - -static cJSON *ast_parse_ternary (ASTParseState *s) { - cJSON *cond = ast_parse_binary (s, 1); - if (!cond) return NULL; - - if (s->token_val == '?') { - const uint8_t *start = s->token_ptr; - ast_next_token (s); - cJSON *then_expr = ast_parse_expr (s); - if (s->token_val == ':') ast_next_token (s); - else ast_error (s, s->token_ptr, "expected ':' in ternary expression"); - cJSON *else_expr = ast_parse_expr (s); - - cJSON *node = ast_node (s, "then", start); - cJSON_AddItemToObject (node, "expression", cond); - cJSON_AddItemToObject (node, "then", then_expr); - cJSON_AddItemToObject (node, "else", else_expr); - ast_node_end (s, node, s->buf_ptr); - return node; - } - return cond; -} - -static cJSON *ast_parse_assign (ASTParseState *s) { - cJSON *left = ast_parse_ternary (s); - if (!left) return NULL; - - const uint8_t *start = s->token_ptr; - const char *kind = NULL; - - switch (s->token_val) { - case '=': kind = "assign"; break; - case TOK_PLUS_ASSIGN: kind = "+="; break; - case TOK_MINUS_ASSIGN: kind = "-="; break; - case TOK_MUL_ASSIGN: kind = "*="; break; - case TOK_DIV_ASSIGN: kind = "/="; break; - case TOK_MOD_ASSIGN: kind = "%="; break; - case TOK_SHL_ASSIGN: kind = "<<="; break; - case TOK_SAR_ASSIGN: kind = ">>="; break; - case TOK_SHR_ASSIGN: kind = ">>>="; break; - case TOK_AND_ASSIGN: kind = "&="; break; - case TOK_XOR_ASSIGN: kind = "^="; break; - case TOK_OR_ASSIGN: kind = "|="; break; - case TOK_POW_ASSIGN: kind = "**="; break; - case TOK_LAND_ASSIGN: kind = "&&="; break; - case TOK_LOR_ASSIGN: kind = "||="; break; - default: - return left; - } - - /* Validate assignment target */ - { - const char *left_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "kind")); - if (left_kind && - strcmp (left_kind, "name") != 0 && - strcmp (left_kind, ".") != 0 && - strcmp (left_kind, "[") != 0 && - strcmp (left_kind, "?.") != 0 && - strcmp (left_kind, "?.[") != 0) { - ast_error (s, start, "invalid assignment left-hand side"); - } - } - - ast_next_token (s); - cJSON *right = ast_parse_assign (s); - - cJSON *node = ast_node (s, kind, start); - cJSON_AddItemToObject (node, "left", left); - cJSON_AddItemToObject (node, "right", right); - - /* Check for push/pop bracket syntax */ - const char *left_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "kind")); - if (left_kind && strcmp (left_kind, "[") == 0 && !cJSON_GetObjectItemCaseSensitive (left, "right")) - cJSON_AddBoolToObject (node, "push", 1); - const char *right_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (right, "kind")); - if (right_kind && strcmp (right_kind, "[") == 0 && !cJSON_GetObjectItemCaseSensitive (right, "right")) - cJSON_AddBoolToObject (node, "pop", 1); - - ast_node_end (s, node, s->buf_ptr); - return node; -} - -/* Parse assignment expression (excludes comma operator) */ -static cJSON *ast_parse_assign_expr (ASTParseState *s) { - return ast_parse_assign (s); -} - -/* Parse full expression including comma operator */ -static cJSON *ast_parse_expr (ASTParseState *s) { - cJSON *left = ast_parse_assign (s); - if (!left) return NULL; - - /* Handle comma operator: (1, 2, 3) => 3 */ - while (s->token_val == ',') { - const uint8_t *start = s->token_ptr; - ast_next_token (s); - cJSON *right = ast_parse_assign (s); - - cJSON *node = ast_node (s, ",", start); - cJSON_AddItemToObject (node, "left", left); - cJSON_AddItemToObject (node, "right", right); - ast_node_end (s, node, s->buf_ptr); - left = node; - } - return left; -} - -static cJSON *ast_parse_block_statements (ASTParseState *s) { - cJSON *stmts = cJSON_CreateArray (); - while (s->token_val != '}' && s->token_val != TOK_EOF) { - const uint8_t *before = s->token_ptr; - cJSON *stmt = ast_parse_statement (s); - if (stmt) { - cJSON_AddItemToArray (stmts, stmt); - } else if (s->token_ptr == before) { - ast_sync_to_statement (s); - } - } - return stmts; -} - -static cJSON *ast_parse_function_inner (ASTParseState *s, BOOL is_expr) { - const uint8_t *start = s->token_ptr; - cJSON *node = ast_node (s, "function", start); - - if (s->in_disruption) { - ast_error (s, s->token_ptr, "cannot define function inside disruption clause"); - } - - ast_next_token (s); /* skip 'function' */ - - /* Optional function name */ - if (s->token_val == TOK_IDENT) { - cjson_add_strn (node, "name", s->token_u.ident.str, s->token_u.ident.len); - ast_next_token (s); - } - - /* Parameters */ - cJSON *params = cJSON_AddArrayToObject (node, "list"); - if (s->token_val == '(') { - ast_next_token (s); - while (s->token_val != ')' && s->token_val != TOK_EOF) { - if (s->token_val == TOK_IDENT) { - const uint8_t *param_ptr = s->token_ptr; - cJSON *param = ast_node (s, "name", param_ptr); - cjson_add_strn (param, "name", s->token_u.ident.str, s->token_u.ident.len); - /* Check for duplicate parameter name */ - { - char *tmp_name = sys_malloc (s->token_u.ident.len + 1); - memcpy (tmp_name, s->token_u.ident.str, s->token_u.ident.len); - tmp_name[s->token_u.ident.len] = '\0'; - cJSON *prev; - cJSON_ArrayForEach (prev, params) { - const char *prev_name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (prev, "name")); - if (prev_name && strcmp (prev_name, tmp_name) == 0) { - ast_error (s, param_ptr, "duplicate parameter name '%s'", tmp_name); - break; - } - } - sys_free (tmp_name); - } - ast_node_end (s, param, s->buf_ptr); - ast_next_token (s); - if (s->token_val == '=' || s->token_val == '|') { - ast_next_token (s); - cJSON *default_val = ast_parse_assign_expr (s); - cJSON_AddItemToObject (param, "expression", default_val); - } - cJSON_AddItemToArray (params, param); - } else { - ast_error (s, s->token_ptr, "expected parameter name"); - break; - } - if (s->token_val == ',') ast_next_token (s); - else break; - } - if (s->token_val == ')') { - ast_next_token (s); - } else if (s->token_val == TOK_EOF) { - ast_error (s, s->token_ptr, "unterminated function parameter list, expected ')'"); - } - } else { - ast_error (s, s->token_ptr, "expected '(' after function name"); - } - - if (cJSON_GetArraySize (params) > 4) { - ast_error (s, s->token_ptr, "functions cannot have more than 4 parameters"); - } - - /* Body */ - if (s->token_val == '{') { - ast_next_token (s); - cJSON *stmts = ast_parse_block_statements (s); - cJSON_AddItemToObject (node, "statements", stmts); - if (s->token_val == '}') { - ast_next_token (s); - } else if (s->token_val == TOK_EOF) { - ast_error (s, s->token_ptr, "unterminated function body, expected '}'"); - } - } else { - ast_error (s, s->token_ptr, "expected '{' for function body"); - } - - /* Optional disruption clause */ - if (s->token_val == TOK_DISRUPTION) { - ast_next_token (s); - if (s->token_val == '{') { - ast_next_token (s); - int old_in_disruption = s->in_disruption; - s->in_disruption = 1; - cJSON *disruption_stmts = ast_parse_block_statements (s); - s->in_disruption = old_in_disruption; - cJSON_AddItemToObject (node, "disruption", disruption_stmts); - if (s->token_val == '}') { - ast_next_token (s); - } else if (s->token_val == TOK_EOF) { - ast_error (s, s->token_ptr, "unterminated disruption clause, expected '}'"); - } - } else { - ast_error (s, s->token_ptr, "expected '{' after disruption"); - } - } - - cJSON_AddNumberToObject (node, "function_nr", s->function_nr++); - ast_node_end (s, node, s->buf_ptr); - return node; -} - -/* Parse arrow function: x => expr, (a, b) => expr, (x = 10) => expr, () => expr */ -static cJSON *ast_parse_arrow_function (ASTParseState *s) { - const uint8_t *start = s->token_ptr; - cJSON *node = ast_node (s, "function", start); - cJSON_AddBoolToObject (node, "arrow", 1); - - if (s->in_disruption) { - ast_error (s, s->token_ptr, "cannot define function inside disruption clause"); - } - - /* Parameters */ - cJSON *params = cJSON_AddArrayToObject (node, "list"); - - if (s->token_val == TOK_IDENT) { - /* Single parameter without parens: x => ... */ - cJSON *param = ast_node (s, "name", s->token_ptr); - cjson_add_strn (param, "name", s->token_u.ident.str, s->token_u.ident.len); - ast_node_end (s, param, s->buf_ptr); - cJSON_AddItemToArray (params, param); - ast_next_token (s); - } else if (s->token_val == '(') { - /* Parenthesized parameters: () => ..., (a, b) => ..., (x = 10) => ... */ - ast_next_token (s); - while (s->token_val != ')' && s->token_val != TOK_EOF) { - if (s->token_val == TOK_IDENT) { - const uint8_t *param_ptr = s->token_ptr; - cJSON *param = ast_node (s, "name", param_ptr); - cjson_add_strn (param, "name", s->token_u.ident.str, s->token_u.ident.len); - /* Check for duplicate parameter name */ - { - char *tmp_name = sys_malloc (s->token_u.ident.len + 1); - memcpy (tmp_name, s->token_u.ident.str, s->token_u.ident.len); - tmp_name[s->token_u.ident.len] = '\0'; - cJSON *prev; - cJSON_ArrayForEach (prev, params) { - const char *prev_name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (prev, "name")); - if (prev_name && strcmp (prev_name, tmp_name) == 0) { - ast_error (s, param_ptr, "duplicate parameter name '%s'", tmp_name); - break; - } - } - sys_free (tmp_name); - } - ast_node_end (s, param, s->buf_ptr); - ast_next_token (s); - - /* Check for default value */ - if (s->token_val == '=' || s->token_val == '|') { - ast_next_token (s); - cJSON *default_val = ast_parse_assign_expr (s); - cJSON_AddItemToObject (param, "expression", default_val); - } - cJSON_AddItemToArray (params, param); - } else { - ast_error (s, s->token_ptr, "expected parameter name"); - break; - } - if (s->token_val == ',') ast_next_token (s); - else break; - } - if (s->token_val == ')') ast_next_token (s); - } - - if (cJSON_GetArraySize (params) > 4) { - ast_error (s, s->token_ptr, "functions cannot have more than 4 parameters"); - } - - /* Arrow token */ - if (s->token_val != TOK_ARROW) { - ast_error (s, s->token_ptr, "expected '=>' in arrow function"); - } else { - ast_next_token (s); - } - - /* Body: either block or expression */ - if (s->token_val == '{') { - ast_next_token (s); - cJSON *stmts = ast_parse_block_statements (s); - cJSON_AddItemToObject (node, "statements", stmts); - if (s->token_val == '}') ast_next_token (s); - } else { - /* Expression body - wrap in implicit return. - Use assign_expr (not full expr) so commas after the body - are NOT consumed — matches JS spec (AssignmentExpression). */ - cJSON *stmts = cJSON_CreateArray (); - cJSON *ret = ast_node (s, "return", s->token_ptr); - cJSON *expr = ast_parse_assign_expr (s); - cJSON_AddItemToObject (ret, "expression", expr); - ast_node_end (s, ret, s->buf_ptr); - cJSON_AddItemToArray (stmts, ret); - cJSON_AddItemToObject (node, "statements", stmts); - } - - cJSON_AddNumberToObject (node, "function_nr", s->function_nr++); - ast_node_end (s, node, s->buf_ptr); - return node; -} - -static void ast_expect_semi (ASTParseState *s) { - if (s->token_val == ';') { ast_next_token (s); return; } - if (s->token_val == TOK_EOF || s->token_val == '}' || s->got_lf - || s->token_val == TOK_ELSE) return; - ast_error (s, s->token_ptr, "expecting ';'"); -} - -/* Skip tokens until a statement sync point to recover from errors */ -static void ast_sync_to_statement (ASTParseState *s) { - while (s->token_val != TOK_EOF) { - switch (s->token_val) { - case ';': - ast_next_token (s); /* consume semicolon */ - return; - case '}': - return; /* don't consume - let caller handle */ - case TOK_VAR: case TOK_DEF: case TOK_IF: case TOK_WHILE: - case TOK_FOR: case TOK_RETURN: case TOK_DISRUPT: - case TOK_FUNCTION: case TOK_BREAK: - case TOK_CONTINUE: case TOK_DO: - return; /* statement-starting keyword found */ - default: - ast_next_token (s); - break; - } - } -} - -static cJSON *ast_parse_statement (ASTParseState *s) { - const uint8_t *start = s->token_ptr; - cJSON *node = NULL; - - switch (s->token_val) { - case '{': { - node = ast_node (s, "block", start); - ast_next_token (s); - cJSON *stmts = ast_parse_block_statements (s); - cJSON_AddItemToObject (node, "statements", stmts); - if (s->token_val == '}') ast_next_token (s); - ast_node_end (s, node, s->buf_ptr); - } break; - - case TOK_VAR: - case TOK_DEF: { - const char *kind_name = (s->token_val == TOK_VAR) ? "var" : "def"; - int is_def = (s->token_val == TOK_DEF); - ast_next_token (s); - - /* Expect an identifier */ - if (s->token_val != TOK_IDENT) { - ast_error (s, s->token_ptr, "expected identifier after '%s'", kind_name); - return NULL; - } - - /* Can have multiple declarations: var x = 1, y = 2 */ - cJSON *decls = cJSON_CreateArray (); - int decl_count = 0; - while (s->token_val == TOK_IDENT) { - const uint8_t *var_ptr = s->token_ptr; - node = ast_node (s, kind_name, start); - cJSON *left = ast_node (s, "name", s->token_ptr); - cjson_add_strn (left, "name", s->token_u.ident.str, s->token_u.ident.len); - /* Save name for potential error message */ - char *var_name = sys_malloc (s->token_u.ident.len + 1); - memcpy (var_name, s->token_u.ident.str, s->token_u.ident.len); - var_name[s->token_u.ident.len] = '\0'; - ast_node_end (s, left, s->buf_ptr); - cJSON_AddItemToObject (node, "left", left); - ast_next_token (s); - - if (s->token_val == '=') { - ast_next_token (s); - cJSON *right = ast_parse_assign_expr (s); - cJSON_AddItemToObject (node, "right", right); - const char *right_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (right, "kind")); - if (right_kind && strcmp (right_kind, "[") == 0 && !cJSON_GetObjectItemCaseSensitive (right, "right")) - cJSON_AddBoolToObject (node, "pop", 1); - } else if (is_def) { - /* def (constant) requires initializer */ - ast_error (s, var_ptr, "missing initializer for constant '%s'", var_name); - } - sys_free (var_name); - ast_node_end (s, node, s->buf_ptr); - cJSON_AddItemToArray (decls, node); - decl_count++; - - if (s->token_val == ',') { - ast_next_token (s); - } else { - break; - } - } - ast_expect_semi (s); - if (decl_count == 1) { - node = cJSON_DetachItemFromArray (decls, 0); - cJSON_Delete (decls); - } else { - node = ast_node (s, "var_list", start); - cJSON_AddItemToObject (node, "list", decls); - ast_node_end (s, node, s->buf_ptr); - } - } break; - - case TOK_IF: { - node = ast_node (s, "if", start); - ast_next_token (s); - if (s->token_val == '(') ast_next_token (s); - else ast_error (s, s->token_ptr, "expected '(' before condition"); - cJSON *cond = ast_parse_expr (s); - cJSON_AddItemToObject (node, "expression", cond); - if (s->token_val == ')') ast_next_token (s); - else ast_error (s, s->token_ptr, "expected ')' after if condition"); - - cJSON *then_stmts = cJSON_AddArrayToObject (node, "then"); - cJSON *then_stmt = ast_parse_statement (s); - if (then_stmt) cJSON_AddItemToArray (then_stmts, then_stmt); - - cJSON *else_ifs = cJSON_AddArrayToObject (node, "list"); - - if (s->token_val == TOK_ELSE) { - ast_next_token (s); - if (s->token_val == TOK_IF) { - /* else if - add to list */ - cJSON *elif = ast_parse_statement (s); - if (elif) cJSON_AddItemToArray (else_ifs, elif); - } else { - cJSON *else_stmts = cJSON_AddArrayToObject (node, "else"); - cJSON *else_stmt = ast_parse_statement (s); - if (else_stmt) cJSON_AddItemToArray (else_stmts, else_stmt); - } - } - ast_node_end (s, node, s->buf_ptr); - } break; - - case TOK_WHILE: { - node = ast_node (s, "while", start); - ast_next_token (s); - if (s->token_val == '(') ast_next_token (s); - else ast_error (s, s->token_ptr, "expected '(' before condition"); - cJSON *cond = ast_parse_expr (s); - cJSON_AddItemToObject (node, "expression", cond); - if (s->token_val == ')') ast_next_token (s); - else ast_error (s, s->token_ptr, "expected ')' after while condition"); - - cJSON *stmts = cJSON_AddArrayToObject (node, "statements"); - cJSON *body = ast_parse_statement (s); - if (body) cJSON_AddItemToArray (stmts, body); - ast_node_end (s, node, s->buf_ptr); - } break; - - case TOK_DO: { - node = ast_node (s, "do", start); - ast_next_token (s); - - cJSON *stmts = cJSON_AddArrayToObject (node, "statements"); - cJSON *body = ast_parse_statement (s); - if (body) cJSON_AddItemToArray (stmts, body); - - if (s->token_val == TOK_WHILE) ast_next_token (s); - else ast_error (s, s->token_ptr, "expected 'while' after do body"); - if (s->token_val == '(') ast_next_token (s); - else ast_error (s, s->token_ptr, "expected '(' before condition"); - cJSON *cond = ast_parse_expr (s); - cJSON_AddItemToObject (node, "expression", cond); - if (s->token_val == ')') ast_next_token (s); - else ast_error (s, s->token_ptr, "expected ')' after do-while condition"); - ast_expect_semi (s); - ast_node_end (s, node, s->buf_ptr); - } break; - - case TOK_FOR: { - node = ast_node (s, "for", start); - ast_next_token (s); - if (s->token_val == '(') ast_next_token (s); - else ast_error (s, s->token_ptr, "expected '(' after for"); - - /* Init */ - if (s->token_val != ';') { - if (s->token_val == TOK_VAR || s->token_val == TOK_DEF) { - cJSON *init = ast_parse_statement (s); - cJSON_AddItemToObject (node, "init", init); - } else { - cJSON *init = ast_parse_expr (s); - cJSON_AddItemToObject (node, "init", init); - if (s->token_val == ';') ast_next_token (s); - } - } else { - ast_next_token (s); - } - - /* Test */ - if (s->token_val != ';') { - cJSON *test = ast_parse_expr (s); - cJSON_AddItemToObject (node, "test", test); - } - if (s->token_val == ';') ast_next_token (s); - - /* Update */ - if (s->token_val != ')') { - cJSON *update = ast_parse_expr (s); - cJSON_AddItemToObject (node, "update", update); - } - if (s->token_val == ')') ast_next_token (s); - else ast_error (s, s->token_ptr, "expected ')' after for clauses"); - - cJSON *stmts = cJSON_AddArrayToObject (node, "statements"); - cJSON *body = ast_parse_statement (s); - if (body) cJSON_AddItemToArray (stmts, body); - ast_node_end (s, node, s->buf_ptr); - } break; - - case TOK_RETURN: { - node = ast_node (s, "return", start); - ast_next_token (s); - if (s->token_val != ';' && s->token_val != '}' && !s->got_lf) { - cJSON *expr = ast_parse_expr (s); - cJSON_AddItemToObject (node, "expression", expr); - } - ast_expect_semi (s); - ast_node_end (s, node, s->buf_ptr); - } break; - - case TOK_GO: { - node = ast_node (s, "go", start); - ast_next_token (s); - if (s->token_val != ';' && s->token_val != '}' && !s->got_lf) { - cJSON *expr = ast_parse_expr (s); - cJSON_AddItemToObject (node, "expression", expr); - } - ast_expect_semi (s); - ast_node_end (s, node, s->buf_ptr); - } break; - - case TOK_DISRUPT: { - node = ast_node (s, "disrupt", start); - ast_next_token (s); - ast_expect_semi (s); - ast_node_end (s, node, s->buf_ptr); - } break; - - case TOK_BREAK: { - node = ast_node (s, "break", start); - ast_next_token (s); - if (s->token_val == TOK_IDENT && !s->got_lf) { - cjson_add_strn (node, "name", s->token_u.ident.str, s->token_u.ident.len); - ast_next_token (s); - } - ast_expect_semi (s); - ast_node_end (s, node, s->buf_ptr); - } break; - - case TOK_CONTINUE: { - node = ast_node (s, "continue", start); - ast_next_token (s); - if (s->token_val == TOK_IDENT && !s->got_lf) { - cjson_add_strn (node, "name", s->token_u.ident.str, s->token_u.ident.len); - ast_next_token (s); - } - ast_expect_semi (s); - ast_node_end (s, node, s->buf_ptr); - } break; - - - case TOK_FUNCTION: { - node = ast_parse_function_inner (s, FALSE); - } break; - - case ';': - /* Empty statement */ - ast_next_token (s); - return NULL; - - case TOK_IDENT: { - /* Check if this is a labeled statement: identifier: statement */ - const uint8_t *p = s->buf_ptr; - while (p < s->buf_end && (*p == ' ' || *p == '\t')) p++; - if (p < s->buf_end && *p == ':') { - /* Labeled statement */ - node = ast_node (s, "label", start); - cjson_add_strn (node, "name", s->token_u.ident.str, s->token_u.ident.len); - ast_next_token (s); /* skip identifier */ - ast_next_token (s); /* skip colon */ - cJSON *stmt = ast_parse_statement (s); - cJSON_AddItemToObject (node, "statement", stmt); - ast_node_end (s, node, s->buf_ptr); - } else { - /* Expression statement */ - cJSON *expr = ast_parse_expr (s); - if (expr) { - node = ast_node (s, "call", start); - cJSON_AddItemToObject (node, "expression", expr); - ast_node_end (s, node, s->buf_ptr); - } - ast_expect_semi (s); - } - } break; - - default: { - /* Expression statement */ - cJSON *expr = ast_parse_expr (s); - if (expr) { - node = ast_node (s, "call", start); - cJSON_AddItemToObject (node, "expression", expr); - ast_node_end (s, node, s->buf_ptr); - } else { - ast_error (s, start, "unexpected token at start of statement"); - return NULL; /* caller syncs */ - } - ast_expect_semi (s); - } break; - } - - return node; -} - -static cJSON *ast_parse_program (ASTParseState *s) { - cJSON *root = cJSON_CreateObject (); - cJSON_AddStringToObject (root, "kind", "program"); - cJSON_AddStringToObject (root, "filename", s->filename); - - cJSON *functions = cJSON_AddArrayToObject (root, "functions"); - cJSON *statements = cJSON_AddArrayToObject (root, "statements"); - - while (s->token_val != TOK_EOF) { - const uint8_t *before = s->token_ptr; - cJSON *stmt = ast_parse_statement (s); - if (stmt) { - const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (stmt, "kind")); - if (kind && strcmp (kind, "function") == 0) { - cJSON_AddItemToArray (functions, stmt); - } else { - cJSON_AddItemToArray (statements, stmt); - } - } else if (s->token_ptr == before) { - /* Statement returned NULL and didn't advance - sync to avoid infinite loop */ - ast_sync_to_statement (s); - } - } - - return root; -} - -/* ============================================================ - AST Semantic Pass - ============================================================ */ - -#define AST_SEM_MAX_VARS 256 - -typedef struct ASTSemVar { - const char *name; - const char *scope_name; /* disambiguated name for block-scope vars (NULL = use name) */ - int is_const; - const char *make; /* "def", "var", "function", "input" */ - int function_nr; /* which function this var belongs to */ - int nr_uses; /* reference count */ - int closure; /* 1 if used by inner function */ -} ASTSemVar; - -typedef struct ASTSemScope { - struct ASTSemScope *parent; - ASTSemVar vars[AST_SEM_MAX_VARS]; - int var_count; - int in_loop; - int function_nr; /* function_nr of enclosing function */ - int is_function_scope; /* 1 if this is a function's top-level scope */ - int block_depth; /* 0 = function scope, 1+ = block scope */ -} ASTSemScope; - -typedef struct ASTSemState { - cJSON *errors; - int has_error; - cJSON *scopes_array; - const char *intrinsics[256]; - int intrinsic_count; - int block_var_counter; /* monotonically increasing counter for unique block var names */ -} ASTSemState; - -static void ast_sem_error (ASTSemState *st, 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 (!st->errors) st->errors = cJSON_CreateArray (); - cJSON_AddItemToArray (st->errors, err); - st->has_error = 1; -} - -static void ast_sem_add_var (ASTSemScope *scope, const char *name, int is_const, - const char *make, int function_nr) { - if (scope->var_count < AST_SEM_MAX_VARS) { - ASTSemVar *v = &scope->vars[scope->var_count]; - v->name = name; - v->scope_name = NULL; - v->is_const = is_const; - v->make = make; - v->function_nr = function_nr; - v->nr_uses = 0; - v->closure = 0; - scope->var_count++; - } -} - -/* Propagate block-scope vars to the function scope (parent) with disambiguated names */ -static void ast_sem_propagate_block_vars (ASTSemState *st, ASTSemScope *parent, - ASTSemScope *block) { - for (int i = 0; i < block->var_count; i++) { - ASTSemVar *v = &block->vars[i]; - const char *sn = v->scope_name ? v->scope_name : v->name; - if (parent->var_count < AST_SEM_MAX_VARS) { - ASTSemVar *pv = &parent->vars[parent->var_count]; - pv->name = sn; - pv->scope_name = NULL; - pv->is_const = v->is_const; - pv->make = v->make; - pv->function_nr = v->function_nr; - pv->nr_uses = v->nr_uses; - pv->closure = v->closure; - parent->var_count++; - } - } -} - -typedef struct { - ASTSemVar *var; - int level; - int def_function_nr; -} ASTSemLookup; - -static ASTSemLookup ast_sem_lookup_var (ASTSemScope *scope, const char *name) { - ASTSemLookup result = {NULL, 0, -1}; - int cur_fn = scope->function_nr; - for (ASTSemScope *s = scope; s; s = s->parent) { - for (int i = 0; i < s->var_count; i++) { - if (strcmp (s->vars[i].name, name) == 0) { - result.var = &s->vars[i]; - result.def_function_nr = s->vars[i].function_nr; - return result; - } - } - /* When crossing into a parent with a different function_nr, increment level */ - if (s->parent && s->parent->function_nr != cur_fn) { - result.level++; - cur_fn = s->parent->function_nr; - } - } - return result; -} - -static ASTSemVar *ast_sem_find_var (ASTSemScope *scope, const char *name) { - ASTSemLookup r = ast_sem_lookup_var (scope, name); - return r.var; -} - -static void ast_sem_add_intrinsic (ASTSemState *st, const char *name) { - for (int i = 0; i < st->intrinsic_count; i++) { - if (strcmp (st->intrinsics[i], name) == 0) return; - } - if (st->intrinsic_count < 256) { - st->intrinsics[st->intrinsic_count++] = name; - } -} - -static cJSON *ast_sem_build_scope_record (ASTSemScope *scope, int *nr_slots, int *nr_close) { - cJSON *rec = cJSON_CreateObject (); - cJSON_AddNumberToObject (rec, "function_nr", scope->function_nr); - int slots = 0, close_slots = 0; - for (int i = 0; i < scope->var_count; i++) { - ASTSemVar *v = &scope->vars[i]; - cJSON *entry = cJSON_CreateObject (); - cJSON_AddStringToObject (entry, "make", v->make); - cJSON_AddNumberToObject (entry, "function_nr", v->function_nr); - cJSON_AddNumberToObject (entry, "nr_uses", v->nr_uses); - cJSON_AddBoolToObject (entry, "closure", v->closure); - cJSON_AddNumberToObject (entry, "level", 0); - cJSON_AddItemToObject (rec, v->name, entry); - slots++; - if (v->closure) close_slots++; - } - *nr_slots = slots; - *nr_close = close_slots; - return rec; -} - -static int ast_sem_in_loop (ASTSemScope *scope) { - for (ASTSemScope *s = scope; s; s = s->parent) { - if (s->in_loop) return 1; - } - return 0; -} - -static BOOL is_functino_name(const char *name) { - static const char *functinos[] = { - "+!", "-!", "*!", "/!", "%!", "**!", - "!", "<=!", ">=!", "=!", "!=!", - "&!", "|!", "^!", "<>!", ">>>!", - "&&!", "||!", "~!", "[]!", NULL - }; - for (int i = 0; functinos[i]; i++) - if (strcmp(name, functinos[i]) == 0) return TRUE; - return FALSE; -} - -static void ast_sem_check_expr (ASTSemState *st, ASTSemScope *scope, cJSON *expr); -static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt); - -static void ast_sem_predeclare_vars (ASTSemState *st, ASTSemScope *scope, cJSON *stmts) { - cJSON *stmt; - cJSON_ArrayForEach (stmt, stmts) { - const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (stmt, "kind")); - if (!kind) continue; - if (strcmp (kind, "function") == 0) { - const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (stmt, "name")); - if (name && !ast_sem_find_var (scope, name)) - ast_sem_add_var (scope, name, 0, "function", scope->function_nr); - } else if (strcmp (kind, "var") == 0) { - cJSON *left = cJSON_GetObjectItemCaseSensitive (stmt, "left"); - const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "name")); - if (name && !ast_sem_find_var (scope, name)) - ast_sem_add_var (scope, name, 0, kind, scope->function_nr); - } else if (strcmp (kind, "var_list") == 0) { - cJSON *item; - cJSON_ArrayForEach (item, cJSON_GetObjectItemCaseSensitive (stmt, "list")) { - const char *ik = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (item, "kind")); - if (ik && strcmp (ik, "var") == 0) { - cJSON *left = cJSON_GetObjectItemCaseSensitive (item, "left"); - const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "name")); - if (name && !ast_sem_find_var (scope, name)) - ast_sem_add_var (scope, name, 0, ik, scope->function_nr); - } - } - } - } -} - -/* Check whether an expression is being assigned to (=, +=, etc.) */ -static void ast_sem_check_assign_target (ASTSemState *st, ASTSemScope *scope, cJSON *left) { - if (!left) return; - const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "kind")); - if (!kind) return; - - if (strcmp (kind, "name") == 0) { - const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "name")); - if (!name) return; - ASTSemVar *v = ast_sem_find_var (scope, name); - if (!v) { - ast_sem_error (st, left, "cannot assign to unbound variable '%s'", name); - } else if (v->is_const) { - ast_sem_error (st, left, "cannot assign to constant '%s'", name); - } - /* Annotate with level/function_nr so compilers can emit correct set instructions */ - ASTSemLookup r = ast_sem_lookup_var (scope, name); - if (r.var) { - cJSON_AddNumberToObject (left, "level", r.level); - cJSON_AddNumberToObject (left, "function_nr", r.def_function_nr); - if (r.var->scope_name) - cJSON_AddStringToObject (left, "scope_name", r.var->scope_name); - } else { - cJSON_AddNumberToObject (left, "level", -1); - } - } else if (strcmp (kind, ".") == 0 || strcmp (kind, "[") == 0 || - strcmp (kind, "?.") == 0 || strcmp (kind, "?.[") == 0) { - /* Property access as assignment target: resolve the object expression */ - cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive (left, "expression"); - if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive (left, "left"); - ast_sem_check_expr (st, scope, obj_expr); - /* Also resolve the index expression for computed access */ - cJSON *idx_expr = cJSON_GetObjectItemCaseSensitive (left, "index"); - if (!idx_expr && strcmp (kind, "[") == 0) - idx_expr = cJSON_GetObjectItemCaseSensitive (left, "right"); - if (idx_expr && cJSON_IsObject (idx_expr)) - ast_sem_check_expr (st, scope, idx_expr); - } -} - -/* Recursively check an expression for semantic errors */ -static void ast_sem_check_expr (ASTSemState *st, ASTSemScope *scope, cJSON *expr) { - if (!expr) return; - const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (expr, "kind")); - if (!kind) return; - - /* 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) { - ast_sem_check_assign_target (st, scope, cJSON_GetObjectItemCaseSensitive (expr, "left")); - ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (expr, "right")); - return; - } - - /* Increment/decrement */ - if (strcmp (kind, "++") == 0 || strcmp (kind, "--") == 0) { - cJSON *operand = cJSON_GetObjectItemCaseSensitive (expr, "expression"); - if (operand) { - const char *op_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (operand, "kind")); - if (op_kind && strcmp (op_kind, "name") == 0) { - const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (operand, "name")); - if (name) { - ASTSemVar *v = ast_sem_find_var (scope, name); - if (!v) { - ast_sem_error (st, expr, "cannot assign to unbound variable '%s'", name); - } else if (v->is_const) { - ast_sem_error (st, expr, "cannot assign to constant '%s'", name); - } - /* Annotate with level/function_nr/scope_name so compilers can emit correct set instructions */ - ASTSemLookup r = ast_sem_lookup_var (scope, name); - if (r.var) { - cJSON_AddNumberToObject (operand, "level", r.level); - cJSON_AddNumberToObject (operand, "function_nr", r.def_function_nr); - if (r.var->scope_name) - cJSON_AddStringToObject (operand, "scope_name", r.var->scope_name); - } else { - cJSON_AddNumberToObject (operand, "level", -1); - } - } - } - } - return; - } - - /* Binary ops, ternary, comma — recurse into children */ - if (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 || strcmp (kind, "&") == 0 || - strcmp (kind, "|") == 0 || strcmp (kind, "^") == 0 || - strcmp (kind, "<<") == 0 || strcmp (kind, ">>") == 0 || - strcmp (kind, ">>>") == 0 || strcmp (kind, "**") == 0 || - strcmp (kind, "in") == 0 || strcmp (kind, "of") == 0 || - strcmp (kind, ".") == 0 || strcmp (kind, "[") == 0 || - strcmp (kind, "?.") == 0 || strcmp (kind, "?.[") == 0) { - ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (expr, "left")); - ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (expr, "right")); - return; - } - - /* Ternary */ - if (strcmp (kind, "then") == 0) { - ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (expr, "expression")); - ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (expr, "then")); - ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (expr, "else")); - return; - } - - /* Call and optional call */ - if (strcmp (kind, "(") == 0 || strcmp (kind, "?.(") == 0) { - ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (expr, "expression")); - cJSON *arg; - cJSON_ArrayForEach (arg, cJSON_GetObjectItemCaseSensitive (expr, "list")) { - ast_sem_check_expr (st, scope, arg); - } - return; - } - - /* Unary ops */ - if (strcmp (kind, "!") == 0 || strcmp (kind, "~") == 0 || - strcmp (kind, "delete") == 0 || strcmp (kind, "neg") == 0 || - strcmp (kind, "pos") == 0 || strcmp (kind, "spread") == 0 || - strcmp (kind, "-unary") == 0 || strcmp (kind, "+unary") == 0 || - strcmp (kind, "unary_-") == 0) { - ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (expr, "expression")); - return; - } - - /* Array literal */ - if (strcmp (kind, "array") == 0) { - cJSON *el; - cJSON_ArrayForEach (el, cJSON_GetObjectItemCaseSensitive (expr, "list")) { - ast_sem_check_expr (st, scope, el); - } - return; - } - - /* Object literal */ - if (strcmp (kind, "object") == 0 || strcmp (kind, "record") == 0) { - cJSON *prop; - cJSON_ArrayForEach (prop, cJSON_GetObjectItemCaseSensitive (expr, "list")) { - cJSON *val = cJSON_GetObjectItemCaseSensitive (prop, "value"); - if (!val) val = cJSON_GetObjectItemCaseSensitive (prop, "right"); - ast_sem_check_expr (st, scope, val); - } - return; - } - - /* Function expression / arrow function — create new scope */ - if (strcmp (kind, "function") == 0) { - cJSON *fn_nr_node = cJSON_GetObjectItemCaseSensitive (expr, "function_nr"); - int fn_nr = fn_nr_node ? (int)cJSON_GetNumberValue (fn_nr_node) : scope->function_nr; - - ASTSemScope fn_scope = {0}; - fn_scope.parent = scope; - fn_scope.function_nr = fn_nr; - fn_scope.is_function_scope = 1; - - cJSON_AddNumberToObject (expr, "outer", scope->function_nr); - - /* Add parameters as input */ - cJSON *param; - cJSON_ArrayForEach (param, cJSON_GetObjectItemCaseSensitive (expr, "list")) { - const char *pname = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (param, "name")); - if (pname) ast_sem_add_var (&fn_scope, pname, 1, "input", fn_nr); - /* Check default value expressions */ - cJSON *def_val = cJSON_GetObjectItemCaseSensitive (param, "expression"); - if (def_val) ast_sem_check_expr (st, &fn_scope, def_val); - } - - /* Pre-register all declarations for mutual recursion / forward references */ - cJSON *fn_stmts = cJSON_GetObjectItemCaseSensitive (expr, "statements"); - ast_sem_predeclare_vars (st, &fn_scope, fn_stmts); - - /* Check function body */ - cJSON *stmt; - cJSON_ArrayForEach (stmt, fn_stmts) { - ast_sem_check_stmt (st, &fn_scope, stmt); - } - - /* Check disruption clause */ - cJSON *disruption = cJSON_GetObjectItemCaseSensitive (expr, "disruption"); - if (disruption) { - cJSON_ArrayForEach (stmt, disruption) { - ast_sem_check_stmt (st, &fn_scope, stmt); - } - } - - /* Build scope record and attach to scopes array */ - int nr_slots, nr_close; - cJSON *rec = ast_sem_build_scope_record (&fn_scope, &nr_slots, &nr_close); - cJSON_AddItemToArray (st->scopes_array, rec); - cJSON_AddNumberToObject (expr, "nr_slots", nr_slots); - cJSON_AddNumberToObject (expr, "nr_close_slots", nr_close); - return; - } - - /* Template literal */ - if (strcmp (kind, "template") == 0 || strcmp (kind, "text literal") == 0) { - cJSON *el; - cJSON_ArrayForEach (el, cJSON_GetObjectItemCaseSensitive (expr, "list")) { - ast_sem_check_expr (st, scope, el); - } - return; - } - - /* Name token — annotate with level and function_nr */ - if (strcmp (kind, "name") == 0) { - const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (expr, "name")); - if (name) { - if (is_functino_name(name)) { - cJSON_AddStringToObject (expr, "make", "functino"); - cJSON_AddNumberToObject (expr, "level", -1); - return; - } - ASTSemLookup r = ast_sem_lookup_var (scope, name); - if (r.var) { - cJSON_AddNumberToObject (expr, "level", r.level); - cJSON_AddNumberToObject (expr, "function_nr", r.def_function_nr); - r.var->nr_uses++; - if (r.level > 0) r.var->closure = 1; - if (r.var->scope_name) - cJSON_AddStringToObject (expr, "scope_name", r.var->scope_name); - } else { - cJSON_AddNumberToObject (expr, "level", -1); - ast_sem_add_intrinsic (st, name); - } - } - return; - } - - /* number, string, regexp, null, true, false, this — leaf nodes, no check needed */ -} - -/* Check a statement for semantic errors */ -static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt) { - if (!stmt) return; - const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (stmt, "kind")); - if (!kind) return; - - if (strcmp (kind, "var_list") == 0) { - cJSON *item; - cJSON_ArrayForEach (item, cJSON_GetObjectItemCaseSensitive (stmt, "list")) { - ast_sem_check_stmt (st, scope, item); - } - return; - } - - if (strcmp (kind, "var") == 0) { - /* Register variable */ - cJSON *left = cJSON_GetObjectItemCaseSensitive (stmt, "left"); - const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "name")); - if (name) { - ASTSemVar *existing = ast_sem_find_var (scope, name); - if (existing && existing->is_const) { - ast_sem_error (st, left, "cannot redeclare constant '%s'", name); - } - if (!existing || existing->function_nr != scope->function_nr - || scope->block_depth > 0) - ast_sem_add_var (scope, name, 0, "var", scope->function_nr); - if (scope->block_depth > 0) { - char buf[128]; - snprintf (buf, sizeof (buf), "_%s_%d", name, st->block_var_counter++); - char *sn = sys_malloc (strlen (buf) + 1); - strcpy (sn, buf); - scope->vars[scope->var_count - 1].scope_name = sn; - cJSON_AddStringToObject (left, "scope_name", sn); - } - } - ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (stmt, "right")); - return; - } - - if (strcmp (kind, "def") == 0) { - /* Register constant */ - cJSON *left = cJSON_GetObjectItemCaseSensitive (stmt, "left"); - const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "name")); - if (name) { - ASTSemVar *existing = ast_sem_find_var (scope, name); - if (existing && existing->is_const) { - ast_sem_error (st, left, "cannot redeclare constant '%s'", name); - } else if (existing && !existing->is_const && existing->function_nr == scope->function_nr) { - /* Pre-scanned as var, now upgrading to const */ - existing->is_const = 1; - existing->make = "def"; - } else { - ast_sem_add_var (scope, name, 1, "def", scope->function_nr); - if (scope->block_depth > 0) { - char buf[128]; - snprintf (buf, sizeof (buf), "_%s_%d", name, st->block_var_counter++); - char *sn = sys_malloc (strlen (buf) + 1); - strcpy (sn, buf); - scope->vars[scope->var_count - 1].scope_name = sn; - cJSON_AddStringToObject (left, "scope_name", sn); - } - } - } - ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (stmt, "right")); - return; - } - - if (strcmp (kind, "call") == 0) { - ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (stmt, "expression")); - return; - } - - if (strcmp (kind, "if") == 0) { - ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (stmt, "expression")); - cJSON *s2; - { - ASTSemScope then_scope = {0}; - then_scope.parent = scope; - then_scope.function_nr = scope->function_nr; - then_scope.block_depth = scope->block_depth + 1; - cJSON_ArrayForEach (s2, cJSON_GetObjectItemCaseSensitive (stmt, "then")) { - ast_sem_check_stmt (st, &then_scope, s2); - } - ast_sem_propagate_block_vars (st, scope, &then_scope); - } - { - ASTSemScope list_scope = {0}; - list_scope.parent = scope; - list_scope.function_nr = scope->function_nr; - list_scope.block_depth = scope->block_depth + 1; - cJSON_ArrayForEach (s2, cJSON_GetObjectItemCaseSensitive (stmt, "list")) { - ast_sem_check_stmt (st, &list_scope, s2); - } - ast_sem_propagate_block_vars (st, scope, &list_scope); - } - { - ASTSemScope else_scope = {0}; - else_scope.parent = scope; - else_scope.function_nr = scope->function_nr; - else_scope.block_depth = scope->block_depth + 1; - cJSON_ArrayForEach (s2, cJSON_GetObjectItemCaseSensitive (stmt, "else")) { - ast_sem_check_stmt (st, &else_scope, s2); - } - ast_sem_propagate_block_vars (st, scope, &else_scope); - } - return; - } - - if (strcmp (kind, "while") == 0) { - ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (stmt, "expression")); - ASTSemScope loop_scope = {0}; - loop_scope.parent = scope; - loop_scope.in_loop = 1; - loop_scope.function_nr = scope->function_nr; - loop_scope.block_depth = scope->block_depth + 1; - cJSON *s2; - cJSON_ArrayForEach (s2, cJSON_GetObjectItemCaseSensitive (stmt, "statements")) { - ast_sem_check_stmt (st, &loop_scope, s2); - } - ast_sem_propagate_block_vars (st, scope, &loop_scope); - return; - } - - if (strcmp (kind, "do") == 0) { - ASTSemScope loop_scope = {0}; - loop_scope.parent = scope; - loop_scope.in_loop = 1; - loop_scope.function_nr = scope->function_nr; - loop_scope.block_depth = scope->block_depth + 1; - cJSON *s2; - cJSON_ArrayForEach (s2, cJSON_GetObjectItemCaseSensitive (stmt, "statements")) { - ast_sem_check_stmt (st, &loop_scope, s2); - } - ast_sem_propagate_block_vars (st, scope, &loop_scope); - ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (stmt, "expression")); - return; - } - - if (strcmp (kind, "for") == 0) { - ASTSemScope loop_scope = {0}; - loop_scope.parent = scope; - loop_scope.in_loop = 1; - loop_scope.function_nr = scope->function_nr; - loop_scope.block_depth = scope->block_depth + 1; - /* init may be a var/def statement or expression */ - cJSON *init = cJSON_GetObjectItemCaseSensitive (stmt, "init"); - 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)) { - ast_sem_check_stmt (st, &loop_scope, init); - } else { - ast_sem_check_expr (st, &loop_scope, init); - } - } - ast_sem_check_expr (st, &loop_scope, cJSON_GetObjectItemCaseSensitive (stmt, "test")); - ast_sem_check_expr (st, &loop_scope, cJSON_GetObjectItemCaseSensitive (stmt, "update")); - cJSON *s2; - cJSON_ArrayForEach (s2, cJSON_GetObjectItemCaseSensitive (stmt, "statements")) { - ast_sem_check_stmt (st, &loop_scope, s2); - } - ast_sem_propagate_block_vars (st, scope, &loop_scope); - return; - } - - if (strcmp (kind, "return") == 0 || strcmp (kind, "go") == 0) { - ast_sem_check_expr (st, scope, cJSON_GetObjectItemCaseSensitive (stmt, "expression")); - return; - } - - if (strcmp (kind, "disrupt") == 0) { - return; - } - - if (strcmp (kind, "break") == 0) { - if (!ast_sem_in_loop (scope)) { - ast_sem_error (st, stmt, "'break' used outside of loop"); - } - return; - } - - if (strcmp (kind, "continue") == 0) { - if (!ast_sem_in_loop (scope)) { - ast_sem_error (st, stmt, "'continue' used outside of loop"); - } - return; - } - - if (strcmp (kind, "block") == 0) { - ASTSemScope block_scope = {0}; - block_scope.parent = scope; - block_scope.function_nr = scope->function_nr; - block_scope.block_depth = scope->block_depth + 1; - cJSON *s2; - cJSON_ArrayForEach (s2, cJSON_GetObjectItemCaseSensitive (stmt, "statements")) { - ast_sem_check_stmt (st, &block_scope, s2); - } - ast_sem_propagate_block_vars (st, scope, &block_scope); - return; - } - - if (strcmp (kind, "label") == 0) { - ast_sem_check_stmt (st, scope, cJSON_GetObjectItemCaseSensitive (stmt, "statement")); - return; - } - - if (strcmp (kind, "function") == 0) { - /* Function declaration — register name, then check body in new scope */ - const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (stmt, "name")); - if (name) ast_sem_add_var (scope, name, 0, "function", scope->function_nr); - - cJSON *fn_nr_node = cJSON_GetObjectItemCaseSensitive (stmt, "function_nr"); - int fn_nr = fn_nr_node ? (int)cJSON_GetNumberValue (fn_nr_node) : scope->function_nr; - - ASTSemScope fn_scope = {0}; - fn_scope.parent = scope; - fn_scope.function_nr = fn_nr; - fn_scope.is_function_scope = 1; - - cJSON_AddNumberToObject (stmt, "outer", scope->function_nr); - - cJSON *param; - cJSON_ArrayForEach (param, cJSON_GetObjectItemCaseSensitive (stmt, "list")) { - const char *pname = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (param, "name")); - if (pname) ast_sem_add_var (&fn_scope, pname, 1, "input", fn_nr); - /* Check default value expressions */ - cJSON *def_val = cJSON_GetObjectItemCaseSensitive (param, "expression"); - if (def_val) ast_sem_check_expr (st, &fn_scope, def_val); - } - - /* Pre-register all var/def/function declarations for mutual recursion */ - ast_sem_predeclare_vars (st, &fn_scope, cJSON_GetObjectItemCaseSensitive (stmt, "statements")); - - cJSON *s2; - cJSON_ArrayForEach (s2, cJSON_GetObjectItemCaseSensitive (stmt, "statements")) { - ast_sem_check_stmt (st, &fn_scope, s2); - } - - /* Check disruption clause */ - cJSON *disruption = cJSON_GetObjectItemCaseSensitive (stmt, "disruption"); - if (disruption) { - cJSON_ArrayForEach (s2, disruption) { - ast_sem_check_stmt (st, &fn_scope, s2); - } - } - - /* Build scope record and attach to scopes array */ - int nr_slots, nr_close; - cJSON *rec = ast_sem_build_scope_record (&fn_scope, &nr_slots, &nr_close); - cJSON_AddItemToArray (st->scopes_array, rec); - cJSON_AddNumberToObject (stmt, "nr_slots", nr_slots); - cJSON_AddNumberToObject (stmt, "nr_close_slots", nr_close); - return; - } -} - -/* Run the semantic pass on a parsed AST, adding errors to the AST */ -static void ast_semantic_check (cJSON *ast, cJSON **errors_out, - cJSON **scopes_out, cJSON **intrinsics_out) { - ASTSemState st = {0}; - st.scopes_array = cJSON_CreateArray (); - - ASTSemScope global_scope = {0}; - global_scope.function_nr = 0; - global_scope.is_function_scope = 1; - - /* Process top-level function declarations first (they are hoisted) */ - cJSON *stmt; - cJSON_ArrayForEach (stmt, cJSON_GetObjectItemCaseSensitive (ast, "functions")) { - const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (stmt, "name")); - if (name) ast_sem_add_var (&global_scope, name, 0, "function", 0); - } - - /* Check all statements (var/def are registered as they are encountered) */ - cJSON_ArrayForEach (stmt, cJSON_GetObjectItemCaseSensitive (ast, "statements")) { - ast_sem_check_stmt (&st, &global_scope, stmt); - } - - /* Check function bodies */ - cJSON_ArrayForEach (stmt, cJSON_GetObjectItemCaseSensitive (ast, "functions")) { - ast_sem_check_stmt (&st, &global_scope, stmt); - } - - /* Build program scope record (function_nr 0) and prepend to scopes array */ - int nr_slots, nr_close; - cJSON *prog_rec = ast_sem_build_scope_record (&global_scope, &nr_slots, &nr_close); - /* Prepend: detach all children, add prog_rec, re-add children */ - cJSON *existing = st.scopes_array->child; - st.scopes_array->child = NULL; - cJSON_AddItemToArray (st.scopes_array, prog_rec); - if (existing) { - cJSON *last = prog_rec; - last->next = existing; - existing->prev = last; - } - - /* Build intrinsics array */ - cJSON *intr_arr = cJSON_CreateArray (); - for (int i = 0; i < st.intrinsic_count; i++) { - cJSON_AddItemToArray (intr_arr, cJSON_CreateString (st.intrinsics[i])); - } - - *errors_out = st.errors; - *scopes_out = st.scopes_array; - *intrinsics_out = intr_arr; -} - -cJSON *JS_ASTTree (const char *source, size_t len, const char *filename) { - ASTParseState s; - memset (&s, 0, sizeof (s)); - - s.filename = filename; - s.buf_start = (const uint8_t *)source; - s.buf_ptr = (const uint8_t *)source; - s.buf_end = (const uint8_t *)source + len; - s.function_nr = 1; - s.errors = NULL; - s.has_error = 0; - s.lc_cache.ptr = s.buf_start; - s.lc_cache.buf_start = s.buf_start; - - /* Get first token */ - ast_next_token (&s); - - /* Parse program */ - cJSON *ast = ast_parse_program (&s); - if (!ast) { - if (s.errors) cJSON_Delete (s.errors); - return NULL; - } - - /* Run semantic pass - only if parsing succeeded without errors */ - if (!s.has_error) { - cJSON *sem_errors = NULL; - cJSON *scopes = NULL; - cJSON *intrinsics = NULL; - ast_semantic_check (ast, &sem_errors, &scopes, &intrinsics); - - /* Attach scopes and intrinsics to AST */ - if (scopes) cJSON_AddItemToObject (ast, "scopes", scopes); - if (intrinsics) cJSON_AddItemToObject (ast, "intrinsics", intrinsics); - - if (sem_errors) { - cJSON_AddItemToObject (ast, "errors", sem_errors); - } - } - - if (s.errors) { - cJSON *existing = cJSON_GetObjectItemCaseSensitive (ast, "errors"); - if (existing) { - /* Append parse errors to existing semantic errors */ - cJSON *err; - cJSON *next; - for (err = s.errors->child; err; err = next) { - next = err->next; - cJSON_DetachItemViaPointer (s.errors, err); - cJSON_AddItemToArray (existing, err); - } - cJSON_Delete (s.errors); - } else { - cJSON_AddItemToObject (ast, "errors", s.errors); - } - } - - return ast; -} - -char *JS_AST (const char *source, size_t len, const char *filename) { - cJSON *ast = JS_ASTTree (source, len, filename); - if (!ast) return NULL; - char *json = cJSON_PrintUnformatted (ast); - cJSON_Delete (ast); - return json; -} - -/* Build a token object for the tokenizer output */ -static cJSON *build_token_object (ASTParseState *s) { - cJSON *tok = cJSON_CreateObject (); - const char *kind = ast_token_kind_str (s->token_val); - cJSON_AddStringToObject (tok, "kind", kind); - - /* Position info */ - int at = (int)(s->token_ptr - s->buf_start); - int from_row, from_col; - ast_get_line_col (s, s->token_ptr, &from_row, &from_col); - int to_row, to_col; - ast_get_line_col (s, s->buf_ptr, &to_row, &to_col); - - cJSON_AddNumberToObject (tok, "at", at); - cJSON_AddNumberToObject (tok, "from_row", from_row); - cJSON_AddNumberToObject (tok, "from_column", from_col); - cJSON_AddNumberToObject (tok, "to_row", to_row); - cJSON_AddNumberToObject (tok, "to_column", to_col); - - /* Value field based on token type */ - switch (s->token_val) { - case TOK_NUMBER: { - /* Store original source text as value */ - size_t len = s->buf_ptr - s->token_ptr; - char *text = sys_malloc (len + 1); - memcpy (text, s->token_ptr, len); - text[len] = '\0'; - cJSON_AddStringToObject (tok, "value", text); - sys_free (text); - /* Store parsed number */ - double d = s->token_u.num.val; - cJSON_AddNumberToObject (tok, "number", d); - } break; - case TOK_STRING: - case TOK_TEMPLATE: { - cjson_add_strn (tok, "value", s->token_u.str.str, s->token_u.str.len); - } break; - case TOK_IDENT: { - cjson_add_strn (tok, "value", s->token_u.ident.str, s->token_u.ident.len); - } break; - case TOK_ERROR: { - /* Store the raw source text as value */ - size_t len = s->buf_ptr - s->token_ptr; - char *text = sys_malloc (len + 1); - memcpy (text, s->token_ptr, len); - text[len] = '\0'; - cJSON_AddStringToObject (tok, "value", text); - sys_free (text); - } break; - case TOK_COMMENT: - case TOK_SPACE: - case TOK_NEWLINE: { - /* Store the raw source text */ - size_t len = s->buf_ptr - s->token_ptr; - char *text = sys_malloc (len + 1); - memcpy (text, s->token_ptr, len); - text[len] = '\0'; - cJSON_AddStringToObject (tok, "value", text); - sys_free (text); - } break; - default: - /* No value field for operators/punctuators/keywords */ - break; - } - - return tok; -} - -char *JS_Tokenize (const char *source, size_t len, const char *filename) { - ASTParseState s; - memset (&s, 0, sizeof (s)); - - s.filename = filename; - s.buf_start = (const uint8_t *)source; - s.buf_ptr = (const uint8_t *)source; - s.buf_end = (const uint8_t *)source + len; - s.function_nr = 0; - s.errors = NULL; - s.has_error = 0; - s.lc_cache.ptr = s.buf_start; - s.lc_cache.buf_start = s.buf_start; - - cJSON *root = cJSON_CreateObject (); - cJSON_AddStringToObject (root, "filename", filename); - cJSON *tokens = cJSON_AddArrayToObject (root, "tokens"); - - /* Tokenize all tokens including whitespace */ - while (1) { - tokenize_next (&s); - cJSON *tok = build_token_object (&s); - cJSON_AddItemToArray (tokens, tok); - if (s.token_val == TOK_EOF) break; - } - - /* Add errors to output if any */ - if (s.errors) { - cJSON_AddItemToObject (root, "errors", s.errors); - } - - char *json = cJSON_PrintUnformatted (root); - cJSON_Delete (root); - return json; -} - -/* ============================================================ - MACH Compiler — AST directly to binary JSCodeRegister - ============================================================ */ - -/* Variable kinds */ -#define MACH_VAR_ARG 0 -#define MACH_VAR_LOCAL 1 -#define MACH_VAR_CLOSED 2 - -/* Variable resolution result */ -typedef enum MachVarResolution { - MACH_VAR_LOCAL_SLOT, /* variable is in current scope */ - MACH_VAR_CLOSURE, /* variable is in parent scope */ - MACH_VAR_UNBOUND /* variable not found in any scope */ -} MachVarResolution; - -typedef struct MachVarInfo { - char *name; - int slot; - int is_const; /* 1 for def, function args; 0 for var */ - int is_closure; /* 1 if captured by a nested function */ - int scope_depth; /* block scope nesting level */ -} MachVarInfo; - -/* ---- Compile-time constant pool entry ---- */ -/* Stores raw data during compilation; converted to JSValues when loading into context */ -typedef enum { MACH_CP_INT, MACH_CP_FLOAT, MACH_CP_STR } MachCPType; -typedef struct { - MachCPType type; - union { - int32_t ival; /* integer constant */ - double fval; /* float constant */ - char *str; /* owned C string */ - }; -} MachCPoolEntry; - -/* ---- Compiled output (context-free) ---- */ -typedef struct MachCode { - uint16_t arity; - uint16_t nr_close_slots; - uint16_t nr_slots; - uint16_t entry_point; - - uint32_t cpool_count; - MachCPoolEntry *cpool; - - uint32_t instr_count; - MachInstr32 *instructions; - - uint32_t func_count; - struct MachCode **functions; - - char *name; /* owned C string, or NULL */ - MachLineEntry *line_table; /* [instr_count], parallel to instructions[] */ - char *filename; /* source filename (sys_malloc'd) */ - uint16_t disruption_pc; /* start of disruption handler (0 = none) */ -} MachCode; - -/* ---- Compiler state ---- */ - -typedef struct MachCompState { - /* Instruction buffer (growable) */ - MachInstr32 *code; - int code_count; - int code_capacity; - - /* Constant pool (raw entries, no GC objects) */ - MachCPoolEntry *cpool; - int cpool_count; - int cpool_capacity; - - /* Nested functions */ - MachCode **functions; - int func_count; - int func_capacity; - - /* Variables */ - MachVarInfo *vars; - int var_count; - int var_capacity; - - /* Register allocation (Lua-style) */ - int freereg; /* next free register */ - int maxreg; /* high-water mark */ - int nr_args; /* parameter count */ - - /* Loop labels for break/continue */ - int loop_break; /* instruction index to patch, or -1 */ - int loop_continue; /* instruction index to patch, or -1 */ - - /* Scope depth for block scoping */ - int scope_depth; - - /* Parent for nested function compilation */ - struct MachCompState *parent; - - /* AST semantic annotations */ - int function_nr; /* current function number (0=program body) */ - cJSON *scopes; /* pointer to AST "scopes" array (not owned) */ - - /* Error tracking */ - int has_error; - - /* Line tracking for debug info */ - int cur_line, cur_col; - MachLineEntry *line_info; /* growable, parallel to code[] */ - int line_capacity; - const char *filename; /* pointer into AST cJSON (not owned) */ -} MachCompState; - -/* Forward declarations */ -static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest); -static void mach_compile_stmt(MachCompState *cs, cJSON *stmt); - -/* ---- Compiler helpers ---- */ - -static void mach_set_pos(MachCompState *cs, cJSON *node) { - cJSON *r = cJSON_GetObjectItemCaseSensitive(node, "from_row"); - cJSON *c = cJSON_GetObjectItemCaseSensitive(node, "from_column"); - if (r) cs->cur_line = (int)r->valuedouble + 1; - if (c) cs->cur_col = (int)c->valuedouble + 1; -} - -static void mach_emit(MachCompState *cs, MachInstr32 instr) { - if (cs->code_count >= cs->code_capacity) { - int new_cap = cs->code_capacity ? cs->code_capacity * 2 : 64; - cs->code = sys_realloc(cs->code, new_cap * sizeof(MachInstr32)); - cs->code_capacity = new_cap; - } - if (cs->code_count >= cs->line_capacity) { - int new_cap = cs->line_capacity ? cs->line_capacity * 2 : 64; - cs->line_info = sys_realloc(cs->line_info, new_cap * sizeof(MachLineEntry)); - cs->line_capacity = new_cap; - } - cs->line_info[cs->code_count] = (MachLineEntry){cs->cur_line, cs->cur_col}; - cs->code[cs->code_count++] = instr; -} - -static int mach_current_pc(MachCompState *cs) { - return cs->code_count; -} - -/* Reserve a register at freereg */ -static int mach_reserve_reg(MachCompState *cs) { - int r = cs->freereg++; - if (cs->freereg > cs->maxreg) cs->maxreg = cs->freereg; - return r; -} - -/* Free temporary registers back to a saved freereg level */ -static void mach_free_reg_to(MachCompState *cs, int saved) { - cs->freereg = saved; -} - -/* Add an integer constant to the pool, return its index */ -static int mach_cpool_add_int(MachCompState *cs, int32_t val) { - for (int i = 0; i < cs->cpool_count; i++) { - MachCPoolEntry *e = &cs->cpool[i]; - if (e->type == MACH_CP_INT && e->ival == val) return i; - } - if (cs->cpool_count >= cs->cpool_capacity) { - int new_cap = cs->cpool_capacity ? cs->cpool_capacity * 2 : 16; - cs->cpool = sys_realloc(cs->cpool, new_cap * sizeof(MachCPoolEntry)); - cs->cpool_capacity = new_cap; - } - cs->cpool[cs->cpool_count] = (MachCPoolEntry){ .type = MACH_CP_INT, .ival = val }; - return cs->cpool_count++; -} - -/* Add a float constant to the pool, return its index */ -static int mach_cpool_add_float(MachCompState *cs, double val) { - for (int i = 0; i < cs->cpool_count; i++) { - MachCPoolEntry *e = &cs->cpool[i]; - if (e->type == MACH_CP_FLOAT && e->fval == val) return i; - } - if (cs->cpool_count >= cs->cpool_capacity) { - int new_cap = cs->cpool_capacity ? cs->cpool_capacity * 2 : 16; - cs->cpool = sys_realloc(cs->cpool, new_cap * sizeof(MachCPoolEntry)); - cs->cpool_capacity = new_cap; - } - cs->cpool[cs->cpool_count] = (MachCPoolEntry){ .type = MACH_CP_FLOAT, .fval = val }; - return cs->cpool_count++; -} - -/* Add a string constant, return its cpool index */ -static int mach_cpool_add_str(MachCompState *cs, const char *str) { - /* Check for existing identical string */ - for (int i = 0; i < cs->cpool_count; i++) { - MachCPoolEntry *e = &cs->cpool[i]; - if (e->type == MACH_CP_STR && strcmp(e->str, str) == 0) - return i; - } - if (cs->cpool_count >= cs->cpool_capacity) { - int new_cap = cs->cpool_capacity ? cs->cpool_capacity * 2 : 16; - cs->cpool = sys_realloc(cs->cpool, new_cap * sizeof(MachCPoolEntry)); - cs->cpool_capacity = new_cap; - } - char *dup = sys_malloc(strlen(str) + 1); - memcpy(dup, str, strlen(str) + 1); - cs->cpool[cs->cpool_count] = (MachCPoolEntry){ .type = MACH_CP_STR, .str = dup }; - return cs->cpool_count++; -} - -/* Convert compile-time cpool entries to JSValue array for JSCodeRegister. - Caller takes ownership of the returned array. Frees the raw entries. - Strings are interned into stone memory (no GC allocation). */ -static JSValue *mach_materialize_cpool(JSContext *ctx, MachCPoolEntry *entries, int count) { - if (count == 0) { sys_free(entries); return NULL; } - JSValue *cpool = js_malloc_rt(count * sizeof(JSValue)); - for (int i = 0; i < count; i++) { - switch (entries[i].type) { - case MACH_CP_INT: - cpool[i] = JS_NewInt32(ctx, entries[i].ival); - break; - case MACH_CP_FLOAT: - cpool[i] = JS_NewFloat64(ctx, entries[i].fval); - break; - case MACH_CP_STR: - cpool[i] = js_key_new(ctx, entries[i].str); - break; - } - } - return cpool; -} - -/* Add a variable */ -static void mach_add_var(MachCompState *cs, const char *name, int slot, int is_const) { - if (cs->var_count >= cs->var_capacity) { - int new_cap = cs->var_capacity ? cs->var_capacity * 2 : 16; - cs->vars = sys_realloc(cs->vars, new_cap * sizeof(MachVarInfo)); - cs->var_capacity = new_cap; - } - MachVarInfo *v = &cs->vars[cs->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; - v->scope_depth = cs->scope_depth; -} - -/* Find a variable in the current scope */ -static int mach_find_var(MachCompState *cs, const char *name) { - for (int i = cs->var_count - 1; i >= 0; i--) { - if (strcmp(cs->vars[i].name, name) == 0) - return cs->vars[i].slot; - } - return -1; -} - -/* Add a nested function, return its index */ -static int mach_add_function(MachCompState *cs, MachCode *fn) { - if (cs->func_count >= cs->func_capacity) { - int new_cap = cs->func_capacity ? cs->func_capacity * 2 : 4; - cs->functions = sys_realloc(cs->functions, new_cap * sizeof(MachCode*)); - cs->func_capacity = new_cap; - } - cs->functions[cs->func_count] = fn; - return cs->func_count++; -} - -/* Find the scope record for a given function_nr in the scopes array */ -static cJSON *mach_find_scope_record(cJSON *scopes, int function_nr) { - if (!scopes) return NULL; - int count = cJSON_GetArraySize(scopes); - for (int i = 0; i < count; i++) { - cJSON *scope = cJSON_GetArrayItem(scopes, i); - cJSON *fn_nr = cJSON_GetObjectItemCaseSensitive(scope, "function_nr"); - if (fn_nr && (int)cJSON_GetNumberValue(fn_nr) == function_nr) - return scope; - } - return NULL; -} - -/* Scan AST scope record for variable declarations. - Variables are direct keys on the scope object with a "make" field. */ -static void mach_scan_scope(MachCompState *cs) { - cJSON *scope = mach_find_scope_record(cs->scopes, cs->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_find_var(cs, name) < 0) { - int is_const = (strcmp(make, "def") == 0 || strcmp(make, "function") == 0); - int slot = mach_reserve_reg(cs); - mach_add_var(cs, name, slot, is_const); - } - } -} - -/* ---- Expression compiler ---- */ - -/* Compile an expression into register dest. If dest < 0, allocate a temp. - Returns the register containing the result. */ -static int mach_compile_expr(MachCompState *cs, cJSON *node, int dest) { - if (!node) { - if (dest < 0) dest = mach_reserve_reg(cs); - mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0)); - return dest; - } - - mach_set_pos(cs, node); - const char *kind = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "kind")); - if (!kind) { - if (dest < 0) dest = mach_reserve_reg(cs); - mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0)); - return dest; - } - - /* Number literal */ - if (strcmp(kind, "number") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - cJSON *num = cJSON_GetObjectItemCaseSensitive(node, "number"); - if (num && cJSON_IsNumber(num)) { - double dval = num->valuedouble; - int ival = (int)dval; - if (dval == (double)ival && ival >= -32768 && ival <= 32767) { - /* Small integer: use LOADI */ - mach_emit(cs, MACH_AsBx(MACH_LOADI, dest, (int16_t)ival)); - } else { - /* Large number: use constant pool */ - int ki; - if (dval == (double)(int32_t)dval) - ki = mach_cpool_add_int(cs, (int32_t)dval); - else - ki = mach_cpool_add_float(cs, dval); - mach_emit(cs, MACH_ABx(MACH_LOADK, dest, ki)); - } - } else { - mach_emit(cs, MACH_AsBx(MACH_LOADI, dest, 0)); - } - return dest; - } - - /* String literal */ - if (strcmp(kind, "string") == 0 || strcmp(kind, "text") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - const char *val = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "value")); - if (val) { - int ki = mach_cpool_add_str(cs, val); - mach_emit(cs, MACH_ABx(MACH_LOADK, dest, ki)); - } else { - int ki = mach_cpool_add_str(cs, ""); - mach_emit(cs, MACH_ABx(MACH_LOADK, dest, ki)); - } - return dest; - } - - /* 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) { - if (dest < 0) dest = mach_reserve_reg(cs); - int save_freereg = cs->freereg; - - cJSON *list = cJSON_GetObjectItemCaseSensitive(node, "list"); - int nexpr = list ? cJSON_GetArraySize(list) : 0; - - /* Reserve consecutive regs for call: [format_fn, fmt_str, arr] */ - int call_base = mach_reserve_reg(cs); - int arg1_reg = mach_reserve_reg(cs); - int arg2_reg = mach_reserve_reg(cs); - - /* Reserve consecutive regs for NEWARRAY: arr_reg, then elem slots */ - int arr_base = mach_reserve_reg(cs); - for (int i = 0; i < nexpr; i++) mach_reserve_reg(cs); - /* Now arr_base+1..arr_base+nexpr are the element slots */ - - /* Compile expressions into arr_base+1..arr_base+nexpr */ - for (int i = 0; i < nexpr; i++) { - cJSON *expr = cJSON_GetArrayItem(list, i); - int slot = arr_base + 1 + i; - int r = mach_compile_expr(cs, expr, slot); - if (r != slot) mach_emit(cs, MACH_ABC(MACH_MOVE, slot, r, 0)); - } - - /* Create array from consecutive element regs */ - mach_emit(cs, MACH_ABC(MACH_NEWARRAY, arr_base, nexpr, 0)); - - /* Load format function */ - int ki_format = mach_cpool_add_str(cs, "format"); - mach_emit(cs, MACH_ABx(MACH_GETINTRINSIC, call_base, ki_format)); - - /* Load format string */ - const char *fmt = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "value")); - int ki_fmt = mach_cpool_add_str(cs, fmt ? fmt : ""); - mach_emit(cs, MACH_ABx(MACH_LOADK, arg1_reg, ki_fmt)); - - /* Move array to arg2 position */ - mach_emit(cs, MACH_ABC(MACH_MOVE, arg2_reg, arr_base, 0)); - - /* Call format(fmt, arr) */ - mach_emit(cs, MACH_ABC(MACH_CALL, call_base, 2, 1)); - - mach_free_reg_to(cs, save_freereg); - if (dest != call_base) - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, call_base, 0)); - return dest; - } - - /* Boolean/null literals */ - if (strcmp(kind, "true") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - mach_emit(cs, MACH_ABx(MACH_LOADTRUE, dest, 0)); - return dest; - } - if (strcmp(kind, "false") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - mach_emit(cs, MACH_ABx(MACH_LOADFALSE, dest, 0)); - return dest; - } - if (strcmp(kind, "null") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0)); - return dest; - } - - /* Name (variable reference) */ - if (strcmp(kind, "name") == 0) { - const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "name")); - if (name) { - cJSON *level_node = cJSON_GetObjectItemCaseSensitive(node, "level"); - int level = level_node ? (int)cJSON_GetNumberValue(level_node) : -1; - - if (level == 0) { - /* Local variable */ - int slot = mach_find_var(cs, name); - if (slot >= 0) { - if (dest >= 0 && dest != slot) { - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, slot, 0)); - return dest; - } - return slot; - } - } else if (level > 0) { - /* Closure variable — walk parent compiler states for slot */ - MachCompState *target = cs; - for (int i = 0; i < level; i++) target = target->parent; - int slot = mach_find_var(target, name); - if (dest < 0) dest = mach_reserve_reg(cs); - mach_emit(cs, MACH_ABC(MACH_GETUP, dest, level, slot)); - return dest; - } - /* Unbound or fallback — emit placeholder, patched at link time */ - if (dest < 0) dest = mach_reserve_reg(cs); - int ki = mach_cpool_add_str(cs, name); - mach_emit(cs, MACH_ABx(MACH_GETNAME, dest, ki)); - return dest; - } - if (dest < 0) dest = mach_reserve_reg(cs); - mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0)); - return dest; - } - - /* Function call: kind="(" */ - if (strcmp(kind, "(") == 0) { - cJSON *fn_expr = cJSON_GetObjectItemCaseSensitive(node, "expression"); - cJSON *args = cJSON_GetObjectItemCaseSensitive(node, "list"); - int nargs = args ? cJSON_GetArraySize(args) : 0; - - /* Check if this is a method call: obj.method(args) or obj[key](args) */ - const char *fn_kind = NULL; - if (fn_expr) - fn_kind = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fn_expr, "kind")); - - /* Functino: inline operator call */ - if (fn_kind && strcmp(fn_kind, "name") == 0) { - const char *fn_make = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fn_expr, "make")); - if (fn_make && strcmp(fn_make, "functino") == 0) { - const char *fname = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fn_expr, "name")); - if (dest < 0) dest = mach_reserve_reg(cs); - - if (strcmp(fname, "~!") == 0) { - int save = cs->freereg; - int r = mach_compile_expr(cs, cJSON_GetArrayItem(args, 0), -1); - mach_emit(cs, MACH_ABC(MACH_BNOT, dest, r, 0)); - mach_free_reg_to(cs, save); - return dest; - } - if (strcmp(fname, "[]!") == 0) { - int save = cs->freereg; - int r0 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 0), -1); - if (cs->freereg <= r0) cs->freereg = r0 + 1; - int r1 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 1), -1); - mach_emit(cs, MACH_ABC(MACH_GETINDEX, dest, r0, r1)); - mach_free_reg_to(cs, save); - return dest; - } - if ((strcmp(fname, "=!") == 0 || strcmp(fname, "!=!") == 0) && nargs == 3) { - int save = cs->freereg; - int base = mach_reserve_reg(cs); - int r0 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 0), base); - if (r0 != base) mach_emit(cs, MACH_ABC(MACH_MOVE, base, r0, 0)); - int r1_reg = mach_reserve_reg(cs); - int r1 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 1), r1_reg); - if (r1 != r1_reg) mach_emit(cs, MACH_ABC(MACH_MOVE, r1_reg, r1, 0)); - int r2_reg = mach_reserve_reg(cs); - int r2 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 2), r2_reg); - if (r2 != r2_reg) mach_emit(cs, MACH_ABC(MACH_MOVE, r2_reg, r2, 0)); - MachOpcode top = (strcmp(fname, "=!") == 0) ? MACH_EQ_TOL : MACH_NEQ_TOL; - mach_emit(cs, MACH_ABC(top, dest, base, 3)); - mach_free_reg_to(cs, save); - return dest; - } - if (strcmp(fname, "&&!") == 0) { - int save = cs->freereg; - int r0 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 0), -1); - if (cs->freereg <= r0) cs->freereg = r0 + 1; - int r1 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 1), -1); - /* Non-short-circuiting: if left is falsy, result=left, else result=right */ - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, r0, 0)); - int jmp_pc = mach_current_pc(cs); - mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, dest, 0)); - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, r1, 0)); - { - int offset = mach_current_pc(cs) - (jmp_pc + 1); - cs->code[jmp_pc] = MACH_AsBx(MACH_GET_OP(cs->code[jmp_pc]), dest, (int16_t)offset); - } - mach_free_reg_to(cs, save); - return dest; - } - if (strcmp(fname, "||!") == 0) { - int save = cs->freereg; - int r0 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 0), -1); - if (cs->freereg <= r0) cs->freereg = r0 + 1; - int r1 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 1), -1); - /* Non-short-circuiting: if left is truthy, result=left, else result=right */ - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, r0, 0)); - int jmp_pc = mach_current_pc(cs); - mach_emit(cs, MACH_AsBx(MACH_JMPTRUE, dest, 0)); - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, r1, 0)); - { - int offset = mach_current_pc(cs) - (jmp_pc + 1); - cs->code[jmp_pc] = MACH_AsBx(MACH_GET_OP(cs->code[jmp_pc]), dest, (int16_t)offset); - } - mach_free_reg_to(cs, save); - return dest; - } - /* Standard 2-arg binary functino */ - { - int save = cs->freereg; - int r0 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 0), -1); - if (cs->freereg <= r0) cs->freereg = r0 + 1; - int r1 = mach_compile_expr(cs, cJSON_GetArrayItem(args, 1), -1); - MachOpcode op; - if (strcmp(fname, "+!") == 0) op = MACH_ADD; - else if (strcmp(fname, "-!") == 0) op = MACH_SUB; - else if (strcmp(fname, "*!") == 0) op = MACH_MUL; - else if (strcmp(fname, "/!") == 0) op = MACH_DIV; - else if (strcmp(fname, "%!") == 0) op = MACH_MOD; - else if (strcmp(fname, "**!") == 0) op = MACH_POW; - else if (strcmp(fname, "!") == 0) op = MACH_GT; - else if (strcmp(fname, "<=!") == 0) op = MACH_LE; - else if (strcmp(fname, ">=!") == 0) op = MACH_GE; - else if (strcmp(fname, "=!") == 0) op = MACH_EQ; - else if (strcmp(fname, "!=!") == 0) op = MACH_NEQ; - else if (strcmp(fname, "&!") == 0) op = MACH_BAND; - else if (strcmp(fname, "|!") == 0) op = MACH_BOR; - else if (strcmp(fname, "^!") == 0) op = MACH_BXOR; - else if (strcmp(fname, "<>!") == 0) op = MACH_SHR; - else op = MACH_USHR; /* >>>! */ - mach_emit(cs, MACH_ABC(op, dest, r0, r1)); - mach_free_reg_to(cs, save); - return dest; - } - } - } - - if (fn_kind && strcmp(fn_kind, ".") == 0) { - /* Method call with dot notation: obj.method(args) */ - int save_freereg = cs->freereg; - int base = mach_reserve_reg(cs); /* R(base) = obj */ - if (dest < 0) dest = base; - mach_reserve_reg(cs); /* R(base+1) = temp slot for VM */ - - /* Compile obj into base */ - cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(fn_expr, "expression"); - if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(fn_expr, "left"); - int obj_r = mach_compile_expr(cs, obj_expr, base); - if (obj_r != base) - mach_emit(cs, MACH_ABC(MACH_MOVE, base, obj_r, 0)); - - /* Extract property name */ - cJSON *prop = cJSON_GetObjectItemCaseSensitive(fn_expr, "name"); - if (!prop) prop = cJSON_GetObjectItemCaseSensitive(fn_expr, "right"); - const char *prop_name = NULL; - if (cJSON_IsString(prop)) prop_name = cJSON_GetStringValue(prop); - else if (prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "value")); - if (!prop_name && prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "name")); - if (!prop_name) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fn_expr, "value")); - if (!prop_name) prop_name = "unknown"; - - int ki = mach_cpool_add_str(cs, prop_name); - - /* Compile args into R(base+2)..R(base+1+nargs) */ - for (int i = 0; i < nargs; i++) { - int arg_reg = mach_reserve_reg(cs); - cJSON *arg = cJSON_GetArrayItem(args, i); - int r = mach_compile_expr(cs, arg, arg_reg); - if (r != arg_reg) - mach_emit(cs, MACH_ABC(MACH_MOVE, arg_reg, r, 0)); - } - - mach_emit(cs, MACH_ABC(MACH_CALLMETHOD, base, nargs, ki)); - mach_free_reg_to(cs, save_freereg); - if (dest >= 0 && dest != base) - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, base, 0)); - else - dest = base; - return dest; - } - - if (fn_kind && strcmp(fn_kind, "[") == 0) { - /* Method call with bracket notation: obj[expr](args) */ - int save_freereg = cs->freereg; - int base = mach_reserve_reg(cs); /* R(base) = obj */ - if (dest < 0) dest = base; - int key_reg = mach_reserve_reg(cs); /* R(base+1) = key */ - - /* Compile obj into base */ - cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(fn_expr, "expression"); - if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(fn_expr, "left"); - int obj_r = mach_compile_expr(cs, obj_expr, base); - if (obj_r != base) - mach_emit(cs, MACH_ABC(MACH_MOVE, base, obj_r, 0)); - - /* Compile key expr into R(base+1) */ - cJSON *idx_expr = cJSON_GetObjectItemCaseSensitive(fn_expr, "index"); - if (!idx_expr) idx_expr = cJSON_GetObjectItemCaseSensitive(fn_expr, "right"); - int kr = mach_compile_expr(cs, idx_expr, key_reg); - if (kr != key_reg) - mach_emit(cs, MACH_ABC(MACH_MOVE, key_reg, kr, 0)); - - /* Compile args into R(base+2)..R(base+1+nargs) */ - for (int i = 0; i < nargs; i++) { - int arg_reg = mach_reserve_reg(cs); - cJSON *arg = cJSON_GetArrayItem(args, i); - int r = mach_compile_expr(cs, arg, arg_reg); - if (r != arg_reg) - mach_emit(cs, MACH_ABC(MACH_MOVE, arg_reg, r, 0)); - } - - /* C=0xFF signals key is in R(base+1) */ - mach_emit(cs, MACH_ABC(MACH_CALLMETHOD, base, nargs, 0xFF)); - mach_free_reg_to(cs, save_freereg); - if (dest >= 0 && dest != base) - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, base, 0)); - else - dest = base; - return dest; - } - - /* Save freereg so we can allocate consecutive regs for call */ - int save_freereg = cs->freereg; - - /* Allocate base register for the function */ - int base = mach_reserve_reg(cs); - if (dest < 0) dest = base; /* result goes to base */ - - /* Compile function expression into base */ - int fn_reg = mach_compile_expr(cs, fn_expr, base); - if (fn_reg != base) { - mach_emit(cs, MACH_ABC(MACH_MOVE, base, fn_reg, 0)); - } - - /* Allocate consecutive arg registers and compile args */ - for (int i = 0; i < nargs; i++) { - int arg_reg = mach_reserve_reg(cs); - cJSON *arg = cJSON_GetArrayItem(args, i); - int r = mach_compile_expr(cs, arg, arg_reg); - if (r != arg_reg) { - mach_emit(cs, MACH_ABC(MACH_MOVE, arg_reg, r, 0)); - } - } - - /* Emit CALL: base=func, B=nargs, C=1 if we want result */ - int keep = (dest >= 0) ? 1 : 0; - mach_emit(cs, MACH_ABC(MACH_CALL, base, nargs, keep)); - - /* Restore freereg */ - mach_free_reg_to(cs, save_freereg); - - /* If we want the result and dest != base, move it */ - if (dest >= 0 && dest != base) { - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, base, 0)); - } else { - dest = base; - } - return dest; - } - - /* Binary operators */ - if (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 || strcmp(kind, "|") == 0 || - strcmp(kind, "^") == 0 || strcmp(kind, "<<") == 0 || - strcmp(kind, ">>") == 0 || strcmp(kind, ">>>") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - - int save = cs->freereg; - cJSON *left = cJSON_GetObjectItemCaseSensitive(node, "left"); - cJSON *right = cJSON_GetObjectItemCaseSensitive(node, "right"); - - int lr = mach_compile_expr(cs, left, -1); - if (cs->freereg <= lr) cs->freereg = lr + 1; /* protect lr from reuse */ - int rr = mach_compile_expr(cs, right, -1); - - MachOpcode op; - if (strcmp(kind, "+") == 0) op = MACH_ADD; - else if (strcmp(kind, "-") == 0) op = MACH_SUB; - else if (strcmp(kind, "*") == 0) op = MACH_MUL; - else if (strcmp(kind, "/") == 0) op = MACH_DIV; - else if (strcmp(kind, "%") == 0) op = MACH_MOD; - else if (strcmp(kind, "**") == 0) op = MACH_POW; - else if (strcmp(kind, "==") == 0 || strcmp(kind, "===") == 0) op = MACH_EQ; - else if (strcmp(kind, "!=") == 0 || strcmp(kind, "!==") == 0) op = MACH_NEQ; - else if (strcmp(kind, "<") == 0) op = MACH_LT; - else if (strcmp(kind, "<=") == 0) op = MACH_LE; - else if (strcmp(kind, ">") == 0) op = MACH_GT; - else if (strcmp(kind, ">=") == 0) op = MACH_GE; - else if (strcmp(kind, "&") == 0) op = MACH_BAND; - else if (strcmp(kind, "|") == 0) op = MACH_BOR; - else if (strcmp(kind, "^") == 0) op = MACH_BXOR; - else if (strcmp(kind, "<<") == 0) op = MACH_SHL; - else if (strcmp(kind, ">>") == 0) op = MACH_SHR; - else op = MACH_USHR; /* >>> */ - - mach_emit(cs, MACH_ABC(op, dest, lr, rr)); - mach_free_reg_to(cs, save); - return dest; - } - - /* Short-circuit logical operators */ - if (strcmp(kind, "&&") == 0 || strcmp(kind, "||") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - cJSON *left = cJSON_GetObjectItemCaseSensitive(node, "left"); - cJSON *right = cJSON_GetObjectItemCaseSensitive(node, "right"); - - int lr = mach_compile_expr(cs, left, dest); - if (lr != dest) - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, lr, 0)); - - /* Emit conditional jump — patch offset later */ - int jmp_pc = mach_current_pc(cs); - if (strcmp(kind, "&&") == 0) - mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, dest, 0)); /* skip right if false */ - else - mach_emit(cs, MACH_AsBx(MACH_JMPTRUE, dest, 0)); /* skip right if true */ - - int rr = mach_compile_expr(cs, right, dest); - if (rr != dest) - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, rr, 0)); - - /* Patch jump offset: target is current PC, offset relative to instruction after jmp */ - int offset = mach_current_pc(cs) - (jmp_pc + 1); - cs->code[jmp_pc] = MACH_AsBx(MACH_GET_OP(cs->code[jmp_pc]), dest, (int16_t)offset); - return dest; - } - - /* Unary operators */ - if (strcmp(kind, "!") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - cJSON *operand = cJSON_GetObjectItemCaseSensitive(node, "expression"); - if (!operand) operand = cJSON_GetObjectItemCaseSensitive(node, "right"); - int save = cs->freereg; - int r = mach_compile_expr(cs, operand, -1); - mach_emit(cs, MACH_ABC(MACH_LNOT, dest, r, 0)); - mach_free_reg_to(cs, save); - return dest; - } - if (strcmp(kind, "~") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - cJSON *operand = cJSON_GetObjectItemCaseSensitive(node, "expression"); - if (!operand) operand = cJSON_GetObjectItemCaseSensitive(node, "right"); - int save = cs->freereg; - int r = mach_compile_expr(cs, operand, -1); - mach_emit(cs, MACH_ABC(MACH_BNOT, dest, r, 0)); - mach_free_reg_to(cs, save); - return dest; - } - if (strcmp(kind, "unary_-") == 0 || strcmp(kind, "-unary") == 0 || (strcmp(kind, "-") == 0 && !cJSON_GetObjectItemCaseSensitive(node, "left"))) { - if (dest < 0) dest = mach_reserve_reg(cs); - cJSON *operand = cJSON_GetObjectItemCaseSensitive(node, "expression"); - if (!operand) operand = cJSON_GetObjectItemCaseSensitive(node, "right"); - int save = cs->freereg; - int r = mach_compile_expr(cs, operand, -1); - mach_emit(cs, MACH_ABC(MACH_NEG, dest, r, 0)); - mach_free_reg_to(cs, save); - return dest; - } - - /* Unary plus: identity for numbers */ - if (strcmp(kind, "+unary") == 0 || strcmp(kind, "pos") == 0) { - cJSON *operand = cJSON_GetObjectItemCaseSensitive(node, "expression"); - return mach_compile_expr(cs, operand, dest); - } - - /* Comma operator: compile left for side effects, return right */ - if (strcmp(kind, ",") == 0) { - cJSON *left = cJSON_GetObjectItemCaseSensitive(node, "left"); - cJSON *right = cJSON_GetObjectItemCaseSensitive(node, "right"); - int save = cs->freereg; - mach_compile_expr(cs, left, -1); - mach_free_reg_to(cs, save); - return mach_compile_expr(cs, right, dest); - } - - /* Increment/Decrement as expression */ - if (strcmp(kind, "++") == 0 || strcmp(kind, "--") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - MachOpcode inc_op = (kind[0] == '+') ? MACH_INC : MACH_DEC; - cJSON *operand = cJSON_GetObjectItemCaseSensitive(node, "expression"); - cJSON *postfix_node = cJSON_GetObjectItemCaseSensitive(node, "postfix"); - int is_postfix = postfix_node && cJSON_IsTrue(postfix_node); - - const char *op_kind = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(operand, "kind")); - if (op_kind && strcmp(op_kind, "name") == 0) { - const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(operand, "name")); - cJSON *level_node = cJSON_GetObjectItemCaseSensitive(operand, "level"); - int level = level_node ? (int)cJSON_GetNumberValue(level_node) : -1; - if (level == 0 && name) { - int slot = mach_find_var(cs, name); - if (slot >= 0) { - if (is_postfix) { - /* Return old value, then increment */ - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, slot, 0)); - mach_emit(cs, MACH_ABC(inc_op, slot, slot, 0)); - } else { - /* Increment, then return new value */ - mach_emit(cs, MACH_ABC(inc_op, slot, slot, 0)); - if (dest != slot) - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, slot, 0)); - } - return dest; - } - } - } - /* Fallback: just compile operand */ - return mach_compile_expr(cs, operand, dest); - } - - /* Compound assignment operators */ - if (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) { - if (dest < 0) dest = mach_reserve_reg(cs); - cJSON *left = cJSON_GetObjectItemCaseSensitive(node, "left"); - cJSON *right = cJSON_GetObjectItemCaseSensitive(node, "right"); - - /* Map compound op to binary op */ - MachOpcode binop; - if (strcmp(kind, "+=") == 0) binop = MACH_ADD; - else if (strcmp(kind, "-=") == 0) binop = MACH_SUB; - else if (strcmp(kind, "*=") == 0) binop = MACH_MUL; - else if (strcmp(kind, "/=") == 0) binop = MACH_DIV; - else if (strcmp(kind, "%=") == 0) binop = MACH_MOD; - else if (strcmp(kind, "**=") == 0) binop = MACH_POW; - else if (strcmp(kind, "&=") == 0) binop = MACH_BAND; - else if (strcmp(kind, "|=") == 0) binop = MACH_BOR; - else if (strcmp(kind, "^=") == 0) binop = MACH_BXOR; - else if (strcmp(kind, "<<=") == 0) binop = MACH_SHL; - else if (strcmp(kind, ">>=") == 0) binop = MACH_SHR; - else binop = MACH_USHR; /* >>>= */ - - const char *lk = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(left, "kind")); - if (lk && strcmp(lk, "name") == 0) { - const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(left, "name")); - cJSON *level_node = cJSON_GetObjectItemCaseSensitive(left, "level"); - int level = level_node ? (int)cJSON_GetNumberValue(level_node) : -1; - if (level == 0 && name) { - int slot = mach_find_var(cs, name); - if (slot >= 0) { - int save = cs->freereg; - int rr = mach_compile_expr(cs, right, -1); - mach_emit(cs, MACH_ABC(binop, slot, slot, rr)); - mach_free_reg_to(cs, save); - if (dest != slot) - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, slot, 0)); - return dest; - } - } - } - /* Fallback: load null */ - mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0)); - return dest; - } - - /* In operator */ - if (strcmp(kind, "in") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - cJSON *left = cJSON_GetObjectItemCaseSensitive(node, "left"); - cJSON *right = cJSON_GetObjectItemCaseSensitive(node, "right"); - int save = cs->freereg; - int lr = mach_compile_expr(cs, left, -1); - if (cs->freereg <= lr) cs->freereg = lr + 1; - int rr = mach_compile_expr(cs, right, -1); - mach_emit(cs, MACH_ABC(MACH_HASPROP, dest, rr, lr)); - mach_free_reg_to(cs, save); - return dest; - } - - /* Assignment */ - if (strcmp(kind, "assign") == 0) { - cJSON *left = cJSON_GetObjectItemCaseSensitive(node, "left"); - cJSON *right = cJSON_GetObjectItemCaseSensitive(node, "right"); - - /* Push: arr[] = val */ - cJSON *push_node = cJSON_GetObjectItemCaseSensitive(node, "push"); - if (push_node && cJSON_IsTrue(push_node)) { - if (dest < 0) dest = mach_reserve_reg(cs); - int save = cs->freereg; - cJSON *arr_expr = cJSON_GetObjectItemCaseSensitive(left, "left"); - if (!arr_expr) arr_expr = cJSON_GetObjectItemCaseSensitive(left, "expression"); - int arr_r = mach_compile_expr(cs, arr_expr, -1); - int val_r = mach_compile_expr(cs, right, -1); - mach_emit(cs, MACH_ABC(MACH_PUSH, arr_r, val_r, 0)); - if (dest >= 0) - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, val_r, 0)); - mach_free_reg_to(cs, save); - return dest; - } - - const char *lk = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(left, "kind")); - - if (lk && strcmp(lk, "name") == 0) { - const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(left, "name")); - cJSON *level_node = cJSON_GetObjectItemCaseSensitive(left, "level"); - int level = level_node ? (int)cJSON_GetNumberValue(level_node) : -1; - - if (level == 0) { - /* Local assignment */ - int slot = name ? mach_find_var(cs, name) : -1; - if (slot >= 0) { - int r = mach_compile_expr(cs, right, slot); - if (r != slot) - mach_emit(cs, MACH_ABC(MACH_MOVE, slot, r, 0)); - if (dest >= 0 && dest != slot) - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, slot, 0)); - return slot; - } - } else if (level > 0) { - /* Closure assignment — walk parent states for slot */ - MachCompState *target = cs; - for (int i = 0; i < level; i++) target = target->parent; - int slot = mach_find_var(target, name); - if (dest < 0) dest = mach_reserve_reg(cs); - int r = mach_compile_expr(cs, right, dest); - if (r != dest) - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, r, 0)); - mach_emit(cs, MACH_ABC(MACH_SETUP, dest, level, slot)); - return dest; - } - /* Unbound (level -1) — error, AST parser should have rejected this */ - if (dest < 0) dest = mach_reserve_reg(cs); - mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0)); - return dest; - } - - /* Property assignment: left kind="." */ - if (lk && strcmp(lk, ".") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - int save = cs->freereg; - cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(left, "expression"); - if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(left, "left"); - cJSON *prop = cJSON_GetObjectItemCaseSensitive(left, "name"); - if (!prop) prop = cJSON_GetObjectItemCaseSensitive(left, "right"); - const char *prop_name = NULL; - if (cJSON_IsString(prop)) prop_name = cJSON_GetStringValue(prop); - else if (prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "value")); - if (!prop_name && prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "name")); - if (!prop_name) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(left, "value")); - - int obj_r = mach_compile_expr(cs, obj_expr, -1); - if (cs->freereg <= obj_r) cs->freereg = obj_r + 1; - int val_r = mach_compile_expr(cs, right, dest); - if (prop_name) { - int ki = mach_cpool_add_str(cs, prop_name); - mach_emit(cs, MACH_ABC(MACH_SETFIELD, obj_r, ki, val_r)); - } - mach_free_reg_to(cs, save); - return val_r; - } - - /* Computed property assignment: left kind="[" */ - if (lk && strcmp(lk, "[") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - int save = cs->freereg; - cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(left, "expression"); - if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(left, "left"); - cJSON *idx_expr = cJSON_GetObjectItemCaseSensitive(left, "index"); - if (!idx_expr) idx_expr = cJSON_GetObjectItemCaseSensitive(left, "right"); - - int obj_r = mach_compile_expr(cs, obj_expr, -1); - if (cs->freereg <= obj_r) cs->freereg = obj_r + 1; - int idx_r = mach_compile_expr(cs, idx_expr, -1); - if (cs->freereg <= idx_r) cs->freereg = idx_r + 1; - int val_r = mach_compile_expr(cs, right, dest); - mach_emit(cs, MACH_ABC(MACH_SETINDEX, obj_r, idx_r, val_r)); - mach_free_reg_to(cs, save); - return val_r; - } - - /* Fallback */ - if (dest < 0) dest = mach_reserve_reg(cs); - mach_compile_expr(cs, right, dest); - return dest; - } - - /* Property access: kind="." */ - if (strcmp(kind, ".") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - int save = cs->freereg; - cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(node, "expression"); - if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(node, "left"); - cJSON *prop = cJSON_GetObjectItemCaseSensitive(node, "name"); - if (!prop) prop = cJSON_GetObjectItemCaseSensitive(node, "right"); - const char *prop_name = NULL; - if (cJSON_IsString(prop)) prop_name = cJSON_GetStringValue(prop); - else if (prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "value")); - if (!prop_name && prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop, "name")); - if (!prop_name) prop_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "value")); - - int obj_r = mach_compile_expr(cs, obj_expr, -1); - if (prop_name) { - int ki = mach_cpool_add_str(cs, prop_name); - mach_emit(cs, MACH_ABC(MACH_GETFIELD, dest, obj_r, ki)); - } - mach_free_reg_to(cs, save); - return dest; - } - - /* Computed property access: kind="[" */ - if (strcmp(kind, "[") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - int save = cs->freereg; - cJSON *obj_expr = cJSON_GetObjectItemCaseSensitive(node, "expression"); - if (!obj_expr) obj_expr = cJSON_GetObjectItemCaseSensitive(node, "left"); - cJSON *idx_expr = cJSON_GetObjectItemCaseSensitive(node, "index"); - if (!idx_expr) idx_expr = cJSON_GetObjectItemCaseSensitive(node, "right"); - - int obj_r = mach_compile_expr(cs, obj_expr, -1); - if (cs->freereg <= obj_r) cs->freereg = obj_r + 1; - int idx_r = mach_compile_expr(cs, idx_expr, -1); - mach_emit(cs, MACH_ABC(MACH_GETINDEX, dest, obj_r, idx_r)); - mach_free_reg_to(cs, save); - return dest; - } - - /* Object literal: kind="object" or "record" */ - if (strcmp(kind, "object") == 0 || strcmp(kind, "record") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - mach_emit(cs, MACH_ABC(MACH_NEWOBJECT, dest, 0, 0)); - cJSON *props = cJSON_GetObjectItemCaseSensitive(node, "list"); - if (props) { - int count = cJSON_GetArraySize(props); - for (int i = 0; i < count; i++) { - cJSON *prop = cJSON_GetArrayItem(props, i); - cJSON *key_node = cJSON_GetObjectItemCaseSensitive(prop, "key"); - if (!key_node) key_node = cJSON_GetObjectItemCaseSensitive(prop, "left"); - cJSON *val_node = cJSON_GetObjectItemCaseSensitive(prop, "value"); - if (!val_node) val_node = cJSON_GetObjectItemCaseSensitive(prop, "right"); - if (!val_node) val_node = cJSON_GetObjectItemCaseSensitive(prop, "expression"); - const char *key = cJSON_GetStringValue(key_node); - if (!key && key_node) key = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(key_node, "value")); - if (!key && key_node) key = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(key_node, "name")); - if (key && val_node) { - int save = cs->freereg; - int vr = mach_compile_expr(cs, val_node, -1); - int ki = mach_cpool_add_str(cs, key); - mach_emit(cs, MACH_ABC(MACH_SETFIELD, dest, ki, vr)); - mach_free_reg_to(cs, save); - } - } - } - return dest; - } - - /* Array literal: kind="array" */ - if (strcmp(kind, "array") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - cJSON *elems = cJSON_GetObjectItemCaseSensitive(node, "list"); - int count = elems ? cJSON_GetArraySize(elems) : 0; - - /* Reserve consecutive regs for elements starting at arr_base+1. - If dest is below freereg, other temps occupy dest+1..freereg-1 - so we must use a fresh base to avoid clobbering them. */ - int save = cs->freereg; - int arr_base; - if (dest + 1 >= cs->freereg) { - arr_base = dest; - cs->freereg = dest + 1; - } else { - arr_base = mach_reserve_reg(cs); - } - for (int i = 0; i < count; i++) { - int er = mach_reserve_reg(cs); - cJSON *elem = cJSON_GetArrayItem(elems, i); - int r = mach_compile_expr(cs, elem, er); - if (r != er) mach_emit(cs, MACH_ABC(MACH_MOVE, er, r, 0)); - } - mach_emit(cs, MACH_ABC(MACH_NEWARRAY, arr_base, count, 0)); - if (arr_base != dest) - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, arr_base, 0)); - mach_free_reg_to(cs, save); - return dest; - } - - /* Ternary: kind="?" or "then" */ - if (strcmp(kind, "?") == 0 || strcmp(kind, "then") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - cJSON *cond = cJSON_GetObjectItemCaseSensitive(node, "expression"); - if (!cond) cond = cJSON_GetObjectItemCaseSensitive(node, "condition"); - cJSON *then_expr = cJSON_GetObjectItemCaseSensitive(node, "then"); - if (!then_expr) then_expr = cJSON_GetObjectItemCaseSensitive(node, "left"); - cJSON *else_expr = cJSON_GetObjectItemCaseSensitive(node, "else"); - if (!else_expr) else_expr = cJSON_GetObjectItemCaseSensitive(node, "right"); - - int save = cs->freereg; - int cr = mach_compile_expr(cs, cond, -1); - int jmpfalse_pc = mach_current_pc(cs); - mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, cr, 0)); - mach_free_reg_to(cs, save); - - mach_compile_expr(cs, then_expr, dest); - int jmpend_pc = mach_current_pc(cs); - mach_emit(cs, MACH_sJ(MACH_JMP, 0)); - - /* Patch jmpfalse */ - int offset = mach_current_pc(cs) - (jmpfalse_pc + 1); - cs->code[jmpfalse_pc] = MACH_AsBx(MACH_JMPFALSE, cr, (int16_t)offset); - - mach_compile_expr(cs, else_expr, dest); - - /* Patch jmpend */ - offset = mach_current_pc(cs) - (jmpend_pc + 1); - cs->code[jmpend_pc] = MACH_sJ(MACH_JMP, offset); - return dest; - } - - /* Function literal */ - if (strcmp(kind, "function") == 0 || strcmp(kind, "=>") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - /* Compile nested function */ - MachCompState child = {0}; - child.parent = cs; - child.scopes = cs->scopes; - child.filename = cs->filename; - child.freereg = 1; /* slot 0 = this */ - - /* Read function_nr from AST node */ - cJSON *fn_nr_node = cJSON_GetObjectItemCaseSensitive(node, "function_nr"); - child.function_nr = fn_nr_node ? (int)cJSON_GetNumberValue(fn_nr_node) : 0; - - /* Register parameters */ - cJSON *params = cJSON_GetObjectItemCaseSensitive(node, "params"); - if (!params) params = cJSON_GetObjectItemCaseSensitive(node, "parameters"); - if (!params) params = cJSON_GetObjectItemCaseSensitive(node, "list"); - int nparams = params ? cJSON_GetArraySize(params) : 0; - child.nr_args = nparams; - for (int i = 0; i < nparams; i++) { - cJSON *p = cJSON_GetArrayItem(params, i); - const char *pname = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(p, "name")); - if (!pname) pname = cJSON_GetStringValue(p); - if (pname) { - int slot = mach_reserve_reg(&child); - mach_add_var(&child, pname, slot, 1); - } - } - - /* Scan scope record for var/def declarations */ - mach_scan_scope(&child); - - /* Emit default parameter initialization */ - for (int i = 0; i < nparams; i++) { - cJSON *p = cJSON_GetArrayItem(params, i); - cJSON *default_expr = cJSON_GetObjectItemCaseSensitive(p, "expression"); - if (default_expr) { - int slot = 1 + i; /* param slots start at 1 (slot 0 = this) */ - /* If param is null, skip the JMP and fall into default code */ - mach_emit(&child, MACH_AsBx(MACH_JMPNULL, slot, 1)); - /* If param is NOT null, jump past the default code */ - int jmp_pc = mach_current_pc(&child); - mach_emit(&child, MACH_sJ(MACH_JMP, 0)); /* placeholder */ - - int save = child.freereg; - mach_compile_expr(&child, default_expr, slot); - child.freereg = save; - - /* Patch JMP offset */ - int offset = mach_current_pc(&child) - (jmp_pc + 1); - child.code[jmp_pc] = MACH_sJ(MACH_JMP, offset); - } - } - - /* Compile body */ - cJSON *body = cJSON_GetObjectItemCaseSensitive(node, "body"); - if (!body) body = node; /* statements may be directly on the function node */ - { - cJSON *stmts = cJSON_GetObjectItemCaseSensitive(body, "statements"); - if (!stmts) stmts = body; /* body might be the statements array directly */ - if (cJSON_IsArray(stmts)) { - int count = cJSON_GetArraySize(stmts); - for (int i = 0; i < count; i++) { - mach_compile_stmt(&child, cJSON_GetArrayItem(stmts, i)); - } - } - } - - /* Implicit return null */ - mach_emit(&child, MACH_ABC(MACH_RETNIL, 0, 0, 0)); - - /* Disruption clause — emitted after body, recorded as disruption_pc */ - int disruption_start = 0; - cJSON *disruption = cJSON_GetObjectItemCaseSensitive(node, "disruption"); - if (disruption && cJSON_IsArray(disruption)) { - disruption_start = mach_current_pc(&child); - int dcount = cJSON_GetArraySize(disruption); - for (int i = 0; i < dcount; i++) - mach_compile_stmt(&child, cJSON_GetArrayItem(disruption, i)); - mach_emit(&child, MACH_ABC(MACH_RETNIL, 0, 0, 0)); - } - - /* Build MachCode for the child function */ - cJSON *fn_scope = mach_find_scope_record(cs->scopes, child.function_nr); - cJSON *fn_ncs = fn_scope ? cJSON_GetObjectItemCaseSensitive(fn_scope, "nr_close_slots") : NULL; - MachCode *fn_code = sys_malloc(sizeof(MachCode)); - memset(fn_code, 0, sizeof(MachCode)); - fn_code->arity = nparams; - fn_code->nr_slots = child.maxreg; - fn_code->nr_close_slots = fn_ncs ? (int)cJSON_GetNumberValue(fn_ncs) : 0; - fn_code->entry_point = 0; - fn_code->instr_count = child.code_count; - fn_code->instructions = child.code; - fn_code->cpool_count = child.cpool_count; - fn_code->cpool = child.cpool; - fn_code->func_count = child.func_count; - fn_code->functions = child.functions; - fn_code->line_table = child.line_info; - fn_code->filename = cs->filename ? strdup(cs->filename) : NULL; - fn_code->disruption_pc = disruption_start; - - cJSON *fname = cJSON_GetObjectItemCaseSensitive(node, "name"); - if (fname && cJSON_IsString(fname)) { - const char *ns = cJSON_GetStringValue(fname); - fn_code->name = sys_malloc(strlen(ns) + 1); - strcpy(fn_code->name, ns); - } else { - fn_code->name = NULL; - } - - /* Free child var table (not code/cpool, those are owned by fn_code now) */ - for (int i = 0; i < child.var_count; i++) - sys_free(child.vars[i].name); - sys_free(child.vars); - - int fi = mach_add_function(cs, fn_code); - mach_emit(cs, MACH_ABx(MACH_CLOSURE, dest, fi)); - return dest; - } - - /* Delete operator */ - if (strcmp(kind, "delete") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - cJSON *operand = cJSON_GetObjectItemCaseSensitive(node, "expression"); - if (!operand) operand = cJSON_GetObjectItemCaseSensitive(node, "right"); - if (operand) { - const char *okind = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(operand, "kind")); - if (okind && strcmp(okind, ".") == 0) { - /* delete obj.prop */ - cJSON *obj_node = cJSON_GetObjectItemCaseSensitive(operand, "left"); - cJSON *prop_node = cJSON_GetObjectItemCaseSensitive(operand, "right"); - const char *pname = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(prop_node, "name")); - if (!pname) pname = cJSON_GetStringValue(prop_node); - int save = cs->freereg; - int objr = mach_compile_expr(cs, obj_node, -1); - int ki = mach_cpool_add_str(cs, pname); - mach_emit(cs, MACH_ABC(MACH_DELETE, dest, objr, ki)); - mach_free_reg_to(cs, save); - return dest; - } else if (okind && strcmp(okind, "[") == 0) { - /* delete obj[expr] */ - cJSON *obj_node = cJSON_GetObjectItemCaseSensitive(operand, "left"); - cJSON *idx_node = cJSON_GetObjectItemCaseSensitive(operand, "right"); - int save = cs->freereg; - int objr = mach_compile_expr(cs, obj_node, -1); - int ir = mach_compile_expr(cs, idx_node, -1); - mach_emit(cs, MACH_ABC(MACH_DELETEINDEX, dest, objr, ir)); - mach_free_reg_to(cs, save); - return dest; - } - } - mach_emit(cs, MACH_ABx(MACH_LOADTRUE, dest, 0)); - return dest; - } - - /* This reference — slot 0 is always 'this' */ - if (strcmp(kind, "this") == 0) { - if (dest >= 0 && dest != 0) { - mach_emit(cs, MACH_ABC(MACH_MOVE, dest, 0, 0)); - return dest; - } - return 0; - } - - /* Regex literal */ - if (strcmp(kind, "regexp") == 0) { - if (dest < 0) dest = mach_reserve_reg(cs); - const char *pattern = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "pattern")); - const char *flags = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(node, "flags")); - if (!pattern) pattern = ""; - if (!flags) flags = ""; - int pi = mach_cpool_add_str(cs, pattern); - int fi = mach_cpool_add_str(cs, flags); - mach_emit(cs, MACH_ABC(MACH_REGEXP, dest, pi, fi)); - return dest; - } - - /* Fallback: unsupported expression kind — load null */ - if (dest < 0) dest = mach_reserve_reg(cs); - mach_emit(cs, MACH_ABx(MACH_LOADNULL, dest, 0)); - return dest; -} - -/* ---- Statement compiler ---- */ - -static void mach_compile_stmt(MachCompState *cs, cJSON *stmt) { - if (!stmt) return; - mach_set_pos(cs, stmt); - const char *kind = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(stmt, "kind")); - if (!kind) return; - - /* var / def declaration */ - 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")); - if (!name) return; - /* Check if var exists at current scope depth — if so, reuse it. - If it exists at a shallower depth, shadow it with a new slot. */ - int slot = -1; - for (int i = cs->var_count - 1; i >= 0; i--) { - if (strcmp(cs->vars[i].name, name) == 0) { - if (cs->vars[i].scope_depth == cs->scope_depth) { - slot = cs->vars[i].slot; /* same scope — reuse */ - } - break; - } - } - if (slot < 0) { - slot = mach_reserve_reg(cs); - mach_add_var(cs, name, slot, strcmp(kind, "def") == 0); - } - /* Pop: var x = arr[] */ - cJSON *pop_node = cJSON_GetObjectItemCaseSensitive(stmt, "pop"); - if (pop_node && cJSON_IsTrue(pop_node) && right) { - cJSON *arr_expr = cJSON_GetObjectItemCaseSensitive(right, "left"); - if (!arr_expr) arr_expr = cJSON_GetObjectItemCaseSensitive(right, "expression"); - int save = cs->freereg; - int arr_r = mach_compile_expr(cs, arr_expr, -1); - mach_emit(cs, MACH_ABC(MACH_POP, slot, arr_r, 0)); - mach_free_reg_to(cs, save); - return; - } - if (right) { - int r = mach_compile_expr(cs, right, slot); - if (r != slot) - mach_emit(cs, MACH_ABC(MACH_MOVE, slot, r, 0)); - } - return; - } - - /* var_list: multiple declarations in one statement */ - if (strcmp(kind, "var_list") == 0) { - cJSON *list = cJSON_GetObjectItemCaseSensitive(stmt, "list"); - if (list && cJSON_IsArray(list)) { - int count = cJSON_GetArraySize(list); - for (int i = 0; i < count; i++) - mach_compile_stmt(cs, cJSON_GetArrayItem(list, i)); - } - return; - } - - /* Function declaration statement */ - if (strcmp(kind, "function") == 0) { - const char *name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(stmt, "name")); - if (!name) return; - int slot = mach_find_var(cs, name); - if (slot < 0) { - slot = mach_reserve_reg(cs); - mach_add_var(cs, name, slot, 1); - } - mach_compile_expr(cs, stmt, slot); - return; - } - - /* Expression statement (call) */ - if (strcmp(kind, "call") == 0) { - cJSON *expr = cJSON_GetObjectItemCaseSensitive(stmt, "expression"); - if (expr) { - int save = cs->freereg; - mach_compile_expr(cs, expr, -1); - mach_free_reg_to(cs, save); - } - return; - } - - /* Return statement */ - if (strcmp(kind, "return") == 0) { - cJSON *expr = cJSON_GetObjectItemCaseSensitive(stmt, "expression"); - if (expr) { - int save = cs->freereg; - int r = mach_compile_expr(cs, expr, -1); - mach_emit(cs, MACH_ABC(MACH_RETURN, r, 0, 0)); - mach_free_reg_to(cs, save); - } else { - mach_emit(cs, MACH_ABC(MACH_RETNIL, 0, 0, 0)); - } - return; - } - - /* Block */ - if (strcmp(kind, "block") == 0) { - int saved_var_count = cs->var_count; - cs->scope_depth++; - cJSON *stmts = cJSON_GetObjectItemCaseSensitive(stmt, "statements"); - if (stmts && cJSON_IsArray(stmts)) { - int count = cJSON_GetArraySize(stmts); - for (int i = 0; i < count; i++) { - mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i)); - } - } - cs->scope_depth--; - for (int i = saved_var_count; i < cs->var_count; i++) - sys_free(cs->vars[i].name); - cs->var_count = saved_var_count; - return; - } - - /* If statement */ - if (strcmp(kind, "if") == 0) { - cJSON *cond = cJSON_GetObjectItemCaseSensitive(stmt, "expression"); - if (!cond) cond = cJSON_GetObjectItemCaseSensitive(stmt, "condition"); - cJSON *then_body = cJSON_GetObjectItemCaseSensitive(stmt, "then"); - if (!then_body) then_body = cJSON_GetObjectItemCaseSensitive(stmt, "block"); - cJSON *else_body = cJSON_GetObjectItemCaseSensitive(stmt, "else"); - - int save = cs->freereg; - int cr = mach_compile_expr(cs, cond, -1); - int jmpfalse_pc = mach_current_pc(cs); - mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, cr, 0)); - mach_free_reg_to(cs, save); - - /* Compile then branch — "then" is a direct array of statements */ - if (then_body) { - int saved_vc = cs->var_count; - cs->scope_depth++; - if (cJSON_IsArray(then_body)) { - int count = cJSON_GetArraySize(then_body); - for (int i = 0; i < count; i++) - mach_compile_stmt(cs, cJSON_GetArrayItem(then_body, i)); - } else { - cJSON *stmts = cJSON_GetObjectItemCaseSensitive(then_body, "statements"); - if (stmts && cJSON_IsArray(stmts)) { - int count = cJSON_GetArraySize(stmts); - for (int i = 0; i < count; i++) - mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i)); - } else { - mach_compile_stmt(cs, then_body); - } - } - cs->scope_depth--; - for (int i = saved_vc; i < cs->var_count; i++) - sys_free(cs->vars[i].name); - cs->var_count = saved_vc; - } - - /* Check for else-if chain ("list") or plain else */ - if (!else_body) { - cJSON *list = cJSON_GetObjectItemCaseSensitive(stmt, "list"); - if (list && cJSON_IsArray(list) && cJSON_GetArraySize(list) > 0) - else_body = cJSON_GetArrayItem(list, 0); - } - - if (else_body) { - int jmpend_pc = mach_current_pc(cs); - mach_emit(cs, MACH_sJ(MACH_JMP, 0)); - - /* Patch jmpfalse to else */ - int offset = mach_current_pc(cs) - (jmpfalse_pc + 1); - cs->code[jmpfalse_pc] = MACH_AsBx(MACH_JMPFALSE, cr, (int16_t)offset); - - /* Compile else — could be a direct array, object, or else-if stmt */ - int saved_vc = cs->var_count; - cs->scope_depth++; - if (cJSON_IsArray(else_body)) { - int count = cJSON_GetArraySize(else_body); - for (int i = 0; i < count; i++) - mach_compile_stmt(cs, cJSON_GetArrayItem(else_body, i)); - } else { - cJSON *stmts = cJSON_GetObjectItemCaseSensitive(else_body, "statements"); - if (stmts && cJSON_IsArray(stmts)) { - int count = cJSON_GetArraySize(stmts); - for (int i = 0; i < count; i++) - mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i)); - } else { - mach_compile_stmt(cs, else_body); - } - } - cs->scope_depth--; - for (int i = saved_vc; i < cs->var_count; i++) - sys_free(cs->vars[i].name); - cs->var_count = saved_vc; - - /* Patch jmpend */ - offset = mach_current_pc(cs) - (jmpend_pc + 1); - cs->code[jmpend_pc] = MACH_sJ(MACH_JMP, offset); - } else { - /* No else — patch jmpfalse to after then */ - int offset = mach_current_pc(cs) - (jmpfalse_pc + 1); - cs->code[jmpfalse_pc] = MACH_AsBx(MACH_JMPFALSE, cr, (int16_t)offset); - } - return; - } - - /* While loop */ - if (strcmp(kind, "while") == 0) { - cJSON *cond = cJSON_GetObjectItemCaseSensitive(stmt, "expression"); - if (!cond) cond = cJSON_GetObjectItemCaseSensitive(stmt, "condition"); - - int old_break = cs->loop_break; - int old_continue = cs->loop_continue; - cs->loop_break = -1; - cs->loop_continue = -1; - - int loop_top = mach_current_pc(cs); - - int save = cs->freereg; - int cr = mach_compile_expr(cs, cond, -1); - int jmpfalse_pc = mach_current_pc(cs); - mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, cr, 0)); - mach_free_reg_to(cs, save); - - /* Compile body — "statements" on a child "block"/"body", or directly on the node */ - { - int saved_vc = cs->var_count; - cs->scope_depth++; - cJSON *body = cJSON_GetObjectItemCaseSensitive(stmt, "block"); - if (!body) body = cJSON_GetObjectItemCaseSensitive(stmt, "body"); - cJSON *stmts = body ? cJSON_GetObjectItemCaseSensitive(body, "statements") : NULL; - if (!stmts) stmts = cJSON_GetObjectItemCaseSensitive(stmt, "statements"); - if (stmts && cJSON_IsArray(stmts)) { - int count = cJSON_GetArraySize(stmts); - for (int i = 0; i < count; i++) - mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i)); - } else if (body) { - mach_compile_stmt(cs, body); - } - cs->scope_depth--; - for (int i = saved_vc; i < cs->var_count; i++) - sys_free(cs->vars[i].name); - cs->var_count = saved_vc; - } - - /* Patch continue chain to loop_top */ - { - int cp = cs->loop_continue; - while (cp >= 0) { - int prev = MACH_GET_sJ(cs->code[cp]); - int off = loop_top - (cp + 1); - cs->code[cp] = MACH_sJ(MACH_JMP, off); - cp = prev; - } - } - - /* Jump back to loop top */ - int offset = loop_top - (mach_current_pc(cs) + 1); - mach_emit(cs, MACH_sJ(MACH_JMP, offset)); - - /* Patch jmpfalse to after loop */ - offset = mach_current_pc(cs) - (jmpfalse_pc + 1); - cs->code[jmpfalse_pc] = MACH_AsBx(MACH_JMPFALSE, cr, (int16_t)offset); - - /* Patch break chain */ - int bp = cs->loop_break; - while (bp >= 0) { - int prev = MACH_GET_sJ(cs->code[bp]); - offset = mach_current_pc(cs) - (bp + 1); - cs->code[bp] = MACH_sJ(MACH_JMP, offset); - bp = prev; - } - cs->loop_break = old_break; - cs->loop_continue = old_continue; - return; - } - - /* For loop */ - if (strcmp(kind, "for") == 0) { - int saved_vc = cs->var_count; - cs->scope_depth++; - cJSON *init = cJSON_GetObjectItemCaseSensitive(stmt, "init"); - cJSON *cond = cJSON_GetObjectItemCaseSensitive(stmt, "test"); - cJSON *update = cJSON_GetObjectItemCaseSensitive(stmt, "update"); - cJSON *body = cJSON_GetObjectItemCaseSensitive(stmt, "block"); - if (!body) body = cJSON_GetObjectItemCaseSensitive(stmt, "body"); - - int old_break = cs->loop_break; - int old_continue = cs->loop_continue; - cs->loop_break = -1; - cs->loop_continue = -1; - - /* Init */ - if (init) mach_compile_stmt(cs, init); - - int loop_top = mach_current_pc(cs); - - /* Condition */ - int jmpfalse_pc = -1; - if (cond) { - int save = cs->freereg; - int cr = mach_compile_expr(cs, cond, -1); - jmpfalse_pc = mach_current_pc(cs); - mach_emit(cs, MACH_AsBx(MACH_JMPFALSE, cr, 0)); - mach_free_reg_to(cs, save); - } - - /* Body — "statements" on a child "block"/"body", or directly on the for node */ - { - int body_vc = cs->var_count; - cs->scope_depth++; - cJSON *stmts = body ? cJSON_GetObjectItemCaseSensitive(body, "statements") : NULL; - if (!stmts) stmts = cJSON_GetObjectItemCaseSensitive(stmt, "statements"); - if (stmts && cJSON_IsArray(stmts)) { - int count = cJSON_GetArraySize(stmts); - for (int i = 0; i < count; i++) - mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i)); - } else if (body) { - mach_compile_stmt(cs, body); - } - cs->scope_depth--; - for (int i = body_vc; i < cs->var_count; i++) - sys_free(cs->vars[i].name); - cs->var_count = body_vc; - } - - /* Patch continue chain to update (or loop_top if no update) */ - { - int continue_target = mach_current_pc(cs); - int cp = cs->loop_continue; - while (cp >= 0) { - int prev = MACH_GET_sJ(cs->code[cp]); - int off = continue_target - (cp + 1); - cs->code[cp] = MACH_sJ(MACH_JMP, off); - cp = prev; - } - } - - /* Update — assignment expressions must be compiled as statements */ - if (update) { - mach_compile_stmt(cs, update); - } - - /* Jump back */ - int offset = loop_top - (mach_current_pc(cs) + 1); - mach_emit(cs, MACH_sJ(MACH_JMP, offset)); - - /* Patch condition exit */ - if (jmpfalse_pc >= 0) { - offset = mach_current_pc(cs) - (jmpfalse_pc + 1); - /* Need to recover the register used for condition - use A from the instruction */ - int cr = MACH_GET_A(cs->code[jmpfalse_pc]); - cs->code[jmpfalse_pc] = MACH_AsBx(MACH_JMPFALSE, cr, (int16_t)offset); - } - - /* Patch break chain */ - int bp = cs->loop_break; - while (bp >= 0) { - int prev = MACH_GET_sJ(cs->code[bp]); - offset = mach_current_pc(cs) - (bp + 1); - cs->code[bp] = MACH_sJ(MACH_JMP, offset); - bp = prev; - } - cs->loop_break = old_break; - cs->loop_continue = old_continue; - cs->scope_depth--; - for (int i = saved_vc; i < cs->var_count; i++) - sys_free(cs->vars[i].name); - cs->var_count = saved_vc; - return; - } - - /* Break */ - if (strcmp(kind, "break") == 0) { - int pc = mach_current_pc(cs); - mach_emit(cs, MACH_sJ(MACH_JMP, cs->loop_break)); - cs->loop_break = pc; - return; - } - - /* Continue */ - if (strcmp(kind, "continue") == 0) { - int pc = mach_current_pc(cs); - mach_emit(cs, MACH_sJ(MACH_JMP, cs->loop_continue)); - cs->loop_continue = pc; - return; - } - - /* Assignment as statement */ - 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) { - int save = cs->freereg; - mach_compile_expr(cs, stmt, -1); - mach_free_reg_to(cs, save); - return; - } - - /* Disrupt statement */ - if (strcmp(kind, "disrupt") == 0) { - mach_emit(cs, MACH_ABC(MACH_THROW, 0, 0, 0)); - return; - } - - /* Fallback: treat as expression statement */ - { - cJSON *expr = cJSON_GetObjectItemCaseSensitive(stmt, "expression"); - if (expr) { - int save = cs->freereg; - mach_compile_expr(cs, expr, -1); - mach_free_reg_to(cs, save); - } - } -} - -/* ---- Link pass: resolve GETNAME to GETINTRINSIC or GETENV ---- */ - -static void mach_link_code(JSContext *ctx, JSCodeRegister *code, JSValue env) { - for (uint32_t i = 0; i < code->instr_count; i++) { - MachInstr32 instr = code->instructions[i]; - if (MACH_GET_OP(instr) != MACH_GETNAME) continue; - int a = MACH_GET_A(instr); - int bx = MACH_GET_Bx(instr); - int in_env = 0; - if (!JS_IsNull(env) && (uint32_t)bx < code->cpool_count) { - JSValue val = JS_GetProperty(ctx, env, code->cpool[bx]); - in_env = !JS_IsNull(val) && !JS_IsException(val); - } - code->instructions[i] = MACH_ABx(in_env ? MACH_GETENV : MACH_GETINTRINSIC, a, bx); - } - for (uint32_t i = 0; i < code->func_count; i++) - if (code->functions[i]) mach_link_code(ctx, code->functions[i], env); -} - -/* ---- Top-level compiler ---- */ - -static MachCode *mach_compile_program(MachCompState *cs, cJSON *ast) { - cJSON *stmts = cJSON_GetObjectItemCaseSensitive(ast, "statements"); - if (!stmts || !cJSON_IsArray(stmts)) return NULL; - - /* Read scopes array from AST */ - cs->scopes = cJSON_GetObjectItemCaseSensitive(ast, "scopes"); - cs->function_nr = 0; - - /* Extract filename for debug info */ - cs->filename = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(ast, "filename")); - - /* Scan scope record for declarations */ - mach_scan_scope(cs); - - /* Hoist function declarations */ - cJSON *functions = cJSON_GetObjectItemCaseSensitive(ast, "functions"); - if (functions && cJSON_IsArray(functions)) { - int fcount = cJSON_GetArraySize(functions); - for (int i = 0; i < fcount; i++) { - cJSON *fn_node = cJSON_GetArrayItem(functions, i); - const char *fn_name = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fn_node, "name")); - if (!fn_name) continue; - int slot = mach_find_var(cs, fn_name); - if (slot < 0) continue; - mach_compile_expr(cs, fn_node, slot); - } - } - - /* Compile each statement */ - int count = cJSON_GetArraySize(stmts); - for (int i = 0; i < count; i++) { - mach_compile_stmt(cs, cJSON_GetArrayItem(stmts, i)); - } - - /* Implicit return null */ - mach_emit(cs, MACH_ABC(MACH_RETNIL, 0, 0, 0)); - - /* nr_close_slots from scope record */ - cJSON *prog_scope = mach_find_scope_record(cs->scopes, 0); - cJSON *ncs_node = prog_scope ? cJSON_GetObjectItemCaseSensitive(prog_scope, "nr_close_slots") : NULL; - - /* Build MachCode */ - MachCode *code = sys_malloc(sizeof(MachCode)); - memset(code, 0, sizeof(MachCode)); - code->arity = 0; - code->nr_slots = cs->maxreg; - code->nr_close_slots = ncs_node ? (int)cJSON_GetNumberValue(ncs_node) : 0; - code->entry_point = 0; - code->instr_count = cs->code_count; - code->instructions = cs->code; - code->cpool_count = cs->cpool_count; - code->cpool = cs->cpool; - code->func_count = cs->func_count; - code->functions = cs->functions; - code->name = NULL; - code->line_table = cs->line_info; - code->filename = cs->filename ? strdup(cs->filename) : NULL; - - return code; -} - -/* Public API: compile AST JSON to MachCode (context-free) */ -MachCode *JS_CompileMachTree(cJSON *ast) { - if (!ast) return NULL; - - MachCompState cs = {0}; - cs.freereg = 1; /* slot 0 = this */ - cs.maxreg = 1; - - MachCode *code = mach_compile_program(&cs, ast); - - /* Free var table (code/cpool/functions are owned by MachCode now) */ - for (int i = 0; i < cs.var_count; i++) - sys_free(cs.vars[i].name); - sys_free(cs.vars); - - return code; -} - -MachCode *JS_CompileMach(const char *ast_json) { - cJSON *ast = cJSON_Parse(ast_json); - if (!ast) return NULL; - MachCode *code = JS_CompileMachTree(ast); - cJSON_Delete(ast); - return code; -} - -/* Free a MachCode tree (compiled but not yet loaded) */ -void JS_FreeMachCode(MachCode *mc) { - if (!mc) return; - sys_free(mc->instructions); - for (uint32_t i = 0; i < mc->cpool_count; i++) { - if (mc->cpool[i].type == MACH_CP_STR) - sys_free(mc->cpool[i].str); - } - sys_free(mc->cpool); - for (uint32_t i = 0; i < mc->func_count; i++) - JS_FreeMachCode(mc->functions[i]); - sys_free(mc->functions); - sys_free(mc->name); - sys_free(mc->line_table); - sys_free(mc->filename); - sys_free(mc); -} - -/* Load a MachCode into a JSCodeRegister (materializes JSValues, needs ctx) */ -JSCodeRegister *JS_LoadMachCode(JSContext *ctx, MachCode *mc, JSValue env) { - JSCodeRegister *code = js_mallocz_rt(sizeof(JSCodeRegister)); - code->arity = mc->arity; - code->nr_close_slots = mc->nr_close_slots; - code->nr_slots = mc->nr_slots; - code->entry_point = mc->entry_point; - code->instr_count = mc->instr_count; - code->instructions = mc->instructions; /* transfer ownership */ - - /* Materialize cpool: raw -> JSValue */ - code->cpool_count = mc->cpool_count; - code->cpool = mach_materialize_cpool(ctx, mc->cpool, mc->cpool_count); - - /* Recursively load nested functions */ - code->func_count = mc->func_count; - if (mc->func_count > 0) { - code->functions = js_malloc_rt(mc->func_count * sizeof(JSCodeRegister *)); - for (uint32_t i = 0; i < mc->func_count; i++) - code->functions[i] = JS_LoadMachCode(ctx, mc->functions[i], env); - } else { - code->functions = NULL; - } - - /* Intern function name */ - code->name = mc->name ? js_key_new(ctx, mc->name) : JS_NULL; - - /* Transfer debug info */ - code->line_table = mc->line_table; - mc->line_table = NULL; - code->filename_cstr = mc->filename ? js_strdup_rt(mc->filename) : NULL; - code->name_cstr = mc->name ? js_strdup_rt(mc->name) : NULL; - - code->disruption_pc = mc->disruption_pc; - - /* Link: resolve GETNAME to GETENV/GETINTRINSIC */ - mach_link_code(ctx, code, env); - - return code; -} - -/* Free a JSCodeRegister and all nested functions */ -static void js_free_code_register(JSCodeRegister *code) { - if (!code) return; - js_free_rt(code->instructions); - js_free_rt(code->cpool); - for (uint32_t i = 0; i < code->func_count; i++) { - js_free_code_register(code->functions[i]); - } - js_free_rt(code->functions); - js_free_rt(code->line_table); - js_free_rt(code->filename_cstr); - js_free_rt(code->name_cstr); - js_free_rt(code); -} - - -/* ============================================================ - MACH VM — register-based bytecode interpreter - ============================================================ */ - -/* Allocate a JSFrameRegister on the GC heap */ -static JSFrameRegister *alloc_frame_register(JSContext *ctx, int slot_count) { - size_t size = sizeof(JSFrameRegister) + slot_count * sizeof(JSValue); - JSFrameRegister *frame = js_mallocz(ctx, size); - if (!frame) return NULL; - - /* cap56 = slot count (used by gc_object_size) */ - frame->hdr = objhdr_make(slot_count, OBJ_FRAME, 0, 0, 0, 0); - frame->function = JS_NULL; - frame->caller = JS_NULL; - frame->address = JS_NewInt32(ctx, 0); - - /* Initialize slots to null */ - for (int i = 0; i < slot_count; i++) { - frame->slots[i] = JS_NULL; - } - - return frame; -} - -/* Create a register-based function from JSCodeRegister */ -static JSValue js_new_register_function(JSContext *ctx, JSCodeRegister *code, JSValue env, JSValue outer_frame) { - /* Protect env and outer_frame from GC — js_mallocz can trigger - collection which moves heap objects, invalidating stack-local copies */ - JSGCRef env_ref, frame_ref; - JS_PushGCRef(ctx, &env_ref); - env_ref.val = env; - JS_PushGCRef(ctx, &frame_ref); - frame_ref.val = outer_frame; - - JSFunction *fn = js_mallocz(ctx, sizeof(JSFunction)); - if (!fn) { - JS_PopGCRef(ctx, &frame_ref); - JS_PopGCRef(ctx, &env_ref); - return JS_EXCEPTION; - } - - fn->header = objhdr_make(0, OBJ_FUNCTION, 0, 0, 0, 0); - fn->kind = JS_FUNC_KIND_REGISTER; - fn->length = code->arity; - fn->name = code->name; - fn->u.reg.code = code; - fn->u.reg.env_record = env_ref.val; - fn->u.reg.outer_frame = frame_ref.val; - - JS_PopGCRef(ctx, &frame_ref); - JS_PopGCRef(ctx, &env_ref); - return JS_MKPTR(fn); -} - -/* Binary operations helper */ -static JSValue reg_vm_binop(JSContext *ctx, int op, JSValue a, JSValue b) { - /* Fast path for integers */ - if (JS_VALUE_IS_BOTH_INT(a, b)) { - int32_t ia = JS_VALUE_GET_INT(a); - int32_t ib = JS_VALUE_GET_INT(b); - switch (op) { - case MACH_ADD: { - int64_t r = (int64_t)ia + (int64_t)ib; - if (r >= INT32_MIN && r <= INT32_MAX) - return JS_NewInt32(ctx, (int32_t)r); - return JS_NewFloat64(ctx, (double)r); - } - case MACH_SUB: { - int64_t r = (int64_t)ia - (int64_t)ib; - if (r >= INT32_MIN && r <= INT32_MAX) - return JS_NewInt32(ctx, (int32_t)r); - return JS_NewFloat64(ctx, (double)r); - } - case MACH_MUL: { - int64_t r = (int64_t)ia * (int64_t)ib; - if (r >= INT32_MIN && r <= INT32_MAX) - return JS_NewInt32(ctx, (int32_t)r); - return JS_NewFloat64(ctx, (double)r); - } - case MACH_DIV: - if (ib == 0) return JS_NULL; - if (ia % ib == 0) return JS_NewInt32(ctx, ia / ib); - return JS_NewFloat64(ctx, (double)ia / (double)ib); - case MACH_MOD: - if (ib == 0) return JS_NULL; - return JS_NewInt32(ctx, ia % ib); - case MACH_EQ: - return JS_NewBool(ctx, ia == ib); - case MACH_NEQ: - return JS_NewBool(ctx, ia != ib); - case MACH_LT: - return JS_NewBool(ctx, ia < ib); - case MACH_LE: - return JS_NewBool(ctx, ia <= ib); - case MACH_GT: - return JS_NewBool(ctx, ia > ib); - case MACH_GE: - return JS_NewBool(ctx, ia >= ib); - case MACH_BAND: - return JS_NewInt32(ctx, ia & ib); - case MACH_BOR: - return JS_NewInt32(ctx, ia | ib); - case MACH_BXOR: - return JS_NewInt32(ctx, ia ^ ib); - case MACH_SHL: - return JS_NewInt32(ctx, ia << (ib & 31)); - case MACH_SHR: - return JS_NewInt32(ctx, ia >> (ib & 31)); - case MACH_USHR: - return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31)); - default: - break; - } - } - - /* String concat for ADD */ - if (op == MACH_ADD && JS_IsText(a) && JS_IsText(b)) - return JS_ConcatString(ctx, a, b); - - /* Comparison ops allow mixed types — return false for mismatches */ - if (op >= MACH_EQ && op <= MACH_GE) { - /* Fast path: identical values (chase pointers for forwarded objects) */ - { - JSValue ca = JS_IsPtr(a) ? JS_MKPTR(chase(a)) : a; - JSValue cb = JS_IsPtr(b) ? JS_MKPTR(chase(b)) : b; - if (ca == cb) { - if (op == MACH_EQ || op == MACH_LE || op == MACH_GE) return JS_TRUE; - if (op == MACH_NEQ) return JS_FALSE; - } - } - if (JS_IsNumber(a) && JS_IsNumber(b)) { - double da, db; - JS_ToFloat64(ctx, &da, a); - JS_ToFloat64(ctx, &db, b); - switch (op) { - case MACH_EQ: return JS_NewBool(ctx, da == db); - case MACH_NEQ: return JS_NewBool(ctx, da != db); - case MACH_LT: return JS_NewBool(ctx, da < db); - case MACH_LE: return JS_NewBool(ctx, da <= db); - case MACH_GT: return JS_NewBool(ctx, da > db); - case MACH_GE: return JS_NewBool(ctx, da >= db); - default: break; - } - } - /* String comparisons */ - if (JS_IsText(a) && JS_IsText(b)) { - int cmp = js_string_compare_value(ctx, a, b, FALSE); - switch (op) { - case MACH_EQ: return JS_NewBool(ctx, cmp == 0); - case MACH_NEQ: return JS_NewBool(ctx, cmp != 0); - case MACH_LT: return JS_NewBool(ctx, cmp < 0); - case MACH_LE: return JS_NewBool(ctx, cmp <= 0); - case MACH_GT: return JS_NewBool(ctx, cmp > 0); - case MACH_GE: return JS_NewBool(ctx, cmp >= 0); - default: break; - } - } - /* Null comparisons */ - if (JS_IsNull(a) && JS_IsNull(b)) { - if (op == MACH_EQ || op == MACH_LE || op == MACH_GE) - return JS_TRUE; - return JS_FALSE; - } - /* Boolean comparisons */ - if (JS_IsBool(a) && JS_IsBool(b)) { - int ba = JS_VALUE_GET_BOOL(a); - int bb = JS_VALUE_GET_BOOL(b); - switch (op) { - case MACH_EQ: return JS_NewBool(ctx, ba == bb); - case MACH_NEQ: return JS_NewBool(ctx, ba != bb); - case MACH_LT: return JS_NewBool(ctx, ba < bb); - case MACH_LE: return JS_NewBool(ctx, ba <= bb); - case MACH_GT: return JS_NewBool(ctx, ba > bb); - case MACH_GE: return JS_NewBool(ctx, ba >= bb); - default: break; - } - } - /* Different types: EQ→false, NEQ→true, others→false */ - if (op == MACH_NEQ) return JS_NewBool(ctx, 1); - return JS_NewBool(ctx, 0); - } - - /* Numeric operations — both must be numeric */ - if (JS_IsNumber(a) && JS_IsNumber(b)) { - double da, db; - JS_ToFloat64(ctx, &da, a); - JS_ToFloat64(ctx, &db, b); - switch (op) { - case MACH_ADD: { - double r = da + db; - if (!isfinite(r)) return JS_NULL; - return JS_NewFloat64(ctx, r); - } - case MACH_SUB: { - double r = da - db; - if (!isfinite(r)) return JS_NULL; - return JS_NewFloat64(ctx, r); - } - case MACH_MUL: { - double r = da * db; - if (!isfinite(r)) return JS_NULL; - return JS_NewFloat64(ctx, r); - } - case MACH_DIV: { - if (db == 0.0) return JS_NULL; - double r = da / db; - if (!isfinite(r)) return JS_NULL; - return JS_NewFloat64(ctx, r); - } - case MACH_MOD: { - if (db == 0.0) return JS_NULL; - double r = fmod(da, db); - if (!isfinite(r)) return JS_NULL; - return JS_NewFloat64(ctx, r); - } - case MACH_POW: { - double r = pow(da, db); - if (!isfinite(r) && isfinite(da) && isfinite(db)) return JS_NULL; - return JS_NewFloat64(ctx, r); - } - case MACH_BAND: case MACH_BOR: case MACH_BXOR: - case MACH_SHL: case MACH_SHR: case MACH_USHR: { - int32_t ia = (int32_t)da; - int32_t ib = (int32_t)db; - switch (op) { - case MACH_BAND: return JS_NewInt32(ctx, ia & ib); - case MACH_BOR: return JS_NewInt32(ctx, ia | ib); - case MACH_BXOR: return JS_NewInt32(ctx, ia ^ ib); - case MACH_SHL: return JS_NewInt32(ctx, ia << (ib & 31)); - case MACH_SHR: return JS_NewInt32(ctx, ia >> (ib & 31)); - case MACH_USHR: return JS_NewInt32(ctx, (uint32_t)ia >> (ib & 31)); - default: break; - } - } - default: break; - } - } - - /* Type mismatch — disrupt */ - return JS_EXCEPTION; -} - -/* Check for interrupt */ -static int reg_vm_check_interrupt(JSContext *ctx) { - if (--ctx->interrupt_counter <= 0) { - ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; - if (ctx->interrupt_handler) { - if (ctx->interrupt_handler(ctx->rt, ctx->interrupt_opaque)) { - return -1; - } - } - } - return 0; -} - -#ifdef HAVE_ASAN -void __asan_on_error(void) { - JSContext *ctx = __asan_js_ctx; - if (!ctx) return; - if (JS_IsNull(ctx->reg_current_frame)) return; - JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame); - uint32_t cur_pc = ctx->current_register_pc; - fprintf(stderr, "\n=== ASAN error: VM stack trace ===\n"); - int is_first = 1; - while (frame) { - if (!JS_IsFunction(frame->function)) break; - JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); - const char *func_name = NULL; - const char *file = NULL; - uint16_t line = 0; - uint32_t pc = is_first ? cur_pc : 0; - if (fn->kind == JS_FUNC_KIND_REGISTER && fn->u.reg.code) { - JSCodeRegister *code = fn->u.reg.code; - file = code->filename_cstr; - func_name = code->name_cstr; - if (!is_first) - pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16); - if (code->line_table && pc < code->instr_count) - line = code->line_table[pc].line; - } else if (fn->kind == JS_FUNC_KIND_MCODE && fn->u.mcode.code) { - JSMCode *code = fn->u.mcode.code; - file = code->filename; - func_name = code->name; - if (!is_first) - pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16); - if (code->line_table && pc < code->instr_count) - line = code->line_table[pc].line; - } - fprintf(stderr, " %s (%s:%u)\n", - func_name ? func_name : "", - file ? file : "", line); - if (JS_IsNull(frame->caller)) break; - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); - is_first = 0; - } - fprintf(stderr, "=================================\n"); -} -#endif - -/* Main register VM execution loop */ -static JSValue JS_CallRegisterVM(JSContext *ctx, JSCodeRegister *code, - JSValue this_obj, int argc, JSValue *argv, - JSValue env, JSValue outer_frame) { - /* Protect env and outer_frame from GC — alloc_frame_register can trigger - collection which moves heap objects, invalidating stack-local copies */ - JSGCRef env_gc, of_gc; - JS_PushGCRef(ctx, &env_gc); - env_gc.val = env; - JS_PushGCRef(ctx, &of_gc); - of_gc.val = outer_frame; - - /* Protect argv and this_obj from GC by pushing onto value_stack. - argv is a C-allocated array whose JSValues may point to GC heap objects; - alloc_frame_register and js_new_register_function can trigger GC. */ - int vs_save = ctx->value_stack_top; - int nargs_copy = (argc < code->arity) ? argc : code->arity; - ctx->value_stack[vs_save] = this_obj; - for (int i = 0; i < nargs_copy; i++) - ctx->value_stack[vs_save + 1 + i] = argv[i]; - ctx->value_stack_top = vs_save + 1 + nargs_copy; - - /* Allocate initial frame */ - JSFrameRegister *frame = alloc_frame_register(ctx, code->nr_slots); - if (!frame) { - ctx->value_stack_top = vs_save; - JS_PopGCRef(ctx, &of_gc); - JS_PopGCRef(ctx, &env_gc); - return JS_EXCEPTION; - } - - /* Protect frame from GC */ - JSGCRef frame_ref; - JS_AddGCRef(ctx, &frame_ref); - frame_ref.val = JS_MKPTR(frame); -#ifdef HAVE_ASAN - __asan_js_ctx = ctx; -#endif - - /* Setup initial frame — wrap top-level code in a function object so that - returning from a called register function can read code/env from frame */ - JSValue top_fn = js_new_register_function(ctx, code, env_gc.val, of_gc.val); - JS_PopGCRef(ctx, &of_gc); - JS_PopGCRef(ctx, &env_gc); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - frame->function = top_fn; - frame->slots[0] = ctx->value_stack[vs_save]; /* slot 0 = this (GC-safe from value_stack) */ - - /* Copy arguments from GC-safe value_stack */ - for (int i = 0; i < nargs_copy; i++) { - frame->slots[1 + i] = ctx->value_stack[vs_save + 1 + i]; - } - ctx->value_stack_top = vs_save; - - uint32_t pc = code->entry_point; - JSValue result = JS_NULL; - - /* Execution loop — 32-bit instruction dispatch */ - for (;;) { - if (reg_vm_check_interrupt(ctx)) { - result = JS_ThrowInternalError(ctx, "interrupted"); - goto done; - } - - if (pc >= code->instr_count) { - /* End of code — implicit return null */ - result = JS_NULL; - if (JS_IsNull(frame->caller)) goto done; - - /* Pop frame */ - JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); - frame->caller = JS_NULL; - frame = caller; - frame_ref.val = JS_MKPTR(frame); - int ret_info = JS_VALUE_GET_INT(frame->address); - JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); - code = fn->u.reg.code; - env = fn->u.reg.env_record; - pc = ret_info >> 16; - int ret_slot = ret_info & 0xFFFF; - if (ret_slot != 0xFFFF) frame->slots[ret_slot] = result; - continue; - } - - MachInstr32 instr = code->instructions[pc++]; - ctx->reg_current_frame = frame_ref.val; - ctx->current_register_pc = pc > 0 ? pc - 1 : 0; - int op = MACH_GET_OP(instr); - int a = MACH_GET_A(instr); - int b = MACH_GET_B(instr); - int c = MACH_GET_C(instr); - - switch (op) { - case MACH_NOP: - break; - - case MACH_LOADK: { - int bx = MACH_GET_Bx(instr); - if (bx < (int)code->cpool_count) - frame->slots[a] = code->cpool[bx]; - break; - } - - case MACH_LOADI: - frame->slots[a] = JS_NewInt32(ctx, MACH_GET_sBx(instr)); - break; - - case MACH_LOADNULL: - frame->slots[a] = JS_NULL; - break; - - case MACH_LOADTRUE: - frame->slots[a] = JS_TRUE; - break; - - case MACH_LOADFALSE: - frame->slots[a] = JS_FALSE; - break; - - case MACH_MOVE: - frame->slots[a] = frame->slots[b]; - break; - - /* Arithmetic / comparison / bitwise — all ABC format */ - case MACH_ADD: case MACH_SUB: case MACH_MUL: case MACH_DIV: - case MACH_MOD: case MACH_POW: - case MACH_EQ: case MACH_NEQ: case MACH_LT: case MACH_LE: - case MACH_GT: case MACH_GE: - case MACH_BAND: case MACH_BOR: case MACH_BXOR: - case MACH_SHL: case MACH_SHR: case MACH_USHR: { - JSValue left = frame->slots[b]; - JSValue right = frame->slots[c]; - JSValue res = reg_vm_binop(ctx, op, left, right); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (JS_IsException(res)) { goto disrupt; } - frame->slots[a] = res; - break; - } - - case MACH_EQ_TOL: - case MACH_NEQ_TOL: { - /* A=dest, B=base, C=3; args in R(B), R(B+1), R(B+2) */ - JSValue left = frame->slots[b]; - JSValue right = frame->slots[b + 1]; - JSValue tol = frame->slots[b + 2]; - BOOL is_eq_op = (op == MACH_EQ_TOL); - if (JS_IsNumber(left) && JS_IsNumber(right) && JS_IsNumber(tol)) { - double da, db, dt; - JS_ToFloat64(ctx, &da, left); - JS_ToFloat64(ctx, &db, right); - JS_ToFloat64(ctx, &dt, tol); - BOOL eq = fabs(da - db) <= dt; - frame->slots[a] = JS_NewBool(ctx, is_eq_op ? eq : !eq); - } else if (JS_IsText(left) && JS_IsText(right) && JS_VALUE_GET_TAG(tol) == JS_TAG_BOOL && JS_VALUE_GET_BOOL(tol)) { - BOOL eq = js_string_compare_value_nocase(ctx, left, right) == 0; - frame->slots[a] = JS_NewBool(ctx, is_eq_op ? eq : !eq); - } else { - /* Fall through to standard eq/neq */ - JSValue res = reg_vm_binop(ctx, is_eq_op ? MACH_EQ : MACH_NEQ, left, right); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (JS_IsException(res)) { goto disrupt; } - frame->slots[a] = res; - } - break; - } - - case MACH_NEG: { - JSValue v = frame->slots[b]; - if (JS_IsInt(v)) { - int32_t i = JS_VALUE_GET_INT(v); - if (i == INT32_MIN) - frame->slots[a] = JS_NewFloat64(ctx, -(double)i); - else - frame->slots[a] = JS_NewInt32(ctx, -i); - } else { - double d; - JS_ToFloat64(ctx, &d, v); - frame->slots[a] = JS_NewFloat64(ctx, -d); - } - break; - } - - case MACH_INC: { - JSValue v = frame->slots[b]; - if (JS_IsInt(v)) { - int32_t i = JS_VALUE_GET_INT(v); - if (i == INT32_MAX) - frame->slots[a] = JS_NewFloat64(ctx, (double)i + 1); - else - frame->slots[a] = JS_NewInt32(ctx, i + 1); - } else { - double d; - JS_ToFloat64(ctx, &d, v); - frame->slots[a] = JS_NewFloat64(ctx, d + 1); - } - break; - } - - case MACH_DEC: { - JSValue v = frame->slots[b]; - if (JS_IsInt(v)) { - int32_t i = JS_VALUE_GET_INT(v); - if (i == INT32_MIN) - frame->slots[a] = JS_NewFloat64(ctx, (double)i - 1); - else - frame->slots[a] = JS_NewInt32(ctx, i - 1); - } else { - double d; - JS_ToFloat64(ctx, &d, v); - frame->slots[a] = JS_NewFloat64(ctx, d - 1); - } - break; - } - - case MACH_LNOT: { - int bval = JS_ToBool(ctx, frame->slots[b]); - frame->slots[a] = JS_NewBool(ctx, !bval); - break; - } - - case MACH_BNOT: { - int32_t i; - JS_ToInt32(ctx, &i, frame->slots[b]); - frame->slots[a] = JS_NewInt32(ctx, ~i); - break; - } - - case MACH_GETFIELD: { - JSValue obj = frame->slots[b]; - JSValue key = code->cpool[c]; - /* Non-proxy functions (arity != 2) can't have properties read */ - 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 = JS_GetProperty(ctx, obj, key); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (JS_IsException(val)) goto disrupt; - frame->slots[a] = val; - break; - } - - case MACH_SETFIELD: { - /* R(A)[K(B)] = R(C) */ - JSValue obj = frame->slots[a]; - JSValue key = code->cpool[b]; - JSValue val = frame->slots[c]; - int ret = JS_SetProperty(ctx, obj, key, val); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (ret < 0) goto disrupt; - break; - } - - case MACH_GETINDEX: { - JSValue obj = frame->slots[b]; - JSValue idx = frame->slots[c]; - JSValue val; - 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[a] = val; - break; - } - - case MACH_SETINDEX: { - /* R(A)[R(B)] = R(C) */ - JSValue obj = frame->slots[a]; - JSValue idx = frame->slots[b]; - JSValue val = frame->slots[c]; - 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"); - ret = -1; - } else if (JS_IsRecord(obj) && !JS_IsText(idx) && !JS_IsRecord(idx)) { - JS_ThrowTypeError(ctx, "object key must be a string or object"); - ret = -1; - } else { - ret = JS_SetProperty(ctx, obj, idx, val); - } - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (ret < 0) goto disrupt; - break; - } - - case MACH_GETINTRINSIC: { - int bx = MACH_GET_Bx(instr); - JSValue key = code->cpool[bx]; - JSValue val = JS_GetProperty(ctx, ctx->global_obj, key); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (JS_IsNull(val)) { - int has = JS_HasProperty(ctx, ctx->global_obj, key); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (has <= 0) { - char buf[128]; - JS_KeyGetStr(ctx, buf, sizeof(buf), key); - JS_ThrowReferenceError(ctx, "'%s' is not defined", buf); - goto disrupt; - } - } - frame->slots[a] = val; - break; - } - - case MACH_GETENV: { - int bx = MACH_GET_Bx(instr); - JSValue key = code->cpool[bx]; - JSValue val = JS_GetProperty(ctx, env, key); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - frame->slots[a] = val; - break; - } - - case MACH_GETNAME: { - /* Runtime fallback: try env then global (should not appear in linked code) */ - int bx = MACH_GET_Bx(instr); - JSValue key = code->cpool[bx]; - JSValue val = JS_NULL; - if (!JS_IsNull(env)) { - val = JS_GetProperty(ctx, env, key); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - } - if (JS_IsNull(val) || JS_IsException(val)) { - val = JS_GetProperty(ctx, ctx->global_obj, key); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - } - frame->slots[a] = val; - break; - } - - case MACH_GETUP: { - /* R(A) = outer_frame[B].slots[C] — walk lexical scope chain */ - int depth = b; - JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); - JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.reg.outer_frame); - for (int d = 1; d < depth; d++) { - fn = JS_VALUE_GET_FUNCTION(target->function); - target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.reg.outer_frame); - } - frame->slots[a] = target->slots[c]; - break; - } - - case MACH_SETUP: { - /* outer_frame[B].slots[C] = R(A) — walk lexical scope chain */ - int depth = b; - JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); - JSFrameRegister *target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.reg.outer_frame); - for (int d = 1; d < depth; d++) { - fn = JS_VALUE_GET_FUNCTION(target->function); - target = (JSFrameRegister *)JS_VALUE_GET_PTR(fn->u.reg.outer_frame); - } - target->slots[c] = frame->slots[a]; - break; - } - - case MACH_JMP: { - int offset = MACH_GET_sJ(instr); - pc = (uint32_t)((int32_t)pc + offset); - break; - } - - case MACH_JMPTRUE: { - int cond = JS_ToBool(ctx, frame->slots[a]); - if (cond) { - int offset = MACH_GET_sBx(instr); - pc = (uint32_t)((int32_t)pc + offset); - } - break; - } - - case MACH_JMPFALSE: { - int cond = JS_ToBool(ctx, frame->slots[a]); - if (!cond) { - int offset = MACH_GET_sBx(instr); - pc = (uint32_t)((int32_t)pc + offset); - } - break; - } - - case MACH_JMPNULL: { - if (JS_IsNull(frame->slots[a])) { - int offset = MACH_GET_sBx(instr); - pc = (uint32_t)((int32_t)pc + offset); - } - break; - } - - case MACH_CALL: { - /* Lua-style call: R(A)=func, B=nargs in R(A+1)..R(A+B), C=nresults */ - int base = a; - int nargs = b; - int nresults = c; - JSValue func_val = frame->slots[base]; - - if (!JS_IsFunction(func_val)) { - goto disrupt; - } - - JSFunction *fn = JS_VALUE_GET_FUNCTION(func_val); - if (fn->kind == JS_FUNC_KIND_C) { - /* C function: push args onto value stack (C-allocated, GC-scanned) */ - int vs_base = ctx->value_stack_top; - for (int i = 0; i < nargs; i++) - ctx->value_stack[vs_base + i] = frame->slots[base + 1 + i]; - 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_c_function(ctx, func_val, JS_NULL, 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; } - if (nresults > 0) frame->slots[base] = ret; - } else if (fn->kind == JS_FUNC_KIND_REGISTER) { - /* Register function: allocate frame, copy args, switch */ - JSCodeRegister *fn_code = fn->u.reg.code; - JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots); - if (!new_frame) { - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - goto disrupt; - } - /* Re-read pointers — GC may have moved them */ - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - func_val = frame->slots[base]; - fn = JS_VALUE_GET_FUNCTION(func_val); - new_frame->function = func_val; - new_frame->slots[0] = JS_NULL; /* this */ - for (int i = 0; i < nargs && i < fn_code->arity; i++) - new_frame->slots[1 + i] = frame->slots[base + 1 + i]; - - /* Save return info: pc in upper 16 bits, base reg or 0xFFFF (discard) in lower */ - int ret_slot = (nresults > 0) ? base : 0xFFFF; - frame->address = JS_NewInt32(ctx, (pc << 16) | ret_slot); - new_frame->caller = JS_MKPTR(frame); - - frame = new_frame; - frame_ref.val = JS_MKPTR(frame); - code = fn_code; - env = fn->u.reg.env_record; - pc = code->entry_point; - } else { - /* Other function kinds (bytecode) — push args onto value stack */ - int vs_base = ctx->value_stack_top; - for (int i = 0; i < nargs; i++) - ctx->value_stack[vs_base + i] = frame->slots[base + 1 + i]; - ctx->value_stack_top = vs_base + nargs; - JSValue ret = JS_CallInternal(ctx, func_val, JS_NULL, nargs, &ctx->value_stack[vs_base], 0); - ctx->value_stack_top = vs_base; - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (JS_IsException(ret)) { goto disrupt; } - if (nresults > 0) frame->slots[base] = ret; - } - break; - } - - case MACH_CALLMETHOD: { - /* Method call: R(A)=obj, B=nargs in R(A+2)..R(A+1+B), C=cpool key index - Result stored in R(A). C=0xFF means key is in R(A+1). - If obj is a function (proxy): call obj(key_str, [args...]) - Else (record): get property, call property(obj_as_this, args...) */ - int base = a; - int nargs = b; - JSGCRef key_ref; - JS_PushGCRef(ctx, &key_ref); - key_ref.val = (c == 0xFF) ? frame->slots[base + 1] : code->cpool[c]; - - if (JS_IsFunction(frame->slots[base]) && JS_IsText(key_ref.val)) { - /* Proxy call: obj(name, [args...]) */ - JSValue arr = JS_NewArray(ctx); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (JS_IsException(arr)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; } - frame->slots[base + 1] = arr; /* protect from GC in temp slot */ - for (int i = 0; i < nargs; i++) { - JS_SetPropertyUint32(ctx, frame->slots[base + 1], i, frame->slots[base + 2 + i]); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - } - /* Push proxy args onto value stack; re-read obj since GC may have moved it */ - int vs_base = ctx->value_stack_top; - ctx->value_stack[vs_base] = key_ref.val; - ctx->value_stack[vs_base + 1] = frame->slots[base + 1]; /* the array */ - 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[base], 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)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; } - frame->slots[base] = ret; - } else if (JS_IsFunction(frame->slots[base])) { - /* Non-proxy function with non-text key: disrupt */ - JS_ThrowTypeError(ctx, "cannot use bracket notation on non-proxy function"); - JS_PopGCRef(ctx, &key_ref); - goto disrupt; - } else { - /* Record method call: get property, call with this=obj */ - JSValue method = JS_GetProperty(ctx, frame->slots[base], key_ref.val); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (JS_IsException(method)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; } - if (!JS_IsFunction(method)) { - frame->slots[base] = JS_NULL; - JS_PopGCRef(ctx, &key_ref); - break; - } - JSFunction *fn = JS_VALUE_GET_FUNCTION(method); - if (fn->kind == JS_FUNC_KIND_C) { - int vs_base = ctx->value_stack_top; - for (int i = 0; i < nargs; i++) - ctx->value_stack[vs_base + i] = frame->slots[base + 2 + i]; - 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_c_function(ctx, method, frame->slots[base], 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)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; } - frame->slots[base] = ret; - } else if (fn->kind == JS_FUNC_KIND_REGISTER) { - JSCodeRegister *fn_code = fn->u.reg.code; - JSFrameRegister *new_frame = alloc_frame_register(ctx, fn_code->nr_slots); - if (!new_frame) { - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - JS_PopGCRef(ctx, &key_ref); - goto disrupt; - } - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - method = JS_GetProperty(ctx, frame->slots[base], key_ref.val); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - fn = JS_VALUE_GET_FUNCTION(method); - new_frame->function = method; - new_frame->slots[0] = frame->slots[base]; /* this */ - for (int i = 0; i < nargs && i < fn_code->arity; i++) - new_frame->slots[1 + i] = frame->slots[base + 2 + i]; - int ret_slot = base; - frame->address = JS_NewInt32(ctx, (pc << 16) | ret_slot); - new_frame->caller = JS_MKPTR(frame); - frame = new_frame; - frame_ref.val = JS_MKPTR(frame); - code = fn_code; - env = fn->u.reg.env_record; - pc = code->entry_point; - } else { - /* Bytecode or other function */ - int vs_base = ctx->value_stack_top; - for (int i = 0; i < nargs; i++) - ctx->value_stack[vs_base + i] = frame->slots[base + 2 + i]; - ctx->value_stack_top = vs_base + nargs; - JSValue ret = JS_CallInternal(ctx, method, frame->slots[base], nargs, &ctx->value_stack[vs_base], 0); - ctx->value_stack_top = vs_base; - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (JS_IsException(ret)) { JS_PopGCRef(ctx, &key_ref); goto disrupt; } - frame->slots[base] = ret; - } - } - JS_PopGCRef(ctx, &key_ref); - break; - } - - case MACH_RETURN: - result = frame->slots[a]; - if (JS_IsNull(frame->caller)) goto done; - { - JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); - frame->caller = JS_NULL; - frame = caller; - frame_ref.val = JS_MKPTR(frame); - int ret_info = JS_VALUE_GET_INT(frame->address); - JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); - code = fn->u.reg.code; - env = fn->u.reg.env_record; - pc = ret_info >> 16; - int ret_slot = ret_info & 0xFFFF; - if (ret_slot != 0xFFFF) frame->slots[ret_slot] = result; - } - break; - - case MACH_RETNIL: - result = JS_NULL; - if (JS_IsNull(frame->caller)) goto done; - { - JSFrameRegister *caller = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); - frame->caller = JS_NULL; - frame = caller; - frame_ref.val = JS_MKPTR(frame); - int ret_info = JS_VALUE_GET_INT(frame->address); - JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); - code = fn->u.reg.code; - env = fn->u.reg.env_record; - pc = ret_info >> 16; - int ret_slot = ret_info & 0xFFFF; - if (ret_slot != 0xFFFF) frame->slots[ret_slot] = result; - } - break; - - case MACH_NEWOBJECT: { - JSValue obj = JS_NewObject(ctx); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (JS_IsException(obj)) { goto disrupt; } - frame->slots[a] = obj; - break; - } - - case MACH_NEWARRAY: { - int count = b; - JSValue arr = JS_NewArray(ctx); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (JS_IsException(arr)) { goto disrupt; } - /* Store array in dest immediately so GC can track it */ - frame->slots[a] = arr; - for (int i = 0; i < count; i++) { - JS_SetPropertyUint32(ctx, frame->slots[a], i, frame->slots[a + 1 + i]); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - } - break; - } - - case MACH_CLOSURE: { - int bx = MACH_GET_Bx(instr); - if ((uint32_t)bx < code->func_count) { - JSCodeRegister *fn_code = code->functions[bx]; - JSValue fn_val = js_new_register_function(ctx, fn_code, env, frame_ref.val); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - frame->slots[a] = fn_val; - } else { - frame->slots[a] = JS_NULL; - } - break; - } - - case MACH_PUSH: { - /* push R(B) onto array R(A) */ - JSValue arr = frame->slots[a]; - JSValue val = frame->slots[b]; - if (!JS_IsArray(arr)) goto disrupt; - JSGCRef arr_gc; - JS_PushGCRef(ctx, &arr_gc); - arr_gc.val = arr; - int rc = JS_ArrayPush(ctx, &arr_gc.val, val); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - JS_PopGCRef(ctx, &arr_gc); - if (rc < 0) goto disrupt; - if (arr_gc.val != arr) frame->slots[a] = arr_gc.val; - break; - } - - case MACH_POP: { - /* R(A) = pop last element from array R(B) */ - JSValue arr = frame->slots[b]; - if (!JS_IsArray(arr)) goto disrupt; - JSValue val = JS_ArrayPop(ctx, arr); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (JS_IsException(val)) goto disrupt; - frame->slots[a] = val; - break; - } - - case MACH_DELETE: { - JSValue obj = frame->slots[b]; - JSValue key = code->cpool[c]; - int ret = JS_DeleteProperty(ctx, obj, key); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (ret < 0) goto disrupt; - frame->slots[a] = JS_NewBool(ctx, ret >= 0); - break; - } - - case MACH_DELETEINDEX: { - JSValue obj = frame->slots[b]; - JSValue key = frame->slots[c]; - int ret = JS_DeleteProperty(ctx, obj, key); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (ret < 0) goto disrupt; - frame->slots[a] = JS_NewBool(ctx, ret >= 0); - break; - } - - case MACH_HASPROP: { - JSValue obj = frame->slots[b]; - JSValue key = frame->slots[c]; - int has = JS_HasProperty(ctx, obj, key); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - frame->slots[a] = JS_NewBool(ctx, has > 0); - break; - } - - case MACH_REGEXP: { - JSValue argv[2]; - argv[0] = code->cpool[b]; /* pattern */ - argv[1] = code->cpool[c]; /* flags */ - JSValue re = js_regexp_constructor(ctx, JS_NULL, 2, argv); - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); - if (JS_IsException(re)) goto disrupt; - frame->slots[a] = re; - break; - } - - case MACH_THROW: - goto disrupt; - - default: - result = JS_ThrowInternalError(ctx, "unknown register VM opcode %d", 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); - code = fn->u.reg.code; - /* Only enter handler if we're not already inside it */ - if (code->disruption_pc > 0 && frame_pc < code->disruption_pc) { - env = fn->u.reg.env_record; - pc = 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: -#ifdef HAVE_ASAN - __asan_js_ctx = NULL; -#endif - ctx->reg_current_frame = JS_NULL; - 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; -} - -/* ============================================================ - MCODE Generator — AST to MCODE JSON (string-based IR) - ============================================================ */ - -typedef struct MachGenState { - cJSON *instructions; - cJSON *data; - cJSON *functions; - - int this_slot; /* always 0 */ - int nr_args; - int nr_close_slots; /* captured from parents */ - int nr_local_slots; - int next_temp_slot; - int max_slot; - - MachVarInfo *vars; - int var_count; - int var_capacity; - - int label_counter; - int func_counter; - - struct MachGenState *parent; - const char *loop_break; - const char *loop_continue; - - int is_arrow; - - /* AST semantic annotations */ - int function_nr; - cJSON *scopes; - - /* Intrinsic (global) name cache */ - struct { const char *name; int slot; } intrinsic_cache[64]; - int intrinsic_count; - - /* Error tracking */ - cJSON *errors; - int has_error; - - /* Line tracking for debug info */ - int cur_line, cur_col; - const char *filename; -} MachGenState; - -static int mach_gen_expr (MachGenState *s, cJSON *expr, int target); -static void mach_gen_statement (MachGenState *s, cJSON *stmt); -static int mach_gen_alloc_slot (MachGenState *s); - -/* Look up an intrinsic in the cache, return slot or -1 */ -static int mach_gen_find_intrinsic (MachGenState *s, const char *name) { - for (int i = 0; i < s->intrinsic_count; i++) { - if (strcmp (s->intrinsic_cache[i].name, name) == 0) - return s->intrinsic_cache[i].slot; - } - return -1; -} - -/* Pre-load intrinsics from the AST intrinsics array */ -static void mach_gen_load_intrinsics (MachGenState *s, cJSON *intrinsics) { - if (!intrinsics) return; - cJSON *item; - cJSON_ArrayForEach (item, intrinsics) { - const char *name = cJSON_GetStringValue (item); - if (!name || s->intrinsic_count >= 64) continue; - if (mach_gen_find_intrinsic (s, name) >= 0) continue; - int slot = mach_gen_alloc_slot (s); - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("access")); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (slot)); - cJSON *lit = cJSON_CreateObject (); - cJSON_AddStringToObject (lit, "kind", "name"); - cJSON_AddStringToObject (lit, "name", name); - cJSON_AddStringToObject (lit, "make", "intrinsic"); - cJSON_AddItemToArray (instr, lit); - cJSON_AddItemToArray (s->instructions, instr); - s->intrinsic_cache[s->intrinsic_count].name = name; - s->intrinsic_cache[s->intrinsic_count].slot = slot; - s->intrinsic_count++; - } -} - -/* Allocate a temporary slot */ -static int mach_gen_alloc_slot (MachGenState *s) { - int slot = s->next_temp_slot++; - if (slot > s->max_slot) s->max_slot = slot; - return slot; -} - -/* Add a variable to the tracking table */ -static void mach_gen_add_var (MachGenState *s, const char *name, int slot, int is_const) { - if (s->var_count >= s->var_capacity) { - int new_cap = s->var_capacity ? s->var_capacity * 2 : 16; - s->vars = sys_realloc (s->vars, new_cap * sizeof(MachVarInfo)); - s->var_capacity = new_cap; - } - MachVarInfo *v = &s->vars[s->var_count++]; - v->name = sys_malloc (strlen (name) + 1); - strcpy (v->name, name); - v->slot = slot; - v->is_const = is_const; - v->is_closure = 0; -} - -/* Find a variable in the current scope only */ -static int mach_gen_find_var (MachGenState *s, const char *name) { - for (int i = 0; i < s->var_count; i++) { - if (strcmp (s->vars[i].name, name) == 0) { - return s->vars[i].slot; - } - } - return -1; -} - -/* Add an error to the state */ -static void mach_gen_error (MachGenState *s, cJSON *node, const char *fmt, ...) { - va_list ap; - char buf[256]; - - va_start (ap, fmt); - vsnprintf (buf, sizeof(buf), fmt, ap); - va_end (ap); - - cJSON *err = cJSON_CreateObject (); - cJSON_AddStringToObject (err, "message", buf); - - cJSON *line_obj = cJSON_GetObjectItemCaseSensitive (node, "from_row"); - cJSON *col_obj = cJSON_GetObjectItemCaseSensitive (node, "from_column"); - if (line_obj) - cJSON_AddNumberToObject (err, "line", cJSON_GetNumberValue (line_obj) + 1); - if (col_obj) - cJSON_AddNumberToObject (err, "column", cJSON_GetNumberValue (col_obj) + 1); - - if (!s->errors) - s->errors = cJSON_CreateArray (); - cJSON_AddItemToArray (s->errors, err); - s->has_error = 1; -} - -/* Scan AST scope record for variable declarations. - Variables are direct keys on the scope object with a "make" field. */ -static void mach_gen_scan_scope (MachGenState *s) { - cJSON *scope = mach_find_scope_record (s->scopes, s->function_nr); - if (!scope) return; - cJSON *v; - cJSON_ArrayForEach (v, scope) { - const char *name = v->string; - if (!name || strcmp (name, "function_nr") == 0 || strcmp (name, "nr_close_slots") == 0) continue; - const char *make = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (v, "make")); - if (!make || strcmp (make, "input") == 0) continue; - if (mach_gen_find_var (s, name) < 0) { - int is_const = (strcmp (make, "def") == 0 || strcmp (make, "function") == 0); - int slot = 1 + s->nr_args + s->nr_local_slots++; - mach_gen_add_var (s, name, slot, is_const); - cJSON *closure_flag = cJSON_GetObjectItemCaseSensitive (v, "closure"); - s->vars[s->var_count - 1].is_closure = (closure_flag && cJSON_IsTrue (closure_flag)); - } - } -} - -static char *mach_gen_label (MachGenState *s, const char *prefix) { - char *label = sys_malloc (64); - snprintf (label, 64, "%s_%d", prefix, s->label_counter++); - return label; -} - -static void mach_gen_emit_label (MachGenState *s, const char *label) { - cJSON *item = cJSON_CreateString (label); - cJSON_AddItemToArray (s->instructions, item); -} - -static void mach_gen_set_pos (MachGenState *s, cJSON *node) { - cJSON *r = cJSON_GetObjectItemCaseSensitive (node, "from_row"); - cJSON *c = cJSON_GetObjectItemCaseSensitive (node, "from_column"); - if (r) s->cur_line = (int)r->valuedouble + 1; - if (c) s->cur_col = (int)c->valuedouble + 1; -} - -static void mach_gen_add_instr (MachGenState *s, cJSON *instr) { - cJSON_AddItemToArray (instr, cJSON_CreateNumber (s->cur_line)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (s->cur_col)); - cJSON_AddItemToArray (s->instructions, instr); -} - -static void mach_gen_emit_0 (MachGenState *s, const char *op) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString (op)); - mach_gen_add_instr (s, instr); -} - -static void mach_gen_emit_1 (MachGenState *s, const char *op, int a) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString (op)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (a)); - mach_gen_add_instr (s, instr); -} - -static void mach_gen_emit_2 (MachGenState *s, const char *op, int a, int b) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString (op)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (a)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (b)); - mach_gen_add_instr (s, instr); -} - -static void mach_gen_emit_3 (MachGenState *s, const char *op, int a, int b, int c) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString (op)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (a)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (b)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (c)); - mach_gen_add_instr (s, instr); -} - -static void mach_gen_emit_4 (MachGenState *s, const char *op, int a, int b, int c, int d) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString (op)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (a)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (b)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (c)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (d)); - mach_gen_add_instr (s, instr); -} - -static void mach_gen_emit_const_num (MachGenState *s, int dest, double val) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("access")); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (val)); - mach_gen_add_instr (s, instr); -} - -static void mach_gen_emit_const_str (MachGenState *s, int dest, const char *val) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("access")); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); - cJSON_AddItemToArray (instr, cJSON_CreateString (val ? val : "")); - mach_gen_add_instr (s, instr); -} - -static void mach_gen_emit_const_bool (MachGenState *s, int dest, int val) { - mach_gen_emit_1 (s, val ? "true" : "false", dest); -} - -static void mach_gen_emit_const_null (MachGenState *s, int dest) { - mach_gen_emit_1 (s, "null", dest); -} - -static void mach_gen_emit_jump (MachGenState *s, const char *label) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("jump")); - cJSON_AddItemToArray (instr, cJSON_CreateString (label)); - mach_gen_add_instr (s, instr); -} - -static void mach_gen_emit_jump_cond (MachGenState *s, const char *op, int slot, const char *label) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString (op)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (slot)); - cJSON_AddItemToArray (instr, cJSON_CreateString (label)); - mach_gen_add_instr (s, instr); -} - -static void mach_gen_emit_get_prop (MachGenState *s, int dest, int obj, const char *prop) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("load")); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj)); - cJSON_AddItemToArray (instr, cJSON_CreateString (prop)); - mach_gen_add_instr (s, instr); -} - -static void mach_gen_emit_set_prop (MachGenState *s, int obj, const char *prop, int val) { - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("store")); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (val)); - cJSON_AddItemToArray (instr, cJSON_CreateString (prop)); - mach_gen_add_instr (s, instr); -} - -static void mach_gen_emit_get_elem (MachGenState *s, int dest, int obj, int idx) { - mach_gen_emit_3 (s, "load", dest, obj, idx); -} - -static void mach_gen_emit_set_elem (MachGenState *s, int obj, int idx, int val) { - mach_gen_emit_3 (s, "store", obj, val, idx); -} - -static void mach_gen_emit_call (MachGenState *s, int dest, int func_slot, cJSON *args) { - int argc = cJSON_GetArraySize (args); - int frame_slot = mach_gen_alloc_slot (s); - mach_gen_emit_3 (s, "frame", frame_slot, func_slot, argc); - int null_slot = mach_gen_alloc_slot (s); - mach_gen_emit_1 (s, "null", null_slot); - mach_gen_emit_3 (s, "setarg", frame_slot, 0, null_slot); - int arg_idx = 1; - cJSON *arg; - cJSON_ArrayForEach (arg, args) { - mach_gen_emit_3 (s, "setarg", frame_slot, arg_idx++, arg->valueint); - } - mach_gen_emit_2 (s, "invoke", frame_slot, dest); -} - -static void mach_gen_emit_call_method (MachGenState *s, int dest, int obj, const char *prop, cJSON *args) { - /* Emit a single callmethod instruction: - ["callmethod", dest, obj_reg, "method_name", arg_reg0, arg_reg1, ...] */ - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("callmethod")); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj)); - cJSON_AddItemToArray (instr, cJSON_CreateString (prop)); - cJSON *arg; - cJSON_ArrayForEach (arg, args) { - cJSON_AddItemToArray (instr, cJSON_CreateNumber (arg->valueint)); - } - mach_gen_add_instr (s, instr); -} - -static void mach_gen_emit_call_method_dyn (MachGenState *s, int dest, int obj, int key_reg, cJSON *args) { - /* Emit a dynamic callmethod instruction: - ["callmethod_dyn", dest, obj_reg, key_reg, arg_reg0, arg_reg1, ...] */ - cJSON *instr = cJSON_CreateArray (); - cJSON_AddItemToArray (instr, cJSON_CreateString ("callmethod_dyn")); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (dest)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (obj)); - cJSON_AddItemToArray (instr, cJSON_CreateNumber (key_reg)); - cJSON *arg; - cJSON_ArrayForEach (arg, args) { - cJSON_AddItemToArray (instr, cJSON_CreateNumber (arg->valueint)); - } - mach_gen_add_instr (s, instr); -} - -static void mach_gen_emit_go_call (MachGenState *s, int func_slot, cJSON *args) { - int argc = cJSON_GetArraySize (args); - int frame_slot = mach_gen_alloc_slot (s); - mach_gen_emit_3 (s, "goframe", frame_slot, func_slot, argc); - int null_slot = mach_gen_alloc_slot (s); - mach_gen_emit_1 (s, "null", null_slot); - mach_gen_emit_3 (s, "setarg", frame_slot, 0, null_slot); - int arg_idx = 1; - cJSON *arg; - cJSON_ArrayForEach (arg, args) { - mach_gen_emit_3 (s, "setarg", frame_slot, arg_idx++, arg->valueint); - } - mach_gen_emit_1 (s, "goinvoke", frame_slot); -} - -static void mach_gen_emit_go_call_method (MachGenState *s, int obj, const char *prop, cJSON *args) { - int func_slot = mach_gen_alloc_slot (s); - mach_gen_emit_get_prop (s, func_slot, obj, prop); - int argc = cJSON_GetArraySize (args); - int frame_slot = mach_gen_alloc_slot (s); - mach_gen_emit_3 (s, "goframe", frame_slot, func_slot, argc); - mach_gen_emit_3 (s, "setarg", frame_slot, 0, obj); - int arg_idx = 1; - cJSON *arg; - cJSON_ArrayForEach (arg, args) { - mach_gen_emit_3 (s, "setarg", frame_slot, arg_idx++, arg->valueint); - } - mach_gen_emit_1 (s, "goinvoke", frame_slot); -} - -static const char *functino_to_mcode_op (const char *name) { - if (strcmp (name, "+!") == 0) return "add"; - if (strcmp (name, "-!") == 0) return "subtract"; - if (strcmp (name, "*!") == 0) return "multiply"; - if (strcmp (name, "/!") == 0) return "divide"; - if (strcmp (name, "%!") == 0) return "modulo"; - if (strcmp (name, "**!") == 0) return "pow"; - if (strcmp (name, "!") == 0) return "gt"; - if (strcmp (name, "<=!") == 0) return "le"; - if (strcmp (name, ">=!") == 0) return "ge"; - if (strcmp (name, "=!") == 0) return "eq"; - if (strcmp (name, "!=!") == 0) return "ne"; - if (strcmp (name, "&!") == 0) return "bitand"; - if (strcmp (name, "|!") == 0) return "bitor"; - if (strcmp (name, "^!") == 0) return "bitxor"; - if (strcmp (name, "<>!") == 0) return "shr"; - if (strcmp (name, ">>>!") == 0) return "ushr"; - if (strcmp (name, "&&!") == 0) return "and"; - if (strcmp (name, "||!") == 0) return "or"; - if (strcmp (name, "~!") == 0) return "bitnot"; - if (strcmp (name, "[]!") == 0) return "load"; - return NULL; -} - -static const char *mach_gen_binop_to_string (const char *kind) { - if (strcmp (kind, "+") == 0) return "add"; - if (strcmp (kind, "-") == 0) return "subtract"; - if (strcmp (kind, "*") == 0) return "multiply"; - if (strcmp (kind, "/") == 0) return "divide"; - if (strcmp (kind, "%") == 0) return "modulo"; - if (strcmp (kind, "&") == 0) return "bitand"; - if (strcmp (kind, "|") == 0) return "bitor"; - if (strcmp (kind, "^") == 0) return "bitxor"; - if (strcmp (kind, "<<") == 0) return "shl"; - if (strcmp (kind, ">>") == 0) return "shr"; - if (strcmp (kind, ">>>") == 0) return "ushr"; - if (strcmp (kind, "==") == 0 || strcmp (kind, "===") == 0) return "eq"; - if (strcmp (kind, "!=") == 0 || strcmp (kind, "!==") == 0) return "ne"; - if (strcmp (kind, "<") == 0) return "lt"; - if (strcmp (kind, "<=") == 0) return "le"; - if (strcmp (kind, ">") == 0) return "gt"; - if (strcmp (kind, ">=") == 0) return "ge"; - if (strcmp (kind, "**") == 0) return "pow"; - if (strcmp (kind, "in") == 0) return "in"; - return "add"; -} - -static int mach_gen_binary (MachGenState *s, cJSON *node) { - const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (node, "kind")); - cJSON *left = cJSON_GetObjectItemCaseSensitive (node, "left"); - cJSON *right = cJSON_GetObjectItemCaseSensitive (node, "right"); - - if (strcmp (kind, "&&") == 0) { - char *end_label = mach_gen_label (s, "and_end"); - int left_slot = mach_gen_expr (s, left, -1); - int dest = mach_gen_alloc_slot (s); - mach_gen_emit_2 (s, "move", dest, left_slot); - mach_gen_emit_jump_cond (s, "jump_false", dest, end_label); - int right_slot = mach_gen_expr (s, right, -1); - mach_gen_emit_2 (s, "move", dest, right_slot); - mach_gen_emit_label (s, end_label); - sys_free (end_label); - return dest; - } - - if (strcmp (kind, "||") == 0) { - char *end_label = mach_gen_label (s, "or_end"); - int left_slot = mach_gen_expr (s, left, -1); - int dest = mach_gen_alloc_slot (s); - mach_gen_emit_2 (s, "move", dest, left_slot); - mach_gen_emit_jump_cond (s, "jump_true", dest, end_label); - int right_slot = mach_gen_expr (s, right, -1); - mach_gen_emit_2 (s, "move", dest, right_slot); - mach_gen_emit_label (s, end_label); - sys_free (end_label); - return dest; - } - - if (strcmp (kind, "??") == 0) { - char *end_label = mach_gen_label (s, "nullish_end"); - int left_slot = mach_gen_expr (s, left, -1); - int dest = mach_gen_alloc_slot (s); - mach_gen_emit_2 (s, "move", dest, left_slot); - mach_gen_emit_jump_cond (s, "jump_not_null", dest, end_label); - int right_slot = mach_gen_expr (s, right, -1); - mach_gen_emit_2 (s, "move", dest, right_slot); - mach_gen_emit_label (s, end_label); - sys_free (end_label); - return dest; - } - - /* Comma operator: evaluate left (discard), evaluate right (keep) */ - if (strcmp (kind, ",") == 0) { - mach_gen_expr (s, left, -1); - return mach_gen_expr (s, right, -1); - } - - int left_slot = mach_gen_expr (s, left, -1); - int right_slot = mach_gen_expr (s, right, -1); - int dest = mach_gen_alloc_slot (s); - const char *op = mach_gen_binop_to_string (kind); - mach_gen_emit_3 (s, op, dest, left_slot, right_slot); - return dest; -} - -static int mach_gen_compound_assign (MachGenState *s, cJSON *node, const char *op) { - cJSON *left = cJSON_GetObjectItemCaseSensitive (node, "left"); - cJSON *right = cJSON_GetObjectItemCaseSensitive (node, "right"); - const char *left_kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "kind")); - - if (strcmp (left_kind, "name") == 0) { - const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (left, "name")); - 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", ""); - - if (s.filename) - cJSON_AddStringToObject (result, "filename", s.filename); - - cJSON *is_arrow = cJSON_GetObjectItemCaseSensitive (func_node, "arrow"); - s.is_arrow = is_arrow && cJSON_IsTrue (is_arrow); - - /* Read function_nr from AST node */ - cJSON *fn_nr_node = cJSON_GetObjectItemCaseSensitive (func_node, "function_nr"); - s.function_nr = fn_nr_node ? (int)cJSON_GetNumberValue (fn_nr_node) : 0; - - /* Parameters */ - cJSON *params = cJSON_GetObjectItemCaseSensitive (func_node, "list"); - if (!params) params = cJSON_GetObjectItemCaseSensitive (func_node, "parameters"); - s.nr_args = cJSON_GetArraySize (params); - s.this_slot = 0; - s.nr_close_slots = 0; - s.nr_local_slots = 0; - - /* Use nr_slots from AST to pre-allocate var capacity */ - cJSON *ns = cJSON_GetObjectItemCaseSensitive (func_node, "nr_slots"); - int ast_nr_slots = ns ? (int)cJSON_GetNumberValue (ns) : 0; - if (ast_nr_slots > 0) { - s.var_capacity = ast_nr_slots; - s.vars = sys_malloc (s.var_capacity * sizeof(MachVarInfo)); - } - - int param_slot = 1; - cJSON *param; - cJSON_ArrayForEach (param, params) { - const char *param_name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (param, "name")); - if (!param_name) param_name = cJSON_GetStringValue (param); - if (param_name) { - mach_gen_add_var (&s, param_name, param_slot, 1); - param_slot++; - } - } - - s.next_temp_slot = 1 + s.nr_args; - s.max_slot = 1 + s.nr_args; - - cJSON_AddNumberToObject (result, "nr_args", s.nr_args); - - /* Scan scope record for variable declarations */ - mach_gen_scan_scope (&s); - - s.next_temp_slot = 1 + s.nr_args + s.nr_local_slots; - if (s.next_temp_slot > s.max_slot) s.max_slot = s.next_temp_slot; - - /* Emit default parameter initialization */ - { - int ps = 1; - cJSON *pp; - cJSON_ArrayForEach(pp, params) { - cJSON *default_expr = cJSON_GetObjectItemCaseSensitive(pp, "expression"); - if (default_expr) { - char *end_label = mach_gen_label(&s, "default_end"); - mach_gen_emit_jump_cond(&s, "jump_not_null", ps, end_label); - int default_slot = mach_gen_expr(&s, default_expr, -1); - mach_gen_emit_2(&s, "move", ps, default_slot); - mach_gen_emit_label(&s, end_label); - sys_free(end_label); - } - ps++; - } - } - - /* Pre-load intrinsics (global names) */ - mach_gen_load_intrinsics (&s, cJSON_GetObjectItemCaseSensitive (func_node, "intrinsics")); - - /* Compile hoisted function declarations from func_node["functions"] */ - cJSON *hoisted = cJSON_GetObjectItemCaseSensitive (func_node, "functions"); - if (hoisted) { - cJSON *fn; - cJSON_ArrayForEach (fn, hoisted) { - const char *fname = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (fn, "name")); - if (fname) { - cJSON *compiled = mach_gen_function (&s, fn); - int func_id = s.func_counter++; - cJSON_AddItemToArray (s.functions, compiled); - int local_slot = mach_gen_find_var (&s, fname); - int dest = mach_gen_alloc_slot (&s); - mach_gen_emit_2 (&s, "function", dest, func_id); - if (local_slot >= 0) mach_gen_emit_2 (&s, "move", local_slot, dest); - } - } - } - - /* Compile body */ - cJSON *stmts = cJSON_GetObjectItemCaseSensitive (func_node, "statements"); - if (!stmts) { - cJSON *body = cJSON_GetObjectItemCaseSensitive (func_node, "body"); - if (body) { - stmts = cJSON_GetObjectItemCaseSensitive (body, "statements"); - if (!stmts) stmts = body; - } - } - cJSON *stmt; - if (stmts && cJSON_IsArray (stmts)) { - cJSON_ArrayForEach (stmt, stmts) { mach_gen_statement (&s, stmt); } - } - - { - int null_slot = mach_gen_alloc_slot (&s); - mach_gen_emit_1 (&s, "null", null_slot); - mach_gen_emit_1 (&s, "return", null_slot); - } - - /* Compile disruption clause if present */ - int disruption_start = 0; - cJSON *disruption = cJSON_GetObjectItemCaseSensitive (func_node, "disruption"); - if (disruption && cJSON_IsArray (disruption)) { - disruption_start = cJSON_GetArraySize (s.instructions); - cJSON_ArrayForEach (stmt, disruption) { mach_gen_statement (&s, stmt); } - int null_slot2 = mach_gen_alloc_slot (&s); - mach_gen_emit_1 (&s, "null", null_slot2); - mach_gen_emit_1 (&s, "return", null_slot2); - } - cJSON_AddNumberToObject (result, "disruption_pc", disruption_start); - - cJSON *fn_scope = mach_find_scope_record (s.scopes, s.function_nr); - cJSON *fn_ncs = fn_scope ? cJSON_GetObjectItemCaseSensitive (fn_scope, "nr_close_slots") : NULL; - cJSON_AddNumberToObject (result, "nr_close_slots", fn_ncs ? (int)cJSON_GetNumberValue (fn_ncs) : 0); - cJSON_AddNumberToObject (result, "nr_slots", s.max_slot + 1); - cJSON_AddItemToObject (result, "instructions", s.instructions); - - parent->label_counter = s.label_counter; - parent->func_counter = s.func_counter; - if (s.errors && s.errors != parent->errors) { - if (!parent->errors) { parent->errors = s.errors; } - else { - cJSON *err; - cJSON_ArrayForEach (err, s.errors) { cJSON_AddItemToArray (parent->errors, cJSON_Duplicate (err, 1)); } - cJSON_Delete (s.errors); - } - } - parent->has_error = parent->has_error || s.has_error; - - for (int i = 0; i < s.var_count; i++) sys_free (s.vars[i].name); - if (s.vars) sys_free (s.vars); - - return result; -} - -static cJSON *mach_gen_program (MachGenState *s, cJSON *ast) { - cJSON *result = cJSON_CreateObject (); - const char *filename = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (ast, "filename")); - cJSON_AddStringToObject (result, "name", filename ? filename : ""); - s->filename = filename; - if (filename) - cJSON_AddStringToObject (result, "filename", filename); - - s->data = cJSON_AddObjectToObject (result, "data"); - s->functions = cJSON_AddArrayToObject (result, "functions"); - - /* Read scopes from AST */ - s->scopes = cJSON_GetObjectItemCaseSensitive (ast, "scopes"); - - s->this_slot = 0; - s->nr_args = 0; - s->nr_close_slots = 0; - s->nr_local_slots = 0; - s->next_temp_slot = 1; - s->max_slot = 1; - - /* Use nr_slots from AST to pre-allocate var capacity */ - cJSON *ns = cJSON_GetObjectItemCaseSensitive (ast, "nr_slots"); - int ast_nr_slots = ns ? (int)cJSON_GetNumberValue (ns) : 0; - if (ast_nr_slots > 0) { - s->var_capacity = ast_nr_slots; - s->vars = sys_malloc (s->var_capacity * sizeof(MachVarInfo)); - } - - /* Scan scope record for variable declarations */ - mach_gen_scan_scope (s); - - s->next_temp_slot = 1 + s->nr_local_slots; - if (s->next_temp_slot > s->max_slot) s->max_slot = s->next_temp_slot; - - /* Intrinsics are loaded lazily at point of use instead of pre-loading, - to avoid loading nested function intrinsics in the wrong scope. */ - - /* Compile hoisted function declarations from ast["functions"] */ - cJSON *hoisted = cJSON_GetObjectItemCaseSensitive (ast, "functions"); - if (hoisted) { - cJSON *fn; - cJSON_ArrayForEach (fn, hoisted) { - const char *name = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (fn, "name")); - if (name) { - cJSON *compiled = mach_gen_function (s, fn); - int func_id = s->func_counter++; - cJSON_AddItemToArray (s->functions, compiled); - int local_slot = mach_gen_find_var (s, name); - int dest = mach_gen_alloc_slot (s); - mach_gen_emit_2 (s, "function", dest, func_id); - if (local_slot >= 0) mach_gen_emit_2 (s, "move", local_slot, dest); - } - } - } - - /* Generate main code */ - cJSON *statements = cJSON_GetObjectItemCaseSensitive (ast, "statements"); - int last_expr_slot = -1; - cJSON *stmt; - cJSON_ArrayForEach (stmt, statements) { - const char *kind = cJSON_GetStringValue (cJSON_GetObjectItemCaseSensitive (stmt, "kind")); - if (kind) { - if (strcmp (kind, "call") == 0) { - cJSON *expr = cJSON_GetObjectItemCaseSensitive (stmt, "expression"); - last_expr_slot = mach_gen_expr (s, expr, -1); - } else if (strcmp (kind, "return") == 0 || strcmp (kind, "disrupt") == 0 || - strcmp (kind, "break") == 0 || strcmp (kind, "continue") == 0) { - mach_gen_statement (s, stmt); - last_expr_slot = -1; - } else if (strcmp (kind, "var") == 0 || strcmp (kind, "def") == 0 || - strcmp (kind, "var_list") == 0 || strcmp (kind, "def_list") == 0 || - strcmp (kind, "function") == 0 || strcmp (kind, "block") == 0 || - strcmp (kind, "if") == 0 || strcmp (kind, "while") == 0 || - strcmp (kind, "do") == 0 || strcmp (kind, "for") == 0 || - strcmp (kind, "switch") == 0) { - mach_gen_statement (s, stmt); - last_expr_slot = -1; - } else { - last_expr_slot = mach_gen_expr (s, stmt, -1); - } - } else { - mach_gen_statement (s, stmt); - } - } - - if (last_expr_slot >= 0) { - mach_gen_emit_1 (s, "return", last_expr_slot); - } else { - int null_slot = mach_gen_alloc_slot (s); - mach_gen_emit_1 (s, "null", null_slot); - mach_gen_emit_1 (s, "return", null_slot); - } - - cJSON *main_obj = cJSON_CreateObject (); - cJSON_AddNumberToObject (main_obj, "nr_args", 0); - cJSON_AddNumberToObject (main_obj, "nr_close_slots", 0); - cJSON_AddNumberToObject (main_obj, "nr_slots", s->max_slot + 1); - cJSON_AddItemToObject (main_obj, "instructions", s->instructions); - cJSON_AddItemToObject (result, "main", main_obj); - - return result; -} - -cJSON *JS_McodeTree (cJSON *ast) { - if (!ast) return NULL; - - MachGenState s = {0}; - s.instructions = cJSON_CreateArray (); - s.errors = NULL; - s.has_error = 0; - - cJSON *mach = mach_gen_program (&s, ast); - - for (int i = 0; i < s.var_count; i++) sys_free (s.vars[i].name); - if (s.vars) sys_free (s.vars); - - if (!mach) { - if (s.errors) cJSON_Delete (s.errors); - return NULL; - } - - if (s.errors) - cJSON_AddItemToObject (mach, "errors", s.errors); - - return mach; -} - -char *JS_Mcode (const char *ast_json) { - cJSON *ast = cJSON_Parse (ast_json); - if (!ast) return NULL; - cJSON *mach = JS_McodeTree (ast); - cJSON_Delete (ast); - if (!mach) return NULL; - char *json = cJSON_PrintUnformatted (mach); - cJSON_Delete (mach); - return json; -} - -/* ============================================================ - MCODE JSON Interpreter - ============================================================ */ - -/* Parse a single MCODE function from cJSON into a JSMCode struct */ -/* Parse a single function (no recursive function parsing) */ -static 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. */ -static 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. */ -static 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. */ -static 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 */ -static 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; -} - -/* Public API: get stack trace as cJSON array */ -cJSON *JS_GetStack(JSContext *ctx) { - if (JS_IsNull(ctx->reg_current_frame)) return NULL; - JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame); - uint32_t cur_pc = ctx->current_register_pc; - - cJSON *arr = cJSON_CreateArray(); - int is_first = 1; - - while (frame) { - if (!JS_IsFunction(frame->function)) break; - JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); - - const char *func_name = NULL; - const char *file = NULL; - uint16_t line = 0, col = 0; - uint32_t pc = is_first ? cur_pc : 0; - - if (fn->kind == JS_FUNC_KIND_REGISTER && fn->u.reg.code) { - JSCodeRegister *code = fn->u.reg.code; - file = code->filename_cstr; - func_name = code->name_cstr; - if (!is_first) { - pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16); - } - if (code->line_table && pc < code->instr_count) { - line = code->line_table[pc].line; - col = code->line_table[pc].col; - } - } else if (fn->kind == JS_FUNC_KIND_MCODE && fn->u.mcode.code) { - JSMCode *code = fn->u.mcode.code; - file = code->filename; - func_name = code->name; - if (!is_first) { - pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16); - } - if (code->line_table && pc < code->instr_count) { - line = code->line_table[pc].line; - col = code->line_table[pc].col; - } - } - - cJSON *entry = cJSON_CreateObject(); - cJSON_AddStringToObject(entry, "function", func_name ? func_name : ""); - cJSON_AddStringToObject(entry, "file", file ? file : ""); - cJSON_AddNumberToObject(entry, "line", line); - cJSON_AddNumberToObject(entry, "column", col); - cJSON_AddItemToArray(arr, entry); - - if (JS_IsNull(frame->caller)) break; - frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); - is_first = 0; - } - - ctx->reg_current_frame = JS_NULL; - return arr; -} - -/* Execute MCODE from cJSON tree. Takes ownership of root (freed internally). */ -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); -} - -/* ============================================================ - MACH Public API - ============================================================ */ - -/* Print a single constant pool value for dump output */ -static void dump_cpool_value(JSContext *ctx, JSValue val) { - uint32_t tag = JS_VALUE_GET_TAG(val); - - if (JS_IsPtr(val)) { - void *ptr = JS_VALUE_GET_PTR(val); - objhdr_t hdr = *(objhdr_t *)ptr; - uint8_t mist_type = objhdr_type(hdr); - if (mist_type == OBJ_TEXT) { - const char *str = JS_ToCString(ctx, val); - if (str) { - printf("\"%s\"", str); - JS_FreeCString(ctx, str); - } else { - printf(""); - } - return; - } - printf("", mist_type); - return; - } - - switch (tag) { - case JS_TAG_INT: - printf("%d", JS_VALUE_GET_INT(val)); - break; - case JS_TAG_BOOL: - printf("%s", JS_VALUE_GET_BOOL(val) ? "true" : "false"); - break; - case JS_TAG_NULL: - printf("null"); - break; - case JS_TAG_SHORT_FLOAT: - printf("%g", JS_VALUE_GET_FLOAT64(val)); - break; - case JS_TAG_STRING_IMM: { - const char *str = JS_ToCString(ctx, val); - if (str) { - printf("\"%s\"", str); - JS_FreeCString(ctx, str); - } else { - printf(""); - } - break; - } - default: - printf("", tag); - break; - } -} - -/* (labels removed in new format) */ - -/* Internal helper to dump JSCodeRegister (32-bit instruction format) */ -static void dump_register_code(JSContext *ctx, JSCodeRegister *code, int indent) { - char pad[64]; - int pad_len = indent * 2; - if (pad_len > 60) pad_len = 60; - memset(pad, ' ', pad_len); - pad[pad_len] = '\0'; - - /* Function header */ - const char *name = ""; - if (!JS_IsNull(code->name)) { - const char *n = JS_ToCString(ctx, code->name); - if (n) name = n; - } - printf("%sFunction: %s\n", pad, name); - printf("%s Arity: %d, Slots: %d, Close: %d\n", pad, - code->arity, code->nr_slots, code->nr_close_slots); - if (!JS_IsNull(code->name)) { - JS_FreeCString(ctx, name); - } - if (code->disruption_pc > 0) - printf("%s Disruption handler at: %d\n", pad, code->disruption_pc); - - /* Constant pool */ - if (code->cpool_count > 0) { - printf("%s\n%sConstant Pool (%d entries):\n", pad, pad, code->cpool_count); - for (uint32_t i = 0; i < code->cpool_count; i++) { - printf("%s [%d]: ", pad, i); - dump_cpool_value(ctx, code->cpool[i]); - printf("\n"); - } - } - - /* Instructions */ - printf("%s\n%sInstructions (%d):\n", pad, pad, code->instr_count); - for (uint32_t i = 0; i < code->instr_count; i++) { - MachInstr32 instr = code->instructions[i]; - int op = MACH_GET_OP(instr); - int a = MACH_GET_A(instr); - int b = MACH_GET_B(instr); - int c = MACH_GET_C(instr); - const char *op_name = (op < MACH_OP_COUNT) ? mach_opcode_names[op] : "???"; - if (!op_name) op_name = "???"; - - printf("%s %3d: %-14s ", pad, i, op_name); - - switch (op) { - /* No operands */ - case MACH_NOP: - case MACH_RETNIL: - break; - - /* A only */ - case MACH_LOADNULL: - case MACH_LOADTRUE: - case MACH_LOADFALSE: - printf("r%d", a); - break; - - /* ABx: load constant */ - case MACH_LOADK: { - int bx = MACH_GET_Bx(instr); - printf("r%d, #%d", a, bx); - if (bx >= 0 && (uint32_t)bx < code->cpool_count) { - printf(" ; "); - dump_cpool_value(ctx, code->cpool[bx]); - } - break; - } - - /* AsBx: load small int */ - case MACH_LOADI: - printf("r%d, %d", a, MACH_GET_sBx(instr)); - break; - - /* A, B: move, unary ops */ - case MACH_MOVE: - case MACH_NEG: - case MACH_INC: - case MACH_DEC: - case MACH_LNOT: - case MACH_BNOT: - printf("r%d, r%d", a, b); - break; - - /* A, B, C: arithmetic, comparison, bitwise */ - case MACH_ADD: case MACH_SUB: case MACH_MUL: case MACH_DIV: - case MACH_MOD: case MACH_POW: - case MACH_EQ: case MACH_NEQ: case MACH_LT: case MACH_LE: - case MACH_GT: case MACH_GE: - case MACH_BAND: case MACH_BOR: case MACH_BXOR: - case MACH_SHL: case MACH_SHR: case MACH_USHR: - printf("r%d, r%d, r%d", a, b, c); - break; - - case MACH_EQ_TOL: case MACH_NEQ_TOL: - printf("r%d, r%d, %d", a, b, c); - break; - - /* Property access */ - case MACH_GETFIELD: - printf("r%d, r%d, #%d", a, b, c); - if ((uint32_t)c < code->cpool_count) { - printf(" ; "); - dump_cpool_value(ctx, code->cpool[c]); - } - break; - case MACH_SETFIELD: - printf("r%d, #%d, r%d", a, b, c); - if ((uint32_t)b < code->cpool_count) { - printf(" ; "); - dump_cpool_value(ctx, code->cpool[b]); - } - break; - case MACH_GETINDEX: - case MACH_SETINDEX: - printf("r%d, r%d, r%d", a, b, c); - break; - - /* ABx: name/intrinsic/env access */ - case MACH_GETNAME: - case MACH_GETINTRINSIC: - case MACH_GETENV: { - int bx = MACH_GET_Bx(instr); - printf("r%d, #%d", a, bx); - if ((uint32_t)bx < code->cpool_count) { - printf(" ; "); - dump_cpool_value(ctx, code->cpool[bx]); - } - break; - } - - /* Closure access */ - case MACH_GETUP: - case MACH_SETUP: - printf("r%d, depth=%d, slot=%d", a, b, c); - break; - - /* isJ: unconditional jump */ - case MACH_JMP: { - int offset = MACH_GET_sJ(instr); - printf("%+d", offset); - printf(" ; -> %d", (int)i + 1 + offset); - break; - } - - /* iAsBx: conditional jumps */ - case MACH_JMPTRUE: - case MACH_JMPFALSE: - case MACH_JMPNULL: { - int offset = MACH_GET_sBx(instr); - printf("r%d, %+d", a, offset); - printf(" ; -> %d", (int)i + 1 + offset); - break; - } - - /* Call */ - case MACH_CALL: - printf("r%d, %d, %d", a, b, c); - break; - - /* Return / throw */ - case MACH_RETURN: - case MACH_THROW: - printf("r%d", a); - break; - - /* Object/array creation */ - case MACH_NEWOBJECT: - printf("r%d", a); - break; - case MACH_NEWARRAY: - printf("r%d, %d", a, b); - break; - - /* Push/Pop */ - case MACH_PUSH: - printf("r%d, r%d", a, b); - break; - case MACH_POP: - printf("r%d, r%d", a, b); - break; - - /* Closure */ - case MACH_CLOSURE: { - int bx = MACH_GET_Bx(instr); - printf("r%d, func#%d", a, bx); - break; - } - - default: - printf("0x%08x", instr); - break; - } - printf("\n"); - } - - /* Nested functions */ - if (code->func_count > 0) { - printf("%s\n%sNested Functions (%d):\n", pad, pad, code->func_count); - for (uint32_t i = 0; i < code->func_count; i++) { - printf("%s [%d]:\n", pad, i); - if (code->functions[i]) { - dump_register_code(ctx, code->functions[i], indent + 2); - } else { - printf("%s \n", pad); - } - } - } -} - -/* Dump MACH bytecode to stdout for debugging. Takes AST cJSON tree. */ -void JS_DumpMachTree(JSContext *ctx, cJSON *ast, JSValue env) { - MachCode *mc = JS_CompileMachTree(ast); - if (!mc) { - printf("=== MACH Bytecode ===\nFailed to compile\n=== End MACH Bytecode ===\n"); - return; - } - - JSCodeRegister *code = JS_LoadMachCode(ctx, mc, env); - printf("=== MACH Bytecode ===\n"); - dump_register_code(ctx, code, 0); - printf("=== End MACH Bytecode ===\n"); -} - -void JS_DumpMach(JSContext *ctx, const char *ast_json, JSValue env) { - cJSON *ast = cJSON_Parse(ast_json); - if (!ast) { - printf("=== MACH Bytecode ===\nFailed to parse\n=== End MACH Bytecode ===\n"); - return; - } - JS_DumpMachTree(ctx, ast, env); - cJSON_Delete(ast); -} - -/* Compile and execute MACH bytecode. Takes AST cJSON tree. */ -JSValue JS_RunMachTree(JSContext *ctx, cJSON *ast, JSValue env) { - MachCode *mc = JS_CompileMachTree(ast); - if (!mc) { - return JS_ThrowSyntaxError(ctx, "failed to compile AST to MACH bytecode"); - } - - JSCodeRegister *code = JS_LoadMachCode(ctx, mc, env); - JSValue result = JS_CallRegisterVM(ctx, code, ctx->global_obj, 0, NULL, env, JS_NULL); - return result; -} - -JSValue JS_RunMach(JSContext *ctx, const char *ast_json, JSValue env) { - cJSON *ast = cJSON_Parse(ast_json); - if (!ast) { - return JS_ThrowSyntaxError(ctx, "failed to parse AST JSON"); - } - JSValue result = JS_RunMachTree(ctx, ast, env); - cJSON_Delete(ast); - return result; -} diff --git a/source/runtime.c b/source/runtime.c new file mode 100644 index 00000000..e3ec5137 --- /dev/null +++ b/source/runtime.c @@ -0,0 +1,13756 @@ +/* + * 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. + */ + +#define BLOB_IMPLEMENTATION +#define NOTA_IMPLEMENTATION +#define WOTA_IMPLEMENTATION +#include "quickjs-internal.h" + +JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT; + +/* === Function definitions from header region (non-inline) === */ + +JS_BOOL JS_IsStone(JSValue v) { + return !JS_IsObject(v) || objhdr_s(*chase(v)); +} + +JS_BOOL JS_IsArray(JSValue v) { + return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_ARRAY; +} + +JS_BOOL JS_IsRecord (JSValue v) { + return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_RECORD; +} + +JS_BOOL JS_IsFunction (JSValue v) { + return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_FUNCTION; +} + +JS_BOOL JS_IsCode (JSValue v) { + return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_CODE; +} + +JS_BOOL JS_IsForwarded (JSValue v) { + return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_FORWARD; +} + +JS_BOOL JS_IsFrame (JSValue v) { + return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_FRAME; +} + +JS_BOOL JS_IsBlob (JSValue v) { + return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_BLOB; +} + +JS_BOOL JS_IsText(JSValue v) { + return MIST_IsImmediateASCII(v) || (JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_TEXT); +} + +uint64_t get_text_hash (JSText *text) { + uint64_t len = objhdr_cap56 (text->hdr); + size_t word_count = (len + 1) / 2; + + if (objhdr_s (text->hdr)) { + /* Stoned text: check for cached hash */ + if (text->length != 0) return text->length; + /* Compute and cache hash using content length from header */ + text->length = fash64_hash_words (text->packed, word_count, len); + if (!text->length) text->length = 1; + return text->length; + } else { + /* Pre-text: compute hash on the fly */ + return fash64_hash_words (text->packed, word_count, len); + } +} + +/* Pack UTF-32 characters into 64-bit words (2 chars per word) */ +void pack_utf32_to_words (const uint32_t *utf32, uint32_t len, uint64_t *packed) { + for (uint32_t i = 0; i < len; i += 2) { + uint64_t hi = utf32[i]; + uint64_t lo = (i + 1 < len) ? utf32[i + 1] : 0; + packed[i / 2] = (hi << 32) | lo; + } +} + +/* Compare two packed UTF-32 texts for equality */ +int text_equal (JSText *a, const uint64_t *packed_b, uint32_t len_b) { + uint32_t len_a = (uint32_t)objhdr_cap56 (a->hdr); + if (len_a != len_b) return 0; + size_t word_count = (len_a + 1) / 2; + return memcmp (a->packed, packed_b, word_count * sizeof (uint64_t)) == 0; +} + +int JS_IsPretext (JSValue v) { + if (!JS_IsText (v)) return 0; + JSText *text = (JSText *)JS_VALUE_GET_PTR (v); + return !objhdr_s (text->hdr); +} + +JSValue *JS_PushGCRef (JSContext *ctx, JSGCRef *ref) { + ref->prev = ctx->top_gc_ref; + ctx->top_gc_ref = ref; + ref->val = JS_NULL; + return &ref->val; +} + +JSValue JS_PopGCRef (JSContext *ctx, JSGCRef *ref) { + ctx->top_gc_ref = ref->prev; + return ref->val; +} + +JSValue *JS_AddGCRef (JSContext *ctx, JSGCRef *ref) { + ref->prev = ctx->last_gc_ref; + ctx->last_gc_ref = ref; + ref->val = JS_NULL; + return &ref->val; +} + +void JS_DeleteGCRef (JSContext *ctx, JSGCRef *ref) { + JSGCRef **pref, *ref1; + pref = &ctx->last_gc_ref; + for (;;) { + ref1 = *pref; + if (ref1 == NULL) + abort (); + if (ref1 == ref) { + *pref = ref1->prev; + break; + } + pref = &ref1->prev; + } +} + +void *st_alloc (JSContext *ctx, size_t bytes, size_t align) { + /* Align the request */ + bytes = (bytes + align - 1) & ~(align - 1); + + /* Check if we have space in the stone arena */ + if (ctx->stone_base && ctx->stone_free + bytes <= ctx->stone_end) { + void *ptr = ctx->stone_free; + ctx->stone_free += bytes; + return ptr; + } + + /* No stone arena or not enough space - allocate a page */ + size_t page_size = sizeof (StonePage) + bytes; + StonePage *page = malloc (page_size); + if (!page) return NULL; + + page->next = ctx->st_pages; + page->size = bytes; + ctx->st_pages = page; + + return page->data; +} + +/* Free all stone arena pages */ +void st_free_all (JSContext *ctx) { + StonePage *page = ctx->st_pages; + while (page) { + StonePage *next = page->next; + free (page); + page = next; + } + ctx->st_pages = NULL; +} + +int st_text_resize (JSContext *ctx) { + uint32_t new_size, new_resize; + uint32_t *new_hash; + JSText **new_array; + + if (ctx->st_text_size == 0) { + /* Initial allocation */ + new_size = ST_TEXT_INITIAL_SIZE; + } else { + /* Double the size */ + new_size = ctx->st_text_size * 2; + } + new_resize = new_size * 3 / 4; /* 75% load factor */ + + /* Allocate new hash table (use runtime malloc, not bump allocator) */ + new_hash = js_malloc_rt (new_size * sizeof (uint32_t)); + if (!new_hash) return -1; + memset (new_hash, 0, new_size * sizeof (uint32_t)); + + /* Allocate new text array (one extra for 1-based indexing) */ + new_array = js_malloc_rt ((new_size + 1) * sizeof (JSText *)); + if (!new_array) { + js_free_rt(new_hash); + return -1; + } + memset (new_array, 0, (new_size + 1) * sizeof (JSText *)); + + /* Rehash existing entries */ + if (ctx->st_text_count > 0) { + uint32_t mask = new_size - 1; + for (uint32_t id = 1; id <= ctx->st_text_count; id++) { + JSText *text = ctx->st_text_array[id]; + new_array[id] = text; + + /* Compute hash and find slot */ + uint64_t hash = get_text_hash (text); + uint32_t slot = hash & mask; + while (new_hash[slot] != 0) + slot = (slot + 1) & mask; + new_hash[slot] = id; + } + } + + /* Free old tables */ + if (ctx->st_text_hash) js_free_rt (ctx->st_text_hash); + if (ctx->st_text_array) js_free_rt (ctx->st_text_array); + + ctx->st_text_hash = new_hash; + ctx->st_text_array = new_array; + ctx->st_text_size = new_size; + ctx->st_text_resize = new_resize; + + return 0; +} + +void *js_realloc (JSContext *ctx, void *ptr, size_t size) { + void *new_ptr; + + /* Align size to 8 bytes */ + size = (size + 7) & ~7; + + if (!ptr) { + /* New allocation */ + new_ptr = js_malloc (ctx, size); + if (!new_ptr) return NULL; + return new_ptr; + } + + /* Bump allocator: just allocate new space. + Caller is responsible for protecting ptr and copying data. */ + new_ptr = js_malloc (ctx, size); + return new_ptr; +} + +void JS_MarkValue (JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) { + (void)rt; + (void)val; + (void)mark_func; + /* No-op with copying GC - values are discovered by tracing from roots */ +} + +JSValue intern_text_to_value (JSContext *ctx, const uint32_t *utf32, uint32_t len) { + /* Pack UTF-32 for hashing and comparison */ + size_t word_count = (len + 1) / 2; + uint64_t *packed = alloca (word_count * sizeof (uint64_t)); + pack_utf32_to_words (utf32, len, packed); + + uint64_t hash = fash64_hash_words (packed, word_count, len); + + /* Look up in hash table */ + uint32_t mask = ctx->st_text_size - 1; + uint32_t slot = hash & mask; + + while (ctx->st_text_hash[slot] != 0) { + uint32_t id = ctx->st_text_hash[slot]; + JSText *existing = ctx->st_text_array[id]; + if (text_equal (existing, packed, len)) { + /* Found existing entry */ + return JS_MKPTR (existing); + } + slot = (slot + 1) & mask; + } + + /* Not found - create new entry */ + if (ctx->st_text_count >= ctx->st_text_resize) { + if (st_text_resize (ctx) < 0) return JS_NULL; /* OOM */ + /* Recompute slot after resize */ + mask = ctx->st_text_size - 1; + slot = hash & mask; + while (ctx->st_text_hash[slot] != 0) + slot = (slot + 1) & mask; + } + + /* Allocate JSText in stone arena */ + size_t text_size = sizeof (JSText) + word_count * sizeof (uint64_t); + JSText *text = st_alloc (ctx, text_size, 8); + if (!text) return JS_NULL; /* OOM */ + + /* Initialize the text */ + text->hdr = objhdr_make (len, OBJ_TEXT, false, false, false, true); /* s=1 for stoned */ + text->length = hash; /* Store hash in length field for stoned text */ + memcpy (text->packed, packed, word_count * sizeof (uint64_t)); + + /* Add to intern table */ + uint32_t new_id = ++ctx->st_text_count; + ctx->st_text_hash[slot] = new_id; + ctx->st_text_array[new_id] = text; + + return JS_MKPTR (text); +} + +JSValue js_key_new (JSContext *ctx, const char *str) { + size_t len = strlen (str); + + /* Try immediate ASCII first (≤7 ASCII chars) */ + if (len <= MIST_ASCII_MAX_LEN) { + JSValue imm = MIST_TryNewImmediateASCII (str, len); + if (!JS_IsNull (imm)) return imm; + } + + /* Check length limit */ + if (len > JS_KEY_MAX_LEN) return JS_NULL; + + /* Convert UTF-8 to UTF-32 for interning - use stack buffer */ + const uint8_t *p = (const uint8_t *)str; + const uint8_t *p_end = p + len; + uint32_t utf32_buf[JS_KEY_MAX_LEN]; + uint32_t utf32_len = 0; + + while (p < p_end && utf32_len < JS_KEY_MAX_LEN) { + uint32_t c; + const uint8_t *p_next; + if (*p < 0x80) { + c = *p++; + } else { + c = unicode_from_utf8 (p, p_end - p, &p_next); + if (c == (uint32_t)-1) { + c = 0xFFFD; /* replacement char for invalid UTF-8 */ + p++; + } else { + p = p_next; + } + } + utf32_buf[utf32_len++] = c; + } + + return intern_text_to_value (ctx, utf32_buf, utf32_len); +} + +/* JS_NewAtomString - creates JSValue string from C string (for compatibility) + */ +JSValue JS_NewAtomString (JSContext *ctx, const char *str) { + return js_key_new (ctx, str); +} + +JSValue js_key_new_len (JSContext *ctx, const char *str, size_t len) { + /* Try immediate ASCII first (≤7 ASCII chars) */ + if (len <= MIST_ASCII_MAX_LEN) { + JSValue imm = MIST_TryNewImmediateASCII (str, len); + if (!JS_IsNull (imm)) return imm; + } + + /* Check length limit */ + if (len > JS_KEY_MAX_LEN) return JS_NULL; + + /* Convert UTF-8 to UTF-32 for interning */ + const uint8_t *p = (const uint8_t *)str; + const uint8_t *p_end = p + len; + uint32_t utf32_buf[JS_KEY_MAX_LEN]; + uint32_t utf32_len = 0; + + while (p < p_end && utf32_len < JS_KEY_MAX_LEN) { + uint32_t c; + const uint8_t *p_next; + if (*p < 0x80) { + c = *p++; + } else { + c = unicode_from_utf8 (p, p_end - p, &p_next); + if (c == (uint32_t)-1) { + c = 0xFFFD; + p++; + } else { + p = p_next; + } + } + utf32_buf[utf32_len++] = c; + } + + return intern_text_to_value (ctx, utf32_buf, utf32_len); +} + +uint64_t js_key_hash (JSValue key) { + if (MIST_IsImmediateASCII (key)) { + /* Hash immediate ASCII the same way as heap strings for consistency */ + int len = MIST_GetImmediateASCIILen (key); + if (len == 0) return 1; + size_t word_count = (len + 1) / 2; + uint64_t packed[4]; /* Max 7 chars = 4 words */ + for (size_t i = 0; i < word_count; i++) { + uint32_t c0 = (i * 2 < (size_t)len) ? MIST_GetImmediateASCIIChar (key, i * 2) : 0; + uint32_t c1 = (i * 2 + 1 < (size_t)len) ? MIST_GetImmediateASCIIChar (key, i * 2 + 1) : 0; + packed[i] = ((uint64_t)c0 << 32) | c1; + } + uint64_t h = fash64_hash_words (packed, word_count, len); + return h ? h : 1; + } + + if (!JS_IsPtr (key)) return 0; + + /* Use chase to follow forwarding pointers */ + void *ptr = chase (key); + objhdr_t hdr = *(objhdr_t *)ptr; + uint8_t type = objhdr_type (hdr); + + if (type == OBJ_TEXT) { + /* For JSText (stoned strings), use get_text_hash */ + JSText *text = (JSText *)ptr; + return get_text_hash (text); + } + if (type == OBJ_RECORD) { + JSRecord *rec = (JSRecord *)ptr; + uint32_t rec_id = REC_GET_REC_ID(rec); + if (rec_id == 0) return 0; + return fash64_hash_one (rec_id); + } + + return 0; +} + +JS_BOOL js_key_equal (JSValue a, JSValue b) { + if (a == b) return TRUE; + + /* Use chase to follow forwarding pointers */ + if (MIST_IsImmediateASCII (a)) { + if (MIST_IsImmediateASCII (b)) return FALSE; + if (!JS_IsPtr (b)) return FALSE; + JSText *tb = (JSText *)chase (b); + if (objhdr_type (tb->hdr) != OBJ_TEXT) return FALSE; + return JSText_equal_ascii (tb, a); + } + if (MIST_IsImmediateASCII (b)) { + if (!JS_IsPtr (a)) return FALSE; + JSText *ta = (JSText *)chase (a); + if (objhdr_type (ta->hdr) != OBJ_TEXT) return FALSE; + return JSText_equal_ascii (ta, b); + } + + if (!JS_IsPtr (a) || !JS_IsPtr (b)) return FALSE; + + void *pa = chase (a); + void *pb = chase (b); + objhdr_t ha = *(objhdr_t *)pa; + objhdr_t hb = *(objhdr_t *)pb; + uint8_t type_a = objhdr_type (ha); + uint8_t type_b = objhdr_type (hb); + + if (type_a != type_b) return FALSE; + if (type_a == OBJ_RECORD) return FALSE; /* pointer equality handled above */ + if (type_a == OBJ_TEXT) + return JSText_equal ((JSText *)pa, (JSText *)pb); + + return FALSE; +} + +JS_BOOL js_key_equal_str (JSValue a, const char *str) { + size_t len = strlen (str); + + if (MIST_IsImmediateASCII (a)) { + int imm_len = MIST_GetImmediateASCIILen (a); + if ((size_t)imm_len != len) return FALSE; + for (int i = 0; i < imm_len; i++) { + if (MIST_GetImmediateASCIIChar (a, i) != str[i]) return FALSE; + } + return TRUE; + } + + if (!JS_IsPtr (a)) return FALSE; + JSText *ta = (JSText *)JS_VALUE_GET_PTR (a); + if (objhdr_type (ta->hdr) != OBJ_TEXT) return FALSE; + uint64_t txt_len = objhdr_cap56 (ta->hdr); + if (txt_len != len) return FALSE; + + /* Compare character by character (UTF-32 vs ASCII) */ + for (size_t i = 0; i < len; i++) { + if (string_get (ta, i) != (uint32_t)(unsigned char)str[i]) + return FALSE; + } + return TRUE; +} + +int rec_find_slot (JSRecord *rec, JSValue k) { + uint64_t mask = objhdr_cap56 (rec->mist_hdr); + uint64_t h64 = js_key_hash (k); + uint64_t slot = (h64 & mask); + if (slot == 0) slot = 1; /* slot 0 is reserved */ + + uint64_t first_tomb = 0; + + for (uint64_t i = 0; i <= mask; i++) { + JSValue slot_key = rec->slots[slot].key; + + if (rec_key_is_empty (slot_key)) { + /* Empty slot - key not found */ + return first_tomb ? -(int)first_tomb : -(int)slot; + } + if (rec_key_is_tomb (slot_key)) { + /* Tombstone - remember for insertion but keep searching */ + if (first_tomb == 0) first_tomb = slot; + } else if (js_key_equal (slot_key, k)) { + /* Found it */ + return (int)slot; + } + + slot = (slot + 1) & mask; + if (slot == 0) slot = 1; + } + + /* Table full (shouldn't happen with proper load factor) */ + return first_tomb ? -(int)first_tomb : -1; +} + +JSValue rec_get (JSContext *ctx, JSRecord *rec, JSValue k) { + if (rec_key_is_empty (k) || rec_key_is_tomb (k)) return JS_NULL; + + /* Walk prototype chain */ + JSRecord *p = rec; + while (p) { + int slot = rec_find_slot (p, k); + if (slot > 0) { return p->slots[slot].val; } + p = p->proto; + } + return JS_NULL; +} + +int rec_resize (JSContext *ctx, JSValue *pobj, uint64_t new_mask) { + /* Protect the source object with a GC ref in case js_malloc triggers GC */ + JSGCRef obj_ref; + JS_AddGCRef (ctx, &obj_ref); + obj_ref.val = *pobj; + + JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (*pobj); + uint64_t old_mask = objhdr_cap56 (rec->mist_hdr); + + /* Allocate new record with larger capacity - may trigger GC! */ + size_t slots_size = sizeof (slot) * (new_mask + 1); + size_t total_size = sizeof (JSRecord) + slots_size; + + JSRecord *new_rec = js_malloc (ctx, total_size); + if (!new_rec) { + JS_DeleteGCRef (ctx, &obj_ref); + return -1; + } + + /* Re-get record from GC ref - it may have moved during GC */ + rec = (JSRecord *)JS_VALUE_GET_OBJ (obj_ref.val); + old_mask = objhdr_cap56 (rec->mist_hdr); + + /* Initialize new record */ + new_rec->mist_hdr = objhdr_make (new_mask, OBJ_RECORD, false, false, false, false); + new_rec->proto = rec->proto; + new_rec->len = 0; + + /* Initialize all slots to empty */ + for (uint64_t i = 0; i <= new_mask; i++) { + new_rec->slots[i].key = JS_NULL; + new_rec->slots[i].val = JS_NULL; + } + + /* Copy slot[0] (class_id, rec_id, opaque) */ + new_rec->slots[0].key = rec->slots[0].key; + new_rec->slots[0].val = rec->slots[0].val; + + /* Rehash all valid entries from old to new */ + for (uint64_t i = 1; i <= old_mask; i++) { + JSValue k = rec->slots[i].key; + if (!rec_key_is_empty (k) && !rec_key_is_tomb (k)) { + /* Insert into new record using linear probing */ + uint64_t h64 = js_key_hash (k); + uint64_t slot = (h64 & new_mask); + if (slot == 0) slot = 1; + + while (!rec_key_is_empty (new_rec->slots[slot].key)) { + slot = (slot + 1) & new_mask; + if (slot == 0) slot = 1; + } + + new_rec->slots[slot].key = k; + new_rec->slots[slot].val = rec->slots[i].val; + new_rec->len++; + } + } + + /* Install forward header at old location so stale references can find the new record */ + rec->mist_hdr = objhdr_make_fwd (new_rec); + + /* Update caller's JSValue to point to new record */ + *pobj = JS_MKPTR (new_rec); + + JS_DeleteGCRef (ctx, &obj_ref); + return 0; +} + +int rec_set_own (JSContext *ctx, JSValue *pobj, JSValue k, JSValue val) { + if (rec_key_is_empty (k) || rec_key_is_tomb (k)) { + return -1; + } + + JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (*pobj); + int slot = rec_find_slot (rec, k); + + if (slot > 0) { + /* Existing key - replace value */ + rec->slots[slot].val = val; + return 0; + } + + /* New key - check if resize needed (75% load factor) */ + uint32_t mask = (uint32_t)objhdr_cap56 (rec->mist_hdr); + if ((rec->len + 1) * 4 > mask * 3) { + /* Over 75% load factor - resize. Protect key and value in case GC runs. */ + JSGCRef k_ref, val_ref; + JS_AddGCRef (ctx, &k_ref); + JS_AddGCRef (ctx, &val_ref); + k_ref.val = k; + val_ref.val = val; + + uint32_t new_mask = (mask + 1) * 2 - 1; + if (rec_resize (ctx, pobj, new_mask) < 0) { + JS_DeleteGCRef (ctx, &val_ref); + JS_DeleteGCRef (ctx, &k_ref); + return -1; + } + + /* Re-get values after resize (they may have moved during GC) */ + k = k_ref.val; + val = val_ref.val; + JS_DeleteGCRef (ctx, &val_ref); + JS_DeleteGCRef (ctx, &k_ref); + + /* Re-get rec after resize (pobj now points to new record) */ + rec = (JSRecord *)JS_VALUE_GET_OBJ (*pobj); + /* Re-find slot after resize */ + slot = rec_find_slot (rec, k); + } + + /* Insert at -slot */ + int insert_slot = -slot; + rec->slots[insert_slot].key = k; + rec->slots[insert_slot].val = val; + rec->len++; + + return 0; +} + +JSRecord *js_new_record_class (JSContext *ctx, uint32_t initial_mask, JSClassID class_id) { + if (initial_mask == 0) initial_mask = JS_RECORD_INITIAL_MASK; + + /* Allocate record + inline slots in one bump allocation */ + size_t slots_size = sizeof (slot) * (initial_mask + 1); + size_t total_size = sizeof (JSRecord) + slots_size; + + JSRecord *rec = js_malloc (ctx, total_size); + if (!rec) return NULL; + + rec->mist_hdr = objhdr_make (initial_mask, OBJ_RECORD, false, false, false, false); + rec->proto = NULL; + rec->len = 0; + + /* Initialize all slots to empty (JS_NULL) */ + for (uint32_t i = 0; i <= initial_mask; i++) { + rec->slots[i].key = JS_NULL; + rec->slots[i].val = JS_NULL; + } + + /* slots[0] is reserved: store class_id (low 32) and rec_id (high 32) in key */ + uint32_t rec_id = ++ctx->rec_key_next; + rec->slots[0].key = (JSValue)class_id | ((JSValue)rec_id << 32); + rec->slots[0].val = 0; /* opaque pointer, initially NULL */ + + return rec; +} + +int JS_SetPropertyInternal (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val) { + if (!JS_IsRecord (this_obj)) { + return -1; + } + /* Use a local copy that rec_set_own can update if resize happens */ + JSValue obj = this_obj; + return rec_set_own (ctx, &obj, prop, val); +} + + +void *js_malloc_rt (size_t size) { + return malloc(size); +} + +void js_free_rt (void *ptr) { + free (ptr); +} + +void *js_realloc_rt (void *ptr, size_t size) { + return realloc (ptr, size); +} + +void *js_mallocz_rt (size_t size) { + void *ptr; + ptr = js_malloc_rt (size); + if (!ptr) return NULL; + return memset (ptr, 0, size); +} + +char *js_strdup_rt (const char *str) { + if (!str) return NULL; + size_t len = strlen(str) + 1; + char *dup = js_malloc_rt(len); + if (dup) memcpy(dup, str, len); + return dup; +} + +/* Throw out of memory in case of error */ +void *js_malloc (JSContext *ctx, size_t size) { + /* Align size to 8 bytes */ + size = (size + 7) & ~7; + +#ifdef FORCE_GC_AT_MALLOC + /* Force GC on every allocation for testing - but don't grow heap unless needed */ + int need_space = (uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end; + if (ctx_gc(ctx, need_space, size) < 0) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } + if ((uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end) { + JS_ThrowOutOfMemory(ctx); + return NULL; + } +#else + /* Check if we have space in current block */ + if ((uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end) { + /* Trigger GC to reclaim memory */ + if (ctx_gc (ctx, 1, size) < 0) { + JS_ThrowOutOfMemory (ctx); + return NULL; + } + /* Re-check after GC */ + if ((uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end) { + JS_ThrowOutOfMemory (ctx); + return NULL; + } + } +#endif + + void *ptr = ctx->heap_free; + ctx->heap_free = (uint8_t *)ctx->heap_free + size; + return ptr; +} + +/* Throw out of memory in case of error */ +void *js_mallocz (JSContext *ctx, size_t size) { + void *ptr = js_malloc (ctx, size); + if (!ptr) return NULL; + return memset (ptr, 0, size); +} + +void js_free (JSContext *ctx, void *ptr) { + /* Bump allocator doesn't free individual allocations - GC handles it */ + (void)ctx; + (void)ptr; +} + +/* Parser memory functions - use system allocator to avoid GC issues */ + + +/* Forward declarations for ppretext_end */ +JSText *js_alloc_string (JSContext *ctx, int max_len); +static inline void string_put (JSText *p, int idx, uint32_t c); + +PPretext *ppretext_init (int capacity) { + PPretext *p = pjs_malloc (sizeof (PPretext)); + if (!p) return NULL; + if (capacity <= 0) capacity = 32; + p->data = pjs_malloc (capacity * sizeof (uint32_t)); + if (!p->data) { pjs_free (p); return NULL; } + p->len = 0; + p->cap = capacity; + return p; +} + +void ppretext_free (PPretext *p) { + if (p) { + pjs_free (p->data); + pjs_free (p); + } +} + +no_inline PPretext *ppretext_realloc (PPretext *p, int new_cap) { + uint32_t *new_data = pjs_realloc (p->data, new_cap * sizeof (uint32_t)); + if (!new_data) return NULL; + p->data = new_data; + p->cap = new_cap; + return p; +} + +PPretext *ppretext_putc (PPretext *p, uint32_t c) { + if (p->len >= p->cap) { + int new_cap = max_int (p->len + 1, p->cap * 3 / 2); + if (!ppretext_realloc (p, new_cap)) return NULL; + } + p->data[p->len++] = c; + return p; +} + +JSValue ppretext_end (JSContext *ctx, PPretext *p) { + if (!p) return JS_EXCEPTION; + int len = p->len; + if (len == 0) { + ppretext_free (p); + return JS_KEY_empty; + } + + /* Allocate heap string (single allocation) */ + JSText *str = js_alloc_string (ctx, len); + if (!str) { + ppretext_free (p); + return JS_EXCEPTION; + } + for (int i = 0; i < len; i++) { + string_put (str, i, p->data[i]); + } + str->hdr = objhdr_set_cap56 (str->hdr, len); + str->hdr = objhdr_set_s (str->hdr, true); + + ppretext_free (p); + return JS_MKPTR (str); +} + +no_inline int js_realloc_array (JSContext *ctx, void **parray, int elem_size, int *psize, int req_size) { + int new_size; + void *new_array; + void *old_array = *parray; + int old_size = *psize; + + /* XXX: potential arithmetic overflow */ + new_size = max_int (req_size, old_size * 3 / 2); + + /* Protect source object with a GC ref before allocating (GC may move it) */ + JSGCRef src_ref; + JS_PushGCRef (ctx, &src_ref); + if (old_array) { + src_ref.val = JS_MKPTR (old_array); + } + + new_array = js_malloc (ctx, new_size * elem_size); + if (!new_array) { + JS_PopGCRef (ctx, &src_ref); + return -1; + } + + /* Get possibly-moved source pointer after GC */ + if (old_array) { + old_array = (void *)chase (src_ref.val); + memcpy (new_array, old_array, old_size * elem_size); + } + JS_PopGCRef (ctx, &src_ref); + + *psize = new_size; + *parray = new_array; + return 0; +} + + + +/* Append a JSValue string to a PPretext (parser pretext) */ +PPretext *ppretext_append_jsvalue (PPretext *p, JSValue str) { + int len = js_string_value_len (str); + for (int i = 0; i < len; i++) { + uint32_t c = js_string_value_get (str, i); + p = ppretext_putc (p, c); + if (!p) return NULL; + } + return p; +} + +/* Append an integer to a PPretext */ +PPretext *ppretext_append_int (PPretext *p, int n) { + char buf[16]; + int len = snprintf (buf, sizeof (buf), "%d", n); + for (int i = 0; i < len; i++) { + p = ppretext_putc (p, buf[i]); + if (!p) return NULL; + } + return p; +} + +/* Convert a JSValue string to a property key. + For immediates, returns the value as-is (can be used directly as keys). + For heap strings, returns interned version. */ +JSValue js_key_from_string (JSContext *ctx, JSValue val) { + if (MIST_IsImmediateASCII (val)) { + return val; /* Immediates can be used directly as keys */ + } + if (JS_IsText (val)) { + JSText *p = JS_VALUE_GET_TEXT (val); + int64_t len = JSText_len (p); /* Use JSText_len which checks header for stoned text */ + /* Extract UTF-32 characters and intern */ + uint32_t *utf32_buf = alloca (len * sizeof (uint32_t)); + for (int64_t i = 0; i < len; i++) { + utf32_buf[i] = string_get (p, i); + } + return intern_text_to_value (ctx, utf32_buf, len); + } + return JS_NULL; +} + +static JSClass const js_std_class_def[] = { + { "error", NULL, NULL }, /* JS_CLASS_ERROR */ + { "regexp", js_regexp_finalizer, NULL }, /* JS_CLASS_REGEXP */ + { "blob", NULL, NULL }, /* JS_CLASS_BLOB - registered separately */ +}; + +static int init_class_range (JSContext *ctx, JSClass const *tab, int start, int count) { + JSClassDef cm_s, *cm = &cm_s; + int i, class_id; + + for (i = 0; i < count; i++) { + class_id = i + start; + memset (cm, 0, sizeof (*cm)); + cm->finalizer = tab[i].finalizer; + cm->gc_mark = tab[i].gc_mark; + if (JS_NewClass1 (ctx, class_id, cm, tab[i].class_name) < 0) return -1; + } + return 0; +} + + +/* Destroy buddy allocator and free pool */ +void buddy_destroy (BuddyAllocator *b) { + if (!b->initialized) return; + + free (b->base); + b->base = NULL; + b->initialized = 0; + for (int i = 0; i < BUDDY_LEVELS; i++) { + b->free_lists[i] = NULL; + } +} + +/* ============================================================ + Heap block allocation wrappers + In POISON_HEAP mode, use malloc so poisoned memory stays poisoned. + Otherwise use buddy allocator for efficiency. + ============================================================ */ + +static void *heap_block_alloc(JSRuntime *rt, size_t size) { +#ifdef POISON_HEAP + (void)rt; + return malloc(size); +#else + return buddy_alloc(&rt->buddy, size); +#endif +} + +static void heap_block_free(JSRuntime *rt, void *ptr, size_t size) { +#ifdef POISON_HEAP + (void)rt; + (void)size; + /* Don't free - leave it poisoned to catch stale accesses */ + gc_poison_region(ptr, size); +#else + buddy_free(&rt->buddy, ptr, size); +#endif +} + +/* ============================================================ + Bump Allocator and Cheney GC + ============================================================ */ + +/* Forward declarations for GC helpers */ +int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size); +JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *from_end, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end); +void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8_t *from_end, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end); +size_t gc_object_size (void *ptr); + +/* Alignment for GC object sizes - must match max alignment requirement */ +#define GC_ALIGN 8 +static inline size_t gc_align_up (size_t n) { return (n + GC_ALIGN - 1) & ~(GC_ALIGN - 1); } + +/* Get size of a heap object based on its type */ +size_t gc_object_size (void *ptr) { + objhdr_t hdr = *(objhdr_t *)ptr; + uint8_t type = objhdr_type (hdr); + uint64_t cap = objhdr_cap56 (hdr); + + switch (type) { + case OBJ_ARRAY: { + /* JSArray + inline values array. Cap is element capacity. */ + size_t values_size = sizeof (JSValue) * cap; + return gc_align_up (sizeof (JSArray) + values_size); + } + case OBJ_TEXT: { + /* JSText: header + pad + hdr + length + packed chars */ + size_t word_count = (cap + 1) / 2; + return gc_align_up (sizeof (JSText) + word_count * sizeof (uint64_t)); + } + case OBJ_RECORD: { + /* JSRecord + inline tab. Cap is mask, so tab size is mask+1 entries. */ + size_t tab_size = sizeof (JSRecordEntry) * (cap + 1); + return gc_align_up (sizeof (JSRecord) + tab_size); + } + case OBJ_FUNCTION: + return gc_align_up (sizeof (JSFunction)); + case OBJ_FRAME: { + /* JSFrame + slots array. cap56 stores slot count */ + uint64_t slot_count = cap; + return gc_align_up (sizeof (JSFrame) + slot_count * sizeof (JSValue)); + } + default: + /* Unknown type - fatal error, heap is corrupt or scan desync'd */ + fflush(stdout); + fprintf (stderr, "gc_object_size: unknown type %d at %p, hdr=0x%llx cap=%llu\n", + type, ptr, (unsigned long long)hdr, (unsigned long long)cap); + /* Dump surrounding memory for debugging */ + uint64_t *words = (uint64_t *)ptr; + fprintf (stderr, " words: [0]=0x%llx [1]=0x%llx [2]=0x%llx [3]=0x%llx\n", + (unsigned long long)words[0], (unsigned long long)words[1], + (unsigned long long)words[2], (unsigned long long)words[3]); + fflush(stderr); + abort (); + } +} +static inline int ptr_in_range (void *p, uint8_t *b, uint8_t *e) { + uint8_t *q = (uint8_t *)p; + return q >= b && q < e; +} + +JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *from_end, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) { + if (!JS_IsPtr (v)) return v; + + for (;;) { + void *ptr = JS_VALUE_GET_PTR (v); + if (is_stone_ptr (ctx, ptr)) return v; + + if (!ptr_in_range (ptr, from_base, from_end)) return v; + + objhdr_t *hdr_ptr = (objhdr_t *)ptr; + objhdr_t hdr = *hdr_ptr; + uint8_t type = objhdr_type (hdr); + + if (type == OBJ_FORWARD) { + void *t = objhdr_fwd_ptr (hdr); + + /* If it already points into to-space, it's a GC forward. */ + if (ptr_in_range (t, to_base, *to_free)) return JS_MKPTR (t); + + /* Otherwise it's a growth-forward (still in from-space). Chase. */ + v = JS_MKPTR (t); + continue; + } + + if (type != OBJ_ARRAY && type != OBJ_TEXT && type != OBJ_RECORD && type != OBJ_FUNCTION && type != OBJ_BLOB && type != OBJ_CODE && type != OBJ_FRAME) { + fprintf (stderr, "gc_copy_value: invalid object type %d at %p (hdr=0x%llx)\n", type, ptr, (unsigned long long)hdr); + fprintf (stderr, " This may be an interior pointer or corrupt root\n"); + fflush (stderr); + abort (); + } + + size_t size = gc_object_size (hdr_ptr); + if (*to_free + size > to_end) { + fprintf (stderr, "gc_copy_value: out of to-space, need %zu bytes\n", size); + abort (); + } + + void *new_ptr = *to_free; + memcpy (new_ptr, hdr_ptr, size); + *to_free += size; + + *hdr_ptr = objhdr_make_fwd (new_ptr); + + return JS_MKPTR (new_ptr); + } +} + +/* Scan a copied object and update its internal references */ +void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8_t *from_end, + uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) { + objhdr_t hdr = *(objhdr_t *)ptr; + uint8_t type = objhdr_type (hdr); + + switch (type) { + case OBJ_ARRAY: { + JSArray *arr = (JSArray *)ptr; + for (uint32_t i = 0; i < arr->len; i++) { + arr->values[i] = gc_copy_value (ctx, arr->values[i], from_base, from_end, to_base, to_free, to_end); + } + break; + } + case OBJ_RECORD: { + JSRecord *rec = (JSRecord *)ptr; + uint32_t mask = (uint32_t)objhdr_cap56 (rec->mist_hdr); +#ifdef DUMP_GC_DETAIL + printf(" record: slots=%u used=%u proto=%p\n", mask + 1, (uint32_t)rec->len, (void*)rec->proto); + fflush(stdout); +#endif + /* Copy prototype */ + if (rec->proto) { + JSValue proto_val = JS_MKPTR (rec->proto); + proto_val = gc_copy_value (ctx, proto_val, from_base, from_end, to_base, to_free, to_end); + rec->proto = (JSRecord *)JS_VALUE_GET_PTR (proto_val); + } + /* Copy table entries */ + for (uint32_t i = 0; i <= mask; i++) { + JSValue k = rec->slots[i].key; + if (!rec_key_is_empty (k) && !rec_key_is_tomb (k)) { + rec->slots[i].key = gc_copy_value (ctx, k, from_base, from_end, to_base, to_free, to_end); + rec->slots[i].val = gc_copy_value (ctx, rec->slots[i].val, from_base, from_end, to_base, to_free, to_end); + } + } + break; + } + case OBJ_FUNCTION: { + JSFunction *fn = (JSFunction *)ptr; + /* Scan the function name */ + fn->name = gc_copy_value (ctx, fn->name, from_base, from_end, to_base, to_free, to_end); + /* Scan bytecode's cpool - it contains JSValues that may reference GC objects */ + if (fn->kind == JS_FUNC_KIND_BYTECODE && fn->u.func.function_bytecode) { + JSFunctionBytecode *b = fn->u.func.function_bytecode; + /* Scan cpool entries */ + for (int i = 0; i < b->cpool_count; i++) { + b->cpool[i] = gc_copy_value (ctx, b->cpool[i], from_base, from_end, to_base, to_free, to_end); + } + /* Scan func_name and filename */ + b->func_name = gc_copy_value (ctx, b->func_name, from_base, from_end, to_base, to_free, to_end); + if (b->has_debug) { + b->debug.filename = gc_copy_value (ctx, b->debug.filename, from_base, from_end, to_base, to_free, to_end); + } + /* Scan outer_frame (for closures) - it's already a JSValue */ + fn->u.func.outer_frame = gc_copy_value (ctx, fn->u.func.outer_frame, from_base, from_end, to_base, to_free, to_end); + /* Scan env_record (stone record / module environment) */ + fn->u.func.env_record = gc_copy_value (ctx, fn->u.func.env_record, from_base, from_end, to_base, to_free, to_end); + } else if (fn->kind == JS_FUNC_KIND_REGISTER && fn->u.reg.code) { + /* Register VM function - scan cpool (off-heap but contains JSValues) */ + JSCodeRegister *code = fn->u.reg.code; + for (uint32_t i = 0; i < code->cpool_count; i++) { + code->cpool[i] = gc_copy_value (ctx, code->cpool[i], from_base, from_end, to_base, to_free, to_end); + } + /* Scan function name */ + code->name = gc_copy_value (ctx, code->name, from_base, from_end, to_base, to_free, to_end); + /* Scan outer_frame and env_record */ + fn->u.reg.outer_frame = gc_copy_value (ctx, fn->u.reg.outer_frame, from_base, from_end, to_base, to_free, to_end); + fn->u.reg.env_record = gc_copy_value (ctx, fn->u.reg.env_record, from_base, from_end, to_base, to_free, to_end); + /* Recursively scan nested function cpools */ + for (uint32_t i = 0; i < code->func_count; i++) { + if (code->functions[i]) { + JSCodeRegister *nested = code->functions[i]; + for (uint32_t j = 0; j < nested->cpool_count; j++) { + nested->cpool[j] = gc_copy_value (ctx, nested->cpool[j], from_base, from_end, to_base, to_free, to_end); + } + nested->name = gc_copy_value (ctx, nested->name, from_base, from_end, to_base, to_free, to_end); + } + } + } else if (fn->kind == JS_FUNC_KIND_MCODE) { + /* MCODE function - scan outer_frame and env_record */ + fn->u.mcode.outer_frame = gc_copy_value (ctx, fn->u.mcode.outer_frame, from_base, from_end, to_base, to_free, to_end); + fn->u.mcode.env_record = gc_copy_value (ctx, fn->u.mcode.env_record, from_base, from_end, to_base, to_free, to_end); + } + break; + } + case OBJ_TEXT: + case OBJ_BLOB: + /* No internal references to scan */ + break; + case OBJ_CODE: { + /* JSFunctionBytecode - scan func_name and filename */ + JSFunctionBytecode *bc = (JSFunctionBytecode *)ptr; + bc->func_name = gc_copy_value (ctx, bc->func_name, from_base, from_end, to_base, to_free, to_end); + if (bc->has_debug) { + bc->debug.filename = gc_copy_value (ctx, bc->debug.filename, from_base, from_end, to_base, to_free, to_end); + } + /* Note: cpool, vardefs, closure_var, byte_code_buf are allocated via js_malloc */ + break; + } + case OBJ_FRAME: { + /* JSFrame - scan function, caller, and slots */ + JSFrame *frame = (JSFrame *)ptr; + /* function and caller are now JSValues - copy them directly */ + frame->function = gc_copy_value (ctx, frame->function, from_base, from_end, to_base, to_free, to_end); + frame->caller = gc_copy_value (ctx, frame->caller, from_base, from_end, to_base, to_free, to_end); + /* Scan all slots */ + uint64_t slot_count = objhdr_cap56 (frame->header); + for (uint64_t i = 0; i < slot_count; i++) { + frame->slots[i] = gc_copy_value (ctx, frame->slots[i], from_base, from_end, to_base, to_free, to_end); + } + break; + } + default: + /* Unknown type during scan - fatal error */ + fprintf (stderr, "gc_scan_object: unknown object type %d at %p\n", type, ptr); + abort (); + } +} + +/* Forward declaration - defined after JSFunctionDef */ +void gc_scan_parser_cpool (JSContext *ctx, uint8_t *from_base, uint8_t *from_end, + uint8_t *to_base, uint8_t **to_free, uint8_t *to_end); + +/* Scan OBJ_CODE cpool for bytecode objects outside GC heap */ +void gc_scan_bytecode_cpool (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *from_end, + uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) { + if (!JS_IsPtr (v)) return; + void *ptr = JS_VALUE_GET_PTR (v); + if (ptr_in_range (ptr, from_base, from_end)) return; /* On GC heap, handled normally */ + if (is_stone_ptr (ctx, ptr)) return; /* Stone memory */ + + /* Check if this is an OBJ_CODE outside GC heap */ + objhdr_t hdr = *(objhdr_t *)ptr; + if (objhdr_type (hdr) == OBJ_CODE) { + JSFunctionBytecode *bc = (JSFunctionBytecode *)ptr; + /* Scan cpool entries */ + for (int i = 0; i < bc->cpool_count; i++) { + bc->cpool[i] = gc_copy_value (ctx, bc->cpool[i], from_base, from_end, to_base, to_free, to_end); + } + /* Scan func_name and filename */ + bc->func_name = gc_copy_value (ctx, bc->func_name, from_base, from_end, to_base, to_free, to_end); + if (bc->has_debug) { + bc->debug.filename = gc_copy_value (ctx, bc->debug.filename, from_base, from_end, to_base, to_free, to_end); + } + } +} + +/* Cheney copying GC - collect garbage and compact live objects + allow_grow: if true, grow heap when recovery is poor + alloc_size: the allocation that triggered GC — used to size the new block */ +int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) { + JSRuntime *rt = ctx->rt; + size_t old_used = ctx->heap_free - ctx->heap_base; + size_t old_heap_size = ctx->current_block_size; + + /* Save OLD heap bounds before allocating new block + Use heap_free (not heap_end) as from_end - only the portion up to heap_free + contains valid objects. Beyond heap_free is uninitialized garbage. */ + uint8_t *from_base = ctx->heap_base; + uint8_t *from_end = ctx->heap_free; + +#ifdef DUMP_GC_DETAIL + printf("ctx_gc: from_base=%p from_end=%p size=%zu\n", (void*)from_base, (void*)from_end, old_heap_size); +#endif + + /* Request new block from runtime. + When allow_grow is set and the pending allocation won't fit in the + current next_block_size, jump straight to a block that can hold + live_data + alloc_size instead of doubling one step at a time. */ + size_t new_size = ctx->next_block_size; + if (allow_grow) { + size_t live_est = (size_t)(from_end - from_base); /* upper bound on live data */ + size_t need = live_est + alloc_size; + while (new_size < need && new_size < (1ULL << BUDDY_MAX_ORDER)) + new_size *= 2; + } + uint8_t *new_block = heap_block_alloc (rt, new_size); + if (!new_block) { + /* Try with same size */ + new_size = ctx->current_block_size; + new_block = heap_block_alloc (rt, new_size); + if (!new_block) return -1; + } + + uint8_t *to_base = new_block; + uint8_t *to_free = new_block; + uint8_t *to_end = new_block + new_size; + + /* Copy roots: global object, class prototypes, exception, etc. */ +#ifdef DUMP_GC_DETAIL + printf(" roots: global_obj = 0x%llx\n", (unsigned long long)ctx->global_obj); fflush(stdout); + if (JS_IsPtr(ctx->global_obj)) { + void *gptr = JS_VALUE_GET_PTR(ctx->global_obj); + printf(" ptr=%p in_from=%d is_stone=%d\n", gptr, + ((uint8_t*)gptr >= from_base && (uint8_t*)gptr < from_end), + is_stone_ptr(ctx, gptr)); + fflush(stdout); + } +#endif + ctx->global_obj = gc_copy_value (ctx, ctx->global_obj, from_base, from_end, to_base, &to_free, to_end); +#ifdef DUMP_GC_DETAIL + printf(" after copy: global_obj = 0x%llx\n", (unsigned long long)ctx->global_obj); fflush(stdout); +#endif +#ifdef DUMP_GC_DETAIL + printf(" roots: regexp_ctor\n"); fflush(stdout); +#endif + ctx->regexp_ctor = gc_copy_value (ctx, ctx->regexp_ctor, from_base, from_end, to_base, &to_free, to_end); +#ifdef DUMP_GC_DETAIL + printf(" roots: throw_type_error\n"); fflush(stdout); +#endif + ctx->throw_type_error = gc_copy_value (ctx, ctx->throw_type_error, from_base, from_end, to_base, &to_free, to_end); + + /* Copy current exception if pending */ +#ifdef DUMP_GC_DETAIL + printf(" roots: current_exception\n"); fflush(stdout); +#endif + ctx->current_exception = gc_copy_value (ctx, ctx->current_exception, from_base, from_end, to_base, &to_free, to_end); + +#ifdef DUMP_GC_DETAIL + printf(" roots: native_error_proto\n"); fflush(stdout); +#endif + for (int i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { + ctx->native_error_proto[i] = gc_copy_value (ctx, ctx->native_error_proto[i], from_base, from_end, to_base, &to_free, to_end); + } + + /* Copy class prototypes */ +#ifdef DUMP_GC_DETAIL + printf(" roots: class_proto (count=%d)\n", ctx->class_count); fflush(stdout); +#endif + for (int i = 0; i < ctx->class_count; i++) { + ctx->class_proto[i] = gc_copy_value (ctx, ctx->class_proto[i], from_base, from_end, to_base, &to_free, to_end); + } + + /* Copy value stack */ +#ifdef DUMP_GC_DETAIL + printf(" roots: value_stack (top=%d)\n", ctx->value_stack_top); fflush(stdout); +#endif + for (int i = 0; i < ctx->value_stack_top; i++) { + ctx->value_stack[i] = gc_copy_value (ctx, ctx->value_stack[i], from_base, from_end, to_base, &to_free, to_end); + } + + /* Copy frame stack references */ +#ifdef DUMP_GC_DETAIL + printf(" roots: frame_stack (top=%d)\n", ctx->frame_stack_top); fflush(stdout); +#endif + for (int i = 0; i <= ctx->frame_stack_top; i++) { + struct VMFrame *frame = &ctx->frame_stack[i]; + frame->cur_func = gc_copy_value (ctx, frame->cur_func, from_base, from_end, to_base, &to_free, to_end); + frame->this_obj = gc_copy_value (ctx, frame->this_obj, from_base, from_end, to_base, &to_free, to_end); + } + + /* Copy register VM current frame */ +#ifdef DUMP_GC_DETAIL + printf(" roots: reg_current_frame\n"); fflush(stdout); +#endif + ctx->reg_current_frame = gc_copy_value (ctx, ctx->reg_current_frame, from_base, from_end, to_base, &to_free, to_end); + + /* Copy JSStackFrame chain (C stack frames) */ +#ifdef DUMP_GC_DETAIL + printf(" roots: current_stack_frame chain\n"); fflush(stdout); +#endif + for (JSStackFrame *sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) { + sf->cur_func = gc_copy_value (ctx, sf->cur_func, from_base, from_end, to_base, &to_free, to_end); + /* Also scan bytecode cpool if it's a bytecode object outside GC heap */ + gc_scan_bytecode_cpool (ctx, sf->cur_func, from_base, from_end, to_base, &to_free, to_end); + /* Scan arg_buf and var_buf contents - they're on C stack but hold JSValues */ + if (JS_IsFunction(sf->cur_func)) { + JSFunction *fn = JS_VALUE_GET_FUNCTION(sf->cur_func); + if (fn->kind == JS_FUNC_KIND_BYTECODE && fn->u.func.function_bytecode) { + JSFunctionBytecode *b = fn->u.func.function_bytecode; + /* Scan arg_buf */ + if (sf->arg_buf) { + for (int i = 0; i < sf->arg_count; i++) { + sf->arg_buf[i] = gc_copy_value(ctx, sf->arg_buf[i], from_base, from_end, to_base, &to_free, to_end); + } + } + /* Scan var_buf */ + if (sf->var_buf) { + for (int i = 0; i < b->var_count; i++) { + sf->var_buf[i] = gc_copy_value(ctx, sf->var_buf[i], from_base, from_end, to_base, &to_free, to_end); + } + } + /* Scan js_frame if present - it's on regular heap but contains JSValues pointing to GC heap */ + sf->js_frame = gc_copy_value(ctx, sf->js_frame, from_base, from_end, to_base, &to_free, to_end); + if (!JS_IsNull(sf->js_frame)) { + JSFrame *jf = JS_VALUE_GET_FRAME(sf->js_frame); + jf->function = gc_copy_value(ctx, jf->function, from_base, from_end, to_base, &to_free, to_end); + jf->caller = gc_copy_value(ctx, jf->caller, from_base, from_end, to_base, &to_free, to_end); + /* Note: jf->slots are same as arg_buf/var_buf, already scanned above */ + } + /* Scan operand stack */ + if (sf->stack_buf && sf->p_sp) { + JSValue *sp = *sf->p_sp; + for (JSValue *p = sf->stack_buf; p < sp; p++) { + *p = gc_copy_value(ctx, *p, from_base, from_end, to_base, &to_free, to_end); + } + } + } + } + } + + /* Copy JS_PUSH_VALUE/JS_POP_VALUE roots */ +#ifdef DUMP_GC_DETAIL + printf(" roots: top_gc_ref\n"); fflush(stdout); +#endif + for (JSGCRef *ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) { + gc_scan_bytecode_cpool (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); + ref->val = gc_copy_value (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); + } + + /* Copy JS_AddGCRef/JS_DeleteGCRef roots */ +#ifdef DUMP_GC_DETAIL + printf(" roots: last_gc_ref\n"); fflush(stdout); +#endif + for (JSGCRef *ref = ctx->last_gc_ref; ref != NULL; ref = ref->prev) { + gc_scan_bytecode_cpool (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); + ref->val = gc_copy_value (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); + } + + /* Scan parser's cpool (if parsing is in progress) */ + if (ctx->current_parse_fd) { + gc_scan_parser_cpool (ctx, from_base, from_end, to_base, &to_free, to_end); + } + + /* Cheney scan: scan copied objects to find more references */ + uint8_t *scan = to_base; +#ifdef DUMP_GC_DETAIL + printf(" scan: to_base=%p to_free=%p to_end=%p\n", (void*)to_base, (void*)to_free, (void*)to_end); + fflush(stdout); +#endif + while (scan < to_free) { +#ifdef DUMP_GC_DETAIL + objhdr_t scan_hdr = *(objhdr_t *)scan; + printf(" scan %p: type=%d hdr=0x%llx", (void*)scan, objhdr_type(scan_hdr), (unsigned long long)scan_hdr); + fflush(stdout); +#endif + size_t obj_size = gc_object_size (scan); +#ifdef DUMP_GC_DETAIL + printf(" size=%zu\n", obj_size); + fflush(stdout); +#endif + gc_scan_object (ctx, scan, from_base, from_end, to_base, &to_free, to_end); + scan += obj_size; + } + + /* Return old block (in poison mode, just poison it and leak) */ + heap_block_free (rt, from_base, old_heap_size); + + /* Update context with new block */ + size_t new_used = to_free - to_base; + size_t recovered = old_used > new_used ? old_used - new_used : 0; + + ctx->heap_base = to_base; + ctx->heap_free = to_free; + ctx->heap_end = to_end; + ctx->current_block_size = new_size; + +#ifdef DUMP_GC_DETAIL + /* Verify global_obj is in valid heap range after GC */ + if (JS_IsPtr(ctx->global_obj)) { + void *gptr = JS_VALUE_GET_PTR(ctx->global_obj); + if ((uint8_t*)gptr < to_base || (uint8_t*)gptr >= to_free) { + printf(" WARNING: global_obj=%p outside [%p, %p) after GC!\n", + gptr, (void*)to_base, (void*)to_free); + fflush(stdout); + } + } +#endif + + /* If <20% recovered, double next block size for future allocations + But only if allow_grow is set (i.e., GC was triggered due to low space) */ +#ifdef DUMP_GC + int will_grow = 0; +#endif + if (allow_grow && old_used > 0 && recovered < old_used / 5) { + size_t doubled = new_size * 2; + if (doubled <= (1ULL << BUDDY_MAX_ORDER)) { + ctx->next_block_size = doubled; +#ifdef DUMP_GC + will_grow = 1; +#endif + } + } + +#ifdef DUMP_GC + printf ("\nGC: %zu -> %zu bytes, recovered %zu (%.1f%%)%s\n", + old_heap_size, + new_size, + recovered, + old_used > 0 ? (recovered * 100.0 / old_used) : 0.0, + will_grow ? ", heap will grow" : ""); +#endif + + return 0; +} + +JSRuntime *JS_NewRuntime (void) { + JSRuntime *rt; + + rt = malloc (sizeof (JSRuntime)); + if (!rt) return NULL; + memset (rt, 0, sizeof (*rt)); + + return rt; +} + +void *JS_GetRuntimeOpaque (JSRuntime *rt) { return rt->user_opaque; } + +void JS_SetRuntimeOpaque (JSRuntime *rt, void *opaque) { + rt->user_opaque = opaque; +} + +void JS_SetMemoryLimit (JSRuntime *rt, size_t limit) { + rt->malloc_limit = limit; +} + +/* Helpers to call system memory functions (for memory allocated by external libs) */ + +#define malloc(s) malloc_is_forbidden (s) +#define free(p) free_is_forbidden (p) +#define realloc(p, s) realloc_is_forbidden (p, s) + +void JS_SetInterruptHandler (JSContext *ctx, JSInterruptHandler *cb, void *opaque) { + ctx->interrupt_handler = cb; + ctx->interrupt_opaque = opaque; +} + +void JS_SetStripInfo (JSRuntime *rt, int flags) { rt->strip_flags = flags; } +int JS_GetStripInfo (JSRuntime *rt) { return rt->strip_flags; } + +/* Allocate a string using bump allocation from context heap. + Note: the string contents are uninitialized */ +JSText *js_alloc_string (JSContext *ctx, int max_len) { + JSText *str; + /* Allocate packed UTF-32: 2 chars per 64-bit word. */ + size_t data_words = (max_len + 1) / 2; + size_t size = sizeof (JSText) + data_words * sizeof (uint64_t); + + str = js_malloc (ctx, size); + if (unlikely (!str)) { + JS_ThrowOutOfMemory (ctx); + return NULL; + } + /* Initialize objhdr_t with OBJ_TEXT type and capacity in cap56 */ + str->hdr = objhdr_make (max_len, OBJ_TEXT, false, false, false, false); + str->length = 0; /* length starts at 0, capacity is in hdr */ + + return str; +} + +void JS_SetRuntimeInfo (JSRuntime *rt, const char *s) { + if (rt) rt->rt_info = s; +} + +void JS_FreeRuntime (JSRuntime *rt) { + /* Destroy buddy allocator */ + buddy_destroy (&rt->buddy); + sys_free (rt); +} + +/* Forward declarations for intrinsics */ +static void JS_AddIntrinsicBasicObjects (JSContext *ctx); +static void JS_AddIntrinsicBaseObjects (JSContext *ctx); +static void JS_AddIntrinsicRegExp (JSContext *ctx); + +JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) { + JSContext *ctx; + + /* Round up to buddy allocator minimum */ + size_t min_size = 1ULL << BUDDY_MIN_ORDER; + if (heap_size < min_size) heap_size = min_size; + + /* Round up to power of 2 for buddy allocator */ + size_t actual = min_size; + while (actual < heap_size && actual < (1ULL << BUDDY_MAX_ORDER)) { + actual <<= 1; + } + heap_size = actual; + + ctx = js_mallocz_rt (sizeof (JSContext)); + + if (!ctx) return NULL; + ctx->trace_hook = NULL; + ctx->rt = rt; + + /* Bootstrap class_array and class_proto together via JS_NewClass1 */ + if (init_class_range (ctx, js_std_class_def, JS_CLASS_OBJECT, countof (js_std_class_def)) < 0) { + js_free_rt (ctx->class_array); + js_free_rt (ctx->class_proto); + js_free_rt (ctx); + return NULL; + } + + ctx->regexp_ctor = JS_NULL; + + /* Initialize VM stacks for trampoline */ + ctx->frame_stack_capacity = 512; + ctx->frame_stack + = js_malloc_rt (sizeof (struct VMFrame) * ctx->frame_stack_capacity); + if (!ctx->frame_stack) { + js_free_rt (ctx->class_array); + js_free_rt (ctx->class_proto); + js_free_rt (ctx); + return NULL; + } + ctx->frame_stack_top = -1; + + ctx->value_stack_capacity = 65536; /* 64K JSValue slots */ + ctx->value_stack + = js_malloc_rt (sizeof (JSValue) * ctx->value_stack_capacity); + if (!ctx->value_stack) { + js_free_rt (ctx->frame_stack); + js_free_rt (ctx->class_array); + js_free_rt (ctx->class_proto); + js_free_rt (ctx); + return NULL; + } + ctx->value_stack_top = 0; + + /* Initialize register VM frame root */ + ctx->reg_current_frame = JS_NULL; + + /* Initialize per-context execution state (moved from JSRuntime) */ + ctx->current_exception = JS_UNINITIALIZED; + ctx->current_stack_frame = NULL; + ctx->stack_size = JS_DEFAULT_STACK_SIZE; + JS_UpdateStackTop (ctx); + + /* Initialize stone text intern table */ + ctx->st_pages = NULL; + ctx->st_text_array = NULL; + ctx->st_text_hash = NULL; + ctx->st_text_count = 0; + ctx->st_text_size = 0; + ctx->st_text_resize = 0; + if (st_text_resize (ctx) < 0) { + js_free_rt (ctx->value_stack); + js_free_rt (ctx->frame_stack); + js_free_rt (ctx->class_array); + js_free_rt (ctx->class_proto); + js_free_rt (ctx); + return NULL; + } + + /* Allocate initial heap block for bump allocation */ + ctx->current_block_size = heap_size; + ctx->next_block_size = ctx->current_block_size; + ctx->heap_base = heap_block_alloc (rt, ctx->current_block_size); + if (!ctx->heap_base) { + js_free_rt (ctx->st_text_hash); + js_free_rt (ctx->st_text_array); + js_free_rt (ctx->value_stack); + js_free_rt (ctx->frame_stack); + js_free_rt (ctx->class_array); + js_free_rt (ctx->class_proto); + js_free_rt (ctx); + return NULL; + } + ctx->heap_free = ctx->heap_base; + ctx->heap_end = ctx->heap_base + ctx->current_block_size; + +#ifdef DUMP_GC_DETAIL + printf("Context init: heap_base=%p heap_end=%p size=%zu\n", (void*)ctx->heap_base, (void*)ctx->heap_end, ctx->current_block_size); +#endif + +#ifdef DUMP_GC_DETAIL + printf("After BasicObjects: heap_base=%p heap_end=%p heap_free=%p\n", (void*)ctx->heap_base, (void*)ctx->heap_end, (void*)ctx->heap_free); +#endif + + return ctx; +} + +JSContext *JS_NewContextRaw (JSRuntime *rt) { + return JS_NewContextRawWithHeapSize (rt, 1ULL << BUDDY_MIN_ORDER); +} + +static void JS_AddIntrinsics (JSContext *ctx) { + JS_AddIntrinsicBaseObjects (ctx); + JS_AddIntrinsicRegExp (ctx); +} + +JSContext *JS_NewContext (JSRuntime *rt) { + JSContext *ctx = JS_NewContextRaw (rt); + if (!ctx) return NULL; + JS_AddIntrinsics (ctx); + return ctx; +} + +JSContext *JS_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size) { + JSContext *ctx = JS_NewContextRawWithHeapSize (rt, heap_size); + if (!ctx) return NULL; + JS_AddIntrinsics (ctx); + return ctx; +} + +void *JS_GetContextOpaque (JSContext *ctx) { return ctx->user_opaque; } + +void JS_SetContextOpaque (JSContext *ctx, void *opaque) { + ctx->user_opaque = opaque; +} + + +void JS_SetClassProto (JSContext *ctx, JSClassID class_id, JSValue obj) { + assert (class_id < ctx->class_count); + set_value (ctx, &ctx->class_proto[class_id], obj); +} + +JSValue JS_GetClassProto (JSContext *ctx, JSClassID class_id) { + assert (class_id < ctx->class_count); + return ctx->class_proto[class_id]; +} + +void JS_FreeContext (JSContext *ctx) { + JSRuntime *rt = ctx->rt; + int i; + +#ifdef DUMP_MEM + { + JSMemoryUsage stats; + JS_ComputeMemoryUsage (rt, &stats); + JS_DumpMemoryUsage (stdout, &stats, rt); + } +#endif + + for (i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { + } + for (i = 0; i < ctx->class_count; i++) { + } + js_free_rt (ctx->class_array); + js_free_rt (ctx->class_proto); + + /* Free VM stacks */ + if (ctx->frame_stack) js_free_rt (ctx->frame_stack); + if (ctx->value_stack) js_free_rt (ctx->value_stack); + + /* Free stone arena and intern table */ + st_free_all (ctx); + js_free_rt (ctx->st_text_hash); + js_free_rt (ctx->st_text_array); + + /* Free heap block */ + if (ctx->heap_base) { + heap_block_free (rt, ctx->heap_base, ctx->current_block_size); + ctx->heap_base = NULL; + ctx->heap_free = NULL; + ctx->heap_end = NULL; + } + + js_free_rt (ctx); +} + +JSRuntime *JS_GetRuntime (JSContext *ctx) { return ctx->rt; } + +static void update_stack_limit (JSContext *ctx) { + if (ctx->stack_size == 0) { + ctx->stack_limit = 0; /* no limit */ + } else { + ctx->stack_limit = ctx->stack_top - ctx->stack_size; + } +} + +void JS_SetMaxStackSize (JSContext *ctx, size_t stack_size) { + ctx->stack_size = stack_size; + update_stack_limit (ctx); +} + +void JS_UpdateStackTop (JSContext *ctx) { + ctx->stack_top = (const uint8_t *)js_get_stack_pointer (); + update_stack_limit (ctx); +} + +JSText *js_alloc_string (JSContext *ctx, int max_len); + +static inline int is_num (int c) { return c >= '0' && c <= '9'; } + +static __maybe_unused void JS_DumpChar (FILE *fo, int c, int sep) { + if (c == sep || c == '\\') { + fputc ('\\', fo); + fputc (c, fo); + } else if (c >= ' ' && c <= 126) { + fputc (c, fo); + } else if (c == '\n') { + fputc ('\\', fo); + fputc ('n', fo); + } else { + fprintf (fo, "\\u%04x", c); + } +} + +__maybe_unused void JS_DumpString (JSRuntime *rt, const JSText *p) { + int i; + + if (p == NULL) { + printf (""); + return; + } + putchar ('"'); + for (i = 0; i < (int)JSText_len (p); i++) { + JS_DumpChar (stdout, string_get (p, i), '"'); + } + putchar ('"'); +} + +static inline BOOL JS_IsEmptyString (JSValue v) { + return v == JS_EMPTY_TEXT; +} + +/* JSClass support */ + +/* a new class ID is allocated if *pclass_id != 0 */ +JSClassID JS_NewClassID (JSClassID *pclass_id) { + JSClassID class_id; + class_id = *pclass_id; + if (class_id == 0) { + class_id = js_class_id_alloc++; + *pclass_id = class_id; + } + return class_id; +} + +JSClassID JS_GetClassID (JSValue v) { + JSRecord *rec; + if (!JS_IsRecord (v)) return JS_INVALID_CLASS_ID; + rec = JS_VALUE_GET_RECORD (v); + return REC_GET_CLASS_ID(rec); +} + +BOOL JS_IsRegisteredClass (JSContext *ctx, JSClassID class_id) { + return (class_id < ctx->class_count + && ctx->class_array[class_id].class_id != 0); +} + +/* create a new object internal class. Return -1 if error, 0 if + OK. The finalizer can be NULL if none is needed. */ +int JS_NewClass1 (JSContext *ctx, JSClassID class_id, const JSClassDef *class_def, const char *name) { + int new_size, i; + JSClass *cl, *new_class_array; + JSValue *new_class_proto; + + if (class_id >= (1 << 16)) return -1; + if (class_id < ctx->class_count && ctx->class_array[class_id].class_id != 0) + return -1; + + if (class_id >= ctx->class_count) { + new_size = max_int (JS_CLASS_INIT_COUNT, + max_int (class_id + 1, ctx->class_count * 3 / 2)); + + /* reallocate the class array */ + new_class_array + = js_realloc_rt (ctx->class_array, sizeof (JSClass) * new_size); + if (!new_class_array) return -1; + memset (new_class_array + ctx->class_count, 0, (new_size - ctx->class_count) * sizeof (JSClass)); + ctx->class_array = new_class_array; + + /* reallocate the class proto array */ + new_class_proto + = js_realloc_rt (ctx->class_proto, sizeof (JSValue) * new_size); + if (!new_class_proto) return -1; + for (i = ctx->class_count; i < new_size; i++) + new_class_proto[i] = JS_NULL; + ctx->class_proto = new_class_proto; + + ctx->class_count = new_size; + } + cl = &ctx->class_array[class_id]; + cl->class_id = class_id; + cl->class_name = name; /* name is already a const char* */ + cl->finalizer = class_def->finalizer; + cl->gc_mark = class_def->gc_mark; + return 0; +} + +int JS_NewClass (JSContext *ctx, JSClassID class_id, const JSClassDef *class_def) { + /* class_name is stored directly as const char* */ + return JS_NewClass1 (ctx, class_id, class_def, class_def->class_name); +} + +JSValue js_new_string8_len (JSContext *ctx, const char *buf, int len) { + JSText *str; + int i; + + /* Empty string - return immediate empty */ + if (len <= 0) { return MIST_TryNewImmediateASCII ("", 0); } + + /* Try immediate ASCII for short strings (≤7 chars) */ + if (len <= MIST_ASCII_MAX_LEN) { + JSValue imm = MIST_TryNewImmediateASCII (buf, len); + if (!JS_IsNull (imm)) return imm; + } + + /* Fall back to heap string */ + str = js_alloc_string (ctx, len); + if (!str) return JS_ThrowMemoryError (ctx); + for (i = 0; i < len; i++) + string_put (str, i, buf[i]); + str->length = len; + + return pretext_end (ctx, str); +} + +JSValue js_new_string8 (JSContext *ctx, const char *buf) { + return js_new_string8_len (ctx, buf, strlen (buf)); +} + +/* GC-safe substring: takes JSValue (which must be rooted by caller) */ +static JSValue js_sub_string (JSContext *ctx, JSText *p, int start, int end) { + int i; + int len = end - start; + if (start == 0 && end == (int)JSText_len (p)) { + return JS_MKPTR (p); + } + + /* Root the source string as a JSValue so it survives js_alloc_string GC */ + JSGCRef src_ref; + JS_PushGCRef (ctx, &src_ref); + src_ref.val = JS_MKPTR (p); + + JSText *str = js_alloc_string (ctx, len); + if (!str) { + JS_PopGCRef (ctx, &src_ref); + return JS_EXCEPTION; + } + + /* Re-chase p after allocation */ + p = JS_VALUE_GET_STRING (src_ref.val); + + for (i = 0; i < len; i++) + string_put (str, i, string_get (p, start + i)); + str->length = len; + + JS_PopGCRef (ctx, &src_ref); + return pretext_end (ctx, str); +} + +/* Substring from a JSValue (handles both immediate ASCII and heap strings) */ +static JSValue js_sub_string_val (JSContext *ctx, JSValue src, int start, int end) { + int len = end - start; + if (len <= 0) return JS_NewString (ctx, ""); + + if (MIST_IsImmediateASCII (src)) { + /* IMM: extract chars directly, try to return IMM */ + if (len <= MIST_ASCII_MAX_LEN) { + char buf[MIST_ASCII_MAX_LEN + 1]; + for (int i = 0; i < len; i++) + buf[i] = (char)MIST_GetImmediateASCIIChar (src, start + i); + return js_new_string8_len (ctx, buf, len); + } + /* Longer than 7 — shouldn't happen for IMM (max 7 chars) but handle it */ + JSText *str = js_alloc_string (ctx, len); + if (!str) return JS_EXCEPTION; + for (int i = 0; i < len; i++) + string_put (str, i, MIST_GetImmediateASCIIChar (src, start + i)); + str->length = len; + return pretext_end (ctx, str); + } + + /* Heap string — delegate to existing js_sub_string */ + return js_sub_string (ctx, JS_VALUE_GET_STRING (src), start, end); +} + +/* Allocate a new pretext (mutable JSText) with initial capacity */ +JSText *pretext_init (JSContext *ctx, int capacity) { + if (capacity <= 0) capacity = 16; + JSText *s = js_alloc_string (ctx, capacity); + if (!s) return NULL; + s->length = 0; + return s; +} + +/* Reallocate a pretext to hold new_len characters */ +static no_inline JSText *pretext_realloc (JSContext *ctx, JSText *s, int new_len) { + if (new_len > JS_STRING_LEN_MAX) { + JS_ThrowInternalError (ctx, "string too long"); + return NULL; + } + int old_cap = (int)objhdr_cap56 (s->hdr); + int old_len = (int)s->length; + /* Grow by 50%, ensuring we have at least new_len capacity */ + int new_cap = max_int (new_len, old_cap * 3 / 2); + + /* Protect source object with a GC ref before allocating (GC may move it) */ + JSGCRef src_ref; + JS_PushGCRef (ctx, &src_ref); + src_ref.val = JS_MKPTR (s); + + /* Allocate new string - this may trigger GC */ + JSText *new_str = js_alloc_string (ctx, new_cap); + if (!new_str) { + JS_PopGCRef (ctx, &src_ref); + return NULL; + } + + /* Get possibly-moved source pointer after GC */ + s = (JSText *)chase (src_ref.val); + JS_PopGCRef (ctx, &src_ref); + + /* Copy data from old string to new */ + new_str->length = old_len; + for (int i = 0; i < old_len; i++) { + string_put (new_str, i, string_get (s, i)); + } + + return new_str; +} + +no_inline JSText *pretext_putc_slow (JSContext *ctx, JSText *s, uint32_t c) { + int len = (int)s->length; + int cap = (int)objhdr_cap56 (s->hdr); + if (unlikely (len >= cap)) { + s = pretext_realloc (ctx, s, len + 1); + if (!s) return NULL; + } + string_put (s, len, c); + s->length++; + return s; +} + +/* 0 <= c <= 0x10ffff */ +JSText *pretext_putc (JSContext *ctx, JSText *s, uint32_t c) { + int len = (int)s->length; + int cap = (int)objhdr_cap56 (s->hdr); + if (likely (len < cap)) { + string_put (s, len, c); + s->length++; + return s; + } + return pretext_putc_slow (ctx, s, c); +} + +static JSText *pretext_write8 (JSContext *ctx, JSText *s, const uint8_t *p, int len) { + int cur_len = (int)s->length; + int cap = (int)objhdr_cap56 (s->hdr); + if (cur_len + len > cap) { + s = pretext_realloc (ctx, s, cur_len + len); + if (!s) return NULL; + } + for (int i = 0; i < len; i++) { + string_put (s, cur_len + i, p[i]); + } + s->length += len; + return s; +} + +/* appending an ASCII string */ +static JSText *pretext_puts8 (JSContext *ctx, JSText *s, const char *str) { + return pretext_write8 (ctx, s, (const uint8_t *)str, strlen (str)); +} + +static JSText *pretext_concat (JSContext *ctx, JSText *s, const JSText *p, uint32_t from, uint32_t to) { + if (to <= from) return s; + int len = (int)(to - from); + int cur_len = (int)s->length; + int cap = (int)objhdr_cap56 (s->hdr); + if (cur_len + len > cap) { + /* Root p across pretext_realloc which can trigger GC */ + JSGCRef p_ref; + JS_PushGCRef (ctx, &p_ref); + p_ref.val = JS_MKPTR ((void *)p); + s = pretext_realloc (ctx, s, cur_len + len); + p = (const JSText *)chase (p_ref.val); + JS_PopGCRef (ctx, &p_ref); + if (!s) return NULL; + } + for (int i = 0; i < len; i++) { + string_put (s, cur_len + i, string_get (p, (int)from + i)); + } + s->length += len; + return s; +} + +JSText *pretext_concat_value (JSContext *ctx, JSText *s, JSValue v) { + if (MIST_IsImmediateASCII (v)) { + int len = MIST_GetImmediateASCIILen (v); + char buf[8]; + for (int i = 0; i < len; i++) + buf[i] = MIST_GetImmediateASCIIChar (v, i); + return pretext_write8 (ctx, s, (const uint8_t *)buf, len); + } + if (JS_IsText (v)) { + JSText *p = JS_VALUE_GET_PTR (v); + return pretext_concat (ctx, s, p, 0, (uint32_t)JSText_len (p)); + } + JSValue v1 = JS_ToString (ctx, v); + if (JS_IsException (v1)) return NULL; + + if (MIST_IsImmediateASCII (v1)) { + int len = MIST_GetImmediateASCIILen (v1); + char buf[8]; + for (int i = 0; i < len; i++) + buf[i] = MIST_GetImmediateASCIIChar (v1, i); + s = pretext_write8 (ctx, s, (const uint8_t *)buf, len); + return s; + } + + JSText *p = JS_VALUE_GET_STRING (v1); + s = pretext_concat (ctx, s, p, 0, (uint32_t)JSText_len (p)); + return s; +} + +/* Finalize a pretext into an immutable JSValue string */ +JSValue pretext_end (JSContext *ctx, JSText *s) { + if (!s) return JS_EXCEPTION; + int len = (int)s->length; + if (len == 0) { + js_free (ctx, s); + return JS_KEY_empty; + } + /* Set final length in capacity field and clear length for hash storage */ + s->hdr = objhdr_set_cap56 (s->hdr, len); + s->length = 0; + s->hdr = objhdr_set_s (s->hdr, true); /* mark as stone */ + return JS_MKPTR (s); +} + +/* create a string from a UTF-8 buffer */ +JSValue JS_NewStringLen (JSContext *ctx, const char *buf, size_t buf_len) { + if (buf_len > JS_STRING_LEN_MAX) + return JS_ThrowInternalError (ctx, "string too long"); + + /* Try immediate ASCII first (<=7 ASCII chars) */ + if (buf_len <= MIST_ASCII_MAX_LEN) { + JSValue ret = MIST_TryNewImmediateASCII (buf, buf_len); + if (!JS_IsNull (ret)) return ret; + } + + /* Count actual codepoints for allocation */ + const uint8_t *p = (const uint8_t *)buf; + const uint8_t *end = p + buf_len; + int codepoint_count = 0; + while (p < end) { + if (*p < 128) { + p++; + codepoint_count++; + } else { + const uint8_t *next; + int c = unicode_from_utf8 (p, (int)(end - p), &next); + if (c < 0) { + /* Invalid UTF-8 byte, treat as single byte */ + p++; + } else { + p = next; + } + codepoint_count++; + } + } + + JSText *str = js_alloc_string (ctx, codepoint_count); + if (!str) return JS_ThrowMemoryError (ctx); + + /* Decode UTF-8 to UTF-32 */ + p = (const uint8_t *)buf; + int i = 0; + while (p < end) { + uint32_t c; + if (*p < 128) { + c = *p++; + } else { + const uint8_t *next; + int decoded = unicode_from_utf8 (p, (int)(end - p), &next); + if (decoded < 0) { + /* Invalid UTF-8 byte, use replacement char or the byte itself */ + c = *p++; + } else { + c = (uint32_t)decoded; + p = next; + } + } + string_put (str, i++, c); + } + str->length = codepoint_count; + return pretext_end (ctx, str); +} + +JSValue JS_ConcatString3 (JSContext *ctx, const char *str1, JSValue str2, const char *str3) { + JSText *b; + int len1, len3, str2_len; + + if (!JS_IsText (str2)) { + str2 = JS_ToString (ctx, str2); + if (JS_IsException (str2)) goto fail; + } + + str2_len = js_string_value_len (str2); + len1 = strlen (str1); + len3 = strlen (str3); + + b = pretext_init (ctx, len1 + str2_len + len3); + if (!b) goto fail; + + b = pretext_write8 (ctx, b, (const uint8_t *)str1, len1); + if (!b) goto fail; + b = pretext_concat_value (ctx, b, str2); + if (!b) goto fail; + b = pretext_write8 (ctx, b, (const uint8_t *)str3, len3); + if (!b) goto fail; + + return pretext_end (ctx, b); + +fail: + return JS_EXCEPTION; +} + +/* return (NULL, 0) if exception. */ +/* return pointer into a JSText with a live ref_count */ +/* cesu8 determines if non-BMP1 codepoints are encoded as 1 or 2 utf-8 + * sequences */ +const char *JS_ToCStringLen2 (JSContext *ctx, size_t *plen, JSValue val1, BOOL cesu8) { + JSGCRef val_ref; + char *q, *ret; + size_t size; + int i, len; + + JS_PushGCRef (ctx, &val_ref); + + if (!JS_IsText (val1)) { + val_ref.val = JS_ToString (ctx, val1); + if (JS_IsException (val_ref.val)) goto fail; + } else { + val_ref.val = val1; + } + + /* Handle immediate ASCII strings */ + if (MIST_IsImmediateASCII (val_ref.val)) { + len = MIST_GetImmediateASCIILen (val_ref.val); + ret = js_malloc_rt (len + 1); /* Use non-GC heap for returned C string */ + if (!ret) goto fail; + /* Re-read from val_ref after potential GC */ + for (i = 0; i < len; i++) { + ret[i] = MIST_GetImmediateASCIIChar (val_ref.val, i); + } + ret[len] = '\0'; + if (plen) *plen = len; + JS_PopGCRef (ctx, &val_ref); + return ret; + } + + /* Handle heap strings (JSText) */ + JSText *str = JS_VALUE_GET_STRING (val_ref.val); + len = (int)JSText_len (str); + + /* Calculate UTF-8 size */ + size = 0; + for (i = 0; i < len; i++) { + uint32_t c = string_get (str, i); + if (c < 0x80) + size += 1; + else if (c < 0x800) + size += 2; + else if (c < 0x10000) + size += 3; + else + size += 4; + } + + ret = js_malloc_rt (size + 1); /* Use non-GC heap for returned C string */ + if (!ret) goto fail; + + /* str pointer is still valid - no GC triggered by js_malloc_rt */ + /* Re-extract for safety in case code above changes */ + str = JS_VALUE_GET_STRING (val_ref.val); + q = ret; + for (i = 0; i < len; i++) { + uint32_t c = string_get (str, i); + q += unicode_to_utf8 ((uint8_t *)q, c); + } + *q = '\0'; + + if (plen) *plen = size; + + JS_PopGCRef (ctx, &val_ref); + return ret; + +fail: + JS_PopGCRef (ctx, &val_ref); + if (plen) *plen = 0; + return NULL; +} + +void JS_FreeCString (JSContext *ctx, const char *ptr) { + /* Free C string allocated from non-GC heap */ + js_free_rt ((void *)ptr); + (void)ctx; + (void)ptr; +} + +JSValue JS_ConcatString1 (JSContext *ctx, const JSText *p1, const JSText *p2) { + JSText *p; + uint32_t len; + int len1 = (int)JSText_len (p1); + int len2 = (int)JSText_len (p2); + + len = len1 + len2; + /* len is uint32_t, JS_STRING_LEN_MAX is 56 bits, so this always fits */ + p = js_alloc_string (ctx, len); + if (!p) return JS_EXCEPTION; + /* Pack first string */ + { + int i; + for (i = 0; i < len1; i++) + string_put (p, i, string_get (p1, i)); + for (i = 0; i < len2; i++) + string_put (p, len1 + i, string_get (p2, i)); + } + return JS_MKPTR (p); +} + +// TODO: this function is fucked. +BOOL JS_ConcatStringInPlace (JSContext *ctx, JSText *p1, JSValue op2) { + (void)ctx; + if (JS_VALUE_GET_TAG (op2) == JS_TAG_STRING) { + JSText *p2 = JS_VALUE_GET_STRING (op2); + int64_t new_len; + int64_t len1 = JSText_len (p1); + int64_t len2 = JSText_len (p2); + + if (len2 == 0) return TRUE; + + new_len = len1 + len2; + + /* Append p2's characters using string_put/string_get */ + for (int64_t i = 0; i < len2; i++) { + string_put (p1, len1 + i, string_get (p2, i)); + } + p1->hdr = objhdr_set_cap56 (p1->hdr, new_len); + return TRUE; + } + return FALSE; +} + +/* Helper for string value comparison (handles immediate and heap strings) */ +int js_string_compare_value (JSContext *ctx, JSValue op1, JSValue op2, BOOL eq_only) { + (void)ctx; + if (eq_only && op1 == op2) return 0; + + int len1 = js_string_value_len (op1); + int len2 = js_string_value_len (op2); + + if (eq_only && len1 != len2) return 1; + + int len = min_int (len1, len2); + + for (int i = 0; i < len; i++) { + uint32_t c1 = js_string_value_get (op1, i); + uint32_t c2 = js_string_value_get (op2, i); + if (c1 != c2) { return (c1 < c2) ? -1 : 1; } + } + + if (len1 == len2) return 0; + return (len1 < len2) ? -1 : 1; +} + +int js_string_compare_value_nocase (JSContext *ctx, JSValue op1, JSValue op2) { + (void)ctx; + int len1 = js_string_value_len (op1); + int len2 = js_string_value_len (op2); + if (len1 != len2) return 1; + for (int i = 0; i < len1; i++) { + uint32_t c1 = js_string_value_get (op1, i); + uint32_t c2 = js_string_value_get (op2, i); + if (c1 >= 'A' && c1 <= 'Z') c1 += 32; + if (c2 >= 'A' && c2 <= 'Z') c2 += 32; + if (c1 != c2) return 1; + } + return 0; +} + +JSValue JS_ConcatString (JSContext *ctx, JSValue op1, JSValue op2) { + if (unlikely (!JS_IsText (op1))) { + op1 = JS_ToString (ctx, op1); + if (JS_IsException (op1)) { + return JS_EXCEPTION; + } + } + if (unlikely (!JS_IsText (op2))) { + op2 = JS_ToString (ctx, op2); + if (JS_IsException (op2)) { + return JS_EXCEPTION; + } + } + + int len1 = js_string_value_len (op1); + int len2 = js_string_value_len (op2); + int new_len = len1 + len2; + JSValue ret_val = JS_NULL; + + /* Try to create immediate ASCII if short enough and all ASCII */ + if (new_len <= MIST_ASCII_MAX_LEN) { + char buf[8]; + BOOL all_ascii = TRUE; + for (int i = 0; i < len1 && all_ascii; i++) { + uint32_t c = js_string_value_get (op1, i); + if (c >= 0x80) + all_ascii = FALSE; + else + buf[i] = (char)c; + } + for (int i = 0; i < len2 && all_ascii; i++) { + uint32_t c = js_string_value_get (op2, i); + if (c >= 0x80) + all_ascii = FALSE; + else + buf[len1 + i] = (char)c; + } + if (all_ascii) { ret_val = MIST_TryNewImmediateASCII (buf, new_len); } + } + + if (JS_IsNull (ret_val)) { + /* Protect op1 and op2 from GC during allocation */ + JSGCRef op1_ref, op2_ref; + JS_PushGCRef (ctx, &op1_ref); + op1_ref.val = op1; + JS_PushGCRef (ctx, &op2_ref); + op2_ref.val = op2; + + JSText *p = js_alloc_string (ctx, new_len); + if (!p) { + JS_PopGCRef (ctx, &op2_ref); + JS_PopGCRef (ctx, &op1_ref); + return JS_EXCEPTION; + } + + /* Get possibly-moved values after GC */ + op1 = op1_ref.val; + op2 = op2_ref.val; + JS_PopGCRef (ctx, &op2_ref); + JS_PopGCRef (ctx, &op1_ref); + + /* Copy characters using string_put/get */ + for (int i = 0; i < len1; i++) { + string_put (p, i, js_string_value_get (op1, i)); + } + for (int i = 0; i < len2; i++) { + string_put (p, len1 + i, js_string_value_get (op2, i)); + } + p->length = new_len; + ret_val = JS_MKPTR (p); + } + + return ret_val; +} + +/* WARNING: proto must be an object or JS_NULL */ +JSValue JS_NewObjectProtoClass (JSContext *ctx, JSValue proto_val, JSClassID class_id) { + JSGCRef proto_ref; + JS_PushGCRef (ctx, &proto_ref); + proto_ref.val = proto_val; + + JSRecord *rec = js_new_record_class (ctx, 0, class_id); + + proto_val = proto_ref.val; /* Get potentially-updated value after GC */ + JS_PopGCRef (ctx, &proto_ref); + + if (!rec) return JS_EXCEPTION; + + /* Set prototype if provided */ + if (JS_IsRecord (proto_val)) { + rec->proto = JS_VALUE_GET_RECORD (proto_val); + } + + return JS_MKPTR (rec); +} + +JSValue JS_NewObjectClass (JSContext *ctx, int class_id) { + return JS_NewObjectProtoClass (ctx, ctx->class_proto[class_id], class_id); +} + +JSValue JS_NewObjectProto (JSContext *ctx, JSValue proto) { + return JS_NewObjectProtoClass (ctx, proto, JS_CLASS_OBJECT); +} + +/* Create an intrinsic array with specified capacity + Uses bump allocation - values are inline after the JSArray struct */ +JSValue JS_NewArrayLen (JSContext *ctx, uint32_t len) { + JSArray *arr; + uint32_t cap; + + cap = len > 0 ? len : JS_ARRAY_INITIAL_SIZE; + size_t values_size = sizeof (JSValue) * cap; + size_t total_size = sizeof (JSArray) + values_size; + + arr = js_malloc (ctx, total_size); + if (!arr) return JS_EXCEPTION; + + arr->mist_hdr = objhdr_make (cap, OBJ_ARRAY, false, false, false, false); + arr->len = len; + + /* Initialize all values to null (values[] is inline flexible array member) */ + for (uint32_t i = 0; i < cap; i++) { + arr->values[i] = JS_NULL; + } + + return JS_MKPTR (arr); +} + +JSValue JS_NewArray (JSContext *ctx) { return JS_NewArrayLen (ctx, 0); } + +JSValue JS_NewObject (JSContext *ctx) { + /* inline JS_NewObjectClass(ctx, JS_CLASS_OBJECT); */ + return JS_NewObjectProtoClass (ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_OBJECT); +} + +/* Helper to check if a value is a bytecode function */ +static BOOL js_is_bytecode_function (JSValue val) { + if (!JS_IsFunction (val)) return FALSE; + JSFunction *f = JS_VALUE_GET_FUNCTION (val); + return f->kind == JS_FUNC_KIND_BYTECODE; +} + +/* return NULL without exception if not a function or no bytecode */ +static JSFunctionBytecode *JS_GetFunctionBytecode (JSValue val) { + JSFunction *f; + if (!JS_IsFunction (val)) return NULL; + f = JS_VALUE_GET_FUNCTION (val); + if (f->kind != JS_FUNC_KIND_BYTECODE) return NULL; + return f->u.func.function_bytecode; +} + +// TODO: needs reworked +int js_method_set_properties (JSContext *ctx, JSValue func_obj, JSValue name, int flags, JSValue home_obj) { + (void)ctx; + (void)flags; + (void)home_obj; + if (!JS_IsFunction (func_obj)) return -1; + JSFunction *f = JS_VALUE_GET_FUNCTION (func_obj); + /* name is now JSValue text */ + if (JS_IsText (name)) { f->name = name; } + return 0; +} + +/* Note: at least 'length' arguments will be readable in 'argv' */ +static JSValue JS_NewCFunction3 (JSContext *ctx, JSCFunction *func, const char *name, int length, JSCFunctionEnum cproto, int magic) { + JSValue func_obj; + JSFunction *f; + + func_obj = js_new_function (ctx, JS_FUNC_KIND_C); + if (JS_IsException (func_obj)) return func_obj; + f = JS_VALUE_GET_FUNCTION (func_obj); + f->u.cfunc.c_function.generic = func; + f->u.cfunc.cproto = cproto; + f->u.cfunc.magic = magic; + f->length = length; + if (name) { + JSGCRef fobj_ref; + JS_PushGCRef (ctx, &fobj_ref); + fobj_ref.val = func_obj; + JSValue key = js_key_new (ctx, name); + func_obj = fobj_ref.val; + JS_PopGCRef (ctx, &fobj_ref); + f = JS_VALUE_GET_FUNCTION (func_obj); /* re-chase after allocation */ + f->name = key; + } else { + f->name = JS_KEY_empty; + } + return func_obj; +} + +/* Note: at least 'length' arguments will be readable in 'argv' */ +JSValue JS_NewCFunction2 (JSContext *ctx, JSCFunction *func, const char *name, int length, JSCFunctionEnum cproto, int magic) { + return JS_NewCFunction3 (ctx, func, name, length, cproto, magic); +} + +/* free_property is defined earlier as a stub since shapes are removed */ + +/* GC-safe array growth function. + Takes JSValue* pointer to a GC-tracked location (like &argv[n]). + Allocates new array, copies data, installs forward header at old location. */ +static int js_array_grow (JSContext *ctx, JSValue *arr_ptr, word_t min_cap) { + JSArray *arr = JS_VALUE_GET_ARRAY (*arr_ptr); + word_t old_cap = js_array_cap (arr); + if (min_cap <= old_cap) return 0; + + if (objhdr_s (arr->mist_hdr)) { + JS_ThrowInternalError (ctx, "cannot grow a stoned array"); + return -1; + } + + word_t new_cap = old_cap ? old_cap : JS_ARRAY_INITIAL_SIZE; + while (new_cap < min_cap && new_cap <= JS_ARRAY_MAX_CAP / 2) + new_cap *= 2; + if (new_cap > JS_ARRAY_MAX_CAP) new_cap = JS_ARRAY_MAX_CAP; + if (new_cap < min_cap) { + JS_ThrowRangeError (ctx, "array capacity overflow"); + return -1; + } + + size_t total_size = sizeof (JSArray) + sizeof (JSValue) * new_cap; + JSArray *new_arr = js_malloc (ctx, total_size); + if (!new_arr) return -1; + + /* Re-chase arr via arr_ptr (GC may have moved it during js_malloc) */ + arr = JS_VALUE_GET_ARRAY (*arr_ptr); + + new_arr->mist_hdr = objhdr_make (new_cap, OBJ_ARRAY, false, false, false, false); + new_arr->len = arr->len; + + JSValue old_ptr = *arr_ptr; + for (word_t i = 0; i < arr->len; i++) + new_arr->values[i] = arr->values[i]; + for (word_t i = arr->len; i < new_cap; i++) + new_arr->values[i] = JS_NULL; + + /* Install forward header at old location */ + arr->mist_hdr = objhdr_make_fwd (new_arr); + + /* Update the tracked JSValue to point to new array */ + *arr_ptr = JS_MKPTR (new_arr); + + /* Fix self-references: update elements that pointed to the old array */ + for (word_t i = 0; i < new_arr->len; i++) { + if (new_arr->values[i] == old_ptr) + new_arr->values[i] = *arr_ptr; + } + + return 0; +} + +/* GC-safe array push. Takes JSValue* to GC-tracked location. */ +static int js_intrinsic_array_push (JSContext *ctx, JSValue *arr_ptr, JSValue val) { + JSArray *arr = JS_VALUE_GET_ARRAY (*arr_ptr); + + if (objhdr_s (arr->mist_hdr)) { + JS_ThrowInternalError (ctx, "cannot push to a stoned array"); + return -1; + } + + if (arr->len >= js_array_cap (arr)) { + if (js_array_grow (ctx, arr_ptr, arr->len + 1) < 0) + return -1; + arr = JS_VALUE_GET_ARRAY (*arr_ptr); /* re-chase after grow */ + } + + arr->values[arr->len++] = val; + return 0; +} + +/* GC-safe array set. Takes JSValue* to GC-tracked location. */ +static int js_intrinsic_array_set (JSContext *ctx, JSValue *arr_ptr, word_t idx, JSValue val) { + JSArray *arr = JS_VALUE_GET_ARRAY (*arr_ptr); + + if (objhdr_s (arr->mist_hdr)) { + JS_ThrowInternalError (ctx, "cannot set on a stoned array"); + return -1; + } + + if (idx >= js_array_cap (arr)) { + /* Root val across js_array_grow which can trigger GC */ + JSGCRef val_ref; + JS_PushGCRef (ctx, &val_ref); + val_ref.val = val; + if (js_array_grow (ctx, arr_ptr, idx + 1) < 0) { + JS_PopGCRef (ctx, &val_ref); + return -1; + } + val = val_ref.val; + JS_PopGCRef (ctx, &val_ref); + arr = JS_VALUE_GET_ARRAY (*arr_ptr); /* re-chase after grow */ + } + + if (idx >= arr->len) { + for (word_t i = arr->len; i < idx; i++) + arr->values[i] = JS_NULL; + arr->len = idx + 1; + } + + arr->values[idx] = val; + return 0; +} + +/* Allocate intrinsic function (JS_TAG_FUNCTION) */ +JSValue js_new_function (JSContext *ctx, JSFunctionKind kind) { + JSFunction *func = js_mallocz (ctx, sizeof (JSFunction)); + if (!func) return JS_EXCEPTION; + func->header = objhdr_make (0, OBJ_FUNCTION, false, false, false, false); + func->kind = kind; + func->name = JS_NULL; + func->length = 0; + /* Initialize closure fields for bytecode functions */ + if (kind == JS_FUNC_KIND_BYTECODE) { + func->u.func.outer_frame = JS_NULL; + func->u.func.env_record = JS_NULL; + } + return JS_MKPTR (func); +} + +/* Get pointer to an upvalue in outer scope frame chain. + depth=0 is current frame, depth=1 is immediate outer, etc. + Returns NULL if depth exceeds the frame chain. + frame_val is a JSValue containing a JSFrame pointer. */ + +void JS_ComputeMemoryUsage (JSRuntime *rt, JSMemoryUsage *s) { +} + +void JS_DumpMemoryUsage (FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) { +} + +/* WARNING: obj is freed */ +JSValue JS_Throw (JSContext *ctx, JSValue obj) { + ctx->current_exception = obj; + return JS_EXCEPTION; +} + +/* return the pending exception (cannot be called twice). */ +JSValue JS_GetException (JSContext *ctx) { + JSValue val = ctx->current_exception; + ctx->current_exception = JS_UNINITIALIZED; + return val; +} + +JS_BOOL +JS_HasException (JSContext *ctx) { + return !JS_IsUninitialized (ctx->current_exception); +} + + +/* use pc_value = -1 to get the position of the function definition */ +static int find_line_num (JSContext *ctx, JSFunctionBytecode *b, uint32_t pc_value, int *pcol_num) { + const uint8_t *p_end, *p; + int new_line_num, line_num, pc, v, ret, new_col_num, col_num; + uint32_t val; + unsigned int op; + + if (!b->has_debug || !b->debug.pc2line_buf) + goto fail; /* function was stripped */ + + p = b->debug.pc2line_buf; + p_end = p + b->debug.pc2line_len; + + /* get the function line and column numbers */ + ret = get_leb128 (&val, p, p_end); + if (ret < 0) goto fail; + p += ret; + line_num = val + 1; + + ret = get_leb128 (&val, p, p_end); + if (ret < 0) goto fail; + p += ret; + col_num = val + 1; + + if (pc_value != -1) { + pc = 0; + while (p < p_end) { + op = *p++; + if (op == 0) { + ret = get_leb128 (&val, p, p_end); + if (ret < 0) goto fail; + pc += val; + p += ret; + ret = get_sleb128 (&v, p, p_end); + if (ret < 0) goto fail; + p += ret; + new_line_num = line_num + v; + } else { + op -= PC2LINE_OP_FIRST; + pc += (op / PC2LINE_RANGE); + new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE; + } + ret = get_sleb128 (&v, p, p_end); + if (ret < 0) goto fail; + p += ret; + new_col_num = col_num + v; + + if (pc_value < pc) goto done; + line_num = new_line_num; + col_num = new_col_num; + } + } +done: + *pcol_num = col_num; + return line_num; +fail: + *pcol_num = 0; + return 0; +} + +/* in order to avoid executing arbitrary code during the stack trace + generation, we only look at simple 'name' properties containing a + string. */ +static const char *get_func_name (JSContext *ctx, JSValue func) { + if (!JS_IsRecord (func)) return NULL; + JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (func); + + /* Create "name" key as immediate ASCII string */ + JSValue name_key = MIST_TryNewImmediateASCII ("name", 4); + + int slot = rec_find_slot (rec, name_key); + if (slot <= 0) return NULL; + + JSValue val = rec->slots[slot].val; + if (!JS_IsText (val)) return NULL; + return JS_ToCString (ctx, val); +} + +#define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0) + +/* if filename != NULL, an additional level is added with the filename + and line number information (used for parse error). */ +void build_backtrace (JSContext *ctx, JSValue error_obj, const char *filename, int line_num, int col_num, int backtrace_flags) { + JSStackFrame *sf; + JSValue str; + DynBuf dbuf; + const char *func_name_str; + const char *str1; + JSGCRef err_ref; + + if (!JS_IsObject (error_obj)) + return; /* protection in the out of memory case */ + + /* Protect error_obj from GC during backtrace building */ + JS_PushGCRef (ctx, &err_ref); + err_ref.val = error_obj; + + js_dbuf_init (ctx, &dbuf); + if (filename) { + dbuf_printf (&dbuf, " at %s", filename); + if (line_num != -1) dbuf_printf (&dbuf, ":%d:%d", line_num, col_num); + dbuf_putc (&dbuf, '\n'); + /* Use short immediate strings for keys to avoid GC issues */ + JSValue key_fileName = MIST_TryNewImmediateASCII ("file", 4); + JSValue key_lineNumber = MIST_TryNewImmediateASCII ("line", 4); + JSValue key_columnNumber = MIST_TryNewImmediateASCII ("col", 3); + str = JS_NewString (ctx, filename); + if (JS_IsException (str)) { + JS_PopGCRef (ctx, &err_ref); + return; + } + if (JS_SetPropertyInternal (ctx, err_ref.val, key_fileName, str) < 0 + || JS_SetPropertyInternal (ctx, err_ref.val, key_lineNumber, JS_NewInt32 (ctx, line_num)) + < 0 + || JS_SetPropertyInternal (ctx, err_ref.val, key_columnNumber, JS_NewInt32 (ctx, col_num)) + < 0) { + JS_PopGCRef (ctx, &err_ref); + return; + } + } + for (sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) { + if (sf->js_mode & JS_MODE_BACKTRACE_BARRIER) break; + if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) { + backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL; + continue; + } + func_name_str = get_func_name (ctx, sf->cur_func); + if (!func_name_str || func_name_str[0] == '\0') + str1 = ""; + else + str1 = func_name_str; + dbuf_printf (&dbuf, " at %s", str1); + JS_FreeCString (ctx, func_name_str); + + if (JS_IsFunction (sf->cur_func)) { + JSFunction *fn = JS_VALUE_GET_FUNCTION (sf->cur_func); + if (fn->kind == JS_FUNC_KIND_BYTECODE) { + JSFunctionBytecode *b; + const char *atom_str; + int line_num1, col_num1; + + b = fn->u.func.function_bytecode; + if (b->has_debug) { + line_num1 = find_line_num (ctx, b, sf->cur_pc - b->byte_code_buf - 1, &col_num1); + atom_str = JS_ToCString (ctx, b->debug.filename); + dbuf_printf (&dbuf, " (%s", atom_str ? atom_str : ""); + JS_FreeCString (ctx, atom_str); + if (line_num1 != 0) + dbuf_printf (&dbuf, ":%d:%d", line_num1, col_num1); + dbuf_putc (&dbuf, ')'); + } + } else { + dbuf_printf (&dbuf, " (native)"); + } + } else { + dbuf_printf (&dbuf, " (native)"); + } + dbuf_putc (&dbuf, '\n'); + } + dbuf_putc (&dbuf, '\0'); + if (dbuf_error (&dbuf)) + str = JS_NULL; + else + str = JS_NewString (ctx, (char *)dbuf.buf); + dbuf_free (&dbuf); + JS_SetPropertyInternal (ctx, err_ref.val, JS_KEY_stack, str); + JS_PopGCRef (ctx, &err_ref); +} + +/* Note: it is important that no exception is returned by this function */ +BOOL is_backtrace_needed (JSContext *ctx, JSValue obj) { + JSRecord *p; + if (!JS_IsRecord (obj)) return FALSE; + p = JS_VALUE_GET_OBJ (obj); + if (REC_GET_CLASS_ID(p) != JS_CLASS_ERROR) return FALSE; + /* Check if "stack" property already exists */ + JSValue stack_key = MIST_TryNewImmediateASCII ("stack", 5); + JSRecord *rec = (JSRecord *)p; + int slot = rec_find_slot (rec, stack_key); + if (slot > 0) return FALSE; + return TRUE; +} + +JSValue JS_NewError (JSContext *ctx) { + return JS_NewObjectClass (ctx, JS_CLASS_ERROR); +} + +JSValue JS_ThrowError2 (JSContext *ctx, JSErrorEnum error_num, const char *fmt, va_list ap, BOOL add_backtrace) { + char buf[256]; + JSValue ret; + JSGCRef obj_ref; + + vsnprintf (buf, sizeof (buf), fmt, ap); + JS_PushGCRef (ctx, &obj_ref); + obj_ref.val = JS_NewObjectProtoClass (ctx, ctx->native_error_proto[error_num], JS_CLASS_ERROR); + if (unlikely (JS_IsException (obj_ref.val))) { + /* out of memory: throw JS_NULL to avoid recursing */ + obj_ref.val = JS_NULL; + } else { + JSValue msg = JS_NewString (ctx, buf); + JS_SetPropertyInternal (ctx, obj_ref.val, JS_KEY_message, msg); + if (add_backtrace) { build_backtrace (ctx, obj_ref.val, NULL, 0, 0, 0); } + } + ret = JS_Throw (ctx, obj_ref.val); + JS_PopGCRef (ctx, &obj_ref); + return ret; +} + +static JSValue JS_ThrowError (JSContext *ctx, JSErrorEnum error_num, const char *fmt, va_list ap) { + JSStackFrame *sf; + BOOL add_backtrace; + + /* the backtrace is added later if called from a bytecode function */ + sf = ctx->current_stack_frame; + add_backtrace = (!sf || (JS_GetFunctionBytecode (sf->cur_func) == NULL)); + return JS_ThrowError2 (ctx, error_num, fmt, ap, add_backtrace); +} + +JSValue __attribute__ ((format (printf, 2, 3))) +JS_ThrowSyntaxError (JSContext *ctx, const char *fmt, ...) { + JSValue val; + va_list ap; + + va_start (ap, fmt); + val = JS_ThrowError (ctx, JS_SYNTAX_ERROR, fmt, ap); + va_end (ap); + return val; +} + +JSValue __attribute__ ((format (printf, 2, 3))) +JS_ThrowTypeError (JSContext *ctx, const char *fmt, ...) { + JSValue val; + va_list ap; + + va_start (ap, fmt); + val = JS_ThrowError (ctx, JS_TYPE_ERROR, fmt, ap); + va_end (ap); + return val; +} + +JSValue __attribute__ ((format (printf, 2, 3))) +JS_ThrowReferenceError (JSContext *ctx, const char *fmt, ...) { + JSValue val; + va_list ap; + + va_start (ap, fmt); + val = JS_ThrowError (ctx, JS_REFERENCE_ERROR, fmt, ap); + va_end (ap); + return val; +} + +JSValue __attribute__ ((format (printf, 2, 3))) +JS_ThrowRangeError (JSContext *ctx, const char *fmt, ...) { + JSValue val; + va_list ap; + + va_start (ap, fmt); + val = JS_ThrowError (ctx, JS_RANGE_ERROR, fmt, ap); + va_end (ap); + return val; +} + +JSValue __attribute__ ((format (printf, 2, 3))) +JS_ThrowInternalError (JSContext *ctx, const char *fmt, ...) { + JSValue val; + va_list ap; + + va_start (ap, fmt); + val = JS_ThrowError (ctx, JS_INTERNAL_ERROR, fmt, ap); + va_end (ap); + return val; +} + +JSValue JS_ThrowOutOfMemory (JSContext *ctx) { + /* Simplified: no re-entry guard needed with copying GC */ + JS_ThrowInternalError (ctx, "out of memory"); + return JS_EXCEPTION; +} + +JSValue JS_ThrowStackOverflow (JSContext *ctx) { + return JS_ThrowInternalError (ctx, "stack overflow"); +} + +static JSValue JS_ThrowTypeErrorNotAnObject (JSContext *ctx) { + return JS_ThrowTypeError (ctx, "not an object"); +} + +JSValue JS_ThrowReferenceErrorUninitialized (JSContext *ctx, + JSValue name) { + char buf[KEY_GET_STR_BUF_SIZE]; + return JS_ThrowReferenceError ( + ctx, "%s is not initialized", JS_IsNull (name) ? "lexical variable" : JS_KeyGetStr (ctx, buf, sizeof (buf), name)); +} + +JSValue JS_ThrowReferenceErrorUninitialized2 (JSContext *ctx, + JSFunctionBytecode *b, + int idx, + BOOL is_ref) { + JSValue name = JS_NULL; + if (is_ref) { + name = b->closure_var[idx].var_name; + } else { + /* not present if the function is stripped and contains no eval() */ + if (b->vardefs) name = b->vardefs[b->arg_count + idx].var_name; + } + return JS_ThrowReferenceErrorUninitialized (ctx, name); +} + +static JSValue JS_ThrowTypeErrorInvalidClass (JSContext *ctx, int class_id) { + const char *name = ctx->class_array[class_id].class_name; + return JS_ThrowTypeError (ctx, "%s object expected", name ? name : "unknown"); +} + +void JS_ThrowInterrupted (JSContext *ctx) { + JS_ThrowInternalError (ctx, "interrupted"); + JS_SetUncatchableException (ctx, TRUE); +} + + +/* Return an Object, JS_NULL or JS_EXCEPTION in case of exotic object. */ +JSValue JS_GetPrototype (JSContext *ctx, JSValue obj) { + JSValue val; + if (JS_IsRecord (obj)) { + JSRecord *p; + p = JS_VALUE_GET_OBJ (obj); + p = JS_OBJ_GET_PROTO (p); + if (!p) + val = JS_NULL; + else + val = JS_MKPTR (p); + } else { + /* Primitives have no prototype */ + val = JS_NULL; + } + return val; +} + +/* Get property from object using JSRecord-based lookup */ +JSValue JS_GetProperty (JSContext *ctx, JSValue obj, JSValue prop) { + if (JS_IsNull (obj)) return JS_NULL; + if (JS_IsException (obj)) return JS_EXCEPTION; + + if (unlikely (!JS_IsRecord (obj))) { + /* Primitives have no properties */ + return JS_NULL; + } + + /* All objects are JSRecords now */ + JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (obj); + return rec_get (ctx, rec, prop); +} + +/* GC-SAFE: Collects keys to stack buffer before any allocation. + Returns a JSValue array of text keys. */ +JSValue JS_GetOwnPropertyNames (JSContext *ctx, JSValue obj) { + uint32_t mask, count, i; + + if (!JS_IsRecord (obj)) { + JS_ThrowTypeErrorNotAnObject (ctx); + return JS_EXCEPTION; + } + + /* Reading slots is GC-safe - no allocation */ + JSRecord *rec = JS_VALUE_GET_OBJ (obj); + mask = (uint32_t)objhdr_cap56 (rec->mist_hdr); + + /* Count text keys first */ + count = 0; + for (i = 1; i <= mask; i++) { + if (JS_IsText (rec->slots[i].key)) count++; + } + + if (count == 0) return JS_NewArrayLen (ctx, 0); + + /* Collect keys into stack buffer (JSValues are just uint64_t) */ + JSValue *keys = alloca (count * sizeof (JSValue)); + uint32_t idx = 0; + for (i = 1; i <= mask; i++) { + JSValue k = rec->slots[i].key; + if (JS_IsText (k)) keys[idx++] = k; + } + + /* Now allocate and fill - GC point, but keys are on stack */ + JSValue arr = JS_NewArrayLen (ctx, count); + if (JS_IsException (arr)) return JS_EXCEPTION; + + for (i = 0; i < count; i++) { + JS_SetPropertyUint32 (ctx, arr, i, keys[i]); + } + + return arr; +} + +/* Return -1 if exception, + FALSE if the property does not exist, TRUE if it exists. If TRUE is + returned, the property descriptor 'desc' is filled present. + Now uses JSRecord-based lookup. */ +int JS_GetOwnPropertyInternal (JSContext *ctx, + JSValue *desc, + JSRecord *p, + JSValue prop) { + JSRecord *rec = (JSRecord *)p; + int slot = rec_find_slot (rec, prop); + + if (slot > 0) { + if (desc) + *desc = rec->slots[slot].val; + return TRUE; + } + return FALSE; +} + +int JS_GetOwnProperty (JSContext *ctx, JSValue *desc, JSValue obj, JSValue prop) { + if (!JS_IsRecord (obj)) { + JS_ThrowTypeErrorNotAnObject (ctx); + return -1; + } + return JS_GetOwnPropertyInternal (ctx, desc, JS_VALUE_GET_OBJ (obj), prop); +} + +/* GC-SAFE: Only calls rec_find_slot and reads prototype pointers. + return -1 if exception otherwise TRUE or FALSE */ +int JS_HasProperty (JSContext *ctx, JSValue obj, JSValue prop) { + JSRecord *p; + int ret; + if (unlikely (!JS_IsRecord (obj))) return FALSE; + p = JS_VALUE_GET_OBJ (obj); + for (;;) { + ret = JS_GetOwnPropertyInternal (ctx, NULL, p, prop); + if (ret != 0) return ret; + p = p->proto; /* Direct pointer chase is safe - no allocation */ + if (!p) break; + } + return FALSE; +} + +static uint32_t js_string_get_length (JSValue val) { + if (JS_IsPtr (val)) { + void *ptr = JS_VALUE_GET_PTR (val); + /* Check objhdr_t at offset 8 for type */ + objhdr_t hdr = *(objhdr_t *)ptr; + if (objhdr_type (hdr) == OBJ_TEXT) { + /* String (JSText or JSText) */ + return (uint32_t)objhdr_cap56 (hdr); + } + return 0; + } else if (MIST_IsImmediateASCII (val)) { + return MIST_GetImmediateASCIILen (val); + } else { + return 0; + } +} + +JSValue JS_GetPropertyValue (JSContext *ctx, JSValue this_obj, JSValue prop) { + JSValue ret; + uint32_t prop_tag = JS_VALUE_GET_TAG (prop); + + if (JS_IsNull (this_obj)) { + return JS_NULL; + } + + if (prop_tag == JS_TAG_INT) { + int idx = JS_VALUE_GET_INT (prop); + return JS_GetPropertyNumber (ctx, this_obj, idx); + } + + if (prop_tag == JS_TAG_SHORT_FLOAT) { + double d = JS_VALUE_GET_FLOAT64 (prop); + uint32_t idx = (uint32_t)d; + if (d != (double)idx) return JS_NULL; + return JS_GetPropertyNumber (ctx, this_obj, idx); + } + + /* Check for string property (immediate or heap) */ + if (JS_IsText (prop)) { + /* Intrinsic arrays don't support string keys */ + if (JS_IsArray (this_obj)) { + return JS_NULL; + } + /* Create an interned key from the string */ + JSValue key = js_key_from_string (ctx, prop); + ret = JS_GetProperty (ctx, this_obj, key); + /* key is interned or immediate, no need to free */ + return ret; + } + + /* Handle object keys directly via objkey map */ + if (JS_IsRecord (prop)) { + /* Intrinsic arrays don't support object keys */ + if (!JS_IsRecord (this_obj)) { + return JS_NULL; + } + JSRecord *rec = JS_VALUE_GET_RECORD (this_obj); + JSValue val = rec_get (ctx, rec, prop); + return val; + } + + /* Unknown type -> null */ + return JS_NULL; +} + +JSValue JS_SetPropertyNumber (JSContext *js, JSValue obj, int idx, JSValue val) { + if (!JS_IsArray (obj)) { + return JS_ThrowInternalError (js, + "cannot set with a number on a non array"); + } + + if (idx < 0) { + return JS_ThrowRangeError (js, "array index out of bounds"); + } + + /* Root obj since js_intrinsic_array_set may trigger GC during array grow */ + JSGCRef obj_ref; + JS_PushGCRef (js, &obj_ref); + obj_ref.val = obj; + if (js_intrinsic_array_set (js, &obj_ref.val, (word_t)idx, val) < 0) { + JS_PopGCRef (js, &obj_ref); + return JS_EXCEPTION; + } + JS_PopGCRef (js, &obj_ref); + return val; +} + +JSValue JS_GetPropertyNumber (JSContext *js, JSValue obj, int idx) { + if (JS_IsArray (obj)) { + JSArray *a = JS_VALUE_GET_ARRAY (obj); + int len = a->len; + if (idx < 0 || idx >= len) { return JS_NULL; } + return a->values[idx]; + } + + if (JS_IsText (obj)) { + uint32_t len = js_string_get_length (obj); + if (idx < 0 || (uint32_t)idx >= len) { return JS_NULL; } + return js_sub_string (js, JS_VALUE_GET_STRING (obj), idx, idx + 1); + } + + return JS_NULL; +} + +JSValue JS_GetPropertyUint32 (JSContext *ctx, JSValue this_obj, uint32_t idx) { + return JS_GetPropertyNumber (ctx, this_obj, idx); +} + +static JSValue JS_GetPropertyInt64 (JSContext *ctx, JSValue obj, int64_t idx) { + return JS_GetPropertyNumber (ctx, obj, idx); +} + +JSValue JS_GetPropertyStr (JSContext *ctx, JSValue this_obj, const char *prop) { + if (JS_VALUE_GET_TAG (this_obj) != JS_TAG_PTR) return JS_NULL; + + size_t len = strlen (prop); + JSValue key; + JSValue ret; + JSGCRef obj_ref; + + JS_PushGCRef (ctx, &obj_ref); + obj_ref.val = this_obj; + + /* Try immediate ASCII first */ + if (len <= MIST_ASCII_MAX_LEN) { + key = MIST_TryNewImmediateASCII (prop, len); + if (JS_IsNull (key)) { key = JS_NewStringLen (ctx, prop, len); } + } else { + key = JS_NewStringLen (ctx, prop, len); + } + if (JS_IsException (key)) { + JS_PopGCRef (ctx, &obj_ref); + return JS_EXCEPTION; + } + ret = JS_GetProperty (ctx, obj_ref.val, key); + JS_PopGCRef (ctx, &obj_ref); + return ret; +} + +/* JS_Invoke - invoke a method on an object by name */ +JSValue JS_Invoke (JSContext *ctx, JSValue this_val, JSValue method, int argc, JSValue *argv) { + JSGCRef this_ref; + JS_PushGCRef (ctx, &this_ref); + this_ref.val = this_val; + JSValue func = JS_GetProperty (ctx, this_ref.val, method); + if (JS_IsException (func)) { JS_PopGCRef (ctx, &this_ref); return JS_EXCEPTION; } + if (!JS_IsFunction (func)) { + JS_PopGCRef (ctx, &this_ref); + return JS_NULL; /* Method not found or not callable */ + } + JSValue ret = JS_Call (ctx, func, this_ref.val, argc, argv); + JS_PopGCRef (ctx, &this_ref); + return ret; +} + +/* GC-SAFE: May trigger GC if record needs to resize */ +int JS_SetProperty (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val) { + if (!JS_IsRecord (this_obj)) { + if (JS_IsNull (this_obj)) { + JS_ThrowTypeError (ctx, "cannot set property of null"); + } else { + JS_ThrowTypeError (ctx, "cannot set property on a primitive"); + } + return -1; + } + + /* All objects are now records - use record set */ + JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (this_obj); + + if (unlikely (obj_is_stone (rec))) { + JS_ThrowTypeError (ctx, "object is stone"); + return -1; + } + + /* Use a local copy that rec_set_own can update if resize happens */ + JSValue obj = this_obj; + return rec_set_own (ctx, &obj, prop, val); +} + +int JS_SetPropertyUint32 (JSContext *ctx, JSValue this_obj, uint32_t idx, JSValue val) { + JSValue ret = JS_SetPropertyNumber (ctx, (JSValue)this_obj, (int)idx, val); + if (JS_IsException (ret)) return -1; + return 0; +} + +int JS_SetPropertyInt64 (JSContext *ctx, JSValue this_obj, int64_t idx, JSValue val) { + if (idx < INT32_MIN || idx > INT32_MAX) { + JS_ThrowRangeError (ctx, "array index out of bounds"); + return -1; + } + JSValue ret = JS_SetPropertyNumber (ctx, (JSValue)this_obj, (int)idx, val); + if (JS_IsException (ret)) return -1; + return 0; +} + +/* GC-SAFE: Protects this_obj and val in case key creation triggers GC */ +int JS_SetPropertyStr (JSContext *ctx, JSValue this_obj, const char *prop, JSValue val) { + /* Protect this_obj and val in case key creation triggers GC */ + JSGCRef obj_ref, val_ref; + JS_AddGCRef (ctx, &obj_ref); + JS_AddGCRef (ctx, &val_ref); + obj_ref.val = this_obj; + val_ref.val = val; + + /* Create JSValue key from string - use js_key_new for interned stone keys */ + JSValue key = js_key_new (ctx, prop); + if (JS_IsException (key)) { + JS_DeleteGCRef (ctx, &val_ref); + JS_DeleteGCRef (ctx, &obj_ref); + return -1; + } + + int ret = JS_SetProperty (ctx, obj_ref.val, key, val_ref.val); + JS_DeleteGCRef (ctx, &val_ref); + JS_DeleteGCRef (ctx, &obj_ref); + return ret; +} + +/* Set property with JSValue prop/key - handles int, string, object keys */ +int JS_SetPropertyValue (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val) { + uint32_t prop_tag = JS_VALUE_GET_TAG (prop); + + if (prop_tag == JS_TAG_INT) { + int idx = JS_VALUE_GET_INT (prop); + JSValue ret = JS_SetPropertyNumber (ctx, this_obj, idx, val); + if (JS_IsException (ret)) return -1; + return 0; + } + + if (JS_IsText (prop)) { + JSValue key = js_key_from_string (ctx, prop); + return JS_SetProperty (ctx, this_obj, key, val); + } + + if (JS_IsRecord (prop)) { + return JS_SetProperty (ctx, this_obj, prop, val); + } + + return -1; +} + +/* Property access with JSValue key - supports object keys directly */ +JSValue JS_GetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key) { + if (JS_IsRecord (key)) { + if (!JS_IsRecord (this_obj)) return JS_NULL; + JSRecord *rec = JS_VALUE_GET_RECORD (this_obj); + return rec_get (ctx, rec, key); + } + + /* For string keys, create an interned key and use JS_GetProperty */ + if (JS_IsText (key)) { + JSValue prop_key = js_key_from_string (ctx, key); + return JS_GetProperty (ctx, this_obj, prop_key); + } + + /* For other types, try to use the value directly as a key */ + return JS_GetProperty (ctx, this_obj, key); +} + +/* CAUTION: rec_set_own is NOT GC-safe if rec_resize is implemented. + Currently safe because rec_resize always fails. + When resize is implemented, rec pointer may become stale. */ +int JS_SetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key, JSValue val) { + if (JS_IsRecord (key)) { + if (!JS_IsRecord (this_obj)) { + JS_ThrowTypeError (ctx, "cannot set property on this value"); + return -1; + } + JSRecord *rec = JS_VALUE_GET_RECORD (this_obj); + if (obj_is_stone (rec)) { + JS_ThrowTypeError (ctx, "cannot modify frozen object"); + return -1; + } + return rec_set_own (ctx, rec, key, val); + } + + /* For string keys, create an interned key */ + if (JS_IsText (key)) { + JSValue prop_key = js_key_from_string (ctx, key); + return JS_SetPropertyInternal (ctx, this_obj, prop_key, val); + } + + /* For other types, use the key directly */ + return JS_SetPropertyInternal (ctx, this_obj, key, val); +} + +/* GC-SAFE for record keys (no allocations). + String keys call js_key_from_string then JS_HasProperty which re-chases. */ +int JS_HasPropertyKey (JSContext *ctx, JSValue obj, JSValue key) { + if (JS_IsRecord (key)) { + if (!JS_IsRecord (obj)) return FALSE; + JSRecord *rec = JS_VALUE_GET_RECORD (obj); + /* Check own and prototype chain */ + while (rec) { + if (rec_find_slot (rec, key) > 0) return TRUE; + rec = rec->proto; + } + return FALSE; + } + + /* For string keys, create an interned key */ + if (JS_IsText (key)) { + JSValue prop_key = js_key_from_string (ctx, key); + return JS_HasProperty (ctx, obj, prop_key); + } + + /* For other types, use directly */ + return JS_HasProperty (ctx, obj, key); +} + +/* GC-SAFE: Only calls rec_find_slot and modifies slots directly */ +int JS_DeletePropertyKey (JSContext *ctx, JSValue obj, JSValue key) { + if (JS_IsRecord (key)) { + if (!JS_IsRecord (obj)) return FALSE; + JSRecord *rec = JS_VALUE_GET_RECORD (obj); + if (obj_is_stone (rec)) { + JS_ThrowTypeError (ctx, "cannot modify frozen object"); + return -1; + } + int slot = rec_find_slot (rec, key); + if (slot <= 0) return FALSE; + /* Delete by marking as tombstone */ + rec->slots[slot].key = JS_EXCEPTION; /* tombstone */ + rec->slots[slot].val = JS_NULL; + rec->len--; + return TRUE; + } + + /* For string keys, create an interned key */ + if (JS_IsText (key)) { + JSValue prop_key = js_key_from_string (ctx, key); + return JS_DeleteProperty (ctx, obj, prop_key); + } + + /* For other types, use directly */ + return JS_DeleteProperty (ctx, obj, key); +} + +/* compute the property flags. For each flag: (JS_PROP_HAS_x forces + it, otherwise def_flags is used) + Note: makes assumption about the bit pattern of the flags +*/ + +/* return TRUE if 'obj' has a non empty 'name' string */ +static BOOL js_object_has_name (JSContext *ctx, JSValue obj) { + if (JS_VALUE_GET_TAG (obj) != JS_TAG_PTR) return FALSE; + JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (obj); + JSValue name_key = MIST_TryNewImmediateASCII ("name", 4); + int slot = rec_find_slot (rec, name_key); + if (slot <= 0) return FALSE; + JSValue val = rec->slots[slot].val; + if (!JS_IsText (val)) return TRUE; /* has name but it's not a string = truthy */ + return (js_string_value_len (val) != 0); +} + +int JS_DefineObjectName (JSContext *ctx, JSValue obj, JSValue name) { + if (!JS_IsNull (name) && JS_IsObject (obj) + && !js_object_has_name (ctx, obj)) { + JSValue name_key = MIST_TryNewImmediateASCII ("name", 4); + if (JS_SetPropertyInternal (ctx, obj, name_key, name) + < 0) + return -1; + } + return 0; +} + +int JS_DefineObjectNameComputed (JSContext *ctx, JSValue obj, JSValue str) { + if (JS_IsObject (obj) && !js_object_has_name (ctx, obj)) { + JSValue name_key = MIST_TryNewImmediateASCII ("name", 4); + if (JS_SetPropertyInternal (ctx, obj, name_key, str) + < 0) + return -1; + } + return 0; +} + + +JSValue JS_ThrowSyntaxErrorVarRedeclaration (JSContext *ctx, + JSValue prop) { + char buf[KEY_GET_STR_BUF_SIZE]; + return JS_ThrowSyntaxError (ctx, "redeclaration of '%s'", JS_KeyGetStr (ctx, buf, sizeof (buf), prop)); +} + +int JS_DeleteProperty (JSContext *ctx, JSValue obj, JSValue prop) { + JSRecord *rec; + int slot; + + /* Arrays do not support property deletion */ + if (JS_IsArray (obj)) { + JS_ThrowTypeError (ctx, "cannot delete array element"); + return -1; + } + + if (JS_VALUE_GET_TAG (obj) != JS_TAG_PTR) { + JS_ThrowTypeErrorNotAnObject (ctx); + return -1; + } + + rec = (JSRecord *)JS_VALUE_GET_OBJ (obj); + if (obj_is_stone (rec)) { + JS_ThrowTypeError (ctx, "cannot delete property of stone object"); + return -1; + } + + slot = rec_find_slot (rec, prop); + if (slot > 0) { + /* Delete by marking as tombstone */ + rec->slots[slot].key = JS_EXCEPTION; /* tombstone */ + rec->slots[slot].val = JS_NULL; + rec->len--; + /* tombs tracking removed - not needed with copying GC */ + return TRUE; + } + return TRUE; /* property not found = deletion succeeded */ +} + +BOOL JS_IsCFunction (JSValue val, JSCFunction *func, int magic) { + JSFunction *f; + if (!JS_IsFunction (val)) return FALSE; + f = JS_VALUE_GET_FUNCTION (val); + if (f->kind == JS_FUNC_KIND_C) + return (f->u.cfunc.c_function.generic == func + && f->u.cfunc.magic == magic); + else + return FALSE; +} + +BOOL JS_IsError (JSContext *ctx, JSValue val) { + JSRecord *p; + if (JS_VALUE_GET_TAG (val) != JS_TAG_PTR) return FALSE; + p = JS_VALUE_GET_OBJ (val); + return (REC_GET_CLASS_ID(p) == JS_CLASS_ERROR); +} + +/* must be called after JS_Throw() - stubbed out, uncatchable not implemented */ +void JS_SetUncatchableException (JSContext *ctx, JS_BOOL flag) { + (void)ctx; (void)flag; + /* uncatchable exception flag not supported with copying GC */ +} + +void JS_SetOpaque (JSValue obj, void *opaque) { + JSRecord *p; + if (JS_VALUE_GET_TAG (obj) == JS_TAG_PTR) { + p = JS_VALUE_GET_OBJ (obj); + REC_SET_OPAQUE(p, opaque); + } +} + +/* return NULL if not an object of class class_id */ +void *JS_GetOpaque (JSValue obj, JSClassID class_id) { + JSRecord *p; + if (JS_VALUE_GET_TAG (obj) != JS_TAG_PTR) return NULL; + p = JS_VALUE_GET_OBJ (obj); + if (REC_GET_CLASS_ID(p) != class_id) return NULL; + return REC_GET_OPAQUE(p); +} + +void *JS_GetOpaque2 (JSContext *ctx, JSValue obj, JSClassID class_id) { + void *p = JS_GetOpaque (obj, class_id); + if (unlikely (!p)) { JS_ThrowTypeErrorInvalidClass (ctx, class_id); } + return p; +} + +void *JS_GetAnyOpaque (JSValue obj, JSClassID *class_id) { + JSRecord *p; + if (!JS_IsRecord (obj)) { + *class_id = 0; + return NULL; + } + p = JS_VALUE_GET_OBJ (obj); + *class_id = REC_GET_CLASS_ID(p); + return REC_GET_OPAQUE(p); +} + +int JS_ToBool (JSContext *ctx, JSValue val) { + uint32_t tag = JS_VALUE_GET_TAG (val); + + /* Check for pointer types first (new tagging system) */ + if (JS_IsPtr (val)) { + void *ptr = JS_VALUE_GET_PTR (val); + objhdr_t hdr = *(objhdr_t *)ptr; + if (objhdr_type (hdr) == OBJ_TEXT) { + /* String (JSText or JSText) - truthy if non-empty */ + BOOL ret = objhdr_cap56 (hdr) != 0; + return ret; + } + /* Objects (record, array, function) are truthy */ + return 1; + } + + switch (tag) { + case JS_TAG_INT: + return JS_VALUE_GET_INT (val) != 0; + case JS_TAG_BOOL: + return JS_VALUE_GET_BOOL (val); + case JS_TAG_NULL: + return 0; + case JS_TAG_EXCEPTION: + return -1; + case JS_TAG_STRING_IMM: { + BOOL ret = MIST_GetImmediateASCIILen (val) != 0; + return ret; + } + default: + if (JS_TAG_IS_FLOAT64 (tag)) { + double d = JS_VALUE_GET_FLOAT64 (val); + return d != 0; /* NaN impossible in short floats */ + } else { + return TRUE; + } + } +} + + + +/* return an exception in case of memory error. Return JS_NAN if + invalid syntax */ +/* XXX: directly use js_atod() */ +JSValue js_atof (JSContext *ctx, const char *str, const char **pp, int radix, int flags) { + const char *p, *p_start; + int sep, is_neg; + BOOL is_float; + int atod_type = flags & ATOD_TYPE_MASK; + char buf1[64], *buf; + int i, j, len; + BOOL buf_allocated = FALSE; + JSValue val; + JSATODTempMem atod_mem; + + /* optional separator between digits */ + sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; + + p = str; + p_start = p; + is_neg = 0; + if (p[0] == '+') { + p++; + p_start++; + if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) goto no_radix_prefix; + } else if (p[0] == '-') { + p++; + p_start++; + is_neg = 1; + if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) goto no_radix_prefix; + } + if (p[0] == '0') { + if ((p[1] == 'x' || p[1] == 'X') && (radix == 0 || radix == 16)) { + p += 2; + radix = 16; + } else if ((p[1] == 'o' || p[1] == 'O') && radix == 0 + && (flags & ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 8; + } else if ((p[1] == 'b' || p[1] == 'B') && radix == 0 + && (flags & ATOD_ACCEPT_BIN_OCT)) { + p += 2; + radix = 2; + } else if ((p[1] >= '0' && p[1] <= '9') && radix == 0 + && (flags & ATOD_ACCEPT_LEGACY_OCTAL)) { + int i; + sep = 256; + for (i = 1; (p[i] >= '0' && p[i] <= '7'); i++) + continue; + if (p[i] == '8' || p[i] == '9') goto no_prefix; + p += 1; + radix = 8; + } else { + goto no_prefix; + } + /* there must be a digit after the prefix */ + if (to_digit ((uint8_t)*p) >= radix) goto fail; + no_prefix:; + } else { + no_radix_prefix: + if (!(flags & ATOD_INT_ONLY) && (atod_type == ATOD_TYPE_FLOAT64) + && strstart (p, "Infinity", &p)) { + double d = 1.0 / 0.0; + if (is_neg) d = -d; + val = JS_NewFloat64 (ctx, d); + goto done; + } + } + if (radix == 0) radix = 10; + is_float = FALSE; + p_start = p; + while (to_digit ((uint8_t)*p) < radix + || (*p == sep && (radix != 10 || p != p_start + 1 || p[-1] != '0') + && to_digit ((uint8_t)p[1]) < radix)) { + p++; + } + if (!(flags & ATOD_INT_ONLY)) { + if (*p == '.' && (p > p_start || to_digit ((uint8_t)p[1]) < radix)) { + is_float = TRUE; + p++; + if (*p == sep) goto fail; + while (to_digit ((uint8_t)*p) < radix + || (*p == sep && to_digit ((uint8_t)p[1]) < radix)) + p++; + } + if (p > p_start + && (((*p == 'e' || *p == 'E') && radix == 10) + || ((*p == 'p' || *p == 'P') + && (radix == 2 || radix == 8 || radix == 16)))) { + const char *p1 = p + 1; + is_float = TRUE; + if (*p1 == '+') { + p1++; + } else if (*p1 == '-') { + p1++; + } + if (is_digit ((uint8_t)*p1)) { + p = p1 + 1; + while (is_digit ((uint8_t)*p) + || (*p == sep && is_digit ((uint8_t)p[1]))) + p++; + } + } + } + if (p == p_start) goto fail; + + buf = buf1; + buf_allocated = FALSE; + len = p - p_start; + if (unlikely ((len + 2) > sizeof (buf1))) { + buf = js_malloc_rt (len + 2); /* no exception raised */ + if (!buf) goto mem_error; + buf_allocated = TRUE; + } + /* remove the separators and the radix prefixes */ + j = 0; + if (is_neg) buf[j++] = '-'; + for (i = 0; i < len; i++) { + if (p_start[i] != '_') buf[j++] = p_start[i]; + } + buf[j] = '\0'; + + if (flags & ATOD_ACCEPT_SUFFIX) { + if (*p == 'n') { + p++; + atod_type = ATOD_TYPE_BIG_INT; + } else { + if (is_float && radix != 10) goto fail; + } + } else { + if (atod_type == ATOD_TYPE_FLOAT64) { + if (is_float && radix != 10) goto fail; + } + } + + switch (atod_type) { + case ATOD_TYPE_FLOAT64: { + double d; + d = js_atod (buf, NULL, radix, is_float ? 0 : JS_ATOD_INT_ONLY, &atod_mem); + /* return int or float64 */ + val = JS_NewFloat64 (ctx, d); + } break; + default: + abort (); + } + +done: + if (buf_allocated) js_free_rt (buf); + if (pp) *pp = p; + return val; +fail: + val = JS_NAN; + goto done; +mem_error: + val = JS_ThrowOutOfMemory (ctx); + goto done; +} + +JSValue JS_ToNumber (JSContext *ctx, JSValue val) { + uint32_t tag; + JSValue ret; + + /* Handle pointer types (new tagging system) */ + if (JS_IsPtr (val)) { + void *ptr = JS_VALUE_GET_PTR (val); + objhdr_t hdr = *(objhdr_t *)ptr; + if (objhdr_type (hdr) == OBJ_TEXT) { + /* String */ + return JS_ThrowTypeError (ctx, "cannot convert text to a number"); + } + /* Objects */ + return JS_ThrowTypeError (ctx, "cannot convert object to number"); + } + + tag = JS_VALUE_GET_NORM_TAG (val); + switch (tag) { + case JS_TAG_SHORT_FLOAT: + case JS_TAG_INT: + case JS_TAG_EXCEPTION: + ret = val; + break; + case JS_TAG_BOOL: + case JS_TAG_NULL: + ret = JS_NewInt32 (ctx, JS_VALUE_GET_INT (val)); + break; + case JS_TAG_STRING_IMM: + return JS_ThrowTypeError (ctx, "cannot convert text to a number"); + default: + ret = JS_NAN; + break; + } + return ret; +} + +static __exception int __JS_ToFloat64 (JSContext *ctx, double *pres, JSValue val) { + double d; + uint32_t tag; + + val = JS_ToNumber (ctx, val); + if (JS_IsException (val)) goto fail; + tag = JS_VALUE_GET_NORM_TAG (val); + switch (tag) { + case JS_TAG_INT: + d = JS_VALUE_GET_INT (val); + break; + case JS_TAG_FLOAT64: + d = JS_VALUE_GET_FLOAT64 (val); + break; + default: + abort (); + } + *pres = d; + return 0; +fail: + *pres = NAN; + return -1; +} + +int JS_ToFloat64 (JSContext *ctx, double *pres, JSValue val) { + uint32_t tag; + + tag = JS_VALUE_GET_TAG (val); + if (tag == JS_TAG_INT) { + *pres = JS_VALUE_GET_INT (val); + return 0; + } else if (JS_TAG_IS_FLOAT64 (tag)) { + *pres = JS_VALUE_GET_FLOAT64 (val); + return 0; + } else { + return __JS_ToFloat64 (ctx, pres, val); + } +} + +/* Note: the integer value is satured to 32 bits */ +int JS_ToInt32Sat (JSContext *ctx, int *pres, JSValue val) { + uint32_t tag; + int ret; + +redo: + tag = JS_VALUE_GET_NORM_TAG (val); + switch (tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + ret = JS_VALUE_GET_INT (val); + break; + case JS_TAG_EXCEPTION: + *pres = 0; + return -1; + case JS_TAG_FLOAT64: { + double d = JS_VALUE_GET_FLOAT64 (val); + /* NaN impossible in short floats */ + if (d < INT32_MIN) + ret = INT32_MIN; + else if (d > INT32_MAX) + ret = INT32_MAX; + else + ret = (int)d; + } break; + default: + val = JS_ToNumber (ctx, val); + if (JS_IsException (val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = ret; + return 0; +} + +int JS_ToInt32Clamp (JSContext *ctx, int *pres, JSValue val, int min, int max, int min_offset) { + int res = JS_ToInt32Sat (ctx, pres, val); + if (res == 0) { + if (*pres < min) { + *pres += min_offset; + if (*pres < min) *pres = min; + } else { + if (*pres > max) *pres = max; + } + } + return res; +} + +int JS_ToInt64Sat (JSContext *ctx, int64_t *pres, JSValue val) { + uint32_t tag; + +redo: + tag = JS_VALUE_GET_NORM_TAG (val); + switch (tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + *pres = JS_VALUE_GET_INT (val); + return 0; + case JS_TAG_EXCEPTION: + *pres = 0; + return -1; + case JS_TAG_FLOAT64: { + double d = JS_VALUE_GET_FLOAT64 (val); + /* NaN impossible in short floats */ + if (d < INT64_MIN) + *pres = INT64_MIN; + else if (d >= 0x1p63) /* must use INT64_MAX + 1 because INT64_MAX cannot + be exactly represented as a double */ + *pres = INT64_MAX; + else + *pres = (int64_t)d; + } + return 0; + default: + val = JS_ToNumber (ctx, val); + if (JS_IsException (val)) { + *pres = 0; + return -1; + } + goto redo; + } +} + +int JS_ToInt64Clamp (JSContext *ctx, int64_t *pres, JSValue val, int64_t min, int64_t max, int64_t neg_offset) { + int res = JS_ToInt64Sat (ctx, pres, val); + if (res == 0) { + if (*pres < 0) *pres += neg_offset; + if (*pres < min) + *pres = min; + else if (*pres > max) + *pres = max; + } + return res; +} + +/* Same as JS_ToInt32() but with a 64 bit result. Return (<0, 0) + in case of exception */ +int JS_ToInt64 (JSContext *ctx, int64_t *pres, JSValue val) { + uint32_t tag; + int64_t ret; + +redo: + tag = JS_VALUE_GET_NORM_TAG (val); + switch (tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + ret = JS_VALUE_GET_INT (val); + break; + case JS_TAG_FLOAT64: { + JSFloat64Union u; + double d; + int e; + d = JS_VALUE_GET_FLOAT64 (val); + u.d = d; + /* we avoid doing fmod(x, 2^64) */ + e = (u.u64 >> 52) & 0x7ff; + if (likely (e <= (1023 + 62))) { + /* fast case */ + ret = (int64_t)d; + } else if (e <= (1023 + 62 + 53)) { + uint64_t v; + /* remainder modulo 2^64 */ + v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); + ret = v << ((e - 1023) - 52); + /* take the sign into account */ + if (u.u64 >> 63) ret = -ret; + } else { + ret = 0; /* also handles NaN and +inf */ + } + } break; + default: + val = JS_ToNumber (ctx, val); + if (JS_IsException (val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = ret; + return 0; +} + +/* return (<0, 0) in case of exception */ +int JS_ToInt32 (JSContext *ctx, int32_t *pres, JSValue val) { + uint32_t tag; + int32_t ret; + +redo: + tag = JS_VALUE_GET_NORM_TAG (val); + switch (tag) { + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + ret = JS_VALUE_GET_INT (val); + break; + case JS_TAG_FLOAT64: { + JSFloat64Union u; + double d; + int e; + d = JS_VALUE_GET_FLOAT64 (val); + u.d = d; + /* we avoid doing fmod(x, 2^32) */ + e = (u.u64 >> 52) & 0x7ff; + if (likely (e <= (1023 + 30))) { + /* fast case */ + ret = (int32_t)d; + } else if (e <= (1023 + 30 + 53)) { + uint64_t v; + /* remainder modulo 2^32 */ + v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); + v = v << ((e - 1023) - 52 + 32); + ret = v >> 32; + /* take the sign into account */ + if (u.u64 >> 63) ret = -ret; + } else { + ret = 0; /* also handles NaN and +inf */ + } + } break; + default: + *pres = 0; + return -1; + val = JS_ToNumber (ctx, val); + if (JS_IsException (val)) { + *pres = 0; + return -1; + } + goto redo; + } + *pres = ret; + return 0; +} + +#define MAX_SAFE_INTEGER (((int64_t)1 << 53) - 1) + +/* convert a value to a length between 0 and MAX_SAFE_INTEGER. + return -1 for exception */ +static __exception int JS_ToLength (JSContext *ctx, int64_t *plen, JSValue val) { + int res = JS_ToInt64Clamp (ctx, plen, val, 0, MAX_SAFE_INTEGER, 0); + return res; +} + +static JSValue js_dtoa2 (JSContext *ctx, double d, int radix, int n_digits, int flags) { + char static_buf[128], *buf, *tmp_buf; + int len, len_max; + JSValue res; + JSDTOATempMem dtoa_mem; + len_max = js_dtoa_max_len (d, radix, n_digits, flags); + + /* longer buffer may be used if radix != 10 */ + if (len_max > sizeof (static_buf) - 1) { + tmp_buf = js_malloc (ctx, len_max + 1); + if (!tmp_buf) return JS_EXCEPTION; + buf = tmp_buf; + } else { + tmp_buf = NULL; + buf = static_buf; + } + len = js_dtoa (buf, d, radix, n_digits, flags, &dtoa_mem); + res = js_new_string8_len (ctx, buf, len); + js_free (ctx, tmp_buf); + return res; +} + +JSValue JS_ToString (JSContext *ctx, JSValue val) { + uint32_t tag; + char buf[32]; + + /* Handle pointer types (new tagging system) */ + if (JS_IsPtr (val)) { + void *ptr = JS_VALUE_GET_PTR (val); + objhdr_t hdr = *(objhdr_t *)ptr; + uint8_t mist_type = objhdr_type (hdr); + if (mist_type == OBJ_TEXT) { + /* String - return as-is */ + return val; + } + /* Objects (record, array, function) */ + return JS_KEY_true; + } + + tag = JS_VALUE_GET_NORM_TAG (val); + switch (tag) { + case JS_TAG_STRING_IMM: + return val; + case JS_TAG_INT: { + size_t len; + len = i32toa (buf, JS_VALUE_GET_INT (val)); + return js_new_string8_len (ctx, buf, len); + } break; + case JS_TAG_BOOL: + return JS_VALUE_GET_BOOL (val) ? JS_KEY_true : JS_KEY_false; + case JS_TAG_NULL: + return JS_KEY_null; + case JS_TAG_EXCEPTION: + return JS_EXCEPTION; + case JS_TAG_SHORT_FLOAT: + return js_dtoa2 (ctx, JS_VALUE_GET_FLOAT64 (val), 10, 0, JS_DTOA_FORMAT_FREE); + default: + return js_new_string8 (ctx, "[unsupported type]"); + } +} + +static JSValue JS_ToStringCheckObject (JSContext *ctx, JSValue val) { + uint32_t tag = JS_VALUE_GET_TAG (val); + if (tag == JS_TAG_NULL) return JS_ThrowTypeError (ctx, "null is forbidden"); + return JS_ToString (ctx, val); +} + +static JSValue JS_ToQuotedString (JSContext *ctx, JSValue val1) { + JSValue val; + int i, len; + uint32_t c; + JSText *b; + char buf[16]; + + val = JS_ToStringCheckObject (ctx, val1); + if (JS_IsException (val)) return val; + + /* Use js_string_value_len to handle both immediate and heap strings */ + len = js_string_value_len (val); + + b = pretext_init (ctx, len + 2); + if (!b) goto fail; + + b = pretext_putc (ctx, b, '\"'); + if (!b) goto fail; + for (i = 0; i < len; i++) { + c = js_string_value_get (val, i); + switch (c) { + case '\t': + c = 't'; + goto quote; + case '\r': + c = 'r'; + goto quote; + case '\n': + c = 'n'; + goto quote; + case '\b': + c = 'b'; + goto quote; + case '\f': + c = 'f'; + goto quote; + case '\"': + case '\\': + quote: + b = pretext_putc (ctx, b, '\\'); + if (!b) goto fail; + b = pretext_putc (ctx, b, c); + if (!b) goto fail; + break; + default: + if (c < 32 || is_surrogate (c)) { + snprintf (buf, sizeof (buf), "\\u%04x", c); + b = pretext_puts8 (ctx, b, buf); + if (!b) goto fail; + } else { + b = pretext_putc (ctx, b, c); + if (!b) goto fail; + } + break; + } + } + b = pretext_putc (ctx, b, '\"'); + if (!b) goto fail; + return pretext_end (ctx, b); +fail: + return JS_EXCEPTION; +} + +#define JS_PRINT_MAX_DEPTH 8 + +typedef struct { + JSRuntime *rt; + JSContext *ctx; /* may be NULL */ + JSPrintValueOptions options; + JSPrintValueWrite *write_func; + void *write_opaque; + int level; + JSRecord *print_stack[JS_PRINT_MAX_DEPTH]; /* level values */ +} JSPrintValueState; + +static void js_print_value (JSPrintValueState *s, JSValue val); + +static void js_putc (JSPrintValueState *s, char c) { + s->write_func (s->write_opaque, &c, 1); +} + +static void js_puts (JSPrintValueState *s, const char *str) { + s->write_func (s->write_opaque, str, strlen (str)); +} + +static void __attribute__ ((format (printf, 2, 3))) +js_printf (JSPrintValueState *s, const char *fmt, ...) { + va_list ap; + char buf[256]; + + va_start (ap, fmt); + vsnprintf (buf, sizeof (buf), fmt, ap); + va_end (ap); + s->write_func (s->write_opaque, buf, strlen (buf)); +} + +static void js_print_float64 (JSPrintValueState *s, double d) { + JSDTOATempMem dtoa_mem; + char buf[32]; + int len; + len = js_dtoa (buf, d, 10, 0, JS_DTOA_FORMAT_FREE | JS_DTOA_MINUS_ZERO, &dtoa_mem); + s->write_func (s->write_opaque, buf, len); +} + +static void js_dump_char (JSPrintValueState *s, int c, int sep) { + if (c == sep || c == '\\') { + js_putc (s, '\\'); + js_putc (s, c); + } else if (c >= ' ' && c <= 126) { + js_putc (s, c); + } else if (c == '\n') { + js_putc (s, '\\'); + js_putc (s, 'n'); + } else { + js_printf (s, "\\u%04x", c); + } +} + +static void js_print_string_rec (JSPrintValueState *s, JSValue val, int sep, uint32_t pos) { + if (MIST_IsImmediateASCII (val)) { + /* Immediate ASCII string */ + int len = MIST_GetImmediateASCIILen (val); + if (pos < s->options.max_string_length) { + uint32_t i, l; + l = min_uint32 (len, s->options.max_string_length - pos); + for (i = 0; i < l; i++) { + js_dump_char (s, MIST_GetImmediateASCIIChar (val, i), sep); + } + } + } else if (JS_IsPtr (val) && objhdr_type (*(objhdr_t *)JS_VALUE_GET_PTR (val)) == OBJ_TEXT) { + /* Heap text (JSText) */ + JSText *p = (JSText *)JS_VALUE_GET_PTR (val); + uint32_t i, len; + if (pos < s->options.max_string_length) { + len = min_uint32 ((uint32_t)JSText_len (p), s->options.max_string_length - pos); + for (i = 0; i < len; i++) { + js_dump_char (s, string_get (p, i), sep); + } + } + } else { + js_printf (s, "", (int)JS_VALUE_GET_TAG (val)); + } +} + +static void js_print_string (JSPrintValueState *s, JSValue val) { + int sep = '\"'; + js_putc (s, sep); + js_print_string_rec (s, val, sep, 0); + js_putc (s, sep); + if (js_string_get_length (val) > s->options.max_string_length) { + uint32_t n = js_string_get_length (val) - s->options.max_string_length; + js_printf (s, "... %u more character%s", n, n > 1 ? "s" : ""); + } +} + +static void js_print_raw_string2 (JSPrintValueState *s, JSValue val, BOOL remove_last_lf) { + const char *cstr; + size_t len; + cstr = JS_ToCStringLen (s->ctx, &len, val); + if (cstr) { + if (remove_last_lf && len > 0 && cstr[len - 1] == '\n') len--; + s->write_func (s->write_opaque, cstr, len); + JS_FreeCString (s->ctx, cstr); + } +} + +static void js_print_raw_string (JSPrintValueState *s, JSValue val) { + js_print_raw_string2 (s, val, FALSE); +} + +static void js_print_comma (JSPrintValueState *s, int *pcomma_state) { + switch (*pcomma_state) { + case 0: + break; + case 1: + js_printf (s, ", "); + break; + case 2: + js_printf (s, " { "); + break; + } + *pcomma_state = 1; +} + +static void js_print_more_items (JSPrintValueState *s, int *pcomma_state, uint32_t n) { + js_print_comma (s, pcomma_state); + js_printf (s, "... %u more item%s", n, n > 1 ? "s" : ""); +} + +static void js_print_value (JSPrintValueState *s, JSValue val) { + uint32_t tag = JS_VALUE_GET_NORM_TAG (val); + const char *str; + + /* Handle pointer types first (new tagging system) */ + if (JS_IsPtr (val)) { + void *ptr = JS_VALUE_GET_PTR (val); + /* Check objhdr_t at offset 8 for type */ + objhdr_t hdr = *(objhdr_t *)ptr; + uint8_t mist_type = objhdr_type (hdr); + + if (mist_type == OBJ_TEXT) { + /* String (JSText or JSText) */ + js_print_string (s, val); + return; + } + return; + } + + switch (tag) { + case JS_TAG_INT: + js_printf (s, "%d", JS_VALUE_GET_INT (val)); + break; + case JS_TAG_BOOL: + if (JS_VALUE_GET_BOOL (val)) + str = "true"; + else + str = "false"; + goto print_str; + case JS_TAG_NULL: + str = "null"; + goto print_str; + case JS_TAG_EXCEPTION: + str = "exception"; + goto print_str; + case JS_TAG_UNINITIALIZED: + str = "uninitialized"; + goto print_str; + print_str: + js_puts (s, str); + break; + case JS_TAG_SHORT_FLOAT: + js_print_float64 (s, JS_VALUE_GET_FLOAT64 (val)); + break; + case JS_TAG_STRING_IMM: + js_print_string (s, val); + break; + default: + js_printf (s, "[unknown tag %d]", tag); + break; + } +} + +void JS_PrintValueSetDefaultOptions (JSPrintValueOptions *options) { + memset (options, 0, sizeof (*options)); + options->max_depth = 2; + options->max_string_length = 1000; + options->max_item_count = 100; +} + +static void JS_PrintValueInternal (JSRuntime *rt, JSContext *ctx, JSPrintValueWrite *write_func, void *write_opaque, JSValue val, const JSPrintValueOptions *options) { + JSPrintValueState ss, *s = &ss; + if (options) + s->options = *options; + else + JS_PrintValueSetDefaultOptions (&s->options); + if (s->options.max_depth <= 0) + s->options.max_depth = JS_PRINT_MAX_DEPTH; + else + s->options.max_depth = min_int (s->options.max_depth, JS_PRINT_MAX_DEPTH); + if (s->options.max_string_length == 0) + s->options.max_string_length = UINT32_MAX; + if (s->options.max_item_count == 0) s->options.max_item_count = UINT32_MAX; + s->rt = rt; + s->ctx = ctx; + s->write_func = write_func; + s->write_opaque = write_opaque; + s->level = 0; + js_print_value (s, val); +} + +void JS_PrintValueRT (JSRuntime *rt, JSPrintValueWrite *write_func, void *write_opaque, JSValue val, const JSPrintValueOptions *options) { + JS_PrintValueInternal (rt, NULL, write_func, write_opaque, val, options); +} + +void JS_PrintValue (JSContext *ctx, JSPrintValueWrite *write_func, void *write_opaque, JSValue val, const JSPrintValueOptions *options) { + JS_PrintValueInternal (ctx->rt, ctx, write_func, write_opaque, val, options); +} + +void js_dump_value_write (void *opaque, const char *buf, size_t len) { + FILE *fo = opaque; + fwrite (buf, 1, len, fo); +} + +/* print_atom removed - atoms no longer used */ + +__maybe_unused void JS_DumpValue (JSContext *ctx, const char *str, JSValue val) { + printf ("%s=", str); + JS_PrintValue (ctx, js_dump_value_write, stdout, val, NULL); + printf ("\n"); +} + +__maybe_unused void JS_DumpObjectHeader (JSRuntime *rt) { + printf ("%14s %4s %4s %14s %s\n", "ADDRESS", "REFS", "SHRF", "PROTO", "CONTENT"); +} + +/* for debug only: dump an object without side effect */ +__maybe_unused void JS_DumpObject (JSRuntime *rt, JSRecord *rec) { + JSPrintValueOptions options; + + printf ("%14p ", (void *)rec); + /* Print prototype from JSRecord */ + if (rec->proto) { + printf ("%14p ", (void *)rec->proto); + } else { + printf ("%14s ", "-"); + } + + JS_PrintValueSetDefaultOptions (&options); + options.max_depth = 1; + options.show_hidden = TRUE; + options.raw_dump = TRUE; + JS_PrintValueRT (rt, js_dump_value_write, stdout, JS_MKPTR (rec), &options); + + printf ("\n"); +} + +__maybe_unused void JS_DumpGCObject (JSRuntime *rt, + objhdr_t *p) { + if (objhdr_type (*p) == OBJ_RECORD) { + JS_DumpObject (rt, (JSRecord *)p); + } else { + switch (objhdr_type (*p)) { + case OBJ_CODE: + printf ("[function bytecode]"); + break; + case OBJ_ARRAY: + printf ("[array]"); + break; + case OBJ_RECORD: + printf ("[record]"); + break; + default: + printf ("[unknown %d]", objhdr_type (*p)); + break; + } + printf ("\n"); + } +} + +/* return -1 if exception (proxy case) or TRUE/FALSE */ +static double js_pow (double a, double b) { + if (unlikely (!isfinite (b)) && fabs (a) == 1) { + /* not compatible with IEEE 754 */ + return NAN; + } else { + return pow (a, b); + } +} + +no_inline __exception int +js_unary_arith_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op) { + JSValue op1; + int v; + uint32_t tag; + + op1 = sp[-1]; + tag = JS_VALUE_GET_TAG (op1); + switch (tag) { + case JS_TAG_INT: { + int64_t v64; + v64 = JS_VALUE_GET_INT (op1); + switch (op) { + case OP_inc: + case OP_dec: + v = 2 * (op - OP_dec) - 1; + v64 += v; + break; + case OP_plus: + break; + case OP_neg: + v64 = -v64; /* -0 normalized to 0 by __JS_NewFloat64 */ + break; + default: + abort (); + } + sp[-1] = JS_NewInt64 (ctx, v64); + } break; + case JS_TAG_FLOAT64: { + double d; + d = JS_VALUE_GET_FLOAT64 (op1); + switch (op) { + case OP_inc: + case OP_dec: + v = 2 * (op - OP_dec) - 1; + d += v; + break; + case OP_plus: + break; + case OP_neg: + d = -d; + break; + default: + abort (); + } + sp[-1] = __JS_NewFloat64 (ctx, d); + } break; + default: + sp[-1] = JS_NULL; + } + return 0; +} + +__exception int js_post_inc_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op) { + sp[0] = sp[-1]; + return js_unary_arith_slow (ctx, sp + 1, op - OP_post_dec + OP_dec); +} + +no_inline int js_not_slow (JSContext *ctx, JSValue *sp) { + JSValue op1; + + op1 = sp[-1]; + op1 = JS_ToNumber (ctx, op1); + if (JS_IsException (op1)) goto exception; + int32_t v1; + if (unlikely (JS_ToInt32 (ctx, &v1, op1))) goto exception; + sp[-1] = JS_NewInt32 (ctx, ~v1); + return 0; +exception: + sp[-1] = JS_NULL; + return -1; +} + +no_inline __exception int +js_binary_arith_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op) { + JSValue op1, op2; + uint32_t tag1, tag2; + double d1, d2; + + op1 = sp[-2]; + op2 = sp[-1]; + tag1 = JS_VALUE_GET_NORM_TAG (op1); + tag2 = JS_VALUE_GET_NORM_TAG (op2); + /* fast path for float operations */ + if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { + d1 = JS_VALUE_GET_FLOAT64 (op1); + d2 = JS_VALUE_GET_FLOAT64 (op2); + goto handle_float64; + } + + if ((tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64) + || (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_INT)) { + if (tag1 == JS_TAG_INT) + d1 = (double)JS_VALUE_GET_INT (op1); + else + d1 = JS_VALUE_GET_FLOAT64 (op1); + if (tag2 == JS_TAG_INT) + d2 = (double)JS_VALUE_GET_INT (op2); + else + d2 = JS_VALUE_GET_FLOAT64 (op2); + goto handle_float64; + } + + op1 = JS_ToNumber (ctx, op1); + if (JS_IsException (op1)) { + goto exception; + } + op2 = JS_ToNumber (ctx, op2); + if (JS_IsException (op2)) { + goto exception; + } + tag1 = JS_VALUE_GET_NORM_TAG (op1); + tag2 = JS_VALUE_GET_NORM_TAG (op2); + + if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { + int32_t v1, v2; + int64_t v; + v1 = JS_VALUE_GET_INT (op1); + v2 = JS_VALUE_GET_INT (op2); + switch (op) { + case OP_sub: + v = (int64_t)v1 - (int64_t)v2; + break; + case OP_mul: + v = (int64_t)v1 * (int64_t)v2; + /* -0 normalized to 0, no special case needed */ + break; + case OP_div: + sp[-2] = JS_NewFloat64 (ctx, (double)v1 / (double)v2); + return 0; + case OP_mod: + if (v1 < 0 || v2 <= 0) { + sp[-2] = JS_NewFloat64 (ctx, fmod (v1, v2)); + return 0; + } else { + v = (int64_t)v1 % (int64_t)v2; + } + break; + case OP_pow: + sp[-2] = JS_NewFloat64 (ctx, js_pow (v1, v2)); + return 0; + default: + abort (); + } + sp[-2] = JS_NewInt64 (ctx, v); + } else { + double dr; + /* float64 result */ + if (JS_ToFloat64 (ctx, &d1, op1)) { + goto exception; + } + if (JS_ToFloat64 (ctx, &d2, op2)) goto exception; + handle_float64: + switch (op) { + case OP_sub: + dr = d1 - d2; + break; + case OP_mul: + dr = d1 * d2; + break; + case OP_div: + dr = d1 / d2; + break; + case OP_mod: + dr = fmod (d1, d2); + break; + case OP_pow: + dr = js_pow (d1, d2); + break; + default: + abort (); + } + sp[-2] = __JS_NewFloat64 (ctx, dr); + } + return 0; +exception: + sp[-2] = JS_NULL; + sp[-1] = JS_NULL; + return -1; +} + +no_inline int js_relational_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op) { + JSValue op1 = sp[-2], op2 = sp[-1]; + uint32_t tag1 = JS_VALUE_GET_NORM_TAG (op1); + uint32_t tag2 = JS_VALUE_GET_NORM_TAG (op2); + int res; + + /* string <=> string */ + if (JS_IsText (op1) && JS_IsText (op2)) { + res = js_string_compare_value (ctx, op1, op2, FALSE); + + switch (op) { + case OP_lt: + res = (res < 0); + break; + case OP_lte: + res = (res <= 0); + break; + case OP_gt: + res = (res > 0); + break; + default: + res = (res >= 0); + break; + } + + /* number <=> number */ + } else if ((tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64) + && (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64)) { + double d1 = (tag1 == JS_TAG_FLOAT64 ? JS_VALUE_GET_FLOAT64 (op1) + : (double)JS_VALUE_GET_INT (op1)); + double d2 = (tag2 == JS_TAG_FLOAT64 ? JS_VALUE_GET_FLOAT64 (op2) + : (double)JS_VALUE_GET_INT (op2)); + + switch (op) { + case OP_lt: + res = (d1 < d2); + break; + case OP_lte: + res = (d1 <= d2); + break; + case OP_gt: + res = (d1 > d2); + break; + default: + res = (d1 >= d2); + break; + } + + /* anything else → TypeError */ + } else { + JS_ThrowTypeError ( + ctx, + "Relational operators only supported on two strings or two numbers"); + goto exception; + } + + /* free the two input values and push the result */ + sp[-2] = JS_NewBool (ctx, res); + return 0; + +exception: + sp[-2] = JS_NULL; + sp[-1] = JS_NULL; + return -1; +} + +/* Simplified equality: no NaN (becomes null), no coercion, no SameValue distinction */ +BOOL js_strict_eq (JSContext *ctx, JSValue op1, JSValue op2) { + /* Fast path: identical values */ + if (op1 == op2) return TRUE; + + int tag1 = JS_VALUE_GET_NORM_TAG (op1); + int tag2 = JS_VALUE_GET_NORM_TAG (op2); + + /* Different types are never equal (no coercion) */ + /* Special case: INT and FLOAT can be equal */ + if (tag1 != tag2) { + if (!((tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64) && + (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64))) + return FALSE; + } + + switch (tag1) { + case JS_TAG_INT: + case JS_TAG_FLOAT64: { + /* Numbers: unpack and compare */ + double d1 = (tag1 == JS_TAG_INT) ? (double)JS_VALUE_GET_INT (op1) + : JS_VALUE_GET_FLOAT64 (op1); + double d2 = (tag2 == JS_TAG_INT) ? (double)JS_VALUE_GET_INT (op2) + : JS_VALUE_GET_FLOAT64 (op2); + return d1 == d2; + } + case JS_TAG_STRING_IMM: + /* Immediate text vs immediate text (handled by op1 == op2 fast path) */ + /* or vs heap text */ + if (JS_IsText (op2)) + return js_string_compare_value (ctx, op1, op2, TRUE) == 0; + return FALSE; + case JS_TAG_PTR: + /* Heap text vs heap text or vs immediate text */ + if (JS_IsText (op1) && JS_IsText (op2)) + return js_string_compare_value (ctx, op1, op2, TRUE) == 0; + /* Records/objects: pointer equality (op1 == op2 handles same object) */ + return FALSE; /* Different objects */ + case JS_TAG_BOOL: + case JS_TAG_NULL: + /* Already handled by op1 == op2 fast path */ + return FALSE; + default: + return FALSE; + } +} + +BOOL JS_StrictEq (JSContext *ctx, JSValue op1, JSValue op2) { + return js_strict_eq (ctx, op1, op2); +} + +no_inline int js_strict_eq_slow (JSContext *ctx, JSValue *sp, BOOL is_neq) { + BOOL res = js_strict_eq (ctx, sp[-2], sp[-1]); + sp[-2] = JS_NewBool (ctx, res ^ is_neq); + return 0; +} + +__exception int js_operator_in (JSContext *ctx, JSValue *sp) { + JSValue op1, op2; + int ret; + + op1 = sp[-2]; + op2 = sp[-1]; + + if (JS_VALUE_GET_TAG (op2) != JS_TAG_PTR) { + JS_ThrowTypeError (ctx, "invalid 'in' operand"); + return -1; + } + ret = JS_HasPropertyKey (ctx, op2, op1); + if (ret < 0) return -1; + sp[-2] = JS_NewBool (ctx, ret); + return 0; +} + +__exception int js_operator_delete (JSContext *ctx, JSValue *sp) { + JSValue op1, op2; + int ret; + + op1 = sp[-2]; + op2 = sp[-1]; + + ret = JS_DeletePropertyKey (ctx, op1, op2); + if (unlikely (ret < 0)) return -1; + sp[-2] = JS_NewBool (ctx, ret); + return 0; +} + +/* XXX: not 100% compatible, but mozilla seems to use a similar + implementation to ensure that caller in non strict mode does not + throw (ES5 compatibility) */ +static JSValue js_throw_type_error (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + return JS_ThrowTypeError (ctx, "invalid property access"); +} + + +__exception int JS_CopyDataProperties (JSContext *ctx, JSValue target, JSValue source, JSValue excluded, BOOL setprop) { + JSValue keys, key, val; + uint32_t i, key_count; + int ret; + + if (JS_VALUE_GET_TAG (source) != JS_TAG_PTR) return 0; + + /* Get all string keys from source */ + keys = JS_GetOwnPropertyNames (ctx, source); + if (JS_IsException (keys)) return -1; + if (js_get_length32 (ctx, &key_count, keys)) { + return -1; + } + + for (i = 0; i < key_count; i++) { + key = JS_GetPropertyUint32 (ctx, keys, i); + if (JS_IsException (key)) goto exception; + + /* Check if key is excluded */ + if (JS_VALUE_GET_TAG (excluded) == JS_TAG_PTR) { + /* Check if key exists in excluded object */ + JSValue test = JS_GetProperty (ctx, excluded, key); + if (!JS_IsNull (test) && !JS_IsException (test)) { + continue; + } + } + + /* Get property value from source */ + val = JS_GetProperty (ctx, source, key); + if (JS_IsException (val)) { + goto exception; + } + + /* Set property on target */ + ret = JS_SetProperty (ctx, target, key, val); + if (ret < 0) goto exception; + } + + return 0; + +exception: + return -1; +} + +JSValue js_closure2 (JSContext *ctx, JSValue func_obj, JSFunctionBytecode *b, JSStackFrame *sf) { + JSFunction *f; + f = JS_VALUE_GET_FUNCTION (func_obj); + f->u.func.function_bytecode = b; + + /* Set outer_frame to parent's JSFrame for the new closure model. + This allows OP_get_up/OP_set_up to access captured variables via frame chain. */ + f->u.func.outer_frame = sf ? sf->js_frame : JS_NULL; + + return func_obj; +} + +JSValue js_closure (JSContext *ctx, JSValue bfunc, JSStackFrame *sf) { + JSFunctionBytecode *b; + JSValue func_obj; + JSFunction *f; + JSGCRef bfunc_ref; + + /* Protect bfunc from GC during function allocation */ + JS_PUSH_VALUE (ctx, bfunc); + + func_obj = js_new_function (ctx, JS_FUNC_KIND_BYTECODE); + if (JS_IsException (func_obj)) { + JS_POP_VALUE (ctx, bfunc); + return JS_EXCEPTION; + } + + JS_POP_VALUE (ctx, bfunc); + b = JS_VALUE_GET_PTR (bfunc); + + func_obj = js_closure2 (ctx, func_obj, b, sf); + if (JS_IsException (func_obj)) { + /* bfunc has been freed */ + goto fail; + } + f = JS_VALUE_GET_FUNCTION (func_obj); + /* Use bytecode func_name if valid, otherwise empty string */ + f->name = JS_IsText (b->func_name) ? b->func_name + : JS_KEY_empty; + f->length = b->arg_count; /* arity = total parameter count */ + return func_obj; +fail: + /* bfunc is freed when func_obj is freed */ + return JS_EXCEPTION; +} + + +JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv) { + JSCFunctionType func; + JSFunction *f; + JSStackFrame sf_s, *sf = &sf_s, *prev_sf; + JSValue ret_val; + JSValue *arg_buf; + int arg_count, i; + int saved_vs_top = -1; /* for value stack padding cleanup */ + JSCFunctionEnum cproto; + + f = JS_VALUE_GET_FUNCTION (func_obj); + cproto = f->u.cfunc.cproto; + arg_count = f->length; + + /* better to always check stack overflow */ + if (js_check_stack_overflow (ctx, sizeof (arg_buf[0]) * arg_count)) + return JS_ThrowStackOverflow (ctx); + + prev_sf = ctx->current_stack_frame; + sf->prev_frame = prev_sf; + ctx->current_stack_frame = sf; + sf->js_mode = 0; + sf->cur_func = (JSValue)func_obj; + sf->arg_count = argc; + sf->js_frame = JS_NULL; /* C functions don't have JSFrame */ + sf->stack_buf = NULL; /* C functions don't have operand stack */ + sf->p_sp = NULL; + arg_buf = argv; + + if (unlikely (argc < arg_count)) { + /* Pad args on the value stack (GC-scanned) instead of alloca */ + saved_vs_top = ctx->value_stack_top; + for (i = 0; i < argc; i++) + ctx->value_stack[saved_vs_top + i] = argv[i]; + for (i = argc; i < arg_count; i++) + ctx->value_stack[saved_vs_top + i] = JS_NULL; + ctx->value_stack_top = saved_vs_top + arg_count; + arg_buf = &ctx->value_stack[saved_vs_top]; + sf->arg_count = arg_count; + } + sf->arg_buf = (JSValue *)arg_buf; + + func = f->u.cfunc.c_function; + + if (unlikely (ctx->trace_hook) && (ctx->trace_type & JS_HOOK_CALL)) { + js_debug dbg; + js_debug_info (ctx, func_obj, &dbg); + ctx->trace_hook (ctx, JS_HOOK_CALL, &dbg, ctx->trace_data); + } + + switch (cproto) { + case JS_CFUNC_generic: + ret_val = func.generic (ctx, this_obj, argc, arg_buf); + break; + case JS_CFUNC_generic_magic: + ret_val + = func.generic_magic (ctx, this_obj, argc, arg_buf, f->u.cfunc.magic); + break; + case JS_CFUNC_f_f: { + double d1; + + if (unlikely (JS_ToFloat64 (ctx, &d1, arg_buf[0]))) { + ret_val = JS_EXCEPTION; + break; + } + ret_val = JS_NewFloat64 (ctx, func.f_f (d1)); + } break; + case JS_CFUNC_f_f_f: { + double d1, d2; + + if (unlikely (JS_ToFloat64 (ctx, &d1, arg_buf[0]))) { + ret_val = JS_EXCEPTION; + break; + } + if (unlikely (JS_ToFloat64 (ctx, &d2, arg_buf[1]))) { + ret_val = JS_EXCEPTION; + break; + } + ret_val = JS_NewFloat64 (ctx, func.f_f_f (d1, d2)); + } break; + /* Fixed-arity fast paths - direct call without argc/argv marshaling */ + case JS_CFUNC_0: + ret_val = func.f0 (ctx, this_obj); + break; + case JS_CFUNC_1: + ret_val = func.f1 (ctx, this_obj, arg_buf[0]); + break; + case JS_CFUNC_2: + ret_val = func.f2 (ctx, this_obj, arg_buf[0], arg_buf[1]); + break; + case JS_CFUNC_3: + ret_val = func.f3 (ctx, this_obj, arg_buf[0], arg_buf[1], arg_buf[2]); + break; + case JS_CFUNC_4: + ret_val = func.f4 (ctx, this_obj, arg_buf[0], arg_buf[1], arg_buf[2], arg_buf[3]); + break; + /* Pure functions (no this_val) */ + case JS_CFUNC_PURE: + ret_val = func.pure (ctx, argc, arg_buf); + break; + case JS_CFUNC_PURE_0: + ret_val = func.pure0 (ctx); + break; + case JS_CFUNC_PURE_1: + ret_val = func.pure1 (ctx, arg_buf[0]); + break; + case JS_CFUNC_PURE_2: + ret_val = func.pure2 (ctx, arg_buf[0], arg_buf[1]); + break; + case JS_CFUNC_PURE_3: + ret_val = func.pure3 (ctx, arg_buf[0], arg_buf[1], arg_buf[2]); + break; + case JS_CFUNC_PURE_4: + ret_val = func.pure4 (ctx, arg_buf[0], arg_buf[1], arg_buf[2], arg_buf[3]); + break; + default: + abort (); + } + + ctx->current_stack_frame = sf->prev_frame; + + /* Restore value stack if we used it for arg padding */ + if (saved_vs_top >= 0) + ctx->value_stack_top = saved_vs_top; + + if (unlikely (ctx->trace_hook) && (ctx->trace_type & JS_HOOK_RET)) + ctx->trace_hook (ctx, JS_HOOK_RET, NULL, ctx->trace_data); + + return ret_val; +} + +JSValue JS_Call (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv) { + if (js_poll_interrupts (ctx)) return JS_EXCEPTION; + if (unlikely (!JS_IsFunction (func_obj))) + return JS_ThrowTypeError (ctx, "not a function"); + JSFunction *f = JS_VALUE_GET_FUNCTION (func_obj); + if (unlikely (f->length >= 0 && argc > f->length)) { + char buf[KEY_GET_STR_BUF_SIZE]; + return JS_ThrowTypeError (ctx, "too many arguments for %s: expected %d, got %d", + JS_KeyGetStr (ctx, buf, KEY_GET_STR_BUF_SIZE, f->name), f->length, argc); + } + switch (f->kind) { + case JS_FUNC_KIND_C: + return js_call_c_function (ctx, func_obj, this_obj, argc, argv); + case JS_FUNC_KIND_BYTECODE: + return JS_CallInternal (ctx, func_obj, this_obj, argc, argv, JS_CALL_FLAG_COPY_ARGV); + case JS_FUNC_KIND_REGISTER: + return JS_CallRegisterVM (ctx, f->u.reg.code, this_obj, argc, argv, + f->u.reg.env_record, f->u.reg.outer_frame); + case JS_FUNC_KIND_MCODE: + return mcode_exec (ctx, f->u.mcode.code, this_obj, argc, argv, f->u.mcode.outer_frame); + default: + return JS_ThrowTypeError (ctx, "not a function"); + } +} + +/*******************************************************************/ +/* runtime functions & objects */ + +int check_function (JSContext *ctx, JSValue obj) { + if (likely (JS_IsFunction (obj))) return 0; + JS_ThrowTypeError (ctx, "not a function"); + return -1; +} + +int check_exception_free (JSContext *ctx, JSValue obj) { + return JS_IsException (obj); +} + +JSValue find_key (JSContext *ctx, const char *name) { + /* Create an interned JSValue key from C string */ + return js_key_new (ctx, name); +} + +static int JS_InstantiateFunctionListItem (JSContext *ctx, JSValue *obj_ptr, JSValue key, const JSCFunctionListEntry *e) { + JSValue val; + + switch (e->def_type) { + case JS_DEF_ALIAS: /* using autoinit for aliases is not safe */ + { + JSValue key1 = find_key (ctx, e->u.alias.name); + switch (e->u.alias.base) { + case -1: + val = JS_GetProperty (ctx, *obj_ptr, key1); + break; + case 0: + val = JS_GetProperty (ctx, ctx->global_obj, key1); + break; + default: + abort (); + } + /* key1 is interned, no need to free */ + } break; + case JS_DEF_CFUNC: + val = JS_NewCFunction2 (ctx, e->u.func.cfunc.generic, e->name, e->u.func.length, e->u.func.cproto, e->magic); + break; + case JS_DEF_PROP_INT32: + val = JS_NewInt32 (ctx, e->u.i32); + break; + case JS_DEF_PROP_INT64: + val = JS_NewInt64 (ctx, e->u.i64); + break; + case JS_DEF_PROP_DOUBLE: + val = __JS_NewFloat64 (ctx, e->u.f64); + break; + case JS_DEF_PROP_UNDEFINED: + val = JS_NULL; + break; + case JS_DEF_PROP_STRING: + val = JS_NewAtomString (ctx, e->u.str); + break; + + case JS_DEF_OBJECT: + val = JS_NewObject (ctx); + if (JS_IsException (val)) return -1; + JS_SetPropertyFunctionList (ctx, val, e->u.prop_list.tab, e->u.prop_list.len); + break; + default: + abort (); + } + JS_SetPropertyInternal (ctx, *obj_ptr, key, val); + return 0; +} + +int JS_SetPropertyFunctionList (JSContext *ctx, JSValue obj, const JSCFunctionListEntry *tab, int len) { + int i, ret; + + /* Root obj since allocations in the loop can trigger GC */ + JSGCRef obj_ref; + obj_ref.val = obj; + obj_ref.prev = ctx->last_gc_ref; + ctx->last_gc_ref = &obj_ref; + + for (i = 0; i < len; i++) { + const JSCFunctionListEntry *e = &tab[i]; + JSValue key = find_key (ctx, e->name); + if (JS_IsNull (key)) { + ctx->last_gc_ref = obj_ref.prev; + return -1; + } + ret = JS_InstantiateFunctionListItem (ctx, &obj_ref.val, key, e); + /* key is interned, no need to free */ + if (ret) { + ctx->last_gc_ref = obj_ref.prev; + return -1; + } + } + ctx->last_gc_ref = obj_ref.prev; + return 0; +} + +__exception int js_get_length32 (JSContext *ctx, uint32_t *pres, JSValue obj) { + int tag = JS_VALUE_GET_TAG (obj); + + /* Fast path for intrinsic arrays */ + if (JS_IsArray (obj)) { + JSArray *arr = JS_VALUE_GET_ARRAY (obj); + *pres = arr->len; + return 0; + } + + if (tag == JS_TAG_FUNCTION) { + JSFunction *fn = JS_VALUE_GET_FUNCTION (obj); + *pres = fn->length; + return 0; + } + + blob *b = js_get_blob (ctx, obj); + if (b) { + *pres = b->length; + return 0; + } + + if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) { + JSText *p = JS_VALUE_GET_STRING (obj); + *pres = JSText_len (p); + return 0; + } + + JSValue len_val; + len_val = JS_GetProperty (ctx, obj, JS_KEY_length); + if (JS_IsException (len_val)) { + *pres = 0; + return -1; + } + return JS_ToUint32 (ctx, pres, len_val); +} + +__exception int js_get_length64 (JSContext *ctx, int64_t *pres, JSValue obj) { + /* Fast path for intrinsic arrays */ + if (JS_IsArray (obj)) { + JSArray *arr = JS_VALUE_GET_ARRAY (obj); + *pres = arr->len; + return 0; + } + JSValue len_val; + len_val = JS_GetProperty (ctx, obj, JS_KEY_length); + if (JS_IsException (len_val)) { + *pres = 0; + return -1; + } + return JS_ToLength (ctx, pres, len_val); +} + +int JS_GetLength (JSContext *ctx, JSValue obj, int64_t *pres) { + return js_get_length64 (ctx, pres, obj); +} + +void free_arg_list (JSContext *ctx, JSValue *tab, uint32_t len) { + (void)ctx; + (void)len; + js_free_rt(tab); +} + +/* XXX: should use ValueArray */ +JSValue *build_arg_list (JSContext *ctx, uint32_t *plen, JSValue *parray_arg) { + uint32_t len, i; + JSValue *tab; + + /* Fast path for intrinsic arrays */ + if (JS_IsArray (*parray_arg)) { + JSArray *arr = JS_VALUE_GET_ARRAY (*parray_arg); + len = arr->len; + if (len > JS_MAX_LOCAL_VARS) { + JS_ThrowRangeError ( + ctx, "too many arguments in function call (only %d allowed)", JS_MAX_LOCAL_VARS); + return NULL; + } + tab = js_mallocz_rt (sizeof (tab[0]) * max_uint32 (1, len)); + if (!tab) return NULL; + arr = JS_VALUE_GET_ARRAY (*parray_arg); + for (i = 0; i < len; i++) { + tab[i] = arr->values[i]; + } + *plen = len; + return tab; + } + + JS_ThrowTypeError (ctx, "not an array"); + return NULL; +} + +/* Error class */ +static JSValue js_error_constructor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv, int magic) { + JSValue obj, msg; + JSValue message, options, proto; + int arg_index; + + /* Use the appropriate error prototype based on magic */ + if (magic < 0) { + proto = ctx->class_proto[JS_CLASS_ERROR]; + } else { + proto = ctx->native_error_proto[magic]; + } + obj = JS_NewObjectProtoClass (ctx, proto, JS_CLASS_ERROR); + if (JS_IsException (obj)) return obj; + arg_index = (magic == JS_AGGREGATE_ERROR); + + message = argv[arg_index++]; + if (!JS_IsNull (message)) { + msg = JS_ToString (ctx, message); + if (unlikely (JS_IsException (msg))) goto exception; + JS_SetPropertyInternal (ctx, obj, JS_KEY_message, msg); + } + + if (arg_index < argc) { + options = argv[arg_index]; + if (JS_IsObject (options)) { + int present = JS_HasProperty (ctx, options, JS_KEY_cause); + if (present < 0) goto exception; + if (present) { + JSValue cause = JS_GetProperty (ctx, options, JS_KEY_cause); + if (JS_IsException (cause)) goto exception; + JS_SetPropertyInternal (ctx, obj, JS_KEY_cause, cause); + } + } + } + + if (magic == JS_AGGREGATE_ERROR) { + /* Require errors to be an array (no iterator support) */ + JSValue error_list; + if (JS_IsArray (argv[0])) { + uint32_t len, i; + if (js_get_length32 (ctx, &len, argv[0])) goto exception; + error_list = JS_NewArray (ctx); + if (JS_IsException (error_list)) goto exception; + for (i = 0; i < len; i++) { + JSValue item = JS_GetPropertyUint32 (ctx, argv[0], i); + if (JS_IsException (item)) { + goto exception; + } + if (JS_SetPropertyUint32 (ctx, error_list, i, item) < 0) { + goto exception; + } + } + } else { + error_list = JS_NewArray (ctx); + if (JS_IsException (error_list)) goto exception; + } + JS_SetPropertyInternal (ctx, obj, JS_KEY_errors, error_list); + } + + /* skip the Error() function in the backtrace */ + build_backtrace (ctx, obj, NULL, 0, 0, JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL); + return obj; +exception: + return JS_EXCEPTION; +} + +JSValue js_error_toString (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + printf ("E TO STR\n"); + JSValue name, msg; + + if (!JS_IsObject (this_val)) return JS_ThrowTypeErrorNotAnObject (ctx); + name = JS_GetProperty (ctx, this_val, JS_KEY_name); + if (JS_IsNull (name)) + name = JS_KEY_Error; + else + name = JS_ToString (ctx, name); + if (JS_IsException (name)) return JS_EXCEPTION; + + msg = JS_GetProperty (ctx, this_val, JS_KEY_message); + if (JS_IsNull (msg)) + msg = JS_KEY_empty; + else + msg = JS_ToString (ctx, msg); + if (JS_IsException (msg)) { + return JS_EXCEPTION; + } + if (!JS_IsEmptyString (name) && !JS_IsEmptyString (msg)) + name = JS_ConcatString3 (ctx, "", name, ": "); + return JS_ConcatString (ctx, name, msg); +} + +static JSValue js_array_includes (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + JSValue found = js_cell_array_find (ctx, this_val, argc, argv); + if (JS_IsException (found)) return JS_EXCEPTION; + + if (JS_IsNull (found)) return JS_NewBool (ctx, FALSE); + return JS_NewBool (ctx, TRUE); +} + +/* return < 0 if exception or TRUE/FALSE */ +static int js_is_regexp (JSContext *ctx, JSValue obj); + +/* RegExp */ + +void js_regexp_finalizer (JSRuntime *rt, JSValue val) { + JSRegExp *re = JS_GetOpaque (val, JS_CLASS_REGEXP); + if (re) { + js_free_rt (re->pattern); + js_free_rt (re->bytecode); + js_free_rt (re); + } + (void)rt; +} + +/* create a string containing the RegExp bytecode */ +JSValue js_compile_regexp (JSContext *ctx, JSValue pattern, JSValue flags) { + const char *str; + int re_flags, mask; + uint8_t *re_bytecode_buf; + size_t i, len; + int re_bytecode_len; + JSValue ret; + char error_msg[64]; + + re_flags = 0; + if (!JS_IsNull (flags)) { + str = JS_ToCStringLen (ctx, &len, flags); + if (!str) return JS_EXCEPTION; + /* XXX: re_flags = LRE_FLAG_OCTAL unless strict mode? */ + for (i = 0; i < len; i++) { + switch (str[i]) { + case 'd': + mask = LRE_FLAG_INDICES; + break; + case 'g': + mask = LRE_FLAG_GLOBAL; + break; + case 'i': + mask = LRE_FLAG_IGNORECASE; + break; + case 'm': + mask = LRE_FLAG_MULTILINE; + break; + case 's': + mask = LRE_FLAG_DOTALL; + break; + case 'u': + mask = LRE_FLAG_UNICODE; + break; + case 'v': + mask = LRE_FLAG_UNICODE_SETS; + break; + case 'y': + mask = LRE_FLAG_STICKY; + break; + default: + goto bad_flags; + } + if ((re_flags & mask) != 0) { + bad_flags: + JS_FreeCString (ctx, str); + goto bad_flags1; + } + re_flags |= mask; + } + JS_FreeCString (ctx, str); + } + + /* 'u' and 'v' cannot be both set */ + if ((re_flags & LRE_FLAG_UNICODE_SETS) && (re_flags & LRE_FLAG_UNICODE)) { + bad_flags1: + return JS_ThrowSyntaxError (ctx, "invalid regular expression flags"); + } + + str = JS_ToCStringLen2 ( + ctx, &len, pattern, !(re_flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS))); + if (!str) return JS_EXCEPTION; + re_bytecode_buf = lre_compile (&re_bytecode_len, error_msg, sizeof (error_msg), str, len, re_flags, ctx); + JS_FreeCString (ctx, str); + if (!re_bytecode_buf) { + JS_ThrowSyntaxError (ctx, "%s", error_msg); + return JS_EXCEPTION; + } + + ret + = js_new_string8_len (ctx, (const char *)re_bytecode_buf, re_bytecode_len); + js_free (ctx, re_bytecode_buf); + return ret; +} + +/* create a RegExp object from a string containing the RegExp bytecode + and the source pattern */ +JSValue js_regexp_constructor_internal (JSContext *ctx, JSValue pattern, JSValue bc) { + JSValue obj; + JSRecord *p; + JSRegExp *re; + const char *pat_cstr; + size_t pat_len; + int bc_len, i; + + /* sanity check - need strings for pattern and bytecode */ + if (!JS_IsText (bc) || !JS_IsText (pattern)) { + JS_ThrowTypeError (ctx, "string expected"); + fail: + return JS_EXCEPTION; + } + + /* Root pattern and bc across allocating calls */ + JSGCRef pat_ref, bc_ref; + JS_PushGCRef (ctx, &pat_ref); + pat_ref.val = pattern; + JS_PushGCRef (ctx, &bc_ref); + bc_ref.val = bc; + + obj = JS_NewObjectProtoClass (ctx, ctx->class_proto[JS_CLASS_REGEXP], JS_CLASS_REGEXP); + if (JS_IsException (obj)) { + JS_PopGCRef (ctx, &bc_ref); + JS_PopGCRef (ctx, &pat_ref); + goto fail; + } + + /* Root obj across allocating calls */ + JSGCRef obj_ref; + JS_PushGCRef (ctx, &obj_ref); + obj_ref.val = obj; + +#define REGEXP_CLEANUP() do { JS_PopGCRef (ctx, &obj_ref); JS_PopGCRef (ctx, &bc_ref); JS_PopGCRef (ctx, &pat_ref); } while(0) + + /* Allocate JSRegExp off-heap (not on GC heap) so opaque pointer stays valid after GC */ + re = js_malloc_rt (sizeof(JSRegExp)); + if (!re) { REGEXP_CLEANUP (); JS_ThrowOutOfMemory (ctx); goto fail; } + p = JS_VALUE_GET_OBJ (obj_ref.val); + REC_SET_OPAQUE(p, re); + re->pattern = NULL; + re->bytecode = NULL; + + /* Extract pattern as UTF-8 C string */ + pat_cstr = JS_ToCStringLen (ctx, &pat_len, pat_ref.val); + if (!pat_cstr) { REGEXP_CLEANUP (); goto fail; } + re->pattern = js_malloc_rt (pat_len + 1); + if (!re->pattern) { + JS_FreeCString (ctx, pat_cstr); + REGEXP_CLEANUP (); + goto fail; + } + memcpy (re->pattern, pat_cstr, pat_len + 1); + re->pattern_len = (uint32_t)pat_len; + JS_FreeCString (ctx, pat_cstr); + + /* Extract bytecode as raw bytes via string_get (not JS_ToCStringLen + which UTF-8 encodes and would mangle bytes >= 128) */ + bc = bc_ref.val; + if (MIST_IsImmediateASCII (bc)) { + bc_len = MIST_GetImmediateASCIILen (bc); + re->bytecode = js_malloc_rt (bc_len); + if (!re->bytecode) { REGEXP_CLEANUP (); goto fail; } + for (i = 0; i < bc_len; i++) + re->bytecode[i] = (uint8_t)MIST_GetImmediateASCIIChar (bc, i); + } else { + JSText *bc_str = (JSText *)chase (bc_ref.val); + bc_len = (int)JSText_len (bc_str); + re->bytecode = js_malloc_rt (bc_len); + if (!re->bytecode) { REGEXP_CLEANUP (); goto fail; } + for (i = 0; i < bc_len; i++) + re->bytecode[i] = (uint8_t)string_get (bc_str, i); + } + re->bytecode_len = (uint32_t)bc_len; + + { + JSValue key = JS_KEY_STR (ctx, "lastIndex"); + obj = obj_ref.val; /* re-read after JS_KEY_STR allocation */ + JS_SetPropertyInternal (ctx, obj, key, JS_NewInt32 (ctx, 0)); + } + obj = obj_ref.val; + REGEXP_CLEANUP (); + return obj; +} +#undef REGEXP_CLEANUP + +static JSRegExp *js_get_regexp (JSContext *ctx, JSValue obj, BOOL throw_error) { + if (JS_VALUE_GET_TAG (obj) == JS_TAG_PTR) { + JSRecord *p = JS_VALUE_GET_OBJ (obj); + if (REC_GET_CLASS_ID(p) == JS_CLASS_REGEXP) return (JSRegExp *)REC_GET_OPAQUE(p); + } + if (throw_error) { JS_ThrowTypeErrorInvalidClass (ctx, JS_CLASS_REGEXP); } + return NULL; +} + +/* return < 0 if exception or TRUE/FALSE */ +static int js_is_regexp (JSContext *ctx, JSValue obj) { + JSValue m; + + if (!JS_IsObject (obj)) return FALSE; + m = JS_GetPropertyStr (ctx, obj, "Symbol.match"); + if (JS_IsException (m)) return -1; + if (!JS_IsNull (m)) return JS_ToBool (ctx, m); + return js_get_regexp (ctx, obj, FALSE) != NULL; +} + +JSValue js_regexp_constructor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + JSValue pattern, flags, bc, val; + JSValue pat, flags1; + JSRegExp *re; + int pat_is_regexp; + + pat = argv[0]; + flags1 = argv[1]; + pat_is_regexp = js_is_regexp (ctx, pat); + if (pat_is_regexp < 0) return JS_EXCEPTION; + /* If called with a regexp and no flags, just return a copy */ + if (pat_is_regexp && JS_IsNull (flags1)) { + re = js_get_regexp (ctx, pat, FALSE); + if (re) return pat; + } + re = js_get_regexp (ctx, pat, FALSE); + if (re) { + pattern = JS_NewString (ctx, re->pattern); + if (JS_IsException (pattern)) goto fail; + if (JS_IsNull (flags1)) { + bc = js_new_string8_len (ctx, (const char *)re->bytecode, re->bytecode_len); + if (JS_IsException (bc)) goto fail; + goto no_compilation; + } else { + flags = JS_ToString (ctx, flags1); + if (JS_IsException (flags)) goto fail; + } + } else { + flags = JS_NULL; + if (pat_is_regexp) { + pattern = JS_GetProperty (ctx, pat, JS_KEY_source); + if (JS_IsException (pattern)) goto fail; + if (JS_IsNull (flags1)) { + flags = JS_GetProperty (ctx, pat, JS_KEY_flags); + if (JS_IsException (flags)) goto fail; + } else { + flags = flags1; + } + } else { + pattern = pat; + flags = flags1; + } + if (JS_IsNull (pattern)) { + pattern = JS_KEY_empty; + } else { + val = pattern; + pattern = JS_ToString (ctx, val); + if (JS_IsException (pattern)) goto fail; + } + } + bc = js_compile_regexp (ctx, pattern, flags); + if (JS_IsException (bc)) goto fail; +no_compilation: + return js_regexp_constructor_internal (ctx, pattern, bc); +fail: + return JS_EXCEPTION; +} + +static JSValue js_regexp_compile (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + JSRegExp *re1, *re; + JSValue pattern1, flags1; + JSValue bc, pattern; + const char *pat_cstr; + size_t pat_len; + int bc_len, i; + + re = js_get_regexp (ctx, this_val, TRUE); + if (!re) return JS_EXCEPTION; + pattern1 = argv[0]; + flags1 = argv[1]; + re1 = js_get_regexp (ctx, pattern1, FALSE); + if (re1) { + if (!JS_IsNull (flags1)) + return JS_ThrowTypeError (ctx, "flags must be undefined"); + pattern = JS_NewString (ctx, re1->pattern); + if (JS_IsException (pattern)) goto fail; + bc = js_new_string8_len (ctx, (const char *)re1->bytecode, re1->bytecode_len); + if (JS_IsException (bc)) goto fail; + } else { + bc = JS_NULL; + if (JS_IsNull (pattern1)) + pattern = JS_KEY_empty; + else + pattern = JS_ToString (ctx, pattern1); + if (JS_IsException (pattern)) goto fail; + bc = js_compile_regexp (ctx, pattern, flags1); + if (JS_IsException (bc)) goto fail; + } + /* Free old C buffers */ + js_free_rt (re->pattern); + re->pattern = NULL; + js_free_rt (re->bytecode); + re->bytecode = NULL; + + /* Extract pattern as UTF-8 C string */ + pat_cstr = JS_ToCStringLen (ctx, &pat_len, pattern); + if (!pat_cstr) goto fail; + re->pattern = js_malloc_rt (pat_len + 1); + if (!re->pattern) { + JS_FreeCString (ctx, pat_cstr); + goto fail; + } + memcpy (re->pattern, pat_cstr, pat_len + 1); + re->pattern_len = (uint32_t)pat_len; + JS_FreeCString (ctx, pat_cstr); + + /* Extract bytecode as raw bytes */ + if (MIST_IsImmediateASCII (bc)) { + bc_len = MIST_GetImmediateASCIILen (bc); + re->bytecode = js_malloc_rt (bc_len); + if (!re->bytecode) goto fail; + for (i = 0; i < bc_len; i++) + re->bytecode[i] = (uint8_t)MIST_GetImmediateASCIIChar (bc, i); + } else { + JSText *bc_str = (JSText *)JS_VALUE_GET_PTR (bc); + bc_len = (int)JSText_len (bc_str); + re->bytecode = js_malloc_rt (bc_len); + if (!re->bytecode) goto fail; + for (i = 0; i < bc_len; i++) + re->bytecode[i] = (uint8_t)string_get (bc_str, i); + } + re->bytecode_len = (uint32_t)bc_len; + + { + JSValue key = JS_KEY_STR (ctx, "lastIndex"); + int ret = JS_SetProperty (ctx, this_val, key, JS_NewInt32 (ctx, 0)); + if (ret < 0) return JS_EXCEPTION; + } + return this_val; +fail: + return JS_EXCEPTION; +} + +JSValue js_regexp_toString (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + JSValue pattern, flags; + + if (!JS_IsObject (this_val)) return JS_ThrowTypeErrorNotAnObject (ctx); + + JSText *b = pretext_init (ctx, 0); + if (!b) return JS_EXCEPTION; + + b = pretext_putc (ctx, b, '/'); + if (!b) return JS_EXCEPTION; + pattern = JS_GetProperty (ctx, this_val, JS_KEY_source); + b = pretext_concat_value (ctx, b, pattern); + if (!b) return JS_EXCEPTION; + b = pretext_putc (ctx, b, '/'); + if (!b) return JS_EXCEPTION; + flags = JS_GetProperty (ctx, this_val, JS_KEY_flags); + b = pretext_concat_value (ctx, b, flags); + if (!b) return JS_EXCEPTION; + return pretext_end (ctx, b); +} + +int lre_check_stack_overflow (void *opaque, size_t alloca_size) { + JSContext *ctx = opaque; + return js_check_stack_overflow (ctx, alloca_size); +} + +int lre_check_timeout (void *opaque) { + JSContext *ctx = opaque; + return (ctx->interrupt_handler + && ctx->interrupt_handler (ctx->rt, ctx->interrupt_opaque)); +} + +void *lre_realloc (void *opaque, void *ptr, size_t size) { + (void)opaque; + /* No JS exception is raised here */ + return js_realloc_rt (ptr, size); +} + +/* Convert UTF-32 JSText to UTF-16 buffer for regex engine. + Returns allocated uint16_t buffer (via js_malloc_rt) that must be freed with js_free_rt. + Sets *out_len to number of uint16 code units. */ +static uint16_t *js_string_to_utf16 (JSContext *ctx, JSText *str, int *out_len) { + int len = (int)JSText_len (str); + /* Worst case: each UTF-32 char becomes 2 UTF-16 surrogates */ + uint16_t *buf = js_malloc_rt (len * 2 * sizeof (uint16_t)); + if (!buf) { JS_ThrowOutOfMemory (ctx); return NULL; } + + int j = 0; + for (int i = 0; i < len; i++) { + uint32_t c = string_get (str, i); + if (c < 0x10000) { + buf[j++] = (uint16_t)c; + } else { + /* Encode as surrogate pair */ + c -= 0x10000; + buf[j++] = (uint16_t)(0xD800 | (c >> 10)); + buf[j++] = (uint16_t)(0xDC00 | (c & 0x3FF)); + } + } + *out_len = j; + return buf; +} + +static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + JSRegExp *re = js_get_regexp (ctx, this_val, TRUE); + JSText *str; + JSGCRef str_ref, this_ref; + JSValue ret, res, val, groups, captures_arr, match0; + uint8_t *re_bytecode; + uint8_t **capture, *str_buf; + uint16_t *utf16_buf = NULL; + int rc, capture_count, shift, i, re_flags; + int utf16_len = 0; + int64_t last_index; + const char *group_name_ptr; + + if (!re) return JS_EXCEPTION; + + /* Root this_val across allocating calls */ + JS_PushGCRef (ctx, &this_ref); + this_ref.val = this_val; + + JS_PushGCRef (ctx, &str_ref); + str_ref.val = JS_ToString (ctx, argv[0]); + if (JS_IsException (str_ref.val)) { + JS_PopGCRef (ctx, &str_ref); + JS_PopGCRef (ctx, &this_ref); + return JS_EXCEPTION; + } + /* Ensure str_val is a heap string for JS_VALUE_GET_STRING */ + if (MIST_IsImmediateASCII (str_ref.val)) { + int imm_len = MIST_GetImmediateASCIILen (str_ref.val); + JSText *hs = js_alloc_string (ctx, imm_len > 0 ? imm_len : 1); + if (!hs) { + JS_PopGCRef (ctx, &str_ref); + JS_PopGCRef (ctx, &this_ref); + return JS_EXCEPTION; + } + for (int ci = 0; ci < imm_len; ci++) + string_put (hs, ci, MIST_GetImmediateASCIIChar (str_ref.val, ci)); + hs->hdr = objhdr_set_cap56 (hs->hdr, imm_len); + hs->length = 0; + hs->hdr = objhdr_set_s (hs->hdr, true); + str_ref.val = JS_MKPTR (hs); + } + + ret = JS_EXCEPTION; + res = JS_NULL; + groups = JS_NULL; + captures_arr = JS_NULL; + match0 = JS_NULL; + capture = NULL; + + val = JS_GetPropertyStr (ctx, this_ref.val, "lastIndex"); + if (JS_IsException (val) || JS_ToLength (ctx, &last_index, val)) + goto fail; + + /* Re-chase re after allocating calls (JS_ToString, js_alloc_string, JS_GetPropertyStr) */ + re = js_get_regexp (ctx, this_ref.val, TRUE); + re_bytecode = re->bytecode; + re_flags = lre_get_flags (re_bytecode); + if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) last_index = 0; + + capture_count = lre_get_capture_count (re_bytecode); + + if (capture_count > 0) { + capture = js_malloc_rt (sizeof (capture[0]) * capture_count * 2); + if (!capture) { JS_ThrowOutOfMemory (ctx); goto fail; } + } + + /* Refresh str after potential GC from js_malloc */ + str = JS_VALUE_GET_STRING (str_ref.val); + + /* Convert UTF-32 string to UTF-16 for regex engine (uses js_malloc_rt, no GC) */ + utf16_buf = js_string_to_utf16 (ctx, str, &utf16_len); + if (!utf16_buf) goto fail; + shift = 1; /* UTF-16 mode */ + str_buf = (uint8_t *)utf16_buf; + + /* Refresh str after potential GC from js_string_to_utf16 */ + str = JS_VALUE_GET_STRING (str_ref.val); + if (last_index > (int)JSText_len (str)) { + rc = 2; + } else { + rc = lre_exec (capture, re_bytecode, str_buf, last_index, (int)JSText_len (str), shift, ctx); + } + + if (rc != 1) { + if (rc >= 0) { + if (rc == 2 || (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY))) { + if (JS_SetPropertyStr (ctx, this_ref.val, "lastIndex", JS_NewInt32 (ctx, 0)) + < 0) + goto fail; + } + ret = JS_NULL; + goto done; + } + if (rc == LRE_RET_TIMEOUT) + JS_ThrowInterrupted (ctx); + else + JS_ThrowInternalError (ctx, "out of memory in regexp execution"); + goto fail; + } + + if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) { + if (JS_SetPropertyStr (ctx, this_ref.val, "lastIndex", JS_NewInt32 (ctx, (capture[1] - str_buf) >> shift)) + < 0) + goto fail; + } + + res = JS_NewObjectProto (ctx, JS_NULL); + if (JS_IsException (res)) goto fail; + + /* Root res, captures_arr, groups, match0 across allocating calls */ + JSGCRef res_ref, cap_ref, grp_ref, m0_ref; + JS_PushGCRef (ctx, &res_ref); + res_ref.val = res; + JS_PushGCRef (ctx, &cap_ref); + cap_ref.val = JS_NULL; + JS_PushGCRef (ctx, &grp_ref); + grp_ref.val = JS_NULL; + JS_PushGCRef (ctx, &m0_ref); + m0_ref.val = JS_NULL; + +#define REGEXP_RESULT_CLEANUP() do { \ + JS_PopGCRef (ctx, &m0_ref); \ + JS_PopGCRef (ctx, &grp_ref); \ + JS_PopGCRef (ctx, &cap_ref); \ + JS_PopGCRef (ctx, &res_ref); \ +} while(0) + + { + int cap_groups = (capture_count > 1) ? (capture_count - 1) : 0; + captures_arr = JS_NewArrayLen (ctx, cap_groups); + if (JS_IsException (captures_arr)) { REGEXP_RESULT_CLEANUP (); goto fail; } + cap_ref.val = captures_arr; + } + + group_name_ptr = lre_get_groupnames (re_bytecode); + if (group_name_ptr) { + groups = JS_NewObjectProto (ctx, JS_NULL); + if (JS_IsException (groups)) { REGEXP_RESULT_CLEANUP (); goto fail; } + grp_ref.val = groups; + } + + { + int match_start = -1; + int match_end = -1; + + for (i = 0; i < capture_count; i++) { + const char *name = NULL; + uint8_t **m = &capture[2 * i]; + int start = -1; + int end = -1; + JSValue s; + + if (group_name_ptr && i > 0) { + if (*group_name_ptr) name = group_name_ptr; + group_name_ptr += strlen (group_name_ptr) + 1; + } + + if (m[0] && m[1]) { + start = (m[0] - str_buf) >> shift; + end = (m[1] - str_buf) >> shift; + } + + s = JS_NULL; + if (start != -1) { + str = JS_VALUE_GET_STRING (str_ref.val); + s = js_sub_string (ctx, str, start, end); + if (JS_IsException (s)) { REGEXP_RESULT_CLEANUP (); goto fail; } + } + + if (i == 0) { + match_start = start; + match_end = end; + match0 = s; + m0_ref.val = match0; + continue; + } + + if (name) { + groups = grp_ref.val; + if (JS_SetPropertyStr (ctx, groups, name, s) < 0) { + REGEXP_RESULT_CLEANUP (); + goto fail; + } + } + + captures_arr = cap_ref.val; + if (JS_SetPropertyUint32 (ctx, captures_arr, (uint32_t)(i - 1), s) < 0) { + REGEXP_RESULT_CLEANUP (); + goto fail; + } + } + + if (match_start < 0) match_start = 0; + if (match_end < match_start) match_end = match_start; + + res = res_ref.val; + if (JS_SetPropertyStr (ctx, res, "index", JS_NewInt32 (ctx, match_start)) + < 0) { + REGEXP_RESULT_CLEANUP (); + goto fail; + } + res = res_ref.val; + if (JS_SetPropertyStr (ctx, res, "end", JS_NewInt32 (ctx, match_end)) < 0) { + REGEXP_RESULT_CLEANUP (); + goto fail; + } + + res = res_ref.val; + match0 = m0_ref.val; + if (JS_SetPropertyStr (ctx, res, "match", match0) < 0) { REGEXP_RESULT_CLEANUP (); goto fail; } + + res = res_ref.val; + captures_arr = cap_ref.val; + if (JS_SetPropertyStr (ctx, res, "captures", captures_arr) < 0) { REGEXP_RESULT_CLEANUP (); goto fail; } + + groups = grp_ref.val; + if (!JS_IsNull (groups)) { + res = res_ref.val; + if (JS_SetPropertyStr (ctx, res, "groups", groups) < 0) { REGEXP_RESULT_CLEANUP (); goto fail; } + } else { + res = res_ref.val; + JS_SetPropertyStr (ctx, res, "groups", JS_NULL); + } + } + + ret = res_ref.val; + REGEXP_RESULT_CLEANUP (); + +done: + JS_PopGCRef (ctx, &str_ref); + JS_PopGCRef (ctx, &this_ref); + js_free_rt (capture); + js_free_rt (utf16_buf); + return ret; + +fail: + JS_PopGCRef (ctx, &str_ref); + JS_PopGCRef (ctx, &this_ref); + js_free_rt (capture); + js_free_rt (utf16_buf); + return JS_EXCEPTION; +} + +static const JSCFunctionListEntry js_regexp_proto_funcs[] = { + JS_CFUNC_DEF ("exec", 1, js_regexp_exec), + JS_CFUNC_DEF ("compile", 2, js_regexp_compile), + JS_CFUNC_DEF ("toString", 0, js_regexp_toString), +}; + +void JS_AddIntrinsicRegExpCompiler (JSContext *ctx) { + ctx->compile_regexp = js_compile_regexp; +} + +static void JS_AddIntrinsicRegExp (JSContext *ctx) { + JSValue obj; + + JS_AddIntrinsicRegExpCompiler (ctx); + + ctx->class_proto[JS_CLASS_REGEXP] = JS_NewObject (ctx); + JS_SetPropertyFunctionList (ctx, ctx->class_proto[JS_CLASS_REGEXP], js_regexp_proto_funcs, countof (js_regexp_proto_funcs)); + obj = JS_NewCFunction2 (ctx, js_regexp_constructor, "RegExp", 2, JS_CFUNC_generic, 0); + JS_SetPropertyStr (ctx, ctx->global_obj, "RegExp", obj); + ctx->regexp_ctor = obj; +} + + +static JSValue internalize_json_property (JSContext *ctx, JSValue holder, JSValue name, JSValue reviver) { + JSValue val, new_el, res; + JSValue args[2]; + int ret, is_array; + uint32_t i, len = 0; + JSValue prop; + + if (js_check_stack_overflow (ctx, 0)) { + return JS_ThrowStackOverflow (ctx); + } + + val = JS_GetProperty (ctx, holder, name); + if (JS_IsException (val)) return val; + is_array = JS_IsArray (val); + if (is_array < 0) goto fail; + if (is_array || JS_IsObject (val)) { + if (is_array) { + if (js_get_length32 (ctx, &len, val)) goto fail; + } else { + /* Object property iteration not yet implemented for JSValue keys */ + len = 0; + } + for (i = 0; i < len; i++) { + /* For arrays, use integer index as key */ + prop = JS_NewInt32 (ctx, i); + new_el = internalize_json_property (ctx, val, prop, reviver); + if (JS_IsException (new_el)) { goto fail; } + if (JS_IsNull (new_el)) { + ret = JS_DeleteProperty (ctx, val, prop); + } else { + ret = JS_SetPropertyInternal (ctx, val, prop, new_el); + } + if (ret < 0) goto fail; + } + } + /* name is already a JSValue, use it directly */ + args[0] = name; + args[1] = val; + res = JS_Call (ctx, reviver, holder, 2, args); + return res; +fail: + return JS_EXCEPTION; +} + +static JSValue js_json_parse (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + JSValue obj, root; + JSValue reviver; + const char *str; + size_t len; + + str = JS_ToCStringLen (ctx, &len, argv[0]); + if (!str) return JS_EXCEPTION; + obj = JS_ParseJSON (ctx, str, len, ""); + JS_FreeCString (ctx, str); + if (JS_IsException (obj)) return obj; + if (argc > 1 && JS_IsFunction (argv[1])) { + reviver = argv[1]; + root = JS_NewObject (ctx); + if (JS_IsException (root)) { + return JS_EXCEPTION; + } + if (JS_SetPropertyInternal (ctx, root, JS_KEY_empty, obj) < 0) { + return JS_EXCEPTION; + } + obj = internalize_json_property (ctx, root, JS_KEY_empty, reviver); + } + return obj; +} + +typedef struct JSONStringifyContext { + JSContext *ctx; + JSValue replacer_func; + JSValue stack; + JSValue property_list; + JSValue gap; + JSValue empty; + JSGCRef b_root; /* GC root for buffer - use JSC_B_GET/SET macros */ +} JSONStringifyContext; + +/* Macros to access the buffer from the rooted JSValue */ +#define JSC_B_GET(jsc) JS_VALUE_GET_STRING((jsc)->b_root.val) +#define JSC_B_SET(jsc, ptr) ((jsc)->b_root.val = JS_MKPTR(ptr)) +#define JSC_B_PUTC(jsc, c) do { \ + JSText *_b = pretext_putc(ctx, JSC_B_GET(jsc), c); \ + if (!_b) goto exception; \ + JSC_B_SET(jsc, _b); \ +} while(0) +#define JSC_B_CONCAT(jsc, v) do { \ + JSText *_b = pretext_concat_value(ctx, JSC_B_GET(jsc), v); \ + if (!_b) goto exception; \ + JSC_B_SET(jsc, _b); \ +} while(0) + +static JSValue js_json_check (JSContext *ctx, JSONStringifyContext *jsc, JSValue holder, JSValue val, JSValue key) { + JSValue v; + JSValue args[2]; + + /* check for object.toJSON method */ + /* ECMA specifies this is done only for Object and BigInt */ + if (JS_IsObject (val)) { + JSValue f = JS_GetProperty (ctx, val, JS_KEY_toJSON); + if (JS_IsException (f)) goto exception; + if (JS_IsFunction (f)) { + v = JS_Call (ctx, f, val, 1, &key); + val = v; + if (JS_IsException (val)) goto exception; + } else { + } + } + + if (!JS_IsNull (jsc->replacer_func)) { + args[0] = key; + args[1] = val; + v = JS_Call (ctx, jsc->replacer_func, holder, 2, args); + val = v; + if (JS_IsException (val)) goto exception; + } + + switch (JS_VALUE_GET_NORM_TAG (val)) { + case JS_TAG_PTR: /* includes arrays (OBJ_ARRAY) via mist_hdr */ + if (JS_IsFunction (val)) break; + /* fall through */ + case JS_TAG_STRING_IMM: + case JS_TAG_INT: + case JS_TAG_FLOAT64: + case JS_TAG_BOOL: + case JS_TAG_NULL: + case JS_TAG_EXCEPTION: + return val; + default: + break; + } + return JS_NULL; + +exception: + return JS_EXCEPTION; +} + +static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue holder, JSValue val, JSValue indent) { + JSValue v; + int64_t i, len; + int ret; + BOOL has_content; + JSGCRef val_ref, indent_ref, indent1_ref, sep_ref, sep1_ref, tab_ref, prop_ref; + + /* Root all values that can be heap pointers and survive across GC points */ + JS_PushGCRef (ctx, &val_ref); + JS_PushGCRef (ctx, &indent_ref); + JS_PushGCRef (ctx, &indent1_ref); + JS_PushGCRef (ctx, &sep_ref); + JS_PushGCRef (ctx, &sep1_ref); + JS_PushGCRef (ctx, &tab_ref); + JS_PushGCRef (ctx, &prop_ref); + + val_ref.val = val; + indent_ref.val = indent; + indent1_ref.val = JS_NULL; + sep_ref.val = JS_NULL; + sep1_ref.val = JS_NULL; + tab_ref.val = JS_NULL; + prop_ref.val = JS_NULL; + + if (js_check_stack_overflow (ctx, 0)) { + JS_ThrowStackOverflow (ctx); + goto exception; + } + + if (JS_IsObject ( + val_ref.val)) { /* includes arrays (OBJ_ARRAY) since they have JS_TAG_PTR */ + v = js_array_includes (ctx, jsc->stack, 1, &val_ref.val); + if (JS_IsException (v)) goto exception; + if (JS_ToBool (ctx, v)) { + JS_ThrowTypeError (ctx, "circular reference"); + goto exception; + } + indent1_ref.val = JS_ConcatString (ctx, indent_ref.val, jsc->gap); + if (JS_IsException (indent1_ref.val)) goto exception; + if (!JS_IsEmptyString (jsc->gap)) { + sep_ref.val = JS_ConcatString3 (ctx, "\n", indent1_ref.val, ""); + if (JS_IsException (sep_ref.val)) goto exception; + sep1_ref.val = js_new_string8 (ctx, " "); + if (JS_IsException (sep1_ref.val)) goto exception; + } else { + sep_ref.val = jsc->empty; + sep1_ref.val = jsc->empty; + } + v = js_cell_push (ctx, jsc->stack, 1, &val_ref.val); + if (check_exception_free (ctx, v)) goto exception; + ret = JS_IsArray (val_ref.val); + if (ret < 0) goto exception; + if (ret) { + if (js_get_length64 (ctx, &len, val_ref.val)) goto exception; + JSC_B_PUTC (jsc, '['); + for (i = 0; i < len; i++) { + if (i > 0) { + JSC_B_PUTC (jsc, ','); + } + JSC_B_CONCAT (jsc, sep_ref.val); + v = JS_GetPropertyInt64 (ctx, val_ref.val, i); + if (JS_IsException (v)) goto exception; + /* XXX: could do this string conversion only when needed */ + prop_ref.val = JS_ToString (ctx, JS_NewInt64 (ctx, i)); + if (JS_IsException (prop_ref.val)) goto exception; + v = js_json_check (ctx, jsc, val_ref.val, v, prop_ref.val); + prop_ref.val = JS_NULL; + if (JS_IsException (v)) goto exception; + if (JS_IsNull (v)) v = JS_NULL; + if (js_json_to_str (ctx, jsc, val_ref.val, v, indent1_ref.val)) goto exception; + } + if (len > 0 && !JS_IsEmptyString (jsc->gap)) { + JSC_B_PUTC (jsc, '\n'); + JSC_B_CONCAT (jsc, indent_ref.val); + } + JSC_B_PUTC (jsc, ']'); + } else { + if (!JS_IsNull (jsc->property_list)) + tab_ref.val = jsc->property_list; + else + tab_ref.val = JS_GetOwnPropertyNames (ctx, val_ref.val); + if (JS_IsException (tab_ref.val)) goto exception; + if (js_get_length64 (ctx, &len, tab_ref.val)) goto exception; + JSC_B_PUTC (jsc, '{'); + has_content = FALSE; + for (i = 0; i < len; i++) { + prop_ref.val = JS_GetPropertyInt64 (ctx, tab_ref.val, i); + if (JS_IsException (prop_ref.val)) goto exception; + v = JS_GetPropertyValue (ctx, val_ref.val, prop_ref.val); + if (JS_IsException (v)) goto exception; + v = js_json_check (ctx, jsc, val_ref.val, v, prop_ref.val); + if (JS_IsException (v)) goto exception; + if (!JS_IsNull (v)) { + if (has_content) { + JSC_B_PUTC (jsc, ','); + } + prop_ref.val = JS_ToQuotedString (ctx, prop_ref.val); + if (JS_IsException (prop_ref.val)) { + goto exception; + } + JSC_B_CONCAT (jsc, sep_ref.val); + JSC_B_CONCAT (jsc, prop_ref.val); + JSC_B_PUTC (jsc, ':'); + JSC_B_CONCAT (jsc, sep1_ref.val); + if (js_json_to_str (ctx, jsc, val_ref.val, v, indent1_ref.val)) goto exception; + has_content = TRUE; + } + } + if (has_content && !JS_IsEmptyString (jsc->gap)) { + JSC_B_PUTC (jsc, '\n'); + JSC_B_CONCAT (jsc, indent_ref.val); + } + JSC_B_PUTC (jsc, '}'); + } + if (check_exception_free (ctx, js_cell_pop (ctx, jsc->stack, 0, NULL))) + goto exception; + goto done; + } + switch (JS_VALUE_GET_NORM_TAG (val_ref.val)) { + case JS_TAG_STRING_IMM: + val_ref.val = JS_ToQuotedString (ctx, val_ref.val); + if (JS_IsException (val_ref.val)) goto exception; + goto concat_value; + case JS_TAG_FLOAT64: + if (!isfinite (JS_VALUE_GET_FLOAT64 (val_ref.val))) { val_ref.val = JS_NULL; } + goto concat_value; + case JS_TAG_INT: + case JS_TAG_BOOL: + case JS_TAG_NULL: + concat_value: { + JSText *_b = pretext_concat_value (ctx, JSC_B_GET (jsc), val_ref.val); + if (!_b) goto exception_ret; + JSC_B_SET (jsc, _b); + goto done; + } + default: + goto done; + } + +done: + JS_PopGCRef (ctx, &prop_ref); + JS_PopGCRef (ctx, &tab_ref); + JS_PopGCRef (ctx, &sep1_ref); + JS_PopGCRef (ctx, &sep_ref); + JS_PopGCRef (ctx, &indent1_ref); + JS_PopGCRef (ctx, &indent_ref); + JS_PopGCRef (ctx, &val_ref); + return 0; + +exception_ret: + JS_PopGCRef (ctx, &prop_ref); + JS_PopGCRef (ctx, &tab_ref); + JS_PopGCRef (ctx, &sep1_ref); + JS_PopGCRef (ctx, &sep_ref); + JS_PopGCRef (ctx, &indent1_ref); + JS_PopGCRef (ctx, &indent_ref); + JS_PopGCRef (ctx, &val_ref); + return -1; + +exception: + JS_PopGCRef (ctx, &prop_ref); + JS_PopGCRef (ctx, &tab_ref); + JS_PopGCRef (ctx, &sep1_ref); + JS_PopGCRef (ctx, &sep_ref); + JS_PopGCRef (ctx, &indent1_ref); + JS_PopGCRef (ctx, &indent_ref); + JS_PopGCRef (ctx, &val_ref); + return -1; +} + +JSValue JS_JSONStringify (JSContext *ctx, JSValue obj, JSValue replacer, JSValue space0) { + JSONStringifyContext jsc_s, *jsc = &jsc_s; + JSValue val, v, space, ret, wrapper; + int res; + int64_t i, j, n; + JSGCRef obj_ref; + + /* Root obj since GC can happen during stringify setup */ + JS_PushGCRef (ctx, &obj_ref); + obj_ref.val = obj; + + jsc->ctx = ctx; + jsc->replacer_func = JS_NULL; + jsc->stack = JS_NULL; + jsc->property_list = JS_NULL; + jsc->gap = JS_NULL; + jsc->empty = JS_KEY_empty; + ret = JS_NULL; + wrapper = JS_NULL; + + /* Root the buffer for GC safety */ + JS_PushGCRef (ctx, &jsc->b_root); + { + JSText *b_init = pretext_init (ctx, 0); + if (!b_init) goto exception; + JSC_B_SET (jsc, b_init); + } + jsc->stack = JS_NewArray (ctx); + if (JS_IsException (jsc->stack)) goto exception; + if (JS_IsFunction (replacer)) { + jsc->replacer_func = replacer; + } else { + res = JS_IsArray (replacer); + if (res < 0) goto exception; + if (res) { + /* XXX: enumeration is not fully correct */ + jsc->property_list = JS_NewArray (ctx); + if (JS_IsException (jsc->property_list)) goto exception; + if (js_get_length64 (ctx, &n, replacer)) goto exception; + for (i = j = 0; i < n; i++) { + JSValue present; + v = JS_GetPropertyInt64 (ctx, replacer, i); + if (JS_IsException (v)) goto exception; + if (JS_IsObject (v)) { + /* Objects are not valid property list items */ + continue; + } else if (JS_IsNumber (v)) { + v = JS_ToString (ctx, v); + if (JS_IsException (v)) goto exception; + } else if (!JS_IsText (v)) { + continue; + } + present + = js_array_includes (ctx, jsc->property_list, 1, (JSValue *)&v); + if (JS_IsException (present)) { + goto exception; + } + if (!JS_ToBool (ctx, present)) { + JS_SetPropertyInt64 (ctx, jsc->property_list, j++, v); + } else { + } + } + } + } + space = space0; + if (JS_IsNumber (space)) { + int n; + if (JS_ToInt32Clamp (ctx, &n, space, 0, 10, 0)) goto exception; + jsc->gap = js_new_string8_len (ctx, " ", n); + } else if (JS_IsText (space)) { + JSText *p = JS_VALUE_GET_STRING (space); + jsc->gap = js_sub_string (ctx, p, 0, min_int ((int)JSText_len (p), 10)); + } else { + jsc->gap = jsc->empty; + } + if (JS_IsException (jsc->gap)) goto exception; + wrapper = JS_NewObject (ctx); + if (JS_IsException (wrapper)) goto exception; + if (JS_SetPropertyInternal (ctx, wrapper, JS_KEY_empty, obj_ref.val) + < 0) + goto exception; + val = obj_ref.val; + + val = js_json_check (ctx, jsc, wrapper, val, jsc->empty); + if (JS_IsException (val)) goto exception; + if (JS_IsNull (val)) { + ret = JS_NULL; + goto done1; + } + if (js_json_to_str (ctx, jsc, wrapper, val, jsc->empty)) goto exception; + + ret = pretext_end (ctx, JSC_B_GET (jsc)); + goto done; + +exception: + ret = JS_EXCEPTION; +done1: +done: + JS_PopGCRef (ctx, &jsc->b_root); + JS_PopGCRef (ctx, &obj_ref); + return ret; +} + +/* ============================================================================ + * Cell Script Native Global Functions + * ============================================================================ + * These functions implement the core Cell script primitives: + * - text: string conversion and manipulation + * - number: number conversion and math utilities + * - array: array creation and manipulation + * - object: object creation and manipulation + * - fn: function utilities + * ============================================================================ + */ + +/* ---------------------------------------------------------------------------- + * number function and sub-functions + * ---------------------------------------------------------------------------- + */ + +/* number(val, format) - convert to number */ +static JSValue js_cell_number (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NULL; + + JSValue val = argv[0]; + int tag = JS_VALUE_GET_TAG (val); + + /* Handle boolean */ + if (tag == JS_TAG_BOOL) { + return JS_NewInt32 (ctx, JS_VALUE_GET_BOOL (val) ? 1 : 0); + } + + /* Handle number - return as-is */ + if (tag == JS_TAG_INT || tag == JS_TAG_FLOAT64) { + return val; + } + + /* Handle string */ + if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) { + const char *str = JS_ToCString (ctx, val); + if (!str) return JS_EXCEPTION; + + JSValue result; + + /* Check for format argument */ + if (argc > 1 && JS_VALUE_GET_TAG (argv[1]) == JS_TAG_INT) { + /* Radix conversion */ + int radix = JS_VALUE_GET_INT (argv[1]); + if (radix < 2 || radix > 36) { + JS_FreeCString (ctx, str); + return JS_NULL; + } + char *endptr; + long long n = strtoll (str, &endptr, radix); + if (endptr == str || *endptr != '\0') { + JS_FreeCString (ctx, str); + return JS_NULL; + } + result = JS_NewInt64 (ctx, n); + } else if (argc > 1 + && (JS_VALUE_GET_TAG (argv[1]) == JS_TAG_STRING + || JS_VALUE_GET_TAG (argv[1]) == JS_TAG_STRING_IMM)) { + /* Format string */ + const char *format = JS_ToCString (ctx, argv[1]); + if (!format) { + JS_FreeCString (ctx, str); + return JS_EXCEPTION; + } + + char *clean = js_malloc (ctx, strlen (str) + 1); + if (!clean) { + JS_FreeCString (ctx, format); + JS_FreeCString (ctx, str); + return JS_EXCEPTION; + } + + const char *p = str; + char *q = clean; + + if (strcmp (format, "u") == 0) { + /* underbar separator */ + while (*p) { + if (*p != '_') *q++ = *p; + p++; + } + } else if (strcmp (format, "d") == 0 || strcmp (format, "l") == 0) { + /* comma separator */ + while (*p) { + if (*p != ',') *q++ = *p; + p++; + } + } else if (strcmp (format, "s") == 0) { + /* space separator */ + while (*p) { + if (*p != ' ') *q++ = *p; + p++; + } + } else if (strcmp (format, "v") == 0) { + /* European style: period separator, comma decimal */ + while (*p) { + if (*p == '.') { + p++; + continue; + } + if (*p == ',') { + *q++ = '.'; + p++; + continue; + } + *q++ = *p++; + } + } else if (strcmp (format, "b") == 0) { + *q = '\0'; + char *endptr; + long long n = strtoll (str, &endptr, 2); + js_free (ctx, clean); + JS_FreeCString (ctx, format); + JS_FreeCString (ctx, str); + if (endptr == str) return JS_NULL; + return JS_NewInt64 (ctx, n); + } else if (strcmp (format, "o") == 0) { + *q = '\0'; + char *endptr; + long long n = strtoll (str, &endptr, 8); + js_free (ctx, clean); + JS_FreeCString (ctx, format); + JS_FreeCString (ctx, str); + if (endptr == str) return JS_NULL; + return JS_NewInt64 (ctx, n); + } else if (strcmp (format, "h") == 0) { + *q = '\0'; + char *endptr; + long long n = strtoll (str, &endptr, 16); + js_free (ctx, clean); + JS_FreeCString (ctx, format); + JS_FreeCString (ctx, str); + if (endptr == str) return JS_NULL; + return JS_NewInt64 (ctx, n); + } else if (strcmp (format, "t") == 0) { + *q = '\0'; + char *endptr; + long long n = strtoll (str, &endptr, 32); + js_free (ctx, clean); + JS_FreeCString (ctx, format); + JS_FreeCString (ctx, str); + if (endptr == str) return JS_NULL; + return JS_NewInt64 (ctx, n); + } else if (strcmp (format, "j") == 0) { + /* JavaScript style prefix */ + js_free (ctx, clean); + JS_FreeCString (ctx, format); + int radix = 10; + const char *start = str; + if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { + radix = 16; + start = str + 2; + } else if (str[0] == '0' && (str[1] == 'o' || str[1] == 'O')) { + radix = 8; + start = str + 2; + } else if (str[0] == '0' && (str[1] == 'b' || str[1] == 'B')) { + radix = 2; + start = str + 2; + } + if (radix != 10) { + char *endptr; + long long n = strtoll (start, &endptr, radix); + JS_FreeCString (ctx, str); + if (endptr == start) return JS_NULL; + return JS_NewInt64 (ctx, n); + } + double d = strtod (str, NULL); + JS_FreeCString (ctx, str); + return JS_NewFloat64 (ctx, d); + } else { + /* Unknown format, just copy */ + strcpy (clean, str); + q = clean + strlen (clean); + } + *q = '\0'; + + double d = strtod (clean, NULL); + js_free (ctx, clean); + JS_FreeCString (ctx, format); + JS_FreeCString (ctx, str); + if (isnan (d)) return JS_NULL; + return JS_NewFloat64 (ctx, d); + } else { + /* Default: parse as decimal */ + char *endptr; + double d = strtod (str, &endptr); + JS_FreeCString (ctx, str); + if (endptr == str || isnan (d)) return JS_NULL; + result = JS_NewFloat64 (ctx, d); + } + + return result; + } + + return JS_NULL; +} + +/* number.whole(n) - truncate to integer */ +static JSValue js_cell_number_whole (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double d; + if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; + return JS_NewFloat64 (ctx, trunc (d)); +} + +/* number.fraction(n) - get fractional part */ +static JSValue js_cell_number_fraction (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double d; + if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; + return JS_NewFloat64 (ctx, d - trunc (d)); +} + +/* number.floor(n, place) - floor with optional decimal place */ +static JSValue js_cell_number_floor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double d; + if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; + if (argc < 2 || JS_IsNull (argv[1])) { + return JS_NewFloat64 (ctx, floor (d)); + } + int place; + if (JS_ToInt32 (ctx, &place, argv[1])) return JS_NULL; + if (place == 0) return JS_NewFloat64 (ctx, floor (d)); + double mult = pow (10, -place); + return JS_NewFloat64 (ctx, floor (d * mult) / mult); +} + +/* number.ceiling(n, place) - ceiling with optional decimal place */ +static JSValue js_cell_number_ceiling (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double d; + if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; + if (argc < 2 || JS_IsNull (argv[1])) { + return JS_NewFloat64 (ctx, ceil (d)); + } + int place; + if (JS_ToInt32 (ctx, &place, argv[1])) return JS_NULL; + if (place == 0) return JS_NewFloat64 (ctx, ceil (d)); + double mult = pow (10, -place); + return JS_NewFloat64 (ctx, ceil (d * mult) / mult); +} + +/* number.abs(n) - absolute value */ +static JSValue js_cell_number_abs (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NULL; + int tag = JS_VALUE_GET_TAG (argv[0]); + if (tag != JS_TAG_INT && tag != JS_TAG_FLOAT64) return JS_NULL; + double d; + if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; + return JS_NewFloat64 (ctx, fabs (d)); +} + +/* number.round(n, place) - round with optional decimal place */ +static JSValue js_cell_number_round (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double d; + if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; + if (argc < 2 || JS_IsNull (argv[1])) { + return JS_NewFloat64 (ctx, round (d)); + } + int place; + if (JS_ToInt32 (ctx, &place, argv[1])) return JS_NULL; + if (place == 0) return JS_NewFloat64 (ctx, round (d)); + double mult = pow (10, -place); + return JS_NewFloat64 (ctx, round (d * mult) / mult); +} + +/* number.sign(n) - return sign (-1, 0, 1) */ +static JSValue js_cell_number_sign (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double d; + if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; + if (d < 0) return JS_NewInt32 (ctx, -1); + if (d > 0) return JS_NewInt32 (ctx, 1); + return JS_NewInt32 (ctx, 0); +} + +/* number.trunc(n, place) - truncate with optional decimal place */ +static JSValue js_cell_number_trunc (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double d; + if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; + if (argc < 2 || JS_IsNull (argv[1])) { + return JS_NewFloat64 (ctx, trunc (d)); + } + int place; + if (JS_ToInt32 (ctx, &place, argv[1])) return JS_NULL; + if (place == 0) return JS_NewFloat64 (ctx, trunc (d)); + double mult = pow (10, -place); + return JS_NewFloat64 (ctx, trunc (d * mult) / mult); +} + +/* number.min(...vals) - minimum value */ +static JSValue js_cell_number_min (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc == 0) return JS_NULL; + double result; + if (JS_ToFloat64 (ctx, &result, argv[0])) return JS_NULL; + for (int i = 1; i < argc; i++) { + double d; + if (JS_ToFloat64 (ctx, &d, argv[i])) return JS_NULL; + if (d < result) result = d; + } + return JS_NewFloat64 (ctx, result); +} + +/* number.max(...vals) - maximum value */ +static JSValue js_cell_number_max (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc == 0) return JS_NULL; + double result; + if (JS_ToFloat64 (ctx, &result, argv[0])) return JS_NULL; + for (int i = 1; i < argc; i++) { + double d; + if (JS_ToFloat64 (ctx, &d, argv[i])) return JS_NULL; + if (d > result) result = d; + } + return JS_NewFloat64 (ctx, result); +} + +/* number.remainder(dividend, divisor) - remainder after division */ +static JSValue js_cell_number_remainder (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) return JS_NULL; + double dividend, divisor; + if (JS_ToFloat64 (ctx, ÷nd, argv[0])) return JS_NULL; + if (JS_ToFloat64 (ctx, &divisor, argv[1])) return JS_NULL; + if (divisor == 0) return JS_NULL; + return JS_NewFloat64 (ctx, + dividend - (trunc (dividend / divisor) * divisor)); +} + +/* ---------------------------------------------------------------------------- + * text function and sub-functions + * ---------------------------------------------------------------------------- + */ + +/* Helper: convert number to string with radix */ +static JSValue js_cell_number_to_radix_string (JSContext *ctx, double num, int radix) { + if (radix < 2 || radix > 36) return JS_NULL; + + /* For base 10, handle floating point properly */ + if (radix == 10) { + char buf[64]; + /* Check if it's an integer */ + if (trunc (num) == num && num >= -9007199254740991.0 + && num <= 9007199254740991.0) { + snprintf (buf, sizeof (buf), "%.0f", num); + } else { + /* Use %g to get a reasonable representation without trailing zeros */ + snprintf (buf, sizeof (buf), "%.15g", num); + } + return JS_NewString (ctx, buf); + } + + /* For other radixes, use integer conversion */ + static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; + char buf[70]; + int len = 0; + int negative = 0; + int64_t n = (int64_t)trunc (num); + + if (n < 0) { + negative = 1; + n = -n; + } + + if (n == 0) { + buf[len++] = '0'; + } else { + while (n > 0) { + buf[len++] = digits[n % radix]; + n /= radix; + } + } + + if (negative) { buf[len++] = '-'; } + + /* Reverse the string */ + char result[72]; + int j = 0; + for (int i = len - 1; i >= 0; i--) { + result[j++] = buf[i]; + } + result[j] = '\0'; + + return JS_NewString (ctx, result); +} + +/* Helper: add separator every n digits from right */ +static char *add_separator (JSContext *ctx, const char *str, char sep, int n) { + if (n <= 0) { + char *result = js_malloc (ctx, strlen (str) + 1); + if (result) strcpy (result, str); + return result; + } + + int negative = (str[0] == '-'); + const char *start = negative ? str + 1 : str; + + /* Find decimal point */ + const char *decimal = strchr (start, '.'); + int int_len = decimal ? (int)(decimal - start) : (int)strlen (start); + + int num_seps = (int_len - 1) / n; + int result_len = strlen (str) + num_seps + 1; + char *result = js_malloc (ctx, result_len); + if (!result) return NULL; + + char *q = result; + if (negative) *q++ = '-'; + + int count = int_len % n; + if (count == 0) count = n; + + for (int i = 0; i < int_len; i++) { + if (i > 0 && count == 0) { + *q++ = sep; + count = n; + } + *q++ = start[i]; + count--; + } + + if (decimal) { + strcpy (q, decimal); + } else { + *q = '\0'; + } + + return result; +} + +/* Helper: format number with format string */ +static JSValue js_cell_format_number (JSContext *ctx, double num, const char *format) { + int separation = 0; + char style = '\0'; + int places = 0; + int i = 0; + + /* Parse separation digit */ + if (format[i] >= '0' && format[i] <= '9') { + separation = format[i] - '0'; + i++; + } + + /* Parse style letter */ + if (format[i]) { + style = format[i]; + i++; + } else { + return JS_NULL; + } + + /* Parse places digits */ + if (format[i] >= '0' && format[i] <= '9') { + places = format[i] - '0'; + i++; + if (format[i] >= '0' && format[i] <= '9') { + places = places * 10 + (format[i] - '0'); + i++; + } + } + + /* Invalid if more characters */ + if (format[i] != '\0') return JS_NULL; + + char buf[128]; + char *result_str = NULL; + + switch (style) { + case 'e': { + /* Exponential */ + if (places > 0) + snprintf (buf, sizeof (buf), "%.*e", places, num); + else + snprintf (buf, sizeof (buf), "%e", num); + return JS_NewString (ctx, buf); + } + case 'n': { + /* Number - scientific for extreme values */ + if (fabs (num) >= 1e21 || (fabs (num) < 1e-6 && num != 0)) { + snprintf (buf, sizeof (buf), "%e", num); + } else if (places > 0) { + snprintf (buf, sizeof (buf), "%.*f", places, num); + } else { + snprintf (buf, sizeof (buf), "%g", num); + } + return JS_NewString (ctx, buf); + } + case 's': { + /* Space separated */ + if (separation == 0) separation = 3; + snprintf (buf, sizeof (buf), "%.*f", places, num); + result_str = add_separator (ctx, buf, ' ', separation); + if (!result_str) return JS_EXCEPTION; + JSValue ret = JS_NewString (ctx, result_str); + js_free (ctx, result_str); + return ret; + } + case 'u': { + /* Underbar separated */ + snprintf (buf, sizeof (buf), "%.*f", places, num); + if (separation > 0) { + result_str = add_separator (ctx, buf, '_', separation); + if (!result_str) return JS_EXCEPTION; + JSValue ret = JS_NewString (ctx, result_str); + js_free (ctx, result_str); + return ret; + } + return JS_NewString (ctx, buf); + } + case 'd': + case 'l': { + /* Decimal/locale with comma separator */ + if (separation == 0) separation = 3; + if (places == 0 && style == 'd') places = 2; + snprintf (buf, sizeof (buf), "%.*f", places, num); + result_str = add_separator (ctx, buf, ',', separation); + if (!result_str) return JS_EXCEPTION; + JSValue ret = JS_NewString (ctx, result_str); + js_free (ctx, result_str); + return ret; + } + case 'v': { + /* European style: comma decimal, period separator */ + snprintf (buf, sizeof (buf), "%.*f", places, num); + /* Replace . with , */ + for (char *p = buf; *p; p++) { + if (*p == '.') *p = ','; + } + if (separation > 0) { + result_str = add_separator (ctx, buf, '.', separation); + if (!result_str) return JS_EXCEPTION; + JSValue ret = JS_NewString (ctx, result_str); + js_free (ctx, result_str); + return ret; + } + return JS_NewString (ctx, buf); + } + case 'i': { + /* Integer base 10 */ + if (places == 0) places = 1; + int64_t n = (int64_t)trunc (num); + int neg = n < 0; + if (neg) n = -n; + snprintf (buf, sizeof (buf), "%lld", (long long)n); + int len = strlen (buf); + /* Pad with zeros */ + if (len < places) { + memmove (buf + (places - len), buf, len + 1); + memset (buf, '0', places - len); + } + if (separation > 0) { + result_str = add_separator (ctx, buf, '_', separation); + if (!result_str) return JS_EXCEPTION; + if (neg) { + char *final = js_malloc (ctx, strlen (result_str) + 2); + if (!final) { + js_free (ctx, result_str); + return JS_EXCEPTION; + } + final[0] = '-'; + strcpy (final + 1, result_str); + js_free (ctx, result_str); + JSValue ret = JS_NewString (ctx, final); + js_free (ctx, final); + return ret; + } + JSValue ret = JS_NewString (ctx, result_str); + js_free (ctx, result_str); + return ret; + } + if (neg) { + memmove (buf + 1, buf, strlen (buf) + 1); + buf[0] = '-'; + } + return JS_NewString (ctx, buf); + } + case 'b': { + /* Binary */ + if (places == 0) places = 1; + return js_cell_number_to_radix_string (ctx, num, 2); + } + case 'o': { + /* Octal */ + if (places == 0) places = 1; + int64_t n = (int64_t)trunc (num); + snprintf (buf, sizeof (buf), "%llo", (long long)(n < 0 ? -n : n)); + /* Uppercase and pad */ + for (char *p = buf; *p; p++) + *p = toupper (*p); + int len = strlen (buf); + if (len < places) { + memmove (buf + (places - len), buf, len + 1); + memset (buf, '0', places - len); + } + if (n < 0) { + memmove (buf + 1, buf, strlen (buf) + 1); + buf[0] = '-'; + } + return JS_NewString (ctx, buf); + } + case 'h': { + /* Hexadecimal */ + if (places == 0) places = 1; + int64_t n = (int64_t)trunc (num); + snprintf (buf, sizeof (buf), "%llX", (long long)(n < 0 ? -n : n)); + int len = strlen (buf); + if (len < places) { + memmove (buf + (places - len), buf, len + 1); + memset (buf, '0', places - len); + } + if (n < 0) { + memmove (buf + 1, buf, strlen (buf) + 1); + buf[0] = '-'; + } + return JS_NewString (ctx, buf); + } + case 't': { + /* Base32 */ + if (places == 0) places = 1; + return js_cell_number_to_radix_string (ctx, num, 32); + } + } + + return JS_NULL; +} + +/* Forward declaration for blob helper */ +blob *js_get_blob (JSContext *ctx, JSValue val); + +/* modulo(dividend, divisor) - result has sign of divisor */ +static JSValue js_cell_modulo (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) return JS_NULL; + + double dividend, divisor; + if (JS_ToFloat64 (ctx, ÷nd, argv[0])) return JS_NULL; + if (JS_ToFloat64 (ctx, &divisor, argv[1])) return JS_NULL; + + /* If either operand is NaN, return null */ + if (isnan (dividend) || isnan (divisor)) return JS_NULL; + + /* If divisor is 0, return null */ + if (divisor == 0) return JS_NULL; + + /* If dividend is 0, return 0 */ + if (dividend == 0) return JS_NewFloat64 (ctx, 0.0); + + /* modulo = dividend - (divisor * floor(dividend / divisor)) */ + double result = dividend - (divisor * floor (dividend / divisor)); + + return JS_NewFloat64 (ctx, result); +} + +/* not(bool) - negate a boolean value */ +static JSValue js_cell_not (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NULL; + + if (!JS_IsBool (argv[0])) return JS_NULL; + + return JS_NewBool (ctx, !JS_ToBool (ctx, argv[0])); +} + +/* neg(number) - negate a number */ +static JSValue js_cell_neg (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NULL; + + double num; + if (JS_ToFloat64 (ctx, &num, argv[0])) return JS_NULL; + + if (isnan (num)) return JS_NULL; + + return JS_NewFloat64 (ctx, -num); +} + +/* character(value) - get character from text or codepoint */ +JSValue js_cell_character (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NewString (ctx, ""); + + JSValue arg = argv[0]; + int tag = JS_VALUE_GET_TAG (arg); + + /* Handle string - return first character */ + if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) { + if (js_string_value_len (arg) == 0) return JS_NewString (ctx, ""); + return js_sub_string_val (ctx, arg, 0, 1); + } + + /* Handle integer - return character from codepoint */ + if (tag == JS_TAG_INT) { + int32_t val = JS_VALUE_GET_INT (arg); + if (val < 0 || val > 0x10FFFF) return JS_NewString (ctx, ""); + + uint32_t codepoint = (uint32_t)val; + if (codepoint < 0x80) { + char buf[2] = { (char)codepoint, '\0' }; + return JS_NewString (ctx, buf); + } + /* Create single-codepoint UTF-32 string */ + JSText *str = js_alloc_string (ctx, 1); + if (!str) return JS_EXCEPTION; + string_put (str, 0, codepoint); + str->length = 1; + return pretext_end (ctx, str); + } + + /* Handle float - convert to integer if non-negative and within range */ + if (tag == JS_TAG_FLOAT64) { + double d = JS_VALUE_GET_FLOAT64 (arg); + if (isnan (d) || d < 0 || d > 0x10FFFF || d != trunc (d)) + return JS_NewString (ctx, ""); + + uint32_t codepoint = (uint32_t)d; + if (codepoint < 0x80) { + char buf[2] = { (char)codepoint, '\0' }; + return JS_NewString (ctx, buf); + } + /* Create single-codepoint UTF-32 string */ + JSText *str = js_alloc_string (ctx, 1); + if (!str) return JS_EXCEPTION; + string_put (str, 0, codepoint); + str->length = 1; + return pretext_end (ctx, str); + } + + return JS_NewString (ctx, ""); +} + +/* text(arg, format) - main text function */ +static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NULL; + + JSValue arg = argv[0]; + int tag = JS_VALUE_GET_TAG (arg); + + /* Handle string / rope */ + if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) { + JSValue str = JS_ToString (ctx, arg); /* owned + flattens rope */ + if (JS_IsException (str)) return JS_EXCEPTION; + + if (argc == 1) return str; + + if (argc >= 2) { + int tag1 = JS_VALUE_GET_TAG (argv[1]); + if (tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64) { + int len = js_string_value_len (str); + int from, to; + + if (JS_ToInt32 (ctx, &from, argv[1])) + return JS_EXCEPTION; + + if (from < 0) from += len; + if (from < 0) from = 0; + if (from > len) from = len; + + to = len; + if (argc >= 3) { + if (JS_ToInt32 (ctx, &to, argv[2])) + return JS_EXCEPTION; + if (to < 0) to += len; + if (to < 0) to = 0; + if (to > len) to = len; + } + + if (from > to) + return JS_NULL; + + return js_sub_string_val (ctx, str, from, to); + } + } + + return str; + } + + /* Handle blob - convert to text representation */ + blob *bd = js_get_blob (ctx, arg); + if (bd) { + if (!bd->is_stone) + return JS_ThrowTypeError (ctx, "text: blob must be stone"); + + char format = '\0'; + if (argc > 1) { + const char *fmt = JS_ToCString (ctx, argv[1]); + if (!fmt) return JS_EXCEPTION; + format = fmt[0]; + JS_FreeCString (ctx, fmt); + } + + size_t byte_len = (bd->length + 7) / 8; + const uint8_t *data = bd->data; + + if (format == 'h') { + static const char hex[] = "0123456789abcdef"; + char *result = js_malloc (ctx, byte_len * 2 + 1); + if (!result) return JS_EXCEPTION; + for (size_t i = 0; i < byte_len; i++) { + result[i * 2] = hex[(data[i] >> 4) & 0xF]; + result[i * 2 + 1] = hex[data[i] & 0xF]; + } + result[byte_len * 2] = '\0'; + JSValue ret = JS_NewString (ctx, result); + js_free (ctx, result); + return ret; + } else if (format == 'b') { + char *result = js_malloc (ctx, bd->length + 1); + if (!result) return JS_EXCEPTION; + for (size_t i = 0; i < (size_t)bd->length; i++) { + size_t byte_idx = i / 8; + size_t bit_idx = i % 8; + result[i] = (data[byte_idx] & (1u << bit_idx)) ? '1' : '0'; + } + result[bd->length] = '\0'; + JSValue ret = JS_NewString (ctx, result); + js_free (ctx, result); + return ret; + } else if (format == 'o') { + size_t octal_len = ((size_t)bd->length + 2) / 3; + char *result = js_malloc (ctx, octal_len + 1); + if (!result) return JS_EXCEPTION; + for (size_t i = 0; i < octal_len; i++) { + int val = 0; + for (int j = 0; j < 3; j++) { + size_t bit_pos = i * 3 + (size_t)j; + if (bit_pos < (size_t)bd->length) { + size_t byte_idx = bit_pos / 8; + size_t bit_idx = bit_pos % 8; + if (data[byte_idx] & (1u << bit_idx)) val |= (1 << j); + } + } + result[i] = (char)('0' + val); + } + result[octal_len] = '\0'; + JSValue ret = JS_NewString (ctx, result); + js_free (ctx, result); + return ret; + } else if (format == 't') { + static const char b32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; + size_t b32_len = ((size_t)bd->length + 4) / 5; + char *result = js_malloc (ctx, b32_len + 1); + if (!result) return JS_EXCEPTION; + for (size_t i = 0; i < b32_len; i++) { + int val = 0; + for (int j = 0; j < 5; j++) { + size_t bit_pos = i * 5 + (size_t)j; + if (bit_pos < (size_t)bd->length) { + size_t byte_idx = bit_pos / 8; + size_t bit_idx = bit_pos % 8; + if (data[byte_idx] & (1u << bit_idx)) val |= (1 << j); + } + } + result[i] = b32[val & 31]; + } + result[b32_len] = '\0'; + JSValue ret = JS_NewString (ctx, result); + js_free (ctx, result); + return ret; + } else { + if (bd->length % 8 != 0) + return JS_ThrowTypeError (ctx, + "text: blob not byte-aligned for UTF-8"); + return JS_NewStringLen (ctx, (const char *)data, byte_len); + } + } + + /* Handle number */ + if (tag == JS_TAG_INT || tag == JS_TAG_FLOAT64) { + double num; + if (JS_ToFloat64 (ctx, &num, arg)) return JS_EXCEPTION; + + if (argc > 1) { + int tag1 = JS_VALUE_GET_TAG (argv[1]); + if (tag1 == JS_TAG_INT) { + int radix = JS_VALUE_GET_INT (argv[1]); + return js_cell_number_to_radix_string (ctx, num, radix); + } + if (tag1 == JS_TAG_STRING || tag1 == JS_TAG_STRING_IMM) { + const char *format = JS_ToCString (ctx, argv[1]); + if (!format) return JS_EXCEPTION; + JSValue result = js_cell_format_number (ctx, num, format); + JS_FreeCString (ctx, format); + return result; + } + } + + return js_cell_number_to_radix_string (ctx, num, 10); + } + + /* Handle array */ + if (JS_IsArray (arg)) { + int64_t len; + JSGCRef arg_ref; + JS_AddGCRef(ctx, &arg_ref); + arg_ref.val = arg; + if (js_get_length64 (ctx, &len, arg_ref.val)) { + JS_DeleteGCRef(ctx, &arg_ref); + return JS_EXCEPTION; + } + + const char *separator = ""; + BOOL sep_alloc = FALSE; + + if (argc > 1 && JS_VALUE_IS_TEXT (argv[1])) { + separator = JS_ToCString (ctx, argv[1]); + if (!separator) return JS_EXCEPTION; + sep_alloc = TRUE; + } + + JSText *b = pretext_init (ctx, 0); + if (!b) { + if (sep_alloc) JS_FreeCString (ctx, separator); + return JS_EXCEPTION; + } + + /* Root b across allocating calls (JS_GetPropertyInt64, JS_ToString) */ + JSGCRef b_ref; + JS_AddGCRef (ctx, &b_ref); + b_ref.val = JS_MKPTR (b); + + for (int64_t i = 0; i < len; i++) { + if (i > 0 && separator[0]) { + b = (JSText *)chase (b_ref.val); + b = pretext_puts8 (ctx, b, separator); + if (!b) goto array_fail; + b_ref.val = JS_MKPTR (b); + } + + b = (JSText *)chase (b_ref.val); /* re-chase before use */ + JSValue item = JS_GetPropertyInt64 (ctx, arg_ref.val, i); + if (JS_IsException (item)) goto array_fail; + + if (!JS_VALUE_IS_TEXT (item)) { + if (sep_alloc) JS_FreeCString (ctx, separator); + JS_DeleteGCRef (ctx, &b_ref); + JS_DeleteGCRef (ctx, &arg_ref); + return JS_ThrowTypeError (ctx, "text: array element is not a string"); + } + + JSValue item_str = JS_ToString (ctx, item); + if (JS_IsException (item_str)) goto array_fail; + + b = (JSText *)chase (b_ref.val); /* re-chase after JS_ToString */ + b = pretext_concat_value (ctx, b, item_str); + if (!b) goto array_fail; + b_ref.val = JS_MKPTR (b); + } + + b = (JSText *)chase (b_ref.val); + if (sep_alloc) JS_FreeCString (ctx, separator); + JS_DeleteGCRef (ctx, &b_ref); + JS_DeleteGCRef (ctx, &arg_ref); + return pretext_end (ctx, b); + + array_fail: + if (sep_alloc) JS_FreeCString (ctx, separator); + JS_DeleteGCRef (ctx, &b_ref); + JS_DeleteGCRef (ctx, &arg_ref); + return JS_EXCEPTION; + } + + /* Handle function - return source or native stub */ + if (JS_IsFunction (arg)) { + JSFunction *fn = JS_VALUE_GET_FUNCTION (arg); + if (fn->kind == JS_FUNC_KIND_BYTECODE) { + JSFunctionBytecode *b = fn->u.func.function_bytecode; + if (b->has_debug && b->debug.source) + return JS_NewStringLen (ctx, b->debug.source, b->debug.source_len); + } + + const char *pref = "function "; + const char *suff = "() {\n [native code]\n}"; + const char *name = ""; + const char *name_cstr = NULL; + + if (fn->kind == JS_FUNC_KIND_BYTECODE) { + JSFunctionBytecode *fb = fn->u.func.function_bytecode; + name_cstr = JS_ToCString (ctx, fb->func_name); + if (name_cstr) name = name_cstr; + } else if (!JS_IsNull (fn->name)) { + name_cstr = JS_ToCString (ctx, fn->name); + if (name_cstr) name = name_cstr; + } + + size_t plen = strlen (pref); + size_t nlen = strlen (name); + size_t slen = strlen (suff); + + char *result = js_malloc (ctx, plen + nlen + slen + 1); + if (!result) { + if (name_cstr) JS_FreeCString (ctx, name_cstr); + return JS_EXCEPTION; + } + + memcpy (result, pref, plen); + memcpy (result + plen, name, nlen); + memcpy (result + plen + nlen, suff, slen + 1); + + JSValue ret = JS_NewString (ctx, result); + js_free (ctx, result); + if (name_cstr) JS_FreeCString (ctx, name_cstr); + return ret; + } + + return JS_ToString (ctx, arg); + return JS_ThrowInternalError (ctx, "Could not convert to text. Tag is %d", tag); +} + +/* text.lower(str) - convert to lowercase */ +JSValue js_cell_text_lower (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NULL; + if (!JS_VALUE_IS_TEXT (argv[0])) return JS_NULL; + + /* Handle immediate ASCII - no GC concern */ + if (MIST_IsImmediateASCII (argv[0])) { + int len = MIST_GetImmediateASCIILen (argv[0]); + JSText *b = pretext_init (ctx, len); + if (!b) return JS_EXCEPTION; + for (int i = 0; i < len; i++) { + uint32_t c = MIST_GetImmediateASCIIChar (argv[0], i); + if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a'; + b = pretext_putc (ctx, b, c); + if (!b) return JS_EXCEPTION; + } + return pretext_end (ctx, b); + } + + /* Heap text: must re-chase after GC points */ + int len = (int)JSText_len (JS_VALUE_GET_STRING (argv[0])); + JSText *b = pretext_init (ctx, len); + if (!b) return JS_EXCEPTION; + + for (int i = 0; i < len; i++) { + JSText *p = JS_VALUE_GET_STRING (argv[0]); /* Re-chase each iteration */ + uint32_t c = string_get (p, i); + if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a'; + b = pretext_putc (ctx, b, c); + if (!b) return JS_EXCEPTION; + } + + return pretext_end (ctx, b); +} + +/* text.upper(str) - convert to uppercase */ +JSValue js_cell_text_upper (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NULL; + if (!JS_VALUE_IS_TEXT (argv[0])) return JS_NULL; + + /* Handle immediate ASCII - no GC concern */ + if (MIST_IsImmediateASCII (argv[0])) { + int len = MIST_GetImmediateASCIILen (argv[0]); + JSText *b = pretext_init (ctx, len); + if (!b) return JS_EXCEPTION; + for (int i = 0; i < len; i++) { + uint32_t c = MIST_GetImmediateASCIIChar (argv[0], i); + if (c >= 'a' && c <= 'z') c = c - 'a' + 'A'; + b = pretext_putc (ctx, b, c); + if (!b) return JS_EXCEPTION; + } + return pretext_end (ctx, b); + } + + /* Heap text: must re-chase after GC points */ + int len = (int)JSText_len (JS_VALUE_GET_STRING (argv[0])); + JSText *b = pretext_init (ctx, len); + if (!b) return JS_EXCEPTION; + + for (int i = 0; i < len; i++) { + JSText *p = JS_VALUE_GET_STRING (argv[0]); /* Re-chase each iteration */ + uint32_t c = string_get (p, i); + if (c >= 'a' && c <= 'z') c = c - 'a' + 'A'; + b = pretext_putc (ctx, b, c); + if (!b) return JS_EXCEPTION; + } + + return pretext_end (ctx, b); +} + +/* text.trim(str, reject) - trim whitespace or custom characters */ +static JSValue js_cell_text_trim (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NULL; + if (!JS_IsText (argv[0])) return JS_NULL; + + JSValue str = argv[0]; + int start = 0; + int end = js_string_value_len (str); + + if (argc > 1 && !JS_IsNull (argv[1])) { + /* Custom trim with reject characters */ + const char *reject = JS_ToCString (ctx, argv[1]); + if (!reject) return JS_EXCEPTION; + size_t reject_len = strlen (reject); + + while (start < end) { + uint32_t c = js_string_value_get (str, start); + int found = 0; + for (size_t i = 0; i < reject_len; i++) { + if (c == (uint8_t)reject[i]) { + found = 1; + break; + } + } + if (!found) break; + start++; + } + while (end > start) { + uint32_t c = js_string_value_get (str, end - 1); + int found = 0; + for (size_t i = 0; i < reject_len; i++) { + if (c == (uint8_t)reject[i]) { + found = 1; + break; + } + } + if (!found) break; + end--; + } + JS_FreeCString (ctx, reject); + } else { + /* Default: trim whitespace */ + while (start < end && lre_is_space (js_string_value_get (str, start))) + start++; + while (end > start && lre_is_space (js_string_value_get (str, end - 1))) + end--; + } + + return js_sub_string_val (ctx, str, start, end); +} + +/* text.codepoint(str) - get first codepoint */ +JSValue js_cell_text_codepoint (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NULL; + if (!JS_IsText (argv[0])) return JS_NULL; + + /* Handle immediate strings directly */ + if (MIST_IsImmediateASCII (argv[0])) { + int plen = MIST_GetImmediateASCIILen (argv[0]); + if (plen == 0) return JS_NULL; + uint32_t c = MIST_GetImmediateASCIIChar (argv[0], 0); + return JS_NewInt32 (ctx, c); + } + + /* Heap string */ + JSText *p = JS_VALUE_GET_STRING (argv[0]); + int plen = (int)JSText_len (p); + if (plen == 0) { + return JS_NULL; + } + + uint32_t c = string_get (p, 0); + /* Handle surrogate pairs */ + if (c >= 0xD800 && c <= 0xDBFF && plen > 1) { + uint32_t c2 = string_get (p, 1); + if (c2 >= 0xDC00 && c2 <= 0xDFFF) { + c = 0x10000 + ((c - 0xD800) << 10) + (c2 - 0xDC00); + } + } + + return JS_NewInt32 (ctx, c); +} + +/* Helpers (C, not C++). Put these above js_cell_text_replace in the same C + * file. */ + +static JSText *pt_concat_value_to_string_free (JSContext *ctx, JSText *b, JSValue v) { + JSValue s = JS_ToString (ctx, v); + if (JS_IsException (s)) return NULL; + b = pretext_concat_value (ctx, b, s); + return b; +} + +/* Build replacement for a match at `found`. + * - If replacement is a function: call it as (match_text, found) + * - Else if replacement exists: duplicate it + * - Else: empty string + * Returns JS_EXCEPTION on error, JS_NULL if callback returned null, or any + * JSValue. This function CONSUMES match_val if it calls a function (it will + * free it via args cleanup), otherwise it will free match_val before + * returning. + */ +static JSValue make_replacement (JSContext *ctx, int argc, JSValue *argv, int found, JSValue match_val) { + JSValue rep; + + if (argc > 2 && JS_IsFunction (argv[2])) { + JSValue args[2]; + args[0] = match_val; + args[1] = JS_NewInt32 (ctx, found); + rep = JS_Call (ctx, argv[2], JS_NULL, 2, args); + return rep; + } + + + if (argc > 2) return argv[2]; + return JS_KEY_empty; +} + +static int JS_IsRegExp (JSContext *ctx, JSValue v) { + if (!JS_IsObject (v)) return 0; + + JSValue exec = JS_GetPropertyStr (ctx, v, "exec"); + if (JS_IsException (exec)) return -1; + + int ok = JS_IsFunction (exec); + return ok; +} + +/* text.replace(text, target, replacement, limit) + * + * Return a new text in which the target is replaced by the replacement. + * + * target: string (pattern support not implemented here; non-string => null) + * replacement: string or function(match_text, start_pos) -> string|null + * limit: max number of replacements (default unlimited). Limit includes null + * matches. + * + * Empty target semantics: + * Replace at every boundary: before first char, between chars, after last + * char. Example: replace("abc", "", "-") => "-a-b-c-" Boundaries count toward + * limit even if replacement returns null. + */ + +static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) return JS_NULL; + + if (!JS_IsText (argv[0])) + return JS_NULL; + + int target_is_regex = 0; + { + if (JS_IsText (argv[1])) { + target_is_regex = 0; + } else if (JS_IsObject (argv[1]) && JS_IsRegExp (ctx, argv[1])) { + target_is_regex = 1; + } else { + return JS_NULL; + } + } + + if (!JS_VALUE_IS_TEXT (argv[0])) + return JS_ThrowInternalError (ctx, "Replace must have text in arg0."); + + int len = js_string_value_len (argv[0]); + + int32_t limit = -1; + if (argc > 3 && !JS_IsNull (argv[3])) { + if (JS_ToInt32 (ctx, &limit, argv[3])) { return JS_NULL; } + if (limit < 0) limit = -1; + } + + JSText *b = pretext_init (ctx, len); + if (!b) return JS_EXCEPTION; + + /* Root b across all allocating calls */ + JSGCRef b_ref; + JS_PushGCRef (ctx, &b_ref); + b_ref.val = JS_MKPTR (b); + +/* Macro to re-chase b from GC ref before use */ +#define B_RECHASE() b = (JSText *)chase (b_ref.val) + +/* Macro to update b_ref after b changes */ +#define B_UPDATE(new_b) do { b = (new_b); b_ref.val = JS_MKPTR (b); } while(0) +#define B_CLEANUP() JS_PopGCRef (ctx, &b_ref) + + if (!target_is_regex) { + if (!JS_VALUE_IS_TEXT (argv[1])) { + B_CLEANUP (); + return JS_ThrowInternalError ( + ctx, "Second arg of replace must be pattern or text."); + } + + int t_len = js_string_value_len (argv[1]); + + if (t_len == 0) { + int32_t count = 0; + + for (int boundary = 0; boundary <= len; boundary++) { + if (limit >= 0 && count >= limit) break; + + JSValue match = JS_KEY_empty; + if (JS_IsException (match)) { B_CLEANUP (); goto fail_str_target; } + + JSValue rep = make_replacement (ctx, argc, argv, boundary, match); + if (JS_IsException (rep)) { B_CLEANUP (); goto fail_str_target; } + + count++; + + if (!JS_IsNull (rep)) { + B_RECHASE (); + B_UPDATE (pt_concat_value_to_string_free (ctx, b, rep)); + if (!b) { B_CLEANUP (); goto fail_str_target; } + } + + if (boundary < len) { + JSValue ch = js_sub_string_val (ctx, argv[0], boundary, boundary + 1); + if (JS_IsException (ch)) { B_CLEANUP (); goto fail_str_target; } + B_RECHASE (); + B_UPDATE (pretext_concat_value (ctx, b, ch)); + if (!b) { B_CLEANUP (); goto fail_str_target; } + } + } + + B_RECHASE (); + B_CLEANUP (); + return pretext_end (ctx, b); + } + + int pos = 0; + int32_t count = 0; + + while (pos <= len - t_len && (limit < 0 || count < limit)) { + int found = -1; + + /* Search for pattern using character-by-character comparison */ + for (int i = pos; i <= len - t_len; i++) { + int match = 1; + for (int j = 0; j < t_len; j++) { + if (js_string_value_get (argv[0], i + j) != js_string_value_get (argv[1], j)) { + match = 0; + break; + } + } + if (match) { + found = i; + break; + } + } + if (found < 0) break; + + if (found > pos) { + int sub_len = found - pos; + JSText *sub_str = js_alloc_string (ctx, sub_len); + if (!sub_str) { B_CLEANUP (); goto fail_str_target; } + for (int i = 0; i < sub_len; i++) { + string_put (sub_str, i, js_string_value_get (argv[0], pos + i)); + } + sub_str->length = sub_len; + JSValue sub = pretext_end (ctx, sub_str); + if (JS_IsException (sub)) { B_CLEANUP (); goto fail_str_target; } + B_RECHASE (); + B_UPDATE (pretext_concat_value (ctx, b, sub)); + if (!b) { B_CLEANUP (); goto fail_str_target; } + } + + /* Build match substring manually */ + JSText *match_str = js_alloc_string (ctx, t_len); + if (!match_str) { B_CLEANUP (); goto fail_str_target; } + for (int i = 0; i < t_len; i++) { + string_put (match_str, i, js_string_value_get (argv[0], found + i)); + } + match_str->length = t_len; + JSValue match = pretext_end (ctx, match_str); + if (JS_IsException (match)) { B_CLEANUP (); goto fail_str_target; } + + JSValue rep = make_replacement (ctx, argc, argv, found, match); + if (JS_IsException (rep)) { B_CLEANUP (); goto fail_str_target; } + + count++; + + if (!JS_IsNull (rep)) { + B_RECHASE (); + B_UPDATE (pt_concat_value_to_string_free (ctx, b, rep)); + if (!b) { B_CLEANUP (); goto fail_str_target; } + } + + pos = found + t_len; + } + + if (pos < len) { + int sub_len = len - pos; + JSText *sub_str = js_alloc_string (ctx, sub_len); + if (!sub_str) { B_CLEANUP (); goto fail_str_target; } + for (int i = 0; i < sub_len; i++) { + string_put (sub_str, i, js_string_value_get (argv[0], pos + i)); + } + sub_str->length = sub_len; + JSValue sub = pretext_end (ctx, sub_str); + if (JS_IsException (sub)) { B_CLEANUP (); goto fail_str_target; } + B_RECHASE (); + B_UPDATE (pretext_concat_value (ctx, b, sub)); + if (!b) { B_CLEANUP (); goto fail_str_target; } + } + + B_RECHASE (); + B_CLEANUP (); + return pretext_end (ctx, b); + + fail_str_target: + return JS_EXCEPTION; + } + + /* Regex target - root rx across allocating calls */ + JSGCRef rx_ref; + JS_PushGCRef (ctx, &rx_ref); + rx_ref.val = argv[1]; + +#define RX_CLEANUP() do { JS_PopGCRef (ctx, &rx_ref); B_CLEANUP (); } while(0) +#define RX_VAL (rx_ref.val) + + JSValue orig_last_index = JS_GetPropertyStr (ctx, RX_VAL, "lastIndex"); + if (JS_IsException (orig_last_index)) { RX_CLEANUP (); goto fail_rx; } + int have_orig_last_index = 1; + + int pos = 0; + int32_t count = 0; + + while (pos <= len && (limit < 0 || count < limit)) { + if (JS_SetPropertyStr (ctx, RX_VAL, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) { + RX_CLEANUP (); goto fail_rx; + } + + JSValue sub_str = js_sub_string_val (ctx, argv[0], pos, len); + if (JS_IsException (sub_str)) { RX_CLEANUP (); goto fail_rx; } + + JSValue exec_res + = JS_Invoke (ctx, RX_VAL, JS_KEY_exec, 1, (JSValue *)&sub_str); + if (JS_IsException (exec_res)) { RX_CLEANUP (); goto fail_rx; } + + if (JS_IsNull (exec_res)) { + break; + } + + JSValue idx_val = JS_GetPropertyStr (ctx, exec_res, "index"); + if (JS_IsException (idx_val)) { RX_CLEANUP (); goto fail_rx; } + + int32_t local_index = 0; + if (JS_ToInt32 (ctx, &local_index, idx_val)) { RX_CLEANUP (); goto fail_rx; } + + if (local_index < 0) local_index = 0; + int found = pos + local_index; + if (found < pos) found = pos; + if (found > len) { + break; + } + + JSValue match = JS_GetPropertyStr (ctx, exec_res, "match"); + if (JS_IsException (match)) { RX_CLEANUP (); goto fail_rx; } + + JSValue end_val = JS_GetPropertyStr (ctx, exec_res, "end"); + if (JS_IsException (end_val)) { RX_CLEANUP (); goto fail_rx; } + + int32_t end = 0; + if (JS_ToInt32 (ctx, &end, end_val)) { RX_CLEANUP (); goto fail_rx; } + + int match_len = end - local_index; + if (match_len < 0) match_len = 0; + + if (found > pos) { + JSValue prefix = js_sub_string_val (ctx, argv[0], pos, found); + if (JS_IsException (prefix)) { RX_CLEANUP (); goto fail_rx; } + B_RECHASE (); + B_UPDATE (pretext_concat_value (ctx, b, prefix)); + if (!b) { RX_CLEANUP (); goto fail_rx; } + } + + JSValue rep = make_replacement (ctx, argc, argv, found, match); + if (JS_IsException (rep)) { RX_CLEANUP (); goto fail_rx; } + + count++; + + if (!JS_IsNull (rep)) { + B_RECHASE (); + B_UPDATE (pt_concat_value_to_string_free (ctx, b, rep)); + if (!b) { RX_CLEANUP (); goto fail_rx; } + } + + pos = found + match_len; + if (match_len == 0) { + if (pos < len) + pos++; + else + break; + } + } + + if (pos < len) { + JSValue tail = js_sub_string_val (ctx, argv[0], pos, len); + if (JS_IsException (tail)) { RX_CLEANUP (); goto fail_rx; } + B_RECHASE (); + B_UPDATE (pretext_concat_value (ctx, b, tail)); + if (!b) { RX_CLEANUP (); goto fail_rx; } + } + + if (have_orig_last_index) + JS_SetPropertyStr (ctx, RX_VAL, "lastIndex", orig_last_index); + + B_RECHASE (); + RX_CLEANUP (); + return pretext_end (ctx, b); + +fail_rx: + if (!JS_IsNull (orig_last_index) && !JS_IsException (orig_last_index)) { + JS_SetPropertyStr (ctx, argv[1], "lastIndex", orig_last_index); + } else { + } + return JS_EXCEPTION; +} + +#undef RX_CLEANUP +#undef RX_VAL +#undef B_RECHASE +#undef B_UPDATE +#undef B_CLEANUP + +/* text.search(str, target, from) - find substring or regex match */ +static JSValue js_cell_text_search (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) return JS_NULL; + + if (!JS_IsText (argv[0])) return JS_NULL; + + int target_is_regex = 0; + if (JS_IsText (argv[1])) { + target_is_regex = 0; + } else if (JS_IsObject (argv[1]) && JS_IsRegExp (ctx, argv[1])) { + target_is_regex = 1; + } else { + return JS_NULL; + } + + JSValue str = argv[0]; + int len = js_string_value_len (str); + + int from = 0; + if (argc > 2 && !JS_IsNull (argv[2])) { + if (JS_ToInt32 (ctx, &from, argv[2])) { + return JS_NULL; + } + if (from < 0) from += len; + if (from < 0) from = 0; + } + if (from > len) { + return JS_NULL; + } + + if (!target_is_regex) { + JSValue target = argv[1]; + int t_len = js_string_value_len (target); + + int result = -1; + if (len >= t_len) { + for (int i = from; i <= len - t_len; i++) { + int match = 1; + for (int j = 0; j < t_len; j++) { + if (js_string_value_get (str, i + j) != js_string_value_get (target, j)) { + match = 0; + break; + } + } + if (match) { + result = i; + break; + } + } + } + + if (result == -1) return JS_NULL; + return JS_NewInt32 (ctx, result); + } + + /* Regex target - root rx and str across allocating calls */ + JSGCRef rx_ref, str_ref; + JS_PushGCRef (ctx, &rx_ref); + rx_ref.val = argv[1]; + JS_PushGCRef (ctx, &str_ref); + str_ref.val = str; + +#define SEARCH_CLEANUP() do { JS_PopGCRef (ctx, &str_ref); JS_PopGCRef (ctx, &rx_ref); } while(0) + + JSValue orig_last_index = JS_GetPropertyStr (ctx, rx_ref.val, "lastIndex"); + if (JS_IsException (orig_last_index)) { + SEARCH_CLEANUP (); + return JS_EXCEPTION; + } + int have_orig_last_index = 1; + + if (JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) + goto fail_rx_search; + + JSValue sub_str = js_sub_string_val (ctx, str_ref.val, from, len); + if (JS_IsException (sub_str)) goto fail_rx_search; + + JSValue exec_res = JS_Invoke (ctx, rx_ref.val, JS_KEY_exec, 1, (JSValue *)&sub_str); + if (JS_IsException (exec_res)) goto fail_rx_search; + + if (JS_IsNull (exec_res)) { + if (have_orig_last_index) + JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); + SEARCH_CLEANUP (); + return JS_NULL; + } + + JSValue idx_val = JS_GetPropertyStr (ctx, exec_res, "index"); + if (JS_IsException (idx_val)) { + goto fail_rx_search; + } + + int32_t local_index = 0; + if (JS_ToInt32 (ctx, &local_index, idx_val)) { + goto fail_rx_search; + } + + if (local_index < 0) local_index = 0; + + if (have_orig_last_index) + JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); + + SEARCH_CLEANUP (); + return JS_NewInt32 (ctx, from + local_index); + +fail_rx_search: + if (!JS_IsNull (orig_last_index) && !JS_IsException (orig_last_index)) { + JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); + } + SEARCH_CLEANUP (); + return JS_EXCEPTION; +} +#undef SEARCH_CLEANUP +static inline uint32_t js_str_get (JSText *s, int idx) { + return string_get (s, idx); +} + +static int js_str_find_range (JSText *hay, int from, int to, JSText *needle) { + int nlen = (int)JSText_len (needle); + int hlen = (int)JSText_len (hay); + + if (from < 0) from = 0; + if (to < 0) to = 0; + if (to > hlen) to = hlen; + if (from > to) return -1; + + if (nlen == 0) return from; + if (nlen > (to - from)) return -1; + + int limit = to - nlen; + for (int i = from; i <= limit; i++) { + int j = 0; + for (; j < nlen; j++) { + if (js_str_get (hay, i + j) != js_str_get (needle, j)) break; + } + if (j == nlen) return i; + } + return -1; +} + +/* text_extract(text, pattern, from?, to?) - return array of matches or null + - literal pattern: [match] + - regexp pattern: [full_match, cap1, cap2, ...] +*/ +static JSValue js_cell_text_extract (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) return JS_NULL; + + if (!JS_IsText (argv[0])) + return JS_NULL; + + JSValue str = argv[0]; + int len = js_string_value_len (str); + + int from = 0; + if (argc >= 3 && !JS_IsNull (argv[2])) { + if (JS_ToInt32 (ctx, &from, argv[2])) return JS_EXCEPTION; + if (from < 0) from += len; + if (from < 0) from = 0; + if (from > len) from = len; + } + + int to = len; + if (argc >= 4 && !JS_IsNull (argv[3])) { + if (JS_ToInt32 (ctx, &to, argv[3])) return JS_EXCEPTION; + if (to < 0) to += len; + if (to < 0) to = 0; + if (to > len) to = len; + } + + if (from > to) return JS_NULL; + + /* RegExp path: convert new exec result record -> classic array */ + if (JS_IsObject (argv[1]) && JS_IsRegExp (ctx, argv[1])) { + /* Root rx, str, out across allocating calls */ + JSGCRef rx_ref, str_ref, out_ref; + JS_PushGCRef (ctx, &rx_ref); + rx_ref.val = argv[1]; + JS_PushGCRef (ctx, &str_ref); + str_ref.val = str; + JS_PushGCRef (ctx, &out_ref); + out_ref.val = JS_NULL; + +#define EXT_CLEANUP() do { JS_PopGCRef (ctx, &out_ref); JS_PopGCRef (ctx, &str_ref); JS_PopGCRef (ctx, &rx_ref); } while(0) + + JSValue orig_last_index = JS_GetPropertyStr (ctx, rx_ref.val, "lastIndex"); + if (JS_IsException (orig_last_index)) { EXT_CLEANUP (); return JS_EXCEPTION; } + + if (JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) + goto fail_rx; + + JSValue sub_str; + if (from == 0 && to == len) { + sub_str = str_ref.val; + } else { + sub_str = js_sub_string_val (ctx, str_ref.val, from, to); + if (JS_IsException (sub_str)) goto fail_rx; + } + + JSGCRef exec_ref; + JS_PushGCRef (ctx, &exec_ref); + exec_ref.val = JS_Invoke (ctx, rx_ref.val, JS_KEY_exec, 1, (JSValue *)&sub_str); + if (JS_IsException (exec_ref.val)) { JS_PopGCRef (ctx, &exec_ref); goto fail_rx; } + + if (JS_IsNull (exec_ref.val)) { + JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); + JS_PopGCRef (ctx, &exec_ref); + EXT_CLEANUP (); + return JS_NULL; + } + + /* Build result array */ + JSValue out = JS_NewArray (ctx); + if (JS_IsException (out)) { + JS_PopGCRef (ctx, &exec_ref); + goto fail_rx; + } + out_ref.val = out; + + /* out[0] = exec_res.match */ + JSValue match0 = JS_GetPropertyStr (ctx, exec_ref.val, "match"); + if (JS_IsException (match0)) { + JS_PopGCRef (ctx, &exec_ref); + goto fail_rx; + } + out = out_ref.val; + if (JS_SetPropertyUint32 (ctx, out, 0, match0) < 0) { + JS_PopGCRef (ctx, &exec_ref); + goto fail_rx; + } + + /* Append capture groups from exec_res.captures */ + JSValue caps = JS_GetPropertyStr (ctx, exec_ref.val, "captures"); + JS_PopGCRef (ctx, &exec_ref); /* exec_ref no longer needed */ + if (!JS_IsException (caps) && JS_IsArray (caps)) { + int64_t caps_len = 0; + if (js_get_length64 (ctx, &caps_len, caps) == 0 && caps_len > 0) { + for (int64_t i = 0; i < caps_len; i++) { + JSValue cap = JS_GetPropertyInt64 (ctx, caps, i); + if (JS_IsException (cap)) { + goto fail_rx; + } + out = out_ref.val; + if (JS_SetPropertyInt64 (ctx, out, i + 1, cap) < 0) { + goto fail_rx; + } + } + } + } + + JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); + out = out_ref.val; + EXT_CLEANUP (); + return out; + + fail_rx: + if (!JS_IsNull (orig_last_index) && !JS_IsException (orig_last_index)) { + JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); + } + EXT_CLEANUP (); + return JS_EXCEPTION; + } +#undef EXT_CLEANUP + + /* Literal text path */ + JSValue needle_val = JS_ToString (ctx, argv[1]); + if (JS_IsException (needle_val)) return JS_EXCEPTION; + str = argv[0]; /* refresh after potential GC */ + + int needle_len = js_string_value_len (needle_val); + + /* Find needle in str[from..to) */ + int pos = -1; + if (needle_len == 0) { + pos = from; + } else if (needle_len <= (to - from)) { + int limit = to - needle_len; + for (int i = from; i <= limit; i++) { + int j = 0; + for (; j < needle_len; j++) { + if (js_string_value_get (str, i + j) != js_string_value_get (needle_val, j)) + break; + } + if (j == needle_len) { pos = i; break; } + } + } + + if (pos < 0) return JS_NULL; + + JSGCRef arr_ref; + JS_PushGCRef (ctx, &arr_ref); + arr_ref.val = JS_NewArrayLen (ctx, 1); + if (JS_IsException (arr_ref.val)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + str = argv[0]; /* refresh after potential GC */ + + JSValue match = js_sub_string_val (ctx, str, pos, pos + needle_len); + if (JS_IsException (match)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + + JSValue arr = arr_ref.val; + JS_PopGCRef (ctx, &arr_ref); + + if (JS_SetPropertyUint32 (ctx, arr, 0, match) < 0) + return JS_EXCEPTION; + + return arr; +} + +/* format(text, collection, transformer) - string interpolation + * Finds {name} or {name:format} patterns and substitutes from collection. + * Collection can be array (index by number) or record (index by key). + * Transformer can be function(value, format) or record of functions. + */ +static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) return JS_NULL; + if (!JS_IsText (argv[0])) return JS_NULL; + + JSValue text_val = argv[0]; + JSValue collection = argv[1]; + JSValue transformer = argc > 2 ? argv[2] : JS_NULL; + + int is_array = JS_IsArray (collection); + int is_record = JS_IsRecord (collection); + if (!is_array && !is_record) return JS_NULL; + + int len = js_string_value_len (text_val); + + /* Root text_val, collection, transformer BEFORE any allocation */ + JSGCRef res_ref, text_ref, coll_ref, xform_ref; + JS_PushGCRef (ctx, &res_ref); + JS_PushGCRef (ctx, &text_ref); + JS_PushGCRef (ctx, &coll_ref); + JS_PushGCRef (ctx, &xform_ref); + res_ref.val = JS_NULL; + text_ref.val = text_val; + coll_ref.val = collection; + xform_ref.val = transformer; + +#define FMT_CLEANUP() do { \ + JS_PopGCRef (ctx, &xform_ref); \ + JS_PopGCRef (ctx, &coll_ref); \ + JS_PopGCRef (ctx, &text_ref); \ + JS_PopGCRef (ctx, &res_ref); \ +} while(0) + + JSText *result = pretext_init (ctx, len); + if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } + res_ref.val = JS_MKPTR (result); + + int pos = 0; + while (pos < len) { + text_val = text_ref.val; + /* Find next '{' */ + int brace_start = -1; + for (int i = pos; i < len; i++) { + if (js_string_value_get (text_val, i) == '{') { + brace_start = i; + break; + } + } + + if (brace_start < 0) { + /* No more braces, copy rest of string */ + JSValue tail = js_sub_string_val (ctx, text_ref.val, pos, len); + if (JS_IsException (tail)) { FMT_CLEANUP(); return JS_EXCEPTION; } + result = (JSText *)chase (res_ref.val); + result = pretext_concat_value (ctx, result, tail); + if (result) res_ref.val = JS_MKPTR (result); + break; + } + + /* Copy text before brace */ + if (brace_start > pos) { + JSValue prefix = js_sub_string_val (ctx, text_ref.val, pos, brace_start); + if (JS_IsException (prefix)) { FMT_CLEANUP(); return JS_EXCEPTION; } + result = (JSText *)chase (res_ref.val); + result = pretext_concat_value (ctx, result, prefix); + if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } + res_ref.val = JS_MKPTR (result); + } + + /* Find closing '}' */ + text_val = text_ref.val; + int brace_end = -1; + for (int i = brace_start + 1; i < len; i++) { + if (js_string_value_get (text_val, i) == '}') { + brace_end = i; + break; + } + } + + if (brace_end < 0) { + /* No closing brace, copy '{' and continue */ + JSValue ch = js_sub_string_val (ctx, text_ref.val, brace_start, brace_start + 1); + if (JS_IsException (ch)) { FMT_CLEANUP(); return JS_EXCEPTION; } + result = (JSText *)chase (res_ref.val); + result = pretext_concat_value (ctx, result, ch); + if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } + res_ref.val = JS_MKPTR (result); + pos = brace_start + 1; + continue; + } + + /* Extract content between braces */ + JSValue middle = js_sub_string_val (ctx, text_ref.val, brace_start + 1, brace_end); + if (JS_IsException (middle)) { FMT_CLEANUP(); return JS_EXCEPTION; } + + /* Split on ':' to get name and format_spec */ + int middle_len = js_string_value_len (middle); + + int colon_pos = -1; + for (int i = 0; i < middle_len; i++) { + if (js_string_value_get (middle, i) == ':') { + colon_pos = i; + break; + } + } + + JSValue name_val, format_spec; + if (colon_pos >= 0) { + name_val = js_sub_string_val (ctx, middle, 0, colon_pos); + format_spec = js_sub_string_val (ctx, middle, colon_pos + 1, middle_len); + } else { + name_val = middle; + format_spec = JS_KEY_empty; + } + + /* Get value from collection — protect with GCRef since JS_Call below can trigger GC */ + JSGCRef cv_ref; + JS_PushGCRef (ctx, &cv_ref); + cv_ref.val = JS_NULL; + if (is_array) { + int name_len = js_string_value_len (name_val); + int32_t idx = 0; + int valid = (name_len > 0); + for (int ni = 0; ni < name_len && valid; ni++) { + uint32_t ch = js_string_value_get (name_val, ni); + if (ch >= '0' && ch <= '9') + idx = idx * 10 + (ch - '0'); + else + valid = 0; + } + if (valid && idx >= 0) { + cv_ref.val = JS_GetPropertyUint32 (ctx, coll_ref.val, (uint32_t)idx); + } + } else { + cv_ref.val = JS_GetProperty (ctx, coll_ref.val, name_val); + } + + /* Try to get substitution */ + JSValue substitution = JS_NULL; + int made_substitution = 0; + + if (!JS_IsNull (xform_ref.val)) { + if (JS_IsFunction (xform_ref.val)) { + JSValue args[2] = { cv_ref.val, format_spec }; + JSValue result_val = JS_Call (ctx, xform_ref.val, JS_NULL, 2, args); + if (JS_IsText (result_val)) { + substitution = result_val; + made_substitution = 1; + } + } else if (JS_IsRecord (xform_ref.val)) { + JSValue func = JS_GetProperty (ctx, xform_ref.val, format_spec); + if (JS_IsFunction (func)) { + JSValue result_val = JS_Call (ctx, func, JS_NULL, 1, &cv_ref.val); + if (JS_IsText (result_val)) { + substitution = result_val; + made_substitution = 1; + } + } + } + } + + if (!made_substitution && JS_IsNumber (cv_ref.val) && !JS_IsNull (format_spec)) { + JSValue text_method = JS_GetPropertyStr (ctx, cv_ref.val, "text"); + if (JS_IsFunction (text_method)) { + JSValue result_val = JS_Call (ctx, text_method, cv_ref.val, 1, &format_spec); + if (JS_IsText (result_val)) { + substitution = result_val; + made_substitution = 1; + } + } + } + + if (!made_substitution && !JS_IsNull (cv_ref.val)) { + JSValue conv_text_val = JS_ToString (ctx, cv_ref.val); + if (JS_IsText (conv_text_val)) { + substitution = conv_text_val; + made_substitution = 1; + } + } + JS_PopGCRef (ctx, &cv_ref); + + result = (JSText *)chase (res_ref.val); + if (made_substitution) { + result = pretext_concat_value (ctx, result, substitution); + if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } + res_ref.val = JS_MKPTR (result); + } else { + JSValue orig = js_sub_string_val (ctx, text_ref.val, brace_start, brace_end + 1); + if (JS_IsException (orig)) { FMT_CLEANUP(); return JS_EXCEPTION; } + result = (JSText *)chase (res_ref.val); + result = pretext_concat_value (ctx, result, orig); + if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } + res_ref.val = JS_MKPTR (result); + } + + pos = brace_end + 1; + } + + result = (JSText *)chase (res_ref.val); + FMT_CLEANUP(); +#undef FMT_CLEANUP + return pretext_end (ctx, result); +} + +/* print(args...) - print arguments to stdout */ +static JSValue js_print (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + for (int i = 0; i < argc; i++) { + const char *str = JS_ToCString (ctx, argv[i]); + if (str) { + fputs (str, stdout); + JS_FreeCString (ctx, str); + } + if (i < argc - 1) fputc (' ', stdout); + } + fputc ('\n', stdout); + return JS_NULL; +} + +static JSValue js_stacktrace (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + cJSON *stack = JS_GetStack(ctx); + if (stack) { + int n = cJSON_GetArraySize(stack); + for (int i = 0; i < n; i++) { + cJSON *fr = cJSON_GetArrayItem(stack, i); + const char *fn = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fr, "function")); + const char *file = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fr, "file")); + int line = (int)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(fr, "line")); + int col = (int)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(fr, "column")); + printf(" at %s (%s:%d:%d)\n", fn ? fn : "", file ? file : "", line, col); + } + cJSON_Delete(stack); + } + return JS_NULL; +} + +/* ---------------------------------------------------------------------------- + * Bytecode dump function (always available, for debugging) + * ---------------------------------------------------------------------------- + */ + +/* Opcode names for bytecode dump */ +static const char *dump_opcode_names[] = { +#define FMT(f) +#define DEF(id, size, n_pop, n_push, f) #id, +#define def(id, size, n_pop, n_push, f) +#include "quickjs-opcode.h" +#undef def +#undef DEF +#undef FMT +}; + +static void dump_bytecode_opcodes (JSContext *ctx, JSFunctionBytecode *b) { + const uint8_t *tab = b->byte_code_buf; + int len = b->byte_code_len; + const JSValue *cpool = b->cpool; + uint32_t cpool_count = b->cpool_count; + const JSVarDef *vars = b->vardefs ? b->vardefs + b->arg_count : NULL; + int var_count = b->var_count; + int pos = 0; + + while (pos < len) { + int op = tab[pos]; + if (op >= OP_COUNT) { + printf (" %5d: \n", pos, op); + pos++; + continue; + } + const JSOpCode *oi = &short_opcode_info (op); + int size = oi->size; + if (pos + size > len) { + printf (" %5d: \n", pos, op); + break; + } + + printf (" %5d: %s", pos, dump_opcode_names[op]); + pos++; + + switch (oi->fmt) { + case OP_FMT_none_int: + printf (" %d", op - OP_push_0); + break; + case OP_FMT_npopx: + printf (" %d", op - OP_call0); + break; + case OP_FMT_u8: + printf (" %u", get_u8 (tab + pos)); + break; + case OP_FMT_i8: + printf (" %d", get_i8 (tab + pos)); + break; + case OP_FMT_u16: + case OP_FMT_npop: + printf (" %u", get_u16 (tab + pos)); + break; + case OP_FMT_npop_u16: + printf (" %u,%u", get_u16 (tab + pos), get_u16 (tab + pos + 2)); + break; + case OP_FMT_i16: + printf (" %d", get_i16 (tab + pos)); + break; + case OP_FMT_i32: + printf (" %d", get_i32 (tab + pos)); + break; + case OP_FMT_u32: + printf (" %u", get_u32 (tab + pos)); + break; + case OP_FMT_label8: + printf (" ->%d", pos + get_i8 (tab + pos)); + break; + case OP_FMT_label16: + printf (" ->%d", pos + get_i16 (tab + pos)); + break; + case OP_FMT_label: + printf (" ->%u", pos + get_u32 (tab + pos)); + break; + case OP_FMT_label_u16: + printf (" ->%u,%u", pos + get_u32 (tab + pos), get_u16 (tab + pos + 4)); + break; + case OP_FMT_const8: { + uint32_t idx = get_u8 (tab + pos); + printf (" [%u]", idx); + if (idx < cpool_count) { + printf (": "); + JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[idx], NULL); + } + break; + } + case OP_FMT_const: { + uint32_t idx = get_u32 (tab + pos); + printf (" [%u]", idx); + if (idx < cpool_count) { + printf (": "); + JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[idx], NULL); + } + break; + } + case OP_FMT_key: { + uint32_t idx = get_u32 (tab + pos); + printf (" [%u]", idx); + if (idx < cpool_count) { + printf (": "); + JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[idx], NULL); + } + break; + } + case OP_FMT_key_u8: { + uint32_t idx = get_u32 (tab + pos); + printf (" [%u],%d", idx, get_u8 (tab + pos + 4)); + if (idx < cpool_count) { + printf (": "); + JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[idx], NULL); + } + break; + } + case OP_FMT_key_u16: { + uint32_t idx = get_u32 (tab + pos); + printf (" [%u],%d", idx, get_u16 (tab + pos + 4)); + if (idx < cpool_count) { + printf (": "); + JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[idx], NULL); + } + break; + } + case OP_FMT_none_loc: + printf (" loc%d", (op - OP_get_loc0) % 4); + break; + case OP_FMT_loc8: { + int idx = get_u8 (tab + pos); + printf (" loc%d", idx); + if (vars && idx < var_count) { + char buf[KEY_GET_STR_BUF_SIZE]; + printf (": %s", JS_KeyGetStr (ctx, buf, sizeof(buf), vars[idx].var_name)); + } + break; + } + case OP_FMT_loc: { + int idx = get_u16 (tab + pos); + printf (" loc%d", idx); + if (vars && idx < var_count) { + char buf[KEY_GET_STR_BUF_SIZE]; + printf (": %s", JS_KeyGetStr (ctx, buf, sizeof(buf), vars[idx].var_name)); + } + break; + } + case OP_FMT_none_arg: + printf (" arg%d", (op - OP_get_arg0) % 4); + break; + case OP_FMT_arg: + printf (" arg%d", get_u16 (tab + pos)); + break; + default: + break; + } + printf ("\n"); + pos += size - 1; /* -1 because we already incremented pos after reading opcode */ + } +} + +void JS_DumpFunctionBytecode (JSContext *ctx, JSValue func_val) { + JSFunctionBytecode *b = NULL; + + if (!JS_IsPtr (func_val)) { + printf ("JS_DumpFunctionBytecode: not a pointer value\n"); + return; + } + + /* Get the object header to check type */ + void *ptr = JS_VALUE_GET_PTR (func_val); + objhdr_t hdr = *(objhdr_t *)ptr; + uint8_t type = objhdr_type (hdr); + + if (type == OBJ_FUNCTION) { + /* It's a JSFunction - extract bytecode */ + JSFunction *fn = (JSFunction *)ptr; + if (fn->kind != JS_FUNC_KIND_BYTECODE) { + printf ("JS_DumpFunctionBytecode: not a bytecode function (kind=%d)\n", fn->kind); + return; + } + b = fn->u.func.function_bytecode; + } else if (type == OBJ_CODE) { + /* It's raw bytecode from js_create_function */ + b = (JSFunctionBytecode *)ptr; + } else { + printf ("JS_DumpFunctionBytecode: not a function or bytecode (type=%d)\n", type); + return; + } + + if (!b) { + printf ("JS_DumpFunctionBytecode: no bytecode\n"); + return; + } + + char buf[KEY_GET_STR_BUF_SIZE]; + + printf ("=== Bytecode Dump ===\n"); + + /* Function name */ + const char *fname = JS_KeyGetStr (ctx, buf, sizeof(buf), b->func_name); + printf ("Function: %s\n", fname ? fname : ""); + + /* Debug info */ + if (b->has_debug && !JS_IsNull (b->debug.filename)) { + printf ("File: %s\n", JS_KeyGetStr (ctx, buf, sizeof(buf), b->debug.filename)); + } + + /* Basic stats */ + printf ("Args: %d, Vars: %d, Stack: %d\n", b->arg_count, b->var_count, b->stack_size); + printf ("Bytecode length: %d bytes\n", b->byte_code_len); + + /* Arguments */ + if (b->arg_count > 0 && b->vardefs) { + printf ("\nArguments:\n"); + for (int i = 0; i < b->arg_count; i++) { + printf (" %d: %s\n", i, JS_KeyGetStr (ctx, buf, sizeof(buf), b->vardefs[i].var_name)); + } + } + + /* Local variables */ + if (b->var_count > 0 && b->vardefs) { + printf ("\nLocal variables:\n"); + for (int i = 0; i < b->var_count; i++) { + JSVarDef *vd = &b->vardefs[b->arg_count + i]; + const char *kind = vd->is_const ? "const" : vd->is_lexical ? "let" : "var"; + printf (" %d: %s %s", i, kind, JS_KeyGetStr (ctx, buf, sizeof(buf), vd->var_name)); + if (vd->scope_level) printf (" [scope:%d]", vd->scope_level); + printf ("\n"); + } + } + + /* Closure variables */ + if (b->closure_var_count > 0) { + printf ("\nClosure variables:\n"); + for (int i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cv = &b->closure_var[i]; + printf (" %d: %s (%s:%s%d)\n", i, + JS_KeyGetStr (ctx, buf, sizeof(buf), cv->var_name), + cv->is_local ? "local" : "parent", + cv->is_arg ? "arg" : "loc", + cv->var_idx); + } + } + + /* Constant pool */ + if (b->cpool_count > 0) { + printf ("\nConstant pool (%d entries):\n", b->cpool_count); + for (uint32_t i = 0; i < b->cpool_count; i++) { + printf (" [%u]: ", i); + JS_PrintValue (ctx, js_dump_value_write, stdout, b->cpool[i], NULL); + printf ("\n"); + } + } + + /* Bytecode instructions */ + printf ("\nBytecode:\n"); + dump_bytecode_opcodes (ctx, b); + + printf ("=== End Bytecode Dump ===\n"); +} + +/* ---------------------------------------------------------------------------- + * array function and sub-functions + * ---------------------------------------------------------------------------- + */ + +/* array(arg, arg2, arg3, arg4) - main array function */ +static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + (void)this_val; + if (argc < 1) return JS_NULL; + + JSValue arg = argv[0]; + + /* array(number) - create array of size */ + /* array(number, initial_value) - create array with initial values */ + if (JS_IsNumber (arg)) { + if (!JS_IsInteger (arg)) + return JS_ThrowTypeError (ctx, "Array expected an integer."); + + int len = JS_VALUE_GET_INT (arg); + if (len < 0) return JS_NULL; + + JSGCRef result_ref; + JSValue result = JS_NewArrayLen (ctx, len); + if (JS_IsException (result)) return result; + + if (argc > 1 && JS_IsFunction (argv[1])) { + /* Fill with function results - GC-safe */ + JSGCRef func_ref; + JS_PushGCRef (ctx, &func_ref); + func_ref.val = argv[1]; + int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func_ref.val))->length; + + if (arity >= 1) { + for (int i = 0; i < len; i++) { + JSValue idx_arg = JS_NewInt32 (ctx, i); + JS_PUSH_VALUE (ctx, result); + JSValue val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &idx_arg, 0); + JS_POP_VALUE (ctx, result); + if (JS_IsException (val)) { JS_PopGCRef (ctx, &func_ref); return JS_EXCEPTION; } + JSArray *out = JS_VALUE_GET_ARRAY (result); /* re-chase after call */ + out->values[i] = val; + } + } else { + for (int i = 0; i < len; i++) { + JS_PUSH_VALUE (ctx, result); + JSValue val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 0, NULL, 0); + JS_POP_VALUE (ctx, result); + if (JS_IsException (val)) { JS_PopGCRef (ctx, &func_ref); return JS_EXCEPTION; } + JSArray *out = JS_VALUE_GET_ARRAY (result); /* re-chase after call */ + out->values[i] = val; + } + } + JS_PopGCRef (ctx, &func_ref); + } else if (argc > 1) { + /* Fill with value */ + JSArray *out = JS_VALUE_GET_ARRAY (result); + for (int i = 0; i < len; i++) + out->values[i] = argv[1]; + } + return result; + } + + /* array(array) - copy */ + /* array(array, function) - map */ + /* array(array, another_array) - concat */ + /* array(array, from, to) - slice */ + if (JS_IsArray (arg)) { + /* Root input array and arg1 for GC safety in this section */ + JSGCRef arg0_ref, arg1_ref; + JS_PushGCRef (ctx, &arg0_ref); + JS_PushGCRef (ctx, &arg1_ref); + arg0_ref.val = argv[0]; + arg1_ref.val = argc > 1 ? argv[1] : JS_NULL; + + JSArray *arr = JS_VALUE_GET_ARRAY (arg0_ref.val); + int len = arr->len; + + if (argc < 2 || JS_IsNull (argv[1])) { + /* Copy */ + JSValue result = JS_NewArrayLen (ctx, len); + if (JS_IsException (result)) { + JS_PopGCRef (ctx, &arg1_ref); + JS_PopGCRef (ctx, &arg0_ref); + return result; + } + arr = JS_VALUE_GET_ARRAY (arg0_ref.val); /* re-chase after allocation */ + JSArray *out = JS_VALUE_GET_ARRAY (result); + for (int i = 0; i < len; i++) { + out->values[i] = arr->values[i]; + } + out->len = len; + JS_PopGCRef (ctx, &arg1_ref); + JS_PopGCRef (ctx, &arg0_ref); + return result; + } + + if (JS_IsFunction (arg1_ref.val)) { + /* Map - GC-safe: root result throughout, use rooted refs for func and array */ + int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (arg1_ref.val))->length; + + int reverse = argc > 2 && JS_ToBool (ctx, argv[2]); + JSValue exit_val = argc > 3 ? argv[3] : JS_NULL; + + JSGCRef result_ref; + JS_PushGCRef (ctx, &result_ref); /* Push first - sets val to JS_NULL */ + result_ref.val = JS_NewArray (ctx); /* Then assign */ + if (JS_IsException (result_ref.val)) { + JS_PopGCRef (ctx, &result_ref); + JS_PopGCRef (ctx, &arg1_ref); + JS_PopGCRef (ctx, &arg0_ref); + return result_ref.val; + } + + if (arity >= 2) { + if (reverse) { + for (int i = len - 1; i >= 0; i--) { + /* Re-chase input array each iteration */ + arr = JS_VALUE_GET_ARRAY (arg0_ref.val); + if (i >= (int)arr->len) continue; /* array may have shrunk */ + JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, i) }; + JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 2, args, 0); + if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } + if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break; + if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } + } + } else { + for (int i = 0; i < len; i++) { + arr = JS_VALUE_GET_ARRAY (arg0_ref.val); + if (i >= (int)arr->len) break; + JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, i) }; + JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 2, args, 0); + if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } + if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break; + if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } + } + } + } else { + if (reverse) { + for (int i = len - 1; i >= 0; i--) { + arr = JS_VALUE_GET_ARRAY (arg0_ref.val); + if (i >= (int)arr->len) continue; + JSValue item = arr->values[i]; + JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 1, &item, 0); + if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } + if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break; + if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } + } + } else { + for (int i = 0; i < len; i++) { + arr = JS_VALUE_GET_ARRAY (arg0_ref.val); + if (i >= (int)arr->len) break; + JSValue item = arr->values[i]; + JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 1, &item, 0); + if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } + if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break; + if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } + } + } + } + JSValue result = result_ref.val; + JS_PopGCRef (ctx, &result_ref); + JS_PopGCRef (ctx, &arg1_ref); + JS_PopGCRef (ctx, &arg0_ref); + return result; + } + + if (JS_IsArray (arg1_ref.val)) { + /* Concat */ + JSArray *arr2 = JS_VALUE_GET_ARRAY (arg1_ref.val); + int len2 = arr2->len; + + JSValue result = JS_NewArrayLen (ctx, len + len2); + if (JS_IsException (result)) { + JS_PopGCRef (ctx, &arg1_ref); + JS_PopGCRef (ctx, &arg0_ref); + return result; + } + /* Re-chase arrays after allocation */ + arr = JS_VALUE_GET_ARRAY (arg0_ref.val); + arr2 = JS_VALUE_GET_ARRAY (arg1_ref.val); + JSArray *out = JS_VALUE_GET_ARRAY (result); + + for (int i = 0; i < len; i++) { + out->values[i] = arr->values[i]; + } + for (int i = 0; i < len2; i++) { + out->values[len + i] = arr2->values[i]; + } + out->len = len + len2; + JS_PopGCRef (ctx, &arg1_ref); + JS_PopGCRef (ctx, &arg0_ref); + return result; + } + + if (JS_IsNumber (argv[1])) { + /* Slice */ + if (!JS_IsInteger (argv[1])) { + JS_PopGCRef (ctx, &arg1_ref); + JS_PopGCRef (ctx, &arg0_ref); + return JS_NULL; + } + int from = JS_VALUE_GET_INT (argv[1]); + int to; + if (argc > 2 && !JS_IsNull (argv[2])) { + if (!JS_IsNumber (argv[2]) || !JS_IsInteger (argv[2])) { + JS_PopGCRef (ctx, &arg1_ref); + JS_PopGCRef (ctx, &arg0_ref); + return JS_NULL; + } + to = JS_VALUE_GET_INT (argv[2]); + } else { + to = len; + } + + if (from < 0) from += len; + if (to < 0) to += len; + if (from < 0 || from > len || to < 0 || to > len || from > to) { + JS_PopGCRef (ctx, &arg1_ref); + JS_PopGCRef (ctx, &arg0_ref); + return JS_NULL; + } + + int slice_len = to - from; + JSValue result = JS_NewArrayLen (ctx, slice_len); + if (JS_IsException (result)) { + JS_PopGCRef (ctx, &arg1_ref); + JS_PopGCRef (ctx, &arg0_ref); + return result; + } + /* Re-chase arrays after allocation */ + arr = JS_VALUE_GET_ARRAY (arg0_ref.val); + JSArray *out = JS_VALUE_GET_ARRAY (result); + + for (int i = 0; i < slice_len; i++) { + out->values[i] = arr->values[from + i]; + } + out->len = slice_len; + JS_PopGCRef (ctx, &arg1_ref); + JS_PopGCRef (ctx, &arg0_ref); + return result; + } + + JS_PopGCRef (ctx, &arg1_ref); + JS_PopGCRef (ctx, &arg0_ref); + return JS_NULL; + } + + /* array(object) - keys */ + if (JS_IsRecord (arg)) { + /* Return object keys */ + return JS_GetOwnPropertyNames (ctx, arg); + } + + /* array(text) - split into characters */ + /* array(text, separator) - split by separator */ + /* array(text, length) - dice into chunks */ + if (JS_VALUE_IS_TEXT (arg)) { + int len = js_string_value_len (arg); + + if (argc < 2 || JS_IsNull (argv[1])) { + /* Split into characters */ + JSValue result = JS_NewArrayLen (ctx, len); + if (JS_IsException (result)) { return result; } + JSArray *out = JS_VALUE_GET_ARRAY (result); + for (int i = 0; i < len; i++) { + JSValue ch = js_sub_string_val (ctx, arg, i, i + 1); + if (JS_IsException (ch)) { + return JS_EXCEPTION; + } + out->values[i] = ch; + } + out->len = len; + return result; + } + + if (JS_VALUE_IS_TEXT (argv[1])) { + /* Split by separator */ + const char *cstr = JS_ToCString (ctx, arg); + const char *sep = JS_ToCString (ctx, argv[1]); + if (!cstr || !sep) { + if (cstr) JS_FreeCString (ctx, cstr); + if (sep) JS_FreeCString (ctx, sep); + return JS_EXCEPTION; + } + + size_t sep_len = strlen (sep); + + /* Count the number of parts first */ + int64_t count = 0; + if (sep_len == 0) { + count = len; + } else { + const char *pos = cstr; + const char *found; + count = 1; + while ((found = strstr (pos, sep)) != NULL) { + count++; + pos = found + sep_len; + } + } + + JSValue result = JS_NewArrayLen (ctx, count); + if (JS_IsException (result)) { + JS_FreeCString (ctx, cstr); + JS_FreeCString (ctx, sep); + return result; + } + + int64_t idx = 0; + const char *pos = cstr; + const char *found; + + if (sep_len == 0) { + for (int i = 0; i < len; i++) { + JSValue ch = js_sub_string_val (ctx, arg, i, i + 1); + JS_SetPropertyInt64 (ctx, result, idx++, ch); + } + } else { + while ((found = strstr (pos, sep)) != NULL) { + JSValue part = JS_NewStringLen (ctx, pos, found - pos); + JS_SetPropertyInt64 (ctx, result, idx++, part); + pos = found + sep_len; + } + JSValue part = JS_NewString (ctx, pos); + JS_SetPropertyInt64 (ctx, result, idx++, part); + } + + JS_FreeCString (ctx, cstr); + JS_FreeCString (ctx, sep); + return result; + } + + if (JS_IsObject (argv[1]) && JS_IsRegExp (ctx, argv[1])) { + /* Split by regex (manual "global" iteration; ignore g flag semantics) */ + /* Root rx, result, arg across allocating calls */ + JSGCRef rx_ref, res_ref, arg_ref; + JS_PushGCRef (ctx, &rx_ref); + rx_ref.val = argv[1]; + JS_PushGCRef (ctx, &res_ref); + res_ref.val = JS_NULL; + JS_PushGCRef (ctx, &arg_ref); + arg_ref.val = arg; + +#define RXS_CLEANUP() do { JS_PopGCRef (ctx, &arg_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &rx_ref); } while(0) + + JSValue result = JS_NewArray (ctx); + if (JS_IsException (result)) { RXS_CLEANUP (); return result; } + res_ref.val = result; + + /* Save & restore lastIndex to avoid mutating caller-visible state */ + JSValue orig_last_index = JS_GetPropertyStr (ctx, rx_ref.val, "lastIndex"); + if (JS_IsException (orig_last_index)) { + RXS_CLEANUP (); + return JS_EXCEPTION; + } + + int pos = 0; + int64_t out_idx = 0; + + while (pos <= len) { + if (JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) + goto fail_rx_split; + + JSValue sub_str = js_sub_string_val (ctx, arg_ref.val, pos, len); + if (JS_IsException (sub_str)) goto fail_rx_split; + + JSValue exec_res + = JS_Invoke (ctx, rx_ref.val, JS_KEY_exec, 1, (JSValue *)&sub_str); + if (JS_IsException (exec_res)) goto fail_rx_split; + + if (JS_IsNull (exec_res)) { + JSValue tail = js_sub_string_val (ctx, arg_ref.val, pos, len); + if (JS_IsException (tail)) goto fail_rx_split; + result = res_ref.val; + if (JS_ArrayPush (ctx, &result, tail) < 0) { res_ref.val = result; goto fail_rx_split; } + res_ref.val = result; + break; + } + + JSValue idx_val = JS_GetPropertyStr (ctx, exec_res, "index"); + if (JS_IsException (idx_val)) goto fail_rx_split; + + int32_t local_index = 0; + if (JS_ToInt32 (ctx, &local_index, idx_val)) goto fail_rx_split; + if (local_index < 0) local_index = 0; + + int found = pos + local_index; + if (found < pos) found = pos; + if (found > len) { + JSValue tail = js_sub_string_val (ctx, arg_ref.val, pos, len); + if (JS_IsException (tail)) goto fail_rx_split; + result = res_ref.val; + JS_SetPropertyInt64 (ctx, result, out_idx++, tail); + break; + } + + JSValue end_val = JS_GetPropertyStr (ctx, exec_res, "end"); + if (JS_IsException (end_val)) goto fail_rx_split; + + int32_t end = 0; + if (JS_ToInt32 (ctx, &end, end_val)) goto fail_rx_split; + + int match_len = end - local_index; + if (match_len < 0) match_len = 0; + + JSValue part = js_sub_string_val (ctx, arg_ref.val, pos, found); + if (JS_IsException (part)) goto fail_rx_split; + result = res_ref.val; + if (JS_ArrayPush (ctx, &result, part) < 0) { res_ref.val = result; goto fail_rx_split; } + res_ref.val = result; + + pos = found + match_len; + if (match_len == 0) { + if (found >= len) { + JSValue empty = JS_NewStringLen (ctx, "", 0); + if (JS_IsException (empty)) goto fail_rx_split; + result = res_ref.val; + if (JS_ArrayPush (ctx, &result, empty) < 0) { res_ref.val = result; goto fail_rx_split; } + res_ref.val = result; + break; + } + pos = found + 1; + } + } + + JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); + result = res_ref.val; + RXS_CLEANUP (); + return result; + + fail_rx_split: + if (!JS_IsException (orig_last_index)) { + JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); + } + RXS_CLEANUP (); + return JS_EXCEPTION; + } +#undef RXS_CLEANUP + + if (JS_VALUE_IS_NUMBER (argv[1])) { + /* Dice into chunks */ + int chunk_len; + if (JS_ToInt32 (ctx, &chunk_len, argv[1])) + return JS_NULL; + if (chunk_len <= 0) + return JS_NULL; + + int64_t count = (len + chunk_len - 1) / chunk_len; + JSValue result = JS_NewArrayLen (ctx, count); + if (JS_IsException (result)) + return result; + + int64_t idx = 0; + for (int i = 0; i < len; i += chunk_len) { + int end = i + chunk_len; + if (end > len) end = len; + JSValue chunk = js_sub_string_val (ctx, arg, i, end); + if (JS_IsException (chunk)) + return JS_EXCEPTION; + JS_SetPropertyInt64 (ctx, result, idx++, chunk); + } + + return result; + } + + return JS_NULL; + } + + return JS_NULL; +} + +/* array.reduce(arr, fn, initial, reverse) */ +/* GC-safe reduce: re-chase array after each call */ +static JSValue js_cell_array_reduce (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) return JS_NULL; + if (!JS_IsArray (argv[0])) return JS_NULL; + if (!JS_IsFunction (argv[1])) return JS_NULL; + + /* GC-safe: root argv[0] and argv[1] for the duration of this function */ + JSGCRef arr_ref, func_ref; + JS_PushGCRef (ctx, &arr_ref); + JS_PushGCRef (ctx, &func_ref); + arr_ref.val = argv[0]; + func_ref.val = argv[1]; + + JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); + word_t len = arr->len; + + int reverse = argc > 3 && JS_ToBool (ctx, argv[3]); + JSGCRef acc_ref; + JSValue acc; + + if (argc < 3 || JS_IsNull (argv[2])) { + if (len == 0) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } + if (len == 1) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return arr->values[0]; } + + if (reverse) { + acc = arr->values[len - 1]; + for (word_t i = len - 1; i > 0; i--) { + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + if (i - 1 >= arr->len) continue; + JSValue args[2] = { acc, arr->values[i - 1] }; + JS_PUSH_VALUE (ctx, acc); + JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); + JS_POP_VALUE (ctx, acc); + if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + acc = new_acc; + } + } else { + acc = arr->values[0]; + for (word_t i = 1; i < len; i++) { + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + if (i >= arr->len) break; + JSValue args[2] = { acc, arr->values[i] }; + JS_PUSH_VALUE (ctx, acc); + JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); + JS_POP_VALUE (ctx, acc); + if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + acc = new_acc; + } + } + } else { + if (len == 0) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return argv[2]; } + acc = argv[2]; + + if (reverse) { + for (word_t i = len; i > 0; i--) { + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + if (i - 1 >= arr->len) continue; + JSValue args[2] = { acc, arr->values[i - 1] }; + JS_PUSH_VALUE (ctx, acc); + JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); + JS_POP_VALUE (ctx, acc); + if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + acc = new_acc; + } + } else { + for (word_t i = 0; i < len; i++) { + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + if (i >= arr->len) break; + JSValue args[2] = { acc, arr->values[i] }; + JS_PUSH_VALUE (ctx, acc); + JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); + JS_POP_VALUE (ctx, acc); + if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + acc = new_acc; + } + } + } + + JS_PopGCRef (ctx, &func_ref); + JS_PopGCRef (ctx, &arr_ref); + return acc; +} + +/* array.for(arr, fn, reverse, exit) - GC-safe */ +static JSValue js_cell_array_for (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) return JS_NULL; + if (!JS_IsArray (argv[0])) return JS_NULL; + if (!JS_IsFunction (argv[1])) return JS_NULL; + + /* GC-safe: root argv[0] and argv[1] */ + JSGCRef arr_ref, func_ref; + JS_PushGCRef (ctx, &arr_ref); + JS_PushGCRef (ctx, &func_ref); + arr_ref.val = argv[0]; + func_ref.val = argv[1]; + + JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); + + word_t len = arr->len; + if (len == 0) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } + + int reverse = argc > 2 && JS_ToBool (ctx, argv[2]); + JSValue exit_val = argc > 3 ? argv[3] : JS_NULL; + + /* Determine function arity */ + int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func_ref.val))->length; + + if (reverse) { + for (word_t i = len; i > 0; i--) { + /* Re-chase array each iteration */ + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + if (i - 1 >= arr->len) continue; + JSValue result; + if (arity == 1) { + JSValue item = arr->values[i - 1]; + result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); + } else { + JSValue args[2]; + args[0] = arr->values[i - 1]; + args[1] = JS_NewInt32 (ctx, (int32_t)(i - 1)); + result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); + } + + if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + if (!JS_IsNull (exit_val) && js_strict_eq (ctx, result, exit_val)) { + JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); + return result; + } + } + } else { + for (word_t i = 0; i < len; i++) { + /* Re-chase array each iteration */ + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + if (i >= arr->len) break; + JSValue result; + if (arity == 1) { + JSValue item = arr->values[i]; + result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); + } else { + JSValue args[2]; + args[0] = arr->values[i]; + args[1] = JS_NewInt32 (ctx, (int32_t)i); + result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); + } + + if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + if (!JS_IsNull (exit_val) && js_strict_eq (ctx, result, exit_val)) { + JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); + return result; + } + } + } + + JS_PopGCRef (ctx, &func_ref); + JS_PopGCRef (ctx, &arr_ref); + return JS_NULL; +} + +/* array.find(arr, fn, reverse, from) */ +/* array.find(arr, fn, reverse, from) - GC-safe */ +static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) return JS_NULL; + if (!JS_IsArray (argv[0])) return JS_NULL; + + /* GC-safe: root argv[0] */ + JSGCRef arr_ref; + JS_PushGCRef (ctx, &arr_ref); + arr_ref.val = argv[0]; + + JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); + word_t len = arr->len; + + int reverse = argc > 2 && JS_ToBool (ctx, argv[2]); + int32_t from; + if (argc > 3 && !JS_IsNull (argv[3])) { + if (JS_ToInt32 (ctx, &from, argv[3])) { JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } + } else { + from = reverse ? (int32_t)(len - 1) : 0; + } + + if (!JS_IsFunction (argv[1])) { + /* Compare exactly - no GC concerns since no calls */ + JSValue target = argv[1]; + if (reverse) { + for (int32_t i = from; i >= 0; i--) { + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + if ((word_t)i >= arr->len) continue; + if (js_strict_eq (ctx, arr->values[i], target)) { + JS_PopGCRef (ctx, &arr_ref); + return JS_NewInt32 (ctx, i); + } + } + } else { + for (word_t i = (word_t)from; i < len; i++) { + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + if (i >= arr->len) break; + if (js_strict_eq (ctx, arr->values[i], target)) { + JS_PopGCRef (ctx, &arr_ref); + return JS_NewInt32 (ctx, (int32_t)i); + } + } + } + JS_PopGCRef (ctx, &arr_ref); + return JS_NULL; + } + + /* Use function predicate - must re-chase after each call */ + JSGCRef func_ref; + JS_PushGCRef (ctx, &func_ref); + func_ref.val = argv[1]; + int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func_ref.val))->length; + + if (arity == 2) { + if (reverse) { + for (int32_t i = from; i >= 0; i--) { + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + if ((word_t)i >= arr->len) continue; + JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, i) }; + JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); + if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + if (JS_ToBool (ctx, result)) { + JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); + return JS_NewInt32 (ctx, i); + } + } + } else { + for (word_t i = (word_t)from; i < len; i++) { + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + if (i >= arr->len) break; + JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, (int32_t)i) }; + JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); + if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + if (JS_ToBool (ctx, result)) { + JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); + return JS_NewInt32 (ctx, (int32_t)i); + } + } + } + } else { + if (reverse) { + for (int32_t i = from; i >= 0; i--) { + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + if ((word_t)i >= arr->len) continue; + JSValue item = arr->values[i]; + JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); + if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + if (JS_ToBool (ctx, result)) { + JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); + return JS_NewInt32 (ctx, i); + } + } + } else { + for (word_t i = (word_t)from; i < len; i++) { + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + if (i >= arr->len) break; + JSValue item = arr->values[i]; + JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); + if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } + if (JS_ToBool (ctx, result)) { + JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); + return JS_NewInt32 (ctx, (int32_t)i); + } + } + } + } + + JS_PopGCRef (ctx, &func_ref); + JS_PopGCRef (ctx, &arr_ref); + return JS_NULL; +} + +/* array.filter(arr, fn) - GC-safe */ +static JSValue js_cell_array_filter (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) return JS_NULL; + if (!JS_IsArray (argv[0])) return JS_NULL; + if (!JS_IsFunction (argv[1])) return JS_NULL; + + /* Protect input array and function throughout the loop */ + JSGCRef input_ref, func_ref, result_ref; + JS_PushGCRef (ctx, &input_ref); + JS_PushGCRef (ctx, &func_ref); + JS_PushGCRef (ctx, &result_ref); + input_ref.val = argv[0]; + func_ref.val = argv[1]; + + JSArray *arr = JS_VALUE_GET_ARRAY (input_ref.val); + word_t len = arr->len; + + result_ref.val = JS_NewArray (ctx); + if (JS_IsException (result_ref.val)) { + JS_PopGCRef (ctx, &result_ref); + JS_PopGCRef (ctx, &func_ref); + JS_PopGCRef (ctx, &input_ref); + return JS_EXCEPTION; + } + + int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func_ref.val))->length; + + for (word_t i = 0; i < len; i++) { + /* Re-chase input array each iteration (it may have moved) */ + arr = JS_VALUE_GET_ARRAY (input_ref.val); + if (i >= arr->len) break; + JSValue item = arr->values[i]; + + JSValue val; + if (arity >= 2) { + JSValue args[2] = { item, JS_NewInt32 (ctx, (int32_t)i) }; + val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); + } else if (arity == 1) { + val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); + } else { + val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 0, NULL, 0); + } + + if (JS_IsException (val)) { + JS_PopGCRef (ctx, &result_ref); + JS_PopGCRef (ctx, &func_ref); + JS_PopGCRef (ctx, &input_ref); + return JS_EXCEPTION; + } + + if (JS_VALUE_GET_TAG (val) == JS_TAG_BOOL) { + if (JS_VALUE_GET_BOOL (val)) { + /* Re-read item after the call (GC may have moved the input array) */ + arr = JS_VALUE_GET_ARRAY (input_ref.val); + if (i < arr->len) { + item = arr->values[i]; + if (js_intrinsic_array_push (ctx, &result_ref.val, item) < 0) { + JS_PopGCRef (ctx, &result_ref); + JS_PopGCRef (ctx, &func_ref); + JS_PopGCRef (ctx, &input_ref); + return JS_EXCEPTION; + } + } + } + } else { + JS_PopGCRef (ctx, &result_ref); + JS_PopGCRef (ctx, &func_ref); + JS_PopGCRef (ctx, &input_ref); + return JS_NULL; + } + } + + JSValue result = result_ref.val; + JS_PopGCRef (ctx, &result_ref); + JS_PopGCRef (ctx, &func_ref); + JS_PopGCRef (ctx, &input_ref); + return result; +} + +/* array.sort(arr, select) - GC-safe */ +static JSValue js_cell_array_sort (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + (void)this_val; + if (argc < 1) return JS_NULL; + if (!JS_IsArray (argv[0])) return JS_NULL; + + /* GC-safe: root argv[0] */ + JSGCRef arr_ref; + JS_PushGCRef (ctx, &arr_ref); + arr_ref.val = argv[0]; + + JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); + word_t len = arr->len; + + JSValue result = JS_NewArrayLen (ctx, len); + if (JS_IsException (result)) { JS_PopGCRef (ctx, &arr_ref); return result; } + + if (len == 0) { JS_PopGCRef (ctx, &arr_ref); return result; } + + /* Root result across allocating calls */ + JSGCRef result_ref; + JS_PushGCRef (ctx, &result_ref); + result_ref.val = result; + + /* Re-chase arr after allocation */ + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + len = arr->len; + + /* Use alloca for temporary working arrays - they won't move during GC */ + JSValue *items = alloca (sizeof (JSValue) * len); + double *keys = alloca (sizeof (double) * len); + char **str_keys = NULL; + int is_string = 0; + + /* Extract items and keys - re-chase arrays as needed */ + for (word_t i = 0; i < len; i++) { + /* Re-chase input and key arrays each iteration */ + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + if (i >= arr->len) break; + items[i] = arr->values[i]; + + JSValue key; + if (argc < 2 || JS_IsNull (argv[1])) { + key = items[i]; + } else if (JS_VALUE_GET_TAG (argv[1]) == JS_TAG_INT) { + /* Numeric index - use for nested arrays */ + int32_t idx; + JS_ToInt32 (ctx, &idx, argv[1]); + if (JS_IsArray (items[i])) { + JSArray *nested = JS_VALUE_GET_ARRAY (items[i]); + if (idx >= 0 && (word_t)idx < nested->len) + key = nested->values[idx]; + else + key = JS_NULL; + } else { + key = JS_GetPropertyInt64 (ctx, items[i], idx); + /* Re-read items[i] after potential GC */ + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + items[i] = arr->values[i]; + } + } else if (JS_VALUE_GET_TAG (argv[1]) == JS_TAG_STRING + || JS_VALUE_GET_TAG (argv[1]) == JS_TAG_STRING_IMM) { + JSValue prop_key = js_key_from_string (ctx, argv[1]); + /* Re-read items[i] after allocation (js_key_from_string can trigger GC) */ + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + items[i] = arr->values[i]; + key = JS_GetProperty (ctx, items[i], prop_key); + } else if (argc >= 2 && JS_IsArray (argv[1])) { + /* Re-chase key array */ + JSArray *key_arr = JS_VALUE_GET_ARRAY (argv[1]); + if (i < key_arr->len) + key = key_arr->values[i]; + else + key = JS_NULL; + } else { + key = items[i]; + } + + if (JS_IsException (key)) { + if (str_keys) { + for (word_t j = 0; j < i; j++) + JS_FreeCString (ctx, str_keys[j]); + js_free (ctx, str_keys); + } + JS_PopGCRef (ctx, &result_ref); + JS_PopGCRef (ctx, &arr_ref); + return JS_EXCEPTION; + } + + int key_tag = JS_VALUE_GET_TAG (key); + if (key_tag == JS_TAG_INT || key_tag == JS_TAG_FLOAT64 || key_tag == JS_TAG_SHORT_FLOAT) { + JS_ToFloat64 (ctx, &keys[i], key); + if (i == 0) is_string = 0; + } else if (key_tag == JS_TAG_STRING || key_tag == JS_TAG_STRING_IMM) { + if (i == 0) { + is_string = 1; + str_keys = alloca (sizeof (char *) * len); + } + if (is_string) { str_keys[i] = (char *)JS_ToCString (ctx, key); } + } else { + if (str_keys) { + for (word_t j = 0; j < i; j++) + JS_FreeCString (ctx, str_keys[j]); + } + JS_PopGCRef (ctx, &result_ref); + JS_PopGCRef (ctx, &arr_ref); + return JS_NULL; + } + } + + /* Re-read all items from GC-safe source after key extraction (GC may have moved them) */ + arr = JS_VALUE_GET_ARRAY (arr_ref.val); + for (word_t i = 0; i < len && i < arr->len; i++) + items[i] = arr->values[i]; + + /* Create index array using alloca */ + int *indices = alloca (sizeof (int) * len); + for (word_t i = 0; i < len; i++) + indices[i] = (int)i; + + /* Simple insertion sort (stable) */ + for (word_t i = 1; i < len; i++) { + int temp = indices[i]; + int j = (int)i - 1; + while (j >= 0) { + int cmp; + if (is_string) { + cmp = strcmp (str_keys[indices[j]], str_keys[temp]); + } else { + double a = keys[indices[j]], b = keys[temp]; + cmp = (a > b) - (a < b); + } + if (cmp <= 0) break; + indices[j + 1] = indices[j]; + j--; + } + indices[j + 1] = temp; + } + + /* Build sorted array directly into output - re-chase result */ + JSArray *out = JS_VALUE_GET_ARRAY (result_ref.val); + for (word_t i = 0; i < len; i++) { + out->values[i] = items[indices[i]]; + } + out->len = len; + + /* Cleanup string keys only (alloca frees automatically) */ + if (str_keys) { + for (word_t i = 0; i < len; i++) + JS_FreeCString (ctx, str_keys[i]); + } + + result = result_ref.val; + JS_PopGCRef (ctx, &result_ref); + JS_PopGCRef (ctx, &arr_ref); + return result; +} + +/* ---------------------------------------------------------------------------- + * object function and sub-functions + * ---------------------------------------------------------------------------- + */ + +/* object(arg, arg2) - main object function */ +static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NULL; + + JSValue arg = argv[0]; + + /* object(object) - shallow mutable copy */ + if (JS_IsObject (arg) && !JS_IsArray (arg) && !JS_IsFunction (arg)) { + if (argc < 2 || JS_IsNull (argv[1])) { + /* Shallow copy - root arg, result, keys across allocating calls */ + JSGCRef arg_ref, res_ref, keys_ref; + JS_PushGCRef (ctx, &arg_ref); + arg_ref.val = arg; + JS_PushGCRef (ctx, &res_ref); + res_ref.val = JS_NewObject (ctx); + JS_PushGCRef (ctx, &keys_ref); + keys_ref.val = JS_NULL; + +#define OBJ_COPY_CLEANUP() do { JS_PopGCRef (ctx, &keys_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &arg_ref); } while(0) + + if (JS_IsException (res_ref.val)) { OBJ_COPY_CLEANUP (); return JS_EXCEPTION; } + + keys_ref.val = JS_GetOwnPropertyNames (ctx, arg_ref.val); + if (JS_IsException (keys_ref.val)) { + OBJ_COPY_CLEANUP (); + return JS_EXCEPTION; + } + uint32_t len; + if (js_get_length32 (ctx, &len, keys_ref.val)) { + OBJ_COPY_CLEANUP (); + return JS_EXCEPTION; + } + + for (uint32_t i = 0; i < len; i++) { + JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); + JSValue val = JS_GetProperty (ctx, arg_ref.val, key); + if (JS_IsException (val)) { + OBJ_COPY_CLEANUP (); + return JS_EXCEPTION; + } + JS_SetProperty (ctx, res_ref.val, key, val); + } + JSValue result = res_ref.val; + OBJ_COPY_CLEANUP (); + return result; + } +#undef OBJ_COPY_CLEANUP + + /* object(object, another_object) - combine */ + if (JS_IsObject (argv[1]) && !JS_IsArray (argv[1])) { + JSGCRef arg_ref, arg2_ref, res_ref, keys_ref; + JS_PushGCRef (ctx, &arg_ref); + arg_ref.val = arg; + JS_PushGCRef (ctx, &arg2_ref); + arg2_ref.val = argv[1]; + JS_PushGCRef (ctx, &res_ref); + res_ref.val = JS_NewObject (ctx); + JS_PushGCRef (ctx, &keys_ref); + keys_ref.val = JS_NULL; + +#define OBJ_COMBINE_CLEANUP() do { JS_PopGCRef (ctx, &keys_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &arg2_ref); JS_PopGCRef (ctx, &arg_ref); } while(0) + + if (JS_IsException (res_ref.val)) { OBJ_COMBINE_CLEANUP (); return JS_EXCEPTION; } + + /* Copy from first object */ + keys_ref.val = JS_GetOwnPropertyNames (ctx, arg_ref.val); + if (JS_IsException (keys_ref.val)) { + OBJ_COMBINE_CLEANUP (); + return JS_EXCEPTION; + } + uint32_t len; + if (js_get_length32 (ctx, &len, keys_ref.val)) { + OBJ_COMBINE_CLEANUP (); + return JS_EXCEPTION; + } + for (uint32_t i = 0; i < len; i++) { + JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); + JSValue val = JS_GetProperty (ctx, arg_ref.val, key); + if (JS_IsException (val)) { + OBJ_COMBINE_CLEANUP (); + return JS_EXCEPTION; + } + JS_SetProperty (ctx, res_ref.val, key, val); + } + + /* Copy from second object */ + keys_ref.val = JS_GetOwnPropertyNames (ctx, arg2_ref.val); + if (JS_IsException (keys_ref.val)) { + OBJ_COMBINE_CLEANUP (); + return JS_EXCEPTION; + } + if (js_get_length32 (ctx, &len, keys_ref.val)) { + OBJ_COMBINE_CLEANUP (); + return JS_EXCEPTION; + } + for (uint32_t i = 0; i < len; i++) { + JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); + JSValue val = JS_GetProperty (ctx, arg2_ref.val, key); + if (JS_IsException (val)) { + OBJ_COMBINE_CLEANUP (); + return JS_EXCEPTION; + } + JS_SetProperty (ctx, res_ref.val, key, val); + } + JSValue result = res_ref.val; + OBJ_COMBINE_CLEANUP (); + return result; + } +#undef OBJ_COMBINE_CLEANUP + + /* object(object, array_of_keys) - select */ + if (JS_IsArray (argv[1])) { + JSGCRef arg_ref, res_ref, karr_ref; + JS_PushGCRef (ctx, &arg_ref); + arg_ref.val = arg; + JS_PushGCRef (ctx, &res_ref); + res_ref.val = JS_NewObject (ctx); + JS_PushGCRef (ctx, &karr_ref); + karr_ref.val = argv[1]; + +#define OBJ_SEL_CLEANUP() do { JS_PopGCRef (ctx, &karr_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &arg_ref); } while(0) + + if (JS_IsException (res_ref.val)) { OBJ_SEL_CLEANUP (); return JS_EXCEPTION; } + + JSArray *keys = JS_VALUE_GET_ARRAY (karr_ref.val); + int len = keys->len; + + for (int i = 0; i < len; i++) { + keys = JS_VALUE_GET_ARRAY (karr_ref.val); /* re-chase each iteration */ + if (i >= (int)keys->len) break; + JSValue key = keys->values[i]; + if (JS_IsText (key)) { + JSValue prop_key = js_key_from_string (ctx, key); + int has = JS_HasProperty (ctx, arg_ref.val, prop_key); + if (has > 0) { + JSValue val = JS_GetProperty (ctx, arg_ref.val, prop_key); + if (!JS_IsException (val)) { + JS_SetProperty (ctx, res_ref.val, prop_key, val); + } + } + } + } + JSValue result = res_ref.val; + OBJ_SEL_CLEANUP (); + return result; + } +#undef OBJ_SEL_CLEANUP + } + + /* object(array_of_keys) - set with true values */ + /* object(array_of_keys, value) - value set */ + /* object(array_of_keys, function) - functional value set */ + if (JS_IsArray (arg)) { + JSArray *keys = JS_VALUE_GET_ARRAY (arg); + int len = keys->len; + + int is_func = argc >= 2 && JS_IsFunction (argv[1]); + + /* Root keys array and func/value BEFORE JS_NewObject which may trigger GC. + argv[] is on the C stack and is NOT a GC root, so after any allocation + that triggers GC, argv[] values become dangling pointers. */ + JSGCRef keys_ref, func_ref, result_ref; + JS_PushGCRef (ctx, &keys_ref); + keys_ref.val = arg; /* use already-read arg, not argv[0] */ + JS_PushGCRef (ctx, &func_ref); + func_ref.val = argc >= 2 ? argv[1] : JS_NULL; + JS_PushGCRef (ctx, &result_ref); + result_ref.val = JS_NULL; + + JSValue result = JS_NewObject (ctx); + if (JS_IsException (result)) { + JS_PopGCRef (ctx, &result_ref); + JS_PopGCRef (ctx, &func_ref); + JS_PopGCRef (ctx, &keys_ref); + return result; + } + result_ref.val = result; + + for (int i = 0; i < len; i++) { + keys = JS_VALUE_GET_ARRAY (keys_ref.val); + if (i >= (int)keys->len) break; + JSValue key = keys->values[i]; + if (JS_IsText (key)) { + /* Use JSValue key directly - create interned key */ + JSValue prop_key = js_key_from_string (ctx, key); + JSValue val; + if (argc < 2 || JS_IsNull (func_ref.val)) { + val = JS_TRUE; + } else if (is_func) { + JSValue arg_key = key; + val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &arg_key, 0); + if (JS_IsException (val)) { + JS_PopGCRef (ctx, &result_ref); + JS_PopGCRef (ctx, &func_ref); + JS_PopGCRef (ctx, &keys_ref); + return JS_EXCEPTION; + } + } else { + val = func_ref.val; + } + JS_SetProperty (ctx, result_ref.val, prop_key, val); + /* prop_key is interned, no need to free */ + } + } + result = JS_PopGCRef (ctx, &result_ref); + JS_PopGCRef (ctx, &func_ref); + JS_PopGCRef (ctx, &keys_ref); + return result; + } + + return JS_NULL; +} + +/* ---------------------------------------------------------------------------- + * fn function and sub-functions + * ---------------------------------------------------------------------------- + */ + +/* fn.apply(func, args) - arity is enforced in JS_CallInternal */ +static JSValue js_cell_fn_apply (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NULL; + if (!JS_IsFunction (argv[0])) return argv[0]; + + JSGCRef func_ref, args_ref; + JS_PushGCRef (ctx, &func_ref); + JS_PushGCRef (ctx, &args_ref); + func_ref.val = argv[0]; + args_ref.val = argc >= 2 ? argv[1] : JS_NULL; + + if (argc < 2) { + JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 0, NULL, 0); + JS_PopGCRef (ctx, &args_ref); + JS_PopGCRef (ctx, &func_ref); + return result; + } + + if (!JS_IsArray (args_ref.val)) { + /* Wrap single value in array */ + JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &args_ref.val, 0); + JS_PopGCRef (ctx, &args_ref); + JS_PopGCRef (ctx, &func_ref); + return result; + } + + JSArray *arr = JS_VALUE_GET_ARRAY (args_ref.val); + int len = arr->len; + + if (len == 0) { + JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 0, NULL, 0); + JS_PopGCRef (ctx, &args_ref); + JS_PopGCRef (ctx, &func_ref); + return result; + } + + JSValue *args = js_malloc (ctx, sizeof (JSValue) * len); + if (!args) { + JS_PopGCRef (ctx, &args_ref); + JS_PopGCRef (ctx, &func_ref); + return JS_EXCEPTION; + } + arr = JS_VALUE_GET_ARRAY (args_ref.val); /* re-chase after malloc via rooted ref */ + + for (int i = 0; i < len; i++) { + args[i] = arr->values[i]; + } + + JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, len, args, 0); + + js_free (ctx, args); + + JS_PopGCRef (ctx, &args_ref); + JS_PopGCRef (ctx, &func_ref); + return result; +} + +/* ============================================================================ + * Blob Intrinsic Type + * ============================================================================ + */ + +/* Helper to check if JSValue is a blob */ +blob *js_get_blob (JSContext *ctx, JSValue val) { + /* Must be a record, not an array or other object type */ + if (!JS_IsRecord(val)) return NULL; + JSRecord *p = JS_VALUE_GET_OBJ (val); + if (REC_GET_CLASS_ID(p) != JS_CLASS_BLOB) return NULL; + return REC_GET_OPAQUE(p); +} + +/* Helper to create a new blob JSValue */ +JSValue js_new_blob (JSContext *ctx, blob *b) { + JSValue obj = JS_NewObjectClass (ctx, JS_CLASS_BLOB); + if (JS_IsException (obj)) { + blob_destroy (b); + return obj; + } + JS_SetOpaque (obj, b); + return obj; +} + +/* Blob finalizer */ +static void js_blob_finalizer (JSRuntime *rt, JSValue val) { + blob *b = JS_GetOpaque (val, JS_CLASS_BLOB); + if (b) blob_destroy (b); +} + +/* blob() constructor */ +static JSValue js_blob_constructor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + blob *bd = NULL; + + /* blob() - empty blob */ + if (argc == 0) { + bd = blob_new (0); + } + /* blob(capacity) - blob with initial capacity in bits */ + else if (argc == 1 && JS_IsNumber (argv[0])) { + int64_t capacity_bits; + if (JS_ToInt64 (ctx, &capacity_bits, argv[0]) < 0) return JS_EXCEPTION; + if (capacity_bits < 0) capacity_bits = 0; + bd = blob_new ((size_t)capacity_bits); + } + /* blob(length, logical/random) - blob with fill or random */ + else if (argc == 2 && JS_IsNumber (argv[0])) { + int64_t length_bits; + if (JS_ToInt64 (ctx, &length_bits, argv[0]) < 0) return JS_EXCEPTION; + if (length_bits < 0) length_bits = 0; + + if (JS_IsBool (argv[1])) { + int is_one = JS_ToBool (ctx, argv[1]); + bd = blob_new_with_fill ((size_t)length_bits, is_one); + } else if (JS_IsFunction (argv[1])) { + /* Random function provided */ + size_t bytes = (length_bits + 7) / 8; + bd = blob_new ((size_t)length_bits); + if (bd) { + bd->length = length_bits; + memset (bd->data, 0, bytes); + + size_t bits_written = 0; + while (bits_written < (size_t)length_bits) { + JSValue randval = JS_Call (ctx, argv[1], JS_NULL, 0, NULL); + if (JS_IsException (randval)) { + blob_destroy (bd); + return JS_EXCEPTION; + } + + int64_t fitval; + JS_ToInt64 (ctx, &fitval, randval); + + size_t bits_to_use = length_bits - bits_written; + if (bits_to_use > 52) bits_to_use = 52; + + for (size_t j = 0; j < bits_to_use; j++) { + size_t bit_pos = bits_written + j; + size_t byte_idx = bit_pos / 8; + size_t bit_idx = bit_pos % 8; + + if (fitval & (1LL << j)) + bd->data[byte_idx] |= (uint8_t)(1 << bit_idx); + else + bd->data[byte_idx] &= (uint8_t)~(1 << bit_idx); + } + bits_written += bits_to_use; + } + } + } else { + return JS_ThrowTypeError ( + ctx, "Second argument must be boolean or random function"); + } + } + /* blob(blob, from, to) - copy from another blob */ + else if (argc >= 1 && JS_IsObject (argv[0])) { + blob *src = js_get_blob (ctx, argv[0]); + if (!src) + return JS_ThrowTypeError (ctx, + "blob constructor: argument 1 not a blob"); + int64_t from = 0, to = (int64_t)src->length; + if (argc >= 2 && JS_IsNumber (argv[1])) { + JS_ToInt64 (ctx, &from, argv[1]); + if (from < 0) from = 0; + } + if (argc >= 3 && JS_IsNumber (argv[2])) { + JS_ToInt64 (ctx, &to, argv[2]); + if (to < from) to = from; + if (to > (int64_t)src->length) to = (int64_t)src->length; + } + bd = blob_new_from_blob (src, (size_t)from, (size_t)to); + } + /* blob(text) - create blob from UTF-8 string */ + else if (argc == 1 + && (JS_VALUE_GET_TAG (argv[0]) == JS_TAG_STRING + || JS_VALUE_GET_TAG (argv[0]) == JS_TAG_STRING_IMM)) { + const char *str = JS_ToCString (ctx, argv[0]); + if (!str) return JS_EXCEPTION; + size_t len = strlen (str); + bd = blob_new (len * 8); + if (bd) { + memcpy (bd->data, str, len); + bd->length = len * 8; + } + JS_FreeCString (ctx, str); + } else { + return JS_ThrowTypeError (ctx, "blob constructor: invalid arguments"); + } + + if (!bd) return JS_ThrowOutOfMemory (ctx); + + return js_new_blob (ctx, bd); +} + +/* blob.write_bit(logical) */ +static JSValue js_blob_write_bit (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) + return JS_ThrowTypeError (ctx, "write_bit(logical) requires 1 argument"); + blob *bd = js_get_blob (ctx, this_val); + if (!bd) return JS_ThrowTypeError (ctx, "write_bit: not called on a blob"); + + int bit_val; + if (JS_IsNumber (argv[0])) { + int32_t num; + JS_ToInt32 (ctx, &num, argv[0]); + if (num != 0 && num != 1) + return JS_ThrowTypeError ( + ctx, "write_bit: value must be true, false, 0, or 1"); + bit_val = num; + } else { + bit_val = JS_ToBool (ctx, argv[0]); + } + + if (blob_write_bit (bd, bit_val) < 0) + return JS_ThrowTypeError (ctx, + "write_bit: cannot write (maybe stone or OOM)"); + return JS_NULL; +} + +/* blob.write_blob(second_blob) */ +static JSValue js_blob_write_blob (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) + return JS_ThrowTypeError (ctx, + "write_blob(second_blob) requires 1 argument"); + blob *bd = js_get_blob (ctx, this_val); + if (!bd) return JS_ThrowTypeError (ctx, "write_blob: not called on a blob"); + blob *second = js_get_blob (ctx, argv[0]); + if (!second) + return JS_ThrowTypeError (ctx, "write_blob: argument must be a blob"); + + if (blob_write_blob (bd, second) < 0) + return JS_ThrowTypeError (ctx, + "write_blob: cannot write to stone blob or OOM"); + + return JS_NULL; +} + +/* blob.write_number(number) - write dec64 */ +static JSValue js_blob_write_number (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) + return JS_ThrowTypeError (ctx, "write_number(number) requires 1 argument"); + blob *bd = js_get_blob (ctx, this_val); + if (!bd) + return JS_ThrowTypeError (ctx, "write_number: not called on a blob"); + + double d; + if (JS_ToFloat64 (ctx, &d, argv[0]) < 0) return JS_EXCEPTION; + + if (blob_write_dec64 (bd, d) < 0) + return JS_ThrowTypeError ( + ctx, "write_number: cannot write to stone blob or OOM"); + + return JS_NULL; +} + +/* blob.write_fit(value, len) */ +static JSValue js_blob_write_fit (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) + return JS_ThrowTypeError (ctx, + "write_fit(value, len) requires 2 arguments"); + + blob *bd = js_get_blob (ctx, this_val); + if (!bd) return JS_ThrowTypeError (ctx, "write_fit: not called on a blob"); + + int64_t value; + int32_t len; + + if (JS_ToInt64 (ctx, &value, argv[0]) < 0) return JS_EXCEPTION; + if (JS_ToInt32 (ctx, &len, argv[1]) < 0) return JS_EXCEPTION; + + if (blob_write_fit (bd, value, len) < 0) + return JS_ThrowTypeError (ctx, + "write_fit: value doesn't fit or stone blob"); + + return JS_NULL; +} + +/* blob.write_text(text) */ +static JSValue js_blob_write_text (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) + return JS_ThrowTypeError (ctx, "write_text(text) requires 1 argument"); + + blob *bd = js_get_blob (ctx, this_val); + if (!bd) return JS_ThrowTypeError (ctx, "write_text: not called on a blob"); + + const char *str = JS_ToCString (ctx, argv[0]); + if (!str) return JS_EXCEPTION; + + if (blob_write_text (bd, str) < 0) { + JS_FreeCString (ctx, str); + return JS_ThrowTypeError (ctx, + "write_text: cannot write to stone blob or OOM"); + } + + JS_FreeCString (ctx, str); + return JS_NULL; +} + +/* blob.write_pad(block_size) */ +static JSValue js_blob_write_pad (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) + return JS_ThrowTypeError (ctx, + "write_pad(block_size) requires 1 argument"); + + blob *bd = js_get_blob (ctx, this_val); + if (!bd) return JS_ThrowTypeError (ctx, "write_pad: not called on a blob"); + + int32_t block_size; + if (JS_ToInt32 (ctx, &block_size, argv[0]) < 0) return JS_EXCEPTION; + + if (blob_write_pad (bd, block_size) < 0) + return JS_ThrowTypeError (ctx, "write_pad: cannot write"); + + return JS_NULL; +} + +/* blob.w16(value) - write 16-bit value */ +static JSValue js_blob_w16 (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) + return JS_ThrowTypeError (ctx, "w16(value) requires 1 argument"); + + blob *bd = js_get_blob (ctx, this_val); + if (!bd) return JS_ThrowTypeError (ctx, "w16: not called on a blob"); + + int32_t value; + if (JS_ToInt32 (ctx, &value, argv[0]) < 0) return JS_EXCEPTION; + + int16_t short_val = (int16_t)value; + if (blob_write_bytes (bd, &short_val, sizeof (int16_t)) < 0) + return JS_ThrowTypeError (ctx, "w16: cannot write"); + + return JS_NULL; +} + +/* blob.w32(value) - write 32-bit value */ +static JSValue js_blob_w32 (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) + return JS_ThrowTypeError (ctx, "w32(value) requires 1 argument"); + + blob *bd = js_get_blob (ctx, this_val); + if (!bd) return JS_ThrowTypeError (ctx, "w32: not called on a blob"); + + int32_t value; + if (JS_ToInt32 (ctx, &value, argv[0]) < 0) return JS_EXCEPTION; + + if (blob_write_bytes (bd, &value, sizeof (int32_t)) < 0) + return JS_ThrowTypeError (ctx, "w32: cannot write"); + + return JS_NULL; +} + +/* blob.wf(value) - write float */ +static JSValue js_blob_wf (JSContext *ctx, JSValue this_val, JSValue arg0) { + if (JS_IsNull (arg0)) + return JS_ThrowTypeError (ctx, "wf(value) requires 1 argument"); + + blob *bd = js_get_blob (ctx, this_val); + if (!bd) return JS_ThrowTypeError (ctx, "wf: not called on a blob"); + + float f; + double d; + if (JS_ToFloat64 (ctx, &d, arg0) < 0) return JS_EXCEPTION; + f = d; + + if (blob_write_bytes (bd, &f, sizeof (f)) < 0) + return JS_ThrowTypeError (ctx, "wf: cannot write"); + + return JS_NULL; +} + +/* blob.read_logical(from) */ +static JSValue js_blob_read_logical (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) + return JS_ThrowTypeError (ctx, "read_logical(from) requires 1 argument"); + blob *bd = js_get_blob (ctx, this_val); + if (!bd) + return JS_ThrowTypeError (ctx, "read_logical: not called on a blob"); + int64_t pos; + if (JS_ToInt64 (ctx, &pos, argv[0]) < 0) + return JS_ThrowInternalError (ctx, "must provide a positive bit"); + if (pos < 0) + return JS_ThrowRangeError (ctx, + "read_logical: position must be non-negative"); + int bit_val; + if (blob_read_bit (bd, (size_t)pos, &bit_val) < 0) + return JS_ThrowTypeError (ctx, "read_logical: blob must be stone"); + return JS_NewBool (ctx, bit_val); +} + +/* blob.read_blob(from, to) */ +JSValue js_blob_read_blob (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + blob *bd = js_get_blob (ctx, this_val); + if (!bd) return JS_ThrowTypeError (ctx, "read_blob: not called on a blob"); + + if (!bd->is_stone) + return JS_ThrowTypeError (ctx, "read_blob: blob must be stone"); + + int64_t from = 0; + int64_t to = bd->length; + + if (argc >= 1) { + if (JS_ToInt64 (ctx, &from, argv[0]) < 0) return JS_EXCEPTION; + if (from < 0) from = 0; + } + if (argc >= 2) { + if (JS_ToInt64 (ctx, &to, argv[1]) < 0) return JS_EXCEPTION; + if (to > (int64_t)bd->length) to = bd->length; + } + + blob *new_bd = blob_read_blob (bd, from, to); + if (!new_bd) return JS_ThrowOutOfMemory (ctx); + + return js_new_blob (ctx, new_bd); +} + +/* blob.read_number(from) */ +static JSValue js_blob_read_number (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) + return JS_ThrowTypeError (ctx, "read_number(from) requires 1 argument"); + blob *bd = js_get_blob (ctx, this_val); + if (!bd) return JS_ThrowTypeError (ctx, "read_number: not called on a blob"); + + if (!bd->is_stone) + return JS_ThrowTypeError (ctx, "read_number: blob must be stone"); + + double from; + if (JS_ToFloat64 (ctx, &from, argv[0]) < 0) return JS_EXCEPTION; + + if (from < 0) + return JS_ThrowRangeError (ctx, + "read_number: position must be non-negative"); + + double d; + if (blob_read_dec64 (bd, from, &d) < 0) + return JS_ThrowRangeError (ctx, "read_number: out of range"); + + return JS_NewFloat64 (ctx, d); +} + +/* blob.read_fit(from, len) */ +static JSValue js_blob_read_fit (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) + return JS_ThrowTypeError (ctx, "read_fit(from, len) requires 2 arguments"); + + blob *bd = js_get_blob (ctx, this_val); + if (!bd) return JS_ThrowTypeError (ctx, "read_fit: not called on a blob"); + + if (!bd->is_stone) + return JS_ThrowTypeError (ctx, "read_fit: blob must be stone"); + + int64_t from; + int32_t len; + + if (JS_ToInt64 (ctx, &from, argv[0]) < 0) return JS_EXCEPTION; + if (JS_ToInt32 (ctx, &len, argv[1]) < 0) return JS_EXCEPTION; + + if (from < 0) + return JS_ThrowRangeError (ctx, "read_fit: position must be non-negative"); + + int64_t value; + if (blob_read_fit (bd, from, len, &value) < 0) + return JS_ThrowRangeError (ctx, + "read_fit: out of range or invalid length"); + + return JS_NewInt64 (ctx, value); +} + +/* blob.read_text(from) */ +JSValue js_blob_read_text (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + blob *bd = js_get_blob (ctx, this_val); + if (!bd) return JS_ThrowTypeError (ctx, "read_text: not called on a blob"); + + if (!bd->is_stone) + return JS_ThrowTypeError (ctx, "read_text: blob must be stone"); + + int64_t from = 0; + if (argc >= 1) { + if (JS_ToInt64 (ctx, &from, argv[0]) < 0) return JS_EXCEPTION; + } + + char *text; + size_t bits_read; + if (blob_read_text (bd, from, &text, &bits_read) < 0) + return JS_ThrowRangeError (ctx, + "read_text: out of range or invalid encoding"); + + JSValue result = JS_NewString (ctx, text); + /* Note: blob_read_text uses system malloc, so we use sys_free */ + sys_free (text); + + return result; +} + +/* blob.pad?(from, block_size) */ +static JSValue js_blob_pad_q (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) + return JS_ThrowTypeError (ctx, + "pad?(from, block_size) requires 2 arguments"); + blob *bd = js_get_blob (ctx, this_val); + if (!bd) return JS_ThrowTypeError (ctx, "pad?: not called on a blob"); + + if (!bd->is_stone) + return JS_ThrowTypeError (ctx, "pad?: blob must be stone"); + + int64_t from; + int32_t block_size; + if (JS_ToInt64 (ctx, &from, argv[0]) < 0) return JS_EXCEPTION; + if (JS_ToInt32 (ctx, &block_size, argv[1]) < 0) return JS_EXCEPTION; + + return JS_NewBool (ctx, blob_pad_check (bd, from, block_size)); +} + +static const JSCFunctionListEntry js_blob_proto_funcs[] = { + /* Write methods */ + JS_CFUNC_DEF ("write_bit", 1, js_blob_write_bit), + JS_CFUNC_DEF ("write_blob", 1, js_blob_write_blob), + JS_CFUNC_DEF ("write_number", 1, js_blob_write_number), + JS_CFUNC_DEF ("write_fit", 2, js_blob_write_fit), + JS_CFUNC_DEF ("write_text", 1, js_blob_write_text), + JS_CFUNC_DEF ("write_pad", 1, js_blob_write_pad), + JS_CFUNC1_DEF ("wf", js_blob_wf), + JS_CFUNC_DEF ("w16", 1, js_blob_w16), + JS_CFUNC_DEF ("w32", 1, js_blob_w32), + + /* Read methods */ + JS_CFUNC_DEF ("read_logical", 1, js_blob_read_logical), + JS_CFUNC_DEF ("read_blob", 2, js_blob_read_blob), + JS_CFUNC_DEF ("read_number", 1, js_blob_read_number), + JS_CFUNC_DEF ("read_fit", 2, js_blob_read_fit), + JS_CFUNC_DEF ("read_text", 1, js_blob_read_text), + JS_CFUNC_DEF ("pad?", 2, js_blob_pad_q), +}; + +/* ============================================================================ + * Blob external API functions (called from other files via cell.h) + * ============================================================================ + */ + +/* Initialize blob - called during context setup (but we do it in + * JS_AddIntrinsicBaseObjects now) */ +JSValue js_blob_use (JSContext *js) { + return JS_GetPropertyStr (js, js->global_obj, "blob"); +} + +/* Create a new blob from raw data, stone it, and return as JSValue */ +JSValue js_new_blob_stoned_copy (JSContext *js, void *data, size_t bytes) { + blob *b = blob_new (bytes * 8); + if (!b) return JS_ThrowOutOfMemory (js); + memcpy (b->data, data, bytes); + b->length = bytes * 8; + blob_make_stone (b); + return js_new_blob (js, b); +} + +/* Get raw data pointer from a blob (must be stone) - returns byte count */ +void *js_get_blob_data (JSContext *js, size_t *size, JSValue v) { + blob *b = js_get_blob (js, v); + *size = (b->length + 7) / 8; + if (!b) { + JS_ThrowReferenceError (js, "get_blob_data: not called on a blob"); + return NULL; + } + + if (!b->is_stone) { + JS_ThrowReferenceError (js, + "attempted to read data from a non-stone blob"); + return NULL; + } + + if (b->length % 8 != 0) { + JS_ThrowReferenceError ( + js, + "attempted to read data from a non-byte aligned blob [length is %zu]", + b->length); + return NULL; + } + + return b->data; +} + +/* Get raw data pointer from a blob (must be stone) - returns bit count */ +void *js_get_blob_data_bits (JSContext *js, size_t *bits, JSValue v) { + blob *b = js_get_blob (js, v); + if (!b) { + JS_ThrowReferenceError (js, "get_blob_data_bits: not called on a blob"); + return NULL; + } + if (!b->is_stone) { + JS_ThrowReferenceError (js, + "attempted to read data from a non-stone blob"); + return NULL; + } + + if (!b->data) { + JS_ThrowReferenceError (js, "attempted to read data from an empty blob"); + return NULL; + } + + if (b->length % 8 != 0) { + JS_ThrowReferenceError ( + js, "attempted to read data from a non-byte aligned blob"); + return NULL; + } + + if (b->length == 0) { + JS_ThrowReferenceError (js, "attempted to read data from an empty blob"); + return NULL; + } + + *bits = b->length; + return b->data; +} + +/* Check if a value is a blob */ +int js_is_blob (JSContext *js, JSValue v) { + return js_get_blob (js, v) != NULL; +} + +/* ============================================================================ + * eval() function - compile and execute code with environment + * ============================================================================ + */ + +/* eval(text, env) - evaluate code with optional environment record + * text: string to compile and execute + * env: optional stone record for variable bindings (checked first before intrinsics) + */ +static JSValue js_cell_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + const char *str; + size_t len; + JSValue env = JS_NULL; + JSValue result; + JSGCRef env_ref; + + if (argc < 1 || !JS_IsText (argv[0])) { + return JS_ThrowTypeError (ctx, "eval requires a text argument"); + } + + /* Get optional environment record (must be stone if provided) */ + if (argc > 1 && !JS_IsNull (argv[1])) { + if (!JS_IsRecord (argv[1])) { + return JS_ThrowTypeError (ctx, "eval environment must be an object"); + } + JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (argv[1]); + if (!objhdr_s (rec->mist_hdr)) { + return JS_ThrowTypeError (ctx, "eval environment must be stoned"); + } + env = argv[1]; + } + + /* Protect env from GC during compilation */ + JS_AddGCRef (ctx, &env_ref); + env_ref.val = env; + + /* Get text string */ + str = JS_ToCStringLen (ctx, &len, argv[0]); + if (!str) { + JS_DeleteGCRef (ctx, &env_ref); + return JS_EXCEPTION; + } + + /* Compile the text */ + JSValue fun = JS_Compile (ctx, str, len, ""); + JS_FreeCString (ctx, str); + if (JS_IsException (fun)) { + JS_DeleteGCRef (ctx, &env_ref); + return fun; + } + + /* Update env from GC ref (may have moved) */ + env = env_ref.val; + JS_DeleteGCRef (ctx, &env_ref); + + /* Integrate with environment */ + result = JS_Integrate (ctx, fun, env); + return result; +} + +/* ============================================================================ + * mach_eval() function - compile and execute via MACH VM + * ============================================================================ + */ + +/* mach_eval(name, source) - parse to AST and run through MACH VM */ +static JSValue js_mach_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2 || !JS_IsText (argv[0]) || !JS_IsText (argv[1])) + return JS_ThrowTypeError (ctx, "mach_eval requires (name, source) text arguments"); + + const char *name = JS_ToCString (ctx, argv[0]); + if (!name) return JS_EXCEPTION; + + const char *source = JS_ToCString (ctx, argv[1]); + if (!source) { + JS_FreeCString (ctx, name); + return JS_EXCEPTION; + } + + cJSON *ast = JS_ASTTree (source, strlen (source), name); + JS_FreeCString (ctx, source); + + if (!ast) { + JS_FreeCString (ctx, name); + return JS_ThrowSyntaxError (ctx, "mach_eval: failed to parse AST"); + } + + JSValue result = JS_RunMachTree (ctx, ast, JS_NULL); + cJSON_Delete (ast); + JS_FreeCString (ctx, name); + return result; +} + +/* ============================================================================ + * stone() function - deep freeze with blob support + * ============================================================================ + */ + +/* stone(object) - deep freeze an object */ +static JSValue js_cell_stone (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NULL; + + JSValue obj = argv[0]; + + blob *bd = js_get_blob (ctx, obj); + if (bd) { + bd->is_stone = true; + return obj; + } + + if (JS_IsObject (obj)) { + JSRecord *rec = JS_VALUE_GET_RECORD (obj); + obj_set_stone (rec); + return obj; + } + + if (JS_IsArray (obj)) { + JSArray *arr = JS_VALUE_GET_ARRAY (obj); + arr->mist_hdr = objhdr_set_s (arr->mist_hdr, true); + return obj; + } + + return JS_NULL; +} + +/* ============================================================================ + * reverse() function - reverse an array + * ============================================================================ + */ + +static JSValue js_cell_reverse (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NULL; + + JSValue value = argv[0]; + + /* Handle arrays */ + if (JS_IsArray (value)) { + /* GC-safe: root argv[0] */ + JSGCRef arr_ref; + JS_PushGCRef (ctx, &arr_ref); + arr_ref.val = argv[0]; + + JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); + int len = arr->len; + + JSValue result = JS_NewArrayLen (ctx, len); + if (JS_IsException (result)) { JS_PopGCRef (ctx, &arr_ref); return result; } + arr = JS_VALUE_GET_ARRAY (arr_ref.val); /* re-chase after allocation */ + JSArray *out = JS_VALUE_GET_ARRAY (result); + for (int i = len - 1, j = 0; i >= 0; i--, j++) { + out->values[j] = arr->values[i]; + } + out->len = len; + JS_PopGCRef (ctx, &arr_ref); + return result; + } + + /* Handle strings */ + if (JS_IsText (value)) { + int len = js_string_value_len (value); + if (len == 0) return JS_NewString (ctx, ""); + JSText *str = js_alloc_string (ctx, len); + if (!str) return JS_EXCEPTION; + for (int i = 0; i < len; i++) { + string_put (str, i, js_string_value_get (value, len - 1 - i)); + } + str->length = len; + return pretext_end (ctx, str); + } + + /* Handle blobs */ + blob *bd = js_get_blob (ctx, value); + if (bd) { + /* Blobs need proper blob reversal support - return null for now */ + return JS_NULL; + } + + return JS_NULL; +} + +/* ============================================================================ + * proto() function - get prototype of an object + * ============================================================================ + */ + +static JSValue js_cell_pop (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NULL; + + JSValue obj = argv[0]; + if (!JS_IsArray (obj)) return JS_NULL; + + JSArray *arr = JS_VALUE_GET_ARRAY (obj); + + if (arr->len == 0) return JS_NULL; + + /* Transfer ownership: take value without dup, clear slot, decrement len */ + JSValue last = arr->values[arr->len - 1]; + arr->values[arr->len - 1] = JS_NULL; + arr->len--; + return last; +} + +JSValue JS_Stone (JSContext *ctx, JSValue this_val) { + return js_cell_stone (ctx, this_val, 1, &this_val); +} + +/* GC-safe push: takes pointer to array JSValue, updates it if array grows. + Returns 0 on success, -1 on error. */ +int JS_ArrayPush (JSContext *ctx, JSValue *arr_ptr, JSValue val) { + if (!JS_IsArray (*arr_ptr)) { + JS_ThrowTypeError (ctx, "not an array"); + return -1; + } + return js_intrinsic_array_push (ctx, arr_ptr, val); +} + +JSValue JS_ArrayPop (JSContext *ctx, JSValue obj) { + if (!JS_IsArray (obj)) return JS_ThrowTypeError (ctx, "not an array"); + return js_cell_pop (ctx, JS_NULL, 1, &obj); +} + +/* C API: array(arg0, arg1, arg2, arg3) + - array(number) or array(number, fill_value_or_fn) - create array + - array(array) - copy + - array(array, fn, reverse, exit) - map + - array(array, array2) - concat + - array(array, from, to) - slice + - array(object) - keys + - array(text) or array(text, sep_or_len) - split */ +JSValue JS_Array (JSContext *ctx, JSValue arg0, JSValue arg1, JSValue arg2, JSValue arg3) { + JSValue argv[4] = { arg0, arg1, arg2, arg3 }; + int argc = 4; + if (JS_IsNull (arg3)) argc = 3; + if (JS_IsNull (arg2)) argc = 2; + if (JS_IsNull (arg1)) argc = 1; + return js_cell_array (ctx, JS_NULL, argc, argv); +} + +/* C API: filter(arr, fn) - returns new filtered array */ +JSValue JS_ArrayFilter (JSContext *ctx, JSValue arr, JSValue fn) { + JSValue argv[2] = { arr, fn }; + return js_cell_array_filter (ctx, JS_NULL, 2, argv); +} + +/* C API: sort(arr, selector) - returns new sorted array */ +JSValue JS_ArraySort (JSContext *ctx, JSValue arr, JSValue selector) { + JSValue argv[2] = { arr, selector }; + return js_cell_array_sort (ctx, JS_NULL, 2, argv); +} + +/* C API: find(arr, target_or_fn, reverse, from) - returns index or null */ +JSValue JS_ArrayFind (JSContext *ctx, JSValue arr, JSValue target_or_fn, JSValue reverse, JSValue from) { + JSValue argv[4] = { arr, target_or_fn, reverse, from }; + int argc = 4; + if (JS_IsNull (from)) argc = 3; + if (JS_IsNull (reverse)) argc = 2; + return js_cell_array_find (ctx, JS_NULL, argc, argv); +} + +/* C API: arrfor(arr, fn, reverse, exit) - iterate array */ +JSValue JS_ArrFor (JSContext *ctx, JSValue arr, JSValue fn, JSValue reverse, JSValue exit_val) { + JSValue argv[4] = { arr, fn, reverse, exit_val }; + int argc = 4; + if (JS_IsNull (exit_val)) argc = 3; + if (JS_IsNull (reverse)) argc = 2; + return js_cell_array_for (ctx, JS_NULL, argc, argv); +} + +/* C API: reduce(arr, fn, initial, reverse) - reduce array */ +JSValue JS_ArrayReduce (JSContext *ctx, JSValue arr, JSValue fn, JSValue initial, JSValue reverse) { + JSValue argv[4] = { arr, fn, initial, reverse }; + int argc = 4; + if (JS_IsNull (reverse)) argc = 3; + if (JS_IsNull (initial)) argc = 2; + return js_cell_array_reduce (ctx, JS_NULL, argc, argv); +} + +/* ============================================================ + C API Wrappers for Cell Intrinsic Functions + ============================================================ */ + +/* C API: stone(val) - make value immutable */ +JSValue JS_CellStone (JSContext *ctx, JSValue val) { + return js_cell_stone (ctx, JS_NULL, 1, &val); +} + +/* C API: length(val) - get length of array/text/blob */ +JSValue JS_CellLength (JSContext *ctx, JSValue val) { + return js_cell_length (ctx, JS_NULL, 1, &val); +} + +/* C API: reverse(val) - reverse array or text */ +JSValue JS_CellReverse (JSContext *ctx, JSValue val) { + return js_cell_reverse (ctx, JS_NULL, 1, &val); +} + +/* C API: proto(obj) - get prototype */ +JSValue JS_CellProto (JSContext *ctx, JSValue obj) { + return js_cell_proto (ctx, JS_NULL, 1, &obj); +} + +/* C API: splat(val) - convert to array */ +JSValue JS_CellSplat (JSContext *ctx, JSValue val) { + return js_cell_splat (ctx, JS_NULL, 1, &val); +} + +/* C API: meme(obj, deep) - clone object */ +JSValue JS_CellMeme (JSContext *ctx, JSValue obj, JSValue deep) { + JSValue argv[2] = { obj, deep }; + int argc = JS_IsNull (deep) ? 1 : 2; + return js_cell_meme (ctx, JS_NULL, argc, argv); +} + +/* C API: apply(fn, args) - apply function to array of args */ +JSValue JS_CellApply (JSContext *ctx, JSValue fn, JSValue args) { + JSValue argv[2] = { fn, args }; + return js_cell_fn_apply (ctx, JS_NULL, 2, argv); +} + +/* C API: call(fn, this, args...) - call function */ +JSValue JS_CellCall (JSContext *ctx, JSValue fn, JSValue this_val, JSValue args) { + JSValue argv[3] = { fn, this_val, args }; + int argc = JS_IsNull (args) ? 2 : 3; + return js_cell_call (ctx, JS_NULL, argc, argv); +} + +/* C API: modulo(a, b) - modulo operation */ +JSValue JS_CellModulo (JSContext *ctx, JSValue a, JSValue b) { + JSValue argv[2] = { a, b }; + return js_cell_modulo (ctx, JS_NULL, 2, argv); +} + +/* C API: neg(val) - negate number */ +JSValue JS_CellNeg (JSContext *ctx, JSValue val) { + return js_cell_neg (ctx, JS_NULL, 1, &val); +} + +/* C API: not(val) - logical not */ +JSValue JS_CellNot (JSContext *ctx, JSValue val) { + return js_cell_not (ctx, JS_NULL, 1, &val); +} + +/* Text functions */ + +/* C API: text(val) - convert to text */ +JSValue JS_CellText (JSContext *ctx, JSValue val) { + return js_cell_text (ctx, JS_NULL, 1, &val); +} + +/* C API: lower(text) - convert to lowercase */ +JSValue JS_CellLower (JSContext *ctx, JSValue text) { + return js_cell_text_lower (ctx, JS_NULL, 1, &text); +} + +/* C API: upper(text) - convert to uppercase */ +JSValue JS_CellUpper (JSContext *ctx, JSValue text) { + return js_cell_text_upper (ctx, JS_NULL, 1, &text); +} + +/* C API: trim(text, chars) - trim whitespace or specified chars */ +JSValue JS_CellTrim (JSContext *ctx, JSValue text, JSValue chars) { + JSValue argv[2] = { text, chars }; + int argc = JS_IsNull (chars) ? 1 : 2; + return js_cell_text_trim (ctx, JS_NULL, argc, argv); +} + +/* C API: codepoint(text, idx) - get codepoint at index */ +JSValue JS_CellCodepoint (JSContext *ctx, JSValue text, JSValue idx) { + JSValue argv[2] = { text, idx }; + int argc = JS_IsNull (idx) ? 1 : 2; + return js_cell_text_codepoint (ctx, JS_NULL, argc, argv); +} + +/* C API: replace(text, pattern, replacement) - replace in text */ +JSValue JS_CellReplace (JSContext *ctx, JSValue text, JSValue pattern, JSValue replacement) { + JSValue argv[3] = { text, pattern, replacement }; + return js_cell_text_replace (ctx, JS_NULL, 3, argv); +} + +/* C API: search(text, pattern, from) - search in text */ +JSValue JS_CellSearch (JSContext *ctx, JSValue text, JSValue pattern, JSValue from) { + JSValue argv[3] = { text, pattern, from }; + int argc = JS_IsNull (from) ? 2 : 3; + return js_cell_text_search (ctx, JS_NULL, argc, argv); +} + +/* C API: extract(text, from, to) - extract substring + Internally, js_cell_text_extract expects (text, pattern, from, to) + but for simple substring extraction we don't need a pattern */ +JSValue JS_CellExtract (JSContext *ctx, JSValue text, JSValue from, JSValue to) { + if (!JS_IsText (text)) return JS_NULL; + + JSGCRef text_ref; + JS_PushGCRef (ctx, &text_ref); + text_ref.val = text; + + int len = js_string_value_len (text_ref.val); + + int from_idx = 0; + int to_idx = len; + + if (!JS_IsNull (from)) { + if (JS_ToInt32 (ctx, &from_idx, from)) { + JS_PopGCRef (ctx, &text_ref); + return JS_EXCEPTION; + } + if (from_idx < 0) from_idx += len; + if (from_idx < 0) from_idx = 0; + if (from_idx > len) from_idx = len; + } + + if (!JS_IsNull (to)) { + if (JS_ToInt32 (ctx, &to_idx, to)) { + JS_PopGCRef (ctx, &text_ref); + return JS_EXCEPTION; + } + if (to_idx < 0) to_idx += len; + if (to_idx < 0) to_idx = 0; + if (to_idx > len) to_idx = len; + } + + if (from_idx > to_idx) { + JS_PopGCRef (ctx, &text_ref); + return JS_NULL; + } + if (from_idx == to_idx) { + JS_PopGCRef (ctx, &text_ref); + return JS_NewString (ctx, ""); + } + + /* Create result string */ + int result_len = to_idx - from_idx; + JSText *str = js_alloc_string (ctx, result_len); + if (!str) { + JS_PopGCRef (ctx, &text_ref); + return JS_EXCEPTION; + } + for (int i = 0; i < result_len; i++) { + string_put (str, i, js_string_value_get (text_ref.val, from_idx + i)); + } + str->length = result_len; + JS_PopGCRef (ctx, &text_ref); + return pretext_end (ctx, str); +} + +/* C API: character(codepoint) - create single character text */ +JSValue JS_CellCharacter (JSContext *ctx, JSValue codepoint) { + return js_cell_character (ctx, JS_NULL, 1, &codepoint); +} + +/* Number functions */ + +/* C API: number(val) - convert to number */ +JSValue JS_CellNumber (JSContext *ctx, JSValue val) { + return js_cell_number (ctx, JS_NULL, 1, &val); +} + +/* C API: abs(num) - absolute value */ +JSValue JS_CellAbs (JSContext *ctx, JSValue num) { + return js_cell_number_abs (ctx, JS_NULL, 1, &num); +} + +/* C API: sign(num) - sign of number (-1, 0, 1) */ +JSValue JS_CellSign (JSContext *ctx, JSValue num) { + return js_cell_number_sign (ctx, JS_NULL, 1, &num); +} + +/* C API: floor(num) - floor */ +JSValue JS_CellFloor (JSContext *ctx, JSValue num) { + return js_cell_number_floor (ctx, JS_NULL, 1, &num); +} + +/* C API: ceiling(num) - ceiling */ +JSValue JS_CellCeiling (JSContext *ctx, JSValue num) { + return js_cell_number_ceiling (ctx, JS_NULL, 1, &num); +} + +/* C API: round(num) - round to nearest integer */ +JSValue JS_CellRound (JSContext *ctx, JSValue num) { + return js_cell_number_round (ctx, JS_NULL, 1, &num); +} + +/* C API: trunc(num) - truncate towards zero */ +JSValue JS_CellTrunc (JSContext *ctx, JSValue num) { + return js_cell_number_trunc (ctx, JS_NULL, 1, &num); +} + +/* C API: whole(num) - integer part */ +JSValue JS_CellWhole (JSContext *ctx, JSValue num) { + return js_cell_number_whole (ctx, JS_NULL, 1, &num); +} + +/* C API: fraction(num) - fractional part */ +JSValue JS_CellFraction (JSContext *ctx, JSValue num) { + return js_cell_number_fraction (ctx, JS_NULL, 1, &num); +} + +/* C API: min(a, b) - minimum of two numbers */ +JSValue JS_CellMin (JSContext *ctx, JSValue a, JSValue b) { + JSValue argv[2] = { a, b }; + return js_cell_number_min (ctx, JS_NULL, 2, argv); +} + +/* C API: max(a, b) - maximum of two numbers */ +JSValue JS_CellMax (JSContext *ctx, JSValue a, JSValue b) { + JSValue argv[2] = { a, b }; + return js_cell_number_max (ctx, JS_NULL, 2, argv); +} + +/* C API: remainder(a, b) - remainder after division */ +JSValue JS_CellRemainder (JSContext *ctx, JSValue a, JSValue b) { + JSValue argv[2] = { a, b }; + return js_cell_number_remainder (ctx, JS_NULL, 2, argv); +} + +/* Object functions */ + +/* C API: object(proto, props) - create object */ +JSValue JS_CellObject (JSContext *ctx, JSValue proto, JSValue props) { + JSValue argv[2] = { proto, props }; + int argc = JS_IsNull (props) ? 1 : 2; + if (JS_IsNull (proto)) argc = 0; + return js_cell_object (ctx, JS_NULL, argc, argv); +} + +/* C API: format(text, collection, transformer) - string interpolation */ +JSValue JS_CellFormat (JSContext *ctx, JSValue text, JSValue collection, JSValue transformer) { + JSValue argv[3] = { text, collection, transformer }; + int argc = JS_IsNull (transformer) ? 2 : 3; + return js_cell_text_format (ctx, JS_NULL, argc, argv); +} + +/* ============================================================ + Helper Functions for C API + ============================================================ */ + +/* Create an array from a list of JSValues */ +JSValue JS_NewArrayFrom (JSContext *ctx, int count, JSValue *values) { + JSGCRef arr_ref; + JS_PushGCRef (ctx, &arr_ref); + arr_ref.val = JS_NewArray (ctx); + if (JS_IsException (arr_ref.val)) { + JS_PopGCRef (ctx, &arr_ref); + return JS_EXCEPTION; + } + for (int i = 0; i < count; i++) { + if (JS_ArrayPush (ctx, &arr_ref.val, values[i]) < 0) { + JS_PopGCRef (ctx, &arr_ref); + return JS_EXCEPTION; + } + } + JSValue result = arr_ref.val; + JS_PopGCRef (ctx, &arr_ref); + return result; +} + +/* Print a JSValue text to stdout */ +void JS_PrintText (JSContext *ctx, JSValue val) { + if (!JS_IsText (val)) { + /* Try to convert to string first */ + val = JS_ToString (ctx, val); + if (JS_IsException (val) || !JS_IsText (val)) { + printf ("[non-text value]"); + return; + } + } + const char *str = JS_ToCString (ctx, val); + if (str) { + printf ("%s", str); + JS_FreeCString (ctx, str); + } +} + +/* Print a JSValue text to stdout with newline */ +void JS_PrintTextLn (JSContext *ctx, JSValue val) { + JS_PrintText (ctx, val); + printf ("\n"); +} + +/* Format and print - convenience function */ +void JS_PrintFormatted (JSContext *ctx, const char *fmt, int count, JSValue *values) { + JSValue fmt_str = JS_NewString (ctx, fmt); + JSValue arr = JS_NewArrayFrom (ctx, count, values); + JSValue result = JS_CellFormat (ctx, fmt_str, arr, JS_NULL); + JS_PrintText (ctx, result); +} + +static JSValue js_cell_push (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NULL; + + if (!JS_IsArray (argv[0])) return JS_NULL; + + JSGCRef arr_ref; + JS_PushGCRef (ctx, &arr_ref); + arr_ref.val = argv[0]; + + for (int i = 1; i < argc; i++) { + if (js_intrinsic_array_push (ctx, &arr_ref.val, argv[i]) < 0) { + JS_PopGCRef (ctx, &arr_ref); + return JS_EXCEPTION; + } + } + + argv[0] = arr_ref.val; + JS_PopGCRef (ctx, &arr_ref); + return JS_NULL; +} + +static JSValue js_cell_proto (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + (void)this_val; + if (argc < 1) return JS_NULL; + + JSValue obj = argv[0]; + + if (JS_IsArray (obj)) { + return JS_ThrowTypeError (ctx, "arrays do not have prototypes"); + } + + if (!JS_IsObject (obj)) return JS_NULL; + + JSValue proto = JS_GetPrototype (ctx, obj); + if (JS_IsException (proto)) return JS_NULL; + + /* If prototype is Object.prototype, return null */ + if (JS_IsObject (proto)) { + JSValue obj_proto = ctx->class_proto[JS_CLASS_OBJECT]; + if (JS_IsObject (obj_proto) + && JS_VALUE_GET_OBJ (proto) == JS_VALUE_GET_OBJ (obj_proto)) { + return JS_NULL; + } + } + + return proto; +} + +static JSValue js_cell_meme (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + JSValue proto = JS_NULL; + if (argc > 0 && !JS_IsNull (argv[0])) proto = argv[0]; + + JSValue result = JS_NewObjectProto (ctx, proto); + if (JS_IsException (result)) return result; + + if (argc < 2) return result; + + /* Root result across allocating calls */ + JSGCRef result_ref; + JS_PushGCRef (ctx, &result_ref); + result_ref.val = result; + +/* Helper function to apply a single mixin */ +#define APPLY_MIXIN(mix_val) \ + do { \ + if (!JS_IsObject (mix_val) || JS_IsNull (mix_val) || JS_IsArray (mix_val)) \ + break; \ + JSGCRef _mix_ref; \ + JS_PushGCRef (ctx, &_mix_ref); \ + _mix_ref.val = mix_val; \ + JSValue _keys = JS_GetOwnPropertyNames (ctx, _mix_ref.val); \ + if (JS_IsException (_keys)) { \ + JS_PopGCRef (ctx, &_mix_ref); \ + JS_PopGCRef (ctx, &result_ref); \ + return JS_EXCEPTION; \ + } \ + uint32_t _len; \ + if (js_get_length32 (ctx, &_len, _keys)) { \ + JS_PopGCRef (ctx, &_mix_ref); \ + JS_PopGCRef (ctx, &result_ref); \ + return JS_EXCEPTION; \ + } \ + for (uint32_t j = 0; j < _len; j++) { \ + JSValue _key = JS_GetPropertyUint32 (ctx, _keys, j); \ + JSValue val = JS_GetProperty (ctx, _mix_ref.val, _key); \ + if (JS_IsException (val)) { \ + JS_PopGCRef (ctx, &_mix_ref); \ + JS_PopGCRef (ctx, &result_ref); \ + return JS_EXCEPTION; \ + } \ + JS_SetProperty (ctx, result_ref.val, _key, val); \ + } \ + JS_PopGCRef (ctx, &_mix_ref); \ + } while (0) + + /* Process all arguments starting from argv[1] as mixins */ + for (int i = 1; i < argc; i++) { + JSValue mixins = argv[i]; + + if (JS_IsArray (mixins)) { + /* Array of mixins - root the array across calls */ + JSGCRef mixins_ref; + JS_PushGCRef (ctx, &mixins_ref); + mixins_ref.val = mixins; + int64_t len; + if (js_get_length64 (ctx, &len, mixins_ref.val)) { + JS_PopGCRef (ctx, &mixins_ref); + JS_PopGCRef (ctx, &result_ref); + return JS_EXCEPTION; + } + + for (int64_t j = 0; j < len; j++) { + JSValue mix = JS_GetPropertyInt64 (ctx, mixins_ref.val, j); + if (JS_IsException (mix)) { + JS_PopGCRef (ctx, &mixins_ref); + JS_PopGCRef (ctx, &result_ref); + return JS_EXCEPTION; + } + APPLY_MIXIN (mix); + } + JS_PopGCRef (ctx, &mixins_ref); + } else if (JS_IsObject (mixins) && !JS_IsNull (mixins)) { + /* Single mixin object */ + APPLY_MIXIN (mixins); + } + } + +#undef APPLY_MIXIN + + result = result_ref.val; + JS_PopGCRef (ctx, &result_ref); + return result; +} + +/* ============================================================================ + * splat() function - flatten object with prototype chain + * ============================================================================ + */ + +static JSValue js_cell_splat (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NULL; + + JSValue obj = argv[0]; + if (!JS_IsObject (obj) || JS_IsNull (obj)) return JS_NULL; + + /* Root obj, result, current, keys across allocating calls */ + JSGCRef obj_ref, res_ref, cur_ref, keys_ref; + JS_PushGCRef (ctx, &obj_ref); + obj_ref.val = obj; + JS_PushGCRef (ctx, &res_ref); + res_ref.val = JS_NewObject (ctx); + JS_PushGCRef (ctx, &cur_ref); + cur_ref.val = obj_ref.val; /* use rooted value, not stale local */ + JS_PushGCRef (ctx, &keys_ref); + keys_ref.val = JS_NULL; + +#define SPLAT_CLEANUP() do { JS_PopGCRef (ctx, &keys_ref); JS_PopGCRef (ctx, &cur_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &obj_ref); } while(0) + + if (JS_IsException (res_ref.val)) { SPLAT_CLEANUP (); return JS_EXCEPTION; } + + /* Walk prototype chain and collect text keys */ + while (!JS_IsNull (cur_ref.val)) { + keys_ref.val = JS_GetOwnPropertyNames (ctx, cur_ref.val); + if (JS_IsException (keys_ref.val)) { + SPLAT_CLEANUP (); + return JS_EXCEPTION; + } + uint32_t len; + if (js_get_length32 (ctx, &len, keys_ref.val)) { + SPLAT_CLEANUP (); + return JS_EXCEPTION; + } + + for (uint32_t i = 0; i < len; i++) { + JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); + int has = JS_HasProperty (ctx, res_ref.val, key); + if (has < 0) { + SPLAT_CLEANUP (); + return JS_EXCEPTION; + } + if (!has) { + JSValue val = JS_GetProperty (ctx, cur_ref.val, key); + if (JS_IsException (val)) { + SPLAT_CLEANUP (); + return JS_EXCEPTION; + } + int tag = JS_VALUE_GET_TAG (val); + if (JS_IsObject (val) || JS_IsNumber (val) || tag == JS_TAG_STRING + || tag == JS_TAG_STRING_IMM || tag == JS_TAG_BOOL) { + JS_SetProperty (ctx, res_ref.val, key, val); + } + } + } + + cur_ref.val = JS_GetPrototype (ctx, cur_ref.val); + } + + /* Call to_data if present */ + JSValue to_data = JS_GetPropertyStr (ctx, obj_ref.val, "to_data"); + if (JS_IsFunction (to_data)) { + JSValue args[1] = { res_ref.val }; + JSValue extra = JS_Call (ctx, to_data, obj_ref.val, 1, args); + if (!JS_IsException (extra) && JS_IsObject (extra)) { + keys_ref.val = JS_GetOwnPropertyNames (ctx, extra); + if (!JS_IsException (keys_ref.val)) { + uint32_t len; + if (!js_get_length32 (ctx, &len, keys_ref.val)) { + for (uint32_t i = 0; i < len; i++) { + JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); + JSValue val = JS_GetProperty (ctx, extra, key); + JS_SetProperty (ctx, res_ref.val, key, val); + } + } + } + } + } + + JSValue result = res_ref.val; + SPLAT_CLEANUP (); + return result; +} +#undef SPLAT_CLEANUP + +/* ============================================================================ + * length() function + * ============================================================================ + */ + +static JSValue js_cell_length (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NULL; + + JSValue val = argv[0]; + + /* null returns null */ + if (JS_IsNull (val)) return JS_NULL; + + /* Functions return arity (accessed directly, not via properties) */ + if (JS_IsFunction (val)) { + JSFunction *f = JS_VALUE_GET_FUNCTION (val); + return JS_NewInt32 (ctx, f->length); + } + + int tag = JS_VALUE_GET_TAG (val); + + /* Strings return codepoint count */ + if (tag == JS_TAG_STRING_IMM) { + return JS_NewInt32 (ctx, MIST_GetImmediateASCIILen (val)); + } + if (tag == JS_TAG_STRING) { + JSText *p = JS_VALUE_GET_STRING (val); + return JS_NewInt32 (ctx, (int)JSText_len (p)); + } + + /* Check for blob */ + blob *bd = js_get_blob (ctx, val); + if (bd) return JS_NewInt64 (ctx, bd->length); + + /* Arrays return element count */ + if (JS_IsArray (val)) { + JSArray *arr = JS_VALUE_GET_ARRAY (val); + return JS_NewInt32 (ctx, arr->len); + } + + /* Objects with length property */ + if (JS_IsObject (val)) { + JSValue len = JS_GetPropertyStr (ctx, val, "length"); + if (!JS_IsException (len) && !JS_IsNull (len)) { + if (JS_IsFunction (len)) { + JSValue result = JS_Call (ctx, len, val, 0, NULL); + return result; + } + if (JS_VALUE_IS_NUMBER (len)) return len; + } else if (JS_IsException (len)) { + return len; + } + } + + return JS_NULL; +} + +/* ============================================================================ + * call() function - call a function with explicit this and arguments + * ============================================================================ + */ + +/* call(func, this_val, args_array) */ +static JSValue js_cell_call (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) + return JS_ThrowTypeError (ctx, "call requires a function argument"); + + JSGCRef func_ref, this_ref, args_ref; + JS_PushGCRef (ctx, &func_ref); + JS_PushGCRef (ctx, &this_ref); + JS_PushGCRef (ctx, &args_ref); + func_ref.val = argv[0]; + this_ref.val = argc >= 2 ? argv[1] : JS_NULL; + args_ref.val = argc >= 3 ? argv[2] : JS_NULL; + + if (!JS_IsFunction (func_ref.val)) { + JS_PopGCRef (ctx, &args_ref); + JS_PopGCRef (ctx, &this_ref); + JS_PopGCRef (ctx, &func_ref); + return JS_ThrowTypeError (ctx, "first argument must be a function"); + } + + if (argc < 3 || JS_IsNull (args_ref.val)) { + JSValue ret = JS_CallInternal (ctx, func_ref.val, this_ref.val, 0, NULL, 0); + JS_PopGCRef (ctx, &args_ref); + JS_PopGCRef (ctx, &this_ref); + JS_PopGCRef (ctx, &func_ref); + return ret; + } + + if (!JS_IsArray (args_ref.val)) { + JS_PopGCRef (ctx, &args_ref); + JS_PopGCRef (ctx, &this_ref); + JS_PopGCRef (ctx, &func_ref); + return JS_ThrowTypeError (ctx, "third argument must be an array"); + } + + uint32_t len; + JSValue *tab = build_arg_list (ctx, &len, &args_ref.val); + if (!tab) { + JS_PopGCRef (ctx, &args_ref); + JS_PopGCRef (ctx, &this_ref); + JS_PopGCRef (ctx, &func_ref); + return JS_EXCEPTION; + } + + JSValue ret = JS_CallInternal (ctx, func_ref.val, this_ref.val, len, tab, 0); + free_arg_list (ctx, tab, len); + JS_PopGCRef (ctx, &args_ref); + JS_PopGCRef (ctx, &this_ref); + JS_PopGCRef (ctx, &func_ref); + return ret; +} + +/* ============================================================================ + * is_* type checking functions + * ============================================================================ + */ + +/* is_array(val) */ +static JSValue js_cell_is_array (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_FALSE; + return JS_NewBool (ctx, JS_IsArray (argv[0])); +} + +/* is_blob(val) */ +static JSValue js_cell_is_blob (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_FALSE; + return JS_NewBool (ctx, js_get_blob (ctx, argv[0]) != NULL); +} + +/* is_data(val) - check if object is a plain object (data record) */ +static JSValue js_cell_is_data (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_FALSE; + JSValue val = argv[0]; + if (!JS_IsObject (val)) return JS_FALSE; + if (JS_IsArray (val)) return JS_FALSE; + if (JS_IsFunction (val)) return JS_FALSE; + if (js_get_blob (ctx, val)) return JS_FALSE; + /* Check if it's a plain object (prototype is Object.prototype or null) */ + return JS_TRUE; +} + +/* is_function(val) */ +static JSValue js_cell_is_function (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_FALSE; + return JS_NewBool (ctx, JS_IsFunction (argv[0])); +} + +/* is_logical(val) - check if value is a boolean (true or false) */ +static JSValue js_cell_is_logical (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_FALSE; + return JS_NewBool (ctx, JS_VALUE_GET_TAG (argv[0]) == JS_TAG_BOOL); +} + +/* is_integer(val) */ +static JSValue js_cell_is_integer (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_FALSE; + JSValue val = argv[0]; + int tag = JS_VALUE_GET_TAG (val); + if (tag == JS_TAG_INT) return JS_TRUE; + if (tag == JS_TAG_FLOAT64) { + double d = JS_VALUE_GET_FLOAT64 (val); + return JS_NewBool (ctx, isfinite (d) && trunc (d) == d); + } + return JS_FALSE; +} + +/* is_null(val) */ +static JSValue js_cell_is_null (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_FALSE; + return JS_NewBool (ctx, JS_IsNull (argv[0])); +} + +/* is_number(val) */ +static JSValue js_cell_is_number (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_FALSE; + return JS_NewBool (ctx, JS_IsNumber (argv[0])); +} + +/* is_object(val) - true for non-array, non-null objects */ +static JSValue js_cell_is_object (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_FALSE; + JSValue val = argv[0]; + if (!JS_IsObject (val)) return JS_FALSE; + if (JS_IsArray (val)) return JS_FALSE; + return JS_TRUE; +} + +/* is_stone(val) - check if value is immutable */ +static JSValue js_cell_is_stone (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + (void)this_val; + if (argc < 1) return JS_FALSE; + + return JS_NewBool (ctx, JS_IsStone (argv[0])); +} + +/* is_text(val) */ +static JSValue js_cell_is_text (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_FALSE; + int tag = JS_VALUE_GET_TAG (argv[0]); + return JS_NewBool (ctx, tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM); +} + +/* is_proto(val, master) - check if val has master in prototype chain */ +static JSValue js_cell_is_proto (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) return JS_FALSE; + JSValue val = argv[0]; + JSValue master = argv[1]; + + if (!JS_IsObject (val) || JS_IsNull (master)) return JS_FALSE; + + /* Walk prototype chain */ + JSValue proto = JS_GetPrototype (ctx, val); + while (!JS_IsNull (proto) && !JS_IsException (proto)) { + /* If master is a function with prototype property, check that */ + if (JS_IsFunction (master)) { + JSValue master_proto = JS_GetPropertyStr (ctx, master, "prototype"); + if (!JS_IsException (master_proto) && !JS_IsNull (master_proto)) { + JSRecord *p1 = JS_VALUE_GET_OBJ (proto); + JSRecord *p2 = JS_VALUE_GET_OBJ (master_proto); + if (p1 == p2) { + return JS_TRUE; + } + } else if (!JS_IsException (master_proto)) { + } + } + /* Also check if proto == master directly */ + if (JS_IsObject (master)) { + JSRecord *p1 = JS_VALUE_GET_OBJ (proto); + JSRecord *p2 = JS_VALUE_GET_OBJ (master); + if (p1 == p2) { + return JS_TRUE; + } + } + + JSValue next = JS_GetPrototype (ctx, proto); + proto = next; + } + if (JS_IsException (proto)) return proto; + return JS_FALSE; +} + +static const char *const native_error_name[JS_NATIVE_ERROR_COUNT] = { + "EvalError", + "RangeError", + "ReferenceError", + "SyntaxError", + "TypeError", + "URIError", + "InternalError", + "AggregateError", +}; + +/* Minimum amount of objects to be able to compile code and display + error messages. No JSAtom should be allocated by this function. */ +static void JS_AddIntrinsicBasicObjects (JSContext *ctx) { + JSGCRef proto_ref; + int i; + + JS_PushGCRef (ctx, &proto_ref); + + ctx->class_proto[JS_CLASS_OBJECT] = JS_NewObjectProto (ctx, JS_NULL); + + ctx->class_proto[JS_CLASS_ERROR] = JS_NewObject (ctx); + + for (i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { + proto_ref.val = JS_NewObjectProto (ctx, ctx->class_proto[JS_CLASS_ERROR]); + JS_SetPropertyInternal (ctx, proto_ref.val, JS_KEY_name, JS_NewAtomString (ctx, native_error_name[i])); + JS_SetPropertyInternal (ctx, proto_ref.val, JS_KEY_message, JS_KEY_empty); + ctx->native_error_proto[i] = proto_ref.val; + } + + JS_PopGCRef (ctx, &proto_ref); +} + +/* logical(val) — false for 0/false/"false"/null, true for 1/true/"true", null otherwise */ +static JSValue js_cell_logical(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) return JS_NULL; + JSValue v = argv[0]; + if (JS_IsNull(v) || (JS_IsInt(v) && JS_VALUE_GET_INT(v) == 0) || + (JS_IsBool(v) && !JS_VALUE_GET_BOOL(v))) return JS_FALSE; + if ((JS_IsInt(v) && JS_VALUE_GET_INT(v) == 1) || + (JS_IsBool(v) && JS_VALUE_GET_BOOL(v))) return JS_TRUE; + if (JS_IsText(v)) { + char buf[8]; + JS_KeyGetStr(ctx, buf, sizeof(buf), v); + if (strcmp(buf, "false") == 0) return JS_FALSE; + if (strcmp(buf, "true") == 0) return JS_TRUE; + } + return JS_NULL; +} + +/* starts_with(str, prefix) — search(str, prefix) == 0 */ +static JSValue js_cell_starts_with(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) return JS_NULL; + JSValue args[3] = { argv[0], argv[1], JS_NULL }; + JSValue pos = js_cell_text_search(ctx, JS_NULL, 2, args); + if (JS_IsInt(pos) && JS_VALUE_GET_INT(pos) == 0) return JS_TRUE; + return JS_FALSE; +} + +/* ends_with(str, suffix) — search(str, suffix, -length(suffix)) != null */ +static JSValue js_cell_ends_with(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) return JS_NULL; + JSValue len_val = js_cell_length(ctx, JS_NULL, 1, &argv[1]); + int slen = JS_IsInt(len_val) ? JS_VALUE_GET_INT(len_val) : 0; + JSValue offset = JS_NewInt32(ctx, -slen); + JSValue args[3] = { argv[0], argv[1], offset }; + JSValue pos = js_cell_text_search(ctx, JS_NULL, 3, args); + if (!JS_IsNull(pos)) return JS_TRUE; + return JS_FALSE; +} + +/* every(arr, pred) — find(arr, x => !pred(x)) == null */ +static JSValue js_cell_every(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) return JS_NULL; + if (!JS_IsArray(argv[0]) || !JS_IsFunction(argv[1])) return JS_NULL; + JSGCRef arr_ref, fn_ref; + JS_PushGCRef(ctx, &arr_ref); + JS_PushGCRef(ctx, &fn_ref); + arr_ref.val = argv[0]; + fn_ref.val = argv[1]; + JSArray *arr = JS_VALUE_GET_ARRAY(arr_ref.val); + for (int i = 0; i < arr->len; i++) { + JSValue elem = arr->values[i]; + JSValue r = JS_CallInternal(ctx, fn_ref.val, JS_NULL, 1, &elem, 0); + arr = JS_VALUE_GET_ARRAY(arr_ref.val); + if (JS_IsException(r)) { + JS_PopGCRef(ctx, &fn_ref); + JS_PopGCRef(ctx, &arr_ref); + return r; + } + if (!JS_ToBool(ctx, r)) { + JS_PopGCRef(ctx, &fn_ref); + JS_PopGCRef(ctx, &arr_ref); + return JS_FALSE; + } + } + JS_PopGCRef(ctx, &fn_ref); + JS_PopGCRef(ctx, &arr_ref); + return JS_TRUE; +} + +/* some(arr, pred) — find(arr, pred) != null */ +static JSValue js_cell_some(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 2) return JS_NULL; + JSValue r = js_cell_array_find(ctx, JS_NULL, argc, argv); + if (JS_IsException(r)) return r; + if (!JS_IsNull(r)) return JS_TRUE; + return JS_FALSE; +} + +/* GC-SAFE: Helper to set a global function. Creates function first, then reads + ctx->global_obj to ensure it's not stale if GC ran during function creation. */ +static void js_set_global_cfunc(JSContext *ctx, const char *name, JSCFunction *func, int length) { + JSGCRef ref; + JS_PushGCRef(ctx, &ref); + ref.val = JS_NewCFunction(ctx, func, name, length); + JS_SetPropertyStr(ctx, ctx->global_obj, name, ref.val); + JS_PopGCRef(ctx, &ref); +} + +static void JS_AddIntrinsicBaseObjects (JSContext *ctx) { + JSValue obj1; + + ctx->throw_type_error = JS_NewCFunction (ctx, js_throw_type_error, NULL, 0); + ctx->global_obj = JS_NewObject (ctx); + + /* Error */ + obj1 = JS_NewCFunctionMagic (ctx, js_error_constructor, "Error", 1, JS_CFUNC_generic_magic, -1); + JS_SetPropertyStr (ctx, ctx->global_obj, "Error", obj1); + + #define REGISTER_ERROR(idx, name) do { \ + JSValue func_obj = JS_NewCFunctionMagic(ctx, js_error_constructor, name, 1 + ((idx) == JS_AGGREGATE_ERROR), JS_CFUNC_generic_magic, (idx)); \ + JS_SetPropertyStr(ctx, ctx->global_obj, name, func_obj); \ + } while(0) + REGISTER_ERROR(0, "EvalError"); + REGISTER_ERROR(1, "RangeError"); + REGISTER_ERROR(2, "ReferenceError"); + REGISTER_ERROR(3, "SyntaxError"); + REGISTER_ERROR(4, "TypeError"); + REGISTER_ERROR(5, "URIError"); + REGISTER_ERROR(6, "InternalError"); + REGISTER_ERROR(7, "AggregateError"); + #undef REGISTER_ERROR + + /* Cell Script global functions: text, number, array, object, fn */ + { + JSValue text_func = JS_NewCFunction (ctx, js_cell_text, "text", 3); + JS_SetPropertyStr (ctx, ctx->global_obj, "text", text_func); + + JSValue number_func = JS_NewCFunction (ctx, js_cell_number, "number", 2); + JS_SetPropertyStr (ctx, ctx->global_obj, "number", number_func); + + JSValue array_func = JS_NewCFunction (ctx, js_cell_array, "array", 4); + JS_SetPropertyStr (ctx, ctx->global_obj, "array", array_func); + + JSValue object_func = JS_NewCFunction (ctx, js_cell_object, "object", 2); + JS_SetPropertyStr (ctx, ctx->global_obj, "object", object_func); + + /* Blob intrinsic type */ + { + JSClassDef blob_class = { + .class_name = "blob", + .finalizer = js_blob_finalizer, + }; + JS_NewClass (ctx, JS_CLASS_BLOB, &blob_class); + ctx->class_proto[JS_CLASS_BLOB] = JS_NewObject (ctx); + JS_SetPropertyFunctionList (ctx, ctx->class_proto[JS_CLASS_BLOB], js_blob_proto_funcs, countof (js_blob_proto_funcs)); + + JSValue blob_ctor = JS_NewCFunction2 (ctx, js_blob_constructor, "blob", 3, JS_CFUNC_generic, 0); + JS_SetPropertyStr (ctx, ctx->global_obj, "blob", blob_ctor); + } + + /* Core functions - using GC-safe helper */ + js_set_global_cfunc(ctx, "eval", js_cell_eval, 2); + js_set_global_cfunc(ctx, "mach_eval", js_mach_eval, 2); + js_set_global_cfunc(ctx, "stone", js_cell_stone, 1); + js_set_global_cfunc(ctx, "length", js_cell_length, 1); + js_set_global_cfunc(ctx, "call", js_cell_call, 3); + + /* is_* type checking functions */ + js_set_global_cfunc(ctx, "is_array", js_cell_is_array, 1); + js_set_global_cfunc(ctx, "is_blob", js_cell_is_blob, 1); + js_set_global_cfunc(ctx, "is_data", js_cell_is_data, 1); + js_set_global_cfunc(ctx, "is_function", js_cell_is_function, 1); + js_set_global_cfunc(ctx, "is_logical", js_cell_is_logical, 1); + js_set_global_cfunc(ctx, "is_integer", js_cell_is_integer, 1); + js_set_global_cfunc(ctx, "is_null", js_cell_is_null, 1); + js_set_global_cfunc(ctx, "is_number", js_cell_is_number, 1); + js_set_global_cfunc(ctx, "is_object", js_cell_is_object, 1); + js_set_global_cfunc(ctx, "is_stone", js_cell_is_stone, 1); + js_set_global_cfunc(ctx, "is_text", js_cell_is_text, 1); + js_set_global_cfunc(ctx, "is_proto", js_cell_is_proto, 2); + + /* Utility functions */ + js_set_global_cfunc(ctx, "apply", js_cell_fn_apply, 2); + js_set_global_cfunc(ctx, "replace", js_cell_text_replace, 4); + js_set_global_cfunc(ctx, "lower", js_cell_text_lower, 1); + js_set_global_cfunc(ctx, "upper", js_cell_text_upper, 1); + js_set_global_cfunc(ctx, "trim", js_cell_text_trim, 2); + js_set_global_cfunc(ctx, "codepoint", js_cell_text_codepoint, 1); + js_set_global_cfunc(ctx, "search", js_cell_text_search, 3); + js_set_global_cfunc(ctx, "extract", js_cell_text_extract, 4); + js_set_global_cfunc(ctx, "format", js_cell_text_format, 3); + js_set_global_cfunc(ctx, "reduce", js_cell_array_reduce, 4); + js_set_global_cfunc(ctx, "arrfor", js_cell_array_for, 4); + js_set_global_cfunc(ctx, "find", js_cell_array_find, 4); + js_set_global_cfunc(ctx, "filter", js_cell_array_filter, 2); + js_set_global_cfunc(ctx, "sort", js_cell_array_sort, 2); + + /* Number utility functions */ + js_set_global_cfunc(ctx, "whole", js_cell_number_whole, 1); + js_set_global_cfunc(ctx, "fraction", js_cell_number_fraction, 1); + js_set_global_cfunc(ctx, "floor", js_cell_number_floor, 2); + js_set_global_cfunc(ctx, "ceiling", js_cell_number_ceiling, 2); + js_set_global_cfunc(ctx, "abs", js_cell_number_abs, 1); + js_set_global_cfunc(ctx, "round", js_cell_number_round, 2); + js_set_global_cfunc(ctx, "sign", js_cell_number_sign, 1); + js_set_global_cfunc(ctx, "trunc", js_cell_number_trunc, 2); + js_set_global_cfunc(ctx, "min", js_cell_number_min, 2); + js_set_global_cfunc(ctx, "max", js_cell_number_max, 2); + js_set_global_cfunc(ctx, "remainder", js_cell_number_remainder, 2); + js_set_global_cfunc(ctx, "character", js_cell_character, 2); + js_set_global_cfunc(ctx, "modulo", js_cell_modulo, 2); + js_set_global_cfunc(ctx, "neg", js_cell_neg, 1); + js_set_global_cfunc(ctx, "not", js_cell_not, 1); + js_set_global_cfunc(ctx, "reverse", js_cell_reverse, 1); + js_set_global_cfunc(ctx, "proto", js_cell_proto, 1); + js_set_global_cfunc(ctx, "splat", js_cell_splat, 1); + + /* pi - mathematical constant (no GC concern for immediate float) */ + JS_SetPropertyStr(ctx, ctx->global_obj, "pi", + JS_NewFloat64(ctx, 3.14159265358979323846264338327950288419716939937510)); + + js_set_global_cfunc(ctx, "push", js_cell_push, 2); + js_set_global_cfunc(ctx, "pop", js_cell_pop, 1); + js_set_global_cfunc(ctx, "meme", js_cell_meme, 2); + + /* Engine builtins (normally from engine.cm, needed for --mach-run) */ + js_set_global_cfunc(ctx, "logical", js_cell_logical, 1); + js_set_global_cfunc(ctx, "starts_with", js_cell_starts_with, 2); + js_set_global_cfunc(ctx, "ends_with", js_cell_ends_with, 2); + js_set_global_cfunc(ctx, "every", js_cell_every, 2); + js_set_global_cfunc(ctx, "some", js_cell_some, 2); + + /* fn record with apply property */ + { + JSGCRef fn_ref; + JS_PushGCRef(ctx, &fn_ref); + fn_ref.val = JS_NewObject(ctx); + JSValue apply_fn = JS_NewCFunction(ctx, js_cell_fn_apply, "apply", 2); + JS_SetPropertyStr(ctx, fn_ref.val, "apply", apply_fn); + JS_SetPropertyStr(ctx, ctx->global_obj, "fn", fn_ref.val); + JS_PopGCRef(ctx, &fn_ref); + } + + /* I/O functions */ + js_set_global_cfunc(ctx, "print", js_print, -1); /* variadic: length < 0 means no arg limit */ + js_set_global_cfunc(ctx, "stacktrace", js_stacktrace, 0); + } +} + +#define STRLEN(s) (sizeof (s) / sizeof (s[0])) +#define CSTR "" + +static inline void key_to_buf (JSContext *ctx, JSValue key, char *dst, int cap, const char *fallback) { + if (JS_IsNull (key)) { + strncpy (dst, fallback, cap); + dst[cap - 1] = 0; + return; + } + JS_KeyGetStr (ctx, dst, cap, key); + if (dst[0] == 0) { + strncpy (dst, fallback, cap); + dst[cap - 1] = 0; + } +} + +void js_debug_info (JSContext *js, JSValue fn, js_debug *dbg) { + *dbg = (js_debug){ 0 }; + + if (!JS_IsFunction (fn)) return; + + JSFunction *f = JS_VALUE_GET_FUNCTION (fn); + dbg->unique = (int)(uintptr_t)f; + + JSValue name_key = JS_NULL; + if (!JS_IsNull (f->name)) + name_key = f->name; + else if (f->kind == JS_FUNC_KIND_BYTECODE && f->u.func.function_bytecode) + name_key = f->u.func.function_bytecode->func_name; + + key_to_buf (js, name_key, dbg->name, sizeof (dbg->name), ""); + + if (f->kind == JS_FUNC_KIND_BYTECODE) { + JSFunctionBytecode *b = f->u.func.function_bytecode; + key_to_buf (js, b->debug.filename, dbg->filename, sizeof (dbg->filename), "unknown"); + dbg->what = "JS"; + dbg->closure_n = b->closure_var_count; + dbg->param_n = b->arg_count; + dbg->vararg = 1; + dbg->source = (const uint8_t *)b->debug.source; + dbg->srclen = b->debug.source_len; + dbg->line = 0; /* see below */ + return; + } + + if (f->kind == JS_FUNC_KIND_C || f->kind == JS_FUNC_KIND_C_DATA) { + strncpy (dbg->filename, "", sizeof (dbg->filename)); + dbg->filename[sizeof (dbg->filename) - 1] = 0; + dbg->what = "C"; + dbg->param_n = f->length; + dbg->vararg = 1; + dbg->line = 0; + dbg->source = (const uint8_t *)CSTR; + dbg->srclen = STRLEN (CSTR); + } +} + +void js_debug_sethook (JSContext *ctx, js_hook hook, int type, void *user) { + ctx->trace_hook = hook; + ctx->trace_type = type; + ctx->trace_data = user; +} + +uint32_t js_debugger_stack_depth (JSContext *ctx) { + uint32_t stack_index = 0; + JSStackFrame *sf = ctx->current_stack_frame; + while (sf != NULL) { + sf = sf->prev_frame; + stack_index++; + } + return stack_index; +} + +JSValue js_debugger_backtrace_fns (JSContext *ctx, const uint8_t *cur_pc) { + JSValue ret = JS_NewArray (ctx); + JSStackFrame *sf; + uint32_t stack_index = 0; + + for (sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) { + uint32_t id = stack_index++; + JS_SetPropertyUint32 (ctx, ret, id, sf->cur_func); + } + return ret; +} + +JSValue js_debugger_build_backtrace (JSContext *ctx, const uint8_t *cur_pc) { + JSStackFrame *sf; + const char *func_name_str; + JSFunction *f; + JSValue ret = JS_NewArray (ctx); + uint32_t stack_index = 0; + + for (sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) { + JSValue current_frame = JS_NewObject (ctx); + + uint32_t id = stack_index++; + JS_SetPropertyStr (ctx, current_frame, "id", JS_NewUint32 (ctx, id)); + + func_name_str = get_func_name (ctx, sf->cur_func); + if (!func_name_str || func_name_str[0] == '\0') + JS_SetPropertyStr (ctx, current_frame, "name", JS_NewString (ctx, "")); + else + JS_SetPropertyStr (ctx, current_frame, "name", JS_NewString (ctx, func_name_str)); + JS_FreeCString (ctx, func_name_str); + + if (JS_VALUE_GET_TAG (sf->cur_func) == JS_TAG_FUNCTION) { + f = JS_VALUE_GET_FUNCTION (sf->cur_func); + if (f->kind == JS_FUNC_KIND_BYTECODE) { + JSFunctionBytecode *b; + int line_num1; + + b = f->u.func.function_bytecode; + if (b->has_debug) { + const uint8_t *pc = sf != ctx->current_stack_frame || !cur_pc + ? sf->cur_pc + : cur_pc; + int col_num; + line_num1 + = find_line_num (ctx, b, pc - b->byte_code_buf - 1, &col_num); + JS_SetPropertyStr (ctx, current_frame, "filename", b->debug.filename); + if (line_num1 != -1) + JS_SetPropertyStr (ctx, current_frame, "line", JS_NewUint32 (ctx, line_num1)); + } + } else { + JS_SetPropertyStr (ctx, current_frame, "name", JS_NewString (ctx, "(native)")); + } + } else { + JS_SetPropertyStr (ctx, current_frame, "name", JS_NewString (ctx, "(native)")); + } + JS_SetPropertyUint32 (ctx, ret, id, current_frame); + } + return ret; +} + +JSValue js_debugger_fn_info (JSContext *ctx, JSValue fn) { + JSValue ret = JS_NewObject (ctx); + if (!js_is_bytecode_function (fn)) goto done; + + JSFunction *f = JS_VALUE_GET_FUNCTION (fn); + JSFunctionBytecode *b = f->u.func.function_bytecode; + char atom_buf[KEY_GET_STR_BUF_SIZE]; + const char *str; + int i; + + // Function name + if (!JS_IsNull (b->func_name)) { + str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), b->func_name); + JS_SetPropertyStr (ctx, ret, "name", JS_NewString (ctx, str)); + } + + // File location info + if (b->has_debug && !JS_IsNull (b->debug.filename)) { + int line_num, col_num; + str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), b->debug.filename); + line_num = find_line_num (ctx, b, -1, &col_num); + JS_SetPropertyStr (ctx, ret, "filename", JS_NewString (ctx, str)); + JS_SetPropertyStr (ctx, ret, "line", JS_NewInt32 (ctx, line_num)); + JS_SetPropertyStr (ctx, ret, "column", JS_NewInt32 (ctx, col_num)); + } + + // Arguments + if (b->arg_count && b->vardefs) { + JSValue args_array = JS_NewArray (ctx); + for (i = 0; i < b->arg_count; i++) { + str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), b->vardefs[i].var_name); + JS_SetPropertyUint32 (ctx, args_array, i, JS_NewString (ctx, str)); + } + JS_SetPropertyStr (ctx, ret, "args", args_array); + } + + // Local variables + if (b->var_count && b->vardefs) { + JSValue locals_array = JS_NewArray (ctx); + for (i = 0; i < b->var_count; i++) { + JSVarDef *vd = &b->vardefs[b->arg_count + i]; + JSValue local_obj = JS_NewObject (ctx); + + str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), vd->var_name); + JS_SetPropertyStr (ctx, local_obj, "name", JS_NewString (ctx, str)); + + const char *var_type = vd->var_kind == JS_VAR_CATCH ? "catch" + : (vd->var_kind == JS_VAR_FUNCTION_DECL + || vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) + ? "function" + : vd->is_const ? "const" + : vd->is_lexical ? "let" + : "var"; + JS_SetPropertyStr (ctx, local_obj, "type", JS_NewString (ctx, var_type)); + JS_SetPropertyStr (ctx, local_obj, "index", JS_NewInt32 (ctx, i)); + + if (vd->scope_level) { + JS_SetPropertyStr (ctx, local_obj, "scope_level", JS_NewInt32 (ctx, vd->scope_level)); + JS_SetPropertyStr (ctx, local_obj, "scope_next", JS_NewInt32 (ctx, vd->scope_next)); + } + + JS_SetPropertyUint32 (ctx, locals_array, i, local_obj); + } + JS_SetPropertyStr (ctx, ret, "locals", locals_array); + } + + // Closure variables + if (b->closure_var_count) { + JSValue closure_array = JS_NewArray (ctx); + for (i = 0; i < b->closure_var_count; i++) { + JSClosureVar *cv = &b->closure_var[i]; + JSValue closure_obj = JS_NewObject (ctx); + + str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), cv->var_name); + JS_SetPropertyStr (ctx, closure_obj, "name", JS_NewString (ctx, str)); + JS_SetPropertyStr (ctx, closure_obj, "is_local", JS_NewBool (ctx, cv->is_local)); + JS_SetPropertyStr (ctx, closure_obj, "is_arg", JS_NewBool (ctx, cv->is_arg)); + JS_SetPropertyStr (ctx, closure_obj, "var_idx", JS_NewInt32 (ctx, cv->var_idx)); + + const char *var_type = cv->is_const ? "const" + : cv->is_lexical ? "let" + : "var"; + JS_SetPropertyStr (ctx, closure_obj, "type", JS_NewString (ctx, var_type)); + + JS_SetPropertyUint32 (ctx, closure_array, i, closure_obj); + } + JS_SetPropertyStr (ctx, ret, "closure_vars", closure_array); + } + + // Stack size + JS_SetPropertyStr (ctx, ret, "stack_size", JS_NewInt32 (ctx, b->stack_size)); + +done: + return ret; +} + +// Opcode names array for debugger +static const char *opcode_names[] = { +#define FMT(f) +#define DEF(id, size, n_pop, n_push, f) #id, +#define def(id, size, n_pop, n_push, f) +#include "quickjs-opcode.h" +#undef def +#undef DEF +#undef FMT +}; + +JSValue js_debugger_fn_bytecode (JSContext *ctx, JSValue fn) { + if (!js_is_bytecode_function (fn)) return JS_NULL; + + JSFunction *f = JS_VALUE_GET_FUNCTION (fn); + JSFunctionBytecode *b = f->u.func.function_bytecode; + JSValue ret = JS_NewArray (ctx); + + const uint8_t *tab = b->byte_code_buf; + int len = b->byte_code_len; + int pos = 0; + int idx = 0; + BOOL use_short_opcodes = TRUE; + char opcode_str[256]; + + while (pos < len) { + int op = tab[pos]; + const JSOpCode *oi; + + if (use_short_opcodes) + oi = &short_opcode_info (op); + else + oi = &opcode_info[op]; + + int size = oi->size; + if (pos + size > len) { break; } + + if (op >= sizeof (opcode_names) / sizeof (opcode_names[0])) { + snprintf (opcode_str, sizeof (opcode_str), "unknown"); + } else { + const char *opcode_name = opcode_names[op]; + snprintf (opcode_str, sizeof (opcode_str), "%s", opcode_name); + + // Add arguments based on opcode format + int arg_pos = pos + 1; + switch (oi->fmt) { + case OP_FMT_none_int: + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %d", + op - OP_push_0); + break; + case OP_FMT_npopx: + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %d", + op - OP_call0); + break; + case OP_FMT_u8: + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u", + get_u8 (tab + arg_pos)); + break; + case OP_FMT_i8: + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %d", + get_i8 (tab + arg_pos)); + break; + case OP_FMT_u16: + case OP_FMT_npop: + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u", + get_u16 (tab + arg_pos)); + break; + case OP_FMT_npop_u16: + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u %u", + get_u16 (tab + arg_pos), + get_u16 (tab + arg_pos + 2)); + break; + case OP_FMT_i16: + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %d", + get_i16 (tab + arg_pos)); + break; + case OP_FMT_i32: + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %d", + get_i32 (tab + arg_pos)); + break; + case OP_FMT_u32: + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u", + get_u32 (tab + arg_pos)); + break; +#if SHORT_OPCODES + case OP_FMT_label8: + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u", + get_i8 (tab + arg_pos) + arg_pos); + break; + case OP_FMT_label16: + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u", + get_i16 (tab + arg_pos) + arg_pos); + break; + case OP_FMT_const8: + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u", + get_u8 (tab + arg_pos)); + break; +#endif + case OP_FMT_const: + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u", + get_u32 (tab + arg_pos)); + break; + case OP_FMT_label: + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u", + get_u32 (tab + arg_pos) + arg_pos); + break; + case OP_FMT_label_u16: + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u %u", + get_u32 (tab + arg_pos) + arg_pos, + get_u16 (tab + arg_pos + 4)); + break; + case OP_FMT_key: { + /* Key operand is a cpool index */ + uint32_t key_idx = get_u32 (tab + arg_pos); + if (key_idx < b->cpool_count) { + JSValue key = b->cpool[key_idx]; + const char *key_str = JS_ToCString (ctx, key); + if (key_str) { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " key:%s", + key_str); + JS_FreeCString (ctx, key_str); + } else { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " key[%u]", + key_idx); + } + } else { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " key[%u]", + key_idx); + } + } break; + case OP_FMT_key_u8: { + uint32_t cpool_idx = get_u32 (tab + arg_pos); + const char *key_str = NULL; + if (cpool_idx < b->cpool_count) + key_str = JS_ToCString (ctx, b->cpool[cpool_idx]); + if (key_str) { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %s %d", + key_str, + get_u8 (tab + arg_pos + 4)); + JS_FreeCString (ctx, key_str); + } else { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " cpool[%u] %d", + cpool_idx, + get_u8 (tab + arg_pos + 4)); + } + } break; + case OP_FMT_key_u16: { + uint32_t cpool_idx = get_u32 (tab + arg_pos); + const char *key_str = NULL; + if (cpool_idx < b->cpool_count) + key_str = JS_ToCString (ctx, b->cpool[cpool_idx]); + if (key_str) { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %s %d", + key_str, + get_u16 (tab + arg_pos + 4)); + JS_FreeCString (ctx, key_str); + } else { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " cpool[%u] %d", + cpool_idx, + get_u16 (tab + arg_pos + 4)); + } + } break; + case OP_FMT_key_label_u16: { + uint32_t cpool_idx = get_u32 (tab + arg_pos); + int addr = get_u32 (tab + arg_pos + 4); + int extra = get_u16 (tab + arg_pos + 8); + const char *key_str = NULL; + if (cpool_idx < b->cpool_count) + key_str = JS_ToCString (ctx, b->cpool[cpool_idx]); + if (key_str) { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %s %u %u", + key_str, + addr + arg_pos + 4, + extra); + JS_FreeCString (ctx, key_str); + } else { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " cpool[%u] %u %u", + cpool_idx, + addr + arg_pos + 4, + extra); + } + } break; + case OP_FMT_none_loc: { + int idx = (op - OP_get_loc0) % 4; + if (idx < b->var_count) { + const char *var_name + = JS_ToCString (ctx, b->vardefs[idx].var_name); + if (var_name) { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %d: %s", + idx, + var_name); + JS_FreeCString (ctx, var_name); + } else { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %d", + idx); + } + } else { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %d", + idx); + } + } break; + case OP_FMT_loc8: { + int idx = get_u8 (tab + arg_pos); + if (idx < b->var_count) { + const char *var_name + = JS_ToCString (ctx, b->vardefs[idx].var_name); + if (var_name) { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u: %s", + idx, + var_name); + JS_FreeCString (ctx, var_name); + } else { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u", + idx); + } + } else { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u", + idx); + } + } break; + case OP_FMT_loc: { + int idx = get_u16 (tab + arg_pos); + if (idx < b->var_count) { + const char *var_name + = JS_ToCString (ctx, b->vardefs[idx].var_name); + if (var_name) { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u: %s", + idx, + var_name); + JS_FreeCString (ctx, var_name); + } else { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u", + idx); + } + } else { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u", + idx); + } + } break; + case OP_FMT_none_arg: { + int idx = (op - OP_get_arg0) % 4; + if (idx < b->arg_count) { + const char *arg_name + = JS_ToCString (ctx, b->vardefs[idx].var_name); + if (arg_name) { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %d: %s", + idx, + arg_name); + JS_FreeCString (ctx, arg_name); + } else { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %d", + idx); + } + } else { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %d", + idx); + } + } break; + case OP_FMT_arg: { + int idx = get_u16 (tab + arg_pos); + if (idx < b->arg_count) { + const char *arg_name + = JS_ToCString (ctx, b->vardefs[idx].var_name); + if (arg_name) { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u: %s", + idx, + arg_name); + JS_FreeCString (ctx, arg_name); + } else { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u", + idx); + } + } else { + snprintf (opcode_str + strlen (opcode_str), + sizeof (opcode_str) - strlen (opcode_str), + " %u", + idx); + } + } break; + default: + break; + } + } + + JSValue js_opcode_str = JS_NewString (ctx, opcode_str); + JS_SetPropertyUint32 (ctx, ret, idx++, js_opcode_str); + + pos += size; + } + + return ret; +} + +JSValue js_debugger_local_variables (JSContext *ctx, int stack_index) { + JSValue ret = JS_NewObject (ctx); + + JSStackFrame *sf; + int cur_index = 0; + + for (sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) { + // this val is one frame up + if (cur_index == stack_index - 1) { + if (js_is_bytecode_function (sf->cur_func)) { + JSFunction *f = JS_VALUE_GET_FUNCTION (sf->cur_func); + JSFunctionBytecode *b = f->u.func.function_bytecode; + + JSValue this_obj = sf->var_buf[b->var_count]; + // only provide a this if it is not the global object. + if (JS_VALUE_GET_OBJ (this_obj) != JS_VALUE_GET_OBJ (ctx->global_obj)) + JS_SetPropertyStr (ctx, ret, "this", this_obj); + } + } + + if (cur_index < stack_index) { + cur_index++; + continue; + } + + if (!js_is_bytecode_function (sf->cur_func)) goto done; + JSFunction *f = JS_VALUE_GET_FUNCTION (sf->cur_func); + JSFunctionBytecode *b = f->u.func.function_bytecode; + + for (uint32_t i = 0; i < b->arg_count + b->var_count; i++) { + JSValue var_val; + if (i < b->arg_count) + var_val = sf->arg_buf[i]; + else + var_val = sf->var_buf[i - b->arg_count]; + + if (JS_IsUninitialized (var_val)) continue; + + JSVarDef *vd = b->vardefs + i; + JS_SetProperty (ctx, ret, vd->var_name, var_val); + } + + break; + } + +done: + return ret; +} + +void js_debugger_set_closure_variable (JSContext *ctx, JSValue fn, JSValue var_name, JSValue val) { + /* TODO: Reimplement using outer_frame mechanism if debugging is needed */ + (void)ctx; (void)fn; (void)var_name; (void)val; +} + +JSValue js_debugger_closure_variables (JSContext *ctx, JSValue fn) { + /* TODO: Reimplement using outer_frame mechanism if debugging is needed */ + (void)fn; + return JS_NewObject (ctx); +} + +void *js_debugger_val_address (JSContext *ctx, JSValue val) { + return JS_VALUE_GET_PTR (val); +} + +/* ============================================================================ + * Cell Script Module: json + * Provides json.encode() and json.decode() using pure C implementation + * ============================================================================ + */ + +static JSValue js_cell_json_encode (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) + return JS_ThrowTypeError (ctx, "json.encode requires at least 1 argument"); + + JSValue replacer = argc > 1 ? argv[1] : JS_NULL; + JSValue space = argc > 2 ? argv[2] : JS_NewInt32 (ctx, 1); + JSValue result = JS_JSONStringify (ctx, argv[0], replacer, space); + return result; +} + +static JSValue js_cell_json_decode (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + if (argc < 1) + return JS_ThrowTypeError (ctx, "json.decode requires at least 1 argument"); + + if (!JS_IsText (argv[0])) { + JSValue err = JS_NewError (ctx); + JS_SetPropertyStr ( + ctx, err, "message", JS_NewString (ctx, "couldn't parse text: not a string")); + return JS_Throw (ctx, err); + } + + const char *str = JS_ToCString (ctx, argv[0]); + if (!str) return JS_EXCEPTION; + + size_t len = strlen (str); + JSValue result = JS_ParseJSON (ctx, str, len, ""); + JS_FreeCString (ctx, str); + + /* Apply reviver if provided */ + if (argc > 1 && JS_IsFunction (argv[1]) && !JS_IsException (result)) { + /* Create wrapper object to pass to reviver */ + JSValue wrapper = JS_NewObject (ctx); + JS_SetPropertyStr (ctx, wrapper, "", result); + + JSValue holder = wrapper; + JSValue key = JS_KEY_empty; + JSValue args[2] = { key, JS_GetProperty (ctx, holder, key) }; + JSValue final = JS_Call (ctx, argv[1], holder, 2, args); + result = final; + } + + return result; +} + +static const JSCFunctionListEntry js_cell_json_funcs[] = { + JS_CFUNC_DEF ("encode", 1, js_cell_json_encode), + JS_CFUNC_DEF ("decode", 1, js_cell_json_decode), +}; + +JSValue js_json_use (JSContext *ctx) { + JSGCRef obj_ref; + JS_PushGCRef (ctx, &obj_ref); + obj_ref.val = JS_NewObject (ctx); + JS_SetPropertyFunctionList (ctx, obj_ref.val, js_cell_json_funcs, countof (js_cell_json_funcs)); + JSValue result = obj_ref.val; + JS_PopGCRef (ctx, &obj_ref); + return result; +} + +/* ============================================================================ + * Cell Script Module: nota + * Provides nota.encode() and nota.decode() for NOTA binary serialization + * ============================================================================ + */ + +static int nota_get_arr_len (JSContext *ctx, JSValue arr) { + int64_t len; + JS_GetLength (ctx, arr, &len); + return (int)len; +} + +typedef struct NotaEncodeContext { + JSContext *ctx; + JSGCRef *visitedStack_ref; /* pointer to GC-rooted ref */ + NotaBuffer nb; + int cycle; + JSGCRef *replacer_ref; /* pointer to GC-rooted ref */ +} NotaEncodeContext; + +static void nota_stack_push (NotaEncodeContext *enc, JSValueConst val) { + JSContext *ctx = enc->ctx; + int len = nota_get_arr_len (ctx, enc->visitedStack_ref->val); + JS_SetPropertyInt64 (ctx, enc->visitedStack_ref->val, len, JS_DupValue (ctx, val)); +} + +static void nota_stack_pop (NotaEncodeContext *enc) { + JSContext *ctx = enc->ctx; + int len = nota_get_arr_len (ctx, enc->visitedStack_ref->val); + JS_SetPropertyStr (ctx, enc->visitedStack_ref->val, "length", JS_NewUint32 (ctx, len - 1)); +} + +static int nota_stack_has (NotaEncodeContext *enc, JSValueConst val) { + JSContext *ctx = enc->ctx; + int len = nota_get_arr_len (ctx, enc->visitedStack_ref->val); + for (int i = 0; i < len; i++) { + JSValue elem = JS_GetPropertyUint32 (ctx, enc->visitedStack_ref->val, i); + if (JS_IsObject (elem) && JS_IsObject (val)) { + if (JS_StrictEq (ctx, elem, val)) { + JS_FreeValue (ctx, elem); + return 1; + } + } + JS_FreeValue (ctx, elem); + } + return 0; +} + +static JSValue nota_apply_replacer (NotaEncodeContext *enc, JSValueConst holder, JSValueConst key, JSValueConst val) { + if (!enc->replacer_ref || JS_IsNull (enc->replacer_ref->val)) return JS_DupValue (enc->ctx, val); + + JSValue args[2] = { JS_DupValue (enc->ctx, key), JS_DupValue (enc->ctx, val) }; + JSValue result = JS_Call (enc->ctx, enc->replacer_ref->val, holder, 2, args); + JS_FreeValue (enc->ctx, args[0]); + JS_FreeValue (enc->ctx, args[1]); + + if (JS_IsException (result)) return JS_DupValue (enc->ctx, val); + return result; +} + +static char *js_do_nota_decode (JSContext *js, JSValue *tmp, char *nota, JSValue holder, JSValue key, JSValue reviver) { + int type = nota_type (nota); + JSValue ret2; + long long n; + double d; + int b; + char *str; + uint8_t *blob; + + switch (type) { + case NOTA_BLOB: + nota = nota_read_blob (&n, (char **)&blob, nota); + *tmp = js_new_blob_stoned_copy (js, blob, n); + sys_free (blob); + break; + case NOTA_TEXT: + nota = nota_read_text (&str, nota); + *tmp = JS_NewString (js, str); + sys_free (str); + break; + case NOTA_ARR: + nota = nota_read_array (&n, nota); + *tmp = JS_NewArray (js); + for (int i = 0; i < n; i++) { + nota = js_do_nota_decode (js, &ret2, nota, *tmp, JS_NewInt32 (js, i), reviver); + JS_SetPropertyInt64 (js, *tmp, i, ret2); + } + break; + case NOTA_REC: + nota = nota_read_record (&n, nota); + *tmp = JS_NewObject (js); + for (int i = 0; i < n; i++) { + nota = nota_read_text (&str, nota); + JSValue prop_key = JS_NewString (js, str); + nota = js_do_nota_decode (js, &ret2, nota, *tmp, prop_key, reviver); + JS_SetPropertyStr (js, *tmp, str, ret2); + JS_FreeValue (js, prop_key); + sys_free (str); + } + break; + case NOTA_INT: + nota = nota_read_int (&n, nota); + *tmp = JS_NewInt64 (js, n); + break; + case NOTA_SYM: + nota = nota_read_sym (&b, nota); + if (b == NOTA_PRIVATE) { + JSValue inner; + nota = js_do_nota_decode (js, &inner, nota, holder, JS_NULL, reviver); + JSValue obj = JS_NewObject (js); + *tmp = obj; + } else { + switch (b) { + case NOTA_NULL: *tmp = JS_NULL; break; + case NOTA_FALSE: *tmp = JS_NewBool (js, 0); break; + case NOTA_TRUE: *tmp = JS_NewBool (js, 1); break; + default: *tmp = JS_NULL; break; + } + } + break; + default: + case NOTA_FLOAT: + nota = nota_read_float (&d, nota); + *tmp = JS_NewFloat64 (js, d); + break; + } + + if (!JS_IsNull (reviver)) { + JSValue args[2] = { JS_DupValue (js, key), JS_DupValue (js, *tmp) }; + JSValue revived = JS_Call (js, reviver, holder, 2, args); + JS_FreeValue (js, args[0]); + JS_FreeValue (js, args[1]); + if (!JS_IsException (revived)) { + JS_FreeValue (js, *tmp); + *tmp = revived; + } else { + JS_FreeValue (js, revived); + } + } + + return nota; +} + +static void nota_encode_value (NotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValueConst key) { + JSContext *ctx = enc->ctx; + JSGCRef replaced_ref, keys_ref, elem_ref, prop_ref; + JS_PushGCRef (ctx, &replaced_ref); + replaced_ref.val = nota_apply_replacer (enc, holder, key, val); + int tag = JS_VALUE_GET_TAG (replaced_ref.val); + + switch (tag) { + case JS_TAG_INT: + case JS_TAG_FLOAT64: { + double d; + JS_ToFloat64 (ctx, &d, replaced_ref.val); + nota_write_number (&enc->nb, d); + break; + } + case JS_TAG_STRING: { + const char *str = JS_ToCString (ctx, replaced_ref.val); + nota_write_text (&enc->nb, str); + JS_FreeCString (ctx, str); + break; + } + case JS_TAG_BOOL: + if (JS_VALUE_GET_BOOL (replaced_ref.val)) nota_write_sym (&enc->nb, NOTA_TRUE); + else nota_write_sym (&enc->nb, NOTA_FALSE); + break; + case JS_TAG_NULL: + nota_write_sym (&enc->nb, NOTA_NULL); + break; + case JS_TAG_PTR: { + if (js_is_blob (ctx, replaced_ref.val)) { + size_t buf_len; + void *buf_data = js_get_blob_data (ctx, &buf_len, replaced_ref.val); + if (buf_data == (void *)-1) { + JS_PopGCRef (ctx, &replaced_ref); + return; + } + nota_write_blob (&enc->nb, (unsigned long long)buf_len * 8, (const char *)buf_data); + break; + } + + if (JS_IsArray (replaced_ref.val)) { + if (nota_stack_has (enc, replaced_ref.val)) { + enc->cycle = 1; + break; + } + nota_stack_push (enc, replaced_ref.val); + int arr_len = nota_get_arr_len (ctx, replaced_ref.val); + nota_write_array (&enc->nb, arr_len); + JS_PushGCRef (ctx, &elem_ref); + for (int i = 0; i < arr_len; i++) { + elem_ref.val = JS_GetPropertyUint32 (ctx, replaced_ref.val, i); + JSValue elem_key = JS_NewInt32 (ctx, i); + nota_encode_value (enc, elem_ref.val, replaced_ref.val, elem_key); + } + JS_PopGCRef (ctx, &elem_ref); + nota_stack_pop (enc); + break; + } + + JSValue adata = JS_NULL; + if (!JS_IsNull (adata)) { + nota_write_sym (&enc->nb, NOTA_PRIVATE); + nota_encode_value (enc, adata, replaced_ref.val, JS_NULL); + break; + } + if (nota_stack_has (enc, replaced_ref.val)) { + enc->cycle = 1; + break; + } + nota_stack_push (enc, replaced_ref.val); + + JSValue to_json = JS_GetPropertyStr (ctx, replaced_ref.val, "toJSON"); + if (JS_IsFunction (to_json)) { + JSValue result = JS_Call (ctx, to_json, replaced_ref.val, 0, NULL); + if (!JS_IsException (result)) { + nota_encode_value (enc, result, holder, key); + } else { + nota_write_sym (&enc->nb, NOTA_NULL); + } + nota_stack_pop (enc); + break; + } + + JS_PushGCRef (ctx, &keys_ref); + keys_ref.val = JS_GetOwnPropertyNames (ctx, replaced_ref.val); + if (JS_IsException (keys_ref.val)) { + nota_write_sym (&enc->nb, NOTA_NULL); + nota_stack_pop (enc); + JS_PopGCRef (ctx, &keys_ref); + break; + } + int64_t plen64; + if (JS_GetLength (ctx, keys_ref.val, &plen64) < 0) { + nota_write_sym (&enc->nb, NOTA_NULL); + nota_stack_pop (enc); + JS_PopGCRef (ctx, &keys_ref); + break; + } + uint32_t plen = (uint32_t)plen64; + + JS_PushGCRef (ctx, &prop_ref); + JS_PushGCRef (ctx, &elem_ref); + uint32_t non_function_count = 0; + for (uint32_t i = 0; i < plen; i++) { + elem_ref.val = JS_GetPropertyUint32 (ctx, keys_ref.val, i); + prop_ref.val = JS_GetProperty (ctx, replaced_ref.val, elem_ref.val); + if (!JS_IsFunction (prop_ref.val)) non_function_count++; + } + + nota_write_record (&enc->nb, non_function_count); + for (uint32_t i = 0; i < plen; i++) { + elem_ref.val = JS_GetPropertyUint32 (ctx, keys_ref.val, i); + prop_ref.val = JS_GetProperty (ctx, replaced_ref.val, elem_ref.val); + if (!JS_IsFunction (prop_ref.val)) { + const char *prop_name = JS_ToCString (ctx, elem_ref.val); + nota_write_text (&enc->nb, prop_name ? prop_name : ""); + nota_encode_value (enc, prop_ref.val, replaced_ref.val, elem_ref.val); + JS_FreeCString (ctx, prop_name); + } + } + JS_PopGCRef (ctx, &elem_ref); + JS_PopGCRef (ctx, &prop_ref); + JS_PopGCRef (ctx, &keys_ref); + nota_stack_pop (enc); + break; + } + default: + nota_write_sym (&enc->nb, NOTA_NULL); + break; + } + JS_PopGCRef (ctx, &replaced_ref); +} + +void *value2nota (JSContext *ctx, JSValue v) { + JSGCRef val_ref, stack_ref, key_ref; + JS_PushGCRef (ctx, &val_ref); + JS_PushGCRef (ctx, &stack_ref); + JS_PushGCRef (ctx, &key_ref); + val_ref.val = v; + + NotaEncodeContext enc_s, *enc = &enc_s; + enc->ctx = ctx; + stack_ref.val = JS_NewArray (ctx); + enc->visitedStack_ref = &stack_ref; + enc->cycle = 0; + enc->replacer_ref = NULL; + + nota_buffer_init (&enc->nb, 128); + key_ref.val = JS_NewString (ctx, ""); + nota_encode_value (enc, val_ref.val, JS_NULL, key_ref.val); + + if (enc->cycle) { + JS_PopGCRef (ctx, &key_ref); + JS_PopGCRef (ctx, &stack_ref); + JS_PopGCRef (ctx, &val_ref); + nota_buffer_free (&enc->nb); + return NULL; + } + + JS_PopGCRef (ctx, &key_ref); + JS_PopGCRef (ctx, &stack_ref); + JS_PopGCRef (ctx, &val_ref); + void *data_ptr = enc->nb.data; + enc->nb.data = NULL; + nota_buffer_free (&enc->nb); + return data_ptr; +} + +JSValue nota2value (JSContext *js, void *nota) { + if (!nota) return JS_NULL; + JSGCRef holder_ref, key_ref, ret_ref; + JS_PushGCRef (js, &holder_ref); + JS_PushGCRef (js, &key_ref); + JS_PushGCRef (js, &ret_ref); + holder_ref.val = JS_NewObject (js); + key_ref.val = JS_NewString (js, ""); + ret_ref.val = JS_NULL; + js_do_nota_decode (js, &ret_ref.val, nota, holder_ref.val, key_ref.val, JS_NULL); + JSValue result = ret_ref.val; + JS_PopGCRef (js, &ret_ref); + JS_PopGCRef (js, &key_ref); + JS_PopGCRef (js, &holder_ref); + return result; +} + +static JSValue js_nota_encode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + if (argc < 1) return JS_ThrowTypeError (ctx, "nota.encode requires at least 1 argument"); + + JSGCRef val_ref, stack_ref, replacer_ref, key_ref; + JS_PushGCRef (ctx, &val_ref); + JS_PushGCRef (ctx, &stack_ref); + JS_PushGCRef (ctx, &replacer_ref); + JS_PushGCRef (ctx, &key_ref); + val_ref.val = argv[0]; + stack_ref.val = JS_NewArray (ctx); + replacer_ref.val = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL; + + NotaEncodeContext enc_s, *enc = &enc_s; + enc->ctx = ctx; + enc->visitedStack_ref = &stack_ref; + enc->cycle = 0; + enc->replacer_ref = &replacer_ref; + + nota_buffer_init (&enc->nb, 128); + key_ref.val = JS_NewString (ctx, ""); + nota_encode_value (enc, val_ref.val, JS_NULL, key_ref.val); + + JSValue ret; + if (enc->cycle) { + nota_buffer_free (&enc->nb); + ret = JS_ThrowReferenceError (ctx, "Tried to encode something to nota with a cycle."); + } else { + size_t total_len = enc->nb.size; + void *data_ptr = enc->nb.data; + ret = js_new_blob_stoned_copy (ctx, (uint8_t *)data_ptr, total_len); + nota_buffer_free (&enc->nb); + } + + JS_PopGCRef (ctx, &key_ref); + JS_PopGCRef (ctx, &replacer_ref); + JS_PopGCRef (ctx, &stack_ref); + JS_PopGCRef (ctx, &val_ref); + return ret; +} + +static JSValue js_nota_decode (JSContext *js, JSValueConst self, int argc, JSValueConst *argv) { + if (argc < 1) return JS_NULL; + + size_t len; + unsigned char *nota = js_get_blob_data (js, &len, argv[0]); + if (nota == (unsigned char *)-1) return JS_EXCEPTION; + if (!nota) return JS_NULL; + + JSGCRef holder_ref, key_ref, ret_ref, reviver_ref; + JS_PushGCRef (js, &holder_ref); + JS_PushGCRef (js, &key_ref); + JS_PushGCRef (js, &ret_ref); + JS_PushGCRef (js, &reviver_ref); + + reviver_ref.val = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL; + holder_ref.val = JS_NewObject (js); + key_ref.val = JS_NewString (js, ""); + ret_ref.val = JS_NULL; + + js_do_nota_decode (js, &ret_ref.val, (char *)nota, holder_ref.val, key_ref.val, reviver_ref.val); + + JSValue result = ret_ref.val; + JS_PopGCRef (js, &reviver_ref); + JS_PopGCRef (js, &ret_ref); + JS_PopGCRef (js, &key_ref); + JS_PopGCRef (js, &holder_ref); + return result; +} + +static const JSCFunctionListEntry js_nota_funcs[] = { + JS_CFUNC_DEF ("encode", 1, js_nota_encode), + JS_CFUNC_DEF ("decode", 1, js_nota_decode), +}; + +JSValue js_nota_use (JSContext *js) { + JSGCRef export_ref; + JS_PushGCRef (js, &export_ref); + export_ref.val = JS_NewObject (js); + JS_SetPropertyFunctionList (js, export_ref.val, js_nota_funcs, sizeof (js_nota_funcs) / sizeof (JSCFunctionListEntry)); + JSValue result = export_ref.val; + JS_PopGCRef (js, &export_ref); + return result; +} + +/* ============================================================================ + * Cell Script Module: wota + * Provides wota.encode() and wota.decode() for WOTA binary serialization + * ============================================================================ + */ + +typedef struct ObjectRef { + void *ptr; + struct ObjectRef *next; +} ObjectRef; + +typedef struct WotaEncodeContext { + JSContext *ctx; + ObjectRef *visited_stack; + WotaBuffer wb; + int cycle; + JSValue replacer; +} WotaEncodeContext; + +static void wota_stack_push (WotaEncodeContext *enc, JSValueConst val) { + (void)enc; (void)val; + /* Cycle detection disabled for performance */ +} + +static void wota_stack_pop (WotaEncodeContext *enc) { + if (!enc->visited_stack) return; + + ObjectRef *top = enc->visited_stack; + enc->visited_stack = top->next; + sys_free (top); +} + +static int wota_stack_has (WotaEncodeContext *enc, JSValueConst val) { + (void)enc; (void)val; + return 0; + /* Cycle detection disabled for performance */ +} + +static void wota_stack_free (WotaEncodeContext *enc) { + while (enc->visited_stack) { + wota_stack_pop (enc); + } +} + +static JSValue wota_apply_replacer (WotaEncodeContext *enc, JSValueConst holder, JSValue key, JSValueConst val) { + if (JS_IsNull (enc->replacer)) return JS_DupValue (enc->ctx, val); + JSValue key_val = JS_IsNull (key) ? JS_NULL : JS_DupValue (enc->ctx, key); + JSValue args[2] = { key_val, JS_DupValue (enc->ctx, val) }; + JSValue result = JS_Call (enc->ctx, enc->replacer, holder, 2, args); + JS_FreeValue (enc->ctx, args[0]); + JS_FreeValue (enc->ctx, args[1]); + if (JS_IsException (result)) return JS_DupValue (enc->ctx, val); + return result; +} + +static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValue key); + +static void encode_object_properties (WotaEncodeContext *enc, JSValueConst val, JSValueConst holder) { + JSContext *ctx = enc->ctx; + + /* Root the input value to protect it during property enumeration */ + JSGCRef val_ref, keys_ref; + JS_PushGCRef (ctx, &val_ref); + JS_PushGCRef (ctx, &keys_ref); + val_ref.val = JS_DupValue (ctx, val); + + keys_ref.val = JS_GetOwnPropertyNames (ctx, val_ref.val); + if (JS_IsException (keys_ref.val)) { + wota_write_sym (&enc->wb, WOTA_NULL); + JS_FreeValue (ctx, val_ref.val); + JS_PopGCRef (ctx, &keys_ref); + JS_PopGCRef (ctx, &val_ref); + return; + } + int64_t plen64; + if (JS_GetLength (ctx, keys_ref.val, &plen64) < 0) { + JS_FreeValue (ctx, keys_ref.val); + JS_FreeValue (ctx, val_ref.val); + wota_write_sym (&enc->wb, WOTA_NULL); + JS_PopGCRef (ctx, &keys_ref); + JS_PopGCRef (ctx, &val_ref); + return; + } + uint32_t plen = (uint32_t)plen64; + uint32_t non_function_count = 0; + + /* Allocate GC-rooted arrays for props and keys */ + JSGCRef *prop_refs = sys_malloc (sizeof (JSGCRef) * plen); + JSGCRef *key_refs = sys_malloc (sizeof (JSGCRef) * plen); + for (uint32_t i = 0; i < plen; i++) { + JS_PushGCRef (ctx, &prop_refs[i]); + JS_PushGCRef (ctx, &key_refs[i]); + prop_refs[i].val = JS_NULL; + key_refs[i].val = JS_NULL; + } + + for (uint32_t i = 0; i < plen; i++) { + JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); + JSValue prop_val = JS_GetProperty (ctx, val_ref.val, key); + if (!JS_IsFunction (prop_val)) { + key_refs[non_function_count].val = key; + prop_refs[non_function_count].val = prop_val; + non_function_count++; + } else { + JS_FreeValue (ctx, prop_val); + JS_FreeValue (ctx, key); + } + } + JS_FreeValue (ctx, keys_ref.val); + wota_write_record (&enc->wb, non_function_count); + for (uint32_t i = 0; i < non_function_count; i++) { + size_t klen; + const char *prop_name = JS_ToCStringLen (ctx, &klen, key_refs[i].val); + wota_write_text_len (&enc->wb, prop_name ? prop_name : "", prop_name ? klen : 0); + wota_encode_value (enc, prop_refs[i].val, val_ref.val, key_refs[i].val); + JS_FreeCString (ctx, prop_name); + JS_FreeValue (ctx, prop_refs[i].val); + JS_FreeValue (ctx, key_refs[i].val); + } + /* Pop all GC refs in reverse order */ + for (int i = plen - 1; i >= 0; i--) { + JS_PopGCRef (ctx, &key_refs[i]); + JS_PopGCRef (ctx, &prop_refs[i]); + } + sys_free (prop_refs); + sys_free (key_refs); + JS_FreeValue (ctx, val_ref.val); + JS_PopGCRef (ctx, &keys_ref); + JS_PopGCRef (ctx, &val_ref); +} + +static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValue key) { + JSContext *ctx = enc->ctx; + JSValue replaced; + if (!JS_IsNull (enc->replacer) && !JS_IsNull (key)) + replaced = wota_apply_replacer (enc, holder, key, val); + else + replaced = JS_DupValue (enc->ctx, val); + + int tag = JS_VALUE_GET_TAG (replaced); + switch (tag) { + case JS_TAG_INT: { + int32_t d; + JS_ToInt32 (ctx, &d, replaced); + wota_write_int_word (&enc->wb, d); + break; + } + case JS_TAG_FLOAT64: { + double d; + if (JS_ToFloat64 (ctx, &d, replaced) < 0) { + wota_write_sym (&enc->wb, WOTA_NULL); + break; + } + wota_write_float_word (&enc->wb, d); + break; + } + case JS_TAG_STRING: { + size_t plen; + const char *str = JS_ToCStringLen (ctx, &plen, replaced); + wota_write_text_len (&enc->wb, str ? str : "", str ? plen : 0); + JS_FreeCString (ctx, str); + break; + } + case JS_TAG_BOOL: + wota_write_sym (&enc->wb, JS_VALUE_GET_BOOL (replaced) ? WOTA_TRUE : WOTA_FALSE); + break; + case JS_TAG_NULL: + wota_write_sym (&enc->wb, WOTA_NULL); + break; + case JS_TAG_PTR: { + if (js_is_blob (ctx, replaced)) { + size_t buf_len; + void *buf_data = js_get_blob_data (ctx, &buf_len, replaced); + if (buf_data == (void *)-1) { + JS_FreeValue (ctx, replaced); + return; + } + if (buf_len == 0) { + wota_write_blob (&enc->wb, 0, ""); + } else { + wota_write_blob (&enc->wb, (unsigned long long)buf_len * 8, (const char *)buf_data); + } + break; + } + if (JS_IsArray (replaced)) { + if (wota_stack_has (enc, replaced)) { + enc->cycle = 1; + break; + } + wota_stack_push (enc, replaced); + int64_t arr_len; + JS_GetLength (ctx, replaced, &arr_len); + wota_write_array (&enc->wb, arr_len); + for (int64_t i = 0; i < arr_len; i++) { + JSValue elem_val = JS_GetPropertyUint32 (ctx, replaced, i); + wota_encode_value (enc, elem_val, replaced, JS_NewInt32 (ctx, (int32_t)i)); + JS_FreeValue (ctx, elem_val); + } + wota_stack_pop (enc); + break; + } + JSValue adata = JS_NULL; + if (!JS_IsNull (adata)) { + wota_write_sym (&enc->wb, WOTA_PRIVATE); + wota_encode_value (enc, adata, replaced, JS_NULL); + JS_FreeValue (ctx, adata); + break; + } + JS_FreeValue (ctx, adata); + if (wota_stack_has (enc, replaced)) { + enc->cycle = 1; + break; + } + wota_stack_push (enc, replaced); + JSValue to_json = JS_GetPropertyStr (ctx, replaced, "toJSON"); + if (JS_IsFunction (to_json)) { + JSValue result = JS_Call (ctx, to_json, replaced, 0, NULL); + JS_FreeValue (ctx, to_json); + if (!JS_IsException (result)) { + wota_encode_value (enc, result, holder, key); + JS_FreeValue (ctx, result); + } else + wota_write_sym (&enc->wb, WOTA_NULL); + wota_stack_pop (enc); + break; + } + JS_FreeValue (ctx, to_json); + encode_object_properties (enc, replaced, holder); + wota_stack_pop (enc); + break; + } + default: + wota_write_sym (&enc->wb, WOTA_NULL); + break; + } + JS_FreeValue (ctx, replaced); +} + +static char *decode_wota_value (JSContext *ctx, char *data_ptr, JSValue *out_val, JSValue holder, JSValue key, JSValue reviver) { + uint64_t first_word = *(uint64_t *)data_ptr; + int type = (int)(first_word & 0xffU); + switch (type) { + case WOTA_INT: { + long long val; + data_ptr = wota_read_int (&val, data_ptr); + *out_val = JS_NewInt64 (ctx, val); + break; + } + case WOTA_FLOAT: { + double d; + data_ptr = wota_read_float (&d, data_ptr); + *out_val = JS_NewFloat64 (ctx, d); + break; + } + case WOTA_SYM: { + int scode; + data_ptr = wota_read_sym (&scode, data_ptr); + if (scode == WOTA_PRIVATE) { + JSValue inner = JS_NULL; + data_ptr = decode_wota_value (ctx, data_ptr, &inner, holder, JS_NULL, reviver); + JSValue obj = JS_NewObject (ctx); + *out_val = obj; + } else if (scode == WOTA_NULL) *out_val = JS_NULL; + else if (scode == WOTA_FALSE) *out_val = JS_NewBool (ctx, 0); + else if (scode == WOTA_TRUE) *out_val = JS_NewBool (ctx, 1); + else *out_val = JS_NULL; + break; + } + case WOTA_BLOB: { + long long blen; + char *bdata = NULL; + data_ptr = wota_read_blob (&blen, &bdata, data_ptr); + *out_val = bdata ? js_new_blob_stoned_copy (ctx, (uint8_t *)bdata, (size_t)blen) : js_new_blob_stoned_copy (ctx, NULL, 0); + if (bdata) sys_free (bdata); + break; + } + case WOTA_TEXT: { + char *utf8 = NULL; + data_ptr = wota_read_text (&utf8, data_ptr); + *out_val = JS_NewString (ctx, utf8 ? utf8 : ""); + if (utf8) sys_free (utf8); + break; + } + case WOTA_ARR: { + long long c; + data_ptr = wota_read_array (&c, data_ptr); + JSGCRef arr_ref; + JS_PushGCRef (ctx, &arr_ref); + arr_ref.val = JS_NewArrayLen (ctx, c); + for (long long i = 0; i < c; i++) { + JSGCRef elem_ref; + JS_PushGCRef (ctx, &elem_ref); + elem_ref.val = JS_NULL; + JSValue idx_key = JS_NewInt32 (ctx, (int32_t)i); + data_ptr = decode_wota_value (ctx, data_ptr, &elem_ref.val, arr_ref.val, idx_key, reviver); + JS_SetPropertyUint32 (ctx, arr_ref.val, i, elem_ref.val); + JS_PopGCRef (ctx, &elem_ref); + } + *out_val = arr_ref.val; + JS_PopGCRef (ctx, &arr_ref); + break; + } + case WOTA_REC: { + long long c; + data_ptr = wota_read_record (&c, data_ptr); + JSGCRef obj_ref; + JS_PushGCRef (ctx, &obj_ref); + obj_ref.val = JS_NewObject (ctx); + for (long long i = 0; i < c; i++) { + char *tkey = NULL; + size_t key_len; + data_ptr = wota_read_text_len (&key_len, &tkey, data_ptr); + if (!tkey) continue; + JSGCRef prop_key_ref, sub_val_ref; + JS_PushGCRef (ctx, &prop_key_ref); + JS_PushGCRef (ctx, &sub_val_ref); + prop_key_ref.val = JS_NewStringLen (ctx, tkey, key_len); + sub_val_ref.val = JS_NULL; + data_ptr = decode_wota_value (ctx, data_ptr, &sub_val_ref.val, obj_ref.val, prop_key_ref.val, reviver); + JS_SetProperty (ctx, obj_ref.val, prop_key_ref.val, sub_val_ref.val); + JS_FreeValue (ctx, prop_key_ref.val); + JS_PopGCRef (ctx, &sub_val_ref); + JS_PopGCRef (ctx, &prop_key_ref); + sys_free (tkey); + } + *out_val = obj_ref.val; + JS_PopGCRef (ctx, &obj_ref); + break; + } + default: + data_ptr += 8; + *out_val = JS_NULL; + break; + } + if (!JS_IsNull (reviver)) { + JSValue key_val = JS_IsNull (key) ? JS_NULL : JS_DupValue (ctx, key); + JSValue args[2] = { key_val, JS_DupValue (ctx, *out_val) }; + JSValue revived = JS_Call (ctx, reviver, holder, 2, args); + JS_FreeValue (ctx, args[0]); + JS_FreeValue (ctx, args[1]); + if (!JS_IsException (revived)) { + JS_FreeValue (ctx, *out_val); + *out_val = revived; + } else + JS_FreeValue (ctx, revived); + } + return data_ptr; +} + +void *value2wota (JSContext *ctx, JSValue v, JSValue replacer, size_t *bytes) { + JSGCRef val_ref, rep_ref; + JS_PushGCRef (ctx, &val_ref); + JS_PushGCRef (ctx, &rep_ref); + val_ref.val = v; + rep_ref.val = replacer; + + WotaEncodeContext enc_s, *enc = &enc_s; + + enc->ctx = ctx; + enc->visited_stack = NULL; + enc->cycle = 0; + enc->replacer = rep_ref.val; + wota_buffer_init (&enc->wb, 16); + wota_encode_value (enc, val_ref.val, JS_NULL, JS_NULL); + if (enc->cycle) { + wota_stack_free (enc); + wota_buffer_free (&enc->wb); + JS_PopGCRef (ctx, &rep_ref); + JS_PopGCRef (ctx, &val_ref); + return NULL; + } + wota_stack_free (enc); + size_t total_bytes = enc->wb.size * sizeof (uint64_t); + void *wota = sys_realloc (enc->wb.data, total_bytes); + if (bytes) *bytes = total_bytes; + JS_PopGCRef (ctx, &rep_ref); + JS_PopGCRef (ctx, &val_ref); + return wota; +} + +JSValue wota2value (JSContext *ctx, void *wota) { + JSGCRef holder_ref, result_ref; + JS_PushGCRef (ctx, &holder_ref); + JS_PushGCRef (ctx, &result_ref); + result_ref.val = JS_NULL; + holder_ref.val = JS_NewObject (ctx); + decode_wota_value (ctx, wota, &result_ref.val, holder_ref.val, JS_NULL, JS_NULL); + JSValue result = result_ref.val; + JS_PopGCRef (ctx, &result_ref); + JS_PopGCRef (ctx, &holder_ref); + return result; +} + +static JSValue js_wota_encode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + if (argc < 1) return JS_ThrowTypeError (ctx, "wota.encode requires at least 1 argument"); + size_t total_bytes; + void *wota = value2wota (ctx, argv[0], JS_IsFunction (argv[1]) ? argv[1] : JS_NULL, &total_bytes); + JSValue ret = js_new_blob_stoned_copy (ctx, wota, total_bytes); + sys_free (wota); + return ret; +} + +static JSValue js_wota_decode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + if (argc < 1) return JS_NULL; + size_t len; + uint8_t *buf = js_get_blob_data (ctx, &len, argv[0]); + if (buf == (uint8_t *)-1) return JS_EXCEPTION; + if (!buf || len == 0) return JS_ThrowTypeError (ctx, "No blob data present"); + JSValue reviver = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL; + char *data_ptr = (char *)buf; + JSValue result = JS_NULL; + JSValue holder = JS_NewObject (ctx); + JSValue empty_key = JS_NewString (ctx, ""); + decode_wota_value (ctx, data_ptr, &result, holder, empty_key, reviver); + JS_FreeValue (ctx, empty_key); + JS_FreeValue (ctx, holder); + return result; +} + +static const JSCFunctionListEntry js_wota_funcs[] = { + JS_CFUNC_DEF ("encode", 2, js_wota_encode), + JS_CFUNC_DEF ("decode", 2, js_wota_decode), +}; + +JSValue js_wota_use (JSContext *ctx) { + JSGCRef exports_ref; + JS_PushGCRef (ctx, &exports_ref); + exports_ref.val = JS_NewObject (ctx); + JS_SetPropertyFunctionList (ctx, exports_ref.val, js_wota_funcs, sizeof (js_wota_funcs) / sizeof (js_wota_funcs[0])); + JSValue result = exports_ref.val; + JS_PopGCRef (ctx, &exports_ref); + return result; +} + +static JSValue js_math_e (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double power = 1.0; + if (argc > 0 && !JS_IsNull (argv[0])) { + if (JS_ToFloat64 (ctx, &power, argv[0]) < 0) return JS_EXCEPTION; + } + return JS_NewFloat64 (ctx, exp (power)); +} + +/* ============================================================================ + * Cell Script Module: math/radians + * Provides trigonometric and math functions using radians + * ============================================================================ + */ + +static JSValue js_math_rad_arc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, acos (x)); +} + +static JSValue js_math_rad_arc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, asin (x)); +} + +static JSValue js_math_rad_arc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, atan (x)); +} + +static JSValue js_math_rad_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, cos (x)); +} + +static JSValue js_math_rad_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, sin (x)); +} + +static JSValue js_math_rad_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, tan (x)); +} + +static JSValue js_math_ln (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, log (x)); +} + +static JSValue js_math_log10 (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, log10 (x)); +} + +static JSValue js_math_log2 (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, log2 (x)); +} + +static JSValue js_math_power (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x, y; + if (argc < 2) return JS_NULL; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + if (JS_ToFloat64 (ctx, &y, argv[1]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, pow (x, y)); +} + +static JSValue js_math_root (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x, n; + if (argc < 2) return JS_NULL; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + if (JS_ToFloat64 (ctx, &n, argv[1]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, pow (x, 1.0 / n)); +} + +static JSValue js_math_sqrt (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, sqrt (x)); +} + +static const JSCFunctionListEntry js_math_radians_funcs[] + = { JS_CFUNC_DEF ("arc_cosine", 1, js_math_rad_arc_cosine), + JS_CFUNC_DEF ("arc_sine", 1, js_math_rad_arc_sine), + JS_CFUNC_DEF ("arc_tangent", 1, js_math_rad_arc_tangent), + JS_CFUNC_DEF ("cosine", 1, js_math_rad_cosine), + JS_CFUNC_DEF ("sine", 1, js_math_rad_sine), + JS_CFUNC_DEF ("tangent", 1, js_math_rad_tangent), + JS_CFUNC_DEF ("ln", 1, js_math_ln), + JS_CFUNC_DEF ("log", 1, js_math_log10), + JS_CFUNC_DEF ("log2", 1, js_math_log2), + JS_CFUNC_DEF ("power", 2, js_math_power), + JS_CFUNC_DEF ("root", 2, js_math_root), + JS_CFUNC_DEF ("sqrt", 1, js_math_sqrt), + JS_CFUNC_DEF ("e", 1, js_math_e) }; + +JSValue js_math_radians_use (JSContext *ctx) { + JSValue obj = JS_NewObject (ctx); + JS_SetPropertyFunctionList (ctx, obj, js_math_radians_funcs, countof (js_math_radians_funcs)); + return obj; +} + +/* ============================================================================ + * Cell Script Module: math/degrees + * Provides trigonometric and math functions using degrees + * ============================================================================ + */ + +#define DEG2RAD (3.14159265358979323846264338327950288419716939937510 / 180.0) +#define RAD2DEG (180.0 / 3.14159265358979323846264338327950288419716939937510) + +static JSValue js_math_deg_arc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, acos (x) * RAD2DEG); +} + +static JSValue js_math_deg_arc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, asin (x) * RAD2DEG); +} + +static JSValue js_math_deg_arc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, atan (x) * RAD2DEG); +} + +static JSValue js_math_deg_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, cos (x * DEG2RAD)); +} + +static JSValue js_math_deg_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, sin (x * DEG2RAD)); +} + +static JSValue js_math_deg_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, tan (x * DEG2RAD)); +} + +static const JSCFunctionListEntry js_math_degrees_funcs[] + = { JS_CFUNC_DEF ("arc_cosine", 1, js_math_deg_arc_cosine), + JS_CFUNC_DEF ("arc_sine", 1, js_math_deg_arc_sine), + JS_CFUNC_DEF ("arc_tangent", 1, js_math_deg_arc_tangent), + JS_CFUNC_DEF ("cosine", 1, js_math_deg_cosine), + JS_CFUNC_DEF ("sine", 1, js_math_deg_sine), + JS_CFUNC_DEF ("tangent", 1, js_math_deg_tangent), + JS_CFUNC_DEF ("ln", 1, js_math_ln), + JS_CFUNC_DEF ("log", 1, js_math_log10), + JS_CFUNC_DEF ("log2", 1, js_math_log2), + JS_CFUNC_DEF ("power", 2, js_math_power), + JS_CFUNC_DEF ("root", 2, js_math_root), + JS_CFUNC_DEF ("sqrt", 1, js_math_sqrt), + JS_CFUNC_DEF ("e", 1, js_math_e) }; + +JSValue js_math_degrees_use (JSContext *ctx) { + JSValue obj = JS_NewObject (ctx); + JS_SetPropertyFunctionList (ctx, obj, js_math_degrees_funcs, countof (js_math_degrees_funcs)); + return obj; +} + +/* ============================================================================ + * Cell Script Module: math/cycles + * Provides trigonometric and math functions using cycles (0-1 = full rotation) + * ============================================================================ + */ + +#define TWOPI (2.0 * 3.14159265358979323846264338327950288419716939937510) + +static JSValue js_math_cyc_arc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, acos (x) / TWOPI); +} + +static JSValue js_math_cyc_arc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, asin (x) / TWOPI); +} + +static JSValue js_math_cyc_arc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, atan (x) / TWOPI); +} + +static JSValue js_math_cyc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, cos (x * TWOPI)); +} + +static JSValue js_math_cyc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, sin (x * TWOPI)); +} + +static JSValue js_math_cyc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { + double x; + if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; + return JS_NewFloat64 (ctx, tan (x * TWOPI)); +} + +static const JSCFunctionListEntry js_math_cycles_funcs[] + = { JS_CFUNC_DEF ("arc_cosine", 1, js_math_cyc_arc_cosine), + JS_CFUNC_DEF ("arc_sine", 1, js_math_cyc_arc_sine), + JS_CFUNC_DEF ("arc_tangent", 1, js_math_cyc_arc_tangent), + JS_CFUNC_DEF ("cosine", 1, js_math_cyc_cosine), + JS_CFUNC_DEF ("sine", 1, js_math_cyc_sine), + JS_CFUNC_DEF ("tangent", 1, js_math_cyc_tangent), + JS_CFUNC_DEF ("ln", 1, js_math_ln), + JS_CFUNC_DEF ("log", 1, js_math_log10), + JS_CFUNC_DEF ("log2", 1, js_math_log2), + JS_CFUNC_DEF ("power", 2, js_math_power), + JS_CFUNC_DEF ("root", 2, js_math_root), + JS_CFUNC_DEF ("sqrt", 1, js_math_sqrt), + JS_CFUNC_DEF ("e", 1, js_math_e) }; + +JSValue js_math_cycles_use (JSContext *ctx) { + JSValue obj = JS_NewObject (ctx); + JS_SetPropertyFunctionList (ctx, obj, js_math_cycles_funcs, countof (js_math_cycles_funcs)); + return obj; +} +/* Public API: get stack trace as cJSON array */ +cJSON *JS_GetStack(JSContext *ctx) { + if (JS_IsNull(ctx->reg_current_frame)) return NULL; + JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame); + uint32_t cur_pc = ctx->current_register_pc; + + cJSON *arr = cJSON_CreateArray(); + int is_first = 1; + + while (frame) { + if (!JS_IsFunction(frame->function)) break; + JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); + + const char *func_name = NULL; + const char *file = NULL; + uint16_t line = 0, col = 0; + uint32_t pc = is_first ? cur_pc : 0; + + if (fn->kind == JS_FUNC_KIND_REGISTER && fn->u.reg.code) { + JSCodeRegister *code = fn->u.reg.code; + file = code->filename_cstr; + func_name = code->name_cstr; + if (!is_first) { + pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16); + } + if (code->line_table && pc < code->instr_count) { + line = code->line_table[pc].line; + col = code->line_table[pc].col; + } + } else if (fn->kind == JS_FUNC_KIND_MCODE && fn->u.mcode.code) { + JSMCode *code = fn->u.mcode.code; + file = code->filename; + func_name = code->name; + if (!is_first) { + pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16); + } + if (code->line_table && pc < code->instr_count) { + line = code->line_table[pc].line; + col = code->line_table[pc].col; + } + } + + cJSON *entry = cJSON_CreateObject(); + cJSON_AddStringToObject(entry, "function", func_name ? func_name : ""); + cJSON_AddStringToObject(entry, "file", file ? file : ""); + cJSON_AddNumberToObject(entry, "line", line); + cJSON_AddNumberToObject(entry, "column", col); + cJSON_AddItemToArray(arr, entry); + + if (JS_IsNull(frame->caller)) break; + frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); + is_first = 0; + } + + ctx->reg_current_frame = JS_NULL; + return arr; +} diff --git a/source/tokenize.c b/source/tokenize.c new file mode 100644 index 00000000..c9e9b0b4 --- /dev/null +++ b/source/tokenize.c @@ -0,0 +1,1432 @@ +/* + * 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" + +static const char *ast_token_kind_str(int token_val) { + static char single_char[2] = {0, 0}; + switch (token_val) { + case TOK_NUMBER: return "number"; + case TOK_STRING: return "text"; + case TOK_TEMPLATE: return "text"; + case TOK_IDENT: return "name"; + case TOK_COMMENT: return "comment"; + case TOK_NEWLINE: return "newline"; + case TOK_SPACE: return "space"; + case TOK_REGEXP: return "regexp"; + case TOK_PRIVATE_NAME: return "private_name"; + case TOK_EOF: return "eof"; + case TOK_ERROR: return "error"; + /* compound operators */ + case TOK_MUL_ASSIGN: return "*="; + case TOK_DIV_ASSIGN: return "/="; + case TOK_MOD_ASSIGN: return "%="; + case TOK_PLUS_ASSIGN: return "+="; + case TOK_MINUS_ASSIGN: return "-="; + case TOK_SHL_ASSIGN: return "<<="; + case TOK_SAR_ASSIGN: return ">>="; + case TOK_SHR_ASSIGN: return ">>>="; + case TOK_AND_ASSIGN: return "&="; + case TOK_XOR_ASSIGN: return "^="; + case TOK_OR_ASSIGN: return "|="; + case TOK_POW_ASSIGN: return "**="; + case TOK_LAND_ASSIGN: return "&&="; + case TOK_LOR_ASSIGN: return "||="; + case TOK_DEC: return "--"; + case TOK_INC: return "++"; + case TOK_SHL: return "<<"; + case TOK_SAR: return ">>"; + case TOK_SHR: return ">>>"; + case TOK_LT: return "<"; + case TOK_LTE: return "<="; + case TOK_GT: return ">"; + case TOK_GTE: return ">="; + case TOK_EQ: return "=="; + case TOK_STRICT_EQ: return "==="; + case TOK_NEQ: return "!="; + case TOK_STRICT_NEQ: return "!=="; + case TOK_LAND: return "&&"; + case TOK_LOR: return "||"; + case TOK_POW: return "**"; + case TOK_ARROW: return "=>"; + /* keywords */ + case TOK_NULL: return "null"; + case TOK_FALSE: return "false"; + case TOK_TRUE: return "true"; + case TOK_IF: return "if"; + case TOK_ELSE: return "else"; + case TOK_RETURN: return "return"; + case TOK_GO: return "go"; + case TOK_VAR: return "var"; + case TOK_DEF: return "def"; + case TOK_THIS: return "this"; + case TOK_DELETE: return "delete"; + case TOK_IN: return "in"; + case TOK_DO: return "do"; + case TOK_WHILE: return "while"; + case TOK_FOR: return "for"; + case TOK_BREAK: return "break"; + case TOK_CONTINUE: return "continue"; + case TOK_DISRUPT: return "disrupt"; + case TOK_DISRUPTION: return "disruption"; + case TOK_FUNCTION: return "function"; + case TOK_DEBUGGER: return "debugger"; + case TOK_WITH: return "with"; + case TOK_CLASS: return "class"; + case TOK_CONST: return "const"; + case TOK_ENUM: return "enum"; + case TOK_EXPORT: return "export"; + case TOK_EXTENDS: return "extends"; + case TOK_IMPORT: return "import"; + case TOK_SUPER: return "super"; + case TOK_IMPLEMENTS: return "implements"; + case TOK_INTERFACE: return "interface"; + case TOK_LET: return "let"; + case TOK_PRIVATE: return "private"; + case TOK_PROTECTED: return "protected"; + case TOK_PUBLIC: return "public"; + case TOK_STATIC: return "static"; + case TOK_YIELD: return "yield"; + case TOK_AWAIT: return "await"; + case TOK_OF: return "of"; + default: + /* Single character tokens */ + if (token_val >= 0 && token_val < 128) { + single_char[0] = (char)token_val; + return single_char; + } + return "unknown"; + } +} + +/* ============================================================ + AST JSON Output Implementation + ============================================================ */ + + +/* Add a length-delimited string to a cJSON object (source pointers aren't null-terminated) */ +void cjson_add_strn (cJSON *obj, const char *key, const char *str, size_t len) { + char buf[256]; + char *tmp = (len < sizeof (buf)) ? buf : sys_malloc (len + 1); + memcpy (tmp, str, len); + tmp[len] = '\0'; + cJSON_AddStringToObject (obj, key, tmp); + if (tmp != buf) sys_free (tmp); +} + +/* Compare a length-delimited token string against a null-terminated literal */ +inline BOOL tok_eq (const char *str, size_t len, const char *lit) { + size_t ll = strlen (lit); + return len == ll && memcmp (str, lit, ll) == 0; +} + +cJSON *ast_parse_expr (ASTParseState *s); +cJSON *ast_parse_assign_expr (ASTParseState *s); +cJSON *ast_parse_statement (ASTParseState *s); +void ast_sync_to_statement (ASTParseState *s); +cJSON *ast_parse_block_statements (ASTParseState *s); +cJSON *ast_parse_function_inner (ASTParseState *s, BOOL is_expr); +cJSON *ast_parse_arrow_function (ASTParseState *s); + +/* Check if we're looking at an arrow function starting with '(' */ +BOOL ast_is_arrow_function (ASTParseState *s) { + if (s->token_val != '(') return FALSE; + const uint8_t *p = s->buf_ptr; + int depth = 1; + while (p < s->buf_end && depth > 0) { + uint8_t c = *p++; + if (c == '(') depth++; + else if (c == ')') depth--; + else if (c == '"' || c == '\'' || c == '`') { + /* Skip string */ + uint8_t quote = c; + while (p < s->buf_end && *p != quote) { + if (*p == '\\' && p + 1 < s->buf_end) p++; + p++; + } + if (p < s->buf_end) p++; + } + } + /* Skip whitespace */ + while (p < s->buf_end && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++; + /* Check for => */ + return (p + 1 < s->buf_end && p[0] == '=' && p[1] == '>'); +} + +void ast_free_token (ASTParseState *s) { + if (s->decoded_str) { + sys_free (s->decoded_str); + s->decoded_str = NULL; + } +} + +void ast_get_line_col (ASTParseState *s, const uint8_t *ptr, int *line, int *col) { + *line = get_line_col_cached (&s->lc_cache, col, ptr); +} + +cJSON *ast_node (ASTParseState *s, const char *kind, const uint8_t *start_ptr) { + cJSON *node = cJSON_CreateObject (); + cJSON_AddStringToObject (node, "kind", kind); + int at = (int)(start_ptr - s->buf_start); + int from_row, from_col; + ast_get_line_col (s, start_ptr, &from_row, &from_col); + cJSON_AddNumberToObject (node, "at", at); + cJSON_AddNumberToObject (node, "from_row", from_row); + cJSON_AddNumberToObject (node, "from_column", from_col); + return node; +} + +void ast_node_end (ASTParseState *s, cJSON *node, const uint8_t *end_ptr) { + int to_row, to_col; + ast_get_line_col (s, end_ptr, &to_row, &to_col); + cJSON_AddNumberToObject (node, "to_row", to_row); + cJSON_AddNumberToObject (node, "to_column", to_col); +} + +void ast_error (ASTParseState *s, const uint8_t *ptr, const char *fmt, ...) { + if (s->error_count >= 5) return; + s->error_count++; + + va_list ap; + char buf[256]; + int line, col; + + va_start (ap, fmt); + vsnprintf (buf, sizeof(buf), fmt, ap); + va_end (ap); + + ast_get_line_col (s, ptr, &line, &col); + + cJSON *err = cJSON_CreateObject (); + cJSON_AddStringToObject (err, "message", buf); + cJSON_AddNumberToObject (err, "line", line + 1); /* 1-based for user display */ + cJSON_AddNumberToObject (err, "column", col + 1); + cJSON_AddNumberToObject (err, "offset", (int)(ptr - s->buf_start)); + + if (!s->errors) { + s->errors = cJSON_CreateArray (); + } + cJSON_AddItemToArray (s->errors, err); + s->has_error = 1; +} + +/* Decode escape sequences in a string literal into dst. Returns decoded length. */ +static int ast_decode_string (const uint8_t *src, int len, char *dst) { + const uint8_t *end = src + len; + char *out = dst; + while (src < end) { + if (*src == '\\' && src + 1 < end) { + src++; + switch (*src) { + case 'n': *out++ = '\n'; src++; break; + case 't': *out++ = '\t'; src++; break; + case 'r': *out++ = '\r'; src++; break; + case '\\': *out++ = '\\'; src++; break; + case '\'': *out++ = '\''; src++; break; + case '\"': *out++ = '\"'; src++; break; + case '0': *out++ = '\0'; src++; break; + case 'b': *out++ = '\b'; src++; break; + case 'f': *out++ = '\f'; src++; break; + case 'v': *out++ = '\v'; src++; break; + case 'u': { + src++; + unsigned int cp = 0; + for (int i = 0; i < 4 && src < end; i++, src++) { + cp <<= 4; + if (*src >= '0' && *src <= '9') cp |= *src - '0'; + else if (*src >= 'a' && *src <= 'f') cp |= *src - 'a' + 10; + else if (*src >= 'A' && *src <= 'F') cp |= *src - 'A' + 10; + else break; + } + out += unicode_to_utf8 ((uint8_t *)out, cp); + } break; + default: *out++ = *src++; break; + } + } else { + *out++ = *src++; + } + } + return out - dst; +} + +int ast_next_token (ASTParseState *s) { + const uint8_t *p; + int c; + BOOL ident_has_escape; + + ast_free_token (s); + p = s->buf_ptr; + s->got_lf = FALSE; + +redo: + s->token_ptr = p; + c = *p; + switch (c) { + case 0: + if (p >= s->buf_end) { + s->token_val = TOK_EOF; + } else { + goto def_token; + } + break; + case '`': { + const uint8_t *start = p; + p++; + while (p < s->buf_end && *p != '`') { + if (*p == '\\' && p + 1 < s->buf_end) { p += 2; continue; } + if (*p == '$' && p + 1 < s->buf_end && p[1] == '{') { + p += 2; + int depth = 1; + while (p < s->buf_end && depth > 0) { + if (*p == '{') { depth++; p++; } + else if (*p == '}') { depth--; p++; } + else if (*p == '\'' || *p == '"' || *p == '`') { + int q = *p; p++; + while (p < s->buf_end && *p != q) { + if (*p == '\\' && p + 1 < s->buf_end) p++; + p++; + } + if (p < s->buf_end) p++; + } else { p++; } + } + continue; + } + p++; + } + if (p >= s->buf_end) { + ast_error (s, start, "unterminated template literal"); + s->buf_ptr = p; + goto redo; + } + p++; + s->token_val = TOK_TEMPLATE; + { + const uint8_t *raw = start + 1; + int raw_len = p - start - 2; + BOOL has_escape = FALSE; + for (int i = 0; i < raw_len; i++) { + if (raw[i] == '\\') { has_escape = TRUE; break; } + } + if (has_escape) { + char *buf = sys_malloc (raw_len * 4 + 1); + int decoded_len = ast_decode_string (raw, raw_len, buf); + s->decoded_str = buf; + s->token_u.str.str = buf; + s->token_u.str.len = decoded_len; + } else { + s->token_u.str.str = (const char *)raw; + s->token_u.str.len = raw_len; + } + } + } break; + case '\'': + case '\"': { + const uint8_t *start = p; + int quote = c; + p++; + while (p < s->buf_end && *p != quote) { + if (*p == '\\' && p + 1 < s->buf_end) p++; + p++; + } + if (p >= s->buf_end) { + ast_error (s, start, "unterminated string literal"); + s->buf_ptr = p; + goto redo; + } + p++; + /* Store the string content without quotes, decoding escape sequences */ + s->token_val = TOK_STRING; + { + const uint8_t *raw = start + 1; + int raw_len = p - start - 2; + /* Check if any escape sequences need decoding */ + BOOL has_escape = FALSE; + for (int i = 0; i < raw_len; i++) { + if (raw[i] == '\\') { has_escape = TRUE; break; } + } + if (has_escape) { + char *buf = sys_malloc (raw_len * 4 + 1); + int decoded_len = ast_decode_string (raw, raw_len, buf); + s->decoded_str = buf; + s->token_u.str.str = buf; + s->token_u.str.len = decoded_len; + } else { + s->token_u.str.str = (const char *)raw; + s->token_u.str.len = raw_len; + } + } + } break; + case '\r': + if (p[1] == '\n') p++; + /* fall through */ + case '\n': + p++; + s->got_lf = TRUE; + goto redo; + case '\f': + case '\v': + case ' ': + case '\t': + p++; + goto redo; + case '/': + if (p[1] == '*') { + const uint8_t *comment_start = p; + p += 2; + BOOL found_end = FALSE; + while (p < s->buf_end) { + if (p[0] == '*' && p + 1 < s->buf_end && p[1] == '/') { + p += 2; + found_end = TRUE; + break; + } + if (*p == '\n' || *p == '\r') s->got_lf = TRUE; + p++; + } + if (!found_end) + ast_error (s, comment_start, "unterminated block comment"); + goto redo; + } else if (p[1] == '/') { + p += 2; + while (p < s->buf_end && *p != '\n' && *p != '\r') p++; + goto redo; + } else if (p[1] == '=') { + p += 2; + s->token_val = TOK_DIV_ASSIGN; + } else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } else { + p++; + s->token_val = c; + } + break; + case '\\': + goto def_token; + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '_': case '$': { + const uint8_t *start = p; + ident_has_escape = FALSE; + p++; + while (p < s->buf_end) { + c = *p; + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '_' || c == '$' || + c == '?' || c == '!') { + p++; + } else if (c >= 0x80) { + /* unicode identifier */ + p++; + while (p < s->buf_end && (*p & 0xc0) == 0x80) p++; + } else { + break; + } + } + size_t len = p - start; + s->token_u.ident.str = (const char *)start; + s->token_u.ident.len = len; + s->token_u.ident.has_escape = ident_has_escape; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + + /* Check for keywords */ + if (len == 2 && !memcmp (start, "if", 2)) s->token_val = TOK_IF; + else if (len == 2 && !memcmp (start, "in", 2)) s->token_val = TOK_IN; + else if (len == 2 && !memcmp (start, "do", 2)) s->token_val = TOK_DO; + else if (len == 2 && !memcmp (start, "go", 2)) s->token_val = TOK_GO; + else if (len == 3 && !memcmp (start, "var", 3)) s->token_val = TOK_VAR; + else if (len == 3 && !memcmp (start, "def", 3)) s->token_val = TOK_DEF; + else if (len == 3 && !memcmp (start, "for", 3)) s->token_val = TOK_FOR; + else if (len == 4 && !memcmp (start, "else", 4)) s->token_val = TOK_ELSE; + else if (len == 4 && !memcmp (start, "this", 4)) s->token_val = TOK_THIS; + else if (len == 4 && !memcmp (start, "null", 4)) s->token_val = TOK_NULL; + else if (len == 4 && !memcmp (start, "true", 4)) s->token_val = TOK_TRUE; + else if (len == 5 && !memcmp (start, "false", 5)) s->token_val = TOK_FALSE; + else if (len == 5 && !memcmp (start, "while", 5)) s->token_val = TOK_WHILE; + else if (len == 5 && !memcmp (start, "break", 5)) s->token_val = TOK_BREAK; + else if (len == 6 && !memcmp (start, "return", 6)) s->token_val = TOK_RETURN; + else if (len == 6 && !memcmp (start, "delete", 6)) s->token_val = TOK_DELETE; + else if (len == 7 && !memcmp (start, "disrupt", 7)) s->token_val = TOK_DISRUPT; + else if (len == 8 && !memcmp (start, "function", 8)) s->token_val = TOK_FUNCTION; + else if (len == 8 && !memcmp (start, "continue", 8)) s->token_val = TOK_CONTINUE; + else if (len == 10 && !memcmp (start, "disruption", 10)) s->token_val = TOK_DISRUPTION; + } break; + case '.': + if (p[1] >= '0' && p[1] <= '9') { + goto parse_number; + } else { + goto def_token; + } + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + parse_number: { + const uint8_t *start = p; + BOOL is_float = FALSE; + /* hex */ + if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { + p += 2; + const uint8_t *digits_start = p; + while (p < s->buf_end && ((c = *p, (c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || c == '_'))) p++; + if (p == digits_start) + ast_error (s, start, "malformed hex number: no digits after '0x'"); + } else if (p[0] == '0' && (p[1] == 'b' || p[1] == 'B')) { + p += 2; + const uint8_t *digits_start = p; + while (p < s->buf_end && (*p == '0' || *p == '1' || *p == '_')) p++; + if (p == digits_start) + ast_error (s, start, "malformed binary number: no digits after '0b'"); + } else if (p[0] == '0' && (p[1] == 'o' || p[1] == 'O')) { + p += 2; + const uint8_t *digits_start = p; + while (p < s->buf_end && (*p >= '0' && *p <= '7')) p++; + if (p == digits_start) + ast_error (s, start, "malformed octal number: no digits after '0o'"); + } else { + while (p < s->buf_end && ((*p >= '0' && *p <= '9') || *p == '_')) p++; + if (p < s->buf_end && *p == '.') { + is_float = TRUE; + p++; + while (p < s->buf_end && ((*p >= '0' && *p <= '9') || *p == '_')) p++; + } + if (p < s->buf_end && (*p == 'e' || *p == 'E')) { + is_float = TRUE; + p++; + if (p < s->buf_end && (*p == '+' || *p == '-')) p++; + const uint8_t *exp_start = p; + while (p < s->buf_end && (*p >= '0' && *p <= '9')) p++; + if (p == exp_start) + ast_error (s, start, "malformed number: no digits after exponent"); + } + } + s->token_val = TOK_NUMBER; + /* Parse the number value */ + char *numstr = sys_malloc (p - start + 1); + memcpy (numstr, start, p - start); + numstr[p - start] = '\0'; + double val = strtod (numstr, NULL); + sys_free (numstr); + s->token_u.num.val = val; + } break; + case '*': + if (p[1] == '=') { p += 2; s->token_val = TOK_MUL_ASSIGN; } + else if (p[1] == '*') { + if (p[2] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 3; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 3; + } + else if (p[2] == '=') { p += 3; s->token_val = TOK_POW_ASSIGN; } + else { p += 2; s->token_val = TOK_POW; } + } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '%': + if (p[1] == '=') { p += 2; s->token_val = TOK_MOD_ASSIGN; } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '+': + if (p[1] == '=') { p += 2; s->token_val = TOK_PLUS_ASSIGN; } + else if (p[1] == '+') { p += 2; s->token_val = TOK_INC; } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '-': + if (p[1] == '=') { p += 2; s->token_val = TOK_MINUS_ASSIGN; } + else if (p[1] == '-') { p += 2; s->token_val = TOK_DEC; } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '<': + if (p[1] == '=' && p[2] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 3; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 3; + } + else if (p[1] == '=') { p += 2; s->token_val = TOK_LTE; } + else if (p[1] == '<') { + if (p[2] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 3; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 3; + } + else if (p[2] == '=') { p += 3; s->token_val = TOK_SHL_ASSIGN; } + else { p += 2; s->token_val = TOK_SHL; } + } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '>': + if (p[1] == '=' && p[2] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 3; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 3; + } + else if (p[1] == '=') { p += 2; s->token_val = TOK_GTE; } + else if (p[1] == '>') { + if (p[2] == '>') { + if (p[3] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 4; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 4; + } + else if (p[3] == '=') { p += 4; s->token_val = TOK_SHR_ASSIGN; } + else { p += 3; s->token_val = TOK_SHR; } + } + else if (p[2] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 3; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 3; + } + else if (p[2] == '=') { p += 3; s->token_val = TOK_SAR_ASSIGN; } + else { p += 2; s->token_val = TOK_SAR; } + } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '=': + if (p[1] == '=') { + if (p[2] == '=') { p += 3; s->token_val = TOK_STRICT_EQ; } + else { p += 2; s->token_val = TOK_EQ; } + } else if (p[1] == '>') { p += 2; s->token_val = TOK_ARROW; } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '!': + if (p[1] == '=') { + if (p[2] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 3; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 3; + } + else if (p[2] == '=') { p += 3; s->token_val = TOK_STRICT_NEQ; } + else { p += 2; s->token_val = TOK_NEQ; } + } else { goto def_token; } + break; + case '&': + if (p[1] == '&') { + if (p[2] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 3; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 3; + } + else if (p[2] == '=') { p += 3; s->token_val = TOK_LAND_ASSIGN; } + else { p += 2; s->token_val = TOK_LAND; } + } + else if (p[1] == '=') { p += 2; s->token_val = TOK_AND_ASSIGN; } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '|': + if (p[1] == '|') { + if (p[2] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 3; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 3; + } + else if (p[2] == '=') { p += 3; s->token_val = TOK_LOR_ASSIGN; } + else { p += 2; s->token_val = TOK_LOR; } + } + else if (p[1] == '=') { p += 2; s->token_val = TOK_OR_ASSIGN; } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '^': + if (p[1] == '=') { p += 2; s->token_val = TOK_XOR_ASSIGN; } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '[': + if (p[1] == ']' && p[2] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 3; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 3; + } + else { goto def_token; } + break; + case '~': + if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '?': + goto def_token; + default: + def_token: + p++; + s->token_val = c; + break; + } + s->buf_ptr = p; + return 0; +} + +/* Tokenizer function that does NOT skip whitespace/comments - emits them as tokens */ +int tokenize_next (ASTParseState *s) { + const uint8_t *p; + int c; + BOOL ident_has_escape; + + ast_free_token (s); + p = s->buf_ptr; + s->got_lf = FALSE; + + s->token_ptr = p; + c = *p; + switch (c) { + case 0: + if (p >= s->buf_end) { + s->token_val = TOK_EOF; + } else { + goto def_token; + } + break; + case '`': { + const uint8_t *start = p; + p++; + while (p < s->buf_end && *p != '`') { + if (*p == '\\' && p + 1 < s->buf_end) { p += 2; continue; } + if (*p == '$' && p + 1 < s->buf_end && p[1] == '{') { + p += 2; + int depth = 1; + while (p < s->buf_end && depth > 0) { + if (*p == '{') { depth++; p++; } + else if (*p == '}') { depth--; p++; } + else if (*p == '\'' || *p == '"' || *p == '`') { + int q = *p; p++; + while (p < s->buf_end && *p != q) { + if (*p == '\\' && p + 1 < s->buf_end) p++; + p++; + } + if (p < s->buf_end) p++; + } else { p++; } + } + continue; + } + p++; + } + if (p >= s->buf_end) { + ast_error (s, start, "unterminated template literal"); + s->token_val = TOK_ERROR; + s->token_u.str.str = (const char *)(start + 1); + s->token_u.str.len = p - start - 1; + } else { + p++; + s->token_val = TOK_TEMPLATE; + s->token_u.str.str = (const char *)(start + 1); + s->token_u.str.len = p - start - 2; + } + } break; + case '\'': + case '\"': { + const uint8_t *start = p; + int quote = c; + p++; + while (p < s->buf_end && *p != quote) { + if (*p == '\\' && p + 1 < s->buf_end) p++; + p++; + } + if (p >= s->buf_end) { + ast_error (s, start, "unterminated string literal"); + s->token_val = TOK_ERROR; + s->token_u.str.str = (const char *)(start + 1); + s->token_u.str.len = p - start - 1; + } else { + p++; + s->token_val = TOK_STRING; + s->token_u.str.str = (const char *)(start + 1); + s->token_u.str.len = p - start - 2; + } + } break; + case '\r': + if (p[1] == '\n') p++; + /* fall through */ + case '\n': + p++; + s->got_lf = TRUE; + s->token_val = TOK_NEWLINE; + break; + case '\f': + case '\v': + case ' ': + case '\t': { + /* Collect consecutive whitespace (excluding newlines) */ + while (p < s->buf_end && (*p == ' ' || *p == '\t' || *p == '\f' || *p == '\v')) p++; + s->token_val = TOK_SPACE; + } break; + case '/': + if (p[1] == '*') { + /* Multi-line comment */ + const uint8_t *comment_start = p; + p += 2; + BOOL found_end = FALSE; + while (p < s->buf_end) { + if (p[0] == '*' && p + 1 < s->buf_end && p[1] == '/') { + p += 2; + found_end = TRUE; + break; + } + if (*p == '\n' || *p == '\r') s->got_lf = TRUE; + p++; + } + if (!found_end) { + ast_error (s, comment_start, "unterminated block comment"); + s->token_val = TOK_ERROR; + } else { + s->token_val = TOK_COMMENT; + } + } else if (p[1] == '/') { + /* Single-line comment */ + p += 2; + while (p < s->buf_end && *p != '\n' && *p != '\r') p++; + s->token_val = TOK_COMMENT; + } else if (p[1] == '=') { + p += 2; + s->token_val = TOK_DIV_ASSIGN; + } else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } else { + p++; + s->token_val = c; + } + break; + case '\\': + goto def_token; + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '_': case '$': { + const uint8_t *start = p; + ident_has_escape = FALSE; + p++; + while (p < s->buf_end) { + c = *p; + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || c == '_' || c == '$' || + c == '?' || c == '!') { + p++; + } else if (c >= 0x80) { + p++; + while (p < s->buf_end && (*p & 0xc0) == 0x80) p++; + } else { + break; + } + } + size_t len = p - start; + s->token_u.ident.str = (const char *)start; + s->token_u.ident.len = len; + s->token_u.ident.has_escape = ident_has_escape; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + + /* Check for keywords */ + if (len == 2 && !memcmp (start, "if", 2)) s->token_val = TOK_IF; + else if (len == 2 && !memcmp (start, "in", 2)) s->token_val = TOK_IN; + else if (len == 2 && !memcmp (start, "do", 2)) s->token_val = TOK_DO; + else if (len == 2 && !memcmp (start, "go", 2)) s->token_val = TOK_GO; + else if (len == 3 && !memcmp (start, "var", 3)) s->token_val = TOK_VAR; + else if (len == 3 && !memcmp (start, "def", 3)) s->token_val = TOK_DEF; + else if (len == 3 && !memcmp (start, "for", 3)) s->token_val = TOK_FOR; + else if (len == 4 && !memcmp (start, "else", 4)) s->token_val = TOK_ELSE; + else if (len == 4 && !memcmp (start, "this", 4)) s->token_val = TOK_THIS; + else if (len == 4 && !memcmp (start, "null", 4)) s->token_val = TOK_NULL; + else if (len == 4 && !memcmp (start, "true", 4)) s->token_val = TOK_TRUE; + else if (len == 5 && !memcmp (start, "false", 5)) s->token_val = TOK_FALSE; + else if (len == 5 && !memcmp (start, "while", 5)) s->token_val = TOK_WHILE; + else if (len == 5 && !memcmp (start, "break", 5)) s->token_val = TOK_BREAK; + else if (len == 6 && !memcmp (start, "return", 6)) s->token_val = TOK_RETURN; + else if (len == 6 && !memcmp (start, "delete", 6)) s->token_val = TOK_DELETE; + else if (len == 7 && !memcmp (start, "disrupt", 7)) s->token_val = TOK_DISRUPT; + else if (len == 8 && !memcmp (start, "function", 8)) s->token_val = TOK_FUNCTION; + else if (len == 8 && !memcmp (start, "continue", 8)) s->token_val = TOK_CONTINUE; + else if (len == 10 && !memcmp (start, "disruption", 10)) s->token_val = TOK_DISRUPTION; + } break; + case '.': + if (p[1] >= '0' && p[1] <= '9') { + goto tokenize_number; + } else { + goto def_token; + } + break; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + tokenize_number: { + const uint8_t *start = p; + BOOL is_float = FALSE; + if (p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) { + p += 2; + const uint8_t *digits_start = p; + while (p < s->buf_end && ((c = *p, (c >= '0' && c <= '9') || + (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') || c == '_'))) p++; + if (p == digits_start) + ast_error (s, start, "malformed hex number: no digits after '0x'"); + } else if (p[0] == '0' && (p[1] == 'b' || p[1] == 'B')) { + p += 2; + const uint8_t *digits_start = p; + while (p < s->buf_end && (*p == '0' || *p == '1' || *p == '_')) p++; + if (p == digits_start) + ast_error (s, start, "malformed binary number: no digits after '0b'"); + } else if (p[0] == '0' && (p[1] == 'o' || p[1] == 'O')) { + p += 2; + const uint8_t *digits_start = p; + while (p < s->buf_end && (*p >= '0' && *p <= '7')) p++; + if (p == digits_start) + ast_error (s, start, "malformed octal number: no digits after '0o'"); + } else { + while (p < s->buf_end && ((*p >= '0' && *p <= '9') || *p == '_')) p++; + if (p < s->buf_end && *p == '.') { + is_float = TRUE; + p++; + while (p < s->buf_end && ((*p >= '0' && *p <= '9') || *p == '_')) p++; + } + if (p < s->buf_end && (*p == 'e' || *p == 'E')) { + is_float = TRUE; + p++; + if (p < s->buf_end && (*p == '+' || *p == '-')) p++; + const uint8_t *exp_start = p; + while (p < s->buf_end && (*p >= '0' && *p <= '9')) p++; + if (p == exp_start) + ast_error (s, start, "malformed number: no digits after exponent"); + } + } + (void)is_float; + s->token_val = TOK_NUMBER; + char *numstr = sys_malloc (p - start + 1); + memcpy (numstr, start, p - start); + numstr[p - start] = '\0'; + double val = strtod (numstr, NULL); + sys_free (numstr); + s->token_u.num.val = val; + } break; + case '*': + if (p[1] == '=') { p += 2; s->token_val = TOK_MUL_ASSIGN; } + else if (p[1] == '*') { + if (p[2] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 3; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 3; + } + else if (p[2] == '=') { p += 3; s->token_val = TOK_POW_ASSIGN; } + else { p += 2; s->token_val = TOK_POW; } + } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '%': + if (p[1] == '=') { p += 2; s->token_val = TOK_MOD_ASSIGN; } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '+': + if (p[1] == '=') { p += 2; s->token_val = TOK_PLUS_ASSIGN; } + else if (p[1] == '+') { p += 2; s->token_val = TOK_INC; } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '-': + if (p[1] == '=') { p += 2; s->token_val = TOK_MINUS_ASSIGN; } + else if (p[1] == '-') { p += 2; s->token_val = TOK_DEC; } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '<': + if (p[1] == '=' && p[2] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 3; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 3; + } + else if (p[1] == '=') { p += 2; s->token_val = TOK_LTE; } + else if (p[1] == '<') { + if (p[2] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 3; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 3; + } + else if (p[2] == '=') { p += 3; s->token_val = TOK_SHL_ASSIGN; } + else { p += 2; s->token_val = TOK_SHL; } + } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '>': + if (p[1] == '=' && p[2] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 3; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 3; + } + else if (p[1] == '=') { p += 2; s->token_val = TOK_GTE; } + else if (p[1] == '>') { + if (p[2] == '>') { + if (p[3] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 4; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 4; + } + else if (p[3] == '=') { p += 4; s->token_val = TOK_SHR_ASSIGN; } + else { p += 3; s->token_val = TOK_SHR; } + } + else if (p[2] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 3; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 3; + } + else if (p[2] == '=') { p += 3; s->token_val = TOK_SAR_ASSIGN; } + else { p += 2; s->token_val = TOK_SAR; } + } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '=': + if (p[1] == '=') { + if (p[2] == '=') { p += 3; s->token_val = TOK_STRICT_EQ; } + else { p += 2; s->token_val = TOK_EQ; } + } else if (p[1] == '>') { p += 2; s->token_val = TOK_ARROW; } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '!': + if (p[1] == '=') { + if (p[2] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 3; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 3; + } + else if (p[2] == '=') { p += 3; s->token_val = TOK_STRICT_NEQ; } + else { p += 2; s->token_val = TOK_NEQ; } + } else { goto def_token; } + break; + case '&': + if (p[1] == '&') { + if (p[2] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 3; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 3; + } + else if (p[2] == '=') { p += 3; s->token_val = TOK_LAND_ASSIGN; } + else { p += 2; s->token_val = TOK_LAND; } + } + else if (p[1] == '=') { p += 2; s->token_val = TOK_AND_ASSIGN; } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '|': + if (p[1] == '|') { + if (p[2] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 3; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 3; + } + else if (p[2] == '=') { p += 3; s->token_val = TOK_LOR_ASSIGN; } + else { p += 2; s->token_val = TOK_LOR; } + } + else if (p[1] == '=') { p += 2; s->token_val = TOK_OR_ASSIGN; } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '^': + if (p[1] == '=') { p += 2; s->token_val = TOK_XOR_ASSIGN; } + else if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '[': + if (p[1] == ']' && p[2] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 3; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 3; + } + else { goto def_token; } + break; + case '~': + if (p[1] == '!') { + s->token_u.ident.str = (const char *)p; + s->token_u.ident.len = 2; + s->token_u.ident.has_escape = FALSE; + s->token_u.ident.is_reserved = FALSE; + s->token_val = TOK_IDENT; + p += 2; + } + else { goto def_token; } + break; + case '?': + goto def_token; + default: + def_token: + p++; + s->token_val = c; + break; + } + s->buf_ptr = p; + return 0; +} + + +static cJSON *build_token_object (ASTParseState *s) { + cJSON *tok = cJSON_CreateObject (); + const char *kind = ast_token_kind_str (s->token_val); + cJSON_AddStringToObject (tok, "kind", kind); + + /* Position info */ + int at = (int)(s->token_ptr - s->buf_start); + int from_row, from_col; + ast_get_line_col (s, s->token_ptr, &from_row, &from_col); + int to_row, to_col; + ast_get_line_col (s, s->buf_ptr, &to_row, &to_col); + + cJSON_AddNumberToObject (tok, "at", at); + cJSON_AddNumberToObject (tok, "from_row", from_row); + cJSON_AddNumberToObject (tok, "from_column", from_col); + cJSON_AddNumberToObject (tok, "to_row", to_row); + cJSON_AddNumberToObject (tok, "to_column", to_col); + + /* Value field based on token type */ + switch (s->token_val) { + case TOK_NUMBER: { + /* Store original source text as value */ + size_t len = s->buf_ptr - s->token_ptr; + char *text = sys_malloc (len + 1); + memcpy (text, s->token_ptr, len); + text[len] = '\0'; + cJSON_AddStringToObject (tok, "value", text); + sys_free (text); + /* Store parsed number */ + double d = s->token_u.num.val; + cJSON_AddNumberToObject (tok, "number", d); + } break; + case TOK_STRING: + case TOK_TEMPLATE: { + cjson_add_strn (tok, "value", s->token_u.str.str, s->token_u.str.len); + } break; + case TOK_IDENT: { + cjson_add_strn (tok, "value", s->token_u.ident.str, s->token_u.ident.len); + } break; + case TOK_ERROR: { + /* Store the raw source text as value */ + size_t len = s->buf_ptr - s->token_ptr; + char *text = sys_malloc (len + 1); + memcpy (text, s->token_ptr, len); + text[len] = '\0'; + cJSON_AddStringToObject (tok, "value", text); + sys_free (text); + } break; + case TOK_COMMENT: + case TOK_SPACE: + case TOK_NEWLINE: { + /* Store the raw source text */ + size_t len = s->buf_ptr - s->token_ptr; + char *text = sys_malloc (len + 1); + memcpy (text, s->token_ptr, len); + text[len] = '\0'; + cJSON_AddStringToObject (tok, "value", text); + sys_free (text); + } break; + default: + /* No value field for operators/punctuators/keywords */ + break; + } + + return tok; +} + +char *JS_Tokenize (const char *source, size_t len, const char *filename) { + ASTParseState s; + memset (&s, 0, sizeof (s)); + + s.filename = filename; + s.buf_start = (const uint8_t *)source; + s.buf_ptr = (const uint8_t *)source; + s.buf_end = (const uint8_t *)source + len; + s.function_nr = 0; + s.errors = NULL; + s.has_error = 0; + s.lc_cache.ptr = s.buf_start; + s.lc_cache.buf_start = s.buf_start; + + cJSON *root = cJSON_CreateObject (); + cJSON_AddStringToObject (root, "filename", filename); + cJSON *tokens = cJSON_AddArrayToObject (root, "tokens"); + + /* Tokenize all tokens including whitespace */ + while (1) { + tokenize_next (&s); + cJSON *tok = build_token_object (&s); + cJSON_AddItemToArray (tokens, tok); + if (s.token_val == TOK_EOF) break; + } + + /* Add errors to output if any */ + if (s.errors) { + cJSON_AddItemToObject (root, "errors", s.errors); + } + + char *json = cJSON_PrintUnformatted (root); + cJSON_Delete (root); + return json; +} + +/* ============================================================ + MACH Compiler — AST directly to binary JSCodeRegister + ============================================================ */ + +/* Variable kinds */ +#define MACH_VAR_ARG 0 +#define MACH_VAR_LOCAL 1 +#define MACH_VAR_CLOSED 2 + +/* Variable resolution result */ +typedef enum MachVarResolution { + MACH_VAR_LOCAL_SLOT, /* variable is in current scope */ + MACH_VAR_CLOSURE, /* variable is in parent scope */ + MACH_VAR_UNBOUND /* variable not found in any scope */ +} MachVarResolution;