/* * QuickJS Javascript Engine * * Copyright (c) 2017-2025 Fabrice Bellard * Copyright (c) 2017-2025 Charlie Gordon * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. */ #define BLOB_IMPLEMENTATION #define NOTA_IMPLEMENTATION #define WOTA_IMPLEMENTATION #include "quickjs-internal.h" JSClassID js_class_id_alloc = JS_CLASS_INIT_COUNT; /* === Function definitions from header region (non-inline) === */ JS_BOOL JS_IsStone(JSValue v) { return !JS_IsObject(v) || objhdr_s(*chase(v)); } JS_BOOL JS_IsArray(JSValue v) { return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_ARRAY; } JS_BOOL JS_IsRecord (JSValue v) { return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_RECORD; } JS_BOOL JS_IsFunction (JSValue v) { return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_FUNCTION; } JS_BOOL JS_IsCode (JSValue v) { return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_CODE; } JS_BOOL JS_IsForwarded (JSValue v) { return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_FORWARD; } JS_BOOL JS_IsFrame (JSValue v) { return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_FRAME; } JS_BOOL JS_IsBlob (JSValue v) { return JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_BLOB; } JS_BOOL JS_IsText(JSValue v) { return MIST_IsImmediateASCII(v) || (JS_IsObject(v) && objhdr_type(*chase(v)) == OBJ_TEXT); } uint64_t get_text_hash (JSText *text) { uint64_t len = objhdr_cap56 (text->hdr); size_t word_count = (len + 1) / 2; if (objhdr_s (text->hdr)) { /* Stoned text: check for cached hash */ if (text->length != 0) return text->length; /* Compute and cache hash using content length from header */ text->length = fash64_hash_words (text->packed, word_count, len); if (!text->length) text->length = 1; return text->length; } else { /* Pre-text: compute hash on the fly */ return fash64_hash_words (text->packed, word_count, len); } } /* Pack UTF-32 characters into 64-bit words (2 chars per word) */ void pack_utf32_to_words (const uint32_t *utf32, uint32_t len, uint64_t *packed) { for (uint32_t i = 0; i < len; i += 2) { uint64_t hi = utf32[i]; uint64_t lo = (i + 1 < len) ? utf32[i + 1] : 0; packed[i / 2] = (hi << 32) | lo; } } /* Compare two packed UTF-32 texts for equality */ int text_equal (JSText *a, const uint64_t *packed_b, uint32_t len_b) { uint32_t len_a = (uint32_t)objhdr_cap56 (a->hdr); if (len_a != len_b) return 0; size_t word_count = (len_a + 1) / 2; return memcmp (a->packed, packed_b, word_count * sizeof (uint64_t)) == 0; } int JS_IsPretext (JSValue v) { if (!JS_IsText (v)) return 0; JSText *text = (JSText *)JS_VALUE_GET_PTR (v); return !objhdr_s (text->hdr); } JSValue *JS_PushGCRef (JSContext *ctx, JSGCRef *ref) { ref->prev = ctx->top_gc_ref; ctx->top_gc_ref = ref; ref->val = JS_NULL; return &ref->val; } JSValue JS_PopGCRef (JSContext *ctx, JSGCRef *ref) { ctx->top_gc_ref = ref->prev; return ref->val; } JSValue *JS_AddGCRef (JSContext *ctx, JSGCRef *ref) { ref->prev = ctx->last_gc_ref; ctx->last_gc_ref = ref; ref->val = JS_NULL; return &ref->val; } void JS_DeleteGCRef (JSContext *ctx, JSGCRef *ref) { JSGCRef **pref, *ref1; pref = &ctx->last_gc_ref; for (;;) { ref1 = *pref; if (ref1 == NULL) abort (); if (ref1 == ref) { *pref = ref1->prev; break; } pref = &ref1->prev; } } void *st_alloc (JSContext *ctx, size_t bytes, size_t align) { /* Align the request */ bytes = (bytes + align - 1) & ~(align - 1); /* Check if we have space in the stone arena */ if (ctx->stone_base && ctx->stone_free + bytes <= ctx->stone_end) { void *ptr = ctx->stone_free; ctx->stone_free += bytes; return ptr; } /* No stone arena or not enough space - allocate a page */ size_t page_size = sizeof (StonePage) + bytes; StonePage *page = malloc (page_size); if (!page) return NULL; page->next = ctx->st_pages; page->size = bytes; ctx->st_pages = page; return page->data; } /* Free all stone arena pages */ void st_free_all (JSContext *ctx) { StonePage *page = ctx->st_pages; while (page) { StonePage *next = page->next; free (page); page = next; } ctx->st_pages = NULL; } int st_text_resize (JSContext *ctx) { uint32_t new_size, new_resize; uint32_t *new_hash; JSText **new_array; if (ctx->st_text_size == 0) { /* Initial allocation */ new_size = ST_TEXT_INITIAL_SIZE; } else { /* Double the size */ new_size = ctx->st_text_size * 2; } new_resize = new_size * 3 / 4; /* 75% load factor */ /* Allocate new hash table (use runtime malloc, not bump allocator) */ new_hash = js_malloc_rt (new_size * sizeof (uint32_t)); if (!new_hash) return -1; memset (new_hash, 0, new_size * sizeof (uint32_t)); /* Allocate new text array (one extra for 1-based indexing) */ new_array = js_malloc_rt ((new_size + 1) * sizeof (JSText *)); if (!new_array) { js_free_rt(new_hash); return -1; } memset (new_array, 0, (new_size + 1) * sizeof (JSText *)); /* Rehash existing entries */ if (ctx->st_text_count > 0) { uint32_t mask = new_size - 1; for (uint32_t id = 1; id <= ctx->st_text_count; id++) { JSText *text = ctx->st_text_array[id]; new_array[id] = text; /* Compute hash and find slot */ uint64_t hash = get_text_hash (text); uint32_t slot = hash & mask; while (new_hash[slot] != 0) slot = (slot + 1) & mask; new_hash[slot] = id; } } /* Free old tables */ if (ctx->st_text_hash) js_free_rt (ctx->st_text_hash); if (ctx->st_text_array) js_free_rt (ctx->st_text_array); ctx->st_text_hash = new_hash; ctx->st_text_array = new_array; ctx->st_text_size = new_size; ctx->st_text_resize = new_resize; return 0; } void *js_realloc (JSContext *ctx, void *ptr, size_t size) { void *new_ptr; /* Align size to 8 bytes */ size = (size + 7) & ~7; if (!ptr) { /* New allocation */ new_ptr = js_malloc (ctx, size); if (!new_ptr) return NULL; return new_ptr; } /* Bump allocator: just allocate new space. Caller is responsible for protecting ptr and copying data. */ new_ptr = js_malloc (ctx, size); return new_ptr; } void JS_MarkValue (JSRuntime *rt, JSValue val, JS_MarkFunc *mark_func) { (void)rt; (void)val; (void)mark_func; /* No-op with copying GC - values are discovered by tracing from roots */ } JSValue intern_text_to_value (JSContext *ctx, const uint32_t *utf32, uint32_t len) { /* Pack UTF-32 for hashing and comparison */ size_t word_count = (len + 1) / 2; uint64_t *packed = alloca (word_count * sizeof (uint64_t)); pack_utf32_to_words (utf32, len, packed); uint64_t hash = fash64_hash_words (packed, word_count, len); /* Look up in hash table */ uint32_t mask = ctx->st_text_size - 1; uint32_t slot = hash & mask; while (ctx->st_text_hash[slot] != 0) { uint32_t id = ctx->st_text_hash[slot]; JSText *existing = ctx->st_text_array[id]; if (text_equal (existing, packed, len)) { /* Found existing entry */ return JS_MKPTR (existing); } slot = (slot + 1) & mask; } /* Not found - create new entry */ if (ctx->st_text_count >= ctx->st_text_resize) { if (st_text_resize (ctx) < 0) return JS_NULL; /* OOM */ /* Recompute slot after resize */ mask = ctx->st_text_size - 1; slot = hash & mask; while (ctx->st_text_hash[slot] != 0) slot = (slot + 1) & mask; } /* Allocate JSText in stone arena */ size_t text_size = sizeof (JSText) + word_count * sizeof (uint64_t); JSText *text = st_alloc (ctx, text_size, 8); if (!text) return JS_NULL; /* OOM */ /* Initialize the text */ text->hdr = objhdr_make (len, OBJ_TEXT, false, false, false, true); /* s=1 for stoned */ text->length = hash; /* Store hash in length field for stoned text */ memcpy (text->packed, packed, word_count * sizeof (uint64_t)); /* Add to intern table */ uint32_t new_id = ++ctx->st_text_count; ctx->st_text_hash[slot] = new_id; ctx->st_text_array[new_id] = text; return JS_MKPTR (text); } JSValue js_key_new (JSContext *ctx, const char *str) { size_t len = strlen (str); /* Try immediate ASCII first (≤7 ASCII chars) */ if (len <= MIST_ASCII_MAX_LEN) { JSValue imm = MIST_TryNewImmediateASCII (str, len); if (!JS_IsNull (imm)) return imm; } /* Check length limit */ if (len > JS_KEY_MAX_LEN) return JS_NULL; /* Convert UTF-8 to UTF-32 for interning - use stack buffer */ const uint8_t *p = (const uint8_t *)str; const uint8_t *p_end = p + len; uint32_t utf32_buf[JS_KEY_MAX_LEN]; uint32_t utf32_len = 0; while (p < p_end && utf32_len < JS_KEY_MAX_LEN) { uint32_t c; const uint8_t *p_next; if (*p < 0x80) { c = *p++; } else { c = unicode_from_utf8 (p, p_end - p, &p_next); if (c == (uint32_t)-1) { c = 0xFFFD; /* replacement char for invalid UTF-8 */ p++; } else { p = p_next; } } utf32_buf[utf32_len++] = c; } return intern_text_to_value (ctx, utf32_buf, utf32_len); } /* JS_NewAtomString - creates JSValue string from C string (for compatibility) */ JSValue JS_NewAtomString (JSContext *ctx, const char *str) { return js_key_new (ctx, str); } JSValue js_key_new_len (JSContext *ctx, const char *str, size_t len) { /* Try immediate ASCII first (≤7 ASCII chars) */ if (len <= MIST_ASCII_MAX_LEN) { JSValue imm = MIST_TryNewImmediateASCII (str, len); if (!JS_IsNull (imm)) return imm; } /* Check length limit */ if (len > JS_KEY_MAX_LEN) return JS_NULL; /* Convert UTF-8 to UTF-32 for interning */ const uint8_t *p = (const uint8_t *)str; const uint8_t *p_end = p + len; uint32_t utf32_buf[JS_KEY_MAX_LEN]; uint32_t utf32_len = 0; while (p < p_end && utf32_len < JS_KEY_MAX_LEN) { uint32_t c; const uint8_t *p_next; if (*p < 0x80) { c = *p++; } else { c = unicode_from_utf8 (p, p_end - p, &p_next); if (c == (uint32_t)-1) { c = 0xFFFD; p++; } else { p = p_next; } } utf32_buf[utf32_len++] = c; } return intern_text_to_value (ctx, utf32_buf, utf32_len); } uint64_t js_key_hash (JSValue key) { if (MIST_IsImmediateASCII (key)) { /* Hash immediate ASCII the same way as heap strings for consistency */ int len = MIST_GetImmediateASCIILen (key); if (len == 0) return 1; size_t word_count = (len + 1) / 2; uint64_t packed[4]; /* Max 7 chars = 4 words */ for (size_t i = 0; i < word_count; i++) { uint32_t c0 = (i * 2 < (size_t)len) ? MIST_GetImmediateASCIIChar (key, i * 2) : 0; uint32_t c1 = (i * 2 + 1 < (size_t)len) ? MIST_GetImmediateASCIIChar (key, i * 2 + 1) : 0; packed[i] = ((uint64_t)c0 << 32) | c1; } uint64_t h = fash64_hash_words (packed, word_count, len); return h ? h : 1; } if (!JS_IsPtr (key)) return 0; /* Use chase to follow forwarding pointers */ void *ptr = chase (key); objhdr_t hdr = *(objhdr_t *)ptr; uint8_t type = objhdr_type (hdr); if (type == OBJ_TEXT) { /* For JSText (stoned strings), use get_text_hash */ JSText *text = (JSText *)ptr; return get_text_hash (text); } if (type == OBJ_RECORD) { JSRecord *rec = (JSRecord *)ptr; uint32_t rec_id = REC_GET_REC_ID(rec); if (rec_id == 0) return 0; return fash64_hash_one (rec_id); } return 0; } JS_BOOL js_key_equal (JSValue a, JSValue b) { if (a == b) return TRUE; /* Use chase to follow forwarding pointers */ if (MIST_IsImmediateASCII (a)) { if (MIST_IsImmediateASCII (b)) return FALSE; if (!JS_IsPtr (b)) return FALSE; JSText *tb = (JSText *)chase (b); if (objhdr_type (tb->hdr) != OBJ_TEXT) return FALSE; return JSText_equal_ascii (tb, a); } if (MIST_IsImmediateASCII (b)) { if (!JS_IsPtr (a)) return FALSE; JSText *ta = (JSText *)chase (a); if (objhdr_type (ta->hdr) != OBJ_TEXT) return FALSE; return JSText_equal_ascii (ta, b); } if (!JS_IsPtr (a) || !JS_IsPtr (b)) return FALSE; void *pa = chase (a); void *pb = chase (b); objhdr_t ha = *(objhdr_t *)pa; objhdr_t hb = *(objhdr_t *)pb; uint8_t type_a = objhdr_type (ha); uint8_t type_b = objhdr_type (hb); if (type_a != type_b) return FALSE; if (type_a == OBJ_RECORD) return FALSE; /* pointer equality handled above */ if (type_a == OBJ_TEXT) return JSText_equal ((JSText *)pa, (JSText *)pb); return FALSE; } JS_BOOL js_key_equal_str (JSValue a, const char *str) { size_t len = strlen (str); if (MIST_IsImmediateASCII (a)) { int imm_len = MIST_GetImmediateASCIILen (a); if ((size_t)imm_len != len) return FALSE; for (int i = 0; i < imm_len; i++) { if (MIST_GetImmediateASCIIChar (a, i) != str[i]) return FALSE; } return TRUE; } if (!JS_IsPtr (a)) return FALSE; JSText *ta = (JSText *)JS_VALUE_GET_PTR (a); if (objhdr_type (ta->hdr) != OBJ_TEXT) return FALSE; uint64_t txt_len = objhdr_cap56 (ta->hdr); if (txt_len != len) return FALSE; /* Compare character by character (UTF-32 vs ASCII) */ for (size_t i = 0; i < len; i++) { if (string_get (ta, i) != (uint32_t)(unsigned char)str[i]) return FALSE; } return TRUE; } int rec_find_slot (JSRecord *rec, JSValue k) { uint64_t mask = objhdr_cap56 (rec->mist_hdr); uint64_t h64 = js_key_hash (k); uint64_t slot = (h64 & mask); if (slot == 0) slot = 1; /* slot 0 is reserved */ uint64_t first_tomb = 0; for (uint64_t i = 0; i <= mask; i++) { JSValue slot_key = rec->slots[slot].key; if (rec_key_is_empty (slot_key)) { /* Empty slot - key not found */ return first_tomb ? -(int)first_tomb : -(int)slot; } if (rec_key_is_tomb (slot_key)) { /* Tombstone - remember for insertion but keep searching */ if (first_tomb == 0) first_tomb = slot; } else if (js_key_equal (slot_key, k)) { /* Found it */ return (int)slot; } slot = (slot + 1) & mask; if (slot == 0) slot = 1; } /* Table full (shouldn't happen with proper load factor) */ return first_tomb ? -(int)first_tomb : -1; } JSValue rec_get (JSContext *ctx, JSRecord *rec, JSValue k) { if (rec_key_is_empty (k) || rec_key_is_tomb (k)) return JS_NULL; /* Walk prototype chain */ JSRecord *p = rec; while (p) { int slot = rec_find_slot (p, k); if (slot > 0) { return p->slots[slot].val; } p = p->proto; } return JS_NULL; } int rec_resize (JSContext *ctx, JSValue *pobj, uint64_t new_mask) { /* Protect the source object with a GC ref in case js_malloc triggers GC */ JSGCRef obj_ref; JS_AddGCRef (ctx, &obj_ref); obj_ref.val = *pobj; JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (*pobj); uint64_t old_mask = objhdr_cap56 (rec->mist_hdr); /* Allocate new record with larger capacity - may trigger GC! */ size_t slots_size = sizeof (slot) * (new_mask + 1); size_t total_size = sizeof (JSRecord) + slots_size; JSRecord *new_rec = js_malloc (ctx, total_size); if (!new_rec) { JS_DeleteGCRef (ctx, &obj_ref); return -1; } /* Re-get record from GC ref - it may have moved during GC */ rec = (JSRecord *)JS_VALUE_GET_OBJ (obj_ref.val); old_mask = objhdr_cap56 (rec->mist_hdr); /* Initialize new record */ new_rec->mist_hdr = objhdr_make (new_mask, OBJ_RECORD, false, false, false, false); new_rec->proto = rec->proto; new_rec->len = 0; /* Initialize all slots to empty */ for (uint64_t i = 0; i <= new_mask; i++) { new_rec->slots[i].key = JS_NULL; new_rec->slots[i].val = JS_NULL; } /* Copy slot[0] (class_id, rec_id, opaque) */ new_rec->slots[0].key = rec->slots[0].key; new_rec->slots[0].val = rec->slots[0].val; /* Rehash all valid entries from old to new */ for (uint64_t i = 1; i <= old_mask; i++) { JSValue k = rec->slots[i].key; if (!rec_key_is_empty (k) && !rec_key_is_tomb (k)) { /* Insert into new record using linear probing */ uint64_t h64 = js_key_hash (k); uint64_t slot = (h64 & new_mask); if (slot == 0) slot = 1; while (!rec_key_is_empty (new_rec->slots[slot].key)) { slot = (slot + 1) & new_mask; if (slot == 0) slot = 1; } new_rec->slots[slot].key = k; new_rec->slots[slot].val = rec->slots[i].val; new_rec->len++; } } /* Install forward header at old location so stale references can find the new record */ rec->mist_hdr = objhdr_make_fwd (new_rec); /* Update caller's JSValue to point to new record */ *pobj = JS_MKPTR (new_rec); JS_DeleteGCRef (ctx, &obj_ref); return 0; } int rec_set_own (JSContext *ctx, JSValue *pobj, JSValue k, JSValue val) { if (rec_key_is_empty (k) || rec_key_is_tomb (k)) { return -1; } JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (*pobj); int slot = rec_find_slot (rec, k); if (slot > 0) { /* Existing key - replace value */ rec->slots[slot].val = val; return 0; } /* New key - check if resize needed (75% load factor) */ uint32_t mask = (uint32_t)objhdr_cap56 (rec->mist_hdr); if ((rec->len + 1) * 4 > mask * 3) { /* Over 75% load factor - resize. Protect key and value in case GC runs. */ JSGCRef k_ref, val_ref; JS_AddGCRef (ctx, &k_ref); JS_AddGCRef (ctx, &val_ref); k_ref.val = k; val_ref.val = val; uint32_t new_mask = (mask + 1) * 2 - 1; if (rec_resize (ctx, pobj, new_mask) < 0) { JS_DeleteGCRef (ctx, &val_ref); JS_DeleteGCRef (ctx, &k_ref); return -1; } /* Re-get values after resize (they may have moved during GC) */ k = k_ref.val; val = val_ref.val; JS_DeleteGCRef (ctx, &val_ref); JS_DeleteGCRef (ctx, &k_ref); /* Re-get rec after resize (pobj now points to new record) */ rec = (JSRecord *)JS_VALUE_GET_OBJ (*pobj); /* Re-find slot after resize */ slot = rec_find_slot (rec, k); } /* Insert at -slot */ int insert_slot = -slot; rec->slots[insert_slot].key = k; rec->slots[insert_slot].val = val; rec->len++; return 0; } JSRecord *js_new_record_class (JSContext *ctx, uint32_t initial_mask, JSClassID class_id) { if (initial_mask == 0) initial_mask = JS_RECORD_INITIAL_MASK; /* Allocate record + inline slots in one bump allocation */ size_t slots_size = sizeof (slot) * (initial_mask + 1); size_t total_size = sizeof (JSRecord) + slots_size; JSRecord *rec = js_malloc (ctx, total_size); if (!rec) return NULL; rec->mist_hdr = objhdr_make (initial_mask, OBJ_RECORD, false, false, false, false); rec->proto = NULL; rec->len = 0; /* Initialize all slots to empty (JS_NULL) */ for (uint32_t i = 0; i <= initial_mask; i++) { rec->slots[i].key = JS_NULL; rec->slots[i].val = JS_NULL; } /* slots[0] is reserved: store class_id (low 32) and rec_id (high 32) in key */ uint32_t rec_id = ++ctx->rec_key_next; rec->slots[0].key = (JSValue)class_id | ((JSValue)rec_id << 32); rec->slots[0].val = 0; /* opaque pointer, initially NULL */ return rec; } int JS_SetPropertyInternal (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val) { if (!JS_IsRecord (this_obj)) { return -1; } /* Use a local copy that rec_set_own can update if resize happens */ JSValue obj = this_obj; return rec_set_own (ctx, &obj, prop, val); } void *js_malloc_rt (size_t size) { return malloc(size); } void js_free_rt (void *ptr) { free (ptr); } void *js_realloc_rt (void *ptr, size_t size) { return realloc (ptr, size); } void *js_mallocz_rt (size_t size) { void *ptr; ptr = js_malloc_rt (size); if (!ptr) return NULL; return memset (ptr, 0, size); } char *js_strdup_rt (const char *str) { if (!str) return NULL; size_t len = strlen(str) + 1; char *dup = js_malloc_rt(len); if (dup) memcpy(dup, str, len); return dup; } /* Throw out of memory in case of error */ void *js_malloc (JSContext *ctx, size_t size) { /* Align size to 8 bytes */ size = (size + 7) & ~7; #ifdef FORCE_GC_AT_MALLOC /* Force GC on every allocation for testing - but don't grow heap unless needed */ int need_space = (uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end; if (ctx_gc(ctx, need_space, size) < 0) { JS_ThrowOutOfMemory(ctx); return NULL; } if ((uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end) { JS_ThrowOutOfMemory(ctx); return NULL; } #else /* Check if we have space in current block */ if ((uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end) { /* Trigger GC to reclaim memory */ if (ctx_gc (ctx, 1, size) < 0) { JS_ThrowOutOfMemory (ctx); return NULL; } /* Re-check after GC */ if ((uint8_t *)ctx->heap_free + size > (uint8_t *)ctx->heap_end) { JS_ThrowOutOfMemory (ctx); return NULL; } } #endif void *ptr = ctx->heap_free; ctx->heap_free = (uint8_t *)ctx->heap_free + size; return ptr; } /* Throw out of memory in case of error */ void *js_mallocz (JSContext *ctx, size_t size) { void *ptr = js_malloc (ctx, size); if (!ptr) return NULL; return memset (ptr, 0, size); } void js_free (JSContext *ctx, void *ptr) { /* Bump allocator doesn't free individual allocations - GC handles it */ (void)ctx; (void)ptr; } /* Parser memory functions - use system allocator to avoid GC issues */ /* Forward declarations for ppretext_end */ JSText *js_alloc_string (JSContext *ctx, int max_len); static inline void string_put (JSText *p, int idx, uint32_t c); PPretext *ppretext_init (int capacity) { PPretext *p = pjs_malloc (sizeof (PPretext)); if (!p) return NULL; if (capacity <= 0) capacity = 32; p->data = pjs_malloc (capacity * sizeof (uint32_t)); if (!p->data) { pjs_free (p); return NULL; } p->len = 0; p->cap = capacity; return p; } void ppretext_free (PPretext *p) { if (p) { pjs_free (p->data); pjs_free (p); } } no_inline PPretext *ppretext_realloc (PPretext *p, int new_cap) { uint32_t *new_data = pjs_realloc (p->data, new_cap * sizeof (uint32_t)); if (!new_data) return NULL; p->data = new_data; p->cap = new_cap; return p; } PPretext *ppretext_putc (PPretext *p, uint32_t c) { if (p->len >= p->cap) { int new_cap = max_int (p->len + 1, p->cap * 3 / 2); if (!ppretext_realloc (p, new_cap)) return NULL; } p->data[p->len++] = c; return p; } JSValue ppretext_end (JSContext *ctx, PPretext *p) { if (!p) return JS_EXCEPTION; int len = p->len; if (len == 0) { ppretext_free (p); return JS_KEY_empty; } /* Allocate heap string (single allocation) */ JSText *str = js_alloc_string (ctx, len); if (!str) { ppretext_free (p); return JS_EXCEPTION; } for (int i = 0; i < len; i++) { string_put (str, i, p->data[i]); } str->hdr = objhdr_set_cap56 (str->hdr, len); str->hdr = objhdr_set_s (str->hdr, true); ppretext_free (p); return JS_MKPTR (str); } no_inline int js_realloc_array (JSContext *ctx, void **parray, int elem_size, int *psize, int req_size) { int new_size; void *new_array; void *old_array = *parray; int old_size = *psize; /* XXX: potential arithmetic overflow */ new_size = max_int (req_size, old_size * 3 / 2); /* Protect source object with a GC ref before allocating (GC may move it) */ JSGCRef src_ref; JS_PushGCRef (ctx, &src_ref); if (old_array) { src_ref.val = JS_MKPTR (old_array); } new_array = js_malloc (ctx, new_size * elem_size); if (!new_array) { JS_PopGCRef (ctx, &src_ref); return -1; } /* Get possibly-moved source pointer after GC */ if (old_array) { old_array = (void *)chase (src_ref.val); memcpy (new_array, old_array, old_size * elem_size); } JS_PopGCRef (ctx, &src_ref); *psize = new_size; *parray = new_array; return 0; } /* Append a JSValue string to a PPretext (parser pretext) */ PPretext *ppretext_append_jsvalue (PPretext *p, JSValue str) { int len = js_string_value_len (str); for (int i = 0; i < len; i++) { uint32_t c = js_string_value_get (str, i); p = ppretext_putc (p, c); if (!p) return NULL; } return p; } /* Append an integer to a PPretext */ PPretext *ppretext_append_int (PPretext *p, int n) { char buf[16]; int len = snprintf (buf, sizeof (buf), "%d", n); for (int i = 0; i < len; i++) { p = ppretext_putc (p, buf[i]); if (!p) return NULL; } return p; } /* Convert a JSValue string to a property key. For immediates, returns the value as-is (can be used directly as keys). For heap strings, returns interned version. */ JSValue js_key_from_string (JSContext *ctx, JSValue val) { if (MIST_IsImmediateASCII (val)) { return val; /* Immediates can be used directly as keys */ } if (JS_IsText (val)) { JSText *p = JS_VALUE_GET_TEXT (val); int64_t len = JSText_len (p); /* Use JSText_len which checks header for stoned text */ /* Extract UTF-32 characters and intern */ uint32_t *utf32_buf = alloca (len * sizeof (uint32_t)); for (int64_t i = 0; i < len; i++) { utf32_buf[i] = string_get (p, i); } return intern_text_to_value (ctx, utf32_buf, len); } return JS_NULL; } static JSClass const js_std_class_def[] = { { "error", NULL, NULL }, /* JS_CLASS_ERROR */ { "regexp", js_regexp_finalizer, NULL }, /* JS_CLASS_REGEXP */ { "blob", NULL, NULL }, /* JS_CLASS_BLOB - registered separately */ }; static int init_class_range (JSContext *ctx, JSClass const *tab, int start, int count) { JSClassDef cm_s, *cm = &cm_s; int i, class_id; for (i = 0; i < count; i++) { class_id = i + start; memset (cm, 0, sizeof (*cm)); cm->finalizer = tab[i].finalizer; cm->gc_mark = tab[i].gc_mark; if (JS_NewClass1 (ctx, class_id, cm, tab[i].class_name) < 0) return -1; } return 0; } /* Destroy buddy allocator and free pool */ void buddy_destroy (BuddyAllocator *b) { if (!b->initialized) return; free (b->base); b->base = NULL; b->initialized = 0; for (int i = 0; i < BUDDY_LEVELS; i++) { b->free_lists[i] = NULL; } } /* ============================================================ Heap block allocation wrappers In POISON_HEAP mode, use malloc so poisoned memory stays poisoned. Otherwise use buddy allocator for efficiency. ============================================================ */ static void *heap_block_alloc(JSRuntime *rt, size_t size) { #ifdef POISON_HEAP (void)rt; return malloc(size); #else return buddy_alloc(&rt->buddy, size); #endif } static void heap_block_free(JSRuntime *rt, void *ptr, size_t size) { #ifdef POISON_HEAP (void)rt; (void)size; /* Don't free - leave it poisoned to catch stale accesses */ gc_poison_region(ptr, size); #else buddy_free(&rt->buddy, ptr, size); #endif } /* ============================================================ Bump Allocator and Cheney GC ============================================================ */ /* Forward declarations for GC helpers */ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size); JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *from_end, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end); void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8_t *from_end, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end); size_t gc_object_size (void *ptr); /* Alignment for GC object sizes - must match max alignment requirement */ #define GC_ALIGN 8 static inline size_t gc_align_up (size_t n) { return (n + GC_ALIGN - 1) & ~(GC_ALIGN - 1); } /* Get size of a heap object based on its type */ size_t gc_object_size (void *ptr) { objhdr_t hdr = *(objhdr_t *)ptr; uint8_t type = objhdr_type (hdr); uint64_t cap = objhdr_cap56 (hdr); switch (type) { case OBJ_ARRAY: { /* JSArray + inline values array. Cap is element capacity. */ size_t values_size = sizeof (JSValue) * cap; return gc_align_up (sizeof (JSArray) + values_size); } case OBJ_TEXT: { /* JSText: header + pad + hdr + length + packed chars */ size_t word_count = (cap + 1) / 2; return gc_align_up (sizeof (JSText) + word_count * sizeof (uint64_t)); } case OBJ_RECORD: { /* JSRecord + inline tab. Cap is mask, so tab size is mask+1 entries. */ size_t tab_size = sizeof (JSRecordEntry) * (cap + 1); return gc_align_up (sizeof (JSRecord) + tab_size); } case OBJ_FUNCTION: return gc_align_up (sizeof (JSFunction)); case OBJ_FRAME: { /* JSFrame + slots array. cap56 stores slot count */ uint64_t slot_count = cap; return gc_align_up (sizeof (JSFrame) + slot_count * sizeof (JSValue)); } default: /* Unknown type - fatal error, heap is corrupt or scan desync'd */ fflush(stdout); fprintf (stderr, "gc_object_size: unknown type %d at %p, hdr=0x%llx cap=%llu\n", type, ptr, (unsigned long long)hdr, (unsigned long long)cap); /* Dump surrounding memory for debugging */ uint64_t *words = (uint64_t *)ptr; fprintf (stderr, " words: [0]=0x%llx [1]=0x%llx [2]=0x%llx [3]=0x%llx\n", (unsigned long long)words[0], (unsigned long long)words[1], (unsigned long long)words[2], (unsigned long long)words[3]); fflush(stderr); abort (); } } static inline int ptr_in_range (void *p, uint8_t *b, uint8_t *e) { uint8_t *q = (uint8_t *)p; return q >= b && q < e; } JSValue gc_copy_value (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *from_end, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) { if (!JS_IsPtr (v)) return v; for (;;) { void *ptr = JS_VALUE_GET_PTR (v); if (is_stone_ptr (ctx, ptr)) return v; if (!ptr_in_range (ptr, from_base, from_end)) return v; objhdr_t *hdr_ptr = (objhdr_t *)ptr; objhdr_t hdr = *hdr_ptr; uint8_t type = objhdr_type (hdr); if (type == OBJ_FORWARD) { void *t = objhdr_fwd_ptr (hdr); /* If it already points into to-space, it's a GC forward. */ if (ptr_in_range (t, to_base, *to_free)) return JS_MKPTR (t); /* Otherwise it's a growth-forward (still in from-space). Chase. */ v = JS_MKPTR (t); continue; } if (type != OBJ_ARRAY && type != OBJ_TEXT && type != OBJ_RECORD && type != OBJ_FUNCTION && type != OBJ_BLOB && type != OBJ_CODE && type != OBJ_FRAME) { fprintf (stderr, "gc_copy_value: invalid object type %d at %p (hdr=0x%llx)\n", type, ptr, (unsigned long long)hdr); fprintf (stderr, " This may be an interior pointer or corrupt root\n"); fflush (stderr); abort (); } size_t size = gc_object_size (hdr_ptr); if (*to_free + size > to_end) { fprintf (stderr, "gc_copy_value: out of to-space, need %zu bytes\n", size); abort (); } void *new_ptr = *to_free; memcpy (new_ptr, hdr_ptr, size); *to_free += size; *hdr_ptr = objhdr_make_fwd (new_ptr); return JS_MKPTR (new_ptr); } } /* Scan a copied object and update its internal references */ void gc_scan_object (JSContext *ctx, void *ptr, uint8_t *from_base, uint8_t *from_end, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) { objhdr_t hdr = *(objhdr_t *)ptr; uint8_t type = objhdr_type (hdr); switch (type) { case OBJ_ARRAY: { JSArray *arr = (JSArray *)ptr; for (uint32_t i = 0; i < arr->len; i++) { arr->values[i] = gc_copy_value (ctx, arr->values[i], from_base, from_end, to_base, to_free, to_end); } break; } case OBJ_RECORD: { JSRecord *rec = (JSRecord *)ptr; uint32_t mask = (uint32_t)objhdr_cap56 (rec->mist_hdr); #ifdef DUMP_GC_DETAIL printf(" record: slots=%u used=%u proto=%p\n", mask + 1, (uint32_t)rec->len, (void*)rec->proto); fflush(stdout); #endif /* Copy prototype */ if (rec->proto) { JSValue proto_val = JS_MKPTR (rec->proto); proto_val = gc_copy_value (ctx, proto_val, from_base, from_end, to_base, to_free, to_end); rec->proto = (JSRecord *)JS_VALUE_GET_PTR (proto_val); } /* Copy table entries */ for (uint32_t i = 0; i <= mask; i++) { JSValue k = rec->slots[i].key; if (!rec_key_is_empty (k) && !rec_key_is_tomb (k)) { rec->slots[i].key = gc_copy_value (ctx, k, from_base, from_end, to_base, to_free, to_end); rec->slots[i].val = gc_copy_value (ctx, rec->slots[i].val, from_base, from_end, to_base, to_free, to_end); } } break; } case OBJ_FUNCTION: { JSFunction *fn = (JSFunction *)ptr; /* Scan the function name */ fn->name = gc_copy_value (ctx, fn->name, from_base, from_end, to_base, to_free, to_end); /* Scan bytecode's cpool - it contains JSValues that may reference GC objects */ if (fn->kind == JS_FUNC_KIND_BYTECODE && fn->u.func.function_bytecode) { JSFunctionBytecode *b = fn->u.func.function_bytecode; /* Scan cpool entries */ for (int i = 0; i < b->cpool_count; i++) { b->cpool[i] = gc_copy_value (ctx, b->cpool[i], from_base, from_end, to_base, to_free, to_end); } /* Scan func_name and filename */ b->func_name = gc_copy_value (ctx, b->func_name, from_base, from_end, to_base, to_free, to_end); if (b->has_debug) { b->debug.filename = gc_copy_value (ctx, b->debug.filename, from_base, from_end, to_base, to_free, to_end); } /* Scan outer_frame (for closures) - it's already a JSValue */ fn->u.func.outer_frame = gc_copy_value (ctx, fn->u.func.outer_frame, from_base, from_end, to_base, to_free, to_end); /* Scan env_record (stone record / module environment) */ fn->u.func.env_record = gc_copy_value (ctx, fn->u.func.env_record, from_base, from_end, to_base, to_free, to_end); } else if (fn->kind == JS_FUNC_KIND_REGISTER && fn->u.reg.code) { /* Register VM function - scan cpool (off-heap but contains JSValues) */ JSCodeRegister *code = fn->u.reg.code; for (uint32_t i = 0; i < code->cpool_count; i++) { code->cpool[i] = gc_copy_value (ctx, code->cpool[i], from_base, from_end, to_base, to_free, to_end); } /* Scan function name */ code->name = gc_copy_value (ctx, code->name, from_base, from_end, to_base, to_free, to_end); /* Scan outer_frame and env_record */ fn->u.reg.outer_frame = gc_copy_value (ctx, fn->u.reg.outer_frame, from_base, from_end, to_base, to_free, to_end); fn->u.reg.env_record = gc_copy_value (ctx, fn->u.reg.env_record, from_base, from_end, to_base, to_free, to_end); /* Recursively scan nested function cpools */ for (uint32_t i = 0; i < code->func_count; i++) { if (code->functions[i]) { JSCodeRegister *nested = code->functions[i]; for (uint32_t j = 0; j < nested->cpool_count; j++) { nested->cpool[j] = gc_copy_value (ctx, nested->cpool[j], from_base, from_end, to_base, to_free, to_end); } nested->name = gc_copy_value (ctx, nested->name, from_base, from_end, to_base, to_free, to_end); } } } else if (fn->kind == JS_FUNC_KIND_MCODE) { /* MCODE function - scan outer_frame and env_record */ fn->u.mcode.outer_frame = gc_copy_value (ctx, fn->u.mcode.outer_frame, from_base, from_end, to_base, to_free, to_end); fn->u.mcode.env_record = gc_copy_value (ctx, fn->u.mcode.env_record, from_base, from_end, to_base, to_free, to_end); } break; } case OBJ_TEXT: case OBJ_BLOB: /* No internal references to scan */ break; case OBJ_CODE: { /* JSFunctionBytecode - scan func_name and filename */ JSFunctionBytecode *bc = (JSFunctionBytecode *)ptr; bc->func_name = gc_copy_value (ctx, bc->func_name, from_base, from_end, to_base, to_free, to_end); if (bc->has_debug) { bc->debug.filename = gc_copy_value (ctx, bc->debug.filename, from_base, from_end, to_base, to_free, to_end); } /* Note: cpool, vardefs, closure_var, byte_code_buf are allocated via js_malloc */ break; } case OBJ_FRAME: { /* JSFrame - scan function, caller, and slots */ JSFrame *frame = (JSFrame *)ptr; /* function and caller are now JSValues - copy them directly */ frame->function = gc_copy_value (ctx, frame->function, from_base, from_end, to_base, to_free, to_end); frame->caller = gc_copy_value (ctx, frame->caller, from_base, from_end, to_base, to_free, to_end); /* Scan all slots */ uint64_t slot_count = objhdr_cap56 (frame->header); for (uint64_t i = 0; i < slot_count; i++) { frame->slots[i] = gc_copy_value (ctx, frame->slots[i], from_base, from_end, to_base, to_free, to_end); } break; } default: /* Unknown type during scan - fatal error */ fprintf (stderr, "gc_scan_object: unknown object type %d at %p\n", type, ptr); abort (); } } /* Forward declaration - defined after JSFunctionDef */ void gc_scan_parser_cpool (JSContext *ctx, uint8_t *from_base, uint8_t *from_end, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end); /* Scan OBJ_CODE cpool for bytecode objects outside GC heap */ void gc_scan_bytecode_cpool (JSContext *ctx, JSValue v, uint8_t *from_base, uint8_t *from_end, uint8_t *to_base, uint8_t **to_free, uint8_t *to_end) { if (!JS_IsPtr (v)) return; void *ptr = JS_VALUE_GET_PTR (v); if (ptr_in_range (ptr, from_base, from_end)) return; /* On GC heap, handled normally */ if (is_stone_ptr (ctx, ptr)) return; /* Stone memory */ /* Check if this is an OBJ_CODE outside GC heap */ objhdr_t hdr = *(objhdr_t *)ptr; if (objhdr_type (hdr) == OBJ_CODE) { JSFunctionBytecode *bc = (JSFunctionBytecode *)ptr; /* Scan cpool entries */ for (int i = 0; i < bc->cpool_count; i++) { bc->cpool[i] = gc_copy_value (ctx, bc->cpool[i], from_base, from_end, to_base, to_free, to_end); } /* Scan func_name and filename */ bc->func_name = gc_copy_value (ctx, bc->func_name, from_base, from_end, to_base, to_free, to_end); if (bc->has_debug) { bc->debug.filename = gc_copy_value (ctx, bc->debug.filename, from_base, from_end, to_base, to_free, to_end); } } } /* Cheney copying GC - collect garbage and compact live objects allow_grow: if true, grow heap when recovery is poor alloc_size: the allocation that triggered GC — used to size the new block */ int ctx_gc (JSContext *ctx, int allow_grow, size_t alloc_size) { JSRuntime *rt = ctx->rt; size_t old_used = ctx->heap_free - ctx->heap_base; size_t old_heap_size = ctx->current_block_size; /* Save OLD heap bounds before allocating new block Use heap_free (not heap_end) as from_end - only the portion up to heap_free contains valid objects. Beyond heap_free is uninitialized garbage. */ uint8_t *from_base = ctx->heap_base; uint8_t *from_end = ctx->heap_free; #ifdef DUMP_GC_DETAIL printf("ctx_gc: from_base=%p from_end=%p size=%zu\n", (void*)from_base, (void*)from_end, old_heap_size); #endif /* Request new block from runtime. When allow_grow is set and the pending allocation won't fit in the current next_block_size, jump straight to a block that can hold live_data + alloc_size instead of doubling one step at a time. */ size_t new_size = ctx->next_block_size; if (allow_grow) { size_t live_est = (size_t)(from_end - from_base); /* upper bound on live data */ size_t need = live_est + alloc_size; while (new_size < need && new_size < (1ULL << BUDDY_MAX_ORDER)) new_size *= 2; } uint8_t *new_block = heap_block_alloc (rt, new_size); if (!new_block) { /* Try with same size */ new_size = ctx->current_block_size; new_block = heap_block_alloc (rt, new_size); if (!new_block) return -1; } uint8_t *to_base = new_block; uint8_t *to_free = new_block; uint8_t *to_end = new_block + new_size; /* Copy roots: global object, class prototypes, exception, etc. */ #ifdef DUMP_GC_DETAIL printf(" roots: global_obj = 0x%llx\n", (unsigned long long)ctx->global_obj); fflush(stdout); if (JS_IsPtr(ctx->global_obj)) { void *gptr = JS_VALUE_GET_PTR(ctx->global_obj); printf(" ptr=%p in_from=%d is_stone=%d\n", gptr, ((uint8_t*)gptr >= from_base && (uint8_t*)gptr < from_end), is_stone_ptr(ctx, gptr)); fflush(stdout); } #endif ctx->global_obj = gc_copy_value (ctx, ctx->global_obj, from_base, from_end, to_base, &to_free, to_end); #ifdef DUMP_GC_DETAIL printf(" after copy: global_obj = 0x%llx\n", (unsigned long long)ctx->global_obj); fflush(stdout); #endif #ifdef DUMP_GC_DETAIL printf(" roots: regexp_ctor\n"); fflush(stdout); #endif ctx->regexp_ctor = gc_copy_value (ctx, ctx->regexp_ctor, from_base, from_end, to_base, &to_free, to_end); #ifdef DUMP_GC_DETAIL printf(" roots: throw_type_error\n"); fflush(stdout); #endif ctx->throw_type_error = gc_copy_value (ctx, ctx->throw_type_error, from_base, from_end, to_base, &to_free, to_end); /* Copy current exception if pending */ #ifdef DUMP_GC_DETAIL printf(" roots: current_exception\n"); fflush(stdout); #endif ctx->current_exception = gc_copy_value (ctx, ctx->current_exception, from_base, from_end, to_base, &to_free, to_end); #ifdef DUMP_GC_DETAIL printf(" roots: native_error_proto\n"); fflush(stdout); #endif for (int i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { ctx->native_error_proto[i] = gc_copy_value (ctx, ctx->native_error_proto[i], from_base, from_end, to_base, &to_free, to_end); } /* Copy class prototypes */ #ifdef DUMP_GC_DETAIL printf(" roots: class_proto (count=%d)\n", ctx->class_count); fflush(stdout); #endif for (int i = 0; i < ctx->class_count; i++) { ctx->class_proto[i] = gc_copy_value (ctx, ctx->class_proto[i], from_base, from_end, to_base, &to_free, to_end); } /* Copy value stack */ #ifdef DUMP_GC_DETAIL printf(" roots: value_stack (top=%d)\n", ctx->value_stack_top); fflush(stdout); #endif for (int i = 0; i < ctx->value_stack_top; i++) { ctx->value_stack[i] = gc_copy_value (ctx, ctx->value_stack[i], from_base, from_end, to_base, &to_free, to_end); } /* Copy frame stack references */ #ifdef DUMP_GC_DETAIL printf(" roots: frame_stack (top=%d)\n", ctx->frame_stack_top); fflush(stdout); #endif for (int i = 0; i <= ctx->frame_stack_top; i++) { struct VMFrame *frame = &ctx->frame_stack[i]; frame->cur_func = gc_copy_value (ctx, frame->cur_func, from_base, from_end, to_base, &to_free, to_end); frame->this_obj = gc_copy_value (ctx, frame->this_obj, from_base, from_end, to_base, &to_free, to_end); } /* Copy register VM current frame */ #ifdef DUMP_GC_DETAIL printf(" roots: reg_current_frame\n"); fflush(stdout); #endif ctx->reg_current_frame = gc_copy_value (ctx, ctx->reg_current_frame, from_base, from_end, to_base, &to_free, to_end); /* Copy JSStackFrame chain (C stack frames) */ #ifdef DUMP_GC_DETAIL printf(" roots: current_stack_frame chain\n"); fflush(stdout); #endif for (JSStackFrame *sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) { sf->cur_func = gc_copy_value (ctx, sf->cur_func, from_base, from_end, to_base, &to_free, to_end); /* Also scan bytecode cpool if it's a bytecode object outside GC heap */ gc_scan_bytecode_cpool (ctx, sf->cur_func, from_base, from_end, to_base, &to_free, to_end); /* Scan arg_buf and var_buf contents - they're on C stack but hold JSValues */ if (JS_IsFunction(sf->cur_func)) { JSFunction *fn = JS_VALUE_GET_FUNCTION(sf->cur_func); if (fn->kind == JS_FUNC_KIND_BYTECODE && fn->u.func.function_bytecode) { JSFunctionBytecode *b = fn->u.func.function_bytecode; /* Scan arg_buf */ if (sf->arg_buf) { for (int i = 0; i < sf->arg_count; i++) { sf->arg_buf[i] = gc_copy_value(ctx, sf->arg_buf[i], from_base, from_end, to_base, &to_free, to_end); } } /* Scan var_buf */ if (sf->var_buf) { for (int i = 0; i < b->var_count; i++) { sf->var_buf[i] = gc_copy_value(ctx, sf->var_buf[i], from_base, from_end, to_base, &to_free, to_end); } } /* Scan js_frame if present - it's on regular heap but contains JSValues pointing to GC heap */ sf->js_frame = gc_copy_value(ctx, sf->js_frame, from_base, from_end, to_base, &to_free, to_end); if (!JS_IsNull(sf->js_frame)) { JSFrame *jf = JS_VALUE_GET_FRAME(sf->js_frame); jf->function = gc_copy_value(ctx, jf->function, from_base, from_end, to_base, &to_free, to_end); jf->caller = gc_copy_value(ctx, jf->caller, from_base, from_end, to_base, &to_free, to_end); /* Note: jf->slots are same as arg_buf/var_buf, already scanned above */ } /* Scan operand stack */ if (sf->stack_buf && sf->p_sp) { JSValue *sp = *sf->p_sp; for (JSValue *p = sf->stack_buf; p < sp; p++) { *p = gc_copy_value(ctx, *p, from_base, from_end, to_base, &to_free, to_end); } } } } } /* Copy JS_PUSH_VALUE/JS_POP_VALUE roots */ #ifdef DUMP_GC_DETAIL printf(" roots: top_gc_ref\n"); fflush(stdout); #endif for (JSGCRef *ref = ctx->top_gc_ref; ref != NULL; ref = ref->prev) { gc_scan_bytecode_cpool (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); ref->val = gc_copy_value (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); } /* Copy JS_AddGCRef/JS_DeleteGCRef roots */ #ifdef DUMP_GC_DETAIL printf(" roots: last_gc_ref\n"); fflush(stdout); #endif for (JSGCRef *ref = ctx->last_gc_ref; ref != NULL; ref = ref->prev) { gc_scan_bytecode_cpool (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); ref->val = gc_copy_value (ctx, ref->val, from_base, from_end, to_base, &to_free, to_end); } /* Scan parser's cpool (if parsing is in progress) */ if (ctx->current_parse_fd) { gc_scan_parser_cpool (ctx, from_base, from_end, to_base, &to_free, to_end); } /* Cheney scan: scan copied objects to find more references */ uint8_t *scan = to_base; #ifdef DUMP_GC_DETAIL printf(" scan: to_base=%p to_free=%p to_end=%p\n", (void*)to_base, (void*)to_free, (void*)to_end); fflush(stdout); #endif while (scan < to_free) { #ifdef DUMP_GC_DETAIL objhdr_t scan_hdr = *(objhdr_t *)scan; printf(" scan %p: type=%d hdr=0x%llx", (void*)scan, objhdr_type(scan_hdr), (unsigned long long)scan_hdr); fflush(stdout); #endif size_t obj_size = gc_object_size (scan); #ifdef DUMP_GC_DETAIL printf(" size=%zu\n", obj_size); fflush(stdout); #endif gc_scan_object (ctx, scan, from_base, from_end, to_base, &to_free, to_end); scan += obj_size; } /* Return old block (in poison mode, just poison it and leak) */ heap_block_free (rt, from_base, old_heap_size); /* Update context with new block */ size_t new_used = to_free - to_base; size_t recovered = old_used > new_used ? old_used - new_used : 0; ctx->heap_base = to_base; ctx->heap_free = to_free; ctx->heap_end = to_end; ctx->current_block_size = new_size; #ifdef DUMP_GC_DETAIL /* Verify global_obj is in valid heap range after GC */ if (JS_IsPtr(ctx->global_obj)) { void *gptr = JS_VALUE_GET_PTR(ctx->global_obj); if ((uint8_t*)gptr < to_base || (uint8_t*)gptr >= to_free) { printf(" WARNING: global_obj=%p outside [%p, %p) after GC!\n", gptr, (void*)to_base, (void*)to_free); fflush(stdout); } } #endif /* If <20% recovered, double next block size for future allocations But only if allow_grow is set (i.e., GC was triggered due to low space) */ #ifdef DUMP_GC int will_grow = 0; #endif if (allow_grow && old_used > 0 && recovered < old_used / 5) { size_t doubled = new_size * 2; if (doubled <= (1ULL << BUDDY_MAX_ORDER)) { ctx->next_block_size = doubled; #ifdef DUMP_GC will_grow = 1; #endif } } #ifdef DUMP_GC printf ("\nGC: %zu -> %zu bytes, recovered %zu (%.1f%%)%s\n", old_heap_size, new_size, recovered, old_used > 0 ? (recovered * 100.0 / old_used) : 0.0, will_grow ? ", heap will grow" : ""); #endif return 0; } JSRuntime *JS_NewRuntime (void) { JSRuntime *rt; rt = malloc (sizeof (JSRuntime)); if (!rt) return NULL; memset (rt, 0, sizeof (*rt)); return rt; } void *JS_GetRuntimeOpaque (JSRuntime *rt) { return rt->user_opaque; } void JS_SetRuntimeOpaque (JSRuntime *rt, void *opaque) { rt->user_opaque = opaque; } void JS_SetMemoryLimit (JSRuntime *rt, size_t limit) { rt->malloc_limit = limit; } /* Helpers to call system memory functions (for memory allocated by external libs) */ #define malloc(s) malloc_is_forbidden (s) #define free(p) free_is_forbidden (p) #define realloc(p, s) realloc_is_forbidden (p, s) void JS_SetInterruptHandler (JSContext *ctx, JSInterruptHandler *cb, void *opaque) { ctx->interrupt_handler = cb; ctx->interrupt_opaque = opaque; } void JS_SetStripInfo (JSRuntime *rt, int flags) { rt->strip_flags = flags; } int JS_GetStripInfo (JSRuntime *rt) { return rt->strip_flags; } /* Allocate a string using bump allocation from context heap. Note: the string contents are uninitialized */ JSText *js_alloc_string (JSContext *ctx, int max_len) { JSText *str; /* Allocate packed UTF-32: 2 chars per 64-bit word. */ size_t data_words = (max_len + 1) / 2; size_t size = sizeof (JSText) + data_words * sizeof (uint64_t); str = js_malloc (ctx, size); if (unlikely (!str)) { JS_ThrowOutOfMemory (ctx); return NULL; } /* Initialize objhdr_t with OBJ_TEXT type and capacity in cap56 */ str->hdr = objhdr_make (max_len, OBJ_TEXT, false, false, false, false); str->length = 0; /* length starts at 0, capacity is in hdr */ return str; } void JS_SetRuntimeInfo (JSRuntime *rt, const char *s) { if (rt) rt->rt_info = s; } void JS_FreeRuntime (JSRuntime *rt) { /* Destroy buddy allocator */ buddy_destroy (&rt->buddy); sys_free (rt); } /* Forward declarations for intrinsics */ static void JS_AddIntrinsicBasicObjects (JSContext *ctx); static void JS_AddIntrinsicBaseObjects (JSContext *ctx); static void JS_AddIntrinsicRegExp (JSContext *ctx); JSContext *JS_NewContextRawWithHeapSize (JSRuntime *rt, size_t heap_size) { JSContext *ctx; /* Round up to buddy allocator minimum */ size_t min_size = 1ULL << BUDDY_MIN_ORDER; if (heap_size < min_size) heap_size = min_size; /* Round up to power of 2 for buddy allocator */ size_t actual = min_size; while (actual < heap_size && actual < (1ULL << BUDDY_MAX_ORDER)) { actual <<= 1; } heap_size = actual; ctx = js_mallocz_rt (sizeof (JSContext)); if (!ctx) return NULL; ctx->trace_hook = NULL; ctx->rt = rt; /* Bootstrap class_array and class_proto together via JS_NewClass1 */ if (init_class_range (ctx, js_std_class_def, JS_CLASS_OBJECT, countof (js_std_class_def)) < 0) { js_free_rt (ctx->class_array); js_free_rt (ctx->class_proto); js_free_rt (ctx); return NULL; } ctx->regexp_ctor = JS_NULL; /* Initialize VM stacks for trampoline */ ctx->frame_stack_capacity = 512; ctx->frame_stack = js_malloc_rt (sizeof (struct VMFrame) * ctx->frame_stack_capacity); if (!ctx->frame_stack) { js_free_rt (ctx->class_array); js_free_rt (ctx->class_proto); js_free_rt (ctx); return NULL; } ctx->frame_stack_top = -1; ctx->value_stack_capacity = 65536; /* 64K JSValue slots */ ctx->value_stack = js_malloc_rt (sizeof (JSValue) * ctx->value_stack_capacity); if (!ctx->value_stack) { js_free_rt (ctx->frame_stack); js_free_rt (ctx->class_array); js_free_rt (ctx->class_proto); js_free_rt (ctx); return NULL; } ctx->value_stack_top = 0; /* Initialize register VM frame root */ ctx->reg_current_frame = JS_NULL; /* Initialize per-context execution state (moved from JSRuntime) */ ctx->current_exception = JS_UNINITIALIZED; ctx->current_stack_frame = NULL; ctx->stack_size = JS_DEFAULT_STACK_SIZE; JS_UpdateStackTop (ctx); /* Initialize stone text intern table */ ctx->st_pages = NULL; ctx->st_text_array = NULL; ctx->st_text_hash = NULL; ctx->st_text_count = 0; ctx->st_text_size = 0; ctx->st_text_resize = 0; if (st_text_resize (ctx) < 0) { js_free_rt (ctx->value_stack); js_free_rt (ctx->frame_stack); js_free_rt (ctx->class_array); js_free_rt (ctx->class_proto); js_free_rt (ctx); return NULL; } /* Allocate initial heap block for bump allocation */ ctx->current_block_size = heap_size; ctx->next_block_size = ctx->current_block_size; ctx->heap_base = heap_block_alloc (rt, ctx->current_block_size); if (!ctx->heap_base) { js_free_rt (ctx->st_text_hash); js_free_rt (ctx->st_text_array); js_free_rt (ctx->value_stack); js_free_rt (ctx->frame_stack); js_free_rt (ctx->class_array); js_free_rt (ctx->class_proto); js_free_rt (ctx); return NULL; } ctx->heap_free = ctx->heap_base; ctx->heap_end = ctx->heap_base + ctx->current_block_size; #ifdef DUMP_GC_DETAIL printf("Context init: heap_base=%p heap_end=%p size=%zu\n", (void*)ctx->heap_base, (void*)ctx->heap_end, ctx->current_block_size); #endif #ifdef DUMP_GC_DETAIL printf("After BasicObjects: heap_base=%p heap_end=%p heap_free=%p\n", (void*)ctx->heap_base, (void*)ctx->heap_end, (void*)ctx->heap_free); #endif return ctx; } JSContext *JS_NewContextRaw (JSRuntime *rt) { return JS_NewContextRawWithHeapSize (rt, 1ULL << BUDDY_MIN_ORDER); } static void JS_AddIntrinsics (JSContext *ctx) { JS_AddIntrinsicBaseObjects (ctx); JS_AddIntrinsicRegExp (ctx); } JSContext *JS_NewContext (JSRuntime *rt) { JSContext *ctx = JS_NewContextRaw (rt); if (!ctx) return NULL; JS_AddIntrinsics (ctx); return ctx; } JSContext *JS_NewContextWithHeapSize (JSRuntime *rt, size_t heap_size) { JSContext *ctx = JS_NewContextRawWithHeapSize (rt, heap_size); if (!ctx) return NULL; JS_AddIntrinsics (ctx); return ctx; } void *JS_GetContextOpaque (JSContext *ctx) { return ctx->user_opaque; } void JS_SetContextOpaque (JSContext *ctx, void *opaque) { ctx->user_opaque = opaque; } void JS_SetClassProto (JSContext *ctx, JSClassID class_id, JSValue obj) { assert (class_id < ctx->class_count); set_value (ctx, &ctx->class_proto[class_id], obj); } JSValue JS_GetClassProto (JSContext *ctx, JSClassID class_id) { assert (class_id < ctx->class_count); return ctx->class_proto[class_id]; } void JS_FreeContext (JSContext *ctx) { JSRuntime *rt = ctx->rt; int i; #ifdef DUMP_MEM { JSMemoryUsage stats; JS_ComputeMemoryUsage (rt, &stats); JS_DumpMemoryUsage (stdout, &stats, rt); } #endif for (i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { } for (i = 0; i < ctx->class_count; i++) { } js_free_rt (ctx->class_array); js_free_rt (ctx->class_proto); /* Free VM stacks */ if (ctx->frame_stack) js_free_rt (ctx->frame_stack); if (ctx->value_stack) js_free_rt (ctx->value_stack); /* Free stone arena and intern table */ st_free_all (ctx); js_free_rt (ctx->st_text_hash); js_free_rt (ctx->st_text_array); /* Free heap block */ if (ctx->heap_base) { heap_block_free (rt, ctx->heap_base, ctx->current_block_size); ctx->heap_base = NULL; ctx->heap_free = NULL; ctx->heap_end = NULL; } js_free_rt (ctx); } JSRuntime *JS_GetRuntime (JSContext *ctx) { return ctx->rt; } static void update_stack_limit (JSContext *ctx) { if (ctx->stack_size == 0) { ctx->stack_limit = 0; /* no limit */ } else { ctx->stack_limit = ctx->stack_top - ctx->stack_size; } } void JS_SetMaxStackSize (JSContext *ctx, size_t stack_size) { ctx->stack_size = stack_size; update_stack_limit (ctx); } void JS_UpdateStackTop (JSContext *ctx) { ctx->stack_top = (const uint8_t *)js_get_stack_pointer (); update_stack_limit (ctx); } JSText *js_alloc_string (JSContext *ctx, int max_len); static inline int is_num (int c) { return c >= '0' && c <= '9'; } static __maybe_unused void JS_DumpChar (FILE *fo, int c, int sep) { if (c == sep || c == '\\') { fputc ('\\', fo); fputc (c, fo); } else if (c >= ' ' && c <= 126) { fputc (c, fo); } else if (c == '\n') { fputc ('\\', fo); fputc ('n', fo); } else { fprintf (fo, "\\u%04x", c); } } __maybe_unused void JS_DumpString (JSRuntime *rt, const JSText *p) { int i; if (p == NULL) { printf (""); return; } putchar ('"'); for (i = 0; i < (int)JSText_len (p); i++) { JS_DumpChar (stdout, string_get (p, i), '"'); } putchar ('"'); } static inline BOOL JS_IsEmptyString (JSValue v) { return v == JS_EMPTY_TEXT; } /* JSClass support */ /* a new class ID is allocated if *pclass_id != 0 */ JSClassID JS_NewClassID (JSClassID *pclass_id) { JSClassID class_id; class_id = *pclass_id; if (class_id == 0) { class_id = js_class_id_alloc++; *pclass_id = class_id; } return class_id; } JSClassID JS_GetClassID (JSValue v) { JSRecord *rec; if (!JS_IsRecord (v)) return JS_INVALID_CLASS_ID; rec = JS_VALUE_GET_RECORD (v); return REC_GET_CLASS_ID(rec); } BOOL JS_IsRegisteredClass (JSContext *ctx, JSClassID class_id) { return (class_id < ctx->class_count && ctx->class_array[class_id].class_id != 0); } /* create a new object internal class. Return -1 if error, 0 if OK. The finalizer can be NULL if none is needed. */ int JS_NewClass1 (JSContext *ctx, JSClassID class_id, const JSClassDef *class_def, const char *name) { int new_size, i; JSClass *cl, *new_class_array; JSValue *new_class_proto; if (class_id >= (1 << 16)) return -1; if (class_id < ctx->class_count && ctx->class_array[class_id].class_id != 0) return -1; if (class_id >= ctx->class_count) { new_size = max_int (JS_CLASS_INIT_COUNT, max_int (class_id + 1, ctx->class_count * 3 / 2)); /* reallocate the class array */ new_class_array = js_realloc_rt (ctx->class_array, sizeof (JSClass) * new_size); if (!new_class_array) return -1; memset (new_class_array + ctx->class_count, 0, (new_size - ctx->class_count) * sizeof (JSClass)); ctx->class_array = new_class_array; /* reallocate the class proto array */ new_class_proto = js_realloc_rt (ctx->class_proto, sizeof (JSValue) * new_size); if (!new_class_proto) return -1; for (i = ctx->class_count; i < new_size; i++) new_class_proto[i] = JS_NULL; ctx->class_proto = new_class_proto; ctx->class_count = new_size; } cl = &ctx->class_array[class_id]; cl->class_id = class_id; cl->class_name = name; /* name is already a const char* */ cl->finalizer = class_def->finalizer; cl->gc_mark = class_def->gc_mark; return 0; } int JS_NewClass (JSContext *ctx, JSClassID class_id, const JSClassDef *class_def) { /* class_name is stored directly as const char* */ return JS_NewClass1 (ctx, class_id, class_def, class_def->class_name); } JSValue js_new_string8_len (JSContext *ctx, const char *buf, int len) { JSText *str; int i; /* Empty string - return immediate empty */ if (len <= 0) { return MIST_TryNewImmediateASCII ("", 0); } /* Try immediate ASCII for short strings (≤7 chars) */ if (len <= MIST_ASCII_MAX_LEN) { JSValue imm = MIST_TryNewImmediateASCII (buf, len); if (!JS_IsNull (imm)) return imm; } /* Fall back to heap string */ str = js_alloc_string (ctx, len); if (!str) return JS_ThrowMemoryError (ctx); for (i = 0; i < len; i++) string_put (str, i, buf[i]); str->length = len; return pretext_end (ctx, str); } JSValue js_new_string8 (JSContext *ctx, const char *buf) { return js_new_string8_len (ctx, buf, strlen (buf)); } /* GC-safe substring: takes JSValue (which must be rooted by caller) */ static JSValue js_sub_string (JSContext *ctx, JSText *p, int start, int end) { int i; int len = end - start; if (start == 0 && end == (int)JSText_len (p)) { return JS_MKPTR (p); } /* Root the source string as a JSValue so it survives js_alloc_string GC */ JSGCRef src_ref; JS_PushGCRef (ctx, &src_ref); src_ref.val = JS_MKPTR (p); JSText *str = js_alloc_string (ctx, len); if (!str) { JS_PopGCRef (ctx, &src_ref); return JS_EXCEPTION; } /* Re-chase p after allocation */ p = JS_VALUE_GET_STRING (src_ref.val); for (i = 0; i < len; i++) string_put (str, i, string_get (p, start + i)); str->length = len; JS_PopGCRef (ctx, &src_ref); return pretext_end (ctx, str); } /* Substring from a JSValue (handles both immediate ASCII and heap strings) */ static JSValue js_sub_string_val (JSContext *ctx, JSValue src, int start, int end) { int len = end - start; if (len <= 0) return JS_NewString (ctx, ""); if (MIST_IsImmediateASCII (src)) { /* IMM: extract chars directly, try to return IMM */ if (len <= MIST_ASCII_MAX_LEN) { char buf[MIST_ASCII_MAX_LEN + 1]; for (int i = 0; i < len; i++) buf[i] = (char)MIST_GetImmediateASCIIChar (src, start + i); return js_new_string8_len (ctx, buf, len); } /* Longer than 7 — shouldn't happen for IMM (max 7 chars) but handle it */ JSText *str = js_alloc_string (ctx, len); if (!str) return JS_EXCEPTION; for (int i = 0; i < len; i++) string_put (str, i, MIST_GetImmediateASCIIChar (src, start + i)); str->length = len; return pretext_end (ctx, str); } /* Heap string — delegate to existing js_sub_string */ return js_sub_string (ctx, JS_VALUE_GET_STRING (src), start, end); } /* Allocate a new pretext (mutable JSText) with initial capacity */ JSText *pretext_init (JSContext *ctx, int capacity) { if (capacity <= 0) capacity = 16; JSText *s = js_alloc_string (ctx, capacity); if (!s) return NULL; s->length = 0; return s; } /* Reallocate a pretext to hold new_len characters */ static no_inline JSText *pretext_realloc (JSContext *ctx, JSText *s, int new_len) { if (new_len > JS_STRING_LEN_MAX) { JS_ThrowInternalError (ctx, "string too long"); return NULL; } int old_cap = (int)objhdr_cap56 (s->hdr); int old_len = (int)s->length; /* Grow by 50%, ensuring we have at least new_len capacity */ int new_cap = max_int (new_len, old_cap * 3 / 2); /* Protect source object with a GC ref before allocating (GC may move it) */ JSGCRef src_ref; JS_PushGCRef (ctx, &src_ref); src_ref.val = JS_MKPTR (s); /* Allocate new string - this may trigger GC */ JSText *new_str = js_alloc_string (ctx, new_cap); if (!new_str) { JS_PopGCRef (ctx, &src_ref); return NULL; } /* Get possibly-moved source pointer after GC */ s = (JSText *)chase (src_ref.val); JS_PopGCRef (ctx, &src_ref); /* Copy data from old string to new */ new_str->length = old_len; for (int i = 0; i < old_len; i++) { string_put (new_str, i, string_get (s, i)); } return new_str; } no_inline JSText *pretext_putc_slow (JSContext *ctx, JSText *s, uint32_t c) { int len = (int)s->length; int cap = (int)objhdr_cap56 (s->hdr); if (unlikely (len >= cap)) { s = pretext_realloc (ctx, s, len + 1); if (!s) return NULL; } string_put (s, len, c); s->length++; return s; } /* 0 <= c <= 0x10ffff */ JSText *pretext_putc (JSContext *ctx, JSText *s, uint32_t c) { int len = (int)s->length; int cap = (int)objhdr_cap56 (s->hdr); if (likely (len < cap)) { string_put (s, len, c); s->length++; return s; } return pretext_putc_slow (ctx, s, c); } static JSText *pretext_write8 (JSContext *ctx, JSText *s, const uint8_t *p, int len) { int cur_len = (int)s->length; int cap = (int)objhdr_cap56 (s->hdr); if (cur_len + len > cap) { s = pretext_realloc (ctx, s, cur_len + len); if (!s) return NULL; } for (int i = 0; i < len; i++) { string_put (s, cur_len + i, p[i]); } s->length += len; return s; } /* appending an ASCII string */ static JSText *pretext_puts8 (JSContext *ctx, JSText *s, const char *str) { return pretext_write8 (ctx, s, (const uint8_t *)str, strlen (str)); } static JSText *pretext_concat (JSContext *ctx, JSText *s, const JSText *p, uint32_t from, uint32_t to) { if (to <= from) return s; int len = (int)(to - from); int cur_len = (int)s->length; int cap = (int)objhdr_cap56 (s->hdr); if (cur_len + len > cap) { /* Root p across pretext_realloc which can trigger GC */ JSGCRef p_ref; JS_PushGCRef (ctx, &p_ref); p_ref.val = JS_MKPTR ((void *)p); s = pretext_realloc (ctx, s, cur_len + len); p = (const JSText *)chase (p_ref.val); JS_PopGCRef (ctx, &p_ref); if (!s) return NULL; } for (int i = 0; i < len; i++) { string_put (s, cur_len + i, string_get (p, (int)from + i)); } s->length += len; return s; } JSText *pretext_concat_value (JSContext *ctx, JSText *s, JSValue v) { if (MIST_IsImmediateASCII (v)) { int len = MIST_GetImmediateASCIILen (v); char buf[8]; for (int i = 0; i < len; i++) buf[i] = MIST_GetImmediateASCIIChar (v, i); return pretext_write8 (ctx, s, (const uint8_t *)buf, len); } if (JS_IsText (v)) { JSText *p = JS_VALUE_GET_PTR (v); return pretext_concat (ctx, s, p, 0, (uint32_t)JSText_len (p)); } JSValue v1 = JS_ToString (ctx, v); if (JS_IsException (v1)) return NULL; if (MIST_IsImmediateASCII (v1)) { int len = MIST_GetImmediateASCIILen (v1); char buf[8]; for (int i = 0; i < len; i++) buf[i] = MIST_GetImmediateASCIIChar (v1, i); s = pretext_write8 (ctx, s, (const uint8_t *)buf, len); return s; } JSText *p = JS_VALUE_GET_STRING (v1); s = pretext_concat (ctx, s, p, 0, (uint32_t)JSText_len (p)); return s; } /* Finalize a pretext into an immutable JSValue string */ JSValue pretext_end (JSContext *ctx, JSText *s) { if (!s) return JS_EXCEPTION; int len = (int)s->length; if (len == 0) { js_free (ctx, s); return JS_KEY_empty; } /* Set final length in capacity field and clear length for hash storage */ s->hdr = objhdr_set_cap56 (s->hdr, len); s->length = 0; s->hdr = objhdr_set_s (s->hdr, true); /* mark as stone */ return JS_MKPTR (s); } /* create a string from a UTF-8 buffer */ JSValue JS_NewStringLen (JSContext *ctx, const char *buf, size_t buf_len) { if (buf_len > JS_STRING_LEN_MAX) return JS_ThrowInternalError (ctx, "string too long"); /* Try immediate ASCII first (<=7 ASCII chars) */ if (buf_len <= MIST_ASCII_MAX_LEN) { JSValue ret = MIST_TryNewImmediateASCII (buf, buf_len); if (!JS_IsNull (ret)) return ret; } /* Count actual codepoints for allocation */ const uint8_t *p = (const uint8_t *)buf; const uint8_t *end = p + buf_len; int codepoint_count = 0; while (p < end) { if (*p < 128) { p++; codepoint_count++; } else { const uint8_t *next; int c = unicode_from_utf8 (p, (int)(end - p), &next); if (c < 0) { /* Invalid UTF-8 byte, treat as single byte */ p++; } else { p = next; } codepoint_count++; } } JSText *str = js_alloc_string (ctx, codepoint_count); if (!str) return JS_ThrowMemoryError (ctx); /* Decode UTF-8 to UTF-32 */ p = (const uint8_t *)buf; int i = 0; while (p < end) { uint32_t c; if (*p < 128) { c = *p++; } else { const uint8_t *next; int decoded = unicode_from_utf8 (p, (int)(end - p), &next); if (decoded < 0) { /* Invalid UTF-8 byte, use replacement char or the byte itself */ c = *p++; } else { c = (uint32_t)decoded; p = next; } } string_put (str, i++, c); } str->length = codepoint_count; return pretext_end (ctx, str); } JSValue JS_ConcatString3 (JSContext *ctx, const char *str1, JSValue str2, const char *str3) { JSText *b; int len1, len3, str2_len; if (!JS_IsText (str2)) { str2 = JS_ToString (ctx, str2); if (JS_IsException (str2)) goto fail; } str2_len = js_string_value_len (str2); len1 = strlen (str1); len3 = strlen (str3); b = pretext_init (ctx, len1 + str2_len + len3); if (!b) goto fail; b = pretext_write8 (ctx, b, (const uint8_t *)str1, len1); if (!b) goto fail; b = pretext_concat_value (ctx, b, str2); if (!b) goto fail; b = pretext_write8 (ctx, b, (const uint8_t *)str3, len3); if (!b) goto fail; return pretext_end (ctx, b); fail: return JS_EXCEPTION; } /* return (NULL, 0) if exception. */ /* return pointer into a JSText with a live ref_count */ /* cesu8 determines if non-BMP1 codepoints are encoded as 1 or 2 utf-8 * sequences */ const char *JS_ToCStringLen2 (JSContext *ctx, size_t *plen, JSValue val1, BOOL cesu8) { JSGCRef val_ref; char *q, *ret; size_t size; int i, len; JS_PushGCRef (ctx, &val_ref); if (!JS_IsText (val1)) { val_ref.val = JS_ToString (ctx, val1); if (JS_IsException (val_ref.val)) goto fail; } else { val_ref.val = val1; } /* Handle immediate ASCII strings */ if (MIST_IsImmediateASCII (val_ref.val)) { len = MIST_GetImmediateASCIILen (val_ref.val); ret = js_malloc_rt (len + 1); /* Use non-GC heap for returned C string */ if (!ret) goto fail; /* Re-read from val_ref after potential GC */ for (i = 0; i < len; i++) { ret[i] = MIST_GetImmediateASCIIChar (val_ref.val, i); } ret[len] = '\0'; if (plen) *plen = len; JS_PopGCRef (ctx, &val_ref); return ret; } /* Handle heap strings (JSText) */ JSText *str = JS_VALUE_GET_STRING (val_ref.val); len = (int)JSText_len (str); /* Calculate UTF-8 size */ size = 0; for (i = 0; i < len; i++) { uint32_t c = string_get (str, i); if (c < 0x80) size += 1; else if (c < 0x800) size += 2; else if (c < 0x10000) size += 3; else size += 4; } ret = js_malloc_rt (size + 1); /* Use non-GC heap for returned C string */ if (!ret) goto fail; /* str pointer is still valid - no GC triggered by js_malloc_rt */ /* Re-extract for safety in case code above changes */ str = JS_VALUE_GET_STRING (val_ref.val); q = ret; for (i = 0; i < len; i++) { uint32_t c = string_get (str, i); q += unicode_to_utf8 ((uint8_t *)q, c); } *q = '\0'; if (plen) *plen = size; JS_PopGCRef (ctx, &val_ref); return ret; fail: JS_PopGCRef (ctx, &val_ref); if (plen) *plen = 0; return NULL; } void JS_FreeCString (JSContext *ctx, const char *ptr) { /* Free C string allocated from non-GC heap */ js_free_rt ((void *)ptr); (void)ctx; (void)ptr; } JSValue JS_ConcatString1 (JSContext *ctx, const JSText *p1, const JSText *p2) { JSText *p; uint32_t len; int len1 = (int)JSText_len (p1); int len2 = (int)JSText_len (p2); len = len1 + len2; /* len is uint32_t, JS_STRING_LEN_MAX is 56 bits, so this always fits */ p = js_alloc_string (ctx, len); if (!p) return JS_EXCEPTION; /* Pack first string */ { int i; for (i = 0; i < len1; i++) string_put (p, i, string_get (p1, i)); for (i = 0; i < len2; i++) string_put (p, len1 + i, string_get (p2, i)); } return JS_MKPTR (p); } // TODO: this function is fucked. BOOL JS_ConcatStringInPlace (JSContext *ctx, JSText *p1, JSValue op2) { (void)ctx; if (JS_VALUE_GET_TAG (op2) == JS_TAG_STRING) { JSText *p2 = JS_VALUE_GET_STRING (op2); int64_t new_len; int64_t len1 = JSText_len (p1); int64_t len2 = JSText_len (p2); if (len2 == 0) return TRUE; new_len = len1 + len2; /* Append p2's characters using string_put/string_get */ for (int64_t i = 0; i < len2; i++) { string_put (p1, len1 + i, string_get (p2, i)); } p1->hdr = objhdr_set_cap56 (p1->hdr, new_len); return TRUE; } return FALSE; } /* Helper for string value comparison (handles immediate and heap strings) */ int js_string_compare_value (JSContext *ctx, JSValue op1, JSValue op2, BOOL eq_only) { (void)ctx; if (eq_only && op1 == op2) return 0; int len1 = js_string_value_len (op1); int len2 = js_string_value_len (op2); if (eq_only && len1 != len2) return 1; int len = min_int (len1, len2); for (int i = 0; i < len; i++) { uint32_t c1 = js_string_value_get (op1, i); uint32_t c2 = js_string_value_get (op2, i); if (c1 != c2) { return (c1 < c2) ? -1 : 1; } } if (len1 == len2) return 0; return (len1 < len2) ? -1 : 1; } int js_string_compare_value_nocase (JSContext *ctx, JSValue op1, JSValue op2) { (void)ctx; int len1 = js_string_value_len (op1); int len2 = js_string_value_len (op2); if (len1 != len2) return 1; for (int i = 0; i < len1; i++) { uint32_t c1 = js_string_value_get (op1, i); uint32_t c2 = js_string_value_get (op2, i); if (c1 >= 'A' && c1 <= 'Z') c1 += 32; if (c2 >= 'A' && c2 <= 'Z') c2 += 32; if (c1 != c2) return 1; } return 0; } JSValue JS_ConcatString (JSContext *ctx, JSValue op1, JSValue op2) { if (unlikely (!JS_IsText (op1))) { op1 = JS_ToString (ctx, op1); if (JS_IsException (op1)) { return JS_EXCEPTION; } } if (unlikely (!JS_IsText (op2))) { op2 = JS_ToString (ctx, op2); if (JS_IsException (op2)) { return JS_EXCEPTION; } } int len1 = js_string_value_len (op1); int len2 = js_string_value_len (op2); int new_len = len1 + len2; JSValue ret_val = JS_NULL; /* Try to create immediate ASCII if short enough and all ASCII */ if (new_len <= MIST_ASCII_MAX_LEN) { char buf[8]; BOOL all_ascii = TRUE; for (int i = 0; i < len1 && all_ascii; i++) { uint32_t c = js_string_value_get (op1, i); if (c >= 0x80) all_ascii = FALSE; else buf[i] = (char)c; } for (int i = 0; i < len2 && all_ascii; i++) { uint32_t c = js_string_value_get (op2, i); if (c >= 0x80) all_ascii = FALSE; else buf[len1 + i] = (char)c; } if (all_ascii) { ret_val = MIST_TryNewImmediateASCII (buf, new_len); } } if (JS_IsNull (ret_val)) { /* Protect op1 and op2 from GC during allocation */ JSGCRef op1_ref, op2_ref; JS_PushGCRef (ctx, &op1_ref); op1_ref.val = op1; JS_PushGCRef (ctx, &op2_ref); op2_ref.val = op2; JSText *p = js_alloc_string (ctx, new_len); if (!p) { JS_PopGCRef (ctx, &op2_ref); JS_PopGCRef (ctx, &op1_ref); return JS_EXCEPTION; } /* Get possibly-moved values after GC */ op1 = op1_ref.val; op2 = op2_ref.val; JS_PopGCRef (ctx, &op2_ref); JS_PopGCRef (ctx, &op1_ref); /* Copy characters using string_put/get */ for (int i = 0; i < len1; i++) { string_put (p, i, js_string_value_get (op1, i)); } for (int i = 0; i < len2; i++) { string_put (p, len1 + i, js_string_value_get (op2, i)); } p->length = new_len; ret_val = JS_MKPTR (p); } return ret_val; } /* WARNING: proto must be an object or JS_NULL */ JSValue JS_NewObjectProtoClass (JSContext *ctx, JSValue proto_val, JSClassID class_id) { JSGCRef proto_ref; JS_PushGCRef (ctx, &proto_ref); proto_ref.val = proto_val; JSRecord *rec = js_new_record_class (ctx, 0, class_id); proto_val = proto_ref.val; /* Get potentially-updated value after GC */ JS_PopGCRef (ctx, &proto_ref); if (!rec) return JS_EXCEPTION; /* Set prototype if provided */ if (JS_IsRecord (proto_val)) { rec->proto = JS_VALUE_GET_RECORD (proto_val); } return JS_MKPTR (rec); } JSValue JS_NewObjectClass (JSContext *ctx, int class_id) { return JS_NewObjectProtoClass (ctx, ctx->class_proto[class_id], class_id); } JSValue JS_NewObjectProto (JSContext *ctx, JSValue proto) { return JS_NewObjectProtoClass (ctx, proto, JS_CLASS_OBJECT); } /* Create an intrinsic array with specified capacity Uses bump allocation - values are inline after the JSArray struct */ JSValue JS_NewArrayLen (JSContext *ctx, uint32_t len) { JSArray *arr; uint32_t cap; cap = len > 0 ? len : JS_ARRAY_INITIAL_SIZE; size_t values_size = sizeof (JSValue) * cap; size_t total_size = sizeof (JSArray) + values_size; arr = js_malloc (ctx, total_size); if (!arr) return JS_EXCEPTION; arr->mist_hdr = objhdr_make (cap, OBJ_ARRAY, false, false, false, false); arr->len = len; /* Initialize all values to null (values[] is inline flexible array member) */ for (uint32_t i = 0; i < cap; i++) { arr->values[i] = JS_NULL; } return JS_MKPTR (arr); } JSValue JS_NewArray (JSContext *ctx) { return JS_NewArrayLen (ctx, 0); } JSValue JS_NewObject (JSContext *ctx) { /* inline JS_NewObjectClass(ctx, JS_CLASS_OBJECT); */ return JS_NewObjectProtoClass (ctx, ctx->class_proto[JS_CLASS_OBJECT], JS_CLASS_OBJECT); } /* Helper to check if a value is a bytecode function */ static BOOL js_is_bytecode_function (JSValue val) { if (!JS_IsFunction (val)) return FALSE; JSFunction *f = JS_VALUE_GET_FUNCTION (val); return f->kind == JS_FUNC_KIND_BYTECODE; } /* return NULL without exception if not a function or no bytecode */ static JSFunctionBytecode *JS_GetFunctionBytecode (JSValue val) { JSFunction *f; if (!JS_IsFunction (val)) return NULL; f = JS_VALUE_GET_FUNCTION (val); if (f->kind != JS_FUNC_KIND_BYTECODE) return NULL; return f->u.func.function_bytecode; } // TODO: needs reworked int js_method_set_properties (JSContext *ctx, JSValue func_obj, JSValue name, int flags, JSValue home_obj) { (void)ctx; (void)flags; (void)home_obj; if (!JS_IsFunction (func_obj)) return -1; JSFunction *f = JS_VALUE_GET_FUNCTION (func_obj); /* name is now JSValue text */ if (JS_IsText (name)) { f->name = name; } return 0; } /* Note: at least 'length' arguments will be readable in 'argv' */ static JSValue JS_NewCFunction3 (JSContext *ctx, JSCFunction *func, const char *name, int length, JSCFunctionEnum cproto, int magic) { JSValue func_obj; JSFunction *f; func_obj = js_new_function (ctx, JS_FUNC_KIND_C); if (JS_IsException (func_obj)) return func_obj; f = JS_VALUE_GET_FUNCTION (func_obj); f->u.cfunc.c_function.generic = func; f->u.cfunc.cproto = cproto; f->u.cfunc.magic = magic; f->length = length; if (name) { JSGCRef fobj_ref; JS_PushGCRef (ctx, &fobj_ref); fobj_ref.val = func_obj; JSValue key = js_key_new (ctx, name); func_obj = fobj_ref.val; JS_PopGCRef (ctx, &fobj_ref); f = JS_VALUE_GET_FUNCTION (func_obj); /* re-chase after allocation */ f->name = key; } else { f->name = JS_KEY_empty; } return func_obj; } /* Note: at least 'length' arguments will be readable in 'argv' */ JSValue JS_NewCFunction2 (JSContext *ctx, JSCFunction *func, const char *name, int length, JSCFunctionEnum cproto, int magic) { return JS_NewCFunction3 (ctx, func, name, length, cproto, magic); } /* free_property is defined earlier as a stub since shapes are removed */ /* GC-safe array growth function. Takes JSValue* pointer to a GC-tracked location (like &argv[n]). Allocates new array, copies data, installs forward header at old location. */ static int js_array_grow (JSContext *ctx, JSValue *arr_ptr, word_t min_cap) { JSArray *arr = JS_VALUE_GET_ARRAY (*arr_ptr); word_t old_cap = js_array_cap (arr); if (min_cap <= old_cap) return 0; if (objhdr_s (arr->mist_hdr)) { JS_ThrowInternalError (ctx, "cannot grow a stoned array"); return -1; } word_t new_cap = old_cap ? old_cap : JS_ARRAY_INITIAL_SIZE; while (new_cap < min_cap && new_cap <= JS_ARRAY_MAX_CAP / 2) new_cap *= 2; if (new_cap > JS_ARRAY_MAX_CAP) new_cap = JS_ARRAY_MAX_CAP; if (new_cap < min_cap) { JS_ThrowRangeError (ctx, "array capacity overflow"); return -1; } size_t total_size = sizeof (JSArray) + sizeof (JSValue) * new_cap; JSArray *new_arr = js_malloc (ctx, total_size); if (!new_arr) return -1; /* Re-chase arr via arr_ptr (GC may have moved it during js_malloc) */ arr = JS_VALUE_GET_ARRAY (*arr_ptr); new_arr->mist_hdr = objhdr_make (new_cap, OBJ_ARRAY, false, false, false, false); new_arr->len = arr->len; JSValue old_ptr = *arr_ptr; for (word_t i = 0; i < arr->len; i++) new_arr->values[i] = arr->values[i]; for (word_t i = arr->len; i < new_cap; i++) new_arr->values[i] = JS_NULL; /* Install forward header at old location */ arr->mist_hdr = objhdr_make_fwd (new_arr); /* Update the tracked JSValue to point to new array */ *arr_ptr = JS_MKPTR (new_arr); /* Fix self-references: update elements that pointed to the old array */ for (word_t i = 0; i < new_arr->len; i++) { if (new_arr->values[i] == old_ptr) new_arr->values[i] = *arr_ptr; } return 0; } /* GC-safe array push. Takes JSValue* to GC-tracked location. */ static int js_intrinsic_array_push (JSContext *ctx, JSValue *arr_ptr, JSValue val) { JSArray *arr = JS_VALUE_GET_ARRAY (*arr_ptr); if (objhdr_s (arr->mist_hdr)) { JS_ThrowInternalError (ctx, "cannot push to a stoned array"); return -1; } if (arr->len >= js_array_cap (arr)) { if (js_array_grow (ctx, arr_ptr, arr->len + 1) < 0) return -1; arr = JS_VALUE_GET_ARRAY (*arr_ptr); /* re-chase after grow */ } arr->values[arr->len++] = val; return 0; } /* GC-safe array set. Takes JSValue* to GC-tracked location. */ static int js_intrinsic_array_set (JSContext *ctx, JSValue *arr_ptr, word_t idx, JSValue val) { JSArray *arr = JS_VALUE_GET_ARRAY (*arr_ptr); if (objhdr_s (arr->mist_hdr)) { JS_ThrowInternalError (ctx, "cannot set on a stoned array"); return -1; } if (idx >= js_array_cap (arr)) { /* Root val across js_array_grow which can trigger GC */ JSGCRef val_ref; JS_PushGCRef (ctx, &val_ref); val_ref.val = val; if (js_array_grow (ctx, arr_ptr, idx + 1) < 0) { JS_PopGCRef (ctx, &val_ref); return -1; } val = val_ref.val; JS_PopGCRef (ctx, &val_ref); arr = JS_VALUE_GET_ARRAY (*arr_ptr); /* re-chase after grow */ } if (idx >= arr->len) { for (word_t i = arr->len; i < idx; i++) arr->values[i] = JS_NULL; arr->len = idx + 1; } arr->values[idx] = val; return 0; } /* Allocate intrinsic function (JS_TAG_FUNCTION) */ JSValue js_new_function (JSContext *ctx, JSFunctionKind kind) { JSFunction *func = js_mallocz (ctx, sizeof (JSFunction)); if (!func) return JS_EXCEPTION; func->header = objhdr_make (0, OBJ_FUNCTION, false, false, false, false); func->kind = kind; func->name = JS_NULL; func->length = 0; /* Initialize closure fields for bytecode functions */ if (kind == JS_FUNC_KIND_BYTECODE) { func->u.func.outer_frame = JS_NULL; func->u.func.env_record = JS_NULL; } return JS_MKPTR (func); } /* Get pointer to an upvalue in outer scope frame chain. depth=0 is current frame, depth=1 is immediate outer, etc. Returns NULL if depth exceeds the frame chain. frame_val is a JSValue containing a JSFrame pointer. */ void JS_ComputeMemoryUsage (JSRuntime *rt, JSMemoryUsage *s) { } void JS_DumpMemoryUsage (FILE *fp, const JSMemoryUsage *s, JSRuntime *rt) { } /* WARNING: obj is freed */ JSValue JS_Throw (JSContext *ctx, JSValue obj) { ctx->current_exception = obj; return JS_EXCEPTION; } /* return the pending exception (cannot be called twice). */ JSValue JS_GetException (JSContext *ctx) { JSValue val = ctx->current_exception; ctx->current_exception = JS_UNINITIALIZED; return val; } JS_BOOL JS_HasException (JSContext *ctx) { return !JS_IsUninitialized (ctx->current_exception); } /* use pc_value = -1 to get the position of the function definition */ static int find_line_num (JSContext *ctx, JSFunctionBytecode *b, uint32_t pc_value, int *pcol_num) { const uint8_t *p_end, *p; int new_line_num, line_num, pc, v, ret, new_col_num, col_num; uint32_t val; unsigned int op; if (!b->has_debug || !b->debug.pc2line_buf) goto fail; /* function was stripped */ p = b->debug.pc2line_buf; p_end = p + b->debug.pc2line_len; /* get the function line and column numbers */ ret = get_leb128 (&val, p, p_end); if (ret < 0) goto fail; p += ret; line_num = val + 1; ret = get_leb128 (&val, p, p_end); if (ret < 0) goto fail; p += ret; col_num = val + 1; if (pc_value != -1) { pc = 0; while (p < p_end) { op = *p++; if (op == 0) { ret = get_leb128 (&val, p, p_end); if (ret < 0) goto fail; pc += val; p += ret; ret = get_sleb128 (&v, p, p_end); if (ret < 0) goto fail; p += ret; new_line_num = line_num + v; } else { op -= PC2LINE_OP_FIRST; pc += (op / PC2LINE_RANGE); new_line_num = line_num + (op % PC2LINE_RANGE) + PC2LINE_BASE; } ret = get_sleb128 (&v, p, p_end); if (ret < 0) goto fail; p += ret; new_col_num = col_num + v; if (pc_value < pc) goto done; line_num = new_line_num; col_num = new_col_num; } } done: *pcol_num = col_num; return line_num; fail: *pcol_num = 0; return 0; } /* in order to avoid executing arbitrary code during the stack trace generation, we only look at simple 'name' properties containing a string. */ static const char *get_func_name (JSContext *ctx, JSValue func) { if (!JS_IsRecord (func)) return NULL; JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (func); /* Create "name" key as immediate ASCII string */ JSValue name_key = MIST_TryNewImmediateASCII ("name", 4); int slot = rec_find_slot (rec, name_key); if (slot <= 0) return NULL; JSValue val = rec->slots[slot].val; if (!JS_IsText (val)) return NULL; return JS_ToCString (ctx, val); } #define JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL (1 << 0) /* if filename != NULL, an additional level is added with the filename and line number information (used for parse error). */ void build_backtrace (JSContext *ctx, JSValue error_obj, const char *filename, int line_num, int col_num, int backtrace_flags) { JSStackFrame *sf; JSValue str; DynBuf dbuf; const char *func_name_str; const char *str1; JSGCRef err_ref; if (!JS_IsObject (error_obj)) return; /* protection in the out of memory case */ /* Protect error_obj from GC during backtrace building */ JS_PushGCRef (ctx, &err_ref); err_ref.val = error_obj; js_dbuf_init (ctx, &dbuf); if (filename) { dbuf_printf (&dbuf, " at %s", filename); if (line_num != -1) dbuf_printf (&dbuf, ":%d:%d", line_num, col_num); dbuf_putc (&dbuf, '\n'); /* Use short immediate strings for keys to avoid GC issues */ JSValue key_fileName = MIST_TryNewImmediateASCII ("file", 4); JSValue key_lineNumber = MIST_TryNewImmediateASCII ("line", 4); JSValue key_columnNumber = MIST_TryNewImmediateASCII ("col", 3); str = JS_NewString (ctx, filename); if (JS_IsException (str)) { JS_PopGCRef (ctx, &err_ref); return; } if (JS_SetPropertyInternal (ctx, err_ref.val, key_fileName, str) < 0 || JS_SetPropertyInternal (ctx, err_ref.val, key_lineNumber, JS_NewInt32 (ctx, line_num)) < 0 || JS_SetPropertyInternal (ctx, err_ref.val, key_columnNumber, JS_NewInt32 (ctx, col_num)) < 0) { JS_PopGCRef (ctx, &err_ref); return; } } for (sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) { if (sf->js_mode & JS_MODE_BACKTRACE_BARRIER) break; if (backtrace_flags & JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL) { backtrace_flags &= ~JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL; continue; } func_name_str = get_func_name (ctx, sf->cur_func); if (!func_name_str || func_name_str[0] == '\0') str1 = ""; else str1 = func_name_str; dbuf_printf (&dbuf, " at %s", str1); JS_FreeCString (ctx, func_name_str); if (JS_IsFunction (sf->cur_func)) { JSFunction *fn = JS_VALUE_GET_FUNCTION (sf->cur_func); if (fn->kind == JS_FUNC_KIND_BYTECODE) { JSFunctionBytecode *b; const char *atom_str; int line_num1, col_num1; b = fn->u.func.function_bytecode; if (b->has_debug) { line_num1 = find_line_num (ctx, b, sf->cur_pc - b->byte_code_buf - 1, &col_num1); atom_str = JS_ToCString (ctx, b->debug.filename); dbuf_printf (&dbuf, " (%s", atom_str ? atom_str : ""); JS_FreeCString (ctx, atom_str); if (line_num1 != 0) dbuf_printf (&dbuf, ":%d:%d", line_num1, col_num1); dbuf_putc (&dbuf, ')'); } } else { dbuf_printf (&dbuf, " (native)"); } } else { dbuf_printf (&dbuf, " (native)"); } dbuf_putc (&dbuf, '\n'); } dbuf_putc (&dbuf, '\0'); if (dbuf_error (&dbuf)) str = JS_NULL; else str = JS_NewString (ctx, (char *)dbuf.buf); dbuf_free (&dbuf); JS_SetPropertyInternal (ctx, err_ref.val, JS_KEY_stack, str); JS_PopGCRef (ctx, &err_ref); } /* Note: it is important that no exception is returned by this function */ BOOL is_backtrace_needed (JSContext *ctx, JSValue obj) { JSRecord *p; if (!JS_IsRecord (obj)) return FALSE; p = JS_VALUE_GET_OBJ (obj); if (REC_GET_CLASS_ID(p) != JS_CLASS_ERROR) return FALSE; /* Check if "stack" property already exists */ JSValue stack_key = MIST_TryNewImmediateASCII ("stack", 5); JSRecord *rec = (JSRecord *)p; int slot = rec_find_slot (rec, stack_key); if (slot > 0) return FALSE; return TRUE; } JSValue JS_NewError (JSContext *ctx) { return JS_NewObjectClass (ctx, JS_CLASS_ERROR); } JSValue JS_ThrowError2 (JSContext *ctx, JSErrorEnum error_num, const char *fmt, va_list ap, BOOL add_backtrace) { char buf[256]; JSValue ret; JSGCRef obj_ref; vsnprintf (buf, sizeof (buf), fmt, ap); JS_PushGCRef (ctx, &obj_ref); obj_ref.val = JS_NewObjectProtoClass (ctx, ctx->native_error_proto[error_num], JS_CLASS_ERROR); if (unlikely (JS_IsException (obj_ref.val))) { /* out of memory: throw JS_NULL to avoid recursing */ obj_ref.val = JS_NULL; } else { JSValue msg = JS_NewString (ctx, buf); JS_SetPropertyInternal (ctx, obj_ref.val, JS_KEY_message, msg); if (add_backtrace) { build_backtrace (ctx, obj_ref.val, NULL, 0, 0, 0); } } ret = JS_Throw (ctx, obj_ref.val); JS_PopGCRef (ctx, &obj_ref); return ret; } static JSValue JS_ThrowError (JSContext *ctx, JSErrorEnum error_num, const char *fmt, va_list ap) { JSStackFrame *sf; BOOL add_backtrace; /* the backtrace is added later if called from a bytecode function */ sf = ctx->current_stack_frame; add_backtrace = (!sf || (JS_GetFunctionBytecode (sf->cur_func) == NULL)); return JS_ThrowError2 (ctx, error_num, fmt, ap, add_backtrace); } JSValue __attribute__ ((format (printf, 2, 3))) JS_ThrowSyntaxError (JSContext *ctx, const char *fmt, ...) { JSValue val; va_list ap; va_start (ap, fmt); val = JS_ThrowError (ctx, JS_SYNTAX_ERROR, fmt, ap); va_end (ap); return val; } JSValue __attribute__ ((format (printf, 2, 3))) JS_ThrowTypeError (JSContext *ctx, const char *fmt, ...) { JSValue val; va_list ap; va_start (ap, fmt); val = JS_ThrowError (ctx, JS_TYPE_ERROR, fmt, ap); va_end (ap); return val; } JSValue __attribute__ ((format (printf, 2, 3))) JS_ThrowReferenceError (JSContext *ctx, const char *fmt, ...) { JSValue val; va_list ap; va_start (ap, fmt); val = JS_ThrowError (ctx, JS_REFERENCE_ERROR, fmt, ap); va_end (ap); return val; } JSValue __attribute__ ((format (printf, 2, 3))) JS_ThrowRangeError (JSContext *ctx, const char *fmt, ...) { JSValue val; va_list ap; va_start (ap, fmt); val = JS_ThrowError (ctx, JS_RANGE_ERROR, fmt, ap); va_end (ap); return val; } JSValue __attribute__ ((format (printf, 2, 3))) JS_ThrowInternalError (JSContext *ctx, const char *fmt, ...) { JSValue val; va_list ap; va_start (ap, fmt); val = JS_ThrowError (ctx, JS_INTERNAL_ERROR, fmt, ap); va_end (ap); return val; } JSValue JS_ThrowOutOfMemory (JSContext *ctx) { /* Simplified: no re-entry guard needed with copying GC */ JS_ThrowInternalError (ctx, "out of memory"); return JS_EXCEPTION; } JSValue JS_ThrowStackOverflow (JSContext *ctx) { return JS_ThrowInternalError (ctx, "stack overflow"); } static JSValue JS_ThrowTypeErrorNotAnObject (JSContext *ctx) { return JS_ThrowTypeError (ctx, "not an object"); } JSValue JS_ThrowReferenceErrorUninitialized (JSContext *ctx, JSValue name) { char buf[KEY_GET_STR_BUF_SIZE]; return JS_ThrowReferenceError ( ctx, "%s is not initialized", JS_IsNull (name) ? "lexical variable" : JS_KeyGetStr (ctx, buf, sizeof (buf), name)); } JSValue JS_ThrowReferenceErrorUninitialized2 (JSContext *ctx, JSFunctionBytecode *b, int idx, BOOL is_ref) { JSValue name = JS_NULL; if (is_ref) { name = b->closure_var[idx].var_name; } else { /* not present if the function is stripped and contains no eval() */ if (b->vardefs) name = b->vardefs[b->arg_count + idx].var_name; } return JS_ThrowReferenceErrorUninitialized (ctx, name); } static JSValue JS_ThrowTypeErrorInvalidClass (JSContext *ctx, int class_id) { const char *name = ctx->class_array[class_id].class_name; return JS_ThrowTypeError (ctx, "%s object expected", name ? name : "unknown"); } void JS_ThrowInterrupted (JSContext *ctx) { JS_ThrowInternalError (ctx, "interrupted"); JS_SetUncatchableException (ctx, TRUE); } /* Return an Object, JS_NULL or JS_EXCEPTION in case of exotic object. */ JSValue JS_GetPrototype (JSContext *ctx, JSValue obj) { JSValue val; if (JS_IsRecord (obj)) { JSRecord *p; p = JS_VALUE_GET_OBJ (obj); p = JS_OBJ_GET_PROTO (p); if (!p) val = JS_NULL; else val = JS_MKPTR (p); } else { /* Primitives have no prototype */ val = JS_NULL; } return val; } /* Get property from object using JSRecord-based lookup */ JSValue JS_GetProperty (JSContext *ctx, JSValue obj, JSValue prop) { if (JS_IsNull (obj)) return JS_NULL; if (JS_IsException (obj)) return JS_EXCEPTION; if (unlikely (!JS_IsRecord (obj))) { /* Primitives have no properties */ return JS_NULL; } /* All objects are JSRecords now */ JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (obj); return rec_get (ctx, rec, prop); } /* GC-SAFE: Collects keys to stack buffer before any allocation. Returns a JSValue array of text keys. */ JSValue JS_GetOwnPropertyNames (JSContext *ctx, JSValue obj) { uint32_t mask, count, i; if (!JS_IsRecord (obj)) { JS_ThrowTypeErrorNotAnObject (ctx); return JS_EXCEPTION; } /* Reading slots is GC-safe - no allocation */ JSRecord *rec = JS_VALUE_GET_OBJ (obj); mask = (uint32_t)objhdr_cap56 (rec->mist_hdr); /* Count text keys first */ count = 0; for (i = 1; i <= mask; i++) { if (JS_IsText (rec->slots[i].key)) count++; } if (count == 0) return JS_NewArrayLen (ctx, 0); /* Collect keys into stack buffer (JSValues are just uint64_t) */ JSValue *keys = alloca (count * sizeof (JSValue)); uint32_t idx = 0; for (i = 1; i <= mask; i++) { JSValue k = rec->slots[i].key; if (JS_IsText (k)) keys[idx++] = k; } /* Now allocate and fill - GC point, but keys are on stack */ JSValue arr = JS_NewArrayLen (ctx, count); if (JS_IsException (arr)) return JS_EXCEPTION; for (i = 0; i < count; i++) { JS_SetPropertyUint32 (ctx, arr, i, keys[i]); } return arr; } /* Return -1 if exception, FALSE if the property does not exist, TRUE if it exists. If TRUE is returned, the property descriptor 'desc' is filled present. Now uses JSRecord-based lookup. */ int JS_GetOwnPropertyInternal (JSContext *ctx, JSValue *desc, JSRecord *p, JSValue prop) { JSRecord *rec = (JSRecord *)p; int slot = rec_find_slot (rec, prop); if (slot > 0) { if (desc) *desc = rec->slots[slot].val; return TRUE; } return FALSE; } int JS_GetOwnProperty (JSContext *ctx, JSValue *desc, JSValue obj, JSValue prop) { if (!JS_IsRecord (obj)) { JS_ThrowTypeErrorNotAnObject (ctx); return -1; } return JS_GetOwnPropertyInternal (ctx, desc, JS_VALUE_GET_OBJ (obj), prop); } /* GC-SAFE: Only calls rec_find_slot and reads prototype pointers. return -1 if exception otherwise TRUE or FALSE */ int JS_HasProperty (JSContext *ctx, JSValue obj, JSValue prop) { JSRecord *p; int ret; if (unlikely (!JS_IsRecord (obj))) return FALSE; p = JS_VALUE_GET_OBJ (obj); for (;;) { ret = JS_GetOwnPropertyInternal (ctx, NULL, p, prop); if (ret != 0) return ret; p = p->proto; /* Direct pointer chase is safe - no allocation */ if (!p) break; } return FALSE; } static uint32_t js_string_get_length (JSValue val) { if (JS_IsPtr (val)) { void *ptr = JS_VALUE_GET_PTR (val); /* Check objhdr_t at offset 8 for type */ objhdr_t hdr = *(objhdr_t *)ptr; if (objhdr_type (hdr) == OBJ_TEXT) { /* String (JSText or JSText) */ return (uint32_t)objhdr_cap56 (hdr); } return 0; } else if (MIST_IsImmediateASCII (val)) { return MIST_GetImmediateASCIILen (val); } else { return 0; } } JSValue JS_GetPropertyValue (JSContext *ctx, JSValue this_obj, JSValue prop) { JSValue ret; uint32_t prop_tag = JS_VALUE_GET_TAG (prop); if (JS_IsNull (this_obj)) { return JS_NULL; } if (prop_tag == JS_TAG_INT) { int idx = JS_VALUE_GET_INT (prop); return JS_GetPropertyNumber (ctx, this_obj, idx); } if (prop_tag == JS_TAG_SHORT_FLOAT) { double d = JS_VALUE_GET_FLOAT64 (prop); uint32_t idx = (uint32_t)d; if (d != (double)idx) return JS_NULL; return JS_GetPropertyNumber (ctx, this_obj, idx); } /* Check for string property (immediate or heap) */ if (JS_IsText (prop)) { /* Intrinsic arrays don't support string keys */ if (JS_IsArray (this_obj)) { return JS_NULL; } /* Create an interned key from the string */ JSValue key = js_key_from_string (ctx, prop); ret = JS_GetProperty (ctx, this_obj, key); /* key is interned or immediate, no need to free */ return ret; } /* Handle object keys directly via objkey map */ if (JS_IsRecord (prop)) { /* Intrinsic arrays don't support object keys */ if (!JS_IsRecord (this_obj)) { return JS_NULL; } JSRecord *rec = JS_VALUE_GET_RECORD (this_obj); JSValue val = rec_get (ctx, rec, prop); return val; } /* Unknown type -> null */ return JS_NULL; } JSValue JS_SetPropertyNumber (JSContext *js, JSValue obj, int idx, JSValue val) { if (!JS_IsArray (obj)) { return JS_ThrowInternalError (js, "cannot set with a number on a non array"); } if (idx < 0) { return JS_ThrowRangeError (js, "array index out of bounds"); } /* Root obj since js_intrinsic_array_set may trigger GC during array grow */ JSGCRef obj_ref; JS_PushGCRef (js, &obj_ref); obj_ref.val = obj; if (js_intrinsic_array_set (js, &obj_ref.val, (word_t)idx, val) < 0) { JS_PopGCRef (js, &obj_ref); return JS_EXCEPTION; } JS_PopGCRef (js, &obj_ref); return val; } JSValue JS_GetPropertyNumber (JSContext *js, JSValue obj, int idx) { if (JS_IsArray (obj)) { JSArray *a = JS_VALUE_GET_ARRAY (obj); int len = a->len; if (idx < 0 || idx >= len) { return JS_NULL; } return a->values[idx]; } if (JS_IsText (obj)) { uint32_t len = js_string_get_length (obj); if (idx < 0 || (uint32_t)idx >= len) { return JS_NULL; } return js_sub_string (js, JS_VALUE_GET_STRING (obj), idx, idx + 1); } return JS_NULL; } JSValue JS_GetPropertyUint32 (JSContext *ctx, JSValue this_obj, uint32_t idx) { return JS_GetPropertyNumber (ctx, this_obj, idx); } static JSValue JS_GetPropertyInt64 (JSContext *ctx, JSValue obj, int64_t idx) { return JS_GetPropertyNumber (ctx, obj, idx); } JSValue JS_GetPropertyStr (JSContext *ctx, JSValue this_obj, const char *prop) { if (JS_VALUE_GET_TAG (this_obj) != JS_TAG_PTR) return JS_NULL; size_t len = strlen (prop); JSValue key; JSValue ret; JSGCRef obj_ref; JS_PushGCRef (ctx, &obj_ref); obj_ref.val = this_obj; /* Try immediate ASCII first */ if (len <= MIST_ASCII_MAX_LEN) { key = MIST_TryNewImmediateASCII (prop, len); if (JS_IsNull (key)) { key = JS_NewStringLen (ctx, prop, len); } } else { key = JS_NewStringLen (ctx, prop, len); } if (JS_IsException (key)) { JS_PopGCRef (ctx, &obj_ref); return JS_EXCEPTION; } ret = JS_GetProperty (ctx, obj_ref.val, key); JS_PopGCRef (ctx, &obj_ref); return ret; } /* JS_Invoke - invoke a method on an object by name */ JSValue JS_Invoke (JSContext *ctx, JSValue this_val, JSValue method, int argc, JSValue *argv) { JSGCRef this_ref; JS_PushGCRef (ctx, &this_ref); this_ref.val = this_val; JSValue func = JS_GetProperty (ctx, this_ref.val, method); if (JS_IsException (func)) { JS_PopGCRef (ctx, &this_ref); return JS_EXCEPTION; } if (!JS_IsFunction (func)) { JS_PopGCRef (ctx, &this_ref); return JS_NULL; /* Method not found or not callable */ } JSValue ret = JS_Call (ctx, func, this_ref.val, argc, argv); JS_PopGCRef (ctx, &this_ref); return ret; } /* GC-SAFE: May trigger GC if record needs to resize */ int JS_SetProperty (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val) { if (!JS_IsRecord (this_obj)) { if (JS_IsNull (this_obj)) { JS_ThrowTypeError (ctx, "cannot set property of null"); } else { JS_ThrowTypeError (ctx, "cannot set property on a primitive"); } return -1; } /* All objects are now records - use record set */ JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (this_obj); if (unlikely (obj_is_stone (rec))) { JS_ThrowTypeError (ctx, "object is stone"); return -1; } /* Use a local copy that rec_set_own can update if resize happens */ JSValue obj = this_obj; return rec_set_own (ctx, &obj, prop, val); } int JS_SetPropertyUint32 (JSContext *ctx, JSValue this_obj, uint32_t idx, JSValue val) { JSValue ret = JS_SetPropertyNumber (ctx, (JSValue)this_obj, (int)idx, val); if (JS_IsException (ret)) return -1; return 0; } int JS_SetPropertyInt64 (JSContext *ctx, JSValue this_obj, int64_t idx, JSValue val) { if (idx < INT32_MIN || idx > INT32_MAX) { JS_ThrowRangeError (ctx, "array index out of bounds"); return -1; } JSValue ret = JS_SetPropertyNumber (ctx, (JSValue)this_obj, (int)idx, val); if (JS_IsException (ret)) return -1; return 0; } /* GC-SAFE: Protects this_obj and val in case key creation triggers GC */ int JS_SetPropertyStr (JSContext *ctx, JSValue this_obj, const char *prop, JSValue val) { /* Protect this_obj and val in case key creation triggers GC */ JSGCRef obj_ref, val_ref; JS_AddGCRef (ctx, &obj_ref); JS_AddGCRef (ctx, &val_ref); obj_ref.val = this_obj; val_ref.val = val; /* Create JSValue key from string - use js_key_new for interned stone keys */ JSValue key = js_key_new (ctx, prop); if (JS_IsException (key)) { JS_DeleteGCRef (ctx, &val_ref); JS_DeleteGCRef (ctx, &obj_ref); return -1; } int ret = JS_SetProperty (ctx, obj_ref.val, key, val_ref.val); JS_DeleteGCRef (ctx, &val_ref); JS_DeleteGCRef (ctx, &obj_ref); return ret; } /* Set property with JSValue prop/key - handles int, string, object keys */ int JS_SetPropertyValue (JSContext *ctx, JSValue this_obj, JSValue prop, JSValue val) { uint32_t prop_tag = JS_VALUE_GET_TAG (prop); if (prop_tag == JS_TAG_INT) { int idx = JS_VALUE_GET_INT (prop); JSValue ret = JS_SetPropertyNumber (ctx, this_obj, idx, val); if (JS_IsException (ret)) return -1; return 0; } if (JS_IsText (prop)) { JSValue key = js_key_from_string (ctx, prop); return JS_SetProperty (ctx, this_obj, key, val); } if (JS_IsRecord (prop)) { return JS_SetProperty (ctx, this_obj, prop, val); } return -1; } /* Property access with JSValue key - supports object keys directly */ JSValue JS_GetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key) { if (JS_IsRecord (key)) { if (!JS_IsRecord (this_obj)) return JS_NULL; JSRecord *rec = JS_VALUE_GET_RECORD (this_obj); return rec_get (ctx, rec, key); } /* For string keys, create an interned key and use JS_GetProperty */ if (JS_IsText (key)) { JSValue prop_key = js_key_from_string (ctx, key); return JS_GetProperty (ctx, this_obj, prop_key); } /* For other types, try to use the value directly as a key */ return JS_GetProperty (ctx, this_obj, key); } /* CAUTION: rec_set_own is NOT GC-safe if rec_resize is implemented. Currently safe because rec_resize always fails. When resize is implemented, rec pointer may become stale. */ int JS_SetPropertyKey (JSContext *ctx, JSValue this_obj, JSValue key, JSValue val) { if (JS_IsRecord (key)) { if (!JS_IsRecord (this_obj)) { JS_ThrowTypeError (ctx, "cannot set property on this value"); return -1; } JSRecord *rec = JS_VALUE_GET_RECORD (this_obj); if (obj_is_stone (rec)) { JS_ThrowTypeError (ctx, "cannot modify frozen object"); return -1; } return rec_set_own (ctx, rec, key, val); } /* For string keys, create an interned key */ if (JS_IsText (key)) { JSValue prop_key = js_key_from_string (ctx, key); return JS_SetPropertyInternal (ctx, this_obj, prop_key, val); } /* For other types, use the key directly */ return JS_SetPropertyInternal (ctx, this_obj, key, val); } /* GC-SAFE for record keys (no allocations). String keys call js_key_from_string then JS_HasProperty which re-chases. */ int JS_HasPropertyKey (JSContext *ctx, JSValue obj, JSValue key) { if (JS_IsRecord (key)) { if (!JS_IsRecord (obj)) return FALSE; JSRecord *rec = JS_VALUE_GET_RECORD (obj); /* Check own and prototype chain */ while (rec) { if (rec_find_slot (rec, key) > 0) return TRUE; rec = rec->proto; } return FALSE; } /* For string keys, create an interned key */ if (JS_IsText (key)) { JSValue prop_key = js_key_from_string (ctx, key); return JS_HasProperty (ctx, obj, prop_key); } /* For other types, use directly */ return JS_HasProperty (ctx, obj, key); } /* GC-SAFE: Only calls rec_find_slot and modifies slots directly */ int JS_DeletePropertyKey (JSContext *ctx, JSValue obj, JSValue key) { if (JS_IsRecord (key)) { if (!JS_IsRecord (obj)) return FALSE; JSRecord *rec = JS_VALUE_GET_RECORD (obj); if (obj_is_stone (rec)) { JS_ThrowTypeError (ctx, "cannot modify frozen object"); return -1; } int slot = rec_find_slot (rec, key); if (slot <= 0) return FALSE; /* Delete by marking as tombstone */ rec->slots[slot].key = JS_EXCEPTION; /* tombstone */ rec->slots[slot].val = JS_NULL; rec->len--; return TRUE; } /* For string keys, create an interned key */ if (JS_IsText (key)) { JSValue prop_key = js_key_from_string (ctx, key); return JS_DeleteProperty (ctx, obj, prop_key); } /* For other types, use directly */ return JS_DeleteProperty (ctx, obj, key); } /* compute the property flags. For each flag: (JS_PROP_HAS_x forces it, otherwise def_flags is used) Note: makes assumption about the bit pattern of the flags */ /* return TRUE if 'obj' has a non empty 'name' string */ static BOOL js_object_has_name (JSContext *ctx, JSValue obj) { if (JS_VALUE_GET_TAG (obj) != JS_TAG_PTR) return FALSE; JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (obj); JSValue name_key = MIST_TryNewImmediateASCII ("name", 4); int slot = rec_find_slot (rec, name_key); if (slot <= 0) return FALSE; JSValue val = rec->slots[slot].val; if (!JS_IsText (val)) return TRUE; /* has name but it's not a string = truthy */ return (js_string_value_len (val) != 0); } int JS_DefineObjectName (JSContext *ctx, JSValue obj, JSValue name) { if (!JS_IsNull (name) && JS_IsObject (obj) && !js_object_has_name (ctx, obj)) { JSValue name_key = MIST_TryNewImmediateASCII ("name", 4); if (JS_SetPropertyInternal (ctx, obj, name_key, name) < 0) return -1; } return 0; } int JS_DefineObjectNameComputed (JSContext *ctx, JSValue obj, JSValue str) { if (JS_IsObject (obj) && !js_object_has_name (ctx, obj)) { JSValue name_key = MIST_TryNewImmediateASCII ("name", 4); if (JS_SetPropertyInternal (ctx, obj, name_key, str) < 0) return -1; } return 0; } JSValue JS_ThrowSyntaxErrorVarRedeclaration (JSContext *ctx, JSValue prop) { char buf[KEY_GET_STR_BUF_SIZE]; return JS_ThrowSyntaxError (ctx, "redeclaration of '%s'", JS_KeyGetStr (ctx, buf, sizeof (buf), prop)); } int JS_DeleteProperty (JSContext *ctx, JSValue obj, JSValue prop) { JSRecord *rec; int slot; /* Arrays do not support property deletion */ if (JS_IsArray (obj)) { JS_ThrowTypeError (ctx, "cannot delete array element"); return -1; } if (JS_VALUE_GET_TAG (obj) != JS_TAG_PTR) { JS_ThrowTypeErrorNotAnObject (ctx); return -1; } rec = (JSRecord *)JS_VALUE_GET_OBJ (obj); if (obj_is_stone (rec)) { JS_ThrowTypeError (ctx, "cannot delete property of stone object"); return -1; } slot = rec_find_slot (rec, prop); if (slot > 0) { /* Delete by marking as tombstone */ rec->slots[slot].key = JS_EXCEPTION; /* tombstone */ rec->slots[slot].val = JS_NULL; rec->len--; /* tombs tracking removed - not needed with copying GC */ return TRUE; } return TRUE; /* property not found = deletion succeeded */ } BOOL JS_IsCFunction (JSValue val, JSCFunction *func, int magic) { JSFunction *f; if (!JS_IsFunction (val)) return FALSE; f = JS_VALUE_GET_FUNCTION (val); if (f->kind == JS_FUNC_KIND_C) return (f->u.cfunc.c_function.generic == func && f->u.cfunc.magic == magic); else return FALSE; } BOOL JS_IsError (JSContext *ctx, JSValue val) { JSRecord *p; if (JS_VALUE_GET_TAG (val) != JS_TAG_PTR) return FALSE; p = JS_VALUE_GET_OBJ (val); return (REC_GET_CLASS_ID(p) == JS_CLASS_ERROR); } /* must be called after JS_Throw() - stubbed out, uncatchable not implemented */ void JS_SetUncatchableException (JSContext *ctx, JS_BOOL flag) { (void)ctx; (void)flag; /* uncatchable exception flag not supported with copying GC */ } void JS_SetOpaque (JSValue obj, void *opaque) { JSRecord *p; if (JS_VALUE_GET_TAG (obj) == JS_TAG_PTR) { p = JS_VALUE_GET_OBJ (obj); REC_SET_OPAQUE(p, opaque); } } /* return NULL if not an object of class class_id */ void *JS_GetOpaque (JSValue obj, JSClassID class_id) { JSRecord *p; if (JS_VALUE_GET_TAG (obj) != JS_TAG_PTR) return NULL; p = JS_VALUE_GET_OBJ (obj); if (REC_GET_CLASS_ID(p) != class_id) return NULL; return REC_GET_OPAQUE(p); } void *JS_GetOpaque2 (JSContext *ctx, JSValue obj, JSClassID class_id) { void *p = JS_GetOpaque (obj, class_id); if (unlikely (!p)) { JS_ThrowTypeErrorInvalidClass (ctx, class_id); } return p; } void *JS_GetAnyOpaque (JSValue obj, JSClassID *class_id) { JSRecord *p; if (!JS_IsRecord (obj)) { *class_id = 0; return NULL; } p = JS_VALUE_GET_OBJ (obj); *class_id = REC_GET_CLASS_ID(p); return REC_GET_OPAQUE(p); } int JS_ToBool (JSContext *ctx, JSValue val) { uint32_t tag = JS_VALUE_GET_TAG (val); /* Check for pointer types first (new tagging system) */ if (JS_IsPtr (val)) { void *ptr = JS_VALUE_GET_PTR (val); objhdr_t hdr = *(objhdr_t *)ptr; if (objhdr_type (hdr) == OBJ_TEXT) { /* String (JSText or JSText) - truthy if non-empty */ BOOL ret = objhdr_cap56 (hdr) != 0; return ret; } /* Objects (record, array, function) are truthy */ return 1; } switch (tag) { case JS_TAG_INT: return JS_VALUE_GET_INT (val) != 0; case JS_TAG_BOOL: return JS_VALUE_GET_BOOL (val); case JS_TAG_NULL: return 0; case JS_TAG_EXCEPTION: return -1; case JS_TAG_STRING_IMM: { BOOL ret = MIST_GetImmediateASCIILen (val) != 0; return ret; } default: if (JS_TAG_IS_FLOAT64 (tag)) { double d = JS_VALUE_GET_FLOAT64 (val); return d != 0; /* NaN impossible in short floats */ } else { return TRUE; } } } /* return an exception in case of memory error. Return JS_NAN if invalid syntax */ /* XXX: directly use js_atod() */ JSValue js_atof (JSContext *ctx, const char *str, const char **pp, int radix, int flags) { const char *p, *p_start; int sep, is_neg; BOOL is_float; int atod_type = flags & ATOD_TYPE_MASK; char buf1[64], *buf; int i, j, len; BOOL buf_allocated = FALSE; JSValue val; JSATODTempMem atod_mem; /* optional separator between digits */ sep = (flags & ATOD_ACCEPT_UNDERSCORES) ? '_' : 256; p = str; p_start = p; is_neg = 0; if (p[0] == '+') { p++; p_start++; if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) goto no_radix_prefix; } else if (p[0] == '-') { p++; p_start++; is_neg = 1; if (!(flags & ATOD_ACCEPT_PREFIX_AFTER_SIGN)) goto no_radix_prefix; } if (p[0] == '0') { if ((p[1] == 'x' || p[1] == 'X') && (radix == 0 || radix == 16)) { p += 2; radix = 16; } else if ((p[1] == 'o' || p[1] == 'O') && radix == 0 && (flags & ATOD_ACCEPT_BIN_OCT)) { p += 2; radix = 8; } else if ((p[1] == 'b' || p[1] == 'B') && radix == 0 && (flags & ATOD_ACCEPT_BIN_OCT)) { p += 2; radix = 2; } else if ((p[1] >= '0' && p[1] <= '9') && radix == 0 && (flags & ATOD_ACCEPT_LEGACY_OCTAL)) { int i; sep = 256; for (i = 1; (p[i] >= '0' && p[i] <= '7'); i++) continue; if (p[i] == '8' || p[i] == '9') goto no_prefix; p += 1; radix = 8; } else { goto no_prefix; } /* there must be a digit after the prefix */ if (to_digit ((uint8_t)*p) >= radix) goto fail; no_prefix:; } else { no_radix_prefix: if (!(flags & ATOD_INT_ONLY) && (atod_type == ATOD_TYPE_FLOAT64) && strstart (p, "Infinity", &p)) { double d = 1.0 / 0.0; if (is_neg) d = -d; val = JS_NewFloat64 (ctx, d); goto done; } } if (radix == 0) radix = 10; is_float = FALSE; p_start = p; while (to_digit ((uint8_t)*p) < radix || (*p == sep && (radix != 10 || p != p_start + 1 || p[-1] != '0') && to_digit ((uint8_t)p[1]) < radix)) { p++; } if (!(flags & ATOD_INT_ONLY)) { if (*p == '.' && (p > p_start || to_digit ((uint8_t)p[1]) < radix)) { is_float = TRUE; p++; if (*p == sep) goto fail; while (to_digit ((uint8_t)*p) < radix || (*p == sep && to_digit ((uint8_t)p[1]) < radix)) p++; } if (p > p_start && (((*p == 'e' || *p == 'E') && radix == 10) || ((*p == 'p' || *p == 'P') && (radix == 2 || radix == 8 || radix == 16)))) { const char *p1 = p + 1; is_float = TRUE; if (*p1 == '+') { p1++; } else if (*p1 == '-') { p1++; } if (is_digit ((uint8_t)*p1)) { p = p1 + 1; while (is_digit ((uint8_t)*p) || (*p == sep && is_digit ((uint8_t)p[1]))) p++; } } } if (p == p_start) goto fail; buf = buf1; buf_allocated = FALSE; len = p - p_start; if (unlikely ((len + 2) > sizeof (buf1))) { buf = js_malloc_rt (len + 2); /* no exception raised */ if (!buf) goto mem_error; buf_allocated = TRUE; } /* remove the separators and the radix prefixes */ j = 0; if (is_neg) buf[j++] = '-'; for (i = 0; i < len; i++) { if (p_start[i] != '_') buf[j++] = p_start[i]; } buf[j] = '\0'; if (flags & ATOD_ACCEPT_SUFFIX) { if (*p == 'n') { p++; atod_type = ATOD_TYPE_BIG_INT; } else { if (is_float && radix != 10) goto fail; } } else { if (atod_type == ATOD_TYPE_FLOAT64) { if (is_float && radix != 10) goto fail; } } switch (atod_type) { case ATOD_TYPE_FLOAT64: { double d; d = js_atod (buf, NULL, radix, is_float ? 0 : JS_ATOD_INT_ONLY, &atod_mem); /* return int or float64 */ val = JS_NewFloat64 (ctx, d); } break; default: abort (); } done: if (buf_allocated) js_free_rt (buf); if (pp) *pp = p; return val; fail: val = JS_NAN; goto done; mem_error: val = JS_ThrowOutOfMemory (ctx); goto done; } JSValue JS_ToNumber (JSContext *ctx, JSValue val) { uint32_t tag; JSValue ret; /* Handle pointer types (new tagging system) */ if (JS_IsPtr (val)) { void *ptr = JS_VALUE_GET_PTR (val); objhdr_t hdr = *(objhdr_t *)ptr; if (objhdr_type (hdr) == OBJ_TEXT) { /* String */ return JS_ThrowTypeError (ctx, "cannot convert text to a number"); } /* Objects */ return JS_ThrowTypeError (ctx, "cannot convert object to number"); } tag = JS_VALUE_GET_NORM_TAG (val); switch (tag) { case JS_TAG_SHORT_FLOAT: case JS_TAG_INT: case JS_TAG_EXCEPTION: ret = val; break; case JS_TAG_BOOL: case JS_TAG_NULL: ret = JS_NewInt32 (ctx, JS_VALUE_GET_INT (val)); break; case JS_TAG_STRING_IMM: return JS_ThrowTypeError (ctx, "cannot convert text to a number"); default: ret = JS_NAN; break; } return ret; } static __exception int __JS_ToFloat64 (JSContext *ctx, double *pres, JSValue val) { double d; uint32_t tag; val = JS_ToNumber (ctx, val); if (JS_IsException (val)) goto fail; tag = JS_VALUE_GET_NORM_TAG (val); switch (tag) { case JS_TAG_INT: d = JS_VALUE_GET_INT (val); break; case JS_TAG_FLOAT64: d = JS_VALUE_GET_FLOAT64 (val); break; default: abort (); } *pres = d; return 0; fail: *pres = NAN; return -1; } int JS_ToFloat64 (JSContext *ctx, double *pres, JSValue val) { uint32_t tag; tag = JS_VALUE_GET_TAG (val); if (tag == JS_TAG_INT) { *pres = JS_VALUE_GET_INT (val); return 0; } else if (JS_TAG_IS_FLOAT64 (tag)) { *pres = JS_VALUE_GET_FLOAT64 (val); return 0; } else { return __JS_ToFloat64 (ctx, pres, val); } } /* Note: the integer value is satured to 32 bits */ int JS_ToInt32Sat (JSContext *ctx, int *pres, JSValue val) { uint32_t tag; int ret; redo: tag = JS_VALUE_GET_NORM_TAG (val); switch (tag) { case JS_TAG_INT: case JS_TAG_BOOL: case JS_TAG_NULL: ret = JS_VALUE_GET_INT (val); break; case JS_TAG_EXCEPTION: *pres = 0; return -1; case JS_TAG_FLOAT64: { double d = JS_VALUE_GET_FLOAT64 (val); /* NaN impossible in short floats */ if (d < INT32_MIN) ret = INT32_MIN; else if (d > INT32_MAX) ret = INT32_MAX; else ret = (int)d; } break; default: val = JS_ToNumber (ctx, val); if (JS_IsException (val)) { *pres = 0; return -1; } goto redo; } *pres = ret; return 0; } int JS_ToInt32Clamp (JSContext *ctx, int *pres, JSValue val, int min, int max, int min_offset) { int res = JS_ToInt32Sat (ctx, pres, val); if (res == 0) { if (*pres < min) { *pres += min_offset; if (*pres < min) *pres = min; } else { if (*pres > max) *pres = max; } } return res; } int JS_ToInt64Sat (JSContext *ctx, int64_t *pres, JSValue val) { uint32_t tag; redo: tag = JS_VALUE_GET_NORM_TAG (val); switch (tag) { case JS_TAG_INT: case JS_TAG_BOOL: case JS_TAG_NULL: *pres = JS_VALUE_GET_INT (val); return 0; case JS_TAG_EXCEPTION: *pres = 0; return -1; case JS_TAG_FLOAT64: { double d = JS_VALUE_GET_FLOAT64 (val); /* NaN impossible in short floats */ if (d < INT64_MIN) *pres = INT64_MIN; else if (d >= 0x1p63) /* must use INT64_MAX + 1 because INT64_MAX cannot be exactly represented as a double */ *pres = INT64_MAX; else *pres = (int64_t)d; } return 0; default: val = JS_ToNumber (ctx, val); if (JS_IsException (val)) { *pres = 0; return -1; } goto redo; } } int JS_ToInt64Clamp (JSContext *ctx, int64_t *pres, JSValue val, int64_t min, int64_t max, int64_t neg_offset) { int res = JS_ToInt64Sat (ctx, pres, val); if (res == 0) { if (*pres < 0) *pres += neg_offset; if (*pres < min) *pres = min; else if (*pres > max) *pres = max; } return res; } /* Same as JS_ToInt32() but with a 64 bit result. Return (<0, 0) in case of exception */ int JS_ToInt64 (JSContext *ctx, int64_t *pres, JSValue val) { uint32_t tag; int64_t ret; redo: tag = JS_VALUE_GET_NORM_TAG (val); switch (tag) { case JS_TAG_INT: case JS_TAG_BOOL: case JS_TAG_NULL: ret = JS_VALUE_GET_INT (val); break; case JS_TAG_FLOAT64: { JSFloat64Union u; double d; int e; d = JS_VALUE_GET_FLOAT64 (val); u.d = d; /* we avoid doing fmod(x, 2^64) */ e = (u.u64 >> 52) & 0x7ff; if (likely (e <= (1023 + 62))) { /* fast case */ ret = (int64_t)d; } else if (e <= (1023 + 62 + 53)) { uint64_t v; /* remainder modulo 2^64 */ v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); ret = v << ((e - 1023) - 52); /* take the sign into account */ if (u.u64 >> 63) ret = -ret; } else { ret = 0; /* also handles NaN and +inf */ } } break; default: val = JS_ToNumber (ctx, val); if (JS_IsException (val)) { *pres = 0; return -1; } goto redo; } *pres = ret; return 0; } /* return (<0, 0) in case of exception */ int JS_ToInt32 (JSContext *ctx, int32_t *pres, JSValue val) { uint32_t tag; int32_t ret; redo: tag = JS_VALUE_GET_NORM_TAG (val); switch (tag) { case JS_TAG_INT: case JS_TAG_BOOL: case JS_TAG_NULL: ret = JS_VALUE_GET_INT (val); break; case JS_TAG_FLOAT64: { JSFloat64Union u; double d; int e; d = JS_VALUE_GET_FLOAT64 (val); u.d = d; /* we avoid doing fmod(x, 2^32) */ e = (u.u64 >> 52) & 0x7ff; if (likely (e <= (1023 + 30))) { /* fast case */ ret = (int32_t)d; } else if (e <= (1023 + 30 + 53)) { uint64_t v; /* remainder modulo 2^32 */ v = (u.u64 & (((uint64_t)1 << 52) - 1)) | ((uint64_t)1 << 52); v = v << ((e - 1023) - 52 + 32); ret = v >> 32; /* take the sign into account */ if (u.u64 >> 63) ret = -ret; } else { ret = 0; /* also handles NaN and +inf */ } } break; default: *pres = 0; return -1; val = JS_ToNumber (ctx, val); if (JS_IsException (val)) { *pres = 0; return -1; } goto redo; } *pres = ret; return 0; } #define MAX_SAFE_INTEGER (((int64_t)1 << 53) - 1) /* convert a value to a length between 0 and MAX_SAFE_INTEGER. return -1 for exception */ static __exception int JS_ToLength (JSContext *ctx, int64_t *plen, JSValue val) { int res = JS_ToInt64Clamp (ctx, plen, val, 0, MAX_SAFE_INTEGER, 0); return res; } static JSValue js_dtoa2 (JSContext *ctx, double d, int radix, int n_digits, int flags) { char static_buf[128], *buf, *tmp_buf; int len, len_max; JSValue res; JSDTOATempMem dtoa_mem; len_max = js_dtoa_max_len (d, radix, n_digits, flags); /* longer buffer may be used if radix != 10 */ if (len_max > sizeof (static_buf) - 1) { tmp_buf = js_malloc (ctx, len_max + 1); if (!tmp_buf) return JS_EXCEPTION; buf = tmp_buf; } else { tmp_buf = NULL; buf = static_buf; } len = js_dtoa (buf, d, radix, n_digits, flags, &dtoa_mem); res = js_new_string8_len (ctx, buf, len); js_free (ctx, tmp_buf); return res; } JSValue JS_ToString (JSContext *ctx, JSValue val) { uint32_t tag; char buf[32]; /* Handle pointer types (new tagging system) */ if (JS_IsPtr (val)) { void *ptr = JS_VALUE_GET_PTR (val); objhdr_t hdr = *(objhdr_t *)ptr; uint8_t mist_type = objhdr_type (hdr); if (mist_type == OBJ_TEXT) { /* String - return as-is */ return val; } /* Objects (record, array, function) */ return JS_KEY_true; } tag = JS_VALUE_GET_NORM_TAG (val); switch (tag) { case JS_TAG_STRING_IMM: return val; case JS_TAG_INT: { size_t len; len = i32toa (buf, JS_VALUE_GET_INT (val)); return js_new_string8_len (ctx, buf, len); } break; case JS_TAG_BOOL: return JS_VALUE_GET_BOOL (val) ? JS_KEY_true : JS_KEY_false; case JS_TAG_NULL: return JS_KEY_null; case JS_TAG_EXCEPTION: return JS_EXCEPTION; case JS_TAG_SHORT_FLOAT: return js_dtoa2 (ctx, JS_VALUE_GET_FLOAT64 (val), 10, 0, JS_DTOA_FORMAT_FREE); default: return js_new_string8 (ctx, "[unsupported type]"); } } static JSValue JS_ToStringCheckObject (JSContext *ctx, JSValue val) { uint32_t tag = JS_VALUE_GET_TAG (val); if (tag == JS_TAG_NULL) return JS_ThrowTypeError (ctx, "null is forbidden"); return JS_ToString (ctx, val); } static JSValue JS_ToQuotedString (JSContext *ctx, JSValue val1) { JSValue val; int i, len; uint32_t c; JSText *b; char buf[16]; val = JS_ToStringCheckObject (ctx, val1); if (JS_IsException (val)) return val; /* Use js_string_value_len to handle both immediate and heap strings */ len = js_string_value_len (val); b = pretext_init (ctx, len + 2); if (!b) goto fail; b = pretext_putc (ctx, b, '\"'); if (!b) goto fail; for (i = 0; i < len; i++) { c = js_string_value_get (val, i); switch (c) { case '\t': c = 't'; goto quote; case '\r': c = 'r'; goto quote; case '\n': c = 'n'; goto quote; case '\b': c = 'b'; goto quote; case '\f': c = 'f'; goto quote; case '\"': case '\\': quote: b = pretext_putc (ctx, b, '\\'); if (!b) goto fail; b = pretext_putc (ctx, b, c); if (!b) goto fail; break; default: if (c < 32 || is_surrogate (c)) { snprintf (buf, sizeof (buf), "\\u%04x", c); b = pretext_puts8 (ctx, b, buf); if (!b) goto fail; } else { b = pretext_putc (ctx, b, c); if (!b) goto fail; } break; } } b = pretext_putc (ctx, b, '\"'); if (!b) goto fail; return pretext_end (ctx, b); fail: return JS_EXCEPTION; } #define JS_PRINT_MAX_DEPTH 8 typedef struct { JSRuntime *rt; JSContext *ctx; /* may be NULL */ JSPrintValueOptions options; JSPrintValueWrite *write_func; void *write_opaque; int level; JSRecord *print_stack[JS_PRINT_MAX_DEPTH]; /* level values */ } JSPrintValueState; static void js_print_value (JSPrintValueState *s, JSValue val); static void js_putc (JSPrintValueState *s, char c) { s->write_func (s->write_opaque, &c, 1); } static void js_puts (JSPrintValueState *s, const char *str) { s->write_func (s->write_opaque, str, strlen (str)); } static void __attribute__ ((format (printf, 2, 3))) js_printf (JSPrintValueState *s, const char *fmt, ...) { va_list ap; char buf[256]; va_start (ap, fmt); vsnprintf (buf, sizeof (buf), fmt, ap); va_end (ap); s->write_func (s->write_opaque, buf, strlen (buf)); } static void js_print_float64 (JSPrintValueState *s, double d) { JSDTOATempMem dtoa_mem; char buf[32]; int len; len = js_dtoa (buf, d, 10, 0, JS_DTOA_FORMAT_FREE | JS_DTOA_MINUS_ZERO, &dtoa_mem); s->write_func (s->write_opaque, buf, len); } static void js_dump_char (JSPrintValueState *s, int c, int sep) { if (c == sep || c == '\\') { js_putc (s, '\\'); js_putc (s, c); } else if (c >= ' ' && c <= 126) { js_putc (s, c); } else if (c == '\n') { js_putc (s, '\\'); js_putc (s, 'n'); } else { js_printf (s, "\\u%04x", c); } } static void js_print_string_rec (JSPrintValueState *s, JSValue val, int sep, uint32_t pos) { if (MIST_IsImmediateASCII (val)) { /* Immediate ASCII string */ int len = MIST_GetImmediateASCIILen (val); if (pos < s->options.max_string_length) { uint32_t i, l; l = min_uint32 (len, s->options.max_string_length - pos); for (i = 0; i < l; i++) { js_dump_char (s, MIST_GetImmediateASCIIChar (val, i), sep); } } } else if (JS_IsPtr (val) && objhdr_type (*(objhdr_t *)JS_VALUE_GET_PTR (val)) == OBJ_TEXT) { /* Heap text (JSText) */ JSText *p = (JSText *)JS_VALUE_GET_PTR (val); uint32_t i, len; if (pos < s->options.max_string_length) { len = min_uint32 ((uint32_t)JSText_len (p), s->options.max_string_length - pos); for (i = 0; i < len; i++) { js_dump_char (s, string_get (p, i), sep); } } } else { js_printf (s, "", (int)JS_VALUE_GET_TAG (val)); } } static void js_print_string (JSPrintValueState *s, JSValue val) { int sep = '\"'; js_putc (s, sep); js_print_string_rec (s, val, sep, 0); js_putc (s, sep); if (js_string_get_length (val) > s->options.max_string_length) { uint32_t n = js_string_get_length (val) - s->options.max_string_length; js_printf (s, "... %u more character%s", n, n > 1 ? "s" : ""); } } static void js_print_raw_string2 (JSPrintValueState *s, JSValue val, BOOL remove_last_lf) { const char *cstr; size_t len; cstr = JS_ToCStringLen (s->ctx, &len, val); if (cstr) { if (remove_last_lf && len > 0 && cstr[len - 1] == '\n') len--; s->write_func (s->write_opaque, cstr, len); JS_FreeCString (s->ctx, cstr); } } static void js_print_raw_string (JSPrintValueState *s, JSValue val) { js_print_raw_string2 (s, val, FALSE); } static void js_print_comma (JSPrintValueState *s, int *pcomma_state) { switch (*pcomma_state) { case 0: break; case 1: js_printf (s, ", "); break; case 2: js_printf (s, " { "); break; } *pcomma_state = 1; } static void js_print_more_items (JSPrintValueState *s, int *pcomma_state, uint32_t n) { js_print_comma (s, pcomma_state); js_printf (s, "... %u more item%s", n, n > 1 ? "s" : ""); } static void js_print_value (JSPrintValueState *s, JSValue val) { uint32_t tag = JS_VALUE_GET_NORM_TAG (val); const char *str; /* Handle pointer types first (new tagging system) */ if (JS_IsPtr (val)) { void *ptr = JS_VALUE_GET_PTR (val); /* Check objhdr_t at offset 8 for type */ objhdr_t hdr = *(objhdr_t *)ptr; uint8_t mist_type = objhdr_type (hdr); if (mist_type == OBJ_TEXT) { /* String (JSText or JSText) */ js_print_string (s, val); return; } return; } switch (tag) { case JS_TAG_INT: js_printf (s, "%d", JS_VALUE_GET_INT (val)); break; case JS_TAG_BOOL: if (JS_VALUE_GET_BOOL (val)) str = "true"; else str = "false"; goto print_str; case JS_TAG_NULL: str = "null"; goto print_str; case JS_TAG_EXCEPTION: str = "exception"; goto print_str; case JS_TAG_UNINITIALIZED: str = "uninitialized"; goto print_str; print_str: js_puts (s, str); break; case JS_TAG_SHORT_FLOAT: js_print_float64 (s, JS_VALUE_GET_FLOAT64 (val)); break; case JS_TAG_STRING_IMM: js_print_string (s, val); break; default: js_printf (s, "[unknown tag %d]", tag); break; } } void JS_PrintValueSetDefaultOptions (JSPrintValueOptions *options) { memset (options, 0, sizeof (*options)); options->max_depth = 2; options->max_string_length = 1000; options->max_item_count = 100; } static void JS_PrintValueInternal (JSRuntime *rt, JSContext *ctx, JSPrintValueWrite *write_func, void *write_opaque, JSValue val, const JSPrintValueOptions *options) { JSPrintValueState ss, *s = &ss; if (options) s->options = *options; else JS_PrintValueSetDefaultOptions (&s->options); if (s->options.max_depth <= 0) s->options.max_depth = JS_PRINT_MAX_DEPTH; else s->options.max_depth = min_int (s->options.max_depth, JS_PRINT_MAX_DEPTH); if (s->options.max_string_length == 0) s->options.max_string_length = UINT32_MAX; if (s->options.max_item_count == 0) s->options.max_item_count = UINT32_MAX; s->rt = rt; s->ctx = ctx; s->write_func = write_func; s->write_opaque = write_opaque; s->level = 0; js_print_value (s, val); } void JS_PrintValueRT (JSRuntime *rt, JSPrintValueWrite *write_func, void *write_opaque, JSValue val, const JSPrintValueOptions *options) { JS_PrintValueInternal (rt, NULL, write_func, write_opaque, val, options); } void JS_PrintValue (JSContext *ctx, JSPrintValueWrite *write_func, void *write_opaque, JSValue val, const JSPrintValueOptions *options) { JS_PrintValueInternal (ctx->rt, ctx, write_func, write_opaque, val, options); } void js_dump_value_write (void *opaque, const char *buf, size_t len) { FILE *fo = opaque; fwrite (buf, 1, len, fo); } /* print_atom removed - atoms no longer used */ __maybe_unused void JS_DumpValue (JSContext *ctx, const char *str, JSValue val) { printf ("%s=", str); JS_PrintValue (ctx, js_dump_value_write, stdout, val, NULL); printf ("\n"); } __maybe_unused void JS_DumpObjectHeader (JSRuntime *rt) { printf ("%14s %4s %4s %14s %s\n", "ADDRESS", "REFS", "SHRF", "PROTO", "CONTENT"); } /* for debug only: dump an object without side effect */ __maybe_unused void JS_DumpObject (JSRuntime *rt, JSRecord *rec) { JSPrintValueOptions options; printf ("%14p ", (void *)rec); /* Print prototype from JSRecord */ if (rec->proto) { printf ("%14p ", (void *)rec->proto); } else { printf ("%14s ", "-"); } JS_PrintValueSetDefaultOptions (&options); options.max_depth = 1; options.show_hidden = TRUE; options.raw_dump = TRUE; JS_PrintValueRT (rt, js_dump_value_write, stdout, JS_MKPTR (rec), &options); printf ("\n"); } __maybe_unused void JS_DumpGCObject (JSRuntime *rt, objhdr_t *p) { if (objhdr_type (*p) == OBJ_RECORD) { JS_DumpObject (rt, (JSRecord *)p); } else { switch (objhdr_type (*p)) { case OBJ_CODE: printf ("[function bytecode]"); break; case OBJ_ARRAY: printf ("[array]"); break; case OBJ_RECORD: printf ("[record]"); break; default: printf ("[unknown %d]", objhdr_type (*p)); break; } printf ("\n"); } } /* return -1 if exception (proxy case) or TRUE/FALSE */ static double js_pow (double a, double b) { if (unlikely (!isfinite (b)) && fabs (a) == 1) { /* not compatible with IEEE 754 */ return NAN; } else { return pow (a, b); } } no_inline __exception int js_unary_arith_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op) { JSValue op1; int v; uint32_t tag; op1 = sp[-1]; tag = JS_VALUE_GET_TAG (op1); switch (tag) { case JS_TAG_INT: { int64_t v64; v64 = JS_VALUE_GET_INT (op1); switch (op) { case OP_inc: case OP_dec: v = 2 * (op - OP_dec) - 1; v64 += v; break; case OP_plus: break; case OP_neg: v64 = -v64; /* -0 normalized to 0 by __JS_NewFloat64 */ break; default: abort (); } sp[-1] = JS_NewInt64 (ctx, v64); } break; case JS_TAG_FLOAT64: { double d; d = JS_VALUE_GET_FLOAT64 (op1); switch (op) { case OP_inc: case OP_dec: v = 2 * (op - OP_dec) - 1; d += v; break; case OP_plus: break; case OP_neg: d = -d; break; default: abort (); } sp[-1] = __JS_NewFloat64 (ctx, d); } break; default: sp[-1] = JS_NULL; } return 0; } __exception int js_post_inc_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op) { sp[0] = sp[-1]; return js_unary_arith_slow (ctx, sp + 1, op - OP_post_dec + OP_dec); } no_inline int js_not_slow (JSContext *ctx, JSValue *sp) { JSValue op1; op1 = sp[-1]; op1 = JS_ToNumber (ctx, op1); if (JS_IsException (op1)) goto exception; int32_t v1; if (unlikely (JS_ToInt32 (ctx, &v1, op1))) goto exception; sp[-1] = JS_NewInt32 (ctx, ~v1); return 0; exception: sp[-1] = JS_NULL; return -1; } no_inline __exception int js_binary_arith_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op) { JSValue op1, op2; uint32_t tag1, tag2; double d1, d2; op1 = sp[-2]; op2 = sp[-1]; tag1 = JS_VALUE_GET_NORM_TAG (op1); tag2 = JS_VALUE_GET_NORM_TAG (op2); /* fast path for float operations */ if (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_FLOAT64) { d1 = JS_VALUE_GET_FLOAT64 (op1); d2 = JS_VALUE_GET_FLOAT64 (op2); goto handle_float64; } if ((tag1 == JS_TAG_INT && tag2 == JS_TAG_FLOAT64) || (tag1 == JS_TAG_FLOAT64 && tag2 == JS_TAG_INT)) { if (tag1 == JS_TAG_INT) d1 = (double)JS_VALUE_GET_INT (op1); else d1 = JS_VALUE_GET_FLOAT64 (op1); if (tag2 == JS_TAG_INT) d2 = (double)JS_VALUE_GET_INT (op2); else d2 = JS_VALUE_GET_FLOAT64 (op2); goto handle_float64; } op1 = JS_ToNumber (ctx, op1); if (JS_IsException (op1)) { goto exception; } op2 = JS_ToNumber (ctx, op2); if (JS_IsException (op2)) { goto exception; } tag1 = JS_VALUE_GET_NORM_TAG (op1); tag2 = JS_VALUE_GET_NORM_TAG (op2); if (tag1 == JS_TAG_INT && tag2 == JS_TAG_INT) { int32_t v1, v2; int64_t v; v1 = JS_VALUE_GET_INT (op1); v2 = JS_VALUE_GET_INT (op2); switch (op) { case OP_sub: v = (int64_t)v1 - (int64_t)v2; break; case OP_mul: v = (int64_t)v1 * (int64_t)v2; /* -0 normalized to 0, no special case needed */ break; case OP_div: sp[-2] = JS_NewFloat64 (ctx, (double)v1 / (double)v2); return 0; case OP_mod: if (v1 < 0 || v2 <= 0) { sp[-2] = JS_NewFloat64 (ctx, fmod (v1, v2)); return 0; } else { v = (int64_t)v1 % (int64_t)v2; } break; case OP_pow: sp[-2] = JS_NewFloat64 (ctx, js_pow (v1, v2)); return 0; default: abort (); } sp[-2] = JS_NewInt64 (ctx, v); } else { double dr; /* float64 result */ if (JS_ToFloat64 (ctx, &d1, op1)) { goto exception; } if (JS_ToFloat64 (ctx, &d2, op2)) goto exception; handle_float64: switch (op) { case OP_sub: dr = d1 - d2; break; case OP_mul: dr = d1 * d2; break; case OP_div: dr = d1 / d2; break; case OP_mod: dr = fmod (d1, d2); break; case OP_pow: dr = js_pow (d1, d2); break; default: abort (); } sp[-2] = __JS_NewFloat64 (ctx, dr); } return 0; exception: sp[-2] = JS_NULL; sp[-1] = JS_NULL; return -1; } no_inline int js_relational_slow (JSContext *ctx, JSValue *sp, OPCodeEnum op) { JSValue op1 = sp[-2], op2 = sp[-1]; uint32_t tag1 = JS_VALUE_GET_NORM_TAG (op1); uint32_t tag2 = JS_VALUE_GET_NORM_TAG (op2); int res; /* string <=> string */ if (JS_IsText (op1) && JS_IsText (op2)) { res = js_string_compare_value (ctx, op1, op2, FALSE); switch (op) { case OP_lt: res = (res < 0); break; case OP_lte: res = (res <= 0); break; case OP_gt: res = (res > 0); break; default: res = (res >= 0); break; } /* number <=> number */ } else if ((tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64) && (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64)) { double d1 = (tag1 == JS_TAG_FLOAT64 ? JS_VALUE_GET_FLOAT64 (op1) : (double)JS_VALUE_GET_INT (op1)); double d2 = (tag2 == JS_TAG_FLOAT64 ? JS_VALUE_GET_FLOAT64 (op2) : (double)JS_VALUE_GET_INT (op2)); switch (op) { case OP_lt: res = (d1 < d2); break; case OP_lte: res = (d1 <= d2); break; case OP_gt: res = (d1 > d2); break; default: res = (d1 >= d2); break; } /* anything else → TypeError */ } else { JS_ThrowTypeError ( ctx, "Relational operators only supported on two strings or two numbers"); goto exception; } /* free the two input values and push the result */ sp[-2] = JS_NewBool (ctx, res); return 0; exception: sp[-2] = JS_NULL; sp[-1] = JS_NULL; return -1; } /* Simplified equality: no NaN (becomes null), no coercion, no SameValue distinction */ BOOL js_strict_eq (JSContext *ctx, JSValue op1, JSValue op2) { /* Fast path: identical values */ if (op1 == op2) return TRUE; int tag1 = JS_VALUE_GET_NORM_TAG (op1); int tag2 = JS_VALUE_GET_NORM_TAG (op2); /* Different types are never equal (no coercion) */ /* Special case: INT and FLOAT can be equal */ if (tag1 != tag2) { if (!((tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64) && (tag2 == JS_TAG_INT || tag2 == JS_TAG_FLOAT64))) return FALSE; } switch (tag1) { case JS_TAG_INT: case JS_TAG_FLOAT64: { /* Numbers: unpack and compare */ double d1 = (tag1 == JS_TAG_INT) ? (double)JS_VALUE_GET_INT (op1) : JS_VALUE_GET_FLOAT64 (op1); double d2 = (tag2 == JS_TAG_INT) ? (double)JS_VALUE_GET_INT (op2) : JS_VALUE_GET_FLOAT64 (op2); return d1 == d2; } case JS_TAG_STRING_IMM: /* Immediate text vs immediate text (handled by op1 == op2 fast path) */ /* or vs heap text */ if (JS_IsText (op2)) return js_string_compare_value (ctx, op1, op2, TRUE) == 0; return FALSE; case JS_TAG_PTR: /* Heap text vs heap text or vs immediate text */ if (JS_IsText (op1) && JS_IsText (op2)) return js_string_compare_value (ctx, op1, op2, TRUE) == 0; /* Records/objects: pointer equality (op1 == op2 handles same object) */ return FALSE; /* Different objects */ case JS_TAG_BOOL: case JS_TAG_NULL: /* Already handled by op1 == op2 fast path */ return FALSE; default: return FALSE; } } BOOL JS_StrictEq (JSContext *ctx, JSValue op1, JSValue op2) { return js_strict_eq (ctx, op1, op2); } no_inline int js_strict_eq_slow (JSContext *ctx, JSValue *sp, BOOL is_neq) { BOOL res = js_strict_eq (ctx, sp[-2], sp[-1]); sp[-2] = JS_NewBool (ctx, res ^ is_neq); return 0; } __exception int js_operator_in (JSContext *ctx, JSValue *sp) { JSValue op1, op2; int ret; op1 = sp[-2]; op2 = sp[-1]; if (JS_VALUE_GET_TAG (op2) != JS_TAG_PTR) { JS_ThrowTypeError (ctx, "invalid 'in' operand"); return -1; } ret = JS_HasPropertyKey (ctx, op2, op1); if (ret < 0) return -1; sp[-2] = JS_NewBool (ctx, ret); return 0; } __exception int js_operator_delete (JSContext *ctx, JSValue *sp) { JSValue op1, op2; int ret; op1 = sp[-2]; op2 = sp[-1]; ret = JS_DeletePropertyKey (ctx, op1, op2); if (unlikely (ret < 0)) return -1; sp[-2] = JS_NewBool (ctx, ret); return 0; } /* XXX: not 100% compatible, but mozilla seems to use a similar implementation to ensure that caller in non strict mode does not throw (ES5 compatibility) */ static JSValue js_throw_type_error (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { return JS_ThrowTypeError (ctx, "invalid property access"); } __exception int JS_CopyDataProperties (JSContext *ctx, JSValue target, JSValue source, JSValue excluded, BOOL setprop) { JSValue keys, key, val; uint32_t i, key_count; int ret; if (JS_VALUE_GET_TAG (source) != JS_TAG_PTR) return 0; /* Get all string keys from source */ keys = JS_GetOwnPropertyNames (ctx, source); if (JS_IsException (keys)) return -1; if (js_get_length32 (ctx, &key_count, keys)) { return -1; } for (i = 0; i < key_count; i++) { key = JS_GetPropertyUint32 (ctx, keys, i); if (JS_IsException (key)) goto exception; /* Check if key is excluded */ if (JS_VALUE_GET_TAG (excluded) == JS_TAG_PTR) { /* Check if key exists in excluded object */ JSValue test = JS_GetProperty (ctx, excluded, key); if (!JS_IsNull (test) && !JS_IsException (test)) { continue; } } /* Get property value from source */ val = JS_GetProperty (ctx, source, key); if (JS_IsException (val)) { goto exception; } /* Set property on target */ ret = JS_SetProperty (ctx, target, key, val); if (ret < 0) goto exception; } return 0; exception: return -1; } JSValue js_closure2 (JSContext *ctx, JSValue func_obj, JSFunctionBytecode *b, JSStackFrame *sf) { JSFunction *f; f = JS_VALUE_GET_FUNCTION (func_obj); f->u.func.function_bytecode = b; /* Set outer_frame to parent's JSFrame for the new closure model. This allows OP_get_up/OP_set_up to access captured variables via frame chain. */ f->u.func.outer_frame = sf ? sf->js_frame : JS_NULL; return func_obj; } JSValue js_closure (JSContext *ctx, JSValue bfunc, JSStackFrame *sf) { JSFunctionBytecode *b; JSValue func_obj; JSFunction *f; JSGCRef bfunc_ref; /* Protect bfunc from GC during function allocation */ JS_PUSH_VALUE (ctx, bfunc); func_obj = js_new_function (ctx, JS_FUNC_KIND_BYTECODE); if (JS_IsException (func_obj)) { JS_POP_VALUE (ctx, bfunc); return JS_EXCEPTION; } JS_POP_VALUE (ctx, bfunc); b = JS_VALUE_GET_PTR (bfunc); func_obj = js_closure2 (ctx, func_obj, b, sf); if (JS_IsException (func_obj)) { /* bfunc has been freed */ goto fail; } f = JS_VALUE_GET_FUNCTION (func_obj); /* Use bytecode func_name if valid, otherwise empty string */ f->name = JS_IsText (b->func_name) ? b->func_name : JS_KEY_empty; f->length = b->arg_count; /* arity = total parameter count */ return func_obj; fail: /* bfunc is freed when func_obj is freed */ return JS_EXCEPTION; } JSValue js_call_c_function (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv) { JSCFunctionType func; JSFunction *f; JSStackFrame sf_s, *sf = &sf_s, *prev_sf; JSValue ret_val; JSValue *arg_buf; int arg_count, i; int saved_vs_top = -1; /* for value stack padding cleanup */ JSCFunctionEnum cproto; f = JS_VALUE_GET_FUNCTION (func_obj); cproto = f->u.cfunc.cproto; arg_count = f->length; /* better to always check stack overflow */ if (js_check_stack_overflow (ctx, sizeof (arg_buf[0]) * arg_count)) return JS_ThrowStackOverflow (ctx); prev_sf = ctx->current_stack_frame; sf->prev_frame = prev_sf; ctx->current_stack_frame = sf; sf->js_mode = 0; sf->cur_func = (JSValue)func_obj; sf->arg_count = argc; sf->js_frame = JS_NULL; /* C functions don't have JSFrame */ sf->stack_buf = NULL; /* C functions don't have operand stack */ sf->p_sp = NULL; arg_buf = argv; if (unlikely (argc < arg_count)) { /* Pad args on the value stack (GC-scanned) instead of alloca */ saved_vs_top = ctx->value_stack_top; for (i = 0; i < argc; i++) ctx->value_stack[saved_vs_top + i] = argv[i]; for (i = argc; i < arg_count; i++) ctx->value_stack[saved_vs_top + i] = JS_NULL; ctx->value_stack_top = saved_vs_top + arg_count; arg_buf = &ctx->value_stack[saved_vs_top]; sf->arg_count = arg_count; } sf->arg_buf = (JSValue *)arg_buf; func = f->u.cfunc.c_function; if (unlikely (ctx->trace_hook) && (ctx->trace_type & JS_HOOK_CALL)) { js_debug dbg; js_debug_info (ctx, func_obj, &dbg); ctx->trace_hook (ctx, JS_HOOK_CALL, &dbg, ctx->trace_data); } switch (cproto) { case JS_CFUNC_generic: ret_val = func.generic (ctx, this_obj, argc, arg_buf); break; case JS_CFUNC_generic_magic: ret_val = func.generic_magic (ctx, this_obj, argc, arg_buf, f->u.cfunc.magic); break; case JS_CFUNC_f_f: { double d1; if (unlikely (JS_ToFloat64 (ctx, &d1, arg_buf[0]))) { ret_val = JS_EXCEPTION; break; } ret_val = JS_NewFloat64 (ctx, func.f_f (d1)); } break; case JS_CFUNC_f_f_f: { double d1, d2; if (unlikely (JS_ToFloat64 (ctx, &d1, arg_buf[0]))) { ret_val = JS_EXCEPTION; break; } if (unlikely (JS_ToFloat64 (ctx, &d2, arg_buf[1]))) { ret_val = JS_EXCEPTION; break; } ret_val = JS_NewFloat64 (ctx, func.f_f_f (d1, d2)); } break; /* Fixed-arity fast paths - direct call without argc/argv marshaling */ case JS_CFUNC_0: ret_val = func.f0 (ctx, this_obj); break; case JS_CFUNC_1: ret_val = func.f1 (ctx, this_obj, arg_buf[0]); break; case JS_CFUNC_2: ret_val = func.f2 (ctx, this_obj, arg_buf[0], arg_buf[1]); break; case JS_CFUNC_3: ret_val = func.f3 (ctx, this_obj, arg_buf[0], arg_buf[1], arg_buf[2]); break; case JS_CFUNC_4: ret_val = func.f4 (ctx, this_obj, arg_buf[0], arg_buf[1], arg_buf[2], arg_buf[3]); break; /* Pure functions (no this_val) */ case JS_CFUNC_PURE: ret_val = func.pure (ctx, argc, arg_buf); break; case JS_CFUNC_PURE_0: ret_val = func.pure0 (ctx); break; case JS_CFUNC_PURE_1: ret_val = func.pure1 (ctx, arg_buf[0]); break; case JS_CFUNC_PURE_2: ret_val = func.pure2 (ctx, arg_buf[0], arg_buf[1]); break; case JS_CFUNC_PURE_3: ret_val = func.pure3 (ctx, arg_buf[0], arg_buf[1], arg_buf[2]); break; case JS_CFUNC_PURE_4: ret_val = func.pure4 (ctx, arg_buf[0], arg_buf[1], arg_buf[2], arg_buf[3]); break; default: abort (); } ctx->current_stack_frame = sf->prev_frame; /* Restore value stack if we used it for arg padding */ if (saved_vs_top >= 0) ctx->value_stack_top = saved_vs_top; if (unlikely (ctx->trace_hook) && (ctx->trace_type & JS_HOOK_RET)) ctx->trace_hook (ctx, JS_HOOK_RET, NULL, ctx->trace_data); return ret_val; } JSValue JS_Call (JSContext *ctx, JSValue func_obj, JSValue this_obj, int argc, JSValue *argv) { if (js_poll_interrupts (ctx)) return JS_EXCEPTION; if (unlikely (!JS_IsFunction (func_obj))) return JS_ThrowTypeError (ctx, "not a function"); JSFunction *f = JS_VALUE_GET_FUNCTION (func_obj); if (unlikely (f->length >= 0 && argc > f->length)) { char buf[KEY_GET_STR_BUF_SIZE]; return JS_ThrowTypeError (ctx, "too many arguments for %s: expected %d, got %d", JS_KeyGetStr (ctx, buf, KEY_GET_STR_BUF_SIZE, f->name), f->length, argc); } switch (f->kind) { case JS_FUNC_KIND_C: return js_call_c_function (ctx, func_obj, this_obj, argc, argv); case JS_FUNC_KIND_BYTECODE: return JS_CallInternal (ctx, func_obj, this_obj, argc, argv, JS_CALL_FLAG_COPY_ARGV); case JS_FUNC_KIND_REGISTER: return JS_CallRegisterVM (ctx, f->u.reg.code, this_obj, argc, argv, f->u.reg.env_record, f->u.reg.outer_frame); case JS_FUNC_KIND_MCODE: return mcode_exec (ctx, f->u.mcode.code, this_obj, argc, argv, f->u.mcode.outer_frame); default: return JS_ThrowTypeError (ctx, "not a function"); } } /*******************************************************************/ /* runtime functions & objects */ int check_function (JSContext *ctx, JSValue obj) { if (likely (JS_IsFunction (obj))) return 0; JS_ThrowTypeError (ctx, "not a function"); return -1; } int check_exception_free (JSContext *ctx, JSValue obj) { return JS_IsException (obj); } JSValue find_key (JSContext *ctx, const char *name) { /* Create an interned JSValue key from C string */ return js_key_new (ctx, name); } static int JS_InstantiateFunctionListItem (JSContext *ctx, JSValue *obj_ptr, JSValue key, const JSCFunctionListEntry *e) { JSValue val; switch (e->def_type) { case JS_DEF_ALIAS: /* using autoinit for aliases is not safe */ { JSValue key1 = find_key (ctx, e->u.alias.name); switch (e->u.alias.base) { case -1: val = JS_GetProperty (ctx, *obj_ptr, key1); break; case 0: val = JS_GetProperty (ctx, ctx->global_obj, key1); break; default: abort (); } /* key1 is interned, no need to free */ } break; case JS_DEF_CFUNC: val = JS_NewCFunction2 (ctx, e->u.func.cfunc.generic, e->name, e->u.func.length, e->u.func.cproto, e->magic); break; case JS_DEF_PROP_INT32: val = JS_NewInt32 (ctx, e->u.i32); break; case JS_DEF_PROP_INT64: val = JS_NewInt64 (ctx, e->u.i64); break; case JS_DEF_PROP_DOUBLE: val = __JS_NewFloat64 (ctx, e->u.f64); break; case JS_DEF_PROP_UNDEFINED: val = JS_NULL; break; case JS_DEF_PROP_STRING: val = JS_NewAtomString (ctx, e->u.str); break; case JS_DEF_OBJECT: val = JS_NewObject (ctx); if (JS_IsException (val)) return -1; JS_SetPropertyFunctionList (ctx, val, e->u.prop_list.tab, e->u.prop_list.len); break; default: abort (); } JS_SetPropertyInternal (ctx, *obj_ptr, key, val); return 0; } int JS_SetPropertyFunctionList (JSContext *ctx, JSValue obj, const JSCFunctionListEntry *tab, int len) { int i, ret; /* Root obj since allocations in the loop can trigger GC */ JSGCRef obj_ref; obj_ref.val = obj; obj_ref.prev = ctx->last_gc_ref; ctx->last_gc_ref = &obj_ref; for (i = 0; i < len; i++) { const JSCFunctionListEntry *e = &tab[i]; JSValue key = find_key (ctx, e->name); if (JS_IsNull (key)) { ctx->last_gc_ref = obj_ref.prev; return -1; } ret = JS_InstantiateFunctionListItem (ctx, &obj_ref.val, key, e); /* key is interned, no need to free */ if (ret) { ctx->last_gc_ref = obj_ref.prev; return -1; } } ctx->last_gc_ref = obj_ref.prev; return 0; } __exception int js_get_length32 (JSContext *ctx, uint32_t *pres, JSValue obj) { int tag = JS_VALUE_GET_TAG (obj); /* Fast path for intrinsic arrays */ if (JS_IsArray (obj)) { JSArray *arr = JS_VALUE_GET_ARRAY (obj); *pres = arr->len; return 0; } if (tag == JS_TAG_FUNCTION) { JSFunction *fn = JS_VALUE_GET_FUNCTION (obj); *pres = fn->length; return 0; } blob *b = js_get_blob (ctx, obj); if (b) { *pres = b->length; return 0; } if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) { JSText *p = JS_VALUE_GET_STRING (obj); *pres = JSText_len (p); return 0; } JSValue len_val; len_val = JS_GetProperty (ctx, obj, JS_KEY_length); if (JS_IsException (len_val)) { *pres = 0; return -1; } return JS_ToUint32 (ctx, pres, len_val); } __exception int js_get_length64 (JSContext *ctx, int64_t *pres, JSValue obj) { /* Fast path for intrinsic arrays */ if (JS_IsArray (obj)) { JSArray *arr = JS_VALUE_GET_ARRAY (obj); *pres = arr->len; return 0; } JSValue len_val; len_val = JS_GetProperty (ctx, obj, JS_KEY_length); if (JS_IsException (len_val)) { *pres = 0; return -1; } return JS_ToLength (ctx, pres, len_val); } int JS_GetLength (JSContext *ctx, JSValue obj, int64_t *pres) { return js_get_length64 (ctx, pres, obj); } void free_arg_list (JSContext *ctx, JSValue *tab, uint32_t len) { (void)ctx; (void)len; js_free_rt(tab); } /* XXX: should use ValueArray */ JSValue *build_arg_list (JSContext *ctx, uint32_t *plen, JSValue *parray_arg) { uint32_t len, i; JSValue *tab; /* Fast path for intrinsic arrays */ if (JS_IsArray (*parray_arg)) { JSArray *arr = JS_VALUE_GET_ARRAY (*parray_arg); len = arr->len; if (len > JS_MAX_LOCAL_VARS) { JS_ThrowRangeError ( ctx, "too many arguments in function call (only %d allowed)", JS_MAX_LOCAL_VARS); return NULL; } tab = js_mallocz_rt (sizeof (tab[0]) * max_uint32 (1, len)); if (!tab) return NULL; arr = JS_VALUE_GET_ARRAY (*parray_arg); for (i = 0; i < len; i++) { tab[i] = arr->values[i]; } *plen = len; return tab; } JS_ThrowTypeError (ctx, "not an array"); return NULL; } /* Error class */ static JSValue js_error_constructor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv, int magic) { JSValue obj, msg; JSValue message, options, proto; int arg_index; /* Use the appropriate error prototype based on magic */ if (magic < 0) { proto = ctx->class_proto[JS_CLASS_ERROR]; } else { proto = ctx->native_error_proto[magic]; } obj = JS_NewObjectProtoClass (ctx, proto, JS_CLASS_ERROR); if (JS_IsException (obj)) return obj; arg_index = (magic == JS_AGGREGATE_ERROR); message = argv[arg_index++]; if (!JS_IsNull (message)) { msg = JS_ToString (ctx, message); if (unlikely (JS_IsException (msg))) goto exception; JS_SetPropertyInternal (ctx, obj, JS_KEY_message, msg); } if (arg_index < argc) { options = argv[arg_index]; if (JS_IsObject (options)) { int present = JS_HasProperty (ctx, options, JS_KEY_cause); if (present < 0) goto exception; if (present) { JSValue cause = JS_GetProperty (ctx, options, JS_KEY_cause); if (JS_IsException (cause)) goto exception; JS_SetPropertyInternal (ctx, obj, JS_KEY_cause, cause); } } } if (magic == JS_AGGREGATE_ERROR) { /* Require errors to be an array (no iterator support) */ JSValue error_list; if (JS_IsArray (argv[0])) { uint32_t len, i; if (js_get_length32 (ctx, &len, argv[0])) goto exception; error_list = JS_NewArray (ctx); if (JS_IsException (error_list)) goto exception; for (i = 0; i < len; i++) { JSValue item = JS_GetPropertyUint32 (ctx, argv[0], i); if (JS_IsException (item)) { goto exception; } if (JS_SetPropertyUint32 (ctx, error_list, i, item) < 0) { goto exception; } } } else { error_list = JS_NewArray (ctx); if (JS_IsException (error_list)) goto exception; } JS_SetPropertyInternal (ctx, obj, JS_KEY_errors, error_list); } /* skip the Error() function in the backtrace */ build_backtrace (ctx, obj, NULL, 0, 0, JS_BACKTRACE_FLAG_SKIP_FIRST_LEVEL); return obj; exception: return JS_EXCEPTION; } JSValue js_error_toString (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { printf ("E TO STR\n"); JSValue name, msg; if (!JS_IsObject (this_val)) return JS_ThrowTypeErrorNotAnObject (ctx); name = JS_GetProperty (ctx, this_val, JS_KEY_name); if (JS_IsNull (name)) name = JS_KEY_Error; else name = JS_ToString (ctx, name); if (JS_IsException (name)) return JS_EXCEPTION; msg = JS_GetProperty (ctx, this_val, JS_KEY_message); if (JS_IsNull (msg)) msg = JS_KEY_empty; else msg = JS_ToString (ctx, msg); if (JS_IsException (msg)) { return JS_EXCEPTION; } if (!JS_IsEmptyString (name) && !JS_IsEmptyString (msg)) name = JS_ConcatString3 (ctx, "", name, ": "); return JS_ConcatString (ctx, name, msg); } static JSValue js_array_includes (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { JSValue found = js_cell_array_find (ctx, this_val, argc, argv); if (JS_IsException (found)) return JS_EXCEPTION; if (JS_IsNull (found)) return JS_NewBool (ctx, FALSE); return JS_NewBool (ctx, TRUE); } /* return < 0 if exception or TRUE/FALSE */ static int js_is_regexp (JSContext *ctx, JSValue obj); /* RegExp */ void js_regexp_finalizer (JSRuntime *rt, JSValue val) { JSRegExp *re = JS_GetOpaque (val, JS_CLASS_REGEXP); if (re) { js_free_rt (re->pattern); js_free_rt (re->bytecode); js_free_rt (re); } (void)rt; } /* create a string containing the RegExp bytecode */ JSValue js_compile_regexp (JSContext *ctx, JSValue pattern, JSValue flags) { const char *str; int re_flags, mask; uint8_t *re_bytecode_buf; size_t i, len; int re_bytecode_len; JSValue ret; char error_msg[64]; re_flags = 0; if (!JS_IsNull (flags)) { str = JS_ToCStringLen (ctx, &len, flags); if (!str) return JS_EXCEPTION; /* XXX: re_flags = LRE_FLAG_OCTAL unless strict mode? */ for (i = 0; i < len; i++) { switch (str[i]) { case 'd': mask = LRE_FLAG_INDICES; break; case 'g': mask = LRE_FLAG_GLOBAL; break; case 'i': mask = LRE_FLAG_IGNORECASE; break; case 'm': mask = LRE_FLAG_MULTILINE; break; case 's': mask = LRE_FLAG_DOTALL; break; case 'u': mask = LRE_FLAG_UNICODE; break; case 'v': mask = LRE_FLAG_UNICODE_SETS; break; case 'y': mask = LRE_FLAG_STICKY; break; default: goto bad_flags; } if ((re_flags & mask) != 0) { bad_flags: JS_FreeCString (ctx, str); goto bad_flags1; } re_flags |= mask; } JS_FreeCString (ctx, str); } /* 'u' and 'v' cannot be both set */ if ((re_flags & LRE_FLAG_UNICODE_SETS) && (re_flags & LRE_FLAG_UNICODE)) { bad_flags1: return JS_ThrowSyntaxError (ctx, "invalid regular expression flags"); } str = JS_ToCStringLen2 ( ctx, &len, pattern, !(re_flags & (LRE_FLAG_UNICODE | LRE_FLAG_UNICODE_SETS))); if (!str) return JS_EXCEPTION; re_bytecode_buf = lre_compile (&re_bytecode_len, error_msg, sizeof (error_msg), str, len, re_flags, ctx); JS_FreeCString (ctx, str); if (!re_bytecode_buf) { JS_ThrowSyntaxError (ctx, "%s", error_msg); return JS_EXCEPTION; } ret = js_new_string8_len (ctx, (const char *)re_bytecode_buf, re_bytecode_len); js_free (ctx, re_bytecode_buf); return ret; } /* create a RegExp object from a string containing the RegExp bytecode and the source pattern */ JSValue js_regexp_constructor_internal (JSContext *ctx, JSValue pattern, JSValue bc) { JSValue obj; JSRecord *p; JSRegExp *re; const char *pat_cstr; size_t pat_len; int bc_len, i; /* sanity check - need strings for pattern and bytecode */ if (!JS_IsText (bc) || !JS_IsText (pattern)) { JS_ThrowTypeError (ctx, "string expected"); fail: return JS_EXCEPTION; } /* Root pattern and bc across allocating calls */ JSGCRef pat_ref, bc_ref; JS_PushGCRef (ctx, &pat_ref); pat_ref.val = pattern; JS_PushGCRef (ctx, &bc_ref); bc_ref.val = bc; obj = JS_NewObjectProtoClass (ctx, ctx->class_proto[JS_CLASS_REGEXP], JS_CLASS_REGEXP); if (JS_IsException (obj)) { JS_PopGCRef (ctx, &bc_ref); JS_PopGCRef (ctx, &pat_ref); goto fail; } /* Root obj across allocating calls */ JSGCRef obj_ref; JS_PushGCRef (ctx, &obj_ref); obj_ref.val = obj; #define REGEXP_CLEANUP() do { JS_PopGCRef (ctx, &obj_ref); JS_PopGCRef (ctx, &bc_ref); JS_PopGCRef (ctx, &pat_ref); } while(0) /* Allocate JSRegExp off-heap (not on GC heap) so opaque pointer stays valid after GC */ re = js_malloc_rt (sizeof(JSRegExp)); if (!re) { REGEXP_CLEANUP (); JS_ThrowOutOfMemory (ctx); goto fail; } p = JS_VALUE_GET_OBJ (obj_ref.val); REC_SET_OPAQUE(p, re); re->pattern = NULL; re->bytecode = NULL; /* Extract pattern as UTF-8 C string */ pat_cstr = JS_ToCStringLen (ctx, &pat_len, pat_ref.val); if (!pat_cstr) { REGEXP_CLEANUP (); goto fail; } re->pattern = js_malloc_rt (pat_len + 1); if (!re->pattern) { JS_FreeCString (ctx, pat_cstr); REGEXP_CLEANUP (); goto fail; } memcpy (re->pattern, pat_cstr, pat_len + 1); re->pattern_len = (uint32_t)pat_len; JS_FreeCString (ctx, pat_cstr); /* Extract bytecode as raw bytes via string_get (not JS_ToCStringLen which UTF-8 encodes and would mangle bytes >= 128) */ bc = bc_ref.val; if (MIST_IsImmediateASCII (bc)) { bc_len = MIST_GetImmediateASCIILen (bc); re->bytecode = js_malloc_rt (bc_len); if (!re->bytecode) { REGEXP_CLEANUP (); goto fail; } for (i = 0; i < bc_len; i++) re->bytecode[i] = (uint8_t)MIST_GetImmediateASCIIChar (bc, i); } else { JSText *bc_str = (JSText *)chase (bc_ref.val); bc_len = (int)JSText_len (bc_str); re->bytecode = js_malloc_rt (bc_len); if (!re->bytecode) { REGEXP_CLEANUP (); goto fail; } for (i = 0; i < bc_len; i++) re->bytecode[i] = (uint8_t)string_get (bc_str, i); } re->bytecode_len = (uint32_t)bc_len; { JSValue key = JS_KEY_STR (ctx, "lastIndex"); obj = obj_ref.val; /* re-read after JS_KEY_STR allocation */ JS_SetPropertyInternal (ctx, obj, key, JS_NewInt32 (ctx, 0)); } obj = obj_ref.val; REGEXP_CLEANUP (); return obj; } #undef REGEXP_CLEANUP static JSRegExp *js_get_regexp (JSContext *ctx, JSValue obj, BOOL throw_error) { if (JS_VALUE_GET_TAG (obj) == JS_TAG_PTR) { JSRecord *p = JS_VALUE_GET_OBJ (obj); if (REC_GET_CLASS_ID(p) == JS_CLASS_REGEXP) return (JSRegExp *)REC_GET_OPAQUE(p); } if (throw_error) { JS_ThrowTypeErrorInvalidClass (ctx, JS_CLASS_REGEXP); } return NULL; } /* return < 0 if exception or TRUE/FALSE */ static int js_is_regexp (JSContext *ctx, JSValue obj) { JSValue m; if (!JS_IsObject (obj)) return FALSE; m = JS_GetPropertyStr (ctx, obj, "Symbol.match"); if (JS_IsException (m)) return -1; if (!JS_IsNull (m)) return JS_ToBool (ctx, m); return js_get_regexp (ctx, obj, FALSE) != NULL; } JSValue js_regexp_constructor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { JSValue pattern, flags, bc, val; JSValue pat, flags1; JSRegExp *re; int pat_is_regexp; pat = argv[0]; flags1 = argv[1]; pat_is_regexp = js_is_regexp (ctx, pat); if (pat_is_regexp < 0) return JS_EXCEPTION; /* If called with a regexp and no flags, just return a copy */ if (pat_is_regexp && JS_IsNull (flags1)) { re = js_get_regexp (ctx, pat, FALSE); if (re) return pat; } re = js_get_regexp (ctx, pat, FALSE); if (re) { pattern = JS_NewString (ctx, re->pattern); if (JS_IsException (pattern)) goto fail; if (JS_IsNull (flags1)) { bc = js_new_string8_len (ctx, (const char *)re->bytecode, re->bytecode_len); if (JS_IsException (bc)) goto fail; goto no_compilation; } else { flags = JS_ToString (ctx, flags1); if (JS_IsException (flags)) goto fail; } } else { flags = JS_NULL; if (pat_is_regexp) { pattern = JS_GetProperty (ctx, pat, JS_KEY_source); if (JS_IsException (pattern)) goto fail; if (JS_IsNull (flags1)) { flags = JS_GetProperty (ctx, pat, JS_KEY_flags); if (JS_IsException (flags)) goto fail; } else { flags = flags1; } } else { pattern = pat; flags = flags1; } if (JS_IsNull (pattern)) { pattern = JS_KEY_empty; } else { val = pattern; pattern = JS_ToString (ctx, val); if (JS_IsException (pattern)) goto fail; } } bc = js_compile_regexp (ctx, pattern, flags); if (JS_IsException (bc)) goto fail; no_compilation: return js_regexp_constructor_internal (ctx, pattern, bc); fail: return JS_EXCEPTION; } static JSValue js_regexp_compile (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { JSRegExp *re1, *re; JSValue pattern1, flags1; JSValue bc, pattern; const char *pat_cstr; size_t pat_len; int bc_len, i; re = js_get_regexp (ctx, this_val, TRUE); if (!re) return JS_EXCEPTION; pattern1 = argv[0]; flags1 = argv[1]; re1 = js_get_regexp (ctx, pattern1, FALSE); if (re1) { if (!JS_IsNull (flags1)) return JS_ThrowTypeError (ctx, "flags must be undefined"); pattern = JS_NewString (ctx, re1->pattern); if (JS_IsException (pattern)) goto fail; bc = js_new_string8_len (ctx, (const char *)re1->bytecode, re1->bytecode_len); if (JS_IsException (bc)) goto fail; } else { bc = JS_NULL; if (JS_IsNull (pattern1)) pattern = JS_KEY_empty; else pattern = JS_ToString (ctx, pattern1); if (JS_IsException (pattern)) goto fail; bc = js_compile_regexp (ctx, pattern, flags1); if (JS_IsException (bc)) goto fail; } /* Free old C buffers */ js_free_rt (re->pattern); re->pattern = NULL; js_free_rt (re->bytecode); re->bytecode = NULL; /* Extract pattern as UTF-8 C string */ pat_cstr = JS_ToCStringLen (ctx, &pat_len, pattern); if (!pat_cstr) goto fail; re->pattern = js_malloc_rt (pat_len + 1); if (!re->pattern) { JS_FreeCString (ctx, pat_cstr); goto fail; } memcpy (re->pattern, pat_cstr, pat_len + 1); re->pattern_len = (uint32_t)pat_len; JS_FreeCString (ctx, pat_cstr); /* Extract bytecode as raw bytes */ if (MIST_IsImmediateASCII (bc)) { bc_len = MIST_GetImmediateASCIILen (bc); re->bytecode = js_malloc_rt (bc_len); if (!re->bytecode) goto fail; for (i = 0; i < bc_len; i++) re->bytecode[i] = (uint8_t)MIST_GetImmediateASCIIChar (bc, i); } else { JSText *bc_str = (JSText *)JS_VALUE_GET_PTR (bc); bc_len = (int)JSText_len (bc_str); re->bytecode = js_malloc_rt (bc_len); if (!re->bytecode) goto fail; for (i = 0; i < bc_len; i++) re->bytecode[i] = (uint8_t)string_get (bc_str, i); } re->bytecode_len = (uint32_t)bc_len; { JSValue key = JS_KEY_STR (ctx, "lastIndex"); int ret = JS_SetProperty (ctx, this_val, key, JS_NewInt32 (ctx, 0)); if (ret < 0) return JS_EXCEPTION; } return this_val; fail: return JS_EXCEPTION; } JSValue js_regexp_toString (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { JSValue pattern, flags; if (!JS_IsObject (this_val)) return JS_ThrowTypeErrorNotAnObject (ctx); JSText *b = pretext_init (ctx, 0); if (!b) return JS_EXCEPTION; b = pretext_putc (ctx, b, '/'); if (!b) return JS_EXCEPTION; pattern = JS_GetProperty (ctx, this_val, JS_KEY_source); b = pretext_concat_value (ctx, b, pattern); if (!b) return JS_EXCEPTION; b = pretext_putc (ctx, b, '/'); if (!b) return JS_EXCEPTION; flags = JS_GetProperty (ctx, this_val, JS_KEY_flags); b = pretext_concat_value (ctx, b, flags); if (!b) return JS_EXCEPTION; return pretext_end (ctx, b); } int lre_check_stack_overflow (void *opaque, size_t alloca_size) { JSContext *ctx = opaque; return js_check_stack_overflow (ctx, alloca_size); } int lre_check_timeout (void *opaque) { JSContext *ctx = opaque; return (ctx->interrupt_handler && ctx->interrupt_handler (ctx->rt, ctx->interrupt_opaque)); } void *lre_realloc (void *opaque, void *ptr, size_t size) { (void)opaque; /* No JS exception is raised here */ return js_realloc_rt (ptr, size); } /* Convert UTF-32 JSText to UTF-16 buffer for regex engine. Returns allocated uint16_t buffer (via js_malloc_rt) that must be freed with js_free_rt. Sets *out_len to number of uint16 code units. */ static uint16_t *js_string_to_utf16 (JSContext *ctx, JSText *str, int *out_len) { int len = (int)JSText_len (str); /* Worst case: each UTF-32 char becomes 2 UTF-16 surrogates */ uint16_t *buf = js_malloc_rt (len * 2 * sizeof (uint16_t)); if (!buf) { JS_ThrowOutOfMemory (ctx); return NULL; } int j = 0; for (int i = 0; i < len; i++) { uint32_t c = string_get (str, i); if (c < 0x10000) { buf[j++] = (uint16_t)c; } else { /* Encode as surrogate pair */ c -= 0x10000; buf[j++] = (uint16_t)(0xD800 | (c >> 10)); buf[j++] = (uint16_t)(0xDC00 | (c & 0x3FF)); } } *out_len = j; return buf; } static JSValue js_regexp_exec (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { JSRegExp *re = js_get_regexp (ctx, this_val, TRUE); JSText *str; JSGCRef str_ref, this_ref; JSValue ret, res, val, groups, captures_arr, match0; uint8_t *re_bytecode; uint8_t **capture, *str_buf; uint16_t *utf16_buf = NULL; int rc, capture_count, shift, i, re_flags; int utf16_len = 0; int64_t last_index; const char *group_name_ptr; if (!re) return JS_EXCEPTION; /* Root this_val across allocating calls */ JS_PushGCRef (ctx, &this_ref); this_ref.val = this_val; JS_PushGCRef (ctx, &str_ref); str_ref.val = JS_ToString (ctx, argv[0]); if (JS_IsException (str_ref.val)) { JS_PopGCRef (ctx, &str_ref); JS_PopGCRef (ctx, &this_ref); return JS_EXCEPTION; } /* Ensure str_val is a heap string for JS_VALUE_GET_STRING */ if (MIST_IsImmediateASCII (str_ref.val)) { int imm_len = MIST_GetImmediateASCIILen (str_ref.val); JSText *hs = js_alloc_string (ctx, imm_len > 0 ? imm_len : 1); if (!hs) { JS_PopGCRef (ctx, &str_ref); JS_PopGCRef (ctx, &this_ref); return JS_EXCEPTION; } for (int ci = 0; ci < imm_len; ci++) string_put (hs, ci, MIST_GetImmediateASCIIChar (str_ref.val, ci)); hs->hdr = objhdr_set_cap56 (hs->hdr, imm_len); hs->length = 0; hs->hdr = objhdr_set_s (hs->hdr, true); str_ref.val = JS_MKPTR (hs); } ret = JS_EXCEPTION; res = JS_NULL; groups = JS_NULL; captures_arr = JS_NULL; match0 = JS_NULL; capture = NULL; val = JS_GetPropertyStr (ctx, this_ref.val, "lastIndex"); if (JS_IsException (val) || JS_ToLength (ctx, &last_index, val)) goto fail; /* Re-chase re after allocating calls (JS_ToString, js_alloc_string, JS_GetPropertyStr) */ re = js_get_regexp (ctx, this_ref.val, TRUE); re_bytecode = re->bytecode; re_flags = lre_get_flags (re_bytecode); if ((re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) == 0) last_index = 0; capture_count = lre_get_capture_count (re_bytecode); if (capture_count > 0) { capture = js_malloc_rt (sizeof (capture[0]) * capture_count * 2); if (!capture) { JS_ThrowOutOfMemory (ctx); goto fail; } } /* Refresh str after potential GC from js_malloc */ str = JS_VALUE_GET_STRING (str_ref.val); /* Convert UTF-32 string to UTF-16 for regex engine (uses js_malloc_rt, no GC) */ utf16_buf = js_string_to_utf16 (ctx, str, &utf16_len); if (!utf16_buf) goto fail; shift = 1; /* UTF-16 mode */ str_buf = (uint8_t *)utf16_buf; /* Refresh str after potential GC from js_string_to_utf16 */ str = JS_VALUE_GET_STRING (str_ref.val); if (last_index > (int)JSText_len (str)) { rc = 2; } else { rc = lre_exec (capture, re_bytecode, str_buf, last_index, (int)JSText_len (str), shift, ctx); } if (rc != 1) { if (rc >= 0) { if (rc == 2 || (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY))) { if (JS_SetPropertyStr (ctx, this_ref.val, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) goto fail; } ret = JS_NULL; goto done; } if (rc == LRE_RET_TIMEOUT) JS_ThrowInterrupted (ctx); else JS_ThrowInternalError (ctx, "out of memory in regexp execution"); goto fail; } if (re_flags & (LRE_FLAG_GLOBAL | LRE_FLAG_STICKY)) { if (JS_SetPropertyStr (ctx, this_ref.val, "lastIndex", JS_NewInt32 (ctx, (capture[1] - str_buf) >> shift)) < 0) goto fail; } res = JS_NewObjectProto (ctx, JS_NULL); if (JS_IsException (res)) goto fail; /* Root res, captures_arr, groups, match0 across allocating calls */ JSGCRef res_ref, cap_ref, grp_ref, m0_ref; JS_PushGCRef (ctx, &res_ref); res_ref.val = res; JS_PushGCRef (ctx, &cap_ref); cap_ref.val = JS_NULL; JS_PushGCRef (ctx, &grp_ref); grp_ref.val = JS_NULL; JS_PushGCRef (ctx, &m0_ref); m0_ref.val = JS_NULL; #define REGEXP_RESULT_CLEANUP() do { \ JS_PopGCRef (ctx, &m0_ref); \ JS_PopGCRef (ctx, &grp_ref); \ JS_PopGCRef (ctx, &cap_ref); \ JS_PopGCRef (ctx, &res_ref); \ } while(0) { int cap_groups = (capture_count > 1) ? (capture_count - 1) : 0; captures_arr = JS_NewArrayLen (ctx, cap_groups); if (JS_IsException (captures_arr)) { REGEXP_RESULT_CLEANUP (); goto fail; } cap_ref.val = captures_arr; } group_name_ptr = lre_get_groupnames (re_bytecode); if (group_name_ptr) { groups = JS_NewObjectProto (ctx, JS_NULL); if (JS_IsException (groups)) { REGEXP_RESULT_CLEANUP (); goto fail; } grp_ref.val = groups; } { int match_start = -1; int match_end = -1; for (i = 0; i < capture_count; i++) { const char *name = NULL; uint8_t **m = &capture[2 * i]; int start = -1; int end = -1; JSValue s; if (group_name_ptr && i > 0) { if (*group_name_ptr) name = group_name_ptr; group_name_ptr += strlen (group_name_ptr) + 1; } if (m[0] && m[1]) { start = (m[0] - str_buf) >> shift; end = (m[1] - str_buf) >> shift; } s = JS_NULL; if (start != -1) { str = JS_VALUE_GET_STRING (str_ref.val); s = js_sub_string (ctx, str, start, end); if (JS_IsException (s)) { REGEXP_RESULT_CLEANUP (); goto fail; } } if (i == 0) { match_start = start; match_end = end; match0 = s; m0_ref.val = match0; continue; } if (name) { groups = grp_ref.val; if (JS_SetPropertyStr (ctx, groups, name, s) < 0) { REGEXP_RESULT_CLEANUP (); goto fail; } } captures_arr = cap_ref.val; if (JS_SetPropertyUint32 (ctx, captures_arr, (uint32_t)(i - 1), s) < 0) { REGEXP_RESULT_CLEANUP (); goto fail; } } if (match_start < 0) match_start = 0; if (match_end < match_start) match_end = match_start; res = res_ref.val; if (JS_SetPropertyStr (ctx, res, "index", JS_NewInt32 (ctx, match_start)) < 0) { REGEXP_RESULT_CLEANUP (); goto fail; } res = res_ref.val; if (JS_SetPropertyStr (ctx, res, "end", JS_NewInt32 (ctx, match_end)) < 0) { REGEXP_RESULT_CLEANUP (); goto fail; } res = res_ref.val; match0 = m0_ref.val; if (JS_SetPropertyStr (ctx, res, "match", match0) < 0) { REGEXP_RESULT_CLEANUP (); goto fail; } res = res_ref.val; captures_arr = cap_ref.val; if (JS_SetPropertyStr (ctx, res, "captures", captures_arr) < 0) { REGEXP_RESULT_CLEANUP (); goto fail; } groups = grp_ref.val; if (!JS_IsNull (groups)) { res = res_ref.val; if (JS_SetPropertyStr (ctx, res, "groups", groups) < 0) { REGEXP_RESULT_CLEANUP (); goto fail; } } else { res = res_ref.val; JS_SetPropertyStr (ctx, res, "groups", JS_NULL); } } ret = res_ref.val; REGEXP_RESULT_CLEANUP (); done: JS_PopGCRef (ctx, &str_ref); JS_PopGCRef (ctx, &this_ref); js_free_rt (capture); js_free_rt (utf16_buf); return ret; fail: JS_PopGCRef (ctx, &str_ref); JS_PopGCRef (ctx, &this_ref); js_free_rt (capture); js_free_rt (utf16_buf); return JS_EXCEPTION; } static const JSCFunctionListEntry js_regexp_proto_funcs[] = { JS_CFUNC_DEF ("exec", 1, js_regexp_exec), JS_CFUNC_DEF ("compile", 2, js_regexp_compile), JS_CFUNC_DEF ("toString", 0, js_regexp_toString), }; void JS_AddIntrinsicRegExpCompiler (JSContext *ctx) { ctx->compile_regexp = js_compile_regexp; } static void JS_AddIntrinsicRegExp (JSContext *ctx) { JSValue obj; JS_AddIntrinsicRegExpCompiler (ctx); ctx->class_proto[JS_CLASS_REGEXP] = JS_NewObject (ctx); JS_SetPropertyFunctionList (ctx, ctx->class_proto[JS_CLASS_REGEXP], js_regexp_proto_funcs, countof (js_regexp_proto_funcs)); obj = JS_NewCFunction2 (ctx, js_regexp_constructor, "RegExp", 2, JS_CFUNC_generic, 0); JS_SetPropertyStr (ctx, ctx->global_obj, "RegExp", obj); ctx->regexp_ctor = obj; } static JSValue internalize_json_property (JSContext *ctx, JSValue holder, JSValue name, JSValue reviver) { JSValue val, new_el, res; JSValue args[2]; int ret, is_array; uint32_t i, len = 0; JSValue prop; if (js_check_stack_overflow (ctx, 0)) { return JS_ThrowStackOverflow (ctx); } val = JS_GetProperty (ctx, holder, name); if (JS_IsException (val)) return val; is_array = JS_IsArray (val); if (is_array < 0) goto fail; if (is_array || JS_IsObject (val)) { if (is_array) { if (js_get_length32 (ctx, &len, val)) goto fail; } else { /* Object property iteration not yet implemented for JSValue keys */ len = 0; } for (i = 0; i < len; i++) { /* For arrays, use integer index as key */ prop = JS_NewInt32 (ctx, i); new_el = internalize_json_property (ctx, val, prop, reviver); if (JS_IsException (new_el)) { goto fail; } if (JS_IsNull (new_el)) { ret = JS_DeleteProperty (ctx, val, prop); } else { ret = JS_SetPropertyInternal (ctx, val, prop, new_el); } if (ret < 0) goto fail; } } /* name is already a JSValue, use it directly */ args[0] = name; args[1] = val; res = JS_Call (ctx, reviver, holder, 2, args); return res; fail: return JS_EXCEPTION; } static JSValue js_json_parse (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { JSValue obj, root; JSValue reviver; const char *str; size_t len; str = JS_ToCStringLen (ctx, &len, argv[0]); if (!str) return JS_EXCEPTION; obj = JS_ParseJSON (ctx, str, len, ""); JS_FreeCString (ctx, str); if (JS_IsException (obj)) return obj; if (argc > 1 && JS_IsFunction (argv[1])) { reviver = argv[1]; root = JS_NewObject (ctx); if (JS_IsException (root)) { return JS_EXCEPTION; } if (JS_SetPropertyInternal (ctx, root, JS_KEY_empty, obj) < 0) { return JS_EXCEPTION; } obj = internalize_json_property (ctx, root, JS_KEY_empty, reviver); } return obj; } typedef struct JSONStringifyContext { JSContext *ctx; JSValue replacer_func; JSValue stack; JSValue property_list; JSValue gap; JSValue empty; JSGCRef b_root; /* GC root for buffer - use JSC_B_GET/SET macros */ } JSONStringifyContext; /* Macros to access the buffer from the rooted JSValue */ #define JSC_B_GET(jsc) JS_VALUE_GET_STRING((jsc)->b_root.val) #define JSC_B_SET(jsc, ptr) ((jsc)->b_root.val = JS_MKPTR(ptr)) #define JSC_B_PUTC(jsc, c) do { \ JSText *_b = pretext_putc(ctx, JSC_B_GET(jsc), c); \ if (!_b) goto exception; \ JSC_B_SET(jsc, _b); \ } while(0) #define JSC_B_CONCAT(jsc, v) do { \ JSText *_b = pretext_concat_value(ctx, JSC_B_GET(jsc), v); \ if (!_b) goto exception; \ JSC_B_SET(jsc, _b); \ } while(0) static JSValue js_json_check (JSContext *ctx, JSONStringifyContext *jsc, JSValue holder, JSValue val, JSValue key) { JSValue v; JSValue args[2]; /* check for object.toJSON method */ /* ECMA specifies this is done only for Object and BigInt */ if (JS_IsObject (val)) { JSValue f = JS_GetProperty (ctx, val, JS_KEY_toJSON); if (JS_IsException (f)) goto exception; if (JS_IsFunction (f)) { v = JS_Call (ctx, f, val, 1, &key); val = v; if (JS_IsException (val)) goto exception; } else { } } if (!JS_IsNull (jsc->replacer_func)) { args[0] = key; args[1] = val; v = JS_Call (ctx, jsc->replacer_func, holder, 2, args); val = v; if (JS_IsException (val)) goto exception; } switch (JS_VALUE_GET_NORM_TAG (val)) { case JS_TAG_PTR: /* includes arrays (OBJ_ARRAY) via mist_hdr */ if (JS_IsFunction (val)) break; /* fall through */ case JS_TAG_STRING_IMM: case JS_TAG_INT: case JS_TAG_FLOAT64: case JS_TAG_BOOL: case JS_TAG_NULL: case JS_TAG_EXCEPTION: return val; default: break; } return JS_NULL; exception: return JS_EXCEPTION; } static int js_json_to_str (JSContext *ctx, JSONStringifyContext *jsc, JSValue holder, JSValue val, JSValue indent) { JSValue v; int64_t i, len; int ret; BOOL has_content; JSGCRef val_ref, indent_ref, indent1_ref, sep_ref, sep1_ref, tab_ref, prop_ref; /* Root all values that can be heap pointers and survive across GC points */ JS_PushGCRef (ctx, &val_ref); JS_PushGCRef (ctx, &indent_ref); JS_PushGCRef (ctx, &indent1_ref); JS_PushGCRef (ctx, &sep_ref); JS_PushGCRef (ctx, &sep1_ref); JS_PushGCRef (ctx, &tab_ref); JS_PushGCRef (ctx, &prop_ref); val_ref.val = val; indent_ref.val = indent; indent1_ref.val = JS_NULL; sep_ref.val = JS_NULL; sep1_ref.val = JS_NULL; tab_ref.val = JS_NULL; prop_ref.val = JS_NULL; if (js_check_stack_overflow (ctx, 0)) { JS_ThrowStackOverflow (ctx); goto exception; } if (JS_IsObject ( val_ref.val)) { /* includes arrays (OBJ_ARRAY) since they have JS_TAG_PTR */ v = js_array_includes (ctx, jsc->stack, 1, &val_ref.val); if (JS_IsException (v)) goto exception; if (JS_ToBool (ctx, v)) { JS_ThrowTypeError (ctx, "circular reference"); goto exception; } indent1_ref.val = JS_ConcatString (ctx, indent_ref.val, jsc->gap); if (JS_IsException (indent1_ref.val)) goto exception; if (!JS_IsEmptyString (jsc->gap)) { sep_ref.val = JS_ConcatString3 (ctx, "\n", indent1_ref.val, ""); if (JS_IsException (sep_ref.val)) goto exception; sep1_ref.val = js_new_string8 (ctx, " "); if (JS_IsException (sep1_ref.val)) goto exception; } else { sep_ref.val = jsc->empty; sep1_ref.val = jsc->empty; } v = js_cell_push (ctx, jsc->stack, 1, &val_ref.val); if (check_exception_free (ctx, v)) goto exception; ret = JS_IsArray (val_ref.val); if (ret < 0) goto exception; if (ret) { if (js_get_length64 (ctx, &len, val_ref.val)) goto exception; JSC_B_PUTC (jsc, '['); for (i = 0; i < len; i++) { if (i > 0) { JSC_B_PUTC (jsc, ','); } JSC_B_CONCAT (jsc, sep_ref.val); v = JS_GetPropertyInt64 (ctx, val_ref.val, i); if (JS_IsException (v)) goto exception; /* XXX: could do this string conversion only when needed */ prop_ref.val = JS_ToString (ctx, JS_NewInt64 (ctx, i)); if (JS_IsException (prop_ref.val)) goto exception; v = js_json_check (ctx, jsc, val_ref.val, v, prop_ref.val); prop_ref.val = JS_NULL; if (JS_IsException (v)) goto exception; if (JS_IsNull (v)) v = JS_NULL; if (js_json_to_str (ctx, jsc, val_ref.val, v, indent1_ref.val)) goto exception; } if (len > 0 && !JS_IsEmptyString (jsc->gap)) { JSC_B_PUTC (jsc, '\n'); JSC_B_CONCAT (jsc, indent_ref.val); } JSC_B_PUTC (jsc, ']'); } else { if (!JS_IsNull (jsc->property_list)) tab_ref.val = jsc->property_list; else tab_ref.val = JS_GetOwnPropertyNames (ctx, val_ref.val); if (JS_IsException (tab_ref.val)) goto exception; if (js_get_length64 (ctx, &len, tab_ref.val)) goto exception; JSC_B_PUTC (jsc, '{'); has_content = FALSE; for (i = 0; i < len; i++) { prop_ref.val = JS_GetPropertyInt64 (ctx, tab_ref.val, i); if (JS_IsException (prop_ref.val)) goto exception; v = JS_GetPropertyValue (ctx, val_ref.val, prop_ref.val); if (JS_IsException (v)) goto exception; v = js_json_check (ctx, jsc, val_ref.val, v, prop_ref.val); if (JS_IsException (v)) goto exception; if (!JS_IsNull (v)) { if (has_content) { JSC_B_PUTC (jsc, ','); } prop_ref.val = JS_ToQuotedString (ctx, prop_ref.val); if (JS_IsException (prop_ref.val)) { goto exception; } JSC_B_CONCAT (jsc, sep_ref.val); JSC_B_CONCAT (jsc, prop_ref.val); JSC_B_PUTC (jsc, ':'); JSC_B_CONCAT (jsc, sep1_ref.val); if (js_json_to_str (ctx, jsc, val_ref.val, v, indent1_ref.val)) goto exception; has_content = TRUE; } } if (has_content && !JS_IsEmptyString (jsc->gap)) { JSC_B_PUTC (jsc, '\n'); JSC_B_CONCAT (jsc, indent_ref.val); } JSC_B_PUTC (jsc, '}'); } if (check_exception_free (ctx, js_cell_pop (ctx, jsc->stack, 0, NULL))) goto exception; goto done; } switch (JS_VALUE_GET_NORM_TAG (val_ref.val)) { case JS_TAG_STRING_IMM: val_ref.val = JS_ToQuotedString (ctx, val_ref.val); if (JS_IsException (val_ref.val)) goto exception; goto concat_value; case JS_TAG_FLOAT64: if (!isfinite (JS_VALUE_GET_FLOAT64 (val_ref.val))) { val_ref.val = JS_NULL; } goto concat_value; case JS_TAG_INT: case JS_TAG_BOOL: case JS_TAG_NULL: concat_value: { JSText *_b = pretext_concat_value (ctx, JSC_B_GET (jsc), val_ref.val); if (!_b) goto exception_ret; JSC_B_SET (jsc, _b); goto done; } default: goto done; } done: JS_PopGCRef (ctx, &prop_ref); JS_PopGCRef (ctx, &tab_ref); JS_PopGCRef (ctx, &sep1_ref); JS_PopGCRef (ctx, &sep_ref); JS_PopGCRef (ctx, &indent1_ref); JS_PopGCRef (ctx, &indent_ref); JS_PopGCRef (ctx, &val_ref); return 0; exception_ret: JS_PopGCRef (ctx, &prop_ref); JS_PopGCRef (ctx, &tab_ref); JS_PopGCRef (ctx, &sep1_ref); JS_PopGCRef (ctx, &sep_ref); JS_PopGCRef (ctx, &indent1_ref); JS_PopGCRef (ctx, &indent_ref); JS_PopGCRef (ctx, &val_ref); return -1; exception: JS_PopGCRef (ctx, &prop_ref); JS_PopGCRef (ctx, &tab_ref); JS_PopGCRef (ctx, &sep1_ref); JS_PopGCRef (ctx, &sep_ref); JS_PopGCRef (ctx, &indent1_ref); JS_PopGCRef (ctx, &indent_ref); JS_PopGCRef (ctx, &val_ref); return -1; } JSValue JS_JSONStringify (JSContext *ctx, JSValue obj, JSValue replacer, JSValue space0) { JSONStringifyContext jsc_s, *jsc = &jsc_s; JSValue val, v, space, ret, wrapper; int res; int64_t i, j, n; JSGCRef obj_ref; /* Root obj since GC can happen during stringify setup */ JS_PushGCRef (ctx, &obj_ref); obj_ref.val = obj; jsc->ctx = ctx; jsc->replacer_func = JS_NULL; jsc->stack = JS_NULL; jsc->property_list = JS_NULL; jsc->gap = JS_NULL; jsc->empty = JS_KEY_empty; ret = JS_NULL; wrapper = JS_NULL; /* Root the buffer for GC safety */ JS_PushGCRef (ctx, &jsc->b_root); { JSText *b_init = pretext_init (ctx, 0); if (!b_init) goto exception; JSC_B_SET (jsc, b_init); } jsc->stack = JS_NewArray (ctx); if (JS_IsException (jsc->stack)) goto exception; if (JS_IsFunction (replacer)) { jsc->replacer_func = replacer; } else { res = JS_IsArray (replacer); if (res < 0) goto exception; if (res) { /* XXX: enumeration is not fully correct */ jsc->property_list = JS_NewArray (ctx); if (JS_IsException (jsc->property_list)) goto exception; if (js_get_length64 (ctx, &n, replacer)) goto exception; for (i = j = 0; i < n; i++) { JSValue present; v = JS_GetPropertyInt64 (ctx, replacer, i); if (JS_IsException (v)) goto exception; if (JS_IsObject (v)) { /* Objects are not valid property list items */ continue; } else if (JS_IsNumber (v)) { v = JS_ToString (ctx, v); if (JS_IsException (v)) goto exception; } else if (!JS_IsText (v)) { continue; } present = js_array_includes (ctx, jsc->property_list, 1, (JSValue *)&v); if (JS_IsException (present)) { goto exception; } if (!JS_ToBool (ctx, present)) { JS_SetPropertyInt64 (ctx, jsc->property_list, j++, v); } else { } } } } space = space0; if (JS_IsNumber (space)) { int n; if (JS_ToInt32Clamp (ctx, &n, space, 0, 10, 0)) goto exception; jsc->gap = js_new_string8_len (ctx, " ", n); } else if (JS_IsText (space)) { JSText *p = JS_VALUE_GET_STRING (space); jsc->gap = js_sub_string (ctx, p, 0, min_int ((int)JSText_len (p), 10)); } else { jsc->gap = jsc->empty; } if (JS_IsException (jsc->gap)) goto exception; wrapper = JS_NewObject (ctx); if (JS_IsException (wrapper)) goto exception; if (JS_SetPropertyInternal (ctx, wrapper, JS_KEY_empty, obj_ref.val) < 0) goto exception; val = obj_ref.val; val = js_json_check (ctx, jsc, wrapper, val, jsc->empty); if (JS_IsException (val)) goto exception; if (JS_IsNull (val)) { ret = JS_NULL; goto done1; } if (js_json_to_str (ctx, jsc, wrapper, val, jsc->empty)) goto exception; ret = pretext_end (ctx, JSC_B_GET (jsc)); goto done; exception: ret = JS_EXCEPTION; done1: done: JS_PopGCRef (ctx, &jsc->b_root); JS_PopGCRef (ctx, &obj_ref); return ret; } /* ============================================================================ * Cell Script Native Global Functions * ============================================================================ * These functions implement the core Cell script primitives: * - text: string conversion and manipulation * - number: number conversion and math utilities * - array: array creation and manipulation * - object: object creation and manipulation * - fn: function utilities * ============================================================================ */ /* ---------------------------------------------------------------------------- * number function and sub-functions * ---------------------------------------------------------------------------- */ /* number(val, format) - convert to number */ static JSValue js_cell_number (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; JSValue val = argv[0]; int tag = JS_VALUE_GET_TAG (val); /* Handle boolean */ if (tag == JS_TAG_BOOL) { return JS_NewInt32 (ctx, JS_VALUE_GET_BOOL (val) ? 1 : 0); } /* Handle number - return as-is */ if (tag == JS_TAG_INT || tag == JS_TAG_FLOAT64) { return val; } /* Handle string */ if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) { const char *str = JS_ToCString (ctx, val); if (!str) return JS_EXCEPTION; JSValue result; /* Check for format argument */ if (argc > 1 && JS_VALUE_GET_TAG (argv[1]) == JS_TAG_INT) { /* Radix conversion */ int radix = JS_VALUE_GET_INT (argv[1]); if (radix < 2 || radix > 36) { JS_FreeCString (ctx, str); return JS_NULL; } char *endptr; long long n = strtoll (str, &endptr, radix); if (endptr == str || *endptr != '\0') { JS_FreeCString (ctx, str); return JS_NULL; } result = JS_NewInt64 (ctx, n); } else if (argc > 1 && (JS_VALUE_GET_TAG (argv[1]) == JS_TAG_STRING || JS_VALUE_GET_TAG (argv[1]) == JS_TAG_STRING_IMM)) { /* Format string */ const char *format = JS_ToCString (ctx, argv[1]); if (!format) { JS_FreeCString (ctx, str); return JS_EXCEPTION; } char *clean = js_malloc (ctx, strlen (str) + 1); if (!clean) { JS_FreeCString (ctx, format); JS_FreeCString (ctx, str); return JS_EXCEPTION; } const char *p = str; char *q = clean; if (strcmp (format, "u") == 0) { /* underbar separator */ while (*p) { if (*p != '_') *q++ = *p; p++; } } else if (strcmp (format, "d") == 0 || strcmp (format, "l") == 0) { /* comma separator */ while (*p) { if (*p != ',') *q++ = *p; p++; } } else if (strcmp (format, "s") == 0) { /* space separator */ while (*p) { if (*p != ' ') *q++ = *p; p++; } } else if (strcmp (format, "v") == 0) { /* European style: period separator, comma decimal */ while (*p) { if (*p == '.') { p++; continue; } if (*p == ',') { *q++ = '.'; p++; continue; } *q++ = *p++; } } else if (strcmp (format, "b") == 0) { *q = '\0'; char *endptr; long long n = strtoll (str, &endptr, 2); js_free (ctx, clean); JS_FreeCString (ctx, format); JS_FreeCString (ctx, str); if (endptr == str) return JS_NULL; return JS_NewInt64 (ctx, n); } else if (strcmp (format, "o") == 0) { *q = '\0'; char *endptr; long long n = strtoll (str, &endptr, 8); js_free (ctx, clean); JS_FreeCString (ctx, format); JS_FreeCString (ctx, str); if (endptr == str) return JS_NULL; return JS_NewInt64 (ctx, n); } else if (strcmp (format, "h") == 0) { *q = '\0'; char *endptr; long long n = strtoll (str, &endptr, 16); js_free (ctx, clean); JS_FreeCString (ctx, format); JS_FreeCString (ctx, str); if (endptr == str) return JS_NULL; return JS_NewInt64 (ctx, n); } else if (strcmp (format, "t") == 0) { *q = '\0'; char *endptr; long long n = strtoll (str, &endptr, 32); js_free (ctx, clean); JS_FreeCString (ctx, format); JS_FreeCString (ctx, str); if (endptr == str) return JS_NULL; return JS_NewInt64 (ctx, n); } else if (strcmp (format, "j") == 0) { /* JavaScript style prefix */ js_free (ctx, clean); JS_FreeCString (ctx, format); int radix = 10; const char *start = str; if (str[0] == '0' && (str[1] == 'x' || str[1] == 'X')) { radix = 16; start = str + 2; } else if (str[0] == '0' && (str[1] == 'o' || str[1] == 'O')) { radix = 8; start = str + 2; } else if (str[0] == '0' && (str[1] == 'b' || str[1] == 'B')) { radix = 2; start = str + 2; } if (radix != 10) { char *endptr; long long n = strtoll (start, &endptr, radix); JS_FreeCString (ctx, str); if (endptr == start) return JS_NULL; return JS_NewInt64 (ctx, n); } double d = strtod (str, NULL); JS_FreeCString (ctx, str); return JS_NewFloat64 (ctx, d); } else { /* Unknown format, just copy */ strcpy (clean, str); q = clean + strlen (clean); } *q = '\0'; double d = strtod (clean, NULL); js_free (ctx, clean); JS_FreeCString (ctx, format); JS_FreeCString (ctx, str); if (isnan (d)) return JS_NULL; return JS_NewFloat64 (ctx, d); } else { /* Default: parse as decimal */ char *endptr; double d = strtod (str, &endptr); JS_FreeCString (ctx, str); if (endptr == str || isnan (d)) return JS_NULL; result = JS_NewFloat64 (ctx, d); } return result; } return JS_NULL; } /* number.whole(n) - truncate to integer */ static JSValue js_cell_number_whole (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double d; if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; return JS_NewFloat64 (ctx, trunc (d)); } /* number.fraction(n) - get fractional part */ static JSValue js_cell_number_fraction (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double d; if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; return JS_NewFloat64 (ctx, d - trunc (d)); } /* number.floor(n, place) - floor with optional decimal place */ static JSValue js_cell_number_floor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double d; if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; if (argc < 2 || JS_IsNull (argv[1])) { return JS_NewFloat64 (ctx, floor (d)); } int place; if (JS_ToInt32 (ctx, &place, argv[1])) return JS_NULL; if (place == 0) return JS_NewFloat64 (ctx, floor (d)); double mult = pow (10, -place); return JS_NewFloat64 (ctx, floor (d * mult) / mult); } /* number.ceiling(n, place) - ceiling with optional decimal place */ static JSValue js_cell_number_ceiling (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double d; if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; if (argc < 2 || JS_IsNull (argv[1])) { return JS_NewFloat64 (ctx, ceil (d)); } int place; if (JS_ToInt32 (ctx, &place, argv[1])) return JS_NULL; if (place == 0) return JS_NewFloat64 (ctx, ceil (d)); double mult = pow (10, -place); return JS_NewFloat64 (ctx, ceil (d * mult) / mult); } /* number.abs(n) - absolute value */ static JSValue js_cell_number_abs (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; int tag = JS_VALUE_GET_TAG (argv[0]); if (tag != JS_TAG_INT && tag != JS_TAG_FLOAT64) return JS_NULL; double d; if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; return JS_NewFloat64 (ctx, fabs (d)); } /* number.round(n, place) - round with optional decimal place */ static JSValue js_cell_number_round (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double d; if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; if (argc < 2 || JS_IsNull (argv[1])) { return JS_NewFloat64 (ctx, round (d)); } int place; if (JS_ToInt32 (ctx, &place, argv[1])) return JS_NULL; if (place == 0) return JS_NewFloat64 (ctx, round (d)); double mult = pow (10, -place); return JS_NewFloat64 (ctx, round (d * mult) / mult); } /* number.sign(n) - return sign (-1, 0, 1) */ static JSValue js_cell_number_sign (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double d; if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; if (d < 0) return JS_NewInt32 (ctx, -1); if (d > 0) return JS_NewInt32 (ctx, 1); return JS_NewInt32 (ctx, 0); } /* number.trunc(n, place) - truncate with optional decimal place */ static JSValue js_cell_number_trunc (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double d; if (JS_ToFloat64 (ctx, &d, argv[0])) return JS_NULL; if (argc < 2 || JS_IsNull (argv[1])) { return JS_NewFloat64 (ctx, trunc (d)); } int place; if (JS_ToInt32 (ctx, &place, argv[1])) return JS_NULL; if (place == 0) return JS_NewFloat64 (ctx, trunc (d)); double mult = pow (10, -place); return JS_NewFloat64 (ctx, trunc (d * mult) / mult); } /* number.min(...vals) - minimum value */ static JSValue js_cell_number_min (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc == 0) return JS_NULL; double result; if (JS_ToFloat64 (ctx, &result, argv[0])) return JS_NULL; for (int i = 1; i < argc; i++) { double d; if (JS_ToFloat64 (ctx, &d, argv[i])) return JS_NULL; if (d < result) result = d; } return JS_NewFloat64 (ctx, result); } /* number.max(...vals) - maximum value */ static JSValue js_cell_number_max (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc == 0) return JS_NULL; double result; if (JS_ToFloat64 (ctx, &result, argv[0])) return JS_NULL; for (int i = 1; i < argc; i++) { double d; if (JS_ToFloat64 (ctx, &d, argv[i])) return JS_NULL; if (d > result) result = d; } return JS_NewFloat64 (ctx, result); } /* number.remainder(dividend, divisor) - remainder after division */ static JSValue js_cell_number_remainder (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; double dividend, divisor; if (JS_ToFloat64 (ctx, ÷nd, argv[0])) return JS_NULL; if (JS_ToFloat64 (ctx, &divisor, argv[1])) return JS_NULL; if (divisor == 0) return JS_NULL; return JS_NewFloat64 (ctx, dividend - (trunc (dividend / divisor) * divisor)); } /* ---------------------------------------------------------------------------- * text function and sub-functions * ---------------------------------------------------------------------------- */ /* Helper: convert number to string with radix */ static JSValue js_cell_number_to_radix_string (JSContext *ctx, double num, int radix) { if (radix < 2 || radix > 36) return JS_NULL; /* For base 10, handle floating point properly */ if (radix == 10) { char buf[64]; /* Check if it's an integer */ if (trunc (num) == num && num >= -9007199254740991.0 && num <= 9007199254740991.0) { snprintf (buf, sizeof (buf), "%.0f", num); } else { /* Use %g to get a reasonable representation without trailing zeros */ snprintf (buf, sizeof (buf), "%.15g", num); } return JS_NewString (ctx, buf); } /* For other radixes, use integer conversion */ static const char digits[] = "0123456789abcdefghijklmnopqrstuvwxyz"; char buf[70]; int len = 0; int negative = 0; int64_t n = (int64_t)trunc (num); if (n < 0) { negative = 1; n = -n; } if (n == 0) { buf[len++] = '0'; } else { while (n > 0) { buf[len++] = digits[n % radix]; n /= radix; } } if (negative) { buf[len++] = '-'; } /* Reverse the string */ char result[72]; int j = 0; for (int i = len - 1; i >= 0; i--) { result[j++] = buf[i]; } result[j] = '\0'; return JS_NewString (ctx, result); } /* Helper: add separator every n digits from right */ static char *add_separator (JSContext *ctx, const char *str, char sep, int n) { if (n <= 0) { char *result = js_malloc (ctx, strlen (str) + 1); if (result) strcpy (result, str); return result; } int negative = (str[0] == '-'); const char *start = negative ? str + 1 : str; /* Find decimal point */ const char *decimal = strchr (start, '.'); int int_len = decimal ? (int)(decimal - start) : (int)strlen (start); int num_seps = (int_len - 1) / n; int result_len = strlen (str) + num_seps + 1; char *result = js_malloc (ctx, result_len); if (!result) return NULL; char *q = result; if (negative) *q++ = '-'; int count = int_len % n; if (count == 0) count = n; for (int i = 0; i < int_len; i++) { if (i > 0 && count == 0) { *q++ = sep; count = n; } *q++ = start[i]; count--; } if (decimal) { strcpy (q, decimal); } else { *q = '\0'; } return result; } /* Helper: format number with format string */ static JSValue js_cell_format_number (JSContext *ctx, double num, const char *format) { int separation = 0; char style = '\0'; int places = 0; int i = 0; /* Parse separation digit */ if (format[i] >= '0' && format[i] <= '9') { separation = format[i] - '0'; i++; } /* Parse style letter */ if (format[i]) { style = format[i]; i++; } else { return JS_NULL; } /* Parse places digits */ if (format[i] >= '0' && format[i] <= '9') { places = format[i] - '0'; i++; if (format[i] >= '0' && format[i] <= '9') { places = places * 10 + (format[i] - '0'); i++; } } /* Invalid if more characters */ if (format[i] != '\0') return JS_NULL; char buf[128]; char *result_str = NULL; switch (style) { case 'e': { /* Exponential */ if (places > 0) snprintf (buf, sizeof (buf), "%.*e", places, num); else snprintf (buf, sizeof (buf), "%e", num); return JS_NewString (ctx, buf); } case 'n': { /* Number - scientific for extreme values */ if (fabs (num) >= 1e21 || (fabs (num) < 1e-6 && num != 0)) { snprintf (buf, sizeof (buf), "%e", num); } else if (places > 0) { snprintf (buf, sizeof (buf), "%.*f", places, num); } else { snprintf (buf, sizeof (buf), "%g", num); } return JS_NewString (ctx, buf); } case 's': { /* Space separated */ if (separation == 0) separation = 3; snprintf (buf, sizeof (buf), "%.*f", places, num); result_str = add_separator (ctx, buf, ' ', separation); if (!result_str) return JS_EXCEPTION; JSValue ret = JS_NewString (ctx, result_str); js_free (ctx, result_str); return ret; } case 'u': { /* Underbar separated */ snprintf (buf, sizeof (buf), "%.*f", places, num); if (separation > 0) { result_str = add_separator (ctx, buf, '_', separation); if (!result_str) return JS_EXCEPTION; JSValue ret = JS_NewString (ctx, result_str); js_free (ctx, result_str); return ret; } return JS_NewString (ctx, buf); } case 'd': case 'l': { /* Decimal/locale with comma separator */ if (separation == 0) separation = 3; if (places == 0 && style == 'd') places = 2; snprintf (buf, sizeof (buf), "%.*f", places, num); result_str = add_separator (ctx, buf, ',', separation); if (!result_str) return JS_EXCEPTION; JSValue ret = JS_NewString (ctx, result_str); js_free (ctx, result_str); return ret; } case 'v': { /* European style: comma decimal, period separator */ snprintf (buf, sizeof (buf), "%.*f", places, num); /* Replace . with , */ for (char *p = buf; *p; p++) { if (*p == '.') *p = ','; } if (separation > 0) { result_str = add_separator (ctx, buf, '.', separation); if (!result_str) return JS_EXCEPTION; JSValue ret = JS_NewString (ctx, result_str); js_free (ctx, result_str); return ret; } return JS_NewString (ctx, buf); } case 'i': { /* Integer base 10 */ if (places == 0) places = 1; int64_t n = (int64_t)trunc (num); int neg = n < 0; if (neg) n = -n; snprintf (buf, sizeof (buf), "%lld", (long long)n); int len = strlen (buf); /* Pad with zeros */ if (len < places) { memmove (buf + (places - len), buf, len + 1); memset (buf, '0', places - len); } if (separation > 0) { result_str = add_separator (ctx, buf, '_', separation); if (!result_str) return JS_EXCEPTION; if (neg) { char *final = js_malloc (ctx, strlen (result_str) + 2); if (!final) { js_free (ctx, result_str); return JS_EXCEPTION; } final[0] = '-'; strcpy (final + 1, result_str); js_free (ctx, result_str); JSValue ret = JS_NewString (ctx, final); js_free (ctx, final); return ret; } JSValue ret = JS_NewString (ctx, result_str); js_free (ctx, result_str); return ret; } if (neg) { memmove (buf + 1, buf, strlen (buf) + 1); buf[0] = '-'; } return JS_NewString (ctx, buf); } case 'b': { /* Binary */ if (places == 0) places = 1; return js_cell_number_to_radix_string (ctx, num, 2); } case 'o': { /* Octal */ if (places == 0) places = 1; int64_t n = (int64_t)trunc (num); snprintf (buf, sizeof (buf), "%llo", (long long)(n < 0 ? -n : n)); /* Uppercase and pad */ for (char *p = buf; *p; p++) *p = toupper (*p); int len = strlen (buf); if (len < places) { memmove (buf + (places - len), buf, len + 1); memset (buf, '0', places - len); } if (n < 0) { memmove (buf + 1, buf, strlen (buf) + 1); buf[0] = '-'; } return JS_NewString (ctx, buf); } case 'h': { /* Hexadecimal */ if (places == 0) places = 1; int64_t n = (int64_t)trunc (num); snprintf (buf, sizeof (buf), "%llX", (long long)(n < 0 ? -n : n)); int len = strlen (buf); if (len < places) { memmove (buf + (places - len), buf, len + 1); memset (buf, '0', places - len); } if (n < 0) { memmove (buf + 1, buf, strlen (buf) + 1); buf[0] = '-'; } return JS_NewString (ctx, buf); } case 't': { /* Base32 */ if (places == 0) places = 1; return js_cell_number_to_radix_string (ctx, num, 32); } } return JS_NULL; } /* Forward declaration for blob helper */ blob *js_get_blob (JSContext *ctx, JSValue val); /* modulo(dividend, divisor) - result has sign of divisor */ static JSValue js_cell_modulo (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; double dividend, divisor; if (JS_ToFloat64 (ctx, ÷nd, argv[0])) return JS_NULL; if (JS_ToFloat64 (ctx, &divisor, argv[1])) return JS_NULL; /* If either operand is NaN, return null */ if (isnan (dividend) || isnan (divisor)) return JS_NULL; /* If divisor is 0, return null */ if (divisor == 0) return JS_NULL; /* If dividend is 0, return 0 */ if (dividend == 0) return JS_NewFloat64 (ctx, 0.0); /* modulo = dividend - (divisor * floor(dividend / divisor)) */ double result = dividend - (divisor * floor (dividend / divisor)); return JS_NewFloat64 (ctx, result); } /* not(bool) - negate a boolean value */ static JSValue js_cell_not (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; if (!JS_IsBool (argv[0])) return JS_NULL; return JS_NewBool (ctx, !JS_ToBool (ctx, argv[0])); } /* neg(number) - negate a number */ static JSValue js_cell_neg (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; double num; if (JS_ToFloat64 (ctx, &num, argv[0])) return JS_NULL; if (isnan (num)) return JS_NULL; return JS_NewFloat64 (ctx, -num); } /* character(value) - get character from text or codepoint */ JSValue js_cell_character (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NewString (ctx, ""); JSValue arg = argv[0]; int tag = JS_VALUE_GET_TAG (arg); /* Handle string - return first character */ if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) { if (js_string_value_len (arg) == 0) return JS_NewString (ctx, ""); return js_sub_string_val (ctx, arg, 0, 1); } /* Handle integer - return character from codepoint */ if (tag == JS_TAG_INT) { int32_t val = JS_VALUE_GET_INT (arg); if (val < 0 || val > 0x10FFFF) return JS_NewString (ctx, ""); uint32_t codepoint = (uint32_t)val; if (codepoint < 0x80) { char buf[2] = { (char)codepoint, '\0' }; return JS_NewString (ctx, buf); } /* Create single-codepoint UTF-32 string */ JSText *str = js_alloc_string (ctx, 1); if (!str) return JS_EXCEPTION; string_put (str, 0, codepoint); str->length = 1; return pretext_end (ctx, str); } /* Handle float - convert to integer if non-negative and within range */ if (tag == JS_TAG_FLOAT64) { double d = JS_VALUE_GET_FLOAT64 (arg); if (isnan (d) || d < 0 || d > 0x10FFFF || d != trunc (d)) return JS_NewString (ctx, ""); uint32_t codepoint = (uint32_t)d; if (codepoint < 0x80) { char buf[2] = { (char)codepoint, '\0' }; return JS_NewString (ctx, buf); } /* Create single-codepoint UTF-32 string */ JSText *str = js_alloc_string (ctx, 1); if (!str) return JS_EXCEPTION; string_put (str, 0, codepoint); str->length = 1; return pretext_end (ctx, str); } return JS_NewString (ctx, ""); } /* text(arg, format) - main text function */ static JSValue js_cell_text (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; JSValue arg = argv[0]; int tag = JS_VALUE_GET_TAG (arg); /* Handle string / rope */ if (tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM) { JSValue str = JS_ToString (ctx, arg); /* owned + flattens rope */ if (JS_IsException (str)) return JS_EXCEPTION; if (argc == 1) return str; if (argc >= 2) { int tag1 = JS_VALUE_GET_TAG (argv[1]); if (tag1 == JS_TAG_INT || tag1 == JS_TAG_FLOAT64) { int len = js_string_value_len (str); int from, to; if (JS_ToInt32 (ctx, &from, argv[1])) return JS_EXCEPTION; if (from < 0) from += len; if (from < 0) from = 0; if (from > len) from = len; to = len; if (argc >= 3) { if (JS_ToInt32 (ctx, &to, argv[2])) return JS_EXCEPTION; if (to < 0) to += len; if (to < 0) to = 0; if (to > len) to = len; } if (from > to) return JS_NULL; return js_sub_string_val (ctx, str, from, to); } } return str; } /* Handle blob - convert to text representation */ blob *bd = js_get_blob (ctx, arg); if (bd) { if (!bd->is_stone) return JS_ThrowTypeError (ctx, "text: blob must be stone"); char format = '\0'; if (argc > 1) { const char *fmt = JS_ToCString (ctx, argv[1]); if (!fmt) return JS_EXCEPTION; format = fmt[0]; JS_FreeCString (ctx, fmt); } size_t byte_len = (bd->length + 7) / 8; const uint8_t *data = bd->data; if (format == 'h') { static const char hex[] = "0123456789abcdef"; char *result = js_malloc (ctx, byte_len * 2 + 1); if (!result) return JS_EXCEPTION; for (size_t i = 0; i < byte_len; i++) { result[i * 2] = hex[(data[i] >> 4) & 0xF]; result[i * 2 + 1] = hex[data[i] & 0xF]; } result[byte_len * 2] = '\0'; JSValue ret = JS_NewString (ctx, result); js_free (ctx, result); return ret; } else if (format == 'b') { char *result = js_malloc (ctx, bd->length + 1); if (!result) return JS_EXCEPTION; for (size_t i = 0; i < (size_t)bd->length; i++) { size_t byte_idx = i / 8; size_t bit_idx = i % 8; result[i] = (data[byte_idx] & (1u << bit_idx)) ? '1' : '0'; } result[bd->length] = '\0'; JSValue ret = JS_NewString (ctx, result); js_free (ctx, result); return ret; } else if (format == 'o') { size_t octal_len = ((size_t)bd->length + 2) / 3; char *result = js_malloc (ctx, octal_len + 1); if (!result) return JS_EXCEPTION; for (size_t i = 0; i < octal_len; i++) { int val = 0; for (int j = 0; j < 3; j++) { size_t bit_pos = i * 3 + (size_t)j; if (bit_pos < (size_t)bd->length) { size_t byte_idx = bit_pos / 8; size_t bit_idx = bit_pos % 8; if (data[byte_idx] & (1u << bit_idx)) val |= (1 << j); } } result[i] = (char)('0' + val); } result[octal_len] = '\0'; JSValue ret = JS_NewString (ctx, result); js_free (ctx, result); return ret; } else if (format == 't') { static const char b32[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; size_t b32_len = ((size_t)bd->length + 4) / 5; char *result = js_malloc (ctx, b32_len + 1); if (!result) return JS_EXCEPTION; for (size_t i = 0; i < b32_len; i++) { int val = 0; for (int j = 0; j < 5; j++) { size_t bit_pos = i * 5 + (size_t)j; if (bit_pos < (size_t)bd->length) { size_t byte_idx = bit_pos / 8; size_t bit_idx = bit_pos % 8; if (data[byte_idx] & (1u << bit_idx)) val |= (1 << j); } } result[i] = b32[val & 31]; } result[b32_len] = '\0'; JSValue ret = JS_NewString (ctx, result); js_free (ctx, result); return ret; } else { if (bd->length % 8 != 0) return JS_ThrowTypeError (ctx, "text: blob not byte-aligned for UTF-8"); return JS_NewStringLen (ctx, (const char *)data, byte_len); } } /* Handle number */ if (tag == JS_TAG_INT || tag == JS_TAG_FLOAT64) { double num; if (JS_ToFloat64 (ctx, &num, arg)) return JS_EXCEPTION; if (argc > 1) { int tag1 = JS_VALUE_GET_TAG (argv[1]); if (tag1 == JS_TAG_INT) { int radix = JS_VALUE_GET_INT (argv[1]); return js_cell_number_to_radix_string (ctx, num, radix); } if (tag1 == JS_TAG_STRING || tag1 == JS_TAG_STRING_IMM) { const char *format = JS_ToCString (ctx, argv[1]); if (!format) return JS_EXCEPTION; JSValue result = js_cell_format_number (ctx, num, format); JS_FreeCString (ctx, format); return result; } } return js_cell_number_to_radix_string (ctx, num, 10); } /* Handle array */ if (JS_IsArray (arg)) { int64_t len; JSGCRef arg_ref; JS_AddGCRef(ctx, &arg_ref); arg_ref.val = arg; if (js_get_length64 (ctx, &len, arg_ref.val)) { JS_DeleteGCRef(ctx, &arg_ref); return JS_EXCEPTION; } const char *separator = ""; BOOL sep_alloc = FALSE; if (argc > 1 && JS_VALUE_IS_TEXT (argv[1])) { separator = JS_ToCString (ctx, argv[1]); if (!separator) return JS_EXCEPTION; sep_alloc = TRUE; } JSText *b = pretext_init (ctx, 0); if (!b) { if (sep_alloc) JS_FreeCString (ctx, separator); return JS_EXCEPTION; } /* Root b across allocating calls (JS_GetPropertyInt64, JS_ToString) */ JSGCRef b_ref; JS_AddGCRef (ctx, &b_ref); b_ref.val = JS_MKPTR (b); for (int64_t i = 0; i < len; i++) { if (i > 0 && separator[0]) { b = (JSText *)chase (b_ref.val); b = pretext_puts8 (ctx, b, separator); if (!b) goto array_fail; b_ref.val = JS_MKPTR (b); } b = (JSText *)chase (b_ref.val); /* re-chase before use */ JSValue item = JS_GetPropertyInt64 (ctx, arg_ref.val, i); if (JS_IsException (item)) goto array_fail; if (!JS_VALUE_IS_TEXT (item)) { if (sep_alloc) JS_FreeCString (ctx, separator); JS_DeleteGCRef (ctx, &b_ref); JS_DeleteGCRef (ctx, &arg_ref); return JS_ThrowTypeError (ctx, "text: array element is not a string"); } JSValue item_str = JS_ToString (ctx, item); if (JS_IsException (item_str)) goto array_fail; b = (JSText *)chase (b_ref.val); /* re-chase after JS_ToString */ b = pretext_concat_value (ctx, b, item_str); if (!b) goto array_fail; b_ref.val = JS_MKPTR (b); } b = (JSText *)chase (b_ref.val); if (sep_alloc) JS_FreeCString (ctx, separator); JS_DeleteGCRef (ctx, &b_ref); JS_DeleteGCRef (ctx, &arg_ref); return pretext_end (ctx, b); array_fail: if (sep_alloc) JS_FreeCString (ctx, separator); JS_DeleteGCRef (ctx, &b_ref); JS_DeleteGCRef (ctx, &arg_ref); return JS_EXCEPTION; } /* Handle function - return source or native stub */ if (JS_IsFunction (arg)) { JSFunction *fn = JS_VALUE_GET_FUNCTION (arg); if (fn->kind == JS_FUNC_KIND_BYTECODE) { JSFunctionBytecode *b = fn->u.func.function_bytecode; if (b->has_debug && b->debug.source) return JS_NewStringLen (ctx, b->debug.source, b->debug.source_len); } const char *pref = "function "; const char *suff = "() {\n [native code]\n}"; const char *name = ""; const char *name_cstr = NULL; if (fn->kind == JS_FUNC_KIND_BYTECODE) { JSFunctionBytecode *fb = fn->u.func.function_bytecode; name_cstr = JS_ToCString (ctx, fb->func_name); if (name_cstr) name = name_cstr; } else if (!JS_IsNull (fn->name)) { name_cstr = JS_ToCString (ctx, fn->name); if (name_cstr) name = name_cstr; } size_t plen = strlen (pref); size_t nlen = strlen (name); size_t slen = strlen (suff); char *result = js_malloc (ctx, plen + nlen + slen + 1); if (!result) { if (name_cstr) JS_FreeCString (ctx, name_cstr); return JS_EXCEPTION; } memcpy (result, pref, plen); memcpy (result + plen, name, nlen); memcpy (result + plen + nlen, suff, slen + 1); JSValue ret = JS_NewString (ctx, result); js_free (ctx, result); if (name_cstr) JS_FreeCString (ctx, name_cstr); return ret; } return JS_ToString (ctx, arg); return JS_ThrowInternalError (ctx, "Could not convert to text. Tag is %d", tag); } /* text.lower(str) - convert to lowercase */ JSValue js_cell_text_lower (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; if (!JS_VALUE_IS_TEXT (argv[0])) return JS_NULL; /* Handle immediate ASCII - no GC concern */ if (MIST_IsImmediateASCII (argv[0])) { int len = MIST_GetImmediateASCIILen (argv[0]); JSText *b = pretext_init (ctx, len); if (!b) return JS_EXCEPTION; for (int i = 0; i < len; i++) { uint32_t c = MIST_GetImmediateASCIIChar (argv[0], i); if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a'; b = pretext_putc (ctx, b, c); if (!b) return JS_EXCEPTION; } return pretext_end (ctx, b); } /* Heap text: must re-chase after GC points */ int len = (int)JSText_len (JS_VALUE_GET_STRING (argv[0])); JSText *b = pretext_init (ctx, len); if (!b) return JS_EXCEPTION; for (int i = 0; i < len; i++) { JSText *p = JS_VALUE_GET_STRING (argv[0]); /* Re-chase each iteration */ uint32_t c = string_get (p, i); if (c >= 'A' && c <= 'Z') c = c - 'A' + 'a'; b = pretext_putc (ctx, b, c); if (!b) return JS_EXCEPTION; } return pretext_end (ctx, b); } /* text.upper(str) - convert to uppercase */ JSValue js_cell_text_upper (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; if (!JS_VALUE_IS_TEXT (argv[0])) return JS_NULL; /* Handle immediate ASCII - no GC concern */ if (MIST_IsImmediateASCII (argv[0])) { int len = MIST_GetImmediateASCIILen (argv[0]); JSText *b = pretext_init (ctx, len); if (!b) return JS_EXCEPTION; for (int i = 0; i < len; i++) { uint32_t c = MIST_GetImmediateASCIIChar (argv[0], i); if (c >= 'a' && c <= 'z') c = c - 'a' + 'A'; b = pretext_putc (ctx, b, c); if (!b) return JS_EXCEPTION; } return pretext_end (ctx, b); } /* Heap text: must re-chase after GC points */ int len = (int)JSText_len (JS_VALUE_GET_STRING (argv[0])); JSText *b = pretext_init (ctx, len); if (!b) return JS_EXCEPTION; for (int i = 0; i < len; i++) { JSText *p = JS_VALUE_GET_STRING (argv[0]); /* Re-chase each iteration */ uint32_t c = string_get (p, i); if (c >= 'a' && c <= 'z') c = c - 'a' + 'A'; b = pretext_putc (ctx, b, c); if (!b) return JS_EXCEPTION; } return pretext_end (ctx, b); } /* text.trim(str, reject) - trim whitespace or custom characters */ static JSValue js_cell_text_trim (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; if (!JS_IsText (argv[0])) return JS_NULL; JSValue str = argv[0]; int start = 0; int end = js_string_value_len (str); if (argc > 1 && !JS_IsNull (argv[1])) { /* Custom trim with reject characters */ const char *reject = JS_ToCString (ctx, argv[1]); if (!reject) return JS_EXCEPTION; size_t reject_len = strlen (reject); while (start < end) { uint32_t c = js_string_value_get (str, start); int found = 0; for (size_t i = 0; i < reject_len; i++) { if (c == (uint8_t)reject[i]) { found = 1; break; } } if (!found) break; start++; } while (end > start) { uint32_t c = js_string_value_get (str, end - 1); int found = 0; for (size_t i = 0; i < reject_len; i++) { if (c == (uint8_t)reject[i]) { found = 1; break; } } if (!found) break; end--; } JS_FreeCString (ctx, reject); } else { /* Default: trim whitespace */ while (start < end && lre_is_space (js_string_value_get (str, start))) start++; while (end > start && lre_is_space (js_string_value_get (str, end - 1))) end--; } return js_sub_string_val (ctx, str, start, end); } /* text.codepoint(str) - get first codepoint */ JSValue js_cell_text_codepoint (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; if (!JS_IsText (argv[0])) return JS_NULL; /* Handle immediate strings directly */ if (MIST_IsImmediateASCII (argv[0])) { int plen = MIST_GetImmediateASCIILen (argv[0]); if (plen == 0) return JS_NULL; uint32_t c = MIST_GetImmediateASCIIChar (argv[0], 0); return JS_NewInt32 (ctx, c); } /* Heap string */ JSText *p = JS_VALUE_GET_STRING (argv[0]); int plen = (int)JSText_len (p); if (plen == 0) { return JS_NULL; } uint32_t c = string_get (p, 0); /* Handle surrogate pairs */ if (c >= 0xD800 && c <= 0xDBFF && plen > 1) { uint32_t c2 = string_get (p, 1); if (c2 >= 0xDC00 && c2 <= 0xDFFF) { c = 0x10000 + ((c - 0xD800) << 10) + (c2 - 0xDC00); } } return JS_NewInt32 (ctx, c); } /* Helpers (C, not C++). Put these above js_cell_text_replace in the same C * file. */ static JSText *pt_concat_value_to_string_free (JSContext *ctx, JSText *b, JSValue v) { JSValue s = JS_ToString (ctx, v); if (JS_IsException (s)) return NULL; b = pretext_concat_value (ctx, b, s); return b; } /* Build replacement for a match at `found`. * - If replacement is a function: call it as (match_text, found) * - Else if replacement exists: duplicate it * - Else: empty string * Returns JS_EXCEPTION on error, JS_NULL if callback returned null, or any * JSValue. This function CONSUMES match_val if it calls a function (it will * free it via args cleanup), otherwise it will free match_val before * returning. */ static JSValue make_replacement (JSContext *ctx, int argc, JSValue *argv, int found, JSValue match_val) { JSValue rep; if (argc > 2 && JS_IsFunction (argv[2])) { JSValue args[2]; args[0] = match_val; args[1] = JS_NewInt32 (ctx, found); rep = JS_Call (ctx, argv[2], JS_NULL, 2, args); return rep; } if (argc > 2) return argv[2]; return JS_KEY_empty; } static int JS_IsRegExp (JSContext *ctx, JSValue v) { if (!JS_IsObject (v)) return 0; JSValue exec = JS_GetPropertyStr (ctx, v, "exec"); if (JS_IsException (exec)) return -1; int ok = JS_IsFunction (exec); return ok; } /* text.replace(text, target, replacement, limit) * * Return a new text in which the target is replaced by the replacement. * * target: string (pattern support not implemented here; non-string => null) * replacement: string or function(match_text, start_pos) -> string|null * limit: max number of replacements (default unlimited). Limit includes null * matches. * * Empty target semantics: * Replace at every boundary: before first char, between chars, after last * char. Example: replace("abc", "", "-") => "-a-b-c-" Boundaries count toward * limit even if replacement returns null. */ static JSValue js_cell_text_replace (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; if (!JS_IsText (argv[0])) return JS_NULL; int target_is_regex = 0; { if (JS_IsText (argv[1])) { target_is_regex = 0; } else if (JS_IsObject (argv[1]) && JS_IsRegExp (ctx, argv[1])) { target_is_regex = 1; } else { return JS_NULL; } } if (!JS_VALUE_IS_TEXT (argv[0])) return JS_ThrowInternalError (ctx, "Replace must have text in arg0."); int len = js_string_value_len (argv[0]); int32_t limit = -1; if (argc > 3 && !JS_IsNull (argv[3])) { if (JS_ToInt32 (ctx, &limit, argv[3])) { return JS_NULL; } if (limit < 0) limit = -1; } JSText *b = pretext_init (ctx, len); if (!b) return JS_EXCEPTION; /* Root b across all allocating calls */ JSGCRef b_ref; JS_PushGCRef (ctx, &b_ref); b_ref.val = JS_MKPTR (b); /* Macro to re-chase b from GC ref before use */ #define B_RECHASE() b = (JSText *)chase (b_ref.val) /* Macro to update b_ref after b changes */ #define B_UPDATE(new_b) do { b = (new_b); b_ref.val = JS_MKPTR (b); } while(0) #define B_CLEANUP() JS_PopGCRef (ctx, &b_ref) if (!target_is_regex) { if (!JS_VALUE_IS_TEXT (argv[1])) { B_CLEANUP (); return JS_ThrowInternalError ( ctx, "Second arg of replace must be pattern or text."); } int t_len = js_string_value_len (argv[1]); if (t_len == 0) { int32_t count = 0; for (int boundary = 0; boundary <= len; boundary++) { if (limit >= 0 && count >= limit) break; JSValue match = JS_KEY_empty; if (JS_IsException (match)) { B_CLEANUP (); goto fail_str_target; } JSValue rep = make_replacement (ctx, argc, argv, boundary, match); if (JS_IsException (rep)) { B_CLEANUP (); goto fail_str_target; } count++; if (!JS_IsNull (rep)) { B_RECHASE (); B_UPDATE (pt_concat_value_to_string_free (ctx, b, rep)); if (!b) { B_CLEANUP (); goto fail_str_target; } } if (boundary < len) { JSValue ch = js_sub_string_val (ctx, argv[0], boundary, boundary + 1); if (JS_IsException (ch)) { B_CLEANUP (); goto fail_str_target; } B_RECHASE (); B_UPDATE (pretext_concat_value (ctx, b, ch)); if (!b) { B_CLEANUP (); goto fail_str_target; } } } B_RECHASE (); B_CLEANUP (); return pretext_end (ctx, b); } int pos = 0; int32_t count = 0; while (pos <= len - t_len && (limit < 0 || count < limit)) { int found = -1; /* Search for pattern using character-by-character comparison */ for (int i = pos; i <= len - t_len; i++) { int match = 1; for (int j = 0; j < t_len; j++) { if (js_string_value_get (argv[0], i + j) != js_string_value_get (argv[1], j)) { match = 0; break; } } if (match) { found = i; break; } } if (found < 0) break; if (found > pos) { int sub_len = found - pos; JSText *sub_str = js_alloc_string (ctx, sub_len); if (!sub_str) { B_CLEANUP (); goto fail_str_target; } for (int i = 0; i < sub_len; i++) { string_put (sub_str, i, js_string_value_get (argv[0], pos + i)); } sub_str->length = sub_len; JSValue sub = pretext_end (ctx, sub_str); if (JS_IsException (sub)) { B_CLEANUP (); goto fail_str_target; } B_RECHASE (); B_UPDATE (pretext_concat_value (ctx, b, sub)); if (!b) { B_CLEANUP (); goto fail_str_target; } } /* Build match substring manually */ JSText *match_str = js_alloc_string (ctx, t_len); if (!match_str) { B_CLEANUP (); goto fail_str_target; } for (int i = 0; i < t_len; i++) { string_put (match_str, i, js_string_value_get (argv[0], found + i)); } match_str->length = t_len; JSValue match = pretext_end (ctx, match_str); if (JS_IsException (match)) { B_CLEANUP (); goto fail_str_target; } JSValue rep = make_replacement (ctx, argc, argv, found, match); if (JS_IsException (rep)) { B_CLEANUP (); goto fail_str_target; } count++; if (!JS_IsNull (rep)) { B_RECHASE (); B_UPDATE (pt_concat_value_to_string_free (ctx, b, rep)); if (!b) { B_CLEANUP (); goto fail_str_target; } } pos = found + t_len; } if (pos < len) { int sub_len = len - pos; JSText *sub_str = js_alloc_string (ctx, sub_len); if (!sub_str) { B_CLEANUP (); goto fail_str_target; } for (int i = 0; i < sub_len; i++) { string_put (sub_str, i, js_string_value_get (argv[0], pos + i)); } sub_str->length = sub_len; JSValue sub = pretext_end (ctx, sub_str); if (JS_IsException (sub)) { B_CLEANUP (); goto fail_str_target; } B_RECHASE (); B_UPDATE (pretext_concat_value (ctx, b, sub)); if (!b) { B_CLEANUP (); goto fail_str_target; } } B_RECHASE (); B_CLEANUP (); return pretext_end (ctx, b); fail_str_target: return JS_EXCEPTION; } /* Regex target - root rx across allocating calls */ JSGCRef rx_ref; JS_PushGCRef (ctx, &rx_ref); rx_ref.val = argv[1]; #define RX_CLEANUP() do { JS_PopGCRef (ctx, &rx_ref); B_CLEANUP (); } while(0) #define RX_VAL (rx_ref.val) JSValue orig_last_index = JS_GetPropertyStr (ctx, RX_VAL, "lastIndex"); if (JS_IsException (orig_last_index)) { RX_CLEANUP (); goto fail_rx; } int have_orig_last_index = 1; int pos = 0; int32_t count = 0; while (pos <= len && (limit < 0 || count < limit)) { if (JS_SetPropertyStr (ctx, RX_VAL, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) { RX_CLEANUP (); goto fail_rx; } JSValue sub_str = js_sub_string_val (ctx, argv[0], pos, len); if (JS_IsException (sub_str)) { RX_CLEANUP (); goto fail_rx; } JSValue exec_res = JS_Invoke (ctx, RX_VAL, JS_KEY_exec, 1, (JSValue *)&sub_str); if (JS_IsException (exec_res)) { RX_CLEANUP (); goto fail_rx; } if (JS_IsNull (exec_res)) { break; } JSValue idx_val = JS_GetPropertyStr (ctx, exec_res, "index"); if (JS_IsException (idx_val)) { RX_CLEANUP (); goto fail_rx; } int32_t local_index = 0; if (JS_ToInt32 (ctx, &local_index, idx_val)) { RX_CLEANUP (); goto fail_rx; } if (local_index < 0) local_index = 0; int found = pos + local_index; if (found < pos) found = pos; if (found > len) { break; } JSValue match = JS_GetPropertyStr (ctx, exec_res, "match"); if (JS_IsException (match)) { RX_CLEANUP (); goto fail_rx; } JSValue end_val = JS_GetPropertyStr (ctx, exec_res, "end"); if (JS_IsException (end_val)) { RX_CLEANUP (); goto fail_rx; } int32_t end = 0; if (JS_ToInt32 (ctx, &end, end_val)) { RX_CLEANUP (); goto fail_rx; } int match_len = end - local_index; if (match_len < 0) match_len = 0; if (found > pos) { JSValue prefix = js_sub_string_val (ctx, argv[0], pos, found); if (JS_IsException (prefix)) { RX_CLEANUP (); goto fail_rx; } B_RECHASE (); B_UPDATE (pretext_concat_value (ctx, b, prefix)); if (!b) { RX_CLEANUP (); goto fail_rx; } } JSValue rep = make_replacement (ctx, argc, argv, found, match); if (JS_IsException (rep)) { RX_CLEANUP (); goto fail_rx; } count++; if (!JS_IsNull (rep)) { B_RECHASE (); B_UPDATE (pt_concat_value_to_string_free (ctx, b, rep)); if (!b) { RX_CLEANUP (); goto fail_rx; } } pos = found + match_len; if (match_len == 0) { if (pos < len) pos++; else break; } } if (pos < len) { JSValue tail = js_sub_string_val (ctx, argv[0], pos, len); if (JS_IsException (tail)) { RX_CLEANUP (); goto fail_rx; } B_RECHASE (); B_UPDATE (pretext_concat_value (ctx, b, tail)); if (!b) { RX_CLEANUP (); goto fail_rx; } } if (have_orig_last_index) JS_SetPropertyStr (ctx, RX_VAL, "lastIndex", orig_last_index); B_RECHASE (); RX_CLEANUP (); return pretext_end (ctx, b); fail_rx: if (!JS_IsNull (orig_last_index) && !JS_IsException (orig_last_index)) { JS_SetPropertyStr (ctx, argv[1], "lastIndex", orig_last_index); } else { } return JS_EXCEPTION; } #undef RX_CLEANUP #undef RX_VAL #undef B_RECHASE #undef B_UPDATE #undef B_CLEANUP /* text.search(str, target, from) - find substring or regex match */ static JSValue js_cell_text_search (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; if (!JS_IsText (argv[0])) return JS_NULL; int target_is_regex = 0; if (JS_IsText (argv[1])) { target_is_regex = 0; } else if (JS_IsObject (argv[1]) && JS_IsRegExp (ctx, argv[1])) { target_is_regex = 1; } else { return JS_NULL; } JSValue str = argv[0]; int len = js_string_value_len (str); int from = 0; if (argc > 2 && !JS_IsNull (argv[2])) { if (JS_ToInt32 (ctx, &from, argv[2])) { return JS_NULL; } if (from < 0) from += len; if (from < 0) from = 0; } if (from > len) { return JS_NULL; } if (!target_is_regex) { JSValue target = argv[1]; int t_len = js_string_value_len (target); int result = -1; if (len >= t_len) { for (int i = from; i <= len - t_len; i++) { int match = 1; for (int j = 0; j < t_len; j++) { if (js_string_value_get (str, i + j) != js_string_value_get (target, j)) { match = 0; break; } } if (match) { result = i; break; } } } if (result == -1) return JS_NULL; return JS_NewInt32 (ctx, result); } /* Regex target - root rx and str across allocating calls */ JSGCRef rx_ref, str_ref; JS_PushGCRef (ctx, &rx_ref); rx_ref.val = argv[1]; JS_PushGCRef (ctx, &str_ref); str_ref.val = str; #define SEARCH_CLEANUP() do { JS_PopGCRef (ctx, &str_ref); JS_PopGCRef (ctx, &rx_ref); } while(0) JSValue orig_last_index = JS_GetPropertyStr (ctx, rx_ref.val, "lastIndex"); if (JS_IsException (orig_last_index)) { SEARCH_CLEANUP (); return JS_EXCEPTION; } int have_orig_last_index = 1; if (JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) goto fail_rx_search; JSValue sub_str = js_sub_string_val (ctx, str_ref.val, from, len); if (JS_IsException (sub_str)) goto fail_rx_search; JSValue exec_res = JS_Invoke (ctx, rx_ref.val, JS_KEY_exec, 1, (JSValue *)&sub_str); if (JS_IsException (exec_res)) goto fail_rx_search; if (JS_IsNull (exec_res)) { if (have_orig_last_index) JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); SEARCH_CLEANUP (); return JS_NULL; } JSValue idx_val = JS_GetPropertyStr (ctx, exec_res, "index"); if (JS_IsException (idx_val)) { goto fail_rx_search; } int32_t local_index = 0; if (JS_ToInt32 (ctx, &local_index, idx_val)) { goto fail_rx_search; } if (local_index < 0) local_index = 0; if (have_orig_last_index) JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); SEARCH_CLEANUP (); return JS_NewInt32 (ctx, from + local_index); fail_rx_search: if (!JS_IsNull (orig_last_index) && !JS_IsException (orig_last_index)) { JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); } SEARCH_CLEANUP (); return JS_EXCEPTION; } #undef SEARCH_CLEANUP static inline uint32_t js_str_get (JSText *s, int idx) { return string_get (s, idx); } static int js_str_find_range (JSText *hay, int from, int to, JSText *needle) { int nlen = (int)JSText_len (needle); int hlen = (int)JSText_len (hay); if (from < 0) from = 0; if (to < 0) to = 0; if (to > hlen) to = hlen; if (from > to) return -1; if (nlen == 0) return from; if (nlen > (to - from)) return -1; int limit = to - nlen; for (int i = from; i <= limit; i++) { int j = 0; for (; j < nlen; j++) { if (js_str_get (hay, i + j) != js_str_get (needle, j)) break; } if (j == nlen) return i; } return -1; } /* text_extract(text, pattern, from?, to?) - return array of matches or null - literal pattern: [match] - regexp pattern: [full_match, cap1, cap2, ...] */ static JSValue js_cell_text_extract (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; if (!JS_IsText (argv[0])) return JS_NULL; JSValue str = argv[0]; int len = js_string_value_len (str); int from = 0; if (argc >= 3 && !JS_IsNull (argv[2])) { if (JS_ToInt32 (ctx, &from, argv[2])) return JS_EXCEPTION; if (from < 0) from += len; if (from < 0) from = 0; if (from > len) from = len; } int to = len; if (argc >= 4 && !JS_IsNull (argv[3])) { if (JS_ToInt32 (ctx, &to, argv[3])) return JS_EXCEPTION; if (to < 0) to += len; if (to < 0) to = 0; if (to > len) to = len; } if (from > to) return JS_NULL; /* RegExp path: convert new exec result record -> classic array */ if (JS_IsObject (argv[1]) && JS_IsRegExp (ctx, argv[1])) { /* Root rx, str, out across allocating calls */ JSGCRef rx_ref, str_ref, out_ref; JS_PushGCRef (ctx, &rx_ref); rx_ref.val = argv[1]; JS_PushGCRef (ctx, &str_ref); str_ref.val = str; JS_PushGCRef (ctx, &out_ref); out_ref.val = JS_NULL; #define EXT_CLEANUP() do { JS_PopGCRef (ctx, &out_ref); JS_PopGCRef (ctx, &str_ref); JS_PopGCRef (ctx, &rx_ref); } while(0) JSValue orig_last_index = JS_GetPropertyStr (ctx, rx_ref.val, "lastIndex"); if (JS_IsException (orig_last_index)) { EXT_CLEANUP (); return JS_EXCEPTION; } if (JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) goto fail_rx; JSValue sub_str; if (from == 0 && to == len) { sub_str = str_ref.val; } else { sub_str = js_sub_string_val (ctx, str_ref.val, from, to); if (JS_IsException (sub_str)) goto fail_rx; } JSGCRef exec_ref; JS_PushGCRef (ctx, &exec_ref); exec_ref.val = JS_Invoke (ctx, rx_ref.val, JS_KEY_exec, 1, (JSValue *)&sub_str); if (JS_IsException (exec_ref.val)) { JS_PopGCRef (ctx, &exec_ref); goto fail_rx; } if (JS_IsNull (exec_ref.val)) { JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); JS_PopGCRef (ctx, &exec_ref); EXT_CLEANUP (); return JS_NULL; } /* Build result array */ JSValue out = JS_NewArray (ctx); if (JS_IsException (out)) { JS_PopGCRef (ctx, &exec_ref); goto fail_rx; } out_ref.val = out; /* out[0] = exec_res.match */ JSValue match0 = JS_GetPropertyStr (ctx, exec_ref.val, "match"); if (JS_IsException (match0)) { JS_PopGCRef (ctx, &exec_ref); goto fail_rx; } out = out_ref.val; if (JS_SetPropertyUint32 (ctx, out, 0, match0) < 0) { JS_PopGCRef (ctx, &exec_ref); goto fail_rx; } /* Append capture groups from exec_res.captures */ JSValue caps = JS_GetPropertyStr (ctx, exec_ref.val, "captures"); JS_PopGCRef (ctx, &exec_ref); /* exec_ref no longer needed */ if (!JS_IsException (caps) && JS_IsArray (caps)) { int64_t caps_len = 0; if (js_get_length64 (ctx, &caps_len, caps) == 0 && caps_len > 0) { for (int64_t i = 0; i < caps_len; i++) { JSValue cap = JS_GetPropertyInt64 (ctx, caps, i); if (JS_IsException (cap)) { goto fail_rx; } out = out_ref.val; if (JS_SetPropertyInt64 (ctx, out, i + 1, cap) < 0) { goto fail_rx; } } } } JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); out = out_ref.val; EXT_CLEANUP (); return out; fail_rx: if (!JS_IsNull (orig_last_index) && !JS_IsException (orig_last_index)) { JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); } EXT_CLEANUP (); return JS_EXCEPTION; } #undef EXT_CLEANUP /* Literal text path */ JSValue needle_val = JS_ToString (ctx, argv[1]); if (JS_IsException (needle_val)) return JS_EXCEPTION; str = argv[0]; /* refresh after potential GC */ int needle_len = js_string_value_len (needle_val); /* Find needle in str[from..to) */ int pos = -1; if (needle_len == 0) { pos = from; } else if (needle_len <= (to - from)) { int limit = to - needle_len; for (int i = from; i <= limit; i++) { int j = 0; for (; j < needle_len; j++) { if (js_string_value_get (str, i + j) != js_string_value_get (needle_val, j)) break; } if (j == needle_len) { pos = i; break; } } } if (pos < 0) return JS_NULL; JSGCRef arr_ref; JS_PushGCRef (ctx, &arr_ref); arr_ref.val = JS_NewArrayLen (ctx, 1); if (JS_IsException (arr_ref.val)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } str = argv[0]; /* refresh after potential GC */ JSValue match = js_sub_string_val (ctx, str, pos, pos + needle_len); if (JS_IsException (match)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } JSValue arr = arr_ref.val; JS_PopGCRef (ctx, &arr_ref); if (JS_SetPropertyUint32 (ctx, arr, 0, match) < 0) return JS_EXCEPTION; return arr; } /* format(text, collection, transformer) - string interpolation * Finds {name} or {name:format} patterns and substitutes from collection. * Collection can be array (index by number) or record (index by key). * Transformer can be function(value, format) or record of functions. */ static JSValue js_cell_text_format (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; if (!JS_IsText (argv[0])) return JS_NULL; JSValue text_val = argv[0]; JSValue collection = argv[1]; JSValue transformer = argc > 2 ? argv[2] : JS_NULL; int is_array = JS_IsArray (collection); int is_record = JS_IsRecord (collection); if (!is_array && !is_record) return JS_NULL; int len = js_string_value_len (text_val); /* Root text_val, collection, transformer BEFORE any allocation */ JSGCRef res_ref, text_ref, coll_ref, xform_ref; JS_PushGCRef (ctx, &res_ref); JS_PushGCRef (ctx, &text_ref); JS_PushGCRef (ctx, &coll_ref); JS_PushGCRef (ctx, &xform_ref); res_ref.val = JS_NULL; text_ref.val = text_val; coll_ref.val = collection; xform_ref.val = transformer; #define FMT_CLEANUP() do { \ JS_PopGCRef (ctx, &xform_ref); \ JS_PopGCRef (ctx, &coll_ref); \ JS_PopGCRef (ctx, &text_ref); \ JS_PopGCRef (ctx, &res_ref); \ } while(0) JSText *result = pretext_init (ctx, len); if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } res_ref.val = JS_MKPTR (result); int pos = 0; while (pos < len) { text_val = text_ref.val; /* Find next '{' */ int brace_start = -1; for (int i = pos; i < len; i++) { if (js_string_value_get (text_val, i) == '{') { brace_start = i; break; } } if (brace_start < 0) { /* No more braces, copy rest of string */ JSValue tail = js_sub_string_val (ctx, text_ref.val, pos, len); if (JS_IsException (tail)) { FMT_CLEANUP(); return JS_EXCEPTION; } result = (JSText *)chase (res_ref.val); result = pretext_concat_value (ctx, result, tail); if (result) res_ref.val = JS_MKPTR (result); break; } /* Copy text before brace */ if (brace_start > pos) { JSValue prefix = js_sub_string_val (ctx, text_ref.val, pos, brace_start); if (JS_IsException (prefix)) { FMT_CLEANUP(); return JS_EXCEPTION; } result = (JSText *)chase (res_ref.val); result = pretext_concat_value (ctx, result, prefix); if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } res_ref.val = JS_MKPTR (result); } /* Find closing '}' */ text_val = text_ref.val; int brace_end = -1; for (int i = brace_start + 1; i < len; i++) { if (js_string_value_get (text_val, i) == '}') { brace_end = i; break; } } if (brace_end < 0) { /* No closing brace, copy '{' and continue */ JSValue ch = js_sub_string_val (ctx, text_ref.val, brace_start, brace_start + 1); if (JS_IsException (ch)) { FMT_CLEANUP(); return JS_EXCEPTION; } result = (JSText *)chase (res_ref.val); result = pretext_concat_value (ctx, result, ch); if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } res_ref.val = JS_MKPTR (result); pos = brace_start + 1; continue; } /* Extract content between braces */ JSValue middle = js_sub_string_val (ctx, text_ref.val, brace_start + 1, brace_end); if (JS_IsException (middle)) { FMT_CLEANUP(); return JS_EXCEPTION; } /* Split on ':' to get name and format_spec */ int middle_len = js_string_value_len (middle); int colon_pos = -1; for (int i = 0; i < middle_len; i++) { if (js_string_value_get (middle, i) == ':') { colon_pos = i; break; } } JSValue name_val, format_spec; if (colon_pos >= 0) { name_val = js_sub_string_val (ctx, middle, 0, colon_pos); format_spec = js_sub_string_val (ctx, middle, colon_pos + 1, middle_len); } else { name_val = middle; format_spec = JS_KEY_empty; } /* Get value from collection — protect with GCRef since JS_Call below can trigger GC */ JSGCRef cv_ref; JS_PushGCRef (ctx, &cv_ref); cv_ref.val = JS_NULL; if (is_array) { int name_len = js_string_value_len (name_val); int32_t idx = 0; int valid = (name_len > 0); for (int ni = 0; ni < name_len && valid; ni++) { uint32_t ch = js_string_value_get (name_val, ni); if (ch >= '0' && ch <= '9') idx = idx * 10 + (ch - '0'); else valid = 0; } if (valid && idx >= 0) { cv_ref.val = JS_GetPropertyUint32 (ctx, coll_ref.val, (uint32_t)idx); } } else { cv_ref.val = JS_GetProperty (ctx, coll_ref.val, name_val); } /* Try to get substitution */ JSValue substitution = JS_NULL; int made_substitution = 0; if (!JS_IsNull (xform_ref.val)) { if (JS_IsFunction (xform_ref.val)) { JSValue args[2] = { cv_ref.val, format_spec }; JSValue result_val = JS_Call (ctx, xform_ref.val, JS_NULL, 2, args); if (JS_IsText (result_val)) { substitution = result_val; made_substitution = 1; } } else if (JS_IsRecord (xform_ref.val)) { JSValue func = JS_GetProperty (ctx, xform_ref.val, format_spec); if (JS_IsFunction (func)) { JSValue result_val = JS_Call (ctx, func, JS_NULL, 1, &cv_ref.val); if (JS_IsText (result_val)) { substitution = result_val; made_substitution = 1; } } } } if (!made_substitution && JS_IsNumber (cv_ref.val) && !JS_IsNull (format_spec)) { JSValue text_method = JS_GetPropertyStr (ctx, cv_ref.val, "text"); if (JS_IsFunction (text_method)) { JSValue result_val = JS_Call (ctx, text_method, cv_ref.val, 1, &format_spec); if (JS_IsText (result_val)) { substitution = result_val; made_substitution = 1; } } } if (!made_substitution && !JS_IsNull (cv_ref.val)) { JSValue conv_text_val = JS_ToString (ctx, cv_ref.val); if (JS_IsText (conv_text_val)) { substitution = conv_text_val; made_substitution = 1; } } JS_PopGCRef (ctx, &cv_ref); result = (JSText *)chase (res_ref.val); if (made_substitution) { result = pretext_concat_value (ctx, result, substitution); if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } res_ref.val = JS_MKPTR (result); } else { JSValue orig = js_sub_string_val (ctx, text_ref.val, brace_start, brace_end + 1); if (JS_IsException (orig)) { FMT_CLEANUP(); return JS_EXCEPTION; } result = (JSText *)chase (res_ref.val); result = pretext_concat_value (ctx, result, orig); if (!result) { FMT_CLEANUP(); return JS_EXCEPTION; } res_ref.val = JS_MKPTR (result); } pos = brace_end + 1; } result = (JSText *)chase (res_ref.val); FMT_CLEANUP(); #undef FMT_CLEANUP return pretext_end (ctx, result); } /* print(args...) - print arguments to stdout */ static JSValue js_print (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { for (int i = 0; i < argc; i++) { const char *str = JS_ToCString (ctx, argv[i]); if (str) { fputs (str, stdout); JS_FreeCString (ctx, str); } if (i < argc - 1) fputc (' ', stdout); } fputc ('\n', stdout); return JS_NULL; } static JSValue js_stacktrace (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { cJSON *stack = JS_GetStack(ctx); if (stack) { int n = cJSON_GetArraySize(stack); for (int i = 0; i < n; i++) { cJSON *fr = cJSON_GetArrayItem(stack, i); const char *fn = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fr, "function")); const char *file = cJSON_GetStringValue(cJSON_GetObjectItemCaseSensitive(fr, "file")); int line = (int)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(fr, "line")); int col = (int)cJSON_GetNumberValue(cJSON_GetObjectItemCaseSensitive(fr, "column")); printf(" at %s (%s:%d:%d)\n", fn ? fn : "", file ? file : "", line, col); } cJSON_Delete(stack); } return JS_NULL; } /* ---------------------------------------------------------------------------- * Bytecode dump function (always available, for debugging) * ---------------------------------------------------------------------------- */ /* Opcode names for bytecode dump */ static const char *dump_opcode_names[] = { #define FMT(f) #define DEF(id, size, n_pop, n_push, f) #id, #define def(id, size, n_pop, n_push, f) #include "quickjs-opcode.h" #undef def #undef DEF #undef FMT }; static void dump_bytecode_opcodes (JSContext *ctx, JSFunctionBytecode *b) { const uint8_t *tab = b->byte_code_buf; int len = b->byte_code_len; const JSValue *cpool = b->cpool; uint32_t cpool_count = b->cpool_count; const JSVarDef *vars = b->vardefs ? b->vardefs + b->arg_count : NULL; int var_count = b->var_count; int pos = 0; while (pos < len) { int op = tab[pos]; if (op >= OP_COUNT) { printf (" %5d: \n", pos, op); pos++; continue; } const JSOpCode *oi = &short_opcode_info (op); int size = oi->size; if (pos + size > len) { printf (" %5d: \n", pos, op); break; } printf (" %5d: %s", pos, dump_opcode_names[op]); pos++; switch (oi->fmt) { case OP_FMT_none_int: printf (" %d", op - OP_push_0); break; case OP_FMT_npopx: printf (" %d", op - OP_call0); break; case OP_FMT_u8: printf (" %u", get_u8 (tab + pos)); break; case OP_FMT_i8: printf (" %d", get_i8 (tab + pos)); break; case OP_FMT_u16: case OP_FMT_npop: printf (" %u", get_u16 (tab + pos)); break; case OP_FMT_npop_u16: printf (" %u,%u", get_u16 (tab + pos), get_u16 (tab + pos + 2)); break; case OP_FMT_i16: printf (" %d", get_i16 (tab + pos)); break; case OP_FMT_i32: printf (" %d", get_i32 (tab + pos)); break; case OP_FMT_u32: printf (" %u", get_u32 (tab + pos)); break; case OP_FMT_label8: printf (" ->%d", pos + get_i8 (tab + pos)); break; case OP_FMT_label16: printf (" ->%d", pos + get_i16 (tab + pos)); break; case OP_FMT_label: printf (" ->%u", pos + get_u32 (tab + pos)); break; case OP_FMT_label_u16: printf (" ->%u,%u", pos + get_u32 (tab + pos), get_u16 (tab + pos + 4)); break; case OP_FMT_const8: { uint32_t idx = get_u8 (tab + pos); printf (" [%u]", idx); if (idx < cpool_count) { printf (": "); JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[idx], NULL); } break; } case OP_FMT_const: { uint32_t idx = get_u32 (tab + pos); printf (" [%u]", idx); if (idx < cpool_count) { printf (": "); JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[idx], NULL); } break; } case OP_FMT_key: { uint32_t idx = get_u32 (tab + pos); printf (" [%u]", idx); if (idx < cpool_count) { printf (": "); JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[idx], NULL); } break; } case OP_FMT_key_u8: { uint32_t idx = get_u32 (tab + pos); printf (" [%u],%d", idx, get_u8 (tab + pos + 4)); if (idx < cpool_count) { printf (": "); JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[idx], NULL); } break; } case OP_FMT_key_u16: { uint32_t idx = get_u32 (tab + pos); printf (" [%u],%d", idx, get_u16 (tab + pos + 4)); if (idx < cpool_count) { printf (": "); JS_PrintValue (ctx, js_dump_value_write, stdout, cpool[idx], NULL); } break; } case OP_FMT_none_loc: printf (" loc%d", (op - OP_get_loc0) % 4); break; case OP_FMT_loc8: { int idx = get_u8 (tab + pos); printf (" loc%d", idx); if (vars && idx < var_count) { char buf[KEY_GET_STR_BUF_SIZE]; printf (": %s", JS_KeyGetStr (ctx, buf, sizeof(buf), vars[idx].var_name)); } break; } case OP_FMT_loc: { int idx = get_u16 (tab + pos); printf (" loc%d", idx); if (vars && idx < var_count) { char buf[KEY_GET_STR_BUF_SIZE]; printf (": %s", JS_KeyGetStr (ctx, buf, sizeof(buf), vars[idx].var_name)); } break; } case OP_FMT_none_arg: printf (" arg%d", (op - OP_get_arg0) % 4); break; case OP_FMT_arg: printf (" arg%d", get_u16 (tab + pos)); break; default: break; } printf ("\n"); pos += size - 1; /* -1 because we already incremented pos after reading opcode */ } } void JS_DumpFunctionBytecode (JSContext *ctx, JSValue func_val) { JSFunctionBytecode *b = NULL; if (!JS_IsPtr (func_val)) { printf ("JS_DumpFunctionBytecode: not a pointer value\n"); return; } /* Get the object header to check type */ void *ptr = JS_VALUE_GET_PTR (func_val); objhdr_t hdr = *(objhdr_t *)ptr; uint8_t type = objhdr_type (hdr); if (type == OBJ_FUNCTION) { /* It's a JSFunction - extract bytecode */ JSFunction *fn = (JSFunction *)ptr; if (fn->kind != JS_FUNC_KIND_BYTECODE) { printf ("JS_DumpFunctionBytecode: not a bytecode function (kind=%d)\n", fn->kind); return; } b = fn->u.func.function_bytecode; } else if (type == OBJ_CODE) { /* It's raw bytecode from js_create_function */ b = (JSFunctionBytecode *)ptr; } else { printf ("JS_DumpFunctionBytecode: not a function or bytecode (type=%d)\n", type); return; } if (!b) { printf ("JS_DumpFunctionBytecode: no bytecode\n"); return; } char buf[KEY_GET_STR_BUF_SIZE]; printf ("=== Bytecode Dump ===\n"); /* Function name */ const char *fname = JS_KeyGetStr (ctx, buf, sizeof(buf), b->func_name); printf ("Function: %s\n", fname ? fname : ""); /* Debug info */ if (b->has_debug && !JS_IsNull (b->debug.filename)) { printf ("File: %s\n", JS_KeyGetStr (ctx, buf, sizeof(buf), b->debug.filename)); } /* Basic stats */ printf ("Args: %d, Vars: %d, Stack: %d\n", b->arg_count, b->var_count, b->stack_size); printf ("Bytecode length: %d bytes\n", b->byte_code_len); /* Arguments */ if (b->arg_count > 0 && b->vardefs) { printf ("\nArguments:\n"); for (int i = 0; i < b->arg_count; i++) { printf (" %d: %s\n", i, JS_KeyGetStr (ctx, buf, sizeof(buf), b->vardefs[i].var_name)); } } /* Local variables */ if (b->var_count > 0 && b->vardefs) { printf ("\nLocal variables:\n"); for (int i = 0; i < b->var_count; i++) { JSVarDef *vd = &b->vardefs[b->arg_count + i]; const char *kind = vd->is_const ? "const" : vd->is_lexical ? "let" : "var"; printf (" %d: %s %s", i, kind, JS_KeyGetStr (ctx, buf, sizeof(buf), vd->var_name)); if (vd->scope_level) printf (" [scope:%d]", vd->scope_level); printf ("\n"); } } /* Closure variables */ if (b->closure_var_count > 0) { printf ("\nClosure variables:\n"); for (int i = 0; i < b->closure_var_count; i++) { JSClosureVar *cv = &b->closure_var[i]; printf (" %d: %s (%s:%s%d)\n", i, JS_KeyGetStr (ctx, buf, sizeof(buf), cv->var_name), cv->is_local ? "local" : "parent", cv->is_arg ? "arg" : "loc", cv->var_idx); } } /* Constant pool */ if (b->cpool_count > 0) { printf ("\nConstant pool (%d entries):\n", b->cpool_count); for (uint32_t i = 0; i < b->cpool_count; i++) { printf (" [%u]: ", i); JS_PrintValue (ctx, js_dump_value_write, stdout, b->cpool[i], NULL); printf ("\n"); } } /* Bytecode instructions */ printf ("\nBytecode:\n"); dump_bytecode_opcodes (ctx, b); printf ("=== End Bytecode Dump ===\n"); } /* ---------------------------------------------------------------------------- * array function and sub-functions * ---------------------------------------------------------------------------- */ /* array(arg, arg2, arg3, arg4) - main array function */ static JSValue js_cell_array (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { (void)this_val; if (argc < 1) return JS_NULL; JSValue arg = argv[0]; /* array(number) - create array of size */ /* array(number, initial_value) - create array with initial values */ if (JS_IsNumber (arg)) { if (!JS_IsInteger (arg)) return JS_ThrowTypeError (ctx, "Array expected an integer."); int len = JS_VALUE_GET_INT (arg); if (len < 0) return JS_NULL; JSGCRef result_ref; JSValue result = JS_NewArrayLen (ctx, len); if (JS_IsException (result)) return result; if (argc > 1 && JS_IsFunction (argv[1])) { /* Fill with function results - GC-safe */ JSGCRef func_ref; JS_PushGCRef (ctx, &func_ref); func_ref.val = argv[1]; int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func_ref.val))->length; if (arity >= 1) { for (int i = 0; i < len; i++) { JSValue idx_arg = JS_NewInt32 (ctx, i); JS_PUSH_VALUE (ctx, result); JSValue val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &idx_arg, 0); JS_POP_VALUE (ctx, result); if (JS_IsException (val)) { JS_PopGCRef (ctx, &func_ref); return JS_EXCEPTION; } JSArray *out = JS_VALUE_GET_ARRAY (result); /* re-chase after call */ out->values[i] = val; } } else { for (int i = 0; i < len; i++) { JS_PUSH_VALUE (ctx, result); JSValue val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 0, NULL, 0); JS_POP_VALUE (ctx, result); if (JS_IsException (val)) { JS_PopGCRef (ctx, &func_ref); return JS_EXCEPTION; } JSArray *out = JS_VALUE_GET_ARRAY (result); /* re-chase after call */ out->values[i] = val; } } JS_PopGCRef (ctx, &func_ref); } else if (argc > 1) { /* Fill with value */ JSArray *out = JS_VALUE_GET_ARRAY (result); for (int i = 0; i < len; i++) out->values[i] = argv[1]; } return result; } /* array(array) - copy */ /* array(array, function) - map */ /* array(array, another_array) - concat */ /* array(array, from, to) - slice */ if (JS_IsArray (arg)) { /* Root input array and arg1 for GC safety in this section */ JSGCRef arg0_ref, arg1_ref; JS_PushGCRef (ctx, &arg0_ref); JS_PushGCRef (ctx, &arg1_ref); arg0_ref.val = argv[0]; arg1_ref.val = argc > 1 ? argv[1] : JS_NULL; JSArray *arr = JS_VALUE_GET_ARRAY (arg0_ref.val); int len = arr->len; if (argc < 2 || JS_IsNull (argv[1])) { /* Copy */ JSValue result = JS_NewArrayLen (ctx, len); if (JS_IsException (result)) { JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return result; } arr = JS_VALUE_GET_ARRAY (arg0_ref.val); /* re-chase after allocation */ JSArray *out = JS_VALUE_GET_ARRAY (result); for (int i = 0; i < len; i++) { out->values[i] = arr->values[i]; } out->len = len; JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return result; } if (JS_IsFunction (arg1_ref.val)) { /* Map - GC-safe: root result throughout, use rooted refs for func and array */ int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (arg1_ref.val))->length; int reverse = argc > 2 && JS_ToBool (ctx, argv[2]); JSValue exit_val = argc > 3 ? argv[3] : JS_NULL; JSGCRef result_ref; JS_PushGCRef (ctx, &result_ref); /* Push first - sets val to JS_NULL */ result_ref.val = JS_NewArray (ctx); /* Then assign */ if (JS_IsException (result_ref.val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return result_ref.val; } if (arity >= 2) { if (reverse) { for (int i = len - 1; i >= 0; i--) { /* Re-chase input array each iteration */ arr = JS_VALUE_GET_ARRAY (arg0_ref.val); if (i >= (int)arr->len) continue; /* array may have shrunk */ JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, i) }; JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 2, args, 0); if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break; if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } } } else { for (int i = 0; i < len; i++) { arr = JS_VALUE_GET_ARRAY (arg0_ref.val); if (i >= (int)arr->len) break; JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, i) }; JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 2, args, 0); if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break; if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } } } } else { if (reverse) { for (int i = len - 1; i >= 0; i--) { arr = JS_VALUE_GET_ARRAY (arg0_ref.val); if (i >= (int)arr->len) continue; JSValue item = arr->values[i]; JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 1, &item, 0); if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break; if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } } } else { for (int i = 0; i < len; i++) { arr = JS_VALUE_GET_ARRAY (arg0_ref.val); if (i >= (int)arr->len) break; JSValue item = arr->values[i]; JSValue val = JS_CallInternal (ctx, arg1_ref.val, JS_NULL, 1, &item, 0); if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } if (!JS_IsNull (exit_val) && js_strict_eq (ctx, val, exit_val)) break; if (js_intrinsic_array_push (ctx, &result_ref.val, val) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_EXCEPTION; } } } } JSValue result = result_ref.val; JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return result; } if (JS_IsArray (arg1_ref.val)) { /* Concat */ JSArray *arr2 = JS_VALUE_GET_ARRAY (arg1_ref.val); int len2 = arr2->len; JSValue result = JS_NewArrayLen (ctx, len + len2); if (JS_IsException (result)) { JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return result; } /* Re-chase arrays after allocation */ arr = JS_VALUE_GET_ARRAY (arg0_ref.val); arr2 = JS_VALUE_GET_ARRAY (arg1_ref.val); JSArray *out = JS_VALUE_GET_ARRAY (result); for (int i = 0; i < len; i++) { out->values[i] = arr->values[i]; } for (int i = 0; i < len2; i++) { out->values[len + i] = arr2->values[i]; } out->len = len + len2; JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return result; } if (JS_IsNumber (argv[1])) { /* Slice */ if (!JS_IsInteger (argv[1])) { JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_NULL; } int from = JS_VALUE_GET_INT (argv[1]); int to; if (argc > 2 && !JS_IsNull (argv[2])) { if (!JS_IsNumber (argv[2]) || !JS_IsInteger (argv[2])) { JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_NULL; } to = JS_VALUE_GET_INT (argv[2]); } else { to = len; } if (from < 0) from += len; if (to < 0) to += len; if (from < 0 || from > len || to < 0 || to > len || from > to) { JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_NULL; } int slice_len = to - from; JSValue result = JS_NewArrayLen (ctx, slice_len); if (JS_IsException (result)) { JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return result; } /* Re-chase arrays after allocation */ arr = JS_VALUE_GET_ARRAY (arg0_ref.val); JSArray *out = JS_VALUE_GET_ARRAY (result); for (int i = 0; i < slice_len; i++) { out->values[i] = arr->values[from + i]; } out->len = slice_len; JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return result; } JS_PopGCRef (ctx, &arg1_ref); JS_PopGCRef (ctx, &arg0_ref); return JS_NULL; } /* array(object) - keys */ if (JS_IsRecord (arg)) { /* Return object keys */ return JS_GetOwnPropertyNames (ctx, arg); } /* array(text) - split into characters */ /* array(text, separator) - split by separator */ /* array(text, length) - dice into chunks */ if (JS_VALUE_IS_TEXT (arg)) { int len = js_string_value_len (arg); if (argc < 2 || JS_IsNull (argv[1])) { /* Split into characters */ JSValue result = JS_NewArrayLen (ctx, len); if (JS_IsException (result)) { return result; } JSArray *out = JS_VALUE_GET_ARRAY (result); for (int i = 0; i < len; i++) { JSValue ch = js_sub_string_val (ctx, arg, i, i + 1); if (JS_IsException (ch)) { return JS_EXCEPTION; } out->values[i] = ch; } out->len = len; return result; } if (JS_VALUE_IS_TEXT (argv[1])) { /* Split by separator */ const char *cstr = JS_ToCString (ctx, arg); const char *sep = JS_ToCString (ctx, argv[1]); if (!cstr || !sep) { if (cstr) JS_FreeCString (ctx, cstr); if (sep) JS_FreeCString (ctx, sep); return JS_EXCEPTION; } size_t sep_len = strlen (sep); /* Count the number of parts first */ int64_t count = 0; if (sep_len == 0) { count = len; } else { const char *pos = cstr; const char *found; count = 1; while ((found = strstr (pos, sep)) != NULL) { count++; pos = found + sep_len; } } JSValue result = JS_NewArrayLen (ctx, count); if (JS_IsException (result)) { JS_FreeCString (ctx, cstr); JS_FreeCString (ctx, sep); return result; } int64_t idx = 0; const char *pos = cstr; const char *found; if (sep_len == 0) { for (int i = 0; i < len; i++) { JSValue ch = js_sub_string_val (ctx, arg, i, i + 1); JS_SetPropertyInt64 (ctx, result, idx++, ch); } } else { while ((found = strstr (pos, sep)) != NULL) { JSValue part = JS_NewStringLen (ctx, pos, found - pos); JS_SetPropertyInt64 (ctx, result, idx++, part); pos = found + sep_len; } JSValue part = JS_NewString (ctx, pos); JS_SetPropertyInt64 (ctx, result, idx++, part); } JS_FreeCString (ctx, cstr); JS_FreeCString (ctx, sep); return result; } if (JS_IsObject (argv[1]) && JS_IsRegExp (ctx, argv[1])) { /* Split by regex (manual "global" iteration; ignore g flag semantics) */ /* Root rx, result, arg across allocating calls */ JSGCRef rx_ref, res_ref, arg_ref; JS_PushGCRef (ctx, &rx_ref); rx_ref.val = argv[1]; JS_PushGCRef (ctx, &res_ref); res_ref.val = JS_NULL; JS_PushGCRef (ctx, &arg_ref); arg_ref.val = arg; #define RXS_CLEANUP() do { JS_PopGCRef (ctx, &arg_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &rx_ref); } while(0) JSValue result = JS_NewArray (ctx); if (JS_IsException (result)) { RXS_CLEANUP (); return result; } res_ref.val = result; /* Save & restore lastIndex to avoid mutating caller-visible state */ JSValue orig_last_index = JS_GetPropertyStr (ctx, rx_ref.val, "lastIndex"); if (JS_IsException (orig_last_index)) { RXS_CLEANUP (); return JS_EXCEPTION; } int pos = 0; int64_t out_idx = 0; while (pos <= len) { if (JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", JS_NewInt32 (ctx, 0)) < 0) goto fail_rx_split; JSValue sub_str = js_sub_string_val (ctx, arg_ref.val, pos, len); if (JS_IsException (sub_str)) goto fail_rx_split; JSValue exec_res = JS_Invoke (ctx, rx_ref.val, JS_KEY_exec, 1, (JSValue *)&sub_str); if (JS_IsException (exec_res)) goto fail_rx_split; if (JS_IsNull (exec_res)) { JSValue tail = js_sub_string_val (ctx, arg_ref.val, pos, len); if (JS_IsException (tail)) goto fail_rx_split; result = res_ref.val; if (JS_ArrayPush (ctx, &result, tail) < 0) { res_ref.val = result; goto fail_rx_split; } res_ref.val = result; break; } JSValue idx_val = JS_GetPropertyStr (ctx, exec_res, "index"); if (JS_IsException (idx_val)) goto fail_rx_split; int32_t local_index = 0; if (JS_ToInt32 (ctx, &local_index, idx_val)) goto fail_rx_split; if (local_index < 0) local_index = 0; int found = pos + local_index; if (found < pos) found = pos; if (found > len) { JSValue tail = js_sub_string_val (ctx, arg_ref.val, pos, len); if (JS_IsException (tail)) goto fail_rx_split; result = res_ref.val; JS_SetPropertyInt64 (ctx, result, out_idx++, tail); break; } JSValue end_val = JS_GetPropertyStr (ctx, exec_res, "end"); if (JS_IsException (end_val)) goto fail_rx_split; int32_t end = 0; if (JS_ToInt32 (ctx, &end, end_val)) goto fail_rx_split; int match_len = end - local_index; if (match_len < 0) match_len = 0; JSValue part = js_sub_string_val (ctx, arg_ref.val, pos, found); if (JS_IsException (part)) goto fail_rx_split; result = res_ref.val; if (JS_ArrayPush (ctx, &result, part) < 0) { res_ref.val = result; goto fail_rx_split; } res_ref.val = result; pos = found + match_len; if (match_len == 0) { if (found >= len) { JSValue empty = JS_NewStringLen (ctx, "", 0); if (JS_IsException (empty)) goto fail_rx_split; result = res_ref.val; if (JS_ArrayPush (ctx, &result, empty) < 0) { res_ref.val = result; goto fail_rx_split; } res_ref.val = result; break; } pos = found + 1; } } JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); result = res_ref.val; RXS_CLEANUP (); return result; fail_rx_split: if (!JS_IsException (orig_last_index)) { JS_SetPropertyStr (ctx, rx_ref.val, "lastIndex", orig_last_index); } RXS_CLEANUP (); return JS_EXCEPTION; } #undef RXS_CLEANUP if (JS_VALUE_IS_NUMBER (argv[1])) { /* Dice into chunks */ int chunk_len; if (JS_ToInt32 (ctx, &chunk_len, argv[1])) return JS_NULL; if (chunk_len <= 0) return JS_NULL; int64_t count = (len + chunk_len - 1) / chunk_len; JSValue result = JS_NewArrayLen (ctx, count); if (JS_IsException (result)) return result; int64_t idx = 0; for (int i = 0; i < len; i += chunk_len) { int end = i + chunk_len; if (end > len) end = len; JSValue chunk = js_sub_string_val (ctx, arg, i, end); if (JS_IsException (chunk)) return JS_EXCEPTION; JS_SetPropertyInt64 (ctx, result, idx++, chunk); } return result; } return JS_NULL; } return JS_NULL; } /* array.reduce(arr, fn, initial, reverse) */ /* GC-safe reduce: re-chase array after each call */ static JSValue js_cell_array_reduce (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; if (!JS_IsArray (argv[0])) return JS_NULL; if (!JS_IsFunction (argv[1])) return JS_NULL; /* GC-safe: root argv[0] and argv[1] for the duration of this function */ JSGCRef arr_ref, func_ref; JS_PushGCRef (ctx, &arr_ref); JS_PushGCRef (ctx, &func_ref); arr_ref.val = argv[0]; func_ref.val = argv[1]; JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); word_t len = arr->len; int reverse = argc > 3 && JS_ToBool (ctx, argv[3]); JSGCRef acc_ref; JSValue acc; if (argc < 3 || JS_IsNull (argv[2])) { if (len == 0) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } if (len == 1) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return arr->values[0]; } if (reverse) { acc = arr->values[len - 1]; for (word_t i = len - 1; i > 0; i--) { arr = JS_VALUE_GET_ARRAY (arr_ref.val); if (i - 1 >= arr->len) continue; JSValue args[2] = { acc, arr->values[i - 1] }; JS_PUSH_VALUE (ctx, acc); JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); JS_POP_VALUE (ctx, acc); if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } acc = new_acc; } } else { acc = arr->values[0]; for (word_t i = 1; i < len; i++) { arr = JS_VALUE_GET_ARRAY (arr_ref.val); if (i >= arr->len) break; JSValue args[2] = { acc, arr->values[i] }; JS_PUSH_VALUE (ctx, acc); JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); JS_POP_VALUE (ctx, acc); if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } acc = new_acc; } } } else { if (len == 0) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return argv[2]; } acc = argv[2]; if (reverse) { for (word_t i = len; i > 0; i--) { arr = JS_VALUE_GET_ARRAY (arr_ref.val); if (i - 1 >= arr->len) continue; JSValue args[2] = { acc, arr->values[i - 1] }; JS_PUSH_VALUE (ctx, acc); JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); JS_POP_VALUE (ctx, acc); if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } acc = new_acc; } } else { for (word_t i = 0; i < len; i++) { arr = JS_VALUE_GET_ARRAY (arr_ref.val); if (i >= arr->len) break; JSValue args[2] = { acc, arr->values[i] }; JS_PUSH_VALUE (ctx, acc); JSValue new_acc = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); JS_POP_VALUE (ctx, acc); if (JS_IsException (new_acc)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } acc = new_acc; } } } JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return acc; } /* array.for(arr, fn, reverse, exit) - GC-safe */ static JSValue js_cell_array_for (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; if (!JS_IsArray (argv[0])) return JS_NULL; if (!JS_IsFunction (argv[1])) return JS_NULL; /* GC-safe: root argv[0] and argv[1] */ JSGCRef arr_ref, func_ref; JS_PushGCRef (ctx, &arr_ref); JS_PushGCRef (ctx, &func_ref); arr_ref.val = argv[0]; func_ref.val = argv[1]; JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); word_t len = arr->len; if (len == 0) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } int reverse = argc > 2 && JS_ToBool (ctx, argv[2]); JSValue exit_val = argc > 3 ? argv[3] : JS_NULL; /* Determine function arity */ int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func_ref.val))->length; if (reverse) { for (word_t i = len; i > 0; i--) { /* Re-chase array each iteration */ arr = JS_VALUE_GET_ARRAY (arr_ref.val); if (i - 1 >= arr->len) continue; JSValue result; if (arity == 1) { JSValue item = arr->values[i - 1]; result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); } else { JSValue args[2]; args[0] = arr->values[i - 1]; args[1] = JS_NewInt32 (ctx, (int32_t)(i - 1)); result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); } if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } if (!JS_IsNull (exit_val) && js_strict_eq (ctx, result, exit_val)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return result; } } } else { for (word_t i = 0; i < len; i++) { /* Re-chase array each iteration */ arr = JS_VALUE_GET_ARRAY (arr_ref.val); if (i >= arr->len) break; JSValue result; if (arity == 1) { JSValue item = arr->values[i]; result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); } else { JSValue args[2]; args[0] = arr->values[i]; args[1] = JS_NewInt32 (ctx, (int32_t)i); result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); } if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } if (!JS_IsNull (exit_val) && js_strict_eq (ctx, result, exit_val)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return result; } } } JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } /* array.find(arr, fn, reverse, from) */ /* array.find(arr, fn, reverse, from) - GC-safe */ static JSValue js_cell_array_find (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; if (!JS_IsArray (argv[0])) return JS_NULL; /* GC-safe: root argv[0] */ JSGCRef arr_ref; JS_PushGCRef (ctx, &arr_ref); arr_ref.val = argv[0]; JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); word_t len = arr->len; int reverse = argc > 2 && JS_ToBool (ctx, argv[2]); int32_t from; if (argc > 3 && !JS_IsNull (argv[3])) { if (JS_ToInt32 (ctx, &from, argv[3])) { JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } } else { from = reverse ? (int32_t)(len - 1) : 0; } if (!JS_IsFunction (argv[1])) { /* Compare exactly - no GC concerns since no calls */ JSValue target = argv[1]; if (reverse) { for (int32_t i = from; i >= 0; i--) { arr = JS_VALUE_GET_ARRAY (arr_ref.val); if ((word_t)i >= arr->len) continue; if (js_strict_eq (ctx, arr->values[i], target)) { JS_PopGCRef (ctx, &arr_ref); return JS_NewInt32 (ctx, i); } } } else { for (word_t i = (word_t)from; i < len; i++) { arr = JS_VALUE_GET_ARRAY (arr_ref.val); if (i >= arr->len) break; if (js_strict_eq (ctx, arr->values[i], target)) { JS_PopGCRef (ctx, &arr_ref); return JS_NewInt32 (ctx, (int32_t)i); } } } JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } /* Use function predicate - must re-chase after each call */ JSGCRef func_ref; JS_PushGCRef (ctx, &func_ref); func_ref.val = argv[1]; int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func_ref.val))->length; if (arity == 2) { if (reverse) { for (int32_t i = from; i >= 0; i--) { arr = JS_VALUE_GET_ARRAY (arr_ref.val); if ((word_t)i >= arr->len) continue; JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, i) }; JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } if (JS_ToBool (ctx, result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NewInt32 (ctx, i); } } } else { for (word_t i = (word_t)from; i < len; i++) { arr = JS_VALUE_GET_ARRAY (arr_ref.val); if (i >= arr->len) break; JSValue args[2] = { arr->values[i], JS_NewInt32 (ctx, (int32_t)i) }; JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } if (JS_ToBool (ctx, result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NewInt32 (ctx, (int32_t)i); } } } } else { if (reverse) { for (int32_t i = from; i >= 0; i--) { arr = JS_VALUE_GET_ARRAY (arr_ref.val); if ((word_t)i >= arr->len) continue; JSValue item = arr->values[i]; JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } if (JS_ToBool (ctx, result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NewInt32 (ctx, i); } } } else { for (word_t i = (word_t)from; i < len; i++) { arr = JS_VALUE_GET_ARRAY (arr_ref.val); if (i >= arr->len) break; JSValue item = arr->values[i]; JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); if (JS_IsException (result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } if (JS_ToBool (ctx, result)) { JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NewInt32 (ctx, (int32_t)i); } } } } JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } /* array.filter(arr, fn) - GC-safe */ static JSValue js_cell_array_filter (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; if (!JS_IsArray (argv[0])) return JS_NULL; if (!JS_IsFunction (argv[1])) return JS_NULL; /* Protect input array and function throughout the loop */ JSGCRef input_ref, func_ref, result_ref; JS_PushGCRef (ctx, &input_ref); JS_PushGCRef (ctx, &func_ref); JS_PushGCRef (ctx, &result_ref); input_ref.val = argv[0]; func_ref.val = argv[1]; JSArray *arr = JS_VALUE_GET_ARRAY (input_ref.val); word_t len = arr->len; result_ref.val = JS_NewArray (ctx); if (JS_IsException (result_ref.val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &input_ref); return JS_EXCEPTION; } int arity = ((JSFunction *)JS_VALUE_GET_FUNCTION (func_ref.val))->length; for (word_t i = 0; i < len; i++) { /* Re-chase input array each iteration (it may have moved) */ arr = JS_VALUE_GET_ARRAY (input_ref.val); if (i >= arr->len) break; JSValue item = arr->values[i]; JSValue val; if (arity >= 2) { JSValue args[2] = { item, JS_NewInt32 (ctx, (int32_t)i) }; val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 2, args, 0); } else if (arity == 1) { val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &item, 0); } else { val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 0, NULL, 0); } if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &input_ref); return JS_EXCEPTION; } if (JS_VALUE_GET_TAG (val) == JS_TAG_BOOL) { if (JS_VALUE_GET_BOOL (val)) { /* Re-read item after the call (GC may have moved the input array) */ arr = JS_VALUE_GET_ARRAY (input_ref.val); if (i < arr->len) { item = arr->values[i]; if (js_intrinsic_array_push (ctx, &result_ref.val, item) < 0) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &input_ref); return JS_EXCEPTION; } } } } else { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &input_ref); return JS_NULL; } } JSValue result = result_ref.val; JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &input_ref); return result; } /* array.sort(arr, select) - GC-safe */ static JSValue js_cell_array_sort (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { (void)this_val; if (argc < 1) return JS_NULL; if (!JS_IsArray (argv[0])) return JS_NULL; /* GC-safe: root argv[0] */ JSGCRef arr_ref; JS_PushGCRef (ctx, &arr_ref); arr_ref.val = argv[0]; JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); word_t len = arr->len; JSValue result = JS_NewArrayLen (ctx, len); if (JS_IsException (result)) { JS_PopGCRef (ctx, &arr_ref); return result; } if (len == 0) { JS_PopGCRef (ctx, &arr_ref); return result; } /* Root result across allocating calls */ JSGCRef result_ref; JS_PushGCRef (ctx, &result_ref); result_ref.val = result; /* Re-chase arr after allocation */ arr = JS_VALUE_GET_ARRAY (arr_ref.val); len = arr->len; /* Use alloca for temporary working arrays - they won't move during GC */ JSValue *items = alloca (sizeof (JSValue) * len); double *keys = alloca (sizeof (double) * len); char **str_keys = NULL; int is_string = 0; /* Extract items and keys - re-chase arrays as needed */ for (word_t i = 0; i < len; i++) { /* Re-chase input and key arrays each iteration */ arr = JS_VALUE_GET_ARRAY (arr_ref.val); if (i >= arr->len) break; items[i] = arr->values[i]; JSValue key; if (argc < 2 || JS_IsNull (argv[1])) { key = items[i]; } else if (JS_VALUE_GET_TAG (argv[1]) == JS_TAG_INT) { /* Numeric index - use for nested arrays */ int32_t idx; JS_ToInt32 (ctx, &idx, argv[1]); if (JS_IsArray (items[i])) { JSArray *nested = JS_VALUE_GET_ARRAY (items[i]); if (idx >= 0 && (word_t)idx < nested->len) key = nested->values[idx]; else key = JS_NULL; } else { key = JS_GetPropertyInt64 (ctx, items[i], idx); /* Re-read items[i] after potential GC */ arr = JS_VALUE_GET_ARRAY (arr_ref.val); items[i] = arr->values[i]; } } else if (JS_VALUE_GET_TAG (argv[1]) == JS_TAG_STRING || JS_VALUE_GET_TAG (argv[1]) == JS_TAG_STRING_IMM) { JSValue prop_key = js_key_from_string (ctx, argv[1]); /* Re-read items[i] after allocation (js_key_from_string can trigger GC) */ arr = JS_VALUE_GET_ARRAY (arr_ref.val); items[i] = arr->values[i]; key = JS_GetProperty (ctx, items[i], prop_key); } else if (argc >= 2 && JS_IsArray (argv[1])) { /* Re-chase key array */ JSArray *key_arr = JS_VALUE_GET_ARRAY (argv[1]); if (i < key_arr->len) key = key_arr->values[i]; else key = JS_NULL; } else { key = items[i]; } if (JS_IsException (key)) { if (str_keys) { for (word_t j = 0; j < i; j++) JS_FreeCString (ctx, str_keys[j]); js_free (ctx, str_keys); } JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } int key_tag = JS_VALUE_GET_TAG (key); if (key_tag == JS_TAG_INT || key_tag == JS_TAG_FLOAT64 || key_tag == JS_TAG_SHORT_FLOAT) { JS_ToFloat64 (ctx, &keys[i], key); if (i == 0) is_string = 0; } else if (key_tag == JS_TAG_STRING || key_tag == JS_TAG_STRING_IMM) { if (i == 0) { is_string = 1; str_keys = alloca (sizeof (char *) * len); } if (is_string) { str_keys[i] = (char *)JS_ToCString (ctx, key); } } else { if (str_keys) { for (word_t j = 0; j < i; j++) JS_FreeCString (ctx, str_keys[j]); } JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } } /* Re-read all items from GC-safe source after key extraction (GC may have moved them) */ arr = JS_VALUE_GET_ARRAY (arr_ref.val); for (word_t i = 0; i < len && i < arr->len; i++) items[i] = arr->values[i]; /* Create index array using alloca */ int *indices = alloca (sizeof (int) * len); for (word_t i = 0; i < len; i++) indices[i] = (int)i; /* Simple insertion sort (stable) */ for (word_t i = 1; i < len; i++) { int temp = indices[i]; int j = (int)i - 1; while (j >= 0) { int cmp; if (is_string) { cmp = strcmp (str_keys[indices[j]], str_keys[temp]); } else { double a = keys[indices[j]], b = keys[temp]; cmp = (a > b) - (a < b); } if (cmp <= 0) break; indices[j + 1] = indices[j]; j--; } indices[j + 1] = temp; } /* Build sorted array directly into output - re-chase result */ JSArray *out = JS_VALUE_GET_ARRAY (result_ref.val); for (word_t i = 0; i < len; i++) { out->values[i] = items[indices[i]]; } out->len = len; /* Cleanup string keys only (alloca frees automatically) */ if (str_keys) { for (word_t i = 0; i < len; i++) JS_FreeCString (ctx, str_keys[i]); } result = result_ref.val; JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &arr_ref); return result; } /* ---------------------------------------------------------------------------- * object function and sub-functions * ---------------------------------------------------------------------------- */ /* object(arg, arg2) - main object function */ static JSValue js_cell_object (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; JSValue arg = argv[0]; /* object(object) - shallow mutable copy */ if (JS_IsObject (arg) && !JS_IsArray (arg) && !JS_IsFunction (arg)) { if (argc < 2 || JS_IsNull (argv[1])) { /* Shallow copy - root arg, result, keys across allocating calls */ JSGCRef arg_ref, res_ref, keys_ref; JS_PushGCRef (ctx, &arg_ref); arg_ref.val = arg; JS_PushGCRef (ctx, &res_ref); res_ref.val = JS_NewObject (ctx); JS_PushGCRef (ctx, &keys_ref); keys_ref.val = JS_NULL; #define OBJ_COPY_CLEANUP() do { JS_PopGCRef (ctx, &keys_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &arg_ref); } while(0) if (JS_IsException (res_ref.val)) { OBJ_COPY_CLEANUP (); return JS_EXCEPTION; } keys_ref.val = JS_GetOwnPropertyNames (ctx, arg_ref.val); if (JS_IsException (keys_ref.val)) { OBJ_COPY_CLEANUP (); return JS_EXCEPTION; } uint32_t len; if (js_get_length32 (ctx, &len, keys_ref.val)) { OBJ_COPY_CLEANUP (); return JS_EXCEPTION; } for (uint32_t i = 0; i < len; i++) { JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); JSValue val = JS_GetProperty (ctx, arg_ref.val, key); if (JS_IsException (val)) { OBJ_COPY_CLEANUP (); return JS_EXCEPTION; } JS_SetProperty (ctx, res_ref.val, key, val); } JSValue result = res_ref.val; OBJ_COPY_CLEANUP (); return result; } #undef OBJ_COPY_CLEANUP /* object(object, another_object) - combine */ if (JS_IsObject (argv[1]) && !JS_IsArray (argv[1])) { JSGCRef arg_ref, arg2_ref, res_ref, keys_ref; JS_PushGCRef (ctx, &arg_ref); arg_ref.val = arg; JS_PushGCRef (ctx, &arg2_ref); arg2_ref.val = argv[1]; JS_PushGCRef (ctx, &res_ref); res_ref.val = JS_NewObject (ctx); JS_PushGCRef (ctx, &keys_ref); keys_ref.val = JS_NULL; #define OBJ_COMBINE_CLEANUP() do { JS_PopGCRef (ctx, &keys_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &arg2_ref); JS_PopGCRef (ctx, &arg_ref); } while(0) if (JS_IsException (res_ref.val)) { OBJ_COMBINE_CLEANUP (); return JS_EXCEPTION; } /* Copy from first object */ keys_ref.val = JS_GetOwnPropertyNames (ctx, arg_ref.val); if (JS_IsException (keys_ref.val)) { OBJ_COMBINE_CLEANUP (); return JS_EXCEPTION; } uint32_t len; if (js_get_length32 (ctx, &len, keys_ref.val)) { OBJ_COMBINE_CLEANUP (); return JS_EXCEPTION; } for (uint32_t i = 0; i < len; i++) { JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); JSValue val = JS_GetProperty (ctx, arg_ref.val, key); if (JS_IsException (val)) { OBJ_COMBINE_CLEANUP (); return JS_EXCEPTION; } JS_SetProperty (ctx, res_ref.val, key, val); } /* Copy from second object */ keys_ref.val = JS_GetOwnPropertyNames (ctx, arg2_ref.val); if (JS_IsException (keys_ref.val)) { OBJ_COMBINE_CLEANUP (); return JS_EXCEPTION; } if (js_get_length32 (ctx, &len, keys_ref.val)) { OBJ_COMBINE_CLEANUP (); return JS_EXCEPTION; } for (uint32_t i = 0; i < len; i++) { JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); JSValue val = JS_GetProperty (ctx, arg2_ref.val, key); if (JS_IsException (val)) { OBJ_COMBINE_CLEANUP (); return JS_EXCEPTION; } JS_SetProperty (ctx, res_ref.val, key, val); } JSValue result = res_ref.val; OBJ_COMBINE_CLEANUP (); return result; } #undef OBJ_COMBINE_CLEANUP /* object(object, array_of_keys) - select */ if (JS_IsArray (argv[1])) { JSGCRef arg_ref, res_ref, karr_ref; JS_PushGCRef (ctx, &arg_ref); arg_ref.val = arg; JS_PushGCRef (ctx, &res_ref); res_ref.val = JS_NewObject (ctx); JS_PushGCRef (ctx, &karr_ref); karr_ref.val = argv[1]; #define OBJ_SEL_CLEANUP() do { JS_PopGCRef (ctx, &karr_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &arg_ref); } while(0) if (JS_IsException (res_ref.val)) { OBJ_SEL_CLEANUP (); return JS_EXCEPTION; } JSArray *keys = JS_VALUE_GET_ARRAY (karr_ref.val); int len = keys->len; for (int i = 0; i < len; i++) { keys = JS_VALUE_GET_ARRAY (karr_ref.val); /* re-chase each iteration */ if (i >= (int)keys->len) break; JSValue key = keys->values[i]; if (JS_IsText (key)) { JSValue prop_key = js_key_from_string (ctx, key); int has = JS_HasProperty (ctx, arg_ref.val, prop_key); if (has > 0) { JSValue val = JS_GetProperty (ctx, arg_ref.val, prop_key); if (!JS_IsException (val)) { JS_SetProperty (ctx, res_ref.val, prop_key, val); } } } } JSValue result = res_ref.val; OBJ_SEL_CLEANUP (); return result; } #undef OBJ_SEL_CLEANUP } /* object(array_of_keys) - set with true values */ /* object(array_of_keys, value) - value set */ /* object(array_of_keys, function) - functional value set */ if (JS_IsArray (arg)) { JSArray *keys = JS_VALUE_GET_ARRAY (arg); int len = keys->len; int is_func = argc >= 2 && JS_IsFunction (argv[1]); /* Root keys array and func/value BEFORE JS_NewObject which may trigger GC. argv[] is on the C stack and is NOT a GC root, so after any allocation that triggers GC, argv[] values become dangling pointers. */ JSGCRef keys_ref, func_ref, result_ref; JS_PushGCRef (ctx, &keys_ref); keys_ref.val = arg; /* use already-read arg, not argv[0] */ JS_PushGCRef (ctx, &func_ref); func_ref.val = argc >= 2 ? argv[1] : JS_NULL; JS_PushGCRef (ctx, &result_ref); result_ref.val = JS_NULL; JSValue result = JS_NewObject (ctx); if (JS_IsException (result)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &keys_ref); return result; } result_ref.val = result; for (int i = 0; i < len; i++) { keys = JS_VALUE_GET_ARRAY (keys_ref.val); if (i >= (int)keys->len) break; JSValue key = keys->values[i]; if (JS_IsText (key)) { /* Use JSValue key directly - create interned key */ JSValue prop_key = js_key_from_string (ctx, key); JSValue val; if (argc < 2 || JS_IsNull (func_ref.val)) { val = JS_TRUE; } else if (is_func) { JSValue arg_key = key; val = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &arg_key, 0); if (JS_IsException (val)) { JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &keys_ref); return JS_EXCEPTION; } } else { val = func_ref.val; } JS_SetProperty (ctx, result_ref.val, prop_key, val); /* prop_key is interned, no need to free */ } } result = JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &func_ref); JS_PopGCRef (ctx, &keys_ref); return result; } return JS_NULL; } /* ---------------------------------------------------------------------------- * fn function and sub-functions * ---------------------------------------------------------------------------- */ /* fn.apply(func, args) - arity is enforced in JS_CallInternal */ static JSValue js_cell_fn_apply (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; if (!JS_IsFunction (argv[0])) return argv[0]; JSGCRef func_ref, args_ref; JS_PushGCRef (ctx, &func_ref); JS_PushGCRef (ctx, &args_ref); func_ref.val = argv[0]; args_ref.val = argc >= 2 ? argv[1] : JS_NULL; if (argc < 2) { JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 0, NULL, 0); JS_PopGCRef (ctx, &args_ref); JS_PopGCRef (ctx, &func_ref); return result; } if (!JS_IsArray (args_ref.val)) { /* Wrap single value in array */ JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 1, &args_ref.val, 0); JS_PopGCRef (ctx, &args_ref); JS_PopGCRef (ctx, &func_ref); return result; } JSArray *arr = JS_VALUE_GET_ARRAY (args_ref.val); int len = arr->len; if (len == 0) { JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, 0, NULL, 0); JS_PopGCRef (ctx, &args_ref); JS_PopGCRef (ctx, &func_ref); return result; } JSValue *args = js_malloc (ctx, sizeof (JSValue) * len); if (!args) { JS_PopGCRef (ctx, &args_ref); JS_PopGCRef (ctx, &func_ref); return JS_EXCEPTION; } arr = JS_VALUE_GET_ARRAY (args_ref.val); /* re-chase after malloc via rooted ref */ for (int i = 0; i < len; i++) { args[i] = arr->values[i]; } JSValue result = JS_CallInternal (ctx, func_ref.val, JS_NULL, len, args, 0); js_free (ctx, args); JS_PopGCRef (ctx, &args_ref); JS_PopGCRef (ctx, &func_ref); return result; } /* ============================================================================ * Blob Intrinsic Type * ============================================================================ */ /* Helper to check if JSValue is a blob */ blob *js_get_blob (JSContext *ctx, JSValue val) { /* Must be a record, not an array or other object type */ if (!JS_IsRecord(val)) return NULL; JSRecord *p = JS_VALUE_GET_OBJ (val); if (REC_GET_CLASS_ID(p) != JS_CLASS_BLOB) return NULL; return REC_GET_OPAQUE(p); } /* Helper to create a new blob JSValue */ JSValue js_new_blob (JSContext *ctx, blob *b) { JSValue obj = JS_NewObjectClass (ctx, JS_CLASS_BLOB); if (JS_IsException (obj)) { blob_destroy (b); return obj; } JS_SetOpaque (obj, b); return obj; } /* Blob finalizer */ static void js_blob_finalizer (JSRuntime *rt, JSValue val) { blob *b = JS_GetOpaque (val, JS_CLASS_BLOB); if (b) blob_destroy (b); } /* blob() constructor */ static JSValue js_blob_constructor (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { blob *bd = NULL; /* blob() - empty blob */ if (argc == 0) { bd = blob_new (0); } /* blob(capacity) - blob with initial capacity in bits */ else if (argc == 1 && JS_IsNumber (argv[0])) { int64_t capacity_bits; if (JS_ToInt64 (ctx, &capacity_bits, argv[0]) < 0) return JS_EXCEPTION; if (capacity_bits < 0) capacity_bits = 0; bd = blob_new ((size_t)capacity_bits); } /* blob(length, logical/random) - blob with fill or random */ else if (argc == 2 && JS_IsNumber (argv[0])) { int64_t length_bits; if (JS_ToInt64 (ctx, &length_bits, argv[0]) < 0) return JS_EXCEPTION; if (length_bits < 0) length_bits = 0; if (JS_IsBool (argv[1])) { int is_one = JS_ToBool (ctx, argv[1]); bd = blob_new_with_fill ((size_t)length_bits, is_one); } else if (JS_IsFunction (argv[1])) { /* Random function provided */ size_t bytes = (length_bits + 7) / 8; bd = blob_new ((size_t)length_bits); if (bd) { bd->length = length_bits; memset (bd->data, 0, bytes); size_t bits_written = 0; while (bits_written < (size_t)length_bits) { JSValue randval = JS_Call (ctx, argv[1], JS_NULL, 0, NULL); if (JS_IsException (randval)) { blob_destroy (bd); return JS_EXCEPTION; } int64_t fitval; JS_ToInt64 (ctx, &fitval, randval); size_t bits_to_use = length_bits - bits_written; if (bits_to_use > 52) bits_to_use = 52; for (size_t j = 0; j < bits_to_use; j++) { size_t bit_pos = bits_written + j; size_t byte_idx = bit_pos / 8; size_t bit_idx = bit_pos % 8; if (fitval & (1LL << j)) bd->data[byte_idx] |= (uint8_t)(1 << bit_idx); else bd->data[byte_idx] &= (uint8_t)~(1 << bit_idx); } bits_written += bits_to_use; } } } else { return JS_ThrowTypeError ( ctx, "Second argument must be boolean or random function"); } } /* blob(blob, from, to) - copy from another blob */ else if (argc >= 1 && JS_IsObject (argv[0])) { blob *src = js_get_blob (ctx, argv[0]); if (!src) return JS_ThrowTypeError (ctx, "blob constructor: argument 1 not a blob"); int64_t from = 0, to = (int64_t)src->length; if (argc >= 2 && JS_IsNumber (argv[1])) { JS_ToInt64 (ctx, &from, argv[1]); if (from < 0) from = 0; } if (argc >= 3 && JS_IsNumber (argv[2])) { JS_ToInt64 (ctx, &to, argv[2]); if (to < from) to = from; if (to > (int64_t)src->length) to = (int64_t)src->length; } bd = blob_new_from_blob (src, (size_t)from, (size_t)to); } /* blob(text) - create blob from UTF-8 string */ else if (argc == 1 && (JS_VALUE_GET_TAG (argv[0]) == JS_TAG_STRING || JS_VALUE_GET_TAG (argv[0]) == JS_TAG_STRING_IMM)) { const char *str = JS_ToCString (ctx, argv[0]); if (!str) return JS_EXCEPTION; size_t len = strlen (str); bd = blob_new (len * 8); if (bd) { memcpy (bd->data, str, len); bd->length = len * 8; } JS_FreeCString (ctx, str); } else { return JS_ThrowTypeError (ctx, "blob constructor: invalid arguments"); } if (!bd) return JS_ThrowOutOfMemory (ctx); return js_new_blob (ctx, bd); } /* blob.write_bit(logical) */ static JSValue js_blob_write_bit (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_ThrowTypeError (ctx, "write_bit(logical) requires 1 argument"); blob *bd = js_get_blob (ctx, this_val); if (!bd) return JS_ThrowTypeError (ctx, "write_bit: not called on a blob"); int bit_val; if (JS_IsNumber (argv[0])) { int32_t num; JS_ToInt32 (ctx, &num, argv[0]); if (num != 0 && num != 1) return JS_ThrowTypeError ( ctx, "write_bit: value must be true, false, 0, or 1"); bit_val = num; } else { bit_val = JS_ToBool (ctx, argv[0]); } if (blob_write_bit (bd, bit_val) < 0) return JS_ThrowTypeError (ctx, "write_bit: cannot write (maybe stone or OOM)"); return JS_NULL; } /* blob.write_blob(second_blob) */ static JSValue js_blob_write_blob (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_ThrowTypeError (ctx, "write_blob(second_blob) requires 1 argument"); blob *bd = js_get_blob (ctx, this_val); if (!bd) return JS_ThrowTypeError (ctx, "write_blob: not called on a blob"); blob *second = js_get_blob (ctx, argv[0]); if (!second) return JS_ThrowTypeError (ctx, "write_blob: argument must be a blob"); if (blob_write_blob (bd, second) < 0) return JS_ThrowTypeError (ctx, "write_blob: cannot write to stone blob or OOM"); return JS_NULL; } /* blob.write_number(number) - write dec64 */ static JSValue js_blob_write_number (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_ThrowTypeError (ctx, "write_number(number) requires 1 argument"); blob *bd = js_get_blob (ctx, this_val); if (!bd) return JS_ThrowTypeError (ctx, "write_number: not called on a blob"); double d; if (JS_ToFloat64 (ctx, &d, argv[0]) < 0) return JS_EXCEPTION; if (blob_write_dec64 (bd, d) < 0) return JS_ThrowTypeError ( ctx, "write_number: cannot write to stone blob or OOM"); return JS_NULL; } /* blob.write_fit(value, len) */ static JSValue js_blob_write_fit (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_ThrowTypeError (ctx, "write_fit(value, len) requires 2 arguments"); blob *bd = js_get_blob (ctx, this_val); if (!bd) return JS_ThrowTypeError (ctx, "write_fit: not called on a blob"); int64_t value; int32_t len; if (JS_ToInt64 (ctx, &value, argv[0]) < 0) return JS_EXCEPTION; if (JS_ToInt32 (ctx, &len, argv[1]) < 0) return JS_EXCEPTION; if (blob_write_fit (bd, value, len) < 0) return JS_ThrowTypeError (ctx, "write_fit: value doesn't fit or stone blob"); return JS_NULL; } /* blob.write_text(text) */ static JSValue js_blob_write_text (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_ThrowTypeError (ctx, "write_text(text) requires 1 argument"); blob *bd = js_get_blob (ctx, this_val); if (!bd) return JS_ThrowTypeError (ctx, "write_text: not called on a blob"); const char *str = JS_ToCString (ctx, argv[0]); if (!str) return JS_EXCEPTION; if (blob_write_text (bd, str) < 0) { JS_FreeCString (ctx, str); return JS_ThrowTypeError (ctx, "write_text: cannot write to stone blob or OOM"); } JS_FreeCString (ctx, str); return JS_NULL; } /* blob.write_pad(block_size) */ static JSValue js_blob_write_pad (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_ThrowTypeError (ctx, "write_pad(block_size) requires 1 argument"); blob *bd = js_get_blob (ctx, this_val); if (!bd) return JS_ThrowTypeError (ctx, "write_pad: not called on a blob"); int32_t block_size; if (JS_ToInt32 (ctx, &block_size, argv[0]) < 0) return JS_EXCEPTION; if (blob_write_pad (bd, block_size) < 0) return JS_ThrowTypeError (ctx, "write_pad: cannot write"); return JS_NULL; } /* blob.w16(value) - write 16-bit value */ static JSValue js_blob_w16 (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_ThrowTypeError (ctx, "w16(value) requires 1 argument"); blob *bd = js_get_blob (ctx, this_val); if (!bd) return JS_ThrowTypeError (ctx, "w16: not called on a blob"); int32_t value; if (JS_ToInt32 (ctx, &value, argv[0]) < 0) return JS_EXCEPTION; int16_t short_val = (int16_t)value; if (blob_write_bytes (bd, &short_val, sizeof (int16_t)) < 0) return JS_ThrowTypeError (ctx, "w16: cannot write"); return JS_NULL; } /* blob.w32(value) - write 32-bit value */ static JSValue js_blob_w32 (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_ThrowTypeError (ctx, "w32(value) requires 1 argument"); blob *bd = js_get_blob (ctx, this_val); if (!bd) return JS_ThrowTypeError (ctx, "w32: not called on a blob"); int32_t value; if (JS_ToInt32 (ctx, &value, argv[0]) < 0) return JS_EXCEPTION; if (blob_write_bytes (bd, &value, sizeof (int32_t)) < 0) return JS_ThrowTypeError (ctx, "w32: cannot write"); return JS_NULL; } /* blob.wf(value) - write float */ static JSValue js_blob_wf (JSContext *ctx, JSValue this_val, JSValue arg0) { if (JS_IsNull (arg0)) return JS_ThrowTypeError (ctx, "wf(value) requires 1 argument"); blob *bd = js_get_blob (ctx, this_val); if (!bd) return JS_ThrowTypeError (ctx, "wf: not called on a blob"); float f; double d; if (JS_ToFloat64 (ctx, &d, arg0) < 0) return JS_EXCEPTION; f = d; if (blob_write_bytes (bd, &f, sizeof (f)) < 0) return JS_ThrowTypeError (ctx, "wf: cannot write"); return JS_NULL; } /* blob.read_logical(from) */ static JSValue js_blob_read_logical (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_ThrowTypeError (ctx, "read_logical(from) requires 1 argument"); blob *bd = js_get_blob (ctx, this_val); if (!bd) return JS_ThrowTypeError (ctx, "read_logical: not called on a blob"); int64_t pos; if (JS_ToInt64 (ctx, &pos, argv[0]) < 0) return JS_ThrowInternalError (ctx, "must provide a positive bit"); if (pos < 0) return JS_ThrowRangeError (ctx, "read_logical: position must be non-negative"); int bit_val; if (blob_read_bit (bd, (size_t)pos, &bit_val) < 0) return JS_ThrowTypeError (ctx, "read_logical: blob must be stone"); return JS_NewBool (ctx, bit_val); } /* blob.read_blob(from, to) */ JSValue js_blob_read_blob (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { blob *bd = js_get_blob (ctx, this_val); if (!bd) return JS_ThrowTypeError (ctx, "read_blob: not called on a blob"); if (!bd->is_stone) return JS_ThrowTypeError (ctx, "read_blob: blob must be stone"); int64_t from = 0; int64_t to = bd->length; if (argc >= 1) { if (JS_ToInt64 (ctx, &from, argv[0]) < 0) return JS_EXCEPTION; if (from < 0) from = 0; } if (argc >= 2) { if (JS_ToInt64 (ctx, &to, argv[1]) < 0) return JS_EXCEPTION; if (to > (int64_t)bd->length) to = bd->length; } blob *new_bd = blob_read_blob (bd, from, to); if (!new_bd) return JS_ThrowOutOfMemory (ctx); return js_new_blob (ctx, new_bd); } /* blob.read_number(from) */ static JSValue js_blob_read_number (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_ThrowTypeError (ctx, "read_number(from) requires 1 argument"); blob *bd = js_get_blob (ctx, this_val); if (!bd) return JS_ThrowTypeError (ctx, "read_number: not called on a blob"); if (!bd->is_stone) return JS_ThrowTypeError (ctx, "read_number: blob must be stone"); double from; if (JS_ToFloat64 (ctx, &from, argv[0]) < 0) return JS_EXCEPTION; if (from < 0) return JS_ThrowRangeError (ctx, "read_number: position must be non-negative"); double d; if (blob_read_dec64 (bd, from, &d) < 0) return JS_ThrowRangeError (ctx, "read_number: out of range"); return JS_NewFloat64 (ctx, d); } /* blob.read_fit(from, len) */ static JSValue js_blob_read_fit (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_ThrowTypeError (ctx, "read_fit(from, len) requires 2 arguments"); blob *bd = js_get_blob (ctx, this_val); if (!bd) return JS_ThrowTypeError (ctx, "read_fit: not called on a blob"); if (!bd->is_stone) return JS_ThrowTypeError (ctx, "read_fit: blob must be stone"); int64_t from; int32_t len; if (JS_ToInt64 (ctx, &from, argv[0]) < 0) return JS_EXCEPTION; if (JS_ToInt32 (ctx, &len, argv[1]) < 0) return JS_EXCEPTION; if (from < 0) return JS_ThrowRangeError (ctx, "read_fit: position must be non-negative"); int64_t value; if (blob_read_fit (bd, from, len, &value) < 0) return JS_ThrowRangeError (ctx, "read_fit: out of range or invalid length"); return JS_NewInt64 (ctx, value); } /* blob.read_text(from) */ JSValue js_blob_read_text (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { blob *bd = js_get_blob (ctx, this_val); if (!bd) return JS_ThrowTypeError (ctx, "read_text: not called on a blob"); if (!bd->is_stone) return JS_ThrowTypeError (ctx, "read_text: blob must be stone"); int64_t from = 0; if (argc >= 1) { if (JS_ToInt64 (ctx, &from, argv[0]) < 0) return JS_EXCEPTION; } char *text; size_t bits_read; if (blob_read_text (bd, from, &text, &bits_read) < 0) return JS_ThrowRangeError (ctx, "read_text: out of range or invalid encoding"); JSValue result = JS_NewString (ctx, text); /* Note: blob_read_text uses system malloc, so we use sys_free */ sys_free (text); return result; } /* blob.pad?(from, block_size) */ static JSValue js_blob_pad_q (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_ThrowTypeError (ctx, "pad?(from, block_size) requires 2 arguments"); blob *bd = js_get_blob (ctx, this_val); if (!bd) return JS_ThrowTypeError (ctx, "pad?: not called on a blob"); if (!bd->is_stone) return JS_ThrowTypeError (ctx, "pad?: blob must be stone"); int64_t from; int32_t block_size; if (JS_ToInt64 (ctx, &from, argv[0]) < 0) return JS_EXCEPTION; if (JS_ToInt32 (ctx, &block_size, argv[1]) < 0) return JS_EXCEPTION; return JS_NewBool (ctx, blob_pad_check (bd, from, block_size)); } static const JSCFunctionListEntry js_blob_proto_funcs[] = { /* Write methods */ JS_CFUNC_DEF ("write_bit", 1, js_blob_write_bit), JS_CFUNC_DEF ("write_blob", 1, js_blob_write_blob), JS_CFUNC_DEF ("write_number", 1, js_blob_write_number), JS_CFUNC_DEF ("write_fit", 2, js_blob_write_fit), JS_CFUNC_DEF ("write_text", 1, js_blob_write_text), JS_CFUNC_DEF ("write_pad", 1, js_blob_write_pad), JS_CFUNC1_DEF ("wf", js_blob_wf), JS_CFUNC_DEF ("w16", 1, js_blob_w16), JS_CFUNC_DEF ("w32", 1, js_blob_w32), /* Read methods */ JS_CFUNC_DEF ("read_logical", 1, js_blob_read_logical), JS_CFUNC_DEF ("read_blob", 2, js_blob_read_blob), JS_CFUNC_DEF ("read_number", 1, js_blob_read_number), JS_CFUNC_DEF ("read_fit", 2, js_blob_read_fit), JS_CFUNC_DEF ("read_text", 1, js_blob_read_text), JS_CFUNC_DEF ("pad?", 2, js_blob_pad_q), }; /* ============================================================================ * Blob external API functions (called from other files via cell.h) * ============================================================================ */ /* Initialize blob - called during context setup (but we do it in * JS_AddIntrinsicBaseObjects now) */ JSValue js_blob_use (JSContext *js) { return JS_GetPropertyStr (js, js->global_obj, "blob"); } /* Create a new blob from raw data, stone it, and return as JSValue */ JSValue js_new_blob_stoned_copy (JSContext *js, void *data, size_t bytes) { blob *b = blob_new (bytes * 8); if (!b) return JS_ThrowOutOfMemory (js); memcpy (b->data, data, bytes); b->length = bytes * 8; blob_make_stone (b); return js_new_blob (js, b); } /* Get raw data pointer from a blob (must be stone) - returns byte count */ void *js_get_blob_data (JSContext *js, size_t *size, JSValue v) { blob *b = js_get_blob (js, v); *size = (b->length + 7) / 8; if (!b) { JS_ThrowReferenceError (js, "get_blob_data: not called on a blob"); return NULL; } if (!b->is_stone) { JS_ThrowReferenceError (js, "attempted to read data from a non-stone blob"); return NULL; } if (b->length % 8 != 0) { JS_ThrowReferenceError ( js, "attempted to read data from a non-byte aligned blob [length is %zu]", b->length); return NULL; } return b->data; } /* Get raw data pointer from a blob (must be stone) - returns bit count */ void *js_get_blob_data_bits (JSContext *js, size_t *bits, JSValue v) { blob *b = js_get_blob (js, v); if (!b) { JS_ThrowReferenceError (js, "get_blob_data_bits: not called on a blob"); return NULL; } if (!b->is_stone) { JS_ThrowReferenceError (js, "attempted to read data from a non-stone blob"); return NULL; } if (!b->data) { JS_ThrowReferenceError (js, "attempted to read data from an empty blob"); return NULL; } if (b->length % 8 != 0) { JS_ThrowReferenceError ( js, "attempted to read data from a non-byte aligned blob"); return NULL; } if (b->length == 0) { JS_ThrowReferenceError (js, "attempted to read data from an empty blob"); return NULL; } *bits = b->length; return b->data; } /* Check if a value is a blob */ int js_is_blob (JSContext *js, JSValue v) { return js_get_blob (js, v) != NULL; } /* ============================================================================ * eval() function - compile and execute code with environment * ============================================================================ */ /* eval(text, env) - evaluate code with optional environment record * text: string to compile and execute * env: optional stone record for variable bindings (checked first before intrinsics) */ static JSValue js_cell_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { const char *str; size_t len; JSValue env = JS_NULL; JSValue result; JSGCRef env_ref; if (argc < 1 || !JS_IsText (argv[0])) { return JS_ThrowTypeError (ctx, "eval requires a text argument"); } /* Get optional environment record (must be stone if provided) */ if (argc > 1 && !JS_IsNull (argv[1])) { if (!JS_IsRecord (argv[1])) { return JS_ThrowTypeError (ctx, "eval environment must be an object"); } JSRecord *rec = (JSRecord *)JS_VALUE_GET_OBJ (argv[1]); if (!objhdr_s (rec->mist_hdr)) { return JS_ThrowTypeError (ctx, "eval environment must be stoned"); } env = argv[1]; } /* Protect env from GC during compilation */ JS_AddGCRef (ctx, &env_ref); env_ref.val = env; /* Get text string */ str = JS_ToCStringLen (ctx, &len, argv[0]); if (!str) { JS_DeleteGCRef (ctx, &env_ref); return JS_EXCEPTION; } /* Compile the text */ JSValue fun = JS_Compile (ctx, str, len, ""); JS_FreeCString (ctx, str); if (JS_IsException (fun)) { JS_DeleteGCRef (ctx, &env_ref); return fun; } /* Update env from GC ref (may have moved) */ env = env_ref.val; JS_DeleteGCRef (ctx, &env_ref); /* Integrate with environment */ result = JS_Integrate (ctx, fun, env); return result; } /* ============================================================================ * mach_eval() function - compile and execute via MACH VM * ============================================================================ */ /* mach_eval(name, source) - parse to AST and run through MACH VM */ static JSValue js_mach_eval (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2 || !JS_IsText (argv[0]) || !JS_IsText (argv[1])) return JS_ThrowTypeError (ctx, "mach_eval requires (name, source) text arguments"); const char *name = JS_ToCString (ctx, argv[0]); if (!name) return JS_EXCEPTION; const char *source = JS_ToCString (ctx, argv[1]); if (!source) { JS_FreeCString (ctx, name); return JS_EXCEPTION; } cJSON *ast = JS_ASTTree (source, strlen (source), name); JS_FreeCString (ctx, source); if (!ast) { JS_FreeCString (ctx, name); return JS_ThrowSyntaxError (ctx, "mach_eval: failed to parse AST"); } JSValue result = JS_RunMachTree (ctx, ast, JS_NULL); cJSON_Delete (ast); JS_FreeCString (ctx, name); return result; } /* ============================================================================ * stone() function - deep freeze with blob support * ============================================================================ */ /* stone(object) - deep freeze an object */ static JSValue js_cell_stone (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; JSValue obj = argv[0]; blob *bd = js_get_blob (ctx, obj); if (bd) { bd->is_stone = true; return obj; } if (JS_IsObject (obj)) { JSRecord *rec = JS_VALUE_GET_RECORD (obj); obj_set_stone (rec); return obj; } if (JS_IsArray (obj)) { JSArray *arr = JS_VALUE_GET_ARRAY (obj); arr->mist_hdr = objhdr_set_s (arr->mist_hdr, true); return obj; } return JS_NULL; } /* ============================================================================ * reverse() function - reverse an array * ============================================================================ */ static JSValue js_cell_reverse (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; JSValue value = argv[0]; /* Handle arrays */ if (JS_IsArray (value)) { /* GC-safe: root argv[0] */ JSGCRef arr_ref; JS_PushGCRef (ctx, &arr_ref); arr_ref.val = argv[0]; JSArray *arr = JS_VALUE_GET_ARRAY (arr_ref.val); int len = arr->len; JSValue result = JS_NewArrayLen (ctx, len); if (JS_IsException (result)) { JS_PopGCRef (ctx, &arr_ref); return result; } arr = JS_VALUE_GET_ARRAY (arr_ref.val); /* re-chase after allocation */ JSArray *out = JS_VALUE_GET_ARRAY (result); for (int i = len - 1, j = 0; i >= 0; i--, j++) { out->values[j] = arr->values[i]; } out->len = len; JS_PopGCRef (ctx, &arr_ref); return result; } /* Handle strings */ if (JS_IsText (value)) { int len = js_string_value_len (value); if (len == 0) return JS_NewString (ctx, ""); JSText *str = js_alloc_string (ctx, len); if (!str) return JS_EXCEPTION; for (int i = 0; i < len; i++) { string_put (str, i, js_string_value_get (value, len - 1 - i)); } str->length = len; return pretext_end (ctx, str); } /* Handle blobs */ blob *bd = js_get_blob (ctx, value); if (bd) { /* Blobs need proper blob reversal support - return null for now */ return JS_NULL; } return JS_NULL; } /* ============================================================================ * proto() function - get prototype of an object * ============================================================================ */ static JSValue js_cell_pop (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; JSValue obj = argv[0]; if (!JS_IsArray (obj)) return JS_NULL; JSArray *arr = JS_VALUE_GET_ARRAY (obj); if (arr->len == 0) return JS_NULL; /* Transfer ownership: take value without dup, clear slot, decrement len */ JSValue last = arr->values[arr->len - 1]; arr->values[arr->len - 1] = JS_NULL; arr->len--; return last; } JSValue JS_Stone (JSContext *ctx, JSValue this_val) { return js_cell_stone (ctx, this_val, 1, &this_val); } /* GC-safe push: takes pointer to array JSValue, updates it if array grows. Returns 0 on success, -1 on error. */ int JS_ArrayPush (JSContext *ctx, JSValue *arr_ptr, JSValue val) { if (!JS_IsArray (*arr_ptr)) { JS_ThrowTypeError (ctx, "not an array"); return -1; } return js_intrinsic_array_push (ctx, arr_ptr, val); } JSValue JS_ArrayPop (JSContext *ctx, JSValue obj) { if (!JS_IsArray (obj)) return JS_ThrowTypeError (ctx, "not an array"); return js_cell_pop (ctx, JS_NULL, 1, &obj); } /* C API: array(arg0, arg1, arg2, arg3) - array(number) or array(number, fill_value_or_fn) - create array - array(array) - copy - array(array, fn, reverse, exit) - map - array(array, array2) - concat - array(array, from, to) - slice - array(object) - keys - array(text) or array(text, sep_or_len) - split */ JSValue JS_Array (JSContext *ctx, JSValue arg0, JSValue arg1, JSValue arg2, JSValue arg3) { JSValue argv[4] = { arg0, arg1, arg2, arg3 }; int argc = 4; if (JS_IsNull (arg3)) argc = 3; if (JS_IsNull (arg2)) argc = 2; if (JS_IsNull (arg1)) argc = 1; return js_cell_array (ctx, JS_NULL, argc, argv); } /* C API: filter(arr, fn) - returns new filtered array */ JSValue JS_ArrayFilter (JSContext *ctx, JSValue arr, JSValue fn) { JSValue argv[2] = { arr, fn }; return js_cell_array_filter (ctx, JS_NULL, 2, argv); } /* C API: sort(arr, selector) - returns new sorted array */ JSValue JS_ArraySort (JSContext *ctx, JSValue arr, JSValue selector) { JSValue argv[2] = { arr, selector }; return js_cell_array_sort (ctx, JS_NULL, 2, argv); } /* C API: find(arr, target_or_fn, reverse, from) - returns index or null */ JSValue JS_ArrayFind (JSContext *ctx, JSValue arr, JSValue target_or_fn, JSValue reverse, JSValue from) { JSValue argv[4] = { arr, target_or_fn, reverse, from }; int argc = 4; if (JS_IsNull (from)) argc = 3; if (JS_IsNull (reverse)) argc = 2; return js_cell_array_find (ctx, JS_NULL, argc, argv); } /* C API: arrfor(arr, fn, reverse, exit) - iterate array */ JSValue JS_ArrFor (JSContext *ctx, JSValue arr, JSValue fn, JSValue reverse, JSValue exit_val) { JSValue argv[4] = { arr, fn, reverse, exit_val }; int argc = 4; if (JS_IsNull (exit_val)) argc = 3; if (JS_IsNull (reverse)) argc = 2; return js_cell_array_for (ctx, JS_NULL, argc, argv); } /* C API: reduce(arr, fn, initial, reverse) - reduce array */ JSValue JS_ArrayReduce (JSContext *ctx, JSValue arr, JSValue fn, JSValue initial, JSValue reverse) { JSValue argv[4] = { arr, fn, initial, reverse }; int argc = 4; if (JS_IsNull (reverse)) argc = 3; if (JS_IsNull (initial)) argc = 2; return js_cell_array_reduce (ctx, JS_NULL, argc, argv); } /* ============================================================ C API Wrappers for Cell Intrinsic Functions ============================================================ */ /* C API: stone(val) - make value immutable */ JSValue JS_CellStone (JSContext *ctx, JSValue val) { return js_cell_stone (ctx, JS_NULL, 1, &val); } /* C API: length(val) - get length of array/text/blob */ JSValue JS_CellLength (JSContext *ctx, JSValue val) { return js_cell_length (ctx, JS_NULL, 1, &val); } /* C API: reverse(val) - reverse array or text */ JSValue JS_CellReverse (JSContext *ctx, JSValue val) { return js_cell_reverse (ctx, JS_NULL, 1, &val); } /* C API: proto(obj) - get prototype */ JSValue JS_CellProto (JSContext *ctx, JSValue obj) { return js_cell_proto (ctx, JS_NULL, 1, &obj); } /* C API: splat(val) - convert to array */ JSValue JS_CellSplat (JSContext *ctx, JSValue val) { return js_cell_splat (ctx, JS_NULL, 1, &val); } /* C API: meme(obj, deep) - clone object */ JSValue JS_CellMeme (JSContext *ctx, JSValue obj, JSValue deep) { JSValue argv[2] = { obj, deep }; int argc = JS_IsNull (deep) ? 1 : 2; return js_cell_meme (ctx, JS_NULL, argc, argv); } /* C API: apply(fn, args) - apply function to array of args */ JSValue JS_CellApply (JSContext *ctx, JSValue fn, JSValue args) { JSValue argv[2] = { fn, args }; return js_cell_fn_apply (ctx, JS_NULL, 2, argv); } /* C API: call(fn, this, args...) - call function */ JSValue JS_CellCall (JSContext *ctx, JSValue fn, JSValue this_val, JSValue args) { JSValue argv[3] = { fn, this_val, args }; int argc = JS_IsNull (args) ? 2 : 3; return js_cell_call (ctx, JS_NULL, argc, argv); } /* C API: modulo(a, b) - modulo operation */ JSValue JS_CellModulo (JSContext *ctx, JSValue a, JSValue b) { JSValue argv[2] = { a, b }; return js_cell_modulo (ctx, JS_NULL, 2, argv); } /* C API: neg(val) - negate number */ JSValue JS_CellNeg (JSContext *ctx, JSValue val) { return js_cell_neg (ctx, JS_NULL, 1, &val); } /* C API: not(val) - logical not */ JSValue JS_CellNot (JSContext *ctx, JSValue val) { return js_cell_not (ctx, JS_NULL, 1, &val); } /* Text functions */ /* C API: text(val) - convert to text */ JSValue JS_CellText (JSContext *ctx, JSValue val) { return js_cell_text (ctx, JS_NULL, 1, &val); } /* C API: lower(text) - convert to lowercase */ JSValue JS_CellLower (JSContext *ctx, JSValue text) { return js_cell_text_lower (ctx, JS_NULL, 1, &text); } /* C API: upper(text) - convert to uppercase */ JSValue JS_CellUpper (JSContext *ctx, JSValue text) { return js_cell_text_upper (ctx, JS_NULL, 1, &text); } /* C API: trim(text, chars) - trim whitespace or specified chars */ JSValue JS_CellTrim (JSContext *ctx, JSValue text, JSValue chars) { JSValue argv[2] = { text, chars }; int argc = JS_IsNull (chars) ? 1 : 2; return js_cell_text_trim (ctx, JS_NULL, argc, argv); } /* C API: codepoint(text, idx) - get codepoint at index */ JSValue JS_CellCodepoint (JSContext *ctx, JSValue text, JSValue idx) { JSValue argv[2] = { text, idx }; int argc = JS_IsNull (idx) ? 1 : 2; return js_cell_text_codepoint (ctx, JS_NULL, argc, argv); } /* C API: replace(text, pattern, replacement) - replace in text */ JSValue JS_CellReplace (JSContext *ctx, JSValue text, JSValue pattern, JSValue replacement) { JSValue argv[3] = { text, pattern, replacement }; return js_cell_text_replace (ctx, JS_NULL, 3, argv); } /* C API: search(text, pattern, from) - search in text */ JSValue JS_CellSearch (JSContext *ctx, JSValue text, JSValue pattern, JSValue from) { JSValue argv[3] = { text, pattern, from }; int argc = JS_IsNull (from) ? 2 : 3; return js_cell_text_search (ctx, JS_NULL, argc, argv); } /* C API: extract(text, from, to) - extract substring Internally, js_cell_text_extract expects (text, pattern, from, to) but for simple substring extraction we don't need a pattern */ JSValue JS_CellExtract (JSContext *ctx, JSValue text, JSValue from, JSValue to) { if (!JS_IsText (text)) return JS_NULL; JSGCRef text_ref; JS_PushGCRef (ctx, &text_ref); text_ref.val = text; int len = js_string_value_len (text_ref.val); int from_idx = 0; int to_idx = len; if (!JS_IsNull (from)) { if (JS_ToInt32 (ctx, &from_idx, from)) { JS_PopGCRef (ctx, &text_ref); return JS_EXCEPTION; } if (from_idx < 0) from_idx += len; if (from_idx < 0) from_idx = 0; if (from_idx > len) from_idx = len; } if (!JS_IsNull (to)) { if (JS_ToInt32 (ctx, &to_idx, to)) { JS_PopGCRef (ctx, &text_ref); return JS_EXCEPTION; } if (to_idx < 0) to_idx += len; if (to_idx < 0) to_idx = 0; if (to_idx > len) to_idx = len; } if (from_idx > to_idx) { JS_PopGCRef (ctx, &text_ref); return JS_NULL; } if (from_idx == to_idx) { JS_PopGCRef (ctx, &text_ref); return JS_NewString (ctx, ""); } /* Create result string */ int result_len = to_idx - from_idx; JSText *str = js_alloc_string (ctx, result_len); if (!str) { JS_PopGCRef (ctx, &text_ref); return JS_EXCEPTION; } for (int i = 0; i < result_len; i++) { string_put (str, i, js_string_value_get (text_ref.val, from_idx + i)); } str->length = result_len; JS_PopGCRef (ctx, &text_ref); return pretext_end (ctx, str); } /* C API: character(codepoint) - create single character text */ JSValue JS_CellCharacter (JSContext *ctx, JSValue codepoint) { return js_cell_character (ctx, JS_NULL, 1, &codepoint); } /* Number functions */ /* C API: number(val) - convert to number */ JSValue JS_CellNumber (JSContext *ctx, JSValue val) { return js_cell_number (ctx, JS_NULL, 1, &val); } /* C API: abs(num) - absolute value */ JSValue JS_CellAbs (JSContext *ctx, JSValue num) { return js_cell_number_abs (ctx, JS_NULL, 1, &num); } /* C API: sign(num) - sign of number (-1, 0, 1) */ JSValue JS_CellSign (JSContext *ctx, JSValue num) { return js_cell_number_sign (ctx, JS_NULL, 1, &num); } /* C API: floor(num) - floor */ JSValue JS_CellFloor (JSContext *ctx, JSValue num) { return js_cell_number_floor (ctx, JS_NULL, 1, &num); } /* C API: ceiling(num) - ceiling */ JSValue JS_CellCeiling (JSContext *ctx, JSValue num) { return js_cell_number_ceiling (ctx, JS_NULL, 1, &num); } /* C API: round(num) - round to nearest integer */ JSValue JS_CellRound (JSContext *ctx, JSValue num) { return js_cell_number_round (ctx, JS_NULL, 1, &num); } /* C API: trunc(num) - truncate towards zero */ JSValue JS_CellTrunc (JSContext *ctx, JSValue num) { return js_cell_number_trunc (ctx, JS_NULL, 1, &num); } /* C API: whole(num) - integer part */ JSValue JS_CellWhole (JSContext *ctx, JSValue num) { return js_cell_number_whole (ctx, JS_NULL, 1, &num); } /* C API: fraction(num) - fractional part */ JSValue JS_CellFraction (JSContext *ctx, JSValue num) { return js_cell_number_fraction (ctx, JS_NULL, 1, &num); } /* C API: min(a, b) - minimum of two numbers */ JSValue JS_CellMin (JSContext *ctx, JSValue a, JSValue b) { JSValue argv[2] = { a, b }; return js_cell_number_min (ctx, JS_NULL, 2, argv); } /* C API: max(a, b) - maximum of two numbers */ JSValue JS_CellMax (JSContext *ctx, JSValue a, JSValue b) { JSValue argv[2] = { a, b }; return js_cell_number_max (ctx, JS_NULL, 2, argv); } /* C API: remainder(a, b) - remainder after division */ JSValue JS_CellRemainder (JSContext *ctx, JSValue a, JSValue b) { JSValue argv[2] = { a, b }; return js_cell_number_remainder (ctx, JS_NULL, 2, argv); } /* Object functions */ /* C API: object(proto, props) - create object */ JSValue JS_CellObject (JSContext *ctx, JSValue proto, JSValue props) { JSValue argv[2] = { proto, props }; int argc = JS_IsNull (props) ? 1 : 2; if (JS_IsNull (proto)) argc = 0; return js_cell_object (ctx, JS_NULL, argc, argv); } /* C API: format(text, collection, transformer) - string interpolation */ JSValue JS_CellFormat (JSContext *ctx, JSValue text, JSValue collection, JSValue transformer) { JSValue argv[3] = { text, collection, transformer }; int argc = JS_IsNull (transformer) ? 2 : 3; return js_cell_text_format (ctx, JS_NULL, argc, argv); } /* ============================================================ Helper Functions for C API ============================================================ */ /* Create an array from a list of JSValues */ JSValue JS_NewArrayFrom (JSContext *ctx, int count, JSValue *values) { JSGCRef arr_ref; JS_PushGCRef (ctx, &arr_ref); arr_ref.val = JS_NewArray (ctx); if (JS_IsException (arr_ref.val)) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } for (int i = 0; i < count; i++) { if (JS_ArrayPush (ctx, &arr_ref.val, values[i]) < 0) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } } JSValue result = arr_ref.val; JS_PopGCRef (ctx, &arr_ref); return result; } /* Print a JSValue text to stdout */ void JS_PrintText (JSContext *ctx, JSValue val) { if (!JS_IsText (val)) { /* Try to convert to string first */ val = JS_ToString (ctx, val); if (JS_IsException (val) || !JS_IsText (val)) { printf ("[non-text value]"); return; } } const char *str = JS_ToCString (ctx, val); if (str) { printf ("%s", str); JS_FreeCString (ctx, str); } } /* Print a JSValue text to stdout with newline */ void JS_PrintTextLn (JSContext *ctx, JSValue val) { JS_PrintText (ctx, val); printf ("\n"); } /* Format and print - convenience function */ void JS_PrintFormatted (JSContext *ctx, const char *fmt, int count, JSValue *values) { JSValue fmt_str = JS_NewString (ctx, fmt); JSValue arr = JS_NewArrayFrom (ctx, count, values); JSValue result = JS_CellFormat (ctx, fmt_str, arr, JS_NULL); JS_PrintText (ctx, result); } static JSValue js_cell_push (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; if (!JS_IsArray (argv[0])) return JS_NULL; JSGCRef arr_ref; JS_PushGCRef (ctx, &arr_ref); arr_ref.val = argv[0]; for (int i = 1; i < argc; i++) { if (js_intrinsic_array_push (ctx, &arr_ref.val, argv[i]) < 0) { JS_PopGCRef (ctx, &arr_ref); return JS_EXCEPTION; } } argv[0] = arr_ref.val; JS_PopGCRef (ctx, &arr_ref); return JS_NULL; } static JSValue js_cell_proto (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { (void)this_val; if (argc < 1) return JS_NULL; JSValue obj = argv[0]; if (JS_IsArray (obj)) { return JS_ThrowTypeError (ctx, "arrays do not have prototypes"); } if (!JS_IsObject (obj)) return JS_NULL; JSValue proto = JS_GetPrototype (ctx, obj); if (JS_IsException (proto)) return JS_NULL; /* If prototype is Object.prototype, return null */ if (JS_IsObject (proto)) { JSValue obj_proto = ctx->class_proto[JS_CLASS_OBJECT]; if (JS_IsObject (obj_proto) && JS_VALUE_GET_OBJ (proto) == JS_VALUE_GET_OBJ (obj_proto)) { return JS_NULL; } } return proto; } static JSValue js_cell_meme (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { JSValue proto = JS_NULL; if (argc > 0 && !JS_IsNull (argv[0])) proto = argv[0]; JSValue result = JS_NewObjectProto (ctx, proto); if (JS_IsException (result)) return result; if (argc < 2) return result; /* Root result across allocating calls */ JSGCRef result_ref; JS_PushGCRef (ctx, &result_ref); result_ref.val = result; /* Helper function to apply a single mixin */ #define APPLY_MIXIN(mix_val) \ do { \ if (!JS_IsObject (mix_val) || JS_IsNull (mix_val) || JS_IsArray (mix_val)) \ break; \ JSGCRef _mix_ref; \ JS_PushGCRef (ctx, &_mix_ref); \ _mix_ref.val = mix_val; \ JSValue _keys = JS_GetOwnPropertyNames (ctx, _mix_ref.val); \ if (JS_IsException (_keys)) { \ JS_PopGCRef (ctx, &_mix_ref); \ JS_PopGCRef (ctx, &result_ref); \ return JS_EXCEPTION; \ } \ uint32_t _len; \ if (js_get_length32 (ctx, &_len, _keys)) { \ JS_PopGCRef (ctx, &_mix_ref); \ JS_PopGCRef (ctx, &result_ref); \ return JS_EXCEPTION; \ } \ for (uint32_t j = 0; j < _len; j++) { \ JSValue _key = JS_GetPropertyUint32 (ctx, _keys, j); \ JSValue val = JS_GetProperty (ctx, _mix_ref.val, _key); \ if (JS_IsException (val)) { \ JS_PopGCRef (ctx, &_mix_ref); \ JS_PopGCRef (ctx, &result_ref); \ return JS_EXCEPTION; \ } \ JS_SetProperty (ctx, result_ref.val, _key, val); \ } \ JS_PopGCRef (ctx, &_mix_ref); \ } while (0) /* Process all arguments starting from argv[1] as mixins */ for (int i = 1; i < argc; i++) { JSValue mixins = argv[i]; if (JS_IsArray (mixins)) { /* Array of mixins - root the array across calls */ JSGCRef mixins_ref; JS_PushGCRef (ctx, &mixins_ref); mixins_ref.val = mixins; int64_t len; if (js_get_length64 (ctx, &len, mixins_ref.val)) { JS_PopGCRef (ctx, &mixins_ref); JS_PopGCRef (ctx, &result_ref); return JS_EXCEPTION; } for (int64_t j = 0; j < len; j++) { JSValue mix = JS_GetPropertyInt64 (ctx, mixins_ref.val, j); if (JS_IsException (mix)) { JS_PopGCRef (ctx, &mixins_ref); JS_PopGCRef (ctx, &result_ref); return JS_EXCEPTION; } APPLY_MIXIN (mix); } JS_PopGCRef (ctx, &mixins_ref); } else if (JS_IsObject (mixins) && !JS_IsNull (mixins)) { /* Single mixin object */ APPLY_MIXIN (mixins); } } #undef APPLY_MIXIN result = result_ref.val; JS_PopGCRef (ctx, &result_ref); return result; } /* ============================================================================ * splat() function - flatten object with prototype chain * ============================================================================ */ static JSValue js_cell_splat (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; JSValue obj = argv[0]; if (!JS_IsObject (obj) || JS_IsNull (obj)) return JS_NULL; /* Root obj, result, current, keys across allocating calls */ JSGCRef obj_ref, res_ref, cur_ref, keys_ref; JS_PushGCRef (ctx, &obj_ref); obj_ref.val = obj; JS_PushGCRef (ctx, &res_ref); res_ref.val = JS_NewObject (ctx); JS_PushGCRef (ctx, &cur_ref); cur_ref.val = obj_ref.val; /* use rooted value, not stale local */ JS_PushGCRef (ctx, &keys_ref); keys_ref.val = JS_NULL; #define SPLAT_CLEANUP() do { JS_PopGCRef (ctx, &keys_ref); JS_PopGCRef (ctx, &cur_ref); JS_PopGCRef (ctx, &res_ref); JS_PopGCRef (ctx, &obj_ref); } while(0) if (JS_IsException (res_ref.val)) { SPLAT_CLEANUP (); return JS_EXCEPTION; } /* Walk prototype chain and collect text keys */ while (!JS_IsNull (cur_ref.val)) { keys_ref.val = JS_GetOwnPropertyNames (ctx, cur_ref.val); if (JS_IsException (keys_ref.val)) { SPLAT_CLEANUP (); return JS_EXCEPTION; } uint32_t len; if (js_get_length32 (ctx, &len, keys_ref.val)) { SPLAT_CLEANUP (); return JS_EXCEPTION; } for (uint32_t i = 0; i < len; i++) { JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); int has = JS_HasProperty (ctx, res_ref.val, key); if (has < 0) { SPLAT_CLEANUP (); return JS_EXCEPTION; } if (!has) { JSValue val = JS_GetProperty (ctx, cur_ref.val, key); if (JS_IsException (val)) { SPLAT_CLEANUP (); return JS_EXCEPTION; } int tag = JS_VALUE_GET_TAG (val); if (JS_IsObject (val) || JS_IsNumber (val) || tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM || tag == JS_TAG_BOOL) { JS_SetProperty (ctx, res_ref.val, key, val); } } } cur_ref.val = JS_GetPrototype (ctx, cur_ref.val); } /* Call to_data if present */ JSValue to_data = JS_GetPropertyStr (ctx, obj_ref.val, "to_data"); if (JS_IsFunction (to_data)) { JSValue args[1] = { res_ref.val }; JSValue extra = JS_Call (ctx, to_data, obj_ref.val, 1, args); if (!JS_IsException (extra) && JS_IsObject (extra)) { keys_ref.val = JS_GetOwnPropertyNames (ctx, extra); if (!JS_IsException (keys_ref.val)) { uint32_t len; if (!js_get_length32 (ctx, &len, keys_ref.val)) { for (uint32_t i = 0; i < len; i++) { JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); JSValue val = JS_GetProperty (ctx, extra, key); JS_SetProperty (ctx, res_ref.val, key, val); } } } } } JSValue result = res_ref.val; SPLAT_CLEANUP (); return result; } #undef SPLAT_CLEANUP /* ============================================================================ * length() function * ============================================================================ */ static JSValue js_cell_length (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; JSValue val = argv[0]; /* null returns null */ if (JS_IsNull (val)) return JS_NULL; /* Functions return arity (accessed directly, not via properties) */ if (JS_IsFunction (val)) { JSFunction *f = JS_VALUE_GET_FUNCTION (val); return JS_NewInt32 (ctx, f->length); } int tag = JS_VALUE_GET_TAG (val); /* Strings return codepoint count */ if (tag == JS_TAG_STRING_IMM) { return JS_NewInt32 (ctx, MIST_GetImmediateASCIILen (val)); } if (tag == JS_TAG_STRING) { JSText *p = JS_VALUE_GET_STRING (val); return JS_NewInt32 (ctx, (int)JSText_len (p)); } /* Check for blob */ blob *bd = js_get_blob (ctx, val); if (bd) return JS_NewInt64 (ctx, bd->length); /* Arrays return element count */ if (JS_IsArray (val)) { JSArray *arr = JS_VALUE_GET_ARRAY (val); return JS_NewInt32 (ctx, arr->len); } /* Objects with length property */ if (JS_IsObject (val)) { JSValue len = JS_GetPropertyStr (ctx, val, "length"); if (!JS_IsException (len) && !JS_IsNull (len)) { if (JS_IsFunction (len)) { JSValue result = JS_Call (ctx, len, val, 0, NULL); return result; } if (JS_VALUE_IS_NUMBER (len)) return len; } else if (JS_IsException (len)) { return len; } } return JS_NULL; } /* ============================================================================ * call() function - call a function with explicit this and arguments * ============================================================================ */ /* call(func, this_val, args_array) */ static JSValue js_cell_call (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_ThrowTypeError (ctx, "call requires a function argument"); JSGCRef func_ref, this_ref, args_ref; JS_PushGCRef (ctx, &func_ref); JS_PushGCRef (ctx, &this_ref); JS_PushGCRef (ctx, &args_ref); func_ref.val = argv[0]; this_ref.val = argc >= 2 ? argv[1] : JS_NULL; args_ref.val = argc >= 3 ? argv[2] : JS_NULL; if (!JS_IsFunction (func_ref.val)) { JS_PopGCRef (ctx, &args_ref); JS_PopGCRef (ctx, &this_ref); JS_PopGCRef (ctx, &func_ref); return JS_ThrowTypeError (ctx, "first argument must be a function"); } if (argc < 3 || JS_IsNull (args_ref.val)) { JSValue ret = JS_CallInternal (ctx, func_ref.val, this_ref.val, 0, NULL, 0); JS_PopGCRef (ctx, &args_ref); JS_PopGCRef (ctx, &this_ref); JS_PopGCRef (ctx, &func_ref); return ret; } if (!JS_IsArray (args_ref.val)) { JS_PopGCRef (ctx, &args_ref); JS_PopGCRef (ctx, &this_ref); JS_PopGCRef (ctx, &func_ref); return JS_ThrowTypeError (ctx, "third argument must be an array"); } uint32_t len; JSValue *tab = build_arg_list (ctx, &len, &args_ref.val); if (!tab) { JS_PopGCRef (ctx, &args_ref); JS_PopGCRef (ctx, &this_ref); JS_PopGCRef (ctx, &func_ref); return JS_EXCEPTION; } JSValue ret = JS_CallInternal (ctx, func_ref.val, this_ref.val, len, tab, 0); free_arg_list (ctx, tab, len); JS_PopGCRef (ctx, &args_ref); JS_PopGCRef (ctx, &this_ref); JS_PopGCRef (ctx, &func_ref); return ret; } /* ============================================================================ * is_* type checking functions * ============================================================================ */ /* is_array(val) */ static JSValue js_cell_is_array (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_FALSE; return JS_NewBool (ctx, JS_IsArray (argv[0])); } /* is_blob(val) */ static JSValue js_cell_is_blob (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_FALSE; return JS_NewBool (ctx, js_get_blob (ctx, argv[0]) != NULL); } /* is_data(val) - check if object is a plain object (data record) */ static JSValue js_cell_is_data (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_FALSE; JSValue val = argv[0]; if (!JS_IsObject (val)) return JS_FALSE; if (JS_IsArray (val)) return JS_FALSE; if (JS_IsFunction (val)) return JS_FALSE; if (js_get_blob (ctx, val)) return JS_FALSE; /* Check if it's a plain object (prototype is Object.prototype or null) */ return JS_TRUE; } /* is_function(val) */ static JSValue js_cell_is_function (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_FALSE; return JS_NewBool (ctx, JS_IsFunction (argv[0])); } /* is_logical(val) - check if value is a boolean (true or false) */ static JSValue js_cell_is_logical (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_FALSE; return JS_NewBool (ctx, JS_VALUE_GET_TAG (argv[0]) == JS_TAG_BOOL); } /* is_integer(val) */ static JSValue js_cell_is_integer (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_FALSE; JSValue val = argv[0]; int tag = JS_VALUE_GET_TAG (val); if (tag == JS_TAG_INT) return JS_TRUE; if (tag == JS_TAG_FLOAT64) { double d = JS_VALUE_GET_FLOAT64 (val); return JS_NewBool (ctx, isfinite (d) && trunc (d) == d); } return JS_FALSE; } /* is_null(val) */ static JSValue js_cell_is_null (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_FALSE; return JS_NewBool (ctx, JS_IsNull (argv[0])); } /* is_number(val) */ static JSValue js_cell_is_number (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_FALSE; return JS_NewBool (ctx, JS_IsNumber (argv[0])); } /* is_object(val) - true for non-array, non-null objects */ static JSValue js_cell_is_object (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_FALSE; JSValue val = argv[0]; if (!JS_IsObject (val)) return JS_FALSE; if (JS_IsArray (val)) return JS_FALSE; return JS_TRUE; } /* is_stone(val) - check if value is immutable */ static JSValue js_cell_is_stone (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { (void)this_val; if (argc < 1) return JS_FALSE; return JS_NewBool (ctx, JS_IsStone (argv[0])); } /* is_text(val) */ static JSValue js_cell_is_text (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_FALSE; int tag = JS_VALUE_GET_TAG (argv[0]); return JS_NewBool (ctx, tag == JS_TAG_STRING || tag == JS_TAG_STRING_IMM); } /* is_proto(val, master) - check if val has master in prototype chain */ static JSValue js_cell_is_proto (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_FALSE; JSValue val = argv[0]; JSValue master = argv[1]; if (!JS_IsObject (val) || JS_IsNull (master)) return JS_FALSE; /* Walk prototype chain */ JSValue proto = JS_GetPrototype (ctx, val); while (!JS_IsNull (proto) && !JS_IsException (proto)) { /* If master is a function with prototype property, check that */ if (JS_IsFunction (master)) { JSValue master_proto = JS_GetPropertyStr (ctx, master, "prototype"); if (!JS_IsException (master_proto) && !JS_IsNull (master_proto)) { JSRecord *p1 = JS_VALUE_GET_OBJ (proto); JSRecord *p2 = JS_VALUE_GET_OBJ (master_proto); if (p1 == p2) { return JS_TRUE; } } else if (!JS_IsException (master_proto)) { } } /* Also check if proto == master directly */ if (JS_IsObject (master)) { JSRecord *p1 = JS_VALUE_GET_OBJ (proto); JSRecord *p2 = JS_VALUE_GET_OBJ (master); if (p1 == p2) { return JS_TRUE; } } JSValue next = JS_GetPrototype (ctx, proto); proto = next; } if (JS_IsException (proto)) return proto; return JS_FALSE; } static const char *const native_error_name[JS_NATIVE_ERROR_COUNT] = { "EvalError", "RangeError", "ReferenceError", "SyntaxError", "TypeError", "URIError", "InternalError", "AggregateError", }; /* Minimum amount of objects to be able to compile code and display error messages. No JSAtom should be allocated by this function. */ static void JS_AddIntrinsicBasicObjects (JSContext *ctx) { JSGCRef proto_ref; int i; JS_PushGCRef (ctx, &proto_ref); ctx->class_proto[JS_CLASS_OBJECT] = JS_NewObjectProto (ctx, JS_NULL); ctx->class_proto[JS_CLASS_ERROR] = JS_NewObject (ctx); for (i = 0; i < JS_NATIVE_ERROR_COUNT; i++) { proto_ref.val = JS_NewObjectProto (ctx, ctx->class_proto[JS_CLASS_ERROR]); JS_SetPropertyInternal (ctx, proto_ref.val, JS_KEY_name, JS_NewAtomString (ctx, native_error_name[i])); JS_SetPropertyInternal (ctx, proto_ref.val, JS_KEY_message, JS_KEY_empty); ctx->native_error_proto[i] = proto_ref.val; } JS_PopGCRef (ctx, &proto_ref); } /* logical(val) — false for 0/false/"false"/null, true for 1/true/"true", null otherwise */ static JSValue js_cell_logical(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_NULL; JSValue v = argv[0]; if (JS_IsNull(v) || (JS_IsInt(v) && JS_VALUE_GET_INT(v) == 0) || (JS_IsBool(v) && !JS_VALUE_GET_BOOL(v))) return JS_FALSE; if ((JS_IsInt(v) && JS_VALUE_GET_INT(v) == 1) || (JS_IsBool(v) && JS_VALUE_GET_BOOL(v))) return JS_TRUE; if (JS_IsText(v)) { char buf[8]; JS_KeyGetStr(ctx, buf, sizeof(buf), v); if (strcmp(buf, "false") == 0) return JS_FALSE; if (strcmp(buf, "true") == 0) return JS_TRUE; } return JS_NULL; } /* starts_with(str, prefix) — search(str, prefix) == 0 */ static JSValue js_cell_starts_with(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; JSValue args[3] = { argv[0], argv[1], JS_NULL }; JSValue pos = js_cell_text_search(ctx, JS_NULL, 2, args); if (JS_IsInt(pos) && JS_VALUE_GET_INT(pos) == 0) return JS_TRUE; return JS_FALSE; } /* ends_with(str, suffix) — search(str, suffix, -length(suffix)) != null */ static JSValue js_cell_ends_with(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; JSValue len_val = js_cell_length(ctx, JS_NULL, 1, &argv[1]); int slen = JS_IsInt(len_val) ? JS_VALUE_GET_INT(len_val) : 0; JSValue offset = JS_NewInt32(ctx, -slen); JSValue args[3] = { argv[0], argv[1], offset }; JSValue pos = js_cell_text_search(ctx, JS_NULL, 3, args); if (!JS_IsNull(pos)) return JS_TRUE; return JS_FALSE; } /* every(arr, pred) — find(arr, x => !pred(x)) == null */ static JSValue js_cell_every(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; if (!JS_IsArray(argv[0]) || !JS_IsFunction(argv[1])) return JS_NULL; JSGCRef arr_ref, fn_ref; JS_PushGCRef(ctx, &arr_ref); JS_PushGCRef(ctx, &fn_ref); arr_ref.val = argv[0]; fn_ref.val = argv[1]; JSArray *arr = JS_VALUE_GET_ARRAY(arr_ref.val); for (int i = 0; i < arr->len; i++) { JSValue elem = arr->values[i]; JSValue r = JS_CallInternal(ctx, fn_ref.val, JS_NULL, 1, &elem, 0); arr = JS_VALUE_GET_ARRAY(arr_ref.val); if (JS_IsException(r)) { JS_PopGCRef(ctx, &fn_ref); JS_PopGCRef(ctx, &arr_ref); return r; } if (!JS_ToBool(ctx, r)) { JS_PopGCRef(ctx, &fn_ref); JS_PopGCRef(ctx, &arr_ref); return JS_FALSE; } } JS_PopGCRef(ctx, &fn_ref); JS_PopGCRef(ctx, &arr_ref); return JS_TRUE; } /* some(arr, pred) — find(arr, pred) != null */ static JSValue js_cell_some(JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 2) return JS_NULL; JSValue r = js_cell_array_find(ctx, JS_NULL, argc, argv); if (JS_IsException(r)) return r; if (!JS_IsNull(r)) return JS_TRUE; return JS_FALSE; } /* GC-SAFE: Helper to set a global function. Creates function first, then reads ctx->global_obj to ensure it's not stale if GC ran during function creation. */ static void js_set_global_cfunc(JSContext *ctx, const char *name, JSCFunction *func, int length) { JSGCRef ref; JS_PushGCRef(ctx, &ref); ref.val = JS_NewCFunction(ctx, func, name, length); JS_SetPropertyStr(ctx, ctx->global_obj, name, ref.val); JS_PopGCRef(ctx, &ref); } static void JS_AddIntrinsicBaseObjects (JSContext *ctx) { JSValue obj1; ctx->throw_type_error = JS_NewCFunction (ctx, js_throw_type_error, NULL, 0); ctx->global_obj = JS_NewObject (ctx); /* Error */ obj1 = JS_NewCFunctionMagic (ctx, js_error_constructor, "Error", 1, JS_CFUNC_generic_magic, -1); JS_SetPropertyStr (ctx, ctx->global_obj, "Error", obj1); #define REGISTER_ERROR(idx, name) do { \ JSValue func_obj = JS_NewCFunctionMagic(ctx, js_error_constructor, name, 1 + ((idx) == JS_AGGREGATE_ERROR), JS_CFUNC_generic_magic, (idx)); \ JS_SetPropertyStr(ctx, ctx->global_obj, name, func_obj); \ } while(0) REGISTER_ERROR(0, "EvalError"); REGISTER_ERROR(1, "RangeError"); REGISTER_ERROR(2, "ReferenceError"); REGISTER_ERROR(3, "SyntaxError"); REGISTER_ERROR(4, "TypeError"); REGISTER_ERROR(5, "URIError"); REGISTER_ERROR(6, "InternalError"); REGISTER_ERROR(7, "AggregateError"); #undef REGISTER_ERROR /* Cell Script global functions: text, number, array, object, fn */ { JSValue text_func = JS_NewCFunction (ctx, js_cell_text, "text", 3); JS_SetPropertyStr (ctx, ctx->global_obj, "text", text_func); JSValue number_func = JS_NewCFunction (ctx, js_cell_number, "number", 2); JS_SetPropertyStr (ctx, ctx->global_obj, "number", number_func); JSValue array_func = JS_NewCFunction (ctx, js_cell_array, "array", 4); JS_SetPropertyStr (ctx, ctx->global_obj, "array", array_func); JSValue object_func = JS_NewCFunction (ctx, js_cell_object, "object", 2); JS_SetPropertyStr (ctx, ctx->global_obj, "object", object_func); /* Blob intrinsic type */ { JSClassDef blob_class = { .class_name = "blob", .finalizer = js_blob_finalizer, }; JS_NewClass (ctx, JS_CLASS_BLOB, &blob_class); ctx->class_proto[JS_CLASS_BLOB] = JS_NewObject (ctx); JS_SetPropertyFunctionList (ctx, ctx->class_proto[JS_CLASS_BLOB], js_blob_proto_funcs, countof (js_blob_proto_funcs)); JSValue blob_ctor = JS_NewCFunction2 (ctx, js_blob_constructor, "blob", 3, JS_CFUNC_generic, 0); JS_SetPropertyStr (ctx, ctx->global_obj, "blob", blob_ctor); } /* Core functions - using GC-safe helper */ js_set_global_cfunc(ctx, "eval", js_cell_eval, 2); js_set_global_cfunc(ctx, "mach_eval", js_mach_eval, 2); js_set_global_cfunc(ctx, "stone", js_cell_stone, 1); js_set_global_cfunc(ctx, "length", js_cell_length, 1); js_set_global_cfunc(ctx, "call", js_cell_call, 3); /* is_* type checking functions */ js_set_global_cfunc(ctx, "is_array", js_cell_is_array, 1); js_set_global_cfunc(ctx, "is_blob", js_cell_is_blob, 1); js_set_global_cfunc(ctx, "is_data", js_cell_is_data, 1); js_set_global_cfunc(ctx, "is_function", js_cell_is_function, 1); js_set_global_cfunc(ctx, "is_logical", js_cell_is_logical, 1); js_set_global_cfunc(ctx, "is_integer", js_cell_is_integer, 1); js_set_global_cfunc(ctx, "is_null", js_cell_is_null, 1); js_set_global_cfunc(ctx, "is_number", js_cell_is_number, 1); js_set_global_cfunc(ctx, "is_object", js_cell_is_object, 1); js_set_global_cfunc(ctx, "is_stone", js_cell_is_stone, 1); js_set_global_cfunc(ctx, "is_text", js_cell_is_text, 1); js_set_global_cfunc(ctx, "is_proto", js_cell_is_proto, 2); /* Utility functions */ js_set_global_cfunc(ctx, "apply", js_cell_fn_apply, 2); js_set_global_cfunc(ctx, "replace", js_cell_text_replace, 4); js_set_global_cfunc(ctx, "lower", js_cell_text_lower, 1); js_set_global_cfunc(ctx, "upper", js_cell_text_upper, 1); js_set_global_cfunc(ctx, "trim", js_cell_text_trim, 2); js_set_global_cfunc(ctx, "codepoint", js_cell_text_codepoint, 1); js_set_global_cfunc(ctx, "search", js_cell_text_search, 3); js_set_global_cfunc(ctx, "extract", js_cell_text_extract, 4); js_set_global_cfunc(ctx, "format", js_cell_text_format, 3); js_set_global_cfunc(ctx, "reduce", js_cell_array_reduce, 4); js_set_global_cfunc(ctx, "arrfor", js_cell_array_for, 4); js_set_global_cfunc(ctx, "find", js_cell_array_find, 4); js_set_global_cfunc(ctx, "filter", js_cell_array_filter, 2); js_set_global_cfunc(ctx, "sort", js_cell_array_sort, 2); /* Number utility functions */ js_set_global_cfunc(ctx, "whole", js_cell_number_whole, 1); js_set_global_cfunc(ctx, "fraction", js_cell_number_fraction, 1); js_set_global_cfunc(ctx, "floor", js_cell_number_floor, 2); js_set_global_cfunc(ctx, "ceiling", js_cell_number_ceiling, 2); js_set_global_cfunc(ctx, "abs", js_cell_number_abs, 1); js_set_global_cfunc(ctx, "round", js_cell_number_round, 2); js_set_global_cfunc(ctx, "sign", js_cell_number_sign, 1); js_set_global_cfunc(ctx, "trunc", js_cell_number_trunc, 2); js_set_global_cfunc(ctx, "min", js_cell_number_min, 2); js_set_global_cfunc(ctx, "max", js_cell_number_max, 2); js_set_global_cfunc(ctx, "remainder", js_cell_number_remainder, 2); js_set_global_cfunc(ctx, "character", js_cell_character, 2); js_set_global_cfunc(ctx, "modulo", js_cell_modulo, 2); js_set_global_cfunc(ctx, "neg", js_cell_neg, 1); js_set_global_cfunc(ctx, "not", js_cell_not, 1); js_set_global_cfunc(ctx, "reverse", js_cell_reverse, 1); js_set_global_cfunc(ctx, "proto", js_cell_proto, 1); js_set_global_cfunc(ctx, "splat", js_cell_splat, 1); /* pi - mathematical constant (no GC concern for immediate float) */ JS_SetPropertyStr(ctx, ctx->global_obj, "pi", JS_NewFloat64(ctx, 3.14159265358979323846264338327950288419716939937510)); js_set_global_cfunc(ctx, "push", js_cell_push, 2); js_set_global_cfunc(ctx, "pop", js_cell_pop, 1); js_set_global_cfunc(ctx, "meme", js_cell_meme, 2); /* Engine builtins (normally from engine.cm, needed for --mach-run) */ js_set_global_cfunc(ctx, "logical", js_cell_logical, 1); js_set_global_cfunc(ctx, "starts_with", js_cell_starts_with, 2); js_set_global_cfunc(ctx, "ends_with", js_cell_ends_with, 2); js_set_global_cfunc(ctx, "every", js_cell_every, 2); js_set_global_cfunc(ctx, "some", js_cell_some, 2); /* fn record with apply property */ { JSGCRef fn_ref; JS_PushGCRef(ctx, &fn_ref); fn_ref.val = JS_NewObject(ctx); JSValue apply_fn = JS_NewCFunction(ctx, js_cell_fn_apply, "apply", 2); JS_SetPropertyStr(ctx, fn_ref.val, "apply", apply_fn); JS_SetPropertyStr(ctx, ctx->global_obj, "fn", fn_ref.val); JS_PopGCRef(ctx, &fn_ref); } /* I/O functions */ js_set_global_cfunc(ctx, "print", js_print, -1); /* variadic: length < 0 means no arg limit */ js_set_global_cfunc(ctx, "stacktrace", js_stacktrace, 0); } } #define STRLEN(s) (sizeof (s) / sizeof (s[0])) #define CSTR "" static inline void key_to_buf (JSContext *ctx, JSValue key, char *dst, int cap, const char *fallback) { if (JS_IsNull (key)) { strncpy (dst, fallback, cap); dst[cap - 1] = 0; return; } JS_KeyGetStr (ctx, dst, cap, key); if (dst[0] == 0) { strncpy (dst, fallback, cap); dst[cap - 1] = 0; } } void js_debug_info (JSContext *js, JSValue fn, js_debug *dbg) { *dbg = (js_debug){ 0 }; if (!JS_IsFunction (fn)) return; JSFunction *f = JS_VALUE_GET_FUNCTION (fn); dbg->unique = (int)(uintptr_t)f; JSValue name_key = JS_NULL; if (!JS_IsNull (f->name)) name_key = f->name; else if (f->kind == JS_FUNC_KIND_BYTECODE && f->u.func.function_bytecode) name_key = f->u.func.function_bytecode->func_name; key_to_buf (js, name_key, dbg->name, sizeof (dbg->name), ""); if (f->kind == JS_FUNC_KIND_BYTECODE) { JSFunctionBytecode *b = f->u.func.function_bytecode; key_to_buf (js, b->debug.filename, dbg->filename, sizeof (dbg->filename), "unknown"); dbg->what = "JS"; dbg->closure_n = b->closure_var_count; dbg->param_n = b->arg_count; dbg->vararg = 1; dbg->source = (const uint8_t *)b->debug.source; dbg->srclen = b->debug.source_len; dbg->line = 0; /* see below */ return; } if (f->kind == JS_FUNC_KIND_C || f->kind == JS_FUNC_KIND_C_DATA) { strncpy (dbg->filename, "", sizeof (dbg->filename)); dbg->filename[sizeof (dbg->filename) - 1] = 0; dbg->what = "C"; dbg->param_n = f->length; dbg->vararg = 1; dbg->line = 0; dbg->source = (const uint8_t *)CSTR; dbg->srclen = STRLEN (CSTR); } } void js_debug_sethook (JSContext *ctx, js_hook hook, int type, void *user) { ctx->trace_hook = hook; ctx->trace_type = type; ctx->trace_data = user; } uint32_t js_debugger_stack_depth (JSContext *ctx) { uint32_t stack_index = 0; JSStackFrame *sf = ctx->current_stack_frame; while (sf != NULL) { sf = sf->prev_frame; stack_index++; } return stack_index; } JSValue js_debugger_backtrace_fns (JSContext *ctx, const uint8_t *cur_pc) { JSValue ret = JS_NewArray (ctx); JSStackFrame *sf; uint32_t stack_index = 0; for (sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) { uint32_t id = stack_index++; JS_SetPropertyUint32 (ctx, ret, id, sf->cur_func); } return ret; } JSValue js_debugger_build_backtrace (JSContext *ctx, const uint8_t *cur_pc) { JSStackFrame *sf; const char *func_name_str; JSFunction *f; JSValue ret = JS_NewArray (ctx); uint32_t stack_index = 0; for (sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) { JSValue current_frame = JS_NewObject (ctx); uint32_t id = stack_index++; JS_SetPropertyStr (ctx, current_frame, "id", JS_NewUint32 (ctx, id)); func_name_str = get_func_name (ctx, sf->cur_func); if (!func_name_str || func_name_str[0] == '\0') JS_SetPropertyStr (ctx, current_frame, "name", JS_NewString (ctx, "")); else JS_SetPropertyStr (ctx, current_frame, "name", JS_NewString (ctx, func_name_str)); JS_FreeCString (ctx, func_name_str); if (JS_VALUE_GET_TAG (sf->cur_func) == JS_TAG_FUNCTION) { f = JS_VALUE_GET_FUNCTION (sf->cur_func); if (f->kind == JS_FUNC_KIND_BYTECODE) { JSFunctionBytecode *b; int line_num1; b = f->u.func.function_bytecode; if (b->has_debug) { const uint8_t *pc = sf != ctx->current_stack_frame || !cur_pc ? sf->cur_pc : cur_pc; int col_num; line_num1 = find_line_num (ctx, b, pc - b->byte_code_buf - 1, &col_num); JS_SetPropertyStr (ctx, current_frame, "filename", b->debug.filename); if (line_num1 != -1) JS_SetPropertyStr (ctx, current_frame, "line", JS_NewUint32 (ctx, line_num1)); } } else { JS_SetPropertyStr (ctx, current_frame, "name", JS_NewString (ctx, "(native)")); } } else { JS_SetPropertyStr (ctx, current_frame, "name", JS_NewString (ctx, "(native)")); } JS_SetPropertyUint32 (ctx, ret, id, current_frame); } return ret; } JSValue js_debugger_fn_info (JSContext *ctx, JSValue fn) { JSValue ret = JS_NewObject (ctx); if (!js_is_bytecode_function (fn)) goto done; JSFunction *f = JS_VALUE_GET_FUNCTION (fn); JSFunctionBytecode *b = f->u.func.function_bytecode; char atom_buf[KEY_GET_STR_BUF_SIZE]; const char *str; int i; // Function name if (!JS_IsNull (b->func_name)) { str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), b->func_name); JS_SetPropertyStr (ctx, ret, "name", JS_NewString (ctx, str)); } // File location info if (b->has_debug && !JS_IsNull (b->debug.filename)) { int line_num, col_num; str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), b->debug.filename); line_num = find_line_num (ctx, b, -1, &col_num); JS_SetPropertyStr (ctx, ret, "filename", JS_NewString (ctx, str)); JS_SetPropertyStr (ctx, ret, "line", JS_NewInt32 (ctx, line_num)); JS_SetPropertyStr (ctx, ret, "column", JS_NewInt32 (ctx, col_num)); } // Arguments if (b->arg_count && b->vardefs) { JSValue args_array = JS_NewArray (ctx); for (i = 0; i < b->arg_count; i++) { str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), b->vardefs[i].var_name); JS_SetPropertyUint32 (ctx, args_array, i, JS_NewString (ctx, str)); } JS_SetPropertyStr (ctx, ret, "args", args_array); } // Local variables if (b->var_count && b->vardefs) { JSValue locals_array = JS_NewArray (ctx); for (i = 0; i < b->var_count; i++) { JSVarDef *vd = &b->vardefs[b->arg_count + i]; JSValue local_obj = JS_NewObject (ctx); str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), vd->var_name); JS_SetPropertyStr (ctx, local_obj, "name", JS_NewString (ctx, str)); const char *var_type = vd->var_kind == JS_VAR_CATCH ? "catch" : (vd->var_kind == JS_VAR_FUNCTION_DECL || vd->var_kind == JS_VAR_NEW_FUNCTION_DECL) ? "function" : vd->is_const ? "const" : vd->is_lexical ? "let" : "var"; JS_SetPropertyStr (ctx, local_obj, "type", JS_NewString (ctx, var_type)); JS_SetPropertyStr (ctx, local_obj, "index", JS_NewInt32 (ctx, i)); if (vd->scope_level) { JS_SetPropertyStr (ctx, local_obj, "scope_level", JS_NewInt32 (ctx, vd->scope_level)); JS_SetPropertyStr (ctx, local_obj, "scope_next", JS_NewInt32 (ctx, vd->scope_next)); } JS_SetPropertyUint32 (ctx, locals_array, i, local_obj); } JS_SetPropertyStr (ctx, ret, "locals", locals_array); } // Closure variables if (b->closure_var_count) { JSValue closure_array = JS_NewArray (ctx); for (i = 0; i < b->closure_var_count; i++) { JSClosureVar *cv = &b->closure_var[i]; JSValue closure_obj = JS_NewObject (ctx); str = JS_KeyGetStr (ctx, atom_buf, sizeof (atom_buf), cv->var_name); JS_SetPropertyStr (ctx, closure_obj, "name", JS_NewString (ctx, str)); JS_SetPropertyStr (ctx, closure_obj, "is_local", JS_NewBool (ctx, cv->is_local)); JS_SetPropertyStr (ctx, closure_obj, "is_arg", JS_NewBool (ctx, cv->is_arg)); JS_SetPropertyStr (ctx, closure_obj, "var_idx", JS_NewInt32 (ctx, cv->var_idx)); const char *var_type = cv->is_const ? "const" : cv->is_lexical ? "let" : "var"; JS_SetPropertyStr (ctx, closure_obj, "type", JS_NewString (ctx, var_type)); JS_SetPropertyUint32 (ctx, closure_array, i, closure_obj); } JS_SetPropertyStr (ctx, ret, "closure_vars", closure_array); } // Stack size JS_SetPropertyStr (ctx, ret, "stack_size", JS_NewInt32 (ctx, b->stack_size)); done: return ret; } // Opcode names array for debugger static const char *opcode_names[] = { #define FMT(f) #define DEF(id, size, n_pop, n_push, f) #id, #define def(id, size, n_pop, n_push, f) #include "quickjs-opcode.h" #undef def #undef DEF #undef FMT }; JSValue js_debugger_fn_bytecode (JSContext *ctx, JSValue fn) { if (!js_is_bytecode_function (fn)) return JS_NULL; JSFunction *f = JS_VALUE_GET_FUNCTION (fn); JSFunctionBytecode *b = f->u.func.function_bytecode; JSValue ret = JS_NewArray (ctx); const uint8_t *tab = b->byte_code_buf; int len = b->byte_code_len; int pos = 0; int idx = 0; BOOL use_short_opcodes = TRUE; char opcode_str[256]; while (pos < len) { int op = tab[pos]; const JSOpCode *oi; if (use_short_opcodes) oi = &short_opcode_info (op); else oi = &opcode_info[op]; int size = oi->size; if (pos + size > len) { break; } if (op >= sizeof (opcode_names) / sizeof (opcode_names[0])) { snprintf (opcode_str, sizeof (opcode_str), "unknown"); } else { const char *opcode_name = opcode_names[op]; snprintf (opcode_str, sizeof (opcode_str), "%s", opcode_name); // Add arguments based on opcode format int arg_pos = pos + 1; switch (oi->fmt) { case OP_FMT_none_int: snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %d", op - OP_push_0); break; case OP_FMT_npopx: snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %d", op - OP_call0); break; case OP_FMT_u8: snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u", get_u8 (tab + arg_pos)); break; case OP_FMT_i8: snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %d", get_i8 (tab + arg_pos)); break; case OP_FMT_u16: case OP_FMT_npop: snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u", get_u16 (tab + arg_pos)); break; case OP_FMT_npop_u16: snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u %u", get_u16 (tab + arg_pos), get_u16 (tab + arg_pos + 2)); break; case OP_FMT_i16: snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %d", get_i16 (tab + arg_pos)); break; case OP_FMT_i32: snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %d", get_i32 (tab + arg_pos)); break; case OP_FMT_u32: snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u", get_u32 (tab + arg_pos)); break; #if SHORT_OPCODES case OP_FMT_label8: snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u", get_i8 (tab + arg_pos) + arg_pos); break; case OP_FMT_label16: snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u", get_i16 (tab + arg_pos) + arg_pos); break; case OP_FMT_const8: snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u", get_u8 (tab + arg_pos)); break; #endif case OP_FMT_const: snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u", get_u32 (tab + arg_pos)); break; case OP_FMT_label: snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u", get_u32 (tab + arg_pos) + arg_pos); break; case OP_FMT_label_u16: snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u %u", get_u32 (tab + arg_pos) + arg_pos, get_u16 (tab + arg_pos + 4)); break; case OP_FMT_key: { /* Key operand is a cpool index */ uint32_t key_idx = get_u32 (tab + arg_pos); if (key_idx < b->cpool_count) { JSValue key = b->cpool[key_idx]; const char *key_str = JS_ToCString (ctx, key); if (key_str) { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " key:%s", key_str); JS_FreeCString (ctx, key_str); } else { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " key[%u]", key_idx); } } else { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " key[%u]", key_idx); } } break; case OP_FMT_key_u8: { uint32_t cpool_idx = get_u32 (tab + arg_pos); const char *key_str = NULL; if (cpool_idx < b->cpool_count) key_str = JS_ToCString (ctx, b->cpool[cpool_idx]); if (key_str) { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %s %d", key_str, get_u8 (tab + arg_pos + 4)); JS_FreeCString (ctx, key_str); } else { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " cpool[%u] %d", cpool_idx, get_u8 (tab + arg_pos + 4)); } } break; case OP_FMT_key_u16: { uint32_t cpool_idx = get_u32 (tab + arg_pos); const char *key_str = NULL; if (cpool_idx < b->cpool_count) key_str = JS_ToCString (ctx, b->cpool[cpool_idx]); if (key_str) { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %s %d", key_str, get_u16 (tab + arg_pos + 4)); JS_FreeCString (ctx, key_str); } else { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " cpool[%u] %d", cpool_idx, get_u16 (tab + arg_pos + 4)); } } break; case OP_FMT_key_label_u16: { uint32_t cpool_idx = get_u32 (tab + arg_pos); int addr = get_u32 (tab + arg_pos + 4); int extra = get_u16 (tab + arg_pos + 8); const char *key_str = NULL; if (cpool_idx < b->cpool_count) key_str = JS_ToCString (ctx, b->cpool[cpool_idx]); if (key_str) { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %s %u %u", key_str, addr + arg_pos + 4, extra); JS_FreeCString (ctx, key_str); } else { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " cpool[%u] %u %u", cpool_idx, addr + arg_pos + 4, extra); } } break; case OP_FMT_none_loc: { int idx = (op - OP_get_loc0) % 4; if (idx < b->var_count) { const char *var_name = JS_ToCString (ctx, b->vardefs[idx].var_name); if (var_name) { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %d: %s", idx, var_name); JS_FreeCString (ctx, var_name); } else { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %d", idx); } } else { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %d", idx); } } break; case OP_FMT_loc8: { int idx = get_u8 (tab + arg_pos); if (idx < b->var_count) { const char *var_name = JS_ToCString (ctx, b->vardefs[idx].var_name); if (var_name) { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u: %s", idx, var_name); JS_FreeCString (ctx, var_name); } else { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u", idx); } } else { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u", idx); } } break; case OP_FMT_loc: { int idx = get_u16 (tab + arg_pos); if (idx < b->var_count) { const char *var_name = JS_ToCString (ctx, b->vardefs[idx].var_name); if (var_name) { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u: %s", idx, var_name); JS_FreeCString (ctx, var_name); } else { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u", idx); } } else { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u", idx); } } break; case OP_FMT_none_arg: { int idx = (op - OP_get_arg0) % 4; if (idx < b->arg_count) { const char *arg_name = JS_ToCString (ctx, b->vardefs[idx].var_name); if (arg_name) { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %d: %s", idx, arg_name); JS_FreeCString (ctx, arg_name); } else { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %d", idx); } } else { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %d", idx); } } break; case OP_FMT_arg: { int idx = get_u16 (tab + arg_pos); if (idx < b->arg_count) { const char *arg_name = JS_ToCString (ctx, b->vardefs[idx].var_name); if (arg_name) { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u: %s", idx, arg_name); JS_FreeCString (ctx, arg_name); } else { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u", idx); } } else { snprintf (opcode_str + strlen (opcode_str), sizeof (opcode_str) - strlen (opcode_str), " %u", idx); } } break; default: break; } } JSValue js_opcode_str = JS_NewString (ctx, opcode_str); JS_SetPropertyUint32 (ctx, ret, idx++, js_opcode_str); pos += size; } return ret; } JSValue js_debugger_local_variables (JSContext *ctx, int stack_index) { JSValue ret = JS_NewObject (ctx); JSStackFrame *sf; int cur_index = 0; for (sf = ctx->current_stack_frame; sf != NULL; sf = sf->prev_frame) { // this val is one frame up if (cur_index == stack_index - 1) { if (js_is_bytecode_function (sf->cur_func)) { JSFunction *f = JS_VALUE_GET_FUNCTION (sf->cur_func); JSFunctionBytecode *b = f->u.func.function_bytecode; JSValue this_obj = sf->var_buf[b->var_count]; // only provide a this if it is not the global object. if (JS_VALUE_GET_OBJ (this_obj) != JS_VALUE_GET_OBJ (ctx->global_obj)) JS_SetPropertyStr (ctx, ret, "this", this_obj); } } if (cur_index < stack_index) { cur_index++; continue; } if (!js_is_bytecode_function (sf->cur_func)) goto done; JSFunction *f = JS_VALUE_GET_FUNCTION (sf->cur_func); JSFunctionBytecode *b = f->u.func.function_bytecode; for (uint32_t i = 0; i < b->arg_count + b->var_count; i++) { JSValue var_val; if (i < b->arg_count) var_val = sf->arg_buf[i]; else var_val = sf->var_buf[i - b->arg_count]; if (JS_IsUninitialized (var_val)) continue; JSVarDef *vd = b->vardefs + i; JS_SetProperty (ctx, ret, vd->var_name, var_val); } break; } done: return ret; } void js_debugger_set_closure_variable (JSContext *ctx, JSValue fn, JSValue var_name, JSValue val) { /* TODO: Reimplement using outer_frame mechanism if debugging is needed */ (void)ctx; (void)fn; (void)var_name; (void)val; } JSValue js_debugger_closure_variables (JSContext *ctx, JSValue fn) { /* TODO: Reimplement using outer_frame mechanism if debugging is needed */ (void)fn; return JS_NewObject (ctx); } void *js_debugger_val_address (JSContext *ctx, JSValue val) { return JS_VALUE_GET_PTR (val); } /* ============================================================================ * Cell Script Module: json * Provides json.encode() and json.decode() using pure C implementation * ============================================================================ */ static JSValue js_cell_json_encode (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_ThrowTypeError (ctx, "json.encode requires at least 1 argument"); JSValue replacer = argc > 1 ? argv[1] : JS_NULL; JSValue space = argc > 2 ? argv[2] : JS_NewInt32 (ctx, 1); JSValue result = JS_JSONStringify (ctx, argv[0], replacer, space); return result; } static JSValue js_cell_json_decode (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { if (argc < 1) return JS_ThrowTypeError (ctx, "json.decode requires at least 1 argument"); if (!JS_IsText (argv[0])) { JSValue err = JS_NewError (ctx); JS_SetPropertyStr ( ctx, err, "message", JS_NewString (ctx, "couldn't parse text: not a string")); return JS_Throw (ctx, err); } const char *str = JS_ToCString (ctx, argv[0]); if (!str) return JS_EXCEPTION; size_t len = strlen (str); JSValue result = JS_ParseJSON (ctx, str, len, ""); JS_FreeCString (ctx, str); /* Apply reviver if provided */ if (argc > 1 && JS_IsFunction (argv[1]) && !JS_IsException (result)) { /* Create wrapper object to pass to reviver */ JSValue wrapper = JS_NewObject (ctx); JS_SetPropertyStr (ctx, wrapper, "", result); JSValue holder = wrapper; JSValue key = JS_KEY_empty; JSValue args[2] = { key, JS_GetProperty (ctx, holder, key) }; JSValue final = JS_Call (ctx, argv[1], holder, 2, args); result = final; } return result; } static const JSCFunctionListEntry js_cell_json_funcs[] = { JS_CFUNC_DEF ("encode", 1, js_cell_json_encode), JS_CFUNC_DEF ("decode", 1, js_cell_json_decode), }; JSValue js_json_use (JSContext *ctx) { JSGCRef obj_ref; JS_PushGCRef (ctx, &obj_ref); obj_ref.val = JS_NewObject (ctx); JS_SetPropertyFunctionList (ctx, obj_ref.val, js_cell_json_funcs, countof (js_cell_json_funcs)); JSValue result = obj_ref.val; JS_PopGCRef (ctx, &obj_ref); return result; } /* ============================================================================ * Cell Script Module: nota * Provides nota.encode() and nota.decode() for NOTA binary serialization * ============================================================================ */ static int nota_get_arr_len (JSContext *ctx, JSValue arr) { int64_t len; JS_GetLength (ctx, arr, &len); return (int)len; } typedef struct NotaEncodeContext { JSContext *ctx; JSGCRef *visitedStack_ref; /* pointer to GC-rooted ref */ NotaBuffer nb; int cycle; JSGCRef *replacer_ref; /* pointer to GC-rooted ref */ } NotaEncodeContext; static void nota_stack_push (NotaEncodeContext *enc, JSValueConst val) { JSContext *ctx = enc->ctx; int len = nota_get_arr_len (ctx, enc->visitedStack_ref->val); JS_SetPropertyInt64 (ctx, enc->visitedStack_ref->val, len, JS_DupValue (ctx, val)); } static void nota_stack_pop (NotaEncodeContext *enc) { JSContext *ctx = enc->ctx; int len = nota_get_arr_len (ctx, enc->visitedStack_ref->val); JS_SetPropertyStr (ctx, enc->visitedStack_ref->val, "length", JS_NewUint32 (ctx, len - 1)); } static int nota_stack_has (NotaEncodeContext *enc, JSValueConst val) { JSContext *ctx = enc->ctx; int len = nota_get_arr_len (ctx, enc->visitedStack_ref->val); for (int i = 0; i < len; i++) { JSValue elem = JS_GetPropertyUint32 (ctx, enc->visitedStack_ref->val, i); if (JS_IsObject (elem) && JS_IsObject (val)) { if (JS_StrictEq (ctx, elem, val)) { JS_FreeValue (ctx, elem); return 1; } } JS_FreeValue (ctx, elem); } return 0; } static JSValue nota_apply_replacer (NotaEncodeContext *enc, JSValueConst holder, JSValueConst key, JSValueConst val) { if (!enc->replacer_ref || JS_IsNull (enc->replacer_ref->val)) return JS_DupValue (enc->ctx, val); JSValue args[2] = { JS_DupValue (enc->ctx, key), JS_DupValue (enc->ctx, val) }; JSValue result = JS_Call (enc->ctx, enc->replacer_ref->val, holder, 2, args); JS_FreeValue (enc->ctx, args[0]); JS_FreeValue (enc->ctx, args[1]); if (JS_IsException (result)) return JS_DupValue (enc->ctx, val); return result; } static char *js_do_nota_decode (JSContext *js, JSValue *tmp, char *nota, JSValue holder, JSValue key, JSValue reviver) { int type = nota_type (nota); JSValue ret2; long long n; double d; int b; char *str; uint8_t *blob; switch (type) { case NOTA_BLOB: nota = nota_read_blob (&n, (char **)&blob, nota); *tmp = js_new_blob_stoned_copy (js, blob, n); sys_free (blob); break; case NOTA_TEXT: nota = nota_read_text (&str, nota); *tmp = JS_NewString (js, str); sys_free (str); break; case NOTA_ARR: nota = nota_read_array (&n, nota); *tmp = JS_NewArray (js); for (int i = 0; i < n; i++) { nota = js_do_nota_decode (js, &ret2, nota, *tmp, JS_NewInt32 (js, i), reviver); JS_SetPropertyInt64 (js, *tmp, i, ret2); } break; case NOTA_REC: nota = nota_read_record (&n, nota); *tmp = JS_NewObject (js); for (int i = 0; i < n; i++) { nota = nota_read_text (&str, nota); JSValue prop_key = JS_NewString (js, str); nota = js_do_nota_decode (js, &ret2, nota, *tmp, prop_key, reviver); JS_SetPropertyStr (js, *tmp, str, ret2); JS_FreeValue (js, prop_key); sys_free (str); } break; case NOTA_INT: nota = nota_read_int (&n, nota); *tmp = JS_NewInt64 (js, n); break; case NOTA_SYM: nota = nota_read_sym (&b, nota); if (b == NOTA_PRIVATE) { JSValue inner; nota = js_do_nota_decode (js, &inner, nota, holder, JS_NULL, reviver); JSValue obj = JS_NewObject (js); *tmp = obj; } else { switch (b) { case NOTA_NULL: *tmp = JS_NULL; break; case NOTA_FALSE: *tmp = JS_NewBool (js, 0); break; case NOTA_TRUE: *tmp = JS_NewBool (js, 1); break; default: *tmp = JS_NULL; break; } } break; default: case NOTA_FLOAT: nota = nota_read_float (&d, nota); *tmp = JS_NewFloat64 (js, d); break; } if (!JS_IsNull (reviver)) { JSValue args[2] = { JS_DupValue (js, key), JS_DupValue (js, *tmp) }; JSValue revived = JS_Call (js, reviver, holder, 2, args); JS_FreeValue (js, args[0]); JS_FreeValue (js, args[1]); if (!JS_IsException (revived)) { JS_FreeValue (js, *tmp); *tmp = revived; } else { JS_FreeValue (js, revived); } } return nota; } static void nota_encode_value (NotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValueConst key) { JSContext *ctx = enc->ctx; JSGCRef replaced_ref, keys_ref, elem_ref, prop_ref; JS_PushGCRef (ctx, &replaced_ref); replaced_ref.val = nota_apply_replacer (enc, holder, key, val); int tag = JS_VALUE_GET_TAG (replaced_ref.val); switch (tag) { case JS_TAG_INT: case JS_TAG_FLOAT64: { double d; JS_ToFloat64 (ctx, &d, replaced_ref.val); nota_write_number (&enc->nb, d); break; } case JS_TAG_STRING: { const char *str = JS_ToCString (ctx, replaced_ref.val); nota_write_text (&enc->nb, str); JS_FreeCString (ctx, str); break; } case JS_TAG_BOOL: if (JS_VALUE_GET_BOOL (replaced_ref.val)) nota_write_sym (&enc->nb, NOTA_TRUE); else nota_write_sym (&enc->nb, NOTA_FALSE); break; case JS_TAG_NULL: nota_write_sym (&enc->nb, NOTA_NULL); break; case JS_TAG_PTR: { if (js_is_blob (ctx, replaced_ref.val)) { size_t buf_len; void *buf_data = js_get_blob_data (ctx, &buf_len, replaced_ref.val); if (buf_data == (void *)-1) { JS_PopGCRef (ctx, &replaced_ref); return; } nota_write_blob (&enc->nb, (unsigned long long)buf_len * 8, (const char *)buf_data); break; } if (JS_IsArray (replaced_ref.val)) { if (nota_stack_has (enc, replaced_ref.val)) { enc->cycle = 1; break; } nota_stack_push (enc, replaced_ref.val); int arr_len = nota_get_arr_len (ctx, replaced_ref.val); nota_write_array (&enc->nb, arr_len); JS_PushGCRef (ctx, &elem_ref); for (int i = 0; i < arr_len; i++) { elem_ref.val = JS_GetPropertyUint32 (ctx, replaced_ref.val, i); JSValue elem_key = JS_NewInt32 (ctx, i); nota_encode_value (enc, elem_ref.val, replaced_ref.val, elem_key); } JS_PopGCRef (ctx, &elem_ref); nota_stack_pop (enc); break; } JSValue adata = JS_NULL; if (!JS_IsNull (adata)) { nota_write_sym (&enc->nb, NOTA_PRIVATE); nota_encode_value (enc, adata, replaced_ref.val, JS_NULL); break; } if (nota_stack_has (enc, replaced_ref.val)) { enc->cycle = 1; break; } nota_stack_push (enc, replaced_ref.val); JSValue to_json = JS_GetPropertyStr (ctx, replaced_ref.val, "toJSON"); if (JS_IsFunction (to_json)) { JSValue result = JS_Call (ctx, to_json, replaced_ref.val, 0, NULL); if (!JS_IsException (result)) { nota_encode_value (enc, result, holder, key); } else { nota_write_sym (&enc->nb, NOTA_NULL); } nota_stack_pop (enc); break; } JS_PushGCRef (ctx, &keys_ref); keys_ref.val = JS_GetOwnPropertyNames (ctx, replaced_ref.val); if (JS_IsException (keys_ref.val)) { nota_write_sym (&enc->nb, NOTA_NULL); nota_stack_pop (enc); JS_PopGCRef (ctx, &keys_ref); break; } int64_t plen64; if (JS_GetLength (ctx, keys_ref.val, &plen64) < 0) { nota_write_sym (&enc->nb, NOTA_NULL); nota_stack_pop (enc); JS_PopGCRef (ctx, &keys_ref); break; } uint32_t plen = (uint32_t)plen64; JS_PushGCRef (ctx, &prop_ref); JS_PushGCRef (ctx, &elem_ref); uint32_t non_function_count = 0; for (uint32_t i = 0; i < plen; i++) { elem_ref.val = JS_GetPropertyUint32 (ctx, keys_ref.val, i); prop_ref.val = JS_GetProperty (ctx, replaced_ref.val, elem_ref.val); if (!JS_IsFunction (prop_ref.val)) non_function_count++; } nota_write_record (&enc->nb, non_function_count); for (uint32_t i = 0; i < plen; i++) { elem_ref.val = JS_GetPropertyUint32 (ctx, keys_ref.val, i); prop_ref.val = JS_GetProperty (ctx, replaced_ref.val, elem_ref.val); if (!JS_IsFunction (prop_ref.val)) { const char *prop_name = JS_ToCString (ctx, elem_ref.val); nota_write_text (&enc->nb, prop_name ? prop_name : ""); nota_encode_value (enc, prop_ref.val, replaced_ref.val, elem_ref.val); JS_FreeCString (ctx, prop_name); } } JS_PopGCRef (ctx, &elem_ref); JS_PopGCRef (ctx, &prop_ref); JS_PopGCRef (ctx, &keys_ref); nota_stack_pop (enc); break; } default: nota_write_sym (&enc->nb, NOTA_NULL); break; } JS_PopGCRef (ctx, &replaced_ref); } void *value2nota (JSContext *ctx, JSValue v) { JSGCRef val_ref, stack_ref, key_ref; JS_PushGCRef (ctx, &val_ref); JS_PushGCRef (ctx, &stack_ref); JS_PushGCRef (ctx, &key_ref); val_ref.val = v; NotaEncodeContext enc_s, *enc = &enc_s; enc->ctx = ctx; stack_ref.val = JS_NewArray (ctx); enc->visitedStack_ref = &stack_ref; enc->cycle = 0; enc->replacer_ref = NULL; nota_buffer_init (&enc->nb, 128); key_ref.val = JS_NewString (ctx, ""); nota_encode_value (enc, val_ref.val, JS_NULL, key_ref.val); if (enc->cycle) { JS_PopGCRef (ctx, &key_ref); JS_PopGCRef (ctx, &stack_ref); JS_PopGCRef (ctx, &val_ref); nota_buffer_free (&enc->nb); return NULL; } JS_PopGCRef (ctx, &key_ref); JS_PopGCRef (ctx, &stack_ref); JS_PopGCRef (ctx, &val_ref); void *data_ptr = enc->nb.data; enc->nb.data = NULL; nota_buffer_free (&enc->nb); return data_ptr; } JSValue nota2value (JSContext *js, void *nota) { if (!nota) return JS_NULL; JSGCRef holder_ref, key_ref, ret_ref; JS_PushGCRef (js, &holder_ref); JS_PushGCRef (js, &key_ref); JS_PushGCRef (js, &ret_ref); holder_ref.val = JS_NewObject (js); key_ref.val = JS_NewString (js, ""); ret_ref.val = JS_NULL; js_do_nota_decode (js, &ret_ref.val, nota, holder_ref.val, key_ref.val, JS_NULL); JSValue result = ret_ref.val; JS_PopGCRef (js, &ret_ref); JS_PopGCRef (js, &key_ref); JS_PopGCRef (js, &holder_ref); return result; } static JSValue js_nota_encode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_ThrowTypeError (ctx, "nota.encode requires at least 1 argument"); JSGCRef val_ref, stack_ref, replacer_ref, key_ref; JS_PushGCRef (ctx, &val_ref); JS_PushGCRef (ctx, &stack_ref); JS_PushGCRef (ctx, &replacer_ref); JS_PushGCRef (ctx, &key_ref); val_ref.val = argv[0]; stack_ref.val = JS_NewArray (ctx); replacer_ref.val = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL; NotaEncodeContext enc_s, *enc = &enc_s; enc->ctx = ctx; enc->visitedStack_ref = &stack_ref; enc->cycle = 0; enc->replacer_ref = &replacer_ref; nota_buffer_init (&enc->nb, 128); key_ref.val = JS_NewString (ctx, ""); nota_encode_value (enc, val_ref.val, JS_NULL, key_ref.val); JSValue ret; if (enc->cycle) { nota_buffer_free (&enc->nb); ret = JS_ThrowReferenceError (ctx, "Tried to encode something to nota with a cycle."); } else { size_t total_len = enc->nb.size; void *data_ptr = enc->nb.data; ret = js_new_blob_stoned_copy (ctx, (uint8_t *)data_ptr, total_len); nota_buffer_free (&enc->nb); } JS_PopGCRef (ctx, &key_ref); JS_PopGCRef (ctx, &replacer_ref); JS_PopGCRef (ctx, &stack_ref); JS_PopGCRef (ctx, &val_ref); return ret; } static JSValue js_nota_decode (JSContext *js, JSValueConst self, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; size_t len; unsigned char *nota = js_get_blob_data (js, &len, argv[0]); if (nota == (unsigned char *)-1) return JS_EXCEPTION; if (!nota) return JS_NULL; JSGCRef holder_ref, key_ref, ret_ref, reviver_ref; JS_PushGCRef (js, &holder_ref); JS_PushGCRef (js, &key_ref); JS_PushGCRef (js, &ret_ref); JS_PushGCRef (js, &reviver_ref); reviver_ref.val = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL; holder_ref.val = JS_NewObject (js); key_ref.val = JS_NewString (js, ""); ret_ref.val = JS_NULL; js_do_nota_decode (js, &ret_ref.val, (char *)nota, holder_ref.val, key_ref.val, reviver_ref.val); JSValue result = ret_ref.val; JS_PopGCRef (js, &reviver_ref); JS_PopGCRef (js, &ret_ref); JS_PopGCRef (js, &key_ref); JS_PopGCRef (js, &holder_ref); return result; } static const JSCFunctionListEntry js_nota_funcs[] = { JS_CFUNC_DEF ("encode", 1, js_nota_encode), JS_CFUNC_DEF ("decode", 1, js_nota_decode), }; JSValue js_nota_use (JSContext *js) { JSGCRef export_ref; JS_PushGCRef (js, &export_ref); export_ref.val = JS_NewObject (js); JS_SetPropertyFunctionList (js, export_ref.val, js_nota_funcs, sizeof (js_nota_funcs) / sizeof (JSCFunctionListEntry)); JSValue result = export_ref.val; JS_PopGCRef (js, &export_ref); return result; } /* ============================================================================ * Cell Script Module: wota * Provides wota.encode() and wota.decode() for WOTA binary serialization * ============================================================================ */ typedef struct ObjectRef { void *ptr; struct ObjectRef *next; } ObjectRef; typedef struct WotaEncodeContext { JSContext *ctx; ObjectRef *visited_stack; WotaBuffer wb; int cycle; JSValue replacer; } WotaEncodeContext; static void wota_stack_push (WotaEncodeContext *enc, JSValueConst val) { (void)enc; (void)val; /* Cycle detection disabled for performance */ } static void wota_stack_pop (WotaEncodeContext *enc) { if (!enc->visited_stack) return; ObjectRef *top = enc->visited_stack; enc->visited_stack = top->next; sys_free (top); } static int wota_stack_has (WotaEncodeContext *enc, JSValueConst val) { (void)enc; (void)val; return 0; /* Cycle detection disabled for performance */ } static void wota_stack_free (WotaEncodeContext *enc) { while (enc->visited_stack) { wota_stack_pop (enc); } } static JSValue wota_apply_replacer (WotaEncodeContext *enc, JSValueConst holder, JSValue key, JSValueConst val) { if (JS_IsNull (enc->replacer)) return JS_DupValue (enc->ctx, val); JSValue key_val = JS_IsNull (key) ? JS_NULL : JS_DupValue (enc->ctx, key); JSValue args[2] = { key_val, JS_DupValue (enc->ctx, val) }; JSValue result = JS_Call (enc->ctx, enc->replacer, holder, 2, args); JS_FreeValue (enc->ctx, args[0]); JS_FreeValue (enc->ctx, args[1]); if (JS_IsException (result)) return JS_DupValue (enc->ctx, val); return result; } static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValue key); static void encode_object_properties (WotaEncodeContext *enc, JSValueConst val, JSValueConst holder) { JSContext *ctx = enc->ctx; /* Root the input value to protect it during property enumeration */ JSGCRef val_ref, keys_ref; JS_PushGCRef (ctx, &val_ref); JS_PushGCRef (ctx, &keys_ref); val_ref.val = JS_DupValue (ctx, val); keys_ref.val = JS_GetOwnPropertyNames (ctx, val_ref.val); if (JS_IsException (keys_ref.val)) { wota_write_sym (&enc->wb, WOTA_NULL); JS_FreeValue (ctx, val_ref.val); JS_PopGCRef (ctx, &keys_ref); JS_PopGCRef (ctx, &val_ref); return; } int64_t plen64; if (JS_GetLength (ctx, keys_ref.val, &plen64) < 0) { JS_FreeValue (ctx, keys_ref.val); JS_FreeValue (ctx, val_ref.val); wota_write_sym (&enc->wb, WOTA_NULL); JS_PopGCRef (ctx, &keys_ref); JS_PopGCRef (ctx, &val_ref); return; } uint32_t plen = (uint32_t)plen64; uint32_t non_function_count = 0; /* Allocate GC-rooted arrays for props and keys */ JSGCRef *prop_refs = sys_malloc (sizeof (JSGCRef) * plen); JSGCRef *key_refs = sys_malloc (sizeof (JSGCRef) * plen); for (uint32_t i = 0; i < plen; i++) { JS_PushGCRef (ctx, &prop_refs[i]); JS_PushGCRef (ctx, &key_refs[i]); prop_refs[i].val = JS_NULL; key_refs[i].val = JS_NULL; } for (uint32_t i = 0; i < plen; i++) { JSValue key = JS_GetPropertyUint32 (ctx, keys_ref.val, i); JSValue prop_val = JS_GetProperty (ctx, val_ref.val, key); if (!JS_IsFunction (prop_val)) { key_refs[non_function_count].val = key; prop_refs[non_function_count].val = prop_val; non_function_count++; } else { JS_FreeValue (ctx, prop_val); JS_FreeValue (ctx, key); } } JS_FreeValue (ctx, keys_ref.val); wota_write_record (&enc->wb, non_function_count); for (uint32_t i = 0; i < non_function_count; i++) { size_t klen; const char *prop_name = JS_ToCStringLen (ctx, &klen, key_refs[i].val); wota_write_text_len (&enc->wb, prop_name ? prop_name : "", prop_name ? klen : 0); wota_encode_value (enc, prop_refs[i].val, val_ref.val, key_refs[i].val); JS_FreeCString (ctx, prop_name); JS_FreeValue (ctx, prop_refs[i].val); JS_FreeValue (ctx, key_refs[i].val); } /* Pop all GC refs in reverse order */ for (int i = plen - 1; i >= 0; i--) { JS_PopGCRef (ctx, &key_refs[i]); JS_PopGCRef (ctx, &prop_refs[i]); } sys_free (prop_refs); sys_free (key_refs); JS_FreeValue (ctx, val_ref.val); JS_PopGCRef (ctx, &keys_ref); JS_PopGCRef (ctx, &val_ref); } static void wota_encode_value (WotaEncodeContext *enc, JSValueConst val, JSValueConst holder, JSValue key) { JSContext *ctx = enc->ctx; JSValue replaced; if (!JS_IsNull (enc->replacer) && !JS_IsNull (key)) replaced = wota_apply_replacer (enc, holder, key, val); else replaced = JS_DupValue (enc->ctx, val); int tag = JS_VALUE_GET_TAG (replaced); switch (tag) { case JS_TAG_INT: { int32_t d; JS_ToInt32 (ctx, &d, replaced); wota_write_int_word (&enc->wb, d); break; } case JS_TAG_FLOAT64: { double d; if (JS_ToFloat64 (ctx, &d, replaced) < 0) { wota_write_sym (&enc->wb, WOTA_NULL); break; } wota_write_float_word (&enc->wb, d); break; } case JS_TAG_STRING: { size_t plen; const char *str = JS_ToCStringLen (ctx, &plen, replaced); wota_write_text_len (&enc->wb, str ? str : "", str ? plen : 0); JS_FreeCString (ctx, str); break; } case JS_TAG_BOOL: wota_write_sym (&enc->wb, JS_VALUE_GET_BOOL (replaced) ? WOTA_TRUE : WOTA_FALSE); break; case JS_TAG_NULL: wota_write_sym (&enc->wb, WOTA_NULL); break; case JS_TAG_PTR: { if (js_is_blob (ctx, replaced)) { size_t buf_len; void *buf_data = js_get_blob_data (ctx, &buf_len, replaced); if (buf_data == (void *)-1) { JS_FreeValue (ctx, replaced); return; } if (buf_len == 0) { wota_write_blob (&enc->wb, 0, ""); } else { wota_write_blob (&enc->wb, (unsigned long long)buf_len * 8, (const char *)buf_data); } break; } if (JS_IsArray (replaced)) { if (wota_stack_has (enc, replaced)) { enc->cycle = 1; break; } wota_stack_push (enc, replaced); int64_t arr_len; JS_GetLength (ctx, replaced, &arr_len); wota_write_array (&enc->wb, arr_len); for (int64_t i = 0; i < arr_len; i++) { JSValue elem_val = JS_GetPropertyUint32 (ctx, replaced, i); wota_encode_value (enc, elem_val, replaced, JS_NewInt32 (ctx, (int32_t)i)); JS_FreeValue (ctx, elem_val); } wota_stack_pop (enc); break; } JSValue adata = JS_NULL; if (!JS_IsNull (adata)) { wota_write_sym (&enc->wb, WOTA_PRIVATE); wota_encode_value (enc, adata, replaced, JS_NULL); JS_FreeValue (ctx, adata); break; } JS_FreeValue (ctx, adata); if (wota_stack_has (enc, replaced)) { enc->cycle = 1; break; } wota_stack_push (enc, replaced); JSValue to_json = JS_GetPropertyStr (ctx, replaced, "toJSON"); if (JS_IsFunction (to_json)) { JSValue result = JS_Call (ctx, to_json, replaced, 0, NULL); JS_FreeValue (ctx, to_json); if (!JS_IsException (result)) { wota_encode_value (enc, result, holder, key); JS_FreeValue (ctx, result); } else wota_write_sym (&enc->wb, WOTA_NULL); wota_stack_pop (enc); break; } JS_FreeValue (ctx, to_json); encode_object_properties (enc, replaced, holder); wota_stack_pop (enc); break; } default: wota_write_sym (&enc->wb, WOTA_NULL); break; } JS_FreeValue (ctx, replaced); } static char *decode_wota_value (JSContext *ctx, char *data_ptr, JSValue *out_val, JSValue holder, JSValue key, JSValue reviver) { uint64_t first_word = *(uint64_t *)data_ptr; int type = (int)(first_word & 0xffU); switch (type) { case WOTA_INT: { long long val; data_ptr = wota_read_int (&val, data_ptr); *out_val = JS_NewInt64 (ctx, val); break; } case WOTA_FLOAT: { double d; data_ptr = wota_read_float (&d, data_ptr); *out_val = JS_NewFloat64 (ctx, d); break; } case WOTA_SYM: { int scode; data_ptr = wota_read_sym (&scode, data_ptr); if (scode == WOTA_PRIVATE) { JSValue inner = JS_NULL; data_ptr = decode_wota_value (ctx, data_ptr, &inner, holder, JS_NULL, reviver); JSValue obj = JS_NewObject (ctx); *out_val = obj; } else if (scode == WOTA_NULL) *out_val = JS_NULL; else if (scode == WOTA_FALSE) *out_val = JS_NewBool (ctx, 0); else if (scode == WOTA_TRUE) *out_val = JS_NewBool (ctx, 1); else *out_val = JS_NULL; break; } case WOTA_BLOB: { long long blen; char *bdata = NULL; data_ptr = wota_read_blob (&blen, &bdata, data_ptr); *out_val = bdata ? js_new_blob_stoned_copy (ctx, (uint8_t *)bdata, (size_t)blen) : js_new_blob_stoned_copy (ctx, NULL, 0); if (bdata) sys_free (bdata); break; } case WOTA_TEXT: { char *utf8 = NULL; data_ptr = wota_read_text (&utf8, data_ptr); *out_val = JS_NewString (ctx, utf8 ? utf8 : ""); if (utf8) sys_free (utf8); break; } case WOTA_ARR: { long long c; data_ptr = wota_read_array (&c, data_ptr); JSGCRef arr_ref; JS_PushGCRef (ctx, &arr_ref); arr_ref.val = JS_NewArrayLen (ctx, c); for (long long i = 0; i < c; i++) { JSGCRef elem_ref; JS_PushGCRef (ctx, &elem_ref); elem_ref.val = JS_NULL; JSValue idx_key = JS_NewInt32 (ctx, (int32_t)i); data_ptr = decode_wota_value (ctx, data_ptr, &elem_ref.val, arr_ref.val, idx_key, reviver); JS_SetPropertyUint32 (ctx, arr_ref.val, i, elem_ref.val); JS_PopGCRef (ctx, &elem_ref); } *out_val = arr_ref.val; JS_PopGCRef (ctx, &arr_ref); break; } case WOTA_REC: { long long c; data_ptr = wota_read_record (&c, data_ptr); JSGCRef obj_ref; JS_PushGCRef (ctx, &obj_ref); obj_ref.val = JS_NewObject (ctx); for (long long i = 0; i < c; i++) { char *tkey = NULL; size_t key_len; data_ptr = wota_read_text_len (&key_len, &tkey, data_ptr); if (!tkey) continue; JSGCRef prop_key_ref, sub_val_ref; JS_PushGCRef (ctx, &prop_key_ref); JS_PushGCRef (ctx, &sub_val_ref); prop_key_ref.val = JS_NewStringLen (ctx, tkey, key_len); sub_val_ref.val = JS_NULL; data_ptr = decode_wota_value (ctx, data_ptr, &sub_val_ref.val, obj_ref.val, prop_key_ref.val, reviver); JS_SetProperty (ctx, obj_ref.val, prop_key_ref.val, sub_val_ref.val); JS_FreeValue (ctx, prop_key_ref.val); JS_PopGCRef (ctx, &sub_val_ref); JS_PopGCRef (ctx, &prop_key_ref); sys_free (tkey); } *out_val = obj_ref.val; JS_PopGCRef (ctx, &obj_ref); break; } default: data_ptr += 8; *out_val = JS_NULL; break; } if (!JS_IsNull (reviver)) { JSValue key_val = JS_IsNull (key) ? JS_NULL : JS_DupValue (ctx, key); JSValue args[2] = { key_val, JS_DupValue (ctx, *out_val) }; JSValue revived = JS_Call (ctx, reviver, holder, 2, args); JS_FreeValue (ctx, args[0]); JS_FreeValue (ctx, args[1]); if (!JS_IsException (revived)) { JS_FreeValue (ctx, *out_val); *out_val = revived; } else JS_FreeValue (ctx, revived); } return data_ptr; } void *value2wota (JSContext *ctx, JSValue v, JSValue replacer, size_t *bytes) { JSGCRef val_ref, rep_ref; JS_PushGCRef (ctx, &val_ref); JS_PushGCRef (ctx, &rep_ref); val_ref.val = v; rep_ref.val = replacer; WotaEncodeContext enc_s, *enc = &enc_s; enc->ctx = ctx; enc->visited_stack = NULL; enc->cycle = 0; enc->replacer = rep_ref.val; wota_buffer_init (&enc->wb, 16); wota_encode_value (enc, val_ref.val, JS_NULL, JS_NULL); if (enc->cycle) { wota_stack_free (enc); wota_buffer_free (&enc->wb); JS_PopGCRef (ctx, &rep_ref); JS_PopGCRef (ctx, &val_ref); return NULL; } wota_stack_free (enc); size_t total_bytes = enc->wb.size * sizeof (uint64_t); void *wota = sys_realloc (enc->wb.data, total_bytes); if (bytes) *bytes = total_bytes; JS_PopGCRef (ctx, &rep_ref); JS_PopGCRef (ctx, &val_ref); return wota; } JSValue wota2value (JSContext *ctx, void *wota) { JSGCRef holder_ref, result_ref; JS_PushGCRef (ctx, &holder_ref); JS_PushGCRef (ctx, &result_ref); result_ref.val = JS_NULL; holder_ref.val = JS_NewObject (ctx); decode_wota_value (ctx, wota, &result_ref.val, holder_ref.val, JS_NULL, JS_NULL); JSValue result = result_ref.val; JS_PopGCRef (ctx, &result_ref); JS_PopGCRef (ctx, &holder_ref); return result; } static JSValue js_wota_encode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_ThrowTypeError (ctx, "wota.encode requires at least 1 argument"); size_t total_bytes; void *wota = value2wota (ctx, argv[0], JS_IsFunction (argv[1]) ? argv[1] : JS_NULL, &total_bytes); JSValue ret = js_new_blob_stoned_copy (ctx, wota, total_bytes); sys_free (wota); return ret; } static JSValue js_wota_decode (JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { if (argc < 1) return JS_NULL; size_t len; uint8_t *buf = js_get_blob_data (ctx, &len, argv[0]); if (buf == (uint8_t *)-1) return JS_EXCEPTION; if (!buf || len == 0) return JS_ThrowTypeError (ctx, "No blob data present"); JSValue reviver = (argc > 1 && JS_IsFunction (argv[1])) ? argv[1] : JS_NULL; char *data_ptr = (char *)buf; JSValue result = JS_NULL; JSValue holder = JS_NewObject (ctx); JSValue empty_key = JS_NewString (ctx, ""); decode_wota_value (ctx, data_ptr, &result, holder, empty_key, reviver); JS_FreeValue (ctx, empty_key); JS_FreeValue (ctx, holder); return result; } static const JSCFunctionListEntry js_wota_funcs[] = { JS_CFUNC_DEF ("encode", 2, js_wota_encode), JS_CFUNC_DEF ("decode", 2, js_wota_decode), }; JSValue js_wota_use (JSContext *ctx) { JSGCRef exports_ref; JS_PushGCRef (ctx, &exports_ref); exports_ref.val = JS_NewObject (ctx); JS_SetPropertyFunctionList (ctx, exports_ref.val, js_wota_funcs, sizeof (js_wota_funcs) / sizeof (js_wota_funcs[0])); JSValue result = exports_ref.val; JS_PopGCRef (ctx, &exports_ref); return result; } static JSValue js_math_e (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double power = 1.0; if (argc > 0 && !JS_IsNull (argv[0])) { if (JS_ToFloat64 (ctx, &power, argv[0]) < 0) return JS_EXCEPTION; } return JS_NewFloat64 (ctx, exp (power)); } /* ============================================================================ * Cell Script Module: math/radians * Provides trigonometric and math functions using radians * ============================================================================ */ static JSValue js_math_rad_arc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, acos (x)); } static JSValue js_math_rad_arc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, asin (x)); } static JSValue js_math_rad_arc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, atan (x)); } static JSValue js_math_rad_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, cos (x)); } static JSValue js_math_rad_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, sin (x)); } static JSValue js_math_rad_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, tan (x)); } static JSValue js_math_ln (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, log (x)); } static JSValue js_math_log10 (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, log10 (x)); } static JSValue js_math_log2 (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, log2 (x)); } static JSValue js_math_power (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x, y; if (argc < 2) return JS_NULL; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; if (JS_ToFloat64 (ctx, &y, argv[1]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, pow (x, y)); } static JSValue js_math_root (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x, n; if (argc < 2) return JS_NULL; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; if (JS_ToFloat64 (ctx, &n, argv[1]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, pow (x, 1.0 / n)); } static JSValue js_math_sqrt (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, sqrt (x)); } static const JSCFunctionListEntry js_math_radians_funcs[] = { JS_CFUNC_DEF ("arc_cosine", 1, js_math_rad_arc_cosine), JS_CFUNC_DEF ("arc_sine", 1, js_math_rad_arc_sine), JS_CFUNC_DEF ("arc_tangent", 1, js_math_rad_arc_tangent), JS_CFUNC_DEF ("cosine", 1, js_math_rad_cosine), JS_CFUNC_DEF ("sine", 1, js_math_rad_sine), JS_CFUNC_DEF ("tangent", 1, js_math_rad_tangent), JS_CFUNC_DEF ("ln", 1, js_math_ln), JS_CFUNC_DEF ("log", 1, js_math_log10), JS_CFUNC_DEF ("log2", 1, js_math_log2), JS_CFUNC_DEF ("power", 2, js_math_power), JS_CFUNC_DEF ("root", 2, js_math_root), JS_CFUNC_DEF ("sqrt", 1, js_math_sqrt), JS_CFUNC_DEF ("e", 1, js_math_e) }; JSValue js_math_radians_use (JSContext *ctx) { JSValue obj = JS_NewObject (ctx); JS_SetPropertyFunctionList (ctx, obj, js_math_radians_funcs, countof (js_math_radians_funcs)); return obj; } /* ============================================================================ * Cell Script Module: math/degrees * Provides trigonometric and math functions using degrees * ============================================================================ */ #define DEG2RAD (3.14159265358979323846264338327950288419716939937510 / 180.0) #define RAD2DEG (180.0 / 3.14159265358979323846264338327950288419716939937510) static JSValue js_math_deg_arc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, acos (x) * RAD2DEG); } static JSValue js_math_deg_arc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, asin (x) * RAD2DEG); } static JSValue js_math_deg_arc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, atan (x) * RAD2DEG); } static JSValue js_math_deg_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, cos (x * DEG2RAD)); } static JSValue js_math_deg_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, sin (x * DEG2RAD)); } static JSValue js_math_deg_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, tan (x * DEG2RAD)); } static const JSCFunctionListEntry js_math_degrees_funcs[] = { JS_CFUNC_DEF ("arc_cosine", 1, js_math_deg_arc_cosine), JS_CFUNC_DEF ("arc_sine", 1, js_math_deg_arc_sine), JS_CFUNC_DEF ("arc_tangent", 1, js_math_deg_arc_tangent), JS_CFUNC_DEF ("cosine", 1, js_math_deg_cosine), JS_CFUNC_DEF ("sine", 1, js_math_deg_sine), JS_CFUNC_DEF ("tangent", 1, js_math_deg_tangent), JS_CFUNC_DEF ("ln", 1, js_math_ln), JS_CFUNC_DEF ("log", 1, js_math_log10), JS_CFUNC_DEF ("log2", 1, js_math_log2), JS_CFUNC_DEF ("power", 2, js_math_power), JS_CFUNC_DEF ("root", 2, js_math_root), JS_CFUNC_DEF ("sqrt", 1, js_math_sqrt), JS_CFUNC_DEF ("e", 1, js_math_e) }; JSValue js_math_degrees_use (JSContext *ctx) { JSValue obj = JS_NewObject (ctx); JS_SetPropertyFunctionList (ctx, obj, js_math_degrees_funcs, countof (js_math_degrees_funcs)); return obj; } /* ============================================================================ * Cell Script Module: math/cycles * Provides trigonometric and math functions using cycles (0-1 = full rotation) * ============================================================================ */ #define TWOPI (2.0 * 3.14159265358979323846264338327950288419716939937510) static JSValue js_math_cyc_arc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, acos (x) / TWOPI); } static JSValue js_math_cyc_arc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, asin (x) / TWOPI); } static JSValue js_math_cyc_arc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, atan (x) / TWOPI); } static JSValue js_math_cyc_cosine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, cos (x * TWOPI)); } static JSValue js_math_cyc_sine (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, sin (x * TWOPI)); } static JSValue js_math_cyc_tangent (JSContext *ctx, JSValue this_val, int argc, JSValue *argv) { double x; if (JS_ToFloat64 (ctx, &x, argv[0]) < 0) return JS_EXCEPTION; return JS_NewFloat64 (ctx, tan (x * TWOPI)); } static const JSCFunctionListEntry js_math_cycles_funcs[] = { JS_CFUNC_DEF ("arc_cosine", 1, js_math_cyc_arc_cosine), JS_CFUNC_DEF ("arc_sine", 1, js_math_cyc_arc_sine), JS_CFUNC_DEF ("arc_tangent", 1, js_math_cyc_arc_tangent), JS_CFUNC_DEF ("cosine", 1, js_math_cyc_cosine), JS_CFUNC_DEF ("sine", 1, js_math_cyc_sine), JS_CFUNC_DEF ("tangent", 1, js_math_cyc_tangent), JS_CFUNC_DEF ("ln", 1, js_math_ln), JS_CFUNC_DEF ("log", 1, js_math_log10), JS_CFUNC_DEF ("log2", 1, js_math_log2), JS_CFUNC_DEF ("power", 2, js_math_power), JS_CFUNC_DEF ("root", 2, js_math_root), JS_CFUNC_DEF ("sqrt", 1, js_math_sqrt), JS_CFUNC_DEF ("e", 1, js_math_e) }; JSValue js_math_cycles_use (JSContext *ctx) { JSValue obj = JS_NewObject (ctx); JS_SetPropertyFunctionList (ctx, obj, js_math_cycles_funcs, countof (js_math_cycles_funcs)); return obj; } /* Public API: get stack trace as cJSON array */ cJSON *JS_GetStack(JSContext *ctx) { if (JS_IsNull(ctx->reg_current_frame)) return NULL; JSFrameRegister *frame = (JSFrameRegister *)JS_VALUE_GET_PTR(ctx->reg_current_frame); uint32_t cur_pc = ctx->current_register_pc; cJSON *arr = cJSON_CreateArray(); int is_first = 1; while (frame) { if (!JS_IsFunction(frame->function)) break; JSFunction *fn = JS_VALUE_GET_FUNCTION(frame->function); const char *func_name = NULL; const char *file = NULL; uint16_t line = 0, col = 0; uint32_t pc = is_first ? cur_pc : 0; if (fn->kind == JS_FUNC_KIND_REGISTER && fn->u.reg.code) { JSCodeRegister *code = fn->u.reg.code; file = code->filename_cstr; func_name = code->name_cstr; if (!is_first) { pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16); } if (code->line_table && pc < code->instr_count) { line = code->line_table[pc].line; col = code->line_table[pc].col; } } else if (fn->kind == JS_FUNC_KIND_MCODE && fn->u.mcode.code) { JSMCode *code = fn->u.mcode.code; file = code->filename; func_name = code->name; if (!is_first) { pc = (uint32_t)(JS_VALUE_GET_INT(frame->address) >> 16); } if (code->line_table && pc < code->instr_count) { line = code->line_table[pc].line; col = code->line_table[pc].col; } } cJSON *entry = cJSON_CreateObject(); cJSON_AddStringToObject(entry, "function", func_name ? func_name : ""); cJSON_AddStringToObject(entry, "file", file ? file : ""); cJSON_AddNumberToObject(entry, "line", line); cJSON_AddNumberToObject(entry, "column", col); cJSON_AddItemToArray(arr, entry); if (JS_IsNull(frame->caller)) break; frame = (JSFrameRegister *)JS_VALUE_GET_PTR(frame->caller); is_first = 0; } ctx->reg_current_frame = JS_NULL; return arr; }