13757 lines
425 KiB
C
13757 lines
425 KiB
C
/*
|
|
* QuickJS Javascript Engine
|
|
*
|
|
* Copyright (c) 2017-2025 Fabrice Bellard
|
|
* Copyright (c) 2017-2025 Charlie Gordon
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to
|
|
* deal in the Software without restriction, including without limitation the
|
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*/
|
|
|
|
#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 ("<null>");
|
|
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 = "<anonymous>";
|
|
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 : "<null>");
|
|
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, "<invalid string tag %d>", (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, "<input>");
|
|
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 : "<anonymous>", file ? file : "<unknown>", 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: <invalid opcode 0x%02x>\n", pos, op);
|
|
pos++;
|
|
continue;
|
|
}
|
|
const JSOpCode *oi = &short_opcode_info (op);
|
|
int size = oi->size;
|
|
if (pos + size > len) {
|
|
printf (" %5d: <truncated opcode 0x%02x>\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 : "<anonymous>");
|
|
|
|
/* 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, "<eval>");
|
|
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 "<native C>"
|
|
|
|
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), "<anonymous>");
|
|
|
|
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, "<native C>", 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, "<anonymous>"));
|
|
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, "<json>");
|
|
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 : "<anonymous>");
|
|
cJSON_AddStringToObject(entry, "file", file ? file : "<unknown>");
|
|
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;
|
|
}
|