/* * 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 *)JS_VALUE_GET_PTR (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; int class_count; /* size of class_array */ JSClass *class_array; /* see JS_SetStripInfo() */ uint8_t strip_flags; /* Stack overflow protection */ size_t stack_size; const uint8_t *stack_top; const uint8_t *stack_limit; /* Current exception (for error propagation) */ JSValue current_exception; struct JSStackFrame *current_stack_frame; /* User data */ void *user_opaque; /* Interrupt handler for checking execution limits */ JSInterruptHandler *interrupt_handler; void *interrupt_opaque; /* Register VM frame tracking for stack traces */ void *current_register_frame; /* JSFrameRegister* at exception time */ uint32_t current_register_pc; /* PC at exception time */ }; 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_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_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) */ 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 */ JSValue current_exception; BOOL current_exception_is_uncatchable : 8; BOOL in_out_of_memory : 8; JSInterruptHandler *interrupt_handler; void *interrupt_opaque; /* 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 (JSRuntime *rt, 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_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 (JSRuntime *rt, 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 (rt, 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 (JSRuntime *rt, 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 (JSRuntime *rt, size_t alloca_size) { uintptr_t sp; sp = js_get_stack_pointer () - alloca_size; return unlikely (sp < (uintptr_t)rt->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 rt->current_exception = gc_copy_value (ctx, rt->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", rt->class_count); fflush(stdout); #endif for (int i = 0; i < rt->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 = rt->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)); /* create the object, array and function classes */ if (init_class_range (rt, js_std_class_def, JS_CLASS_OBJECT, countof (js_std_class_def)) < 0) goto fail; rt->stack_size = JS_DEFAULT_STACK_SIZE; JS_UpdateStackTop (rt); rt->current_exception = JS_UNINITIALIZED; return rt; fail: JS_FreeRuntime (rt); return NULL; } 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 (JSRuntime *rt, JSInterruptHandler *cb, void *opaque) { rt->interrupt_handler = cb; rt->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) { /* free the classes */ js_free_rt (rt->class_array); /* Destroy buddy allocator */ buddy_destroy (&rt->buddy); } /* 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; int i; /* 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->class_proto = js_malloc_rt (sizeof (ctx->class_proto[0]) * rt->class_count); if (!ctx->class_proto) { js_free_rt (ctx); return NULL; } ctx->rt = rt; for (i = 0; i < rt->class_count; i++) ctx->class_proto[i] = JS_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_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_proto); js_free_rt (ctx); return NULL; } ctx->value_stack_top = 0; /* Initialize register VM frame root */ ctx->reg_current_frame = JS_NULL; /* 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_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_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) { JSRuntime *rt = ctx->rt; assert (class_id < rt->class_count); set_value (ctx, &ctx->class_proto[class_id], obj); } JSValue JS_GetClassProto (JSContext *ctx, JSClassID class_id) { JSRuntime *rt = ctx->rt; assert (class_id < rt->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 < rt->class_count; i++) { } 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; } } JSRuntime *JS_GetRuntime (JSContext *ctx) { return ctx->rt; } static void update_stack_limit (JSRuntime *rt) { if (rt->stack_size == 0) { rt->stack_limit = 0; /* no limit */ } else { rt->stack_limit = rt->stack_top - rt->stack_size; } } void JS_SetMaxStackSize (JSRuntime *rt, size_t stack_size) { rt->stack_size = stack_size; update_stack_limit (rt); } void JS_UpdateStackTop (JSRuntime *rt) { rt->stack_top = (const uint8_t *)js_get_stack_pointer (); update_stack_limit (rt); } 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 (JSRuntime *rt, JSClassID class_id) { return (class_id < rt->class_count && rt->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 (JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def, const char *name) { int new_size; JSClass *cl, *new_class_array; if (class_id >= (1 << 16)) return -1; if (class_id < rt->class_count && rt->class_array[class_id].class_id != 0) return -1; if (class_id >= rt->class_count) { new_size = max_int (JS_CLASS_INIT_COUNT, max_int (class_id + 1, rt->class_count * 3 / 2)); /* reallocate the class array */ new_class_array = js_realloc_rt (rt->class_array, sizeof (JSClass) * new_size); if (!new_class_array) return -1; memset (new_class_array + rt->class_count, 0, (new_size - rt->class_count) * sizeof (JSClass)); rt->class_array = new_class_array; rt->class_count = new_size; } cl = &rt->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; /* call field removed from JSClass - use class_def->call directly if needed */ return 0; } int JS_NewClass (JSRuntime *rt, JSClassID class_id, const JSClassDef *class_def) { /* class_name is stored directly as const char* */ return JS_NewClass1 (rt, 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) { 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, 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 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; f->name = name ? js_key_new (ctx, 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; 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); 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)) { if (js_array_grow (ctx, arr_ptr, idx + 1) < 0) return -1; 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) { JSRuntime *rt = ctx->rt; rt->current_exception = obj; /* uncatchable flag removed - not needed with copying GC */ return JS_EXCEPTION; } /* return the pending exception (cannot be called twice). */ JSValue JS_GetException (JSContext *ctx) { JSValue val; JSRuntime *rt = ctx->rt; val = rt->current_exception; rt->current_exception = JS_UNINITIALIZED; return val; } JS_BOOL JS_HasException (JSContext *ctx) { return !JS_IsUninitialized (ctx->rt->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->rt->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) { JSRuntime *rt = ctx->rt; JSStackFrame *sf; BOOL add_backtrace; /* the backtrace is added later if called from a bytecode function */ sf = rt->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) { JSRuntime *rt = ctx->rt; const char *name = rt->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) { JSRuntime *rt = ctx->rt; ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; if (rt->interrupt_handler) { if (rt->interrupt_handler (rt, rt->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) { JSValue func = JS_GetProperty (ctx, this_val, method); if (JS_IsException (func)) return JS_EXCEPTION; if (!JS_IsFunction (func)) { return JS_NULL; /* Method not found or not callable */ } JSValue ret = JS_Call (ctx, func, this_val, argc, argv); 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 - try immediate ASCII first */ int len = strlen (prop); JSValue key; if (len <= MIST_ASCII_MAX_LEN) { key = MIST_TryNewImmediateASCII (prop, len); if (JS_IsNull (key)) { key = js_new_string8_len (ctx, prop, len); } } else { key = js_new_string8_len (ctx, prop, len); } 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) { JSRuntime *rt = ctx->rt; 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 (rt, sizeof (arg_buf[0]) * arg_count)) return JS_ThrowStackOverflow (ctx); prev_sf = rt->current_stack_frame; sf->prev_frame = prev_sf; rt->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 (); } rt->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) { JSRuntime *rt = caller_ctx->rt; 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 (rt, 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 = rt->current_stack_frame; rt->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, rt->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, rt->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++ = rt->current_exception; rt->current_exception = JS_UNINITIALIZED; pc = b->byte_code_buf + pos; goto restart; } } } ret_val = JS_EXCEPTION; done: rt->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_DOUBLE_QUESTION_MARK_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_DOUBLE_QUESTION_MARK, TOK_QUESTION_MARK_DOT, 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_DOUBLE_QUESTION_MARK_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 "=>"; case TOK_DOUBLE_QUESTION_MARK: return "??"; case TOK_QUESTION_MARK_DOT: 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->rt->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)) 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->rt, 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 '?': if (p[1] == '?') { if (p[2] == '=') { p += 3; s->token.val = TOK_DOUBLE_QUESTION_MARK_ASSIGN; } else { p += 2; s->token.val = TOK_DOUBLE_QUESTION_MARK; } } else if (p[1] == '.' && !(p[2] >= '0' && p[2] <= '9')) { p += 2; s->token.val = TOK_QUESTION_MARK_DOT; } else { goto def_token; } break; 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->rt, 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->rt->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 == TOK_QUESTION_MARK_DOT) { if ((parse_flags & PF_POSTFIX_CALL) == 0) return js_parse_error ( s, "new keyword cannot be used with an optional chain"); op_token_ptr = s->token.ptr; /* optional chaining */ if (next_token (s)) return -1; has_optional_chain = TRUE; if (s->token.val == '(' && accept_lparen) { goto parse_func_call; } else if (s->token.val == '[') { goto parse_array_access; } else { goto parse_property; } } else 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) { if (s->token.val == TOK_DOUBLE_QUESTION_MARK) return js_parse_error (s, "cannot mix ?? with && or ||"); break; } } emit_label (s, label1); } return 0; } static __exception int js_parse_coalesce_expr (JSParseState *s, int parse_flags) { int label1; if (js_parse_logical_and_or (s, TOK_LOR, parse_flags)) return -1; if (s->token.val == TOK_DOUBLE_QUESTION_MARK) { label1 = new_label (s); for (;;) { if (next_token (s)) return -1; emit_op (s, OP_dup); emit_op (s, OP_is_null); emit_goto (s, OP_if_false, label1); emit_op (s, OP_drop); if (js_parse_expr_binary (s, 8, parse_flags)) return -1; if (s->token.val != TOK_DOUBLE_QUESTION_MARK) break; } emit_label (s, label1); } return 0; } /* 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_DOUBLE_QUESTION_MARK_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); if (op == TOK_DOUBLE_QUESTION_MARK_ASSIGN) emit_op (s, OP_is_null); 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->rt, 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->rt, 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; } obj = JS_NewObjectProtoClass (ctx, ctx->class_proto[JS_CLASS_REGEXP], JS_CLASS_REGEXP); if (JS_IsException (obj)) goto fail; p = JS_VALUE_GET_OBJ (obj); /* Allocate JSRegExp and store in opaque slot */ re = js_malloc (ctx, sizeof(JSRegExp)); if (!re) goto fail; 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, 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 via string_get (not JS_ToCStringLen which UTF-8 encodes and would mangle bytes >= 128) */ 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"); JS_SetPropertyInternal (ctx, obj, key, JS_NewInt32 (ctx, 0)); } return obj; } 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->rt, alloca_size); } int lre_check_timeout (void *opaque) { JSContext *ctx = opaque; JSRuntime *rt = ctx->rt; return (rt->interrupt_handler && rt->interrupt_handler (rt, rt->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 that must be freed by caller. 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 (ctx, len * 2 * sizeof (uint16_t)); if (!buf) 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; 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; JS_PushGCRef (ctx, &str_ref); str_ref.val = JS_ToString (ctx, argv[0]); if (JS_IsException (str_ref.val)) { JS_PopGCRef (ctx, &str_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); 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_val, "lastIndex"); if (JS_IsException (val) || JS_ToLength (ctx, &last_index, val)) goto fail; 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 (ctx, sizeof (capture[0]) * capture_count * 2); if (!capture) 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 */ 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 again 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_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_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; { int cap_groups = (capture_count > 1) ? (capture_count - 1) : 0; captures_arr = JS_NewArrayLen (ctx, cap_groups); if (JS_IsException (captures_arr)) goto fail; } group_name_ptr = lre_get_groupnames (re_bytecode); if (group_name_ptr) { groups = JS_NewObjectProto (ctx, JS_NULL); if (JS_IsException (groups)) goto fail; } { 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)) goto fail; } if (i == 0) { match_start = start; match_end = end; match0 = s; continue; } if (name) { if (JS_SetPropertyStr (ctx, groups, name, s) < 0) { goto fail; } } if (JS_SetPropertyUint32 (ctx, captures_arr, (uint32_t)(i - 1), s) < 0) goto fail; } if (match_start < 0) match_start = 0; if (match_end < match_start) match_end = match_start; if (JS_SetPropertyStr (ctx, res, "index", JS_NewInt32 (ctx, match_start)) < 0) goto fail; if (JS_SetPropertyStr (ctx, res, "end", JS_NewInt32 (ctx, match_end)) < 0) goto fail; if (JS_SetPropertyStr (ctx, res, "match", match0) < 0) goto fail; match0 = JS_NULL; if (JS_SetPropertyStr (ctx, res, "captures", captures_arr) < 0) goto fail; captures_arr = JS_NULL; if (!JS_IsNull (groups)) { if (JS_SetPropertyStr (ctx, res, "groups", groups) < 0) goto fail; groups = JS_NULL; } else { JS_SetPropertyStr (ctx, res, "groups", JS_NULL); } } ret = res; res = JS_NULL; done: JS_PopGCRef (ctx, &str_ref); js_free (ctx, capture); js_free (ctx, utf16_buf); return ret; fail: JS_PopGCRef (ctx, &str_ref); js_free (ctx, capture); js_free (ctx, 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->rt, 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->rt, 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; } for (int64_t i = 0; i < len; i++) { if (i > 0 && separator[0]) { b = pretext_puts8 (ctx, b, separator); if (!b) goto array_fail; } 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, &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 = pretext_concat_value (ctx, b, item_str); if (!b) goto array_fail; } if (sep_alloc) JS_FreeCString (ctx, separator); JS_DeleteGCRef(ctx, &arg_ref); return pretext_end (ctx, b); array_fail: if (sep_alloc) JS_FreeCString (ctx, separator); 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; if (!target_is_regex) { if (!JS_VALUE_IS_TEXT (argv[1])) 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)) goto fail_str_target; JSValue rep = make_replacement (ctx, argc, argv, boundary, match); if (JS_IsException (rep)) goto fail_str_target; count++; if (!JS_IsNull (rep)) { b = pt_concat_value_to_string_free (ctx, b, rep); if (!b) goto fail_str_target; } else { } if (boundary < len) { JSValue ch = js_sub_string_val (ctx, argv[0], boundary, boundary + 1); if (JS_IsException (ch)) goto fail_str_target; b = pretext_concat_value (ctx, b, ch); if (!b) goto fail_str_target; } } 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) { /* For substring extraction, need to build manually if immediate string */ int sub_len = found - pos; JSText *sub_str = js_alloc_string (ctx, sub_len); if (!sub_str) 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)) goto fail_str_target; b = pretext_concat_value (ctx, b, sub); if (!b) goto fail_str_target; } /* Build match substring manually */ JSText *match_str = js_alloc_string (ctx, t_len); if (!match_str) 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)) goto fail_str_target; JSValue rep = make_replacement (ctx, argc, argv, found, match); if (JS_IsException (rep)) goto fail_str_target; count++; if (!JS_IsNull (rep)) { b = pt_concat_value_to_string_free (ctx, b, rep); if (!b) goto fail_str_target; } else { } pos = found + t_len; } if (pos < len) { int sub_len = len - pos; JSText *sub_str = js_alloc_string (ctx, sub_len); if (!sub_str) 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)) goto fail_str_target; b = pretext_concat_value (ctx, b, sub); if (!b) goto fail_str_target; } return pretext_end (ctx, b); fail_str_target: return JS_EXCEPTION; } /* Regex target */ JSValue rx = argv[1]; JSValue orig_last_index = JS_GetPropertyStr (ctx, rx, "lastIndex"); if (JS_IsException (orig_last_index)) 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, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) goto fail_rx; JSValue sub_str = js_sub_string_val (ctx, argv[0], pos, len); if (JS_IsException (sub_str)) goto fail_rx; JSValue exec_res = JS_Invoke (ctx, rx, JS_KEY_exec, 1, (JSValue *)&sub_str); if (JS_IsException (exec_res)) goto fail_rx; if (JS_IsNull (exec_res)) { break; } JSValue idx_val = JS_GetPropertyStr (ctx, exec_res, "index"); if (JS_IsException (idx_val)) { goto fail_rx; } int32_t local_index = 0; if (JS_ToInt32 (ctx, &local_index, idx_val)) { 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)) { goto fail_rx; } JSValue end_val = JS_GetPropertyStr (ctx, exec_res, "end"); if (JS_IsException (end_val)) { goto fail_rx; } int32_t end = 0; if (JS_ToInt32 (ctx, &end, end_val)) { 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)) goto fail_rx; b = pretext_concat_value (ctx, b, prefix); if (!b) goto fail_rx; } JSValue rep = make_replacement (ctx, argc, argv, found, match); if (JS_IsException (rep)) goto fail_rx; count++; if (!JS_IsNull (rep)) { b = pt_concat_value_to_string_free (ctx, b, rep); if (!b) goto fail_rx; } else { } 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)) goto fail_rx; b = pretext_concat_value (ctx, b, tail); if (!b) goto fail_rx; } if (have_orig_last_index) JS_SetPropertyStr (ctx, rx, "lastIndex", orig_last_index); return pretext_end (ctx, b); fail_rx: if (!JS_IsNull (orig_last_index) && !JS_IsException (orig_last_index)) { JS_SetPropertyStr (ctx, rx, "lastIndex", orig_last_index); } else { } return JS_EXCEPTION; } /* 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 */ JSValue rx = argv[1]; JSValue orig_last_index = JS_GetPropertyStr (ctx, rx, "lastIndex"); if (JS_IsException (orig_last_index)) { /* str removed - arg not owned */ return JS_EXCEPTION; } int have_orig_last_index = 1; if (JS_SetPropertyStr (ctx, rx, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) goto fail_rx_search; JSValue sub_str = js_sub_string_val (ctx, str, from, len); if (JS_IsException (sub_str)) goto fail_rx_search; JSValue exec_res = JS_Invoke (ctx, rx, 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, "lastIndex", orig_last_index); /* str removed - arg not owned */ 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, "lastIndex", orig_last_index); /* str removed - arg not owned */ 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, "lastIndex", orig_last_index); } else { } /* str removed - arg not owned */ return JS_EXCEPTION; } 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])) { JSValue rx = argv[1]; JSValue orig_last_index = JS_GetPropertyStr (ctx, rx, "lastIndex"); if (JS_IsException (orig_last_index)) return JS_EXCEPTION; if (JS_SetPropertyStr (ctx, rx, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) goto fail_rx; JSValue sub_str; if (from == 0 && to == len) { sub_str = str; } else { sub_str = js_sub_string_val (ctx, str, from, to); if (JS_IsException (sub_str)) goto fail_rx; } JSValue exec_res = JS_Invoke (ctx, rx, JS_KEY_exec, 1, (JSValue *)&sub_str); if (JS_IsException (exec_res)) goto fail_rx; if (JS_IsNull (exec_res)) { JS_SetPropertyStr (ctx, rx, "lastIndex", orig_last_index); return JS_NULL; } /* Build result array */ JSValue out = JS_NewArray (ctx); if (JS_IsException (out)) { goto fail_rx; } /* out[0] = exec_res.match */ JSValue match0 = JS_GetPropertyStr (ctx, exec_res, "match"); if (JS_IsException (match0)) { goto fail_rx; } if (JS_SetPropertyUint32 (ctx, out, 0, match0) < 0) { goto fail_rx; } /* Append capture groups from exec_res.captures (skip [0] if it mirrors * full match) */ JSValue caps = JS_GetPropertyStr (ctx, exec_res, "captures"); if (!JS_IsException (caps) && JS_IsArray (caps)) { int64_t caps_len = 0; if (js_get_length64 (ctx, &caps_len, caps) == 0 && caps_len > 0) { /* Many engines put full match at captures[0]. Your record already has match separately. We assume captures[0] corresponds to group1, group2, ... OR it includes full match. To satisfy your tests, we treat captures[0] as capture group 1. */ for (int64_t i = 0; i < caps_len; i++) { JSValue cap = JS_GetPropertyInt64 (ctx, caps, i); if (JS_IsException (cap)) { goto fail_rx; } if (JS_SetPropertyInt64 (ctx, out, i + 1, cap) < 0) { goto fail_rx; } } } } JS_SetPropertyStr (ctx, rx, "lastIndex", orig_last_index); return out; fail_rx: if (!JS_IsNull (orig_last_index) && !JS_IsException (orig_last_index)) { JS_SetPropertyStr (ctx, rx, "lastIndex", orig_last_index); } else { } return JS_EXCEPTION; } /* Literal text path */ JSValue needle_val = JS_ToString (ctx, argv[1]); if (JS_IsException (needle_val)) return JS_EXCEPTION; 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; JSValue arr = JS_NewArrayLen (ctx, 1); if (JS_IsException (arr)) return JS_EXCEPTION; JSValue match = js_sub_string_val (ctx, str, pos, pos + needle_len); if (JS_IsException (match)) return JS_EXCEPTION; 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); JSText *result = pretext_init (ctx, len); if (!result) return JS_EXCEPTION; int pos = 0; while (pos < len) { /* 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_val, pos, len); if (JS_IsException (tail)) return JS_EXCEPTION; result = pretext_concat_value (ctx, result, tail); break; } /* Copy text before brace */ if (brace_start > pos) { JSValue prefix = js_sub_string_val (ctx, text_val, pos, brace_start); if (JS_IsException (prefix)) return JS_EXCEPTION; result = pretext_concat_value (ctx, result, prefix); if (!result) return JS_EXCEPTION; } /* Find closing '}' */ 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_val, brace_start, brace_start + 1); if (JS_IsException (ch)) return JS_EXCEPTION; result = pretext_concat_value (ctx, result, ch); if (!result) return JS_EXCEPTION; pos = brace_start + 1; continue; } /* Extract content between braces */ JSValue middle = js_sub_string_val (ctx, text_val, brace_start + 1, brace_end); if (JS_IsException (middle)) 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 */ JSValue coll_value = JS_NULL; if (is_array) { /* Parse name as integer index — parse digits from the text directly since JS_ToInt32 doesn't handle text values */ 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) { coll_value = JS_GetPropertyUint32 (ctx, collection, (uint32_t)idx); } } else { /* Use name as key */ coll_value = JS_GetProperty (ctx, collection, name_val); } /* Try to get substitution */ JSValue substitution = JS_NULL; int made_substitution = 0; /* Try transformer first */ if (!JS_IsNull (transformer)) { if (JS_IsFunction (transformer)) { /* transformer(value, format_spec) */ JSValue args[2] = { coll_value, format_spec }; JSValue result_val = JS_Call (ctx, transformer, JS_NULL, 2, args); if (JS_IsText (result_val)) { substitution = result_val; made_substitution = 1; } } else if (JS_IsRecord (transformer)) { /* transformer[format_spec](value) */ JSValue func = JS_GetProperty (ctx, transformer, format_spec); if (JS_IsFunction (func)) { JSValue result_val = JS_Call (ctx, func, JS_NULL, 1, &coll_value); if (JS_IsText (result_val)) { substitution = result_val; made_substitution = 1; } } } } /* If no transformer match and value is number, try number.text(format) */ if (!made_substitution && JS_IsNumber (coll_value) && !JS_IsNull (format_spec)) { JSValue text_method = JS_GetPropertyStr (ctx, coll_value, "text"); if (JS_IsFunction (text_method)) { JSValue result_val = JS_Call (ctx, text_method, coll_value, 1, &format_spec); if (JS_IsText (result_val)) { substitution = result_val; made_substitution = 1; } } } /* If still no substitution but we have a value, convert to text */ if (!made_substitution && !JS_IsNull (coll_value)) { JSValue conv_text_val = JS_ToString (ctx, coll_value); if (JS_IsText (conv_text_val)) { substitution = conv_text_val; made_substitution = 1; } } if (made_substitution) { result = pretext_concat_value (ctx, result, substitution); if (!result) return JS_EXCEPTION; } else { /* No substitution, keep original {name} or {name:format} */ JSValue orig = js_sub_string_val (ctx, text_val, brace_start, brace_end + 1); if (JS_IsException (orig)) return JS_EXCEPTION; result = pretext_concat_value (ctx, result, orig); if (!result) return JS_EXCEPTION; } pos = brace_end + 1; } 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_GetObjectItem(fr, "function")); const char *file = cJSON_GetStringValue(cJSON_GetObjectItem(fr, "file")); int line = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(fr, "line")); int col = (int)cJSON_GetNumberValue(cJSON_GetObjectItem(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 */ JSValue func = argv[1]; int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func))->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, JS_NULL, 1, &idx_arg, 0); JS_POP_VALUE (ctx, result); if (JS_IsException (val)) 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, JS_NULL, 0, NULL, 0); JS_POP_VALUE (ctx, result); if (JS_IsException (val)) return JS_EXCEPTION; JSArray *out = JS_VALUE_GET_ARRAY (result); /* re-chase after call */ out->values[i] = val; } } } 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_IsObject (arg) && !JS_IsArray (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) */ JSValue rx = argv[1]; JSValue result = JS_NewArray (ctx); if (JS_IsException (result)) { return result; } /* Save & restore lastIndex to avoid mutating caller-visible state */ JSValue orig_last_index = JS_GetPropertyStr (ctx, rx, "lastIndex"); if (JS_IsException (orig_last_index)) { return JS_EXCEPTION; } int pos = 0; int64_t out_idx = 0; while (pos <= len) { if (JS_SetPropertyStr (ctx, rx, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) goto fail_rx_split; JSValue sub_str = js_sub_string_val (ctx, arg, pos, len); if (JS_IsException (sub_str)) goto fail_rx_split; JSValue exec_res = JS_Invoke (ctx, rx, 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, pos, len); if (JS_IsException (tail)) goto fail_rx_split; if (JS_ArrayPush (ctx, &result, tail) < 0) { goto fail_rx_split; } 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, pos, len); if (JS_IsException (tail)) goto fail_rx_split; 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, pos, found); if (JS_IsException (part)) goto fail_rx_split; if (JS_ArrayPush (ctx, &result, part) < 0) { goto fail_rx_split; } 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; if (JS_ArrayPush (ctx, &result, empty) < 0) { goto fail_rx_split; } break; } pos = found + 1; } } JS_SetPropertyStr (ctx, rx, "lastIndex", orig_last_index); return result; fail_rx_split: if (!JS_IsException (orig_last_index)) { JS_SetPropertyStr (ctx, rx, "lastIndex", orig_last_index); } return JS_EXCEPTION; } 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] for the duration of this function */ JSGCRef arr_ref; JS_PushGCRef (ctx, &arr_ref); arr_ref.val = argv[0]; JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); JSValue func = argv[1]; 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, &arr_ref); return JS_NULL; } if (len == 1) { 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, JS_NULL, 2, args, 0); JS_POP_VALUE (ctx, acc); if (JS_IsException (new_acc)) { 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, JS_NULL, 2, args, 0); JS_POP_VALUE (ctx, acc); if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } acc = new_acc; } } } else { if (len == 0) { 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, JS_NULL, 2, args, 0); JS_POP_VALUE (ctx, acc); if (JS_IsException (new_acc)) { 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, JS_NULL, 2, args, 0); JS_POP_VALUE (ctx, acc); if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } acc = new_acc; } } } 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] */ JSGCRef arr_ref; JS_PushGCRef (ctx, &arr_ref); arr_ref.val = argv[0]; JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); JSValue func = argv[1]; word_t len = arr->len; if (len == 0) { 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 (argv[1]))->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, 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, JS_NULL, 2, args, 0); } if (JS_IsException (result)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } if (!JS_IsNull (exit_val) && js_strict_eq (ctx, result, exit_val)) { 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, 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, JS_NULL, 2, args, 0); } if (JS_IsException (result)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } if (!JS_IsNull (exit_val) && js_strict_eq (ctx, result, exit_val)) { JS_PopGCRef (ctx, &arr_ref); return result; } } } 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 */ JSValue func = argv[1]; int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (argv[1]))->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, JS_NULL, 2, args, 0); if (JS_IsException (result)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } if (JS_ToBool (ctx, result)) { 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, JS_NULL, 2, args, 0); if (JS_IsException (result)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } if (JS_ToBool (ctx, result)) { 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, JS_NULL, 1, &item, 0); if (JS_IsException (result)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } if (JS_ToBool (ctx, result)) { 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, JS_NULL, 1, &item, 0); if (JS_IsException (result)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } if (JS_ToBool (ctx, result)) { JS_PopGCRef (ctx, &arr_ref); return JS_NewInt32 (ctx, (int32_t)i); } } } } 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; } /* 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); } } 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]); 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, &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, &arr_ref); return JS_NULL; } } /* 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); 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]); } 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 */ JSValue result = JS_NewObject (ctx); if (JS_IsException (result)) return result; JSValue keys = JS_GetOwnPropertyNames (ctx, arg); if (JS_IsException (keys)) { return JS_EXCEPTION; } uint32_t len; if (js_get_length32 (ctx, &len, keys)) { return JS_EXCEPTION; } for (uint32_t i = 0; i < len; i++) { JSValue key = JS_GetPropertyUint32 (ctx, keys, i); JSValue val = JS_GetProperty (ctx, arg, key); if (JS_IsException (val)) { return JS_EXCEPTION; } JS_SetProperty (ctx, result, key, val); } return result; } /* object(object, another_object) - combine */ if (JS_IsObject (argv[1]) && !JS_IsArray (argv[1])) { JSValue result = JS_NewObject (ctx); if (JS_IsException (result)) return result; /* Copy from first object */ JSValue keys = JS_GetOwnPropertyNames (ctx, arg); if (JS_IsException (keys)) { return JS_EXCEPTION; } uint32_t len; if (js_get_length32 (ctx, &len, keys)) { return JS_EXCEPTION; } for (uint32_t i = 0; i < len; i++) { JSValue key = JS_GetPropertyUint32 (ctx, keys, i); JSValue val = JS_GetProperty (ctx, arg, key); if (JS_IsException (val)) { return JS_EXCEPTION; } JS_SetProperty (ctx, result, key, val); } /* Copy from second object */ keys = JS_GetOwnPropertyNames (ctx, argv[1]); if (JS_IsException (keys)) { return JS_EXCEPTION; } if (js_get_length32 (ctx, &len, keys)) { return JS_EXCEPTION; } for (uint32_t i = 0; i < len; i++) { JSValue key = JS_GetPropertyUint32 (ctx, keys, i); JSValue val = JS_GetProperty (ctx, argv[1], key); if (JS_IsException (val)) { return JS_EXCEPTION; } JS_SetProperty (ctx, result, key, val); } return result; } /* object(object, array_of_keys) - select */ if (JS_IsArray (argv[1])) { JSValue result = JS_NewObject (ctx); if (JS_IsException (result)) return result; JSArray *keys = JS_VALUE_GET_ARRAY (argv[1]); int len = keys->len; for (int i = 0; i < len; i++) { keys = JS_VALUE_GET_ARRAY (argv[1]); /* re-chase each iteration */ if (i >= (int)keys->len) break; JSValue key = keys->values[i]; int key_tag = JS_VALUE_GET_TAG (key); if (key_tag == JS_TAG_STRING || key_tag == JS_TAG_STRING_IMM) { /* Use JSValue key directly - create interned key */ JSValue prop_key = js_key_from_string (ctx, key); int has = JS_HasProperty (ctx, arg, prop_key); if (has > 0) { JSValue val = JS_GetProperty (ctx, arg, prop_key); if (!JS_IsException (val)) { JS_SetProperty (ctx, result, prop_key, val); } } /* prop_key is interned, no need to free */ } } return result; } } /* 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; JSValue result = JS_NewObject (ctx); if (JS_IsException (result)) return result; JSGCRef result_ref; int is_func = argc >= 2 && JS_IsFunction (argv[1]); for (int i = 0; i < len; i++) { keys = JS_VALUE_GET_ARRAY (argv[0]); /* re-chase each iteration via argv */ if (i >= (int)keys->len) break; JSValue key = keys->values[i]; int key_tag = JS_VALUE_GET_TAG (key); if (key_tag == JS_TAG_STRING || key_tag == JS_TAG_STRING_IMM) { /* Use JSValue key directly - create interned key */ JSValue prop_key = js_key_from_string (ctx, key); JSValue val; if (argc < 2 || JS_IsNull (argv[1])) { val = JS_TRUE; } else if (is_func) { JSValue arg_key = key; JS_PUSH_VALUE (ctx, result); val = JS_CallInternal (ctx, argv[1], JS_NULL, 1, &arg_key, 0); JS_POP_VALUE (ctx, result); if (JS_IsException (val)) { return JS_EXCEPTION; } } else { val = argv[1]; } JS_SetProperty (ctx, result, prop_key, val); /* prop_key is interned, no need to free */ } } 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; } /* ============================================================================ * 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]; /* Arrays cannot have prototypes */ if (JS_IsArray (obj)) { JS_ThrowTypeError (ctx, "cannot get prototype of array"); return JS_EXCEPTION; } 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; /* Helper function to apply a single mixin */ #define APPLY_MIXIN(mix) \ do { \ if (!JS_IsObject (mix) || JS_IsNull (mix) || JS_IsArray (mix)) \ break; \ JSValue _keys = JS_GetOwnPropertyNames (ctx, mix); \ if (JS_IsException (_keys)) { \ ; \ return JS_EXCEPTION; \ } \ uint32_t _len; \ if (js_get_length32 (ctx, &_len, _keys)) { \ ; \ ; \ return JS_EXCEPTION; \ } \ for (uint32_t j = 0; j < _len; j++) { \ JSValue _key = JS_GetPropertyUint32 (ctx, _keys, j); \ JSValue val = JS_GetProperty (ctx, mix, _key); \ if (JS_IsException (val)) { \ ; \ ; \ ; \ return JS_EXCEPTION; \ } \ JS_SetProperty (ctx, result, _key, val); \ } \ ; \ } 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 */ int64_t len; if (js_get_length64 (ctx, &len, mixins)) { return JS_EXCEPTION; } for (int64_t j = 0; j < len; j++) { JSValue mix = JS_GetPropertyInt64 (ctx, mixins, j); if (JS_IsException (mix)) { return JS_EXCEPTION; } APPLY_MIXIN (mix); } } else if (JS_IsObject (mixins) && !JS_IsNull (mixins)) { /* Single mixin object */ APPLY_MIXIN (mixins); } } #undef APPLY_MIXIN 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; JSValue result = JS_NewObject (ctx); if (JS_IsException (result)) return JS_EXCEPTION; JSValue current = obj; /* Walk prototype chain and collect text keys */ while (!JS_IsNull (current)) { JSValue keys = JS_GetOwnPropertyNames (ctx, current); if (JS_IsException (keys)) { return JS_EXCEPTION; } uint32_t len; if (js_get_length32 (ctx, &len, keys)) { return JS_EXCEPTION; } for (uint32_t i = 0; i < len; i++) { JSValue key = JS_GetPropertyUint32 (ctx, keys, i); /* Check if property not already in result */ int has = JS_HasProperty (ctx, result, key); if (has < 0) { return JS_EXCEPTION; } if (!has) { JSValue val = JS_GetProperty (ctx, current, key); if (JS_IsException (val)) { return JS_EXCEPTION; } /* Only include serializable types */ 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, result, key, val); } else { } } else { } } JSValue next = JS_GetPrototype (ctx, current); current = next; } /* Call to_data if present */ JSValue to_data = JS_GetPropertyStr (ctx, obj, "to_data"); if (JS_IsFunction (to_data)) { JSValue args[1] = { result }; JSValue extra = JS_Call (ctx, to_data, obj, 1, args); if (!JS_IsException (extra) && JS_IsObject (extra)) { JSValue keys2 = JS_GetOwnPropertyNames (ctx, extra); if (!JS_IsException (keys2)) { uint32_t len; if (!js_get_length32 (ctx, &len, keys2)) { for (uint32_t i = 0; i < len; i++) { JSValue key = JS_GetPropertyUint32 (ctx, keys2, i); JSValue val = JS_GetProperty (ctx, extra, key); JS_SetProperty (ctx, result, key, val); } } } } } return result; } /* ============================================================================ * 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 (JS_GetRuntime (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, "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->rt->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->rt->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->rt->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->rt->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->rt->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 in_disruption; char *decoded_str; /* allocated buffer for decoded string escapes */ 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 *tmp = sys_malloc (len + 1); memcpy (tmp, str, len); tmp[len] = '\0'; cJSON_AddStringToObject (obj, key, tmp); 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) { int line_num = 0, col_num = 0; const uint8_t *p = s->buf_start; while (p < ptr) { if (*p == '\n') { line_num++; col_num = 0; } else if (*p < 0x80 || *p >= 0xc0) { col_num++; } p++; } *line = line_num; *col = col_num; } 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, ...) { 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 { 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 == '$') { 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] == '=') { 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] == '=') { 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 { goto def_token; } break; case '!': if (p[1] == '=') { 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] == '=') { 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 { goto def_token; } break; case '|': if (p[1] == '|') { 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 { 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] == '?') { if (p[2] == '=') { p += 3; s->token_val = TOK_DOUBLE_QUESTION_MARK_ASSIGN; } else { p += 2; s->token_val = TOK_DOUBLE_QUESTION_MARK; } } else if (p[1] == '.') { p += 2; s->token_val = TOK_QUESTION_MARK_DOT; } else { goto def_token; } break; 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 { 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 == '$') { 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] == '=') { 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] == '=') { 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 { goto def_token; } break; case '!': if (p[1] == '=') { 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] == '=') { 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 { goto def_token; } break; case '|': if (p[1] == '|') { 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 { 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] == '?') { if (p[2] == '=') { p += 3; s->token_val = TOK_DOUBLE_QUESTION_MARK_ASSIGN; } else { p += 2; s->token_val = TOK_DOUBLE_QUESTION_MARK; } } else if (p[1] == '.') { p += 2; s->token_val = TOK_QUESTION_MARK_DOT; } else { goto def_token; } break; 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); if (s->token_val == TOK_IDENT || s->token_val == TOK_STRING || s->token_val == TOK_NUMBER) { cJSON *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_GetObjectItem (pair, "left"); cJSON *name_item = cJSON_GetObjectItem (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 token '%c'", 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"); } 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) { 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 if (s->token_val == TOK_QUESTION_MARK_DOT) { ast_next_token (s); if (s->token_val == '[') { /* Optional bracket access: o?.["a"] */ ast_next_token (s); cJSON *new_node = ast_node (s, "?.[", start); cJSON_AddItemToObject (new_node, "left", node); 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 == '(') { /* Optional call: o.f?.() */ 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_IDENT) { /* Optional property access: o?.a */ cJSON *new_node = ast_node (s, "?.", start); cJSON_AddItemToObject (new_node, "left", node); cjson_add_strn (new_node, "right", s->token_u.ident.str, s->token_u.ident.len); 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 }, { TOK_DOUBLE_QUESTION_MARK, "??", 3 }, { 0, NULL, 0 } }; static int ast_get_binop_prec (int token) { for (int i = 0; ast_binops[i].kind; i++) { if (ast_binops[i].token == token) return ast_binops[i].prec; } return 0; } static const char *ast_get_binop_kind (int token) { for (int i = 0; ast_binops[i].kind; i++) { if (ast_binops[i].token == token) return ast_binops[i].kind; } 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; int prec = ast_get_binop_prec (s->token_val); if (prec == 0 || prec < min_prec) break; const char *kind = ast_get_binop_kind (s->token_val); ast_next_token (s); /* Right associativity for ** */ int next_prec = (prec == 14) ? prec : prec + 1; cJSON *right = ast_parse_binary (s, next_prec); cJSON *node = ast_node (s, 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; case TOK_DOUBLE_QUESTION_MARK_ASSIGN: kind = "??="; break; default: return left; } /* Validate assignment target */ { const char *left_kind = cJSON_GetStringValue (cJSON_GetObjectItem (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_GetObjectItem (left, "kind")); if (left_kind && strcmp (left_kind, "[") == 0 && !cJSON_GetObjectItem (left, "right")) cJSON_AddBoolToObject (node, "push", 1); const char *right_kind = cJSON_GetStringValue (cJSON_GetObjectItem (right, "kind")); if (right_kind && strcmp (right_kind, "[") == 0 && !cJSON_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (right, "kind")); if (right_kind && strcmp (right_kind, "[") == 0 && !cJSON_GetObjectItem (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"); 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_GetObjectItem (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_GetObjectItem (node, "from_row"); cJSON *col_obj = cJSON_GetObjectItem (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 void ast_sem_check_expr (ASTSemState *st, ASTSemScope *scope, cJSON *expr); static void ast_sem_check_stmt (ASTSemState *st, ASTSemScope *scope, cJSON *stmt); /* Pre-register function declarations in a statement list (hoisting). This allows mutual recursion: function A can reference function B declared later. */ static void ast_sem_predeclare_vars (ASTSemScope *scope, cJSON *stmts) { cJSON *stmt; cJSON_ArrayForEach (stmt, stmts) { const char *kind = cJSON_GetStringValue (cJSON_GetObjectItem (stmt, "kind")); if (!kind) continue; if (strcmp (kind, "function") == 0) { const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (stmt, "name")); if (name && !ast_sem_find_var (scope, name)) ast_sem_add_var (scope, name, 0, "function", 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_GetObjectItem (left, "kind")); if (!kind) return; if (strcmp (kind, "name") == 0) { const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (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_GetObjectItem (left, "expression"); if (!obj_expr) obj_expr = cJSON_GetObjectItem (left, "left"); ast_sem_check_expr (st, scope, obj_expr); /* Also resolve the index expression for computed access */ cJSON *idx_expr = cJSON_GetObjectItem (left, "index"); if (!idx_expr && strcmp (kind, "[") == 0) idx_expr = cJSON_GetObjectItem (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_GetObjectItem (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_GetObjectItem (expr, "left")); ast_sem_check_expr (st, scope, cJSON_GetObjectItem (expr, "right")); return; } /* Increment/decrement */ if (strcmp (kind, "++") == 0 || strcmp (kind, "--") == 0) { cJSON *operand = cJSON_GetObjectItem (expr, "expression"); if (operand) { const char *op_kind = cJSON_GetStringValue (cJSON_GetObjectItem (operand, "kind")); if (op_kind && strcmp (op_kind, "name") == 0) { const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (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_GetObjectItem (expr, "left")); ast_sem_check_expr (st, scope, cJSON_GetObjectItem (expr, "right")); return; } /* Ternary */ if (strcmp (kind, "then") == 0) { ast_sem_check_expr (st, scope, cJSON_GetObjectItem (expr, "expression")); ast_sem_check_expr (st, scope, cJSON_GetObjectItem (expr, "then")); ast_sem_check_expr (st, scope, cJSON_GetObjectItem (expr, "else")); return; } /* Call and optional call */ if (strcmp (kind, "(") == 0 || strcmp (kind, "?.(") == 0) { ast_sem_check_expr (st, scope, cJSON_GetObjectItem (expr, "expression")); cJSON *arg; cJSON_ArrayForEach (arg, cJSON_GetObjectItem (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_GetObjectItem (expr, "expression")); return; } /* Array literal */ if (strcmp (kind, "array") == 0) { cJSON *el; cJSON_ArrayForEach (el, cJSON_GetObjectItem (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_GetObjectItem (expr, "list")) { cJSON *val = cJSON_GetObjectItem (prop, "value"); if (!val) val = cJSON_GetObjectItem (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_GetObjectItem (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_GetObjectItem (expr, "list")) { const char *pname = cJSON_GetStringValue (cJSON_GetObjectItem (param, "name")); if (pname) ast_sem_add_var (&fn_scope, pname, 1, "input", fn_nr); /* Check default value expressions */ cJSON *def_val = cJSON_GetObjectItem (param, "default"); 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 (&fn_scope, cJSON_GetObjectItem (expr, "statements")); /* Check function body */ cJSON *stmt; cJSON_ArrayForEach (stmt, cJSON_GetObjectItem (expr, "statements")) { ast_sem_check_stmt (st, &fn_scope, stmt); } /* Check disruption clause */ cJSON *disruption = cJSON_GetObjectItem (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_GetObjectItem (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_GetObjectItem (expr, "name")); if (name) { 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_GetObjectItem (stmt, "kind")); if (!kind) return; if (strcmp (kind, "var_list") == 0) { cJSON *item; cJSON_ArrayForEach (item, cJSON_GetObjectItem (stmt, "list")) { ast_sem_check_stmt (st, scope, item); } return; } if (strcmp (kind, "var") == 0) { /* Register variable */ cJSON *left = cJSON_GetObjectItem (stmt, "left"); const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (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); } 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_GetObjectItem (stmt, "right")); return; } if (strcmp (kind, "def") == 0) { /* Register constant */ cJSON *left = cJSON_GetObjectItem (stmt, "left"); const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (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) { ast_sem_error (st, left, "cannot redeclare '%s' as constant", name); } 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_GetObjectItem (stmt, "right")); return; } if (strcmp (kind, "call") == 0) { ast_sem_check_expr (st, scope, cJSON_GetObjectItem (stmt, "expression")); return; } if (strcmp (kind, "if") == 0) { ast_sem_check_expr (st, scope, cJSON_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (stmt, "init"); if (init) { const char *init_kind = cJSON_GetStringValue (cJSON_GetObjectItem (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_GetObjectItem (stmt, "test")); ast_sem_check_expr (st, &loop_scope, cJSON_GetObjectItem (stmt, "update")); cJSON *s2; cJSON_ArrayForEach (s2, cJSON_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (stmt, "statement")); return; } if (strcmp (kind, "function") == 0) { /* Function declaration — register name, then check body in new scope */ const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (stmt, "name")); if (name) ast_sem_add_var (scope, name, 0, "function", scope->function_nr); cJSON *fn_nr_node = cJSON_GetObjectItem (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_GetObjectItem (stmt, "list")) { const char *pname = cJSON_GetStringValue (cJSON_GetObjectItem (param, "name")); if (pname) ast_sem_add_var (&fn_scope, pname, 1, "input", fn_nr); /* Check default value expressions */ cJSON *def_val = cJSON_GetObjectItem (param, "default"); 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 (&fn_scope, cJSON_GetObjectItem (stmt, "statements")); cJSON *s2; cJSON_ArrayForEach (s2, cJSON_GetObjectItem (stmt, "statements")) { ast_sem_check_stmt (st, &fn_scope, s2); } /* Check disruption clause */ cJSON *disruption = cJSON_GetObjectItem (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_GetObjectItem (ast, "functions")) { const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (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_GetObjectItem (ast, "statements")) { ast_sem_check_stmt (&st, &global_scope, stmt); } /* Check function bodies */ cJSON_ArrayForEach (stmt, cJSON_GetObjectItem (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; } char *JS_AST (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; /* 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 */ 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); /* Merge parse errors and semantic errors */ if (s.errors && sem_errors) { /* Append semantic errors to parse errors */ cJSON *err; cJSON *next; for (err = sem_errors->child; err; err = next) { next = err->next; cJSON_DetachItemViaPointer (sem_errors, err); cJSON_AddItemToArray (s.errors, err); } cJSON_Delete (sem_errors); cJSON_AddItemToObject (ast, "errors", s.errors); } else if (s.errors) { cJSON_AddItemToObject (ast, "errors", s.errors); } else if (sem_errors) { cJSON_AddItemToObject (ast, "errors", sem_errors); } /* Convert to JSON string */ char *json = cJSON_Print (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; 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_Print (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_GetObjectItem(node, "from_row"); cJSON *c = cJSON_GetObjectItem(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_GetObjectItem(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_GetObjectItem(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_GetObjectItem(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_GetObjectItem(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_GetObjectItem(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_GetObjectItem(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_GetObjectItem(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_GetObjectItem(node, "name")); if (name) { cJSON *level_node = cJSON_GetObjectItem(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_GetObjectItem(node, "expression"); cJSON *args = cJSON_GetObjectItem(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_GetObjectItem(fn_expr, "kind")); 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_GetObjectItem(fn_expr, "expression"); if (!obj_expr) obj_expr = cJSON_GetObjectItem(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_GetObjectItem(fn_expr, "name"); if (!prop) prop = cJSON_GetObjectItem(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_GetObjectItem(prop, "value")); if (!prop_name && prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItem(prop, "name")); if (!prop_name) prop_name = cJSON_GetStringValue(cJSON_GetObjectItem(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; } /* 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_GetObjectItem(node, "left"); cJSON *right = cJSON_GetObjectItem(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_GetObjectItem(node, "left"); cJSON *right = cJSON_GetObjectItem(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_GetObjectItem(node, "expression"); if (!operand) operand = cJSON_GetObjectItem(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_GetObjectItem(node, "expression"); if (!operand) operand = cJSON_GetObjectItem(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_GetObjectItem(node, "left"))) { if (dest < 0) dest = mach_reserve_reg(cs); cJSON *operand = cJSON_GetObjectItem(node, "expression"); if (!operand) operand = cJSON_GetObjectItem(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_GetObjectItem(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_GetObjectItem(node, "left"); cJSON *right = cJSON_GetObjectItem(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_GetObjectItem(node, "expression"); cJSON *postfix_node = cJSON_GetObjectItem(node, "postfix"); int is_postfix = postfix_node && cJSON_IsTrue(postfix_node); const char *op_kind = cJSON_GetStringValue(cJSON_GetObjectItem(operand, "kind")); if (op_kind && strcmp(op_kind, "name") == 0) { const char *name = cJSON_GetStringValue(cJSON_GetObjectItem(operand, "name")); cJSON *level_node = cJSON_GetObjectItem(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_GetObjectItem(node, "left"); cJSON *right = cJSON_GetObjectItem(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_GetObjectItem(left, "kind")); if (lk && strcmp(lk, "name") == 0) { const char *name = cJSON_GetStringValue(cJSON_GetObjectItem(left, "name")); cJSON *level_node = cJSON_GetObjectItem(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_GetObjectItem(node, "left"); cJSON *right = cJSON_GetObjectItem(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_GetObjectItem(node, "left"); cJSON *right = cJSON_GetObjectItem(node, "right"); /* Push: arr[] = val */ cJSON *push_node = cJSON_GetObjectItem(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_GetObjectItem(left, "left"); if (!arr_expr) arr_expr = cJSON_GetObjectItem(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_GetObjectItem(left, "kind")); if (lk && strcmp(lk, "name") == 0) { const char *name = cJSON_GetStringValue(cJSON_GetObjectItem(left, "name")); cJSON *level_node = cJSON_GetObjectItem(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_GetObjectItem(left, "expression"); if (!obj_expr) obj_expr = cJSON_GetObjectItem(left, "left"); cJSON *prop = cJSON_GetObjectItem(left, "name"); if (!prop) prop = cJSON_GetObjectItem(left, "right"); const char *prop_name = NULL; if (cJSON_IsString(prop)) prop_name = cJSON_GetStringValue(prop); else if (prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItem(prop, "value")); if (!prop_name && prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItem(prop, "name")); if (!prop_name) prop_name = cJSON_GetStringValue(cJSON_GetObjectItem(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_GetObjectItem(left, "expression"); if (!obj_expr) obj_expr = cJSON_GetObjectItem(left, "left"); cJSON *idx_expr = cJSON_GetObjectItem(left, "index"); if (!idx_expr) idx_expr = cJSON_GetObjectItem(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_GetObjectItem(node, "expression"); if (!obj_expr) obj_expr = cJSON_GetObjectItem(node, "left"); cJSON *prop = cJSON_GetObjectItem(node, "name"); if (!prop) prop = cJSON_GetObjectItem(node, "right"); const char *prop_name = NULL; if (cJSON_IsString(prop)) prop_name = cJSON_GetStringValue(prop); else if (prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItem(prop, "value")); if (!prop_name && prop) prop_name = cJSON_GetStringValue(cJSON_GetObjectItem(prop, "name")); if (!prop_name) prop_name = cJSON_GetStringValue(cJSON_GetObjectItem(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_GetObjectItem(node, "expression"); if (!obj_expr) obj_expr = cJSON_GetObjectItem(node, "left"); cJSON *idx_expr = cJSON_GetObjectItem(node, "index"); if (!idx_expr) idx_expr = cJSON_GetObjectItem(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_GetObjectItem(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_GetObjectItem(prop, "key"); if (!key_node) key_node = cJSON_GetObjectItem(prop, "left"); cJSON *val_node = cJSON_GetObjectItem(prop, "value"); if (!val_node) val_node = cJSON_GetObjectItem(prop, "right"); if (!val_node) val_node = cJSON_GetObjectItem(prop, "expression"); const char *key = cJSON_GetStringValue(key_node); if (!key && key_node) key = cJSON_GetStringValue(cJSON_GetObjectItem(key_node, "value")); if (!key && key_node) key = cJSON_GetStringValue(cJSON_GetObjectItem(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_GetObjectItem(node, "list"); int count = elems ? cJSON_GetArraySize(elems) : 0; /* Reserve consecutive regs for elements starting at dest+1 */ int save = cs->freereg; cs->freereg = dest + 1; 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, dest, count, 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_GetObjectItem(node, "expression"); if (!cond) cond = cJSON_GetObjectItem(node, "condition"); cJSON *then_expr = cJSON_GetObjectItem(node, "then"); if (!then_expr) then_expr = cJSON_GetObjectItem(node, "left"); cJSON *else_expr = cJSON_GetObjectItem(node, "else"); if (!else_expr) else_expr = cJSON_GetObjectItem(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_GetObjectItem(node, "function_nr"); child.function_nr = fn_nr_node ? (int)cJSON_GetNumberValue(fn_nr_node) : 0; /* Register parameters */ cJSON *params = cJSON_GetObjectItem(node, "params"); if (!params) params = cJSON_GetObjectItem(node, "parameters"); if (!params) params = cJSON_GetObjectItem(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_GetObjectItem(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); /* Compile body */ cJSON *body = cJSON_GetObjectItem(node, "body"); if (!body) body = node; /* statements may be directly on the function node */ { cJSON *stmts = cJSON_GetObjectItem(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_GetObjectItem(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_GetObjectItem(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_GetObjectItem(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_GetObjectItem(node, "expression"); if (!operand) operand = cJSON_GetObjectItem(node, "right"); if (operand) { const char *okind = cJSON_GetStringValue(cJSON_GetObjectItem(operand, "kind")); if (okind && strcmp(okind, ".") == 0) { /* delete obj.prop */ cJSON *obj_node = cJSON_GetObjectItem(operand, "left"); cJSON *prop_node = cJSON_GetObjectItem(operand, "right"); const char *pname = cJSON_GetStringValue(cJSON_GetObjectItem(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_GetObjectItem(operand, "left"); cJSON *idx_node = cJSON_GetObjectItem(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_GetObjectItem(node, "pattern")); const char *flags = cJSON_GetStringValue(cJSON_GetObjectItem(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_GetObjectItem(stmt, "kind")); if (!kind) return; /* var / def declaration */ if (strcmp(kind, "var") == 0 || strcmp(kind, "def") == 0) { cJSON *left = cJSON_GetObjectItem(stmt, "left"); cJSON *right = cJSON_GetObjectItem(stmt, "right"); const char *name = cJSON_GetStringValue(cJSON_GetObjectItem(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_GetObjectItem(stmt, "pop"); if (pop_node && cJSON_IsTrue(pop_node) && right) { cJSON *arr_expr = cJSON_GetObjectItem(right, "left"); if (!arr_expr) arr_expr = cJSON_GetObjectItem(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_GetObjectItem(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_GetObjectItem(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_GetObjectItem(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_GetObjectItem(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_GetObjectItem(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_GetObjectItem(stmt, "expression"); if (!cond) cond = cJSON_GetObjectItem(stmt, "condition"); cJSON *then_body = cJSON_GetObjectItem(stmt, "then"); if (!then_body) then_body = cJSON_GetObjectItem(stmt, "block"); cJSON *else_body = cJSON_GetObjectItem(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_GetObjectItem(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_GetObjectItem(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_GetObjectItem(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_GetObjectItem(stmt, "expression"); if (!cond) cond = cJSON_GetObjectItem(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_GetObjectItem(stmt, "block"); if (!body) body = cJSON_GetObjectItem(stmt, "body"); cJSON *stmts = body ? cJSON_GetObjectItem(body, "statements") : NULL; if (!stmts) stmts = cJSON_GetObjectItem(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_GetObjectItem(stmt, "init"); cJSON *cond = cJSON_GetObjectItem(stmt, "test"); cJSON *update = cJSON_GetObjectItem(stmt, "update"); cJSON *body = cJSON_GetObjectItem(stmt, "block"); if (!body) body = cJSON_GetObjectItem(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_GetObjectItem(body, "statements") : NULL; if (!stmts) stmts = cJSON_GetObjectItem(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_GetObjectItem(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_GetObjectItem(ast, "statements"); if (!stmts || !cJSON_IsArray(stmts)) return NULL; /* Read scopes array from AST */ cs->scopes = cJSON_GetObjectItem(ast, "scopes"); cs->function_nr = 0; /* Extract filename for debug info */ cs->filename = cJSON_GetStringValue(cJSON_GetObjectItem(ast, "filename")); /* Scan scope record for declarations */ mach_scan_scope(cs); /* Hoist function declarations */ cJSON *functions = cJSON_GetObjectItem(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_GetObjectItem(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_GetObjectItem(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_CompileMach(const char *ast_json) { cJSON *ast = cJSON_Parse(ast_json); 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); 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: bitwise-identical values (same object/pointer) */ if (a == b) { 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) { JSRuntime *rt = ctx->rt; if (--ctx->interrupt_counter <= 0) { ctx->interrupt_counter = JS_INTERRUPT_COUNTER_INIT; if (rt->interrupt_handler) { if (rt->interrupt_handler(rt, rt->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->rt->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; /* Allocate initial frame */ JSFrameRegister *frame = alloc_frame_register(ctx, code->nr_slots); if (!frame) { 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] = this_obj; /* slot 0 = this */ /* Copy arguments */ for (int i = 0; i < argc && i < code->arity; i++) { frame->slots[1 + i] = argv[i]; } 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->rt->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_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->rt->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). 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; JSValue obj = frame->slots[base]; JSValue key = code->cpool[c]; if (JS_IsFunction(obj)) { /* Proxy call: obj(name, [args...]) */ JSValue arr = JS_NewArray(ctx); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (JS_IsException(arr)) 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; 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->rt->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)) goto disrupt; frame->slots[base] = ret; } else { /* Record method call: get property, call with this=obj */ JSValue method = JS_GetProperty(ctx, frame->slots[base], key); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (JS_IsException(method)) goto disrupt; if (!JS_IsFunction(method)) { frame->slots[base] = JS_NULL; 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->rt->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)) 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); goto disrupt; } frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); method = JS_GetProperty(ctx, frame->slots[base], key); 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)) goto disrupt; frame->slots[base] = ret; } } 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. */ { 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->rt->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_GetObjectItem (node, "from_row"); cJSON *col_obj = cJSON_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (node, "from_row"); cJSON *c = cJSON_GetObjectItem (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_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_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 *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_GetObjectItem (node, "kind")); cJSON *left = cJSON_GetObjectItem (node, "left"); cJSON *right = cJSON_GetObjectItem (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_GetObjectItem (node, "left"); cJSON *right = cJSON_GetObjectItem (node, "right"); const char *left_kind = cJSON_GetStringValue (cJSON_GetObjectItem (left, "kind")); if (strcmp (left_kind, "name") == 0) { const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (left, "name")); const char *sn = cJSON_GetStringValue (cJSON_GetObjectItem (left, "scope_name")); const char *ln = sn ? sn : name; cJSON *level_node = cJSON_GetObjectItem (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_GetObjectItem (left, "left"); const char *prop = cJSON_GetStringValue (cJSON_GetObjectItem (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_GetObjectItem (left, "left"); cJSON *idx_expr = cJSON_GetObjectItem (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_GetObjectItem (node, "kind")); cJSON *left = cJSON_GetObjectItem (node, "left"); cJSON *right = cJSON_GetObjectItem (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_GetObjectItem (node, "push"); if (push_flag && cJSON_IsTrue (push_flag)) { cJSON *arr_expr = cJSON_GetObjectItem (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_GetObjectItem (left, "kind")); if (strcmp (left_kind, "name") == 0) { const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (left, "name")); const char *sn = cJSON_GetStringValue (cJSON_GetObjectItem (left, "scope_name")); const char *ln = sn ? sn : name; cJSON *level_node = cJSON_GetObjectItem (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_GetObjectItem (left, "left"); const char *prop = cJSON_GetStringValue (cJSON_GetObjectItem (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_GetObjectItem (left, "left"); cJSON *idx_expr = cJSON_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (expr, "pattern")); const char *flags = cJSON_GetStringValue (cJSON_GetObjectItem (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_GetObjectItem (expr, "name")); const char *scope_name = cJSON_GetStringValue (cJSON_GetObjectItem (expr, "scope_name")); const char *lookup_name = scope_name ? scope_name : name; cJSON *level_node = cJSON_GetObjectItem (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_GetObjectItem (expr, "left"); const char *prop = cJSON_GetStringValue (cJSON_GetObjectItem (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_GetObjectItem (expr, "left"); cJSON *idx = cJSON_GetObjectItem (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_GetObjectItem (expr, "expression"); cJSON *args_list = cJSON_GetObjectItem (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_GetObjectItem (callee, "kind")); int dest = mach_gen_alloc_slot (s); if (strcmp (callee_kind, ".") == 0) { cJSON *obj = cJSON_GetObjectItem (callee, "left"); const char *prop = cJSON_GetStringValue (cJSON_GetObjectItem (callee, "right")); int obj_slot = mach_gen_expr (s, obj, -1); mach_gen_emit_call_method (s, dest, obj_slot, prop, 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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (expr, "expression"); return mach_gen_expr (s, operand, -1); } /* Increment/Decrement */ if (strcmp (kind, "++") == 0 || strcmp (kind, "--") == 0) { cJSON *operand = cJSON_GetObjectItem (expr, "expression"); cJSON *is_postfix = cJSON_GetObjectItem (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_GetObjectItem (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_GetObjectItem (operand, "name")); const char *inc_sn = cJSON_GetStringValue (cJSON_GetObjectItem (operand, "scope_name")); const char *inc_ln = inc_sn ? inc_sn : name; cJSON *level_node = cJSON_GetObjectItem (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_GetObjectItem (operand, "left"); const char *prop = cJSON_GetStringValue (cJSON_GetObjectItem (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_GetObjectItem (operand, "left"); cJSON *idx_expr = cJSON_GetObjectItem (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_GetObjectItem (expr, "expression"); const char *operand_kind = cJSON_GetStringValue (cJSON_GetObjectItem (operand, "kind")); int slot = mach_gen_alloc_slot (s); if (strcmp (operand_kind, ".") == 0) { cJSON *obj = cJSON_GetObjectItem (operand, "left"); const char *prop = cJSON_GetStringValue (cJSON_GetObjectItem (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_GetObjectItem (operand, "left"); cJSON *idx = cJSON_GetObjectItem (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_GetObjectItem (expr, "expression"); cJSON *then_expr = cJSON_GetObjectItem (expr, "then"); cJSON *else_expr = cJSON_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (pair, "left"); cJSON *val = cJSON_GetObjectItem (pair, "right"); int val_slot = mach_gen_expr (s, val, -1); const char *key_kind = cJSON_GetStringValue (cJSON_GetObjectItem (key, "kind")); if (key_kind && strcmp (key_kind, "name") == 0) { const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (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_GetObjectItem (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_GetObjectItem (stmt, "kind")); if (!kind) return; if (strcmp (kind, "var") == 0 || strcmp (kind, "def") == 0) { cJSON *left = cJSON_GetObjectItem (stmt, "left"); cJSON *right = cJSON_GetObjectItem (stmt, "right"); const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (left, "name")); const char *scope_name = cJSON_GetStringValue (cJSON_GetObjectItem (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_GetObjectItem (stmt, "pop"); if (pop_flag && cJSON_IsTrue (pop_flag) && right) { cJSON *arr_expr = cJSON_GetObjectItem (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_GetObjectItem (stmt, "list"); cJSON *child; cJSON_ArrayForEach (child, list) { mach_gen_statement (s, child); } return; } if (strcmp (kind, "block") == 0) { cJSON *stmts = cJSON_GetObjectItem (stmt, "statements"); cJSON *child; cJSON_ArrayForEach (child, stmts) { mach_gen_statement (s, child); } return; } if (strcmp (kind, "if") == 0) { cJSON *cond = cJSON_GetObjectItem (stmt, "expression"); cJSON *then_stmts = cJSON_GetObjectItem (stmt, "then"); cJSON *else_stmts = cJSON_GetObjectItem (stmt, "else"); /* Parser uses "list" for else-if chains */ if (!else_stmts) else_stmts = cJSON_GetObjectItem (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_GetObjectItem (stmt, "expression"); cJSON *stmts = cJSON_GetObjectItem (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_GetObjectItem (stmt, "expression"); cJSON *stmts = cJSON_GetObjectItem (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_GetObjectItem (stmt, "init"); cJSON *test = cJSON_GetObjectItem (stmt, "test"); cJSON *update = cJSON_GetObjectItem (stmt, "update"); cJSON *stmts = cJSON_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (stmt, "expression"); if (!call_expr) { mach_gen_error (s, stmt, "'go' requires a function call expression"); return; } const char *call_kind = cJSON_GetStringValue (cJSON_GetObjectItem (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_GetObjectItem (call_expr, "expression"); cJSON *args_list = cJSON_GetObjectItem (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_GetObjectItem (callee, "kind")); if (callee_kind && strcmp (callee_kind, ".") == 0) { cJSON *obj_node = cJSON_GetObjectItem (callee, "left"); const char *prop = cJSON_GetStringValue (cJSON_GetObjectItem (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); else mach_gen_error (s, stmt, "'break' used outside of loop or switch"); return; } if (strcmp (kind, "continue") == 0) { if (s->loop_continue) mach_gen_emit_jump (s, s->loop_continue); else mach_gen_error (s, stmt, "'continue' used outside of loop"); return; } if (strcmp (kind, "switch") == 0) { cJSON *expr = cJSON_GetObjectItem (stmt, "expression"); cJSON *cases = cJSON_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (case_node, "_label")); mach_gen_emit_label (s, label); } cJSON *case_stmts = cJSON_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (func_node, "arrow"); s.is_arrow = is_arrow && cJSON_IsTrue (is_arrow); /* Read function_nr from AST node */ cJSON *fn_nr_node = cJSON_GetObjectItem (func_node, "function_nr"); s.function_nr = fn_nr_node ? (int)cJSON_GetNumberValue (fn_nr_node) : 0; /* Parameters */ cJSON *params = cJSON_GetObjectItem (func_node, "list"); if (!params) params = cJSON_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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; /* Pre-load intrinsics (global names) */ mach_gen_load_intrinsics (&s, cJSON_GetObjectItem (func_node, "intrinsics")); /* Compile hoisted function declarations from func_node["functions"] */ cJSON *hoisted = cJSON_GetObjectItem (func_node, "functions"); if (hoisted) { cJSON *fn; cJSON_ArrayForEach (fn, hoisted) { const char *fname = cJSON_GetStringValue (cJSON_GetObjectItem (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_GetObjectItem (func_node, "statements"); if (!stmts) { cJSON *body = cJSON_GetObjectItem (func_node, "body"); if (body) { stmts = cJSON_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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_GetObjectItem (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; /* Pre-load intrinsics (global names) */ mach_gen_load_intrinsics (s, cJSON_GetObjectItem (ast, "intrinsics")); /* Compile hoisted function declarations from ast["functions"] */ cJSON *hoisted = cJSON_GetObjectItem (ast, "functions"); if (hoisted) { cJSON *fn; cJSON_ArrayForEach (fn, hoisted) { const char *name = cJSON_GetStringValue (cJSON_GetObjectItem (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_GetObjectItem (ast, "statements"); int last_expr_slot = -1; cJSON *stmt; cJSON_ArrayForEach (stmt, statements) { const char *kind = cJSON_GetStringValue (cJSON_GetObjectItem (stmt, "kind")); if (kind) { if (strcmp (kind, "call") == 0) { cJSON *expr = cJSON_GetObjectItem (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; } char *JS_Mcode (const char *ast_json) { cJSON *ast = cJSON_Parse (ast_json); 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); cJSON_Delete (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); char *json = cJSON_Print (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_GetObjectItem(func_def, "nr_args")); code->nr_slots = (uint16_t)cJSON_GetNumberValue(cJSON_GetObjectItem(func_def, "nr_slots")); /* Build instruction array from cJSON linked list */ cJSON *instrs_arr = cJSON_GetObjectItem(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_GetObjectItem(func_def, "name")); code->filename = cJSON_GetStringValue(cJSON_GetObjectItem(func_def, "filename")); /* Extract disruption_pc */ cJSON *dpc = cJSON_GetObjectItem(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) { JSFrameRegister *frame = alloc_frame_register(ctx, code->nr_slots); if (!frame) return JS_EXCEPTION; /* Protect frame from GC */ JSGCRef frame_ref; JS_AddGCRef(ctx, &frame_ref); frame_ref.val = JS_MKPTR(frame); /* Create a function object for the main frame so return can find the code */ JSValue main_func = js_new_mcode_function(ctx, code); if (JS_IsException(main_func)) { JS_DeleteGCRef(ctx, &frame_ref); 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 frame pointer */ JSFunction *main_fn = JS_VALUE_GET_FUNCTION(main_func); main_fn->u.mcode.outer_frame = outer_frame; frame->function = main_func; /* Setup initial frame */ frame->slots[0] = this_obj; /* slot 0 is this */ for (int i = 0; i < argc && i < code->nr_args; i++) { frame->slots[1 + i] = argv[i]; } 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_GetObjectItem(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); 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; JSValue left = frame->slots[(int)a2->valuedouble]; JSValue right = frame->slots[(int)a3->valuedouble]; if (!JS_IsText(left) || !JS_IsText(right)) { goto disrupt; } JSValue space = JS_ConcatString(ctx, left, JS_NewString(ctx, " ")); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); JSValue res = JS_ConcatString(ctx, space, right); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame->slots[dest] = res; } else if (strcmp(op, "length") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (!JS_IsText(v)) { goto disrupt; } frame->slots[dest] = JS_NewInt32(ctx, js_string_value_len(v)); } else if (strcmp(op, "lower") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (!JS_IsText(v)) { goto disrupt; } JSValue res = js_cell_text_lower(ctx, JS_NULL, 1, &v); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame->slots[dest] = res; } else if (strcmp(op, "upper") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (!JS_IsText(v)) { goto disrupt; } JSValue res = js_cell_text_upper(ctx, JS_NULL, 1, &v); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame->slots[dest] = res; } else if (strcmp(op, "character") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (!JS_IsNumber(v)) { goto disrupt; } JSValue res = js_cell_character(ctx, JS_NULL, 1, &v); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); frame->slots[dest] = res; } else if (strcmp(op, "codepoint") == 0) { int dest = (int)a1->valuedouble; JSValue v = frame->slots[(int)a2->valuedouble]; if (!JS_IsText(v)) { goto disrupt; } JSValue res = js_cell_text_codepoint(ctx, JS_NULL, 1, &v); 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, "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 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 from frame slots */ int nr_slots = (int)objhdr_cap56(new_frame->hdr); int c_argc = (nr_slots >= 2) ? nr_slots - 2 : 0; if (c_argc > 16) c_argc = 16; JSValue c_argv[16]; for (int i = 0; i < c_argc; i++) c_argv[i] = new_frame->slots[i + 1]; ctx->reg_current_frame = frame_ref.val; ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0; JSValue c_result = JS_Call(ctx, new_frame->function, new_frame->slots[0], c_argc, c_argv); 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) */ int nargs = 0; for (cJSON *p = a3->next; p && !cJSON_IsString(p); p = p->next) nargs++; if (JS_IsFunction(obj)) { /* Proxy call: obj(name, [args...]) */ JSValue key = JS_NewString(ctx, method_name); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); JSValue arr = JS_NewArray(ctx); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (JS_IsException(arr)) 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); } int vs_base = ctx->value_stack_top; ctx->value_stack[vs_base] = key; ctx->value_stack[vs_base + 1] = frame->slots[dest]; ctx->value_stack_top = vs_base + 2; ctx->reg_current_frame = frame_ref.val; ctx->rt->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 */ 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); 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->rt->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; JSValue func_val = frame->slots[func_slot]; JSValue arr_val = frame->slots[arr_slot]; if (!JS_IsFunction(func_val) || !JS_IsArray(arr_val)) { goto disrupt; } JSValue len_val = JS_GetProperty(ctx, arr_val, 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; JSValue argv[256]; if (len > 256) len = 256; for (int i = 0; i < len; i++) { argv[i] = JS_GetPropertyUint32(ctx, arr_val, i); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); } ctx->reg_current_frame = frame_ref.val; ctx->rt->current_register_pc = pc > 0 ? pc - 1 : 0; result = JS_Call(ctx, func_val, JS_NULL, len, argv); 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; JSValue right = frame->slots[(int)a2->valuedouble]; JSValue pt_val = frame->slots[pt_slot]; if (!JS_IsText(pt_val) || !JS_IsText(right)) { goto disrupt; } JSText *s = JS_VALUE_GET_PTR(pt_val); s = pretext_concat_value(ctx, s, right); 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 = ""; JSValue pat_val = JS_NewString(ctx, pattern); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); JSValue flags_val = *flags_str ? JS_NewString(ctx, flags_str) : JS_NULL; frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); JSValue bc = js_compile_regexp(ctx, pat_val, flags_val); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); if (JS_IsException(bc)) { goto disrupt; } JSValue re_obj = js_regexp_constructor_internal(ctx, pat_val, bc); frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame_ref.val); 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; JSValue val = frame->slots[val_slot]; if (!JS_IsArray(frame->slots[arr_slot])) { goto disrupt; } JSGCRef arr_gc; JS_PushGCRef(ctx, &arr_gc); arr_gc.val = frame->slots[arr_slot]; 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 != frame->slots[arr_slot]) { frame->slots[arr_slot] = arr_gc.val; /* If we pushed the array onto itself and it was relocated, fix the dangling reference in the last element */ if (val_slot == arr_slot) { JSArray *a = JS_VALUE_GET_ARRAY(arr_gc.val); a->values[a->len - 1] = 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. */ { 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->rt->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) { JSRuntime *rt = ctx->rt; if (JS_IsNull(ctx->reg_current_frame)) return NULL; JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame); uint32_t cur_pc = rt->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; } /* Public API: parse MCODE JSON and execute */ JSValue JS_CallMcode(JSContext *ctx, const char *mcode_json) { cJSON *root = cJSON_Parse(mcode_json); if (!root) return JS_ThrowSyntaxError(ctx, "invalid MCODE JSON"); cJSON *main_obj = cJSON_GetObjectItem(root, "main"); if (!main_obj) { cJSON_Delete(root); return JS_ThrowSyntaxError(ctx, "MCODE JSON missing 'main' section"); } cJSON *functions = cJSON_GetObjectItem(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 JSON 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; } /* ============================================================ 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; /* 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 JSON. */ void JS_DumpMach(JSContext *ctx, const char *ast_json, JSValue env) { MachCode *mc = JS_CompileMach(ast_json); 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"); } /* Compile and execute MACH bytecode. Takes AST JSON. */ JSValue JS_RunMach(JSContext *ctx, const char *ast_json, JSValue env) { MachCode *mc = JS_CompileMach(ast_json); 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; }