mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 11:37:35 +00:00
0d748ad58e
This change fixes Cosmopolitan so it has fewer opinions about compiler warnings. The whole repository had to be cleaned up to be buildable in -Werror -Wall mode. This lets us benefit from things like strict const checking. Some actual bugs might have been caught too.
749 lines
24 KiB
C
749 lines
24 KiB
C
/*
|
|
* QuickJS Javascript Engine
|
|
*
|
|
* Copyright (c) 2017-2021 Fabrice Bellard
|
|
* Copyright (c) 2017-2021 Charlie Gordon
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to deal
|
|
* in the Software without restriction, including without limitation the rights
|
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
* copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
* THE SOFTWARE.
|
|
*/
|
|
#include "libc/assert.h"
|
|
#include "third_party/quickjs/internal.h"
|
|
|
|
asm(".ident\t\"\\n\\n\
|
|
QuickJS (MIT License)\\n\
|
|
Copyright (c) 2017-2021 Fabrice Bellard\\n\
|
|
Copyright (c) 2017-2021 Charlie Gordon\"");
|
|
asm(".include \"libc/disclaimer.inc\"");
|
|
/* clang-format off */
|
|
|
|
static JSValue js_map_constructor(JSContext *ctx, JSValueConst new_target,
|
|
int argc, JSValueConst *argv, int magic)
|
|
{
|
|
JSMapState *s;
|
|
JSValue obj, adder = JS_UNDEFINED, iter = JS_UNDEFINED, next_method = JS_UNDEFINED;
|
|
JSValueConst arr;
|
|
BOOL is_set, is_weak;
|
|
is_set = magic & MAGIC_SET;
|
|
is_weak = ((magic & MAGIC_WEAK) != 0);
|
|
obj = js_create_from_ctor(ctx, new_target, JS_CLASS_MAP + magic);
|
|
if (JS_IsException(obj))
|
|
return JS_EXCEPTION;
|
|
s = js_mallocz(ctx, sizeof(*s));
|
|
if (!s)
|
|
goto fail;
|
|
init_list_head(&s->records);
|
|
s->is_weak = is_weak;
|
|
JS_SetOpaque(obj, s);
|
|
s->hash_size = 1;
|
|
s->hash_table = js_malloc(ctx, sizeof(s->hash_table[0]) * s->hash_size);
|
|
if (!s->hash_table)
|
|
goto fail;
|
|
init_list_head(&s->hash_table[0]);
|
|
s->record_count_threshold = 4;
|
|
arr = JS_UNDEFINED;
|
|
if (argc > 0)
|
|
arr = argv[0];
|
|
if (!JS_IsUndefined(arr) && !JS_IsNull(arr)) {
|
|
JSValue item, ret;
|
|
BOOL done;
|
|
adder = JS_GetProperty(ctx, obj, is_set ? JS_ATOM_add : JS_ATOM_set);
|
|
if (JS_IsException(adder))
|
|
goto fail;
|
|
if (!JS_IsFunction(ctx, adder)) {
|
|
JS_ThrowTypeError(ctx, "set/add is not a function");
|
|
goto fail;
|
|
}
|
|
iter = JS_GetIterator(ctx, arr, FALSE);
|
|
if (JS_IsException(iter))
|
|
goto fail;
|
|
next_method = JS_GetProperty(ctx, iter, JS_ATOM_next);
|
|
if (JS_IsException(next_method))
|
|
goto fail;
|
|
for(;;) {
|
|
item = JS_IteratorNext(ctx, iter, next_method, 0, NULL, &done);
|
|
if (JS_IsException(item))
|
|
goto fail;
|
|
if (done) {
|
|
JS_FreeValue(ctx, item);
|
|
break;
|
|
}
|
|
if (is_set) {
|
|
ret = JS_Call(ctx, adder, obj, 1, (JSValueConst *)&item);
|
|
if (JS_IsException(ret)) {
|
|
JS_FreeValue(ctx, item);
|
|
goto fail;
|
|
}
|
|
} else {
|
|
JSValue key, value;
|
|
JSValueConst args[2];
|
|
key = JS_UNDEFINED;
|
|
value = JS_UNDEFINED;
|
|
if (!JS_IsObject(item)) {
|
|
JS_ThrowTypeErrorNotAnObject(ctx);
|
|
goto fail1;
|
|
}
|
|
key = JS_GetPropertyUint32(ctx, item, 0);
|
|
if (JS_IsException(key))
|
|
goto fail1;
|
|
value = JS_GetPropertyUint32(ctx, item, 1);
|
|
if (JS_IsException(value))
|
|
goto fail1;
|
|
args[0] = key;
|
|
args[1] = value;
|
|
ret = JS_Call(ctx, adder, obj, 2, args);
|
|
if (JS_IsException(ret)) {
|
|
fail1:
|
|
JS_FreeValue(ctx, item);
|
|
JS_FreeValue(ctx, key);
|
|
JS_FreeValue(ctx, value);
|
|
goto fail;
|
|
}
|
|
JS_FreeValue(ctx, key);
|
|
JS_FreeValue(ctx, value);
|
|
}
|
|
JS_FreeValue(ctx, ret);
|
|
JS_FreeValue(ctx, item);
|
|
}
|
|
JS_FreeValue(ctx, next_method);
|
|
JS_FreeValue(ctx, iter);
|
|
JS_FreeValue(ctx, adder);
|
|
}
|
|
return obj;
|
|
fail:
|
|
if (JS_IsObject(iter)) {
|
|
/* close the iterator object, preserving pending exception */
|
|
JS_IteratorClose(ctx, iter, TRUE);
|
|
}
|
|
JS_FreeValue(ctx, next_method);
|
|
JS_FreeValue(ctx, iter);
|
|
JS_FreeValue(ctx, adder);
|
|
JS_FreeValue(ctx, obj);
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
/* XXX: could normalize strings to speed up comparison */
|
|
static JSValueConst map_normalize_key(JSContext *ctx, JSValueConst key)
|
|
{
|
|
uint32_t tag = JS_VALUE_GET_TAG(key);
|
|
/* convert -0.0 to +0.0 */
|
|
if (JS_TAG_IS_FLOAT64(tag) && JS_VALUE_GET_FLOAT64(key) == 0.0) {
|
|
key = JS_NewInt32(ctx, 0);
|
|
}
|
|
return key;
|
|
}
|
|
|
|
/* XXX: better hash ? */
|
|
static uint32_t map_hash_key(JSContext *ctx, JSValueConst key)
|
|
{
|
|
uint32_t tag = JS_VALUE_GET_NORM_TAG(key);
|
|
uint32_t h;
|
|
double d;
|
|
JSFloat64Union u;
|
|
switch(tag) {
|
|
case JS_TAG_BOOL:
|
|
h = JS_VALUE_GET_INT(key);
|
|
break;
|
|
case JS_TAG_STRING:
|
|
h = hash_string(JS_VALUE_GET_STRING(key), 0);
|
|
break;
|
|
case JS_TAG_OBJECT:
|
|
case JS_TAG_SYMBOL:
|
|
h = (uintptr_t)JS_VALUE_GET_PTR(key) * 3163;
|
|
break;
|
|
case JS_TAG_INT:
|
|
d = JS_VALUE_GET_INT(key) * 3163;
|
|
goto hash_float64;
|
|
case JS_TAG_FLOAT64:
|
|
d = JS_VALUE_GET_FLOAT64(key);
|
|
/* normalize the NaN */
|
|
if (isnan(d))
|
|
d = JS_FLOAT64_NAN;
|
|
hash_float64:
|
|
u.d = d;
|
|
h = (u.u32[0] ^ u.u32[1]) * 3163;
|
|
break;
|
|
default:
|
|
h = 0; /* XXX: bignum support */
|
|
break;
|
|
}
|
|
h ^= tag;
|
|
return h;
|
|
}
|
|
|
|
static JSMapRecord *map_find_record(JSContext *ctx, JSMapState *s,
|
|
JSValueConst key)
|
|
{
|
|
struct list_head *el;
|
|
JSMapRecord *mr;
|
|
uint32_t h;
|
|
h = map_hash_key(ctx, key) & (s->hash_size - 1);
|
|
list_for_each(el, &s->hash_table[h]) {
|
|
mr = list_entry(el, JSMapRecord, hash_link);
|
|
if (js_same_value_zero(ctx, mr->key, key))
|
|
return mr;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void map_hash_resize(JSContext *ctx, JSMapState *s)
|
|
{
|
|
uint32_t new_hash_size, i, h;
|
|
size_t slack;
|
|
struct list_head *new_hash_table, *el;
|
|
JSMapRecord *mr;
|
|
/* XXX: no reporting of memory allocation failure */
|
|
if (s->hash_size == 1)
|
|
new_hash_size = 4;
|
|
else
|
|
new_hash_size = s->hash_size * 2;
|
|
new_hash_table = js_realloc2(ctx, s->hash_table,
|
|
sizeof(new_hash_table[0]) * new_hash_size, &slack);
|
|
if (!new_hash_table)
|
|
return;
|
|
new_hash_size += slack / sizeof(*new_hash_table);
|
|
for(i = 0; i < new_hash_size; i++)
|
|
init_list_head(&new_hash_table[i]);
|
|
list_for_each(el, &s->records) {
|
|
mr = list_entry(el, JSMapRecord, link);
|
|
if (!mr->empty) {
|
|
h = map_hash_key(ctx, mr->key) & (new_hash_size - 1);
|
|
list_add_tail(&mr->hash_link, &new_hash_table[h]);
|
|
}
|
|
}
|
|
s->hash_table = new_hash_table;
|
|
s->hash_size = new_hash_size;
|
|
s->record_count_threshold = new_hash_size * 2;
|
|
}
|
|
|
|
static JSMapRecord *map_add_record(JSContext *ctx, JSMapState *s,
|
|
JSValueConst key)
|
|
{
|
|
uint32_t h;
|
|
JSMapRecord *mr;
|
|
mr = js_malloc(ctx, sizeof(*mr));
|
|
if (!mr)
|
|
return NULL;
|
|
mr->ref_count = 1;
|
|
mr->map = s;
|
|
mr->empty = FALSE;
|
|
if (s->is_weak) {
|
|
JSObject *p = JS_VALUE_GET_OBJ(key);
|
|
/* Add the weak reference */
|
|
mr->next_weak_ref = p->first_weak_ref;
|
|
p->first_weak_ref = mr;
|
|
} else {
|
|
JS_DupValue(ctx, key);
|
|
}
|
|
mr->key = (JSValue)key;
|
|
h = map_hash_key(ctx, key) & (s->hash_size - 1);
|
|
list_add_tail(&mr->hash_link, &s->hash_table[h]);
|
|
list_add_tail(&mr->link, &s->records);
|
|
s->record_count++;
|
|
if (s->record_count >= s->record_count_threshold) {
|
|
map_hash_resize(ctx, s);
|
|
}
|
|
return mr;
|
|
}
|
|
|
|
/* Remove the weak reference from the object weak
|
|
reference list. we don't use a doubly linked list to
|
|
save space, assuming a given object has few weak
|
|
references to it */
|
|
static void delete_weak_ref(JSRuntime *rt, JSMapRecord *mr)
|
|
{
|
|
JSMapRecord **pmr, *mr1;
|
|
JSObject *p;
|
|
p = JS_VALUE_GET_OBJ(mr->key);
|
|
pmr = &p->first_weak_ref;
|
|
for(;;) {
|
|
mr1 = *pmr;
|
|
assert(mr1 != NULL);
|
|
if (mr1 == mr)
|
|
break;
|
|
pmr = &mr1->next_weak_ref;
|
|
}
|
|
*pmr = mr1->next_weak_ref;
|
|
}
|
|
|
|
static void map_delete_record(JSRuntime *rt, JSMapState *s, JSMapRecord *mr)
|
|
{
|
|
if (mr->empty)
|
|
return;
|
|
list_del(&mr->hash_link);
|
|
if (s->is_weak) {
|
|
delete_weak_ref(rt, mr);
|
|
} else {
|
|
JS_FreeValueRT(rt, mr->key);
|
|
}
|
|
JS_FreeValueRT(rt, mr->value);
|
|
if (--mr->ref_count == 0) {
|
|
list_del(&mr->link);
|
|
js_free_rt(rt, mr);
|
|
} else {
|
|
/* keep a zombie record for iterators */
|
|
mr->empty = TRUE;
|
|
mr->key = JS_UNDEFINED;
|
|
mr->value = JS_UNDEFINED;
|
|
}
|
|
s->record_count--;
|
|
}
|
|
|
|
static void map_decref_record(JSRuntime *rt, JSMapRecord *mr)
|
|
{
|
|
if (--mr->ref_count == 0) {
|
|
/* the record can be safely removed */
|
|
assert(mr->empty);
|
|
list_del(&mr->link);
|
|
js_free_rt(rt, mr);
|
|
}
|
|
}
|
|
|
|
void reset_weak_ref(JSRuntime *rt, JSObject *p)
|
|
{
|
|
JSMapRecord *mr, *mr_next;
|
|
JSMapState *s;
|
|
(void)s;
|
|
/* first pass to remove the records from the WeakMap/WeakSet
|
|
lists */
|
|
for(mr = p->first_weak_ref; mr != NULL; mr = mr->next_weak_ref) {
|
|
s = mr->map;
|
|
assert(s->is_weak);
|
|
assert(!mr->empty); /* no iterator on WeakMap/WeakSet */
|
|
list_del(&mr->hash_link);
|
|
list_del(&mr->link);
|
|
}
|
|
/* second pass to free the values to avoid modifying the weak
|
|
reference list while traversing it. */
|
|
for(mr = p->first_weak_ref; mr != NULL; mr = mr_next) {
|
|
mr_next = mr->next_weak_ref;
|
|
JS_FreeValueRT(rt, mr->value);
|
|
js_free_rt(rt, mr);
|
|
}
|
|
p->first_weak_ref = NULL; /* fail safe */
|
|
}
|
|
|
|
static JSValue js_map_set(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv, int magic)
|
|
{
|
|
JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
|
|
JSMapRecord *mr;
|
|
JSValueConst key, value;
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
key = map_normalize_key(ctx, argv[0]);
|
|
if (s->is_weak && !JS_IsObject(key))
|
|
return JS_ThrowTypeErrorNotAnObject(ctx);
|
|
if (magic & MAGIC_SET)
|
|
value = JS_UNDEFINED;
|
|
else
|
|
value = argv[1];
|
|
mr = map_find_record(ctx, s, key);
|
|
if (mr) {
|
|
JS_FreeValue(ctx, mr->value);
|
|
} else {
|
|
mr = map_add_record(ctx, s, key);
|
|
if (!mr)
|
|
return JS_EXCEPTION;
|
|
}
|
|
mr->value = JS_DupValue(ctx, value);
|
|
return JS_DupValue(ctx, this_val);
|
|
}
|
|
|
|
static JSValue js_map_get(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv, int magic)
|
|
{
|
|
JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
|
|
JSMapRecord *mr;
|
|
JSValueConst key;
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
key = map_normalize_key(ctx, argv[0]);
|
|
mr = map_find_record(ctx, s, key);
|
|
if (!mr)
|
|
return JS_UNDEFINED;
|
|
else
|
|
return JS_DupValue(ctx, mr->value);
|
|
}
|
|
|
|
static JSValue js_map_has(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv, int magic)
|
|
{
|
|
JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
|
|
JSMapRecord *mr;
|
|
JSValueConst key;
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
key = map_normalize_key(ctx, argv[0]);
|
|
mr = map_find_record(ctx, s, key);
|
|
return JS_NewBool(ctx, (mr != NULL));
|
|
}
|
|
|
|
static JSValue js_map_delete(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv, int magic)
|
|
{
|
|
JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
|
|
JSMapRecord *mr;
|
|
JSValueConst key;
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
key = map_normalize_key(ctx, argv[0]);
|
|
mr = map_find_record(ctx, s, key);
|
|
if (!mr)
|
|
return JS_FALSE;
|
|
map_delete_record(ctx->rt, s, mr);
|
|
return JS_TRUE;
|
|
}
|
|
|
|
static JSValue js_map_clear(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv, int magic)
|
|
{
|
|
JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
|
|
struct list_head *el, *el1;
|
|
JSMapRecord *mr;
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
list_for_each_safe(el, el1, &s->records) {
|
|
mr = list_entry(el, JSMapRecord, link);
|
|
map_delete_record(ctx->rt, s, mr);
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
static JSValue js_map_get_size(JSContext *ctx, JSValueConst this_val, int magic)
|
|
{
|
|
JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
return JS_NewUint32(ctx, s->record_count);
|
|
}
|
|
|
|
static JSValue js_map_forEach(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv, int magic)
|
|
{
|
|
JSMapState *s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
|
|
JSValueConst func, this_arg;
|
|
JSValue ret, args[3];
|
|
struct list_head *el;
|
|
JSMapRecord *mr;
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
func = argv[0];
|
|
if (argc > 1)
|
|
this_arg = argv[1];
|
|
else
|
|
this_arg = JS_UNDEFINED;
|
|
if (check_function(ctx, func))
|
|
return JS_EXCEPTION;
|
|
/* Note: the list can be modified while traversing it, but the
|
|
current element is locked */
|
|
el = s->records.next;
|
|
while (el != &s->records) {
|
|
mr = list_entry(el, JSMapRecord, link);
|
|
if (!mr->empty) {
|
|
mr->ref_count++;
|
|
/* must duplicate in case the record is deleted */
|
|
args[1] = JS_DupValue(ctx, mr->key);
|
|
if (magic)
|
|
args[0] = args[1];
|
|
else
|
|
args[0] = JS_DupValue(ctx, mr->value);
|
|
args[2] = (JSValue)this_val;
|
|
ret = JS_Call(ctx, func, this_arg, 3, (JSValueConst *)args);
|
|
JS_FreeValue(ctx, args[0]);
|
|
if (!magic)
|
|
JS_FreeValue(ctx, args[1]);
|
|
el = el->next;
|
|
map_decref_record(ctx->rt, mr);
|
|
if (JS_IsException(ret))
|
|
return ret;
|
|
JS_FreeValue(ctx, ret);
|
|
} else {
|
|
el = el->next;
|
|
}
|
|
}
|
|
return JS_UNDEFINED;
|
|
}
|
|
|
|
void js_map_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSObject *p;
|
|
JSMapState *s;
|
|
struct list_head *el, *el1;
|
|
JSMapRecord *mr;
|
|
p = JS_VALUE_GET_OBJ(val);
|
|
s = p->u.map_state;
|
|
if (s) {
|
|
/* if the object is deleted we are sure that no iterator is
|
|
using it */
|
|
list_for_each_safe(el, el1, &s->records) {
|
|
mr = list_entry(el, JSMapRecord, link);
|
|
if (!mr->empty) {
|
|
if (s->is_weak)
|
|
delete_weak_ref(rt, mr);
|
|
else
|
|
JS_FreeValueRT(rt, mr->key);
|
|
JS_FreeValueRT(rt, mr->value);
|
|
}
|
|
js_free_rt(rt, mr);
|
|
}
|
|
js_free_rt(rt, s->hash_table);
|
|
js_free_rt(rt, s);
|
|
}
|
|
}
|
|
|
|
void js_map_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSMapState *s;
|
|
struct list_head *el;
|
|
JSMapRecord *mr;
|
|
s = p->u.map_state;
|
|
if (s) {
|
|
list_for_each(el, &s->records) {
|
|
mr = list_entry(el, JSMapRecord, link);
|
|
if (!s->is_weak)
|
|
JS_MarkValue(rt, mr->key, mark_func);
|
|
JS_MarkValue(rt, mr->value, mark_func);
|
|
}
|
|
}
|
|
}
|
|
|
|
void js_map_iterator_finalizer(JSRuntime *rt, JSValue val)
|
|
{
|
|
JSObject *p;
|
|
JSMapIteratorData *it;
|
|
p = JS_VALUE_GET_OBJ(val);
|
|
it = p->u.map_iterator_data;
|
|
if (it) {
|
|
/* During the GC sweep phase the Map finalizer may be
|
|
called before the Map iterator finalizer */
|
|
if (JS_IsLiveObject(rt, it->obj) && it->cur_record) {
|
|
map_decref_record(rt, it->cur_record);
|
|
}
|
|
JS_FreeValueRT(rt, it->obj);
|
|
js_free_rt(rt, it);
|
|
}
|
|
}
|
|
|
|
void js_map_iterator_mark(JSRuntime *rt, JSValueConst val, JS_MarkFunc *mark_func)
|
|
{
|
|
JSObject *p = JS_VALUE_GET_OBJ(val);
|
|
JSMapIteratorData *it;
|
|
it = p->u.map_iterator_data;
|
|
if (it) {
|
|
/* the record is already marked by the object */
|
|
JS_MarkValue(rt, it->obj, mark_func);
|
|
}
|
|
}
|
|
|
|
static JSValue js_create_map_iterator(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv, int magic)
|
|
{
|
|
JSIteratorKindEnum kind;
|
|
JSMapState *s;
|
|
JSMapIteratorData *it;
|
|
JSValue enum_obj;
|
|
kind = magic >> 2;
|
|
magic &= 3;
|
|
s = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP + magic);
|
|
if (!s)
|
|
return JS_EXCEPTION;
|
|
enum_obj = JS_NewObjectClass(ctx, JS_CLASS_MAP_ITERATOR + magic);
|
|
if (JS_IsException(enum_obj))
|
|
goto fail;
|
|
it = js_malloc(ctx, sizeof(*it));
|
|
if (!it) {
|
|
JS_FreeValue(ctx, enum_obj);
|
|
goto fail;
|
|
}
|
|
it->obj = JS_DupValue(ctx, this_val);
|
|
it->kind = kind;
|
|
it->cur_record = NULL;
|
|
JS_SetOpaque(enum_obj, it);
|
|
return enum_obj;
|
|
fail:
|
|
return JS_EXCEPTION;
|
|
}
|
|
|
|
static JSValue js_map_iterator_next(JSContext *ctx, JSValueConst this_val,
|
|
int argc, JSValueConst *argv,
|
|
BOOL *pdone, int magic)
|
|
{
|
|
JSMapIteratorData *it;
|
|
JSMapState *s;
|
|
JSMapRecord *mr;
|
|
struct list_head *el;
|
|
it = JS_GetOpaque2(ctx, this_val, JS_CLASS_MAP_ITERATOR + magic);
|
|
if (!it) {
|
|
*pdone = FALSE;
|
|
return JS_EXCEPTION;
|
|
}
|
|
if (JS_IsUndefined(it->obj))
|
|
goto done;
|
|
s = JS_GetOpaque(it->obj, JS_CLASS_MAP + magic);
|
|
assert(s != NULL);
|
|
if (!it->cur_record) {
|
|
el = s->records.next;
|
|
} else {
|
|
mr = it->cur_record;
|
|
el = mr->link.next;
|
|
map_decref_record(ctx->rt, mr); /* the record can be freed here */
|
|
}
|
|
for(;;) {
|
|
if (el == &s->records) {
|
|
/* no more record */
|
|
it->cur_record = NULL;
|
|
JS_FreeValue(ctx, it->obj);
|
|
it->obj = JS_UNDEFINED;
|
|
done:
|
|
/* end of enumeration */
|
|
*pdone = TRUE;
|
|
return JS_UNDEFINED;
|
|
}
|
|
mr = list_entry(el, JSMapRecord, link);
|
|
if (!mr->empty)
|
|
break;
|
|
/* get the next record */
|
|
el = mr->link.next;
|
|
}
|
|
/* lock the record so that it won't be freed */
|
|
mr->ref_count++;
|
|
it->cur_record = mr;
|
|
*pdone = FALSE;
|
|
if (it->kind == JS_ITERATOR_KIND_KEY) {
|
|
return JS_DupValue(ctx, mr->key);
|
|
} else {
|
|
JSValueConst args[2];
|
|
args[0] = mr->key;
|
|
if (magic)
|
|
args[1] = mr->key;
|
|
else
|
|
args[1] = mr->value;
|
|
if (it->kind == JS_ITERATOR_KIND_VALUE) {
|
|
return JS_DupValue(ctx, args[1]);
|
|
} else {
|
|
return js_create_array(ctx, 2, args);
|
|
}
|
|
}
|
|
}
|
|
|
|
static const JSCFunctionListEntry js_map_funcs[] = {
|
|
JS_CGETSET_DEF("[Symbol.species]", js_get_this, NULL ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_map_proto_funcs[] = {
|
|
JS_CFUNC_MAGIC_DEF("set", 2, js_map_set, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("get", 1, js_map_get, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("clear", 0, js_map_clear, 0 ),
|
|
JS_CGETSET_MAGIC_DEF("size", js_map_get_size, NULL, 0),
|
|
JS_CFUNC_MAGIC_DEF("forEach", 1, js_map_forEach, 0 ),
|
|
JS_CFUNC_MAGIC_DEF("values", 0, js_create_map_iterator, (JS_ITERATOR_KIND_VALUE << 2) | 0 ),
|
|
JS_CFUNC_MAGIC_DEF("keys", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY << 2) | 0 ),
|
|
JS_CFUNC_MAGIC_DEF("entries", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY_AND_VALUE << 2) | 0 ),
|
|
JS_ALIAS_DEF("[Symbol.iterator]", "entries" ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Map", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_map_iterator_proto_funcs[] = {
|
|
JS_ITERATOR_NEXT_DEF("next", 0, js_map_iterator_next, 0 ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Map Iterator", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_set_proto_funcs[] = {
|
|
JS_CFUNC_MAGIC_DEF("add", 1, js_map_set, MAGIC_SET ),
|
|
JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_SET ),
|
|
JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_SET ),
|
|
JS_CFUNC_MAGIC_DEF("clear", 0, js_map_clear, MAGIC_SET ),
|
|
JS_CGETSET_MAGIC_DEF("size", js_map_get_size, NULL, MAGIC_SET ),
|
|
JS_CFUNC_MAGIC_DEF("forEach", 1, js_map_forEach, MAGIC_SET ),
|
|
JS_CFUNC_MAGIC_DEF("values", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY << 2) | MAGIC_SET ),
|
|
JS_ALIAS_DEF("keys", "values" ),
|
|
JS_ALIAS_DEF("[Symbol.iterator]", "values" ),
|
|
JS_CFUNC_MAGIC_DEF("entries", 0, js_create_map_iterator, (JS_ITERATOR_KIND_KEY_AND_VALUE << 2) | MAGIC_SET ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Set", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_set_iterator_proto_funcs[] = {
|
|
JS_ITERATOR_NEXT_DEF("next", 0, js_map_iterator_next, MAGIC_SET ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "Set Iterator", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_weak_map_proto_funcs[] = {
|
|
JS_CFUNC_MAGIC_DEF("set", 2, js_map_set, MAGIC_WEAK ),
|
|
JS_CFUNC_MAGIC_DEF("get", 1, js_map_get, MAGIC_WEAK ),
|
|
JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_WEAK ),
|
|
JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_WEAK ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakMap", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry js_weak_set_proto_funcs[] = {
|
|
JS_CFUNC_MAGIC_DEF("add", 1, js_map_set, MAGIC_SET | MAGIC_WEAK ),
|
|
JS_CFUNC_MAGIC_DEF("has", 1, js_map_has, MAGIC_SET | MAGIC_WEAK ),
|
|
JS_CFUNC_MAGIC_DEF("delete", 1, js_map_delete, MAGIC_SET | MAGIC_WEAK ),
|
|
JS_PROP_STRING_DEF("[Symbol.toStringTag]", "WeakSet", JS_PROP_CONFIGURABLE ),
|
|
};
|
|
|
|
static const JSCFunctionListEntry * const js_map_proto_funcs_ptr[6] = {
|
|
js_map_proto_funcs,
|
|
js_set_proto_funcs,
|
|
js_weak_map_proto_funcs,
|
|
js_weak_set_proto_funcs,
|
|
js_map_iterator_proto_funcs,
|
|
js_set_iterator_proto_funcs,
|
|
};
|
|
|
|
static const uint8_t js_map_proto_funcs_count[6] = {
|
|
countof(js_map_proto_funcs),
|
|
countof(js_set_proto_funcs),
|
|
countof(js_weak_map_proto_funcs),
|
|
countof(js_weak_set_proto_funcs),
|
|
countof(js_map_iterator_proto_funcs),
|
|
countof(js_set_iterator_proto_funcs),
|
|
};
|
|
|
|
void JS_AddIntrinsicMapSet(JSContext *ctx)
|
|
{
|
|
int i;
|
|
JSValue obj1;
|
|
char buf[ATOM_GET_STR_BUF_SIZE];
|
|
for(i = 0; i < 4; i++) {
|
|
const char *name = JS_AtomGetStr(ctx, buf, sizeof(buf),
|
|
JS_ATOM_Map + i);
|
|
ctx->class_proto[JS_CLASS_MAP + i] = JS_NewObject(ctx);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_MAP + i],
|
|
js_map_proto_funcs_ptr[i],
|
|
js_map_proto_funcs_count[i]);
|
|
obj1 = JS_NewCFunctionMagic(ctx, js_map_constructor, name, 0,
|
|
JS_CFUNC_constructor_magic, i);
|
|
if (i < 2) {
|
|
JS_SetPropertyFunctionList(ctx, obj1, js_map_funcs,
|
|
countof(js_map_funcs));
|
|
}
|
|
JS_NewGlobalCConstructor2(ctx, obj1, name, ctx->class_proto[JS_CLASS_MAP + i]);
|
|
}
|
|
for(i = 0; i < 2; i++) {
|
|
ctx->class_proto[JS_CLASS_MAP_ITERATOR + i] =
|
|
JS_NewObjectProto(ctx, ctx->iterator_proto);
|
|
JS_SetPropertyFunctionList(ctx, ctx->class_proto[JS_CLASS_MAP_ITERATOR + i],
|
|
js_map_proto_funcs_ptr[i + 4],
|
|
js_map_proto_funcs_count[i + 4]);
|
|
}
|
|
}
|