diff --git a/meson.build b/meson.build index 036f6ac..371585f 100644 --- a/meson.build +++ b/meson.build @@ -74,7 +74,7 @@ endif build_date = run_command('date', '+%Y-%m-%d', check: true).stdout().strip() version_conf = configuration_data() -version_conf.set('ANT_VERSION', '0.0.8.5') +version_conf.set('ANT_VERSION', '0.0.8.6') version_conf.set('ANT_GIT_HASH', git_hash) version_conf.set('ANT_BUILD_DATE', build_date) diff --git a/src/ant.c b/src/ant.c index 490597f..f8ca684 100644 --- a/src/ant.c +++ b/src/ant.c @@ -380,6 +380,8 @@ static jsval_t builtin_Date(struct js *js, jsval_t *args, int nargs); static jsval_t builtin_Date_now(struct js *js, jsval_t *args, int nargs); static jsval_t builtin_Map(struct js *js, jsval_t *args, int nargs); static jsval_t builtin_Set(struct js *js, jsval_t *args, int nargs); +static jsval_t builtin_WeakMap(struct js *js, jsval_t *args, int nargs); +static jsval_t builtin_WeakSet(struct js *js, jsval_t *args, int nargs); static jsval_t map_set(struct js *js, jsval_t *args, int nargs); static jsval_t map_get(struct js *js, jsval_t *args, int nargs); static jsval_t map_has(struct js *js, jsval_t *args, int nargs); @@ -397,6 +399,13 @@ static jsval_t set_clear(struct js *js, jsval_t *args, int nargs); static jsval_t set_size(struct js *js, jsval_t *args, int nargs); static jsval_t set_values(struct js *js, jsval_t *args, int nargs); static jsval_t set_forEach(struct js *js, jsval_t *args, int nargs); +static jsval_t weakmap_set(struct js *js, jsval_t *args, int nargs); +static jsval_t weakmap_get(struct js *js, jsval_t *args, int nargs); +static jsval_t weakmap_has(struct js *js, jsval_t *args, int nargs); +static jsval_t weakmap_delete(struct js *js, jsval_t *args, int nargs); +static jsval_t weakset_add(struct js *js, jsval_t *args, int nargs); +static jsval_t weakset_has(struct js *js, jsval_t *args, int nargs); +static jsval_t weakset_delete(struct js *js, jsval_t *args, int nargs); static jsval_t builtin_function_call(struct js *js, jsval_t *args, int nargs); static jsval_t builtin_function_apply(struct js *js, jsval_t *args, int nargs); static jsval_t builtin_function_bind(struct js *js, jsval_t *args, int nargs); @@ -10354,6 +10363,17 @@ typedef struct set_entry { UT_hash_handle hh; } set_entry_t; +typedef struct weakmap_entry { + jsval_t key_obj; + jsval_t value; + UT_hash_handle hh; +} weakmap_entry_t; + +typedef struct weakset_entry { + jsval_t value_obj; + UT_hash_handle hh; +} weakset_entry_t; + static const char* jsval_to_key(struct js *js, jsval_t val) { if (vtype(val) == T_STR) { jsoff_t len; @@ -10616,6 +10636,201 @@ static jsval_t set_size(struct js *js, jsval_t *args, int nargs) { return tov((double)count); } +static jsval_t builtin_WeakMap(struct js *js, jsval_t *args, int nargs) { + jsval_t wm_obj = mkobj(js, 0); + + jsval_t wm_proto = get_ctor_proto(js, "WeakMap", 7); + if (vtype(wm_proto) == T_OBJ) { + set_proto(js, wm_obj, wm_proto); + } + + weakmap_entry_t **wm_head = (weakmap_entry_t **)ANT_GC_MALLOC(sizeof(weakmap_entry_t *)); + if (!wm_head) return js_mkerr(js, "out of memory"); + *wm_head = NULL; + + jsval_t wm_ptr = mkval(T_NUM, (size_t)wm_head); + jsval_t wm_key = js_mkstr(js, "__weakmap", 9); + setprop(js, wm_obj, wm_key, wm_ptr); + + return wm_obj; +} + +static jsval_t builtin_WeakSet(struct js *js, jsval_t *args, int nargs) { + jsval_t ws_obj = mkobj(js, 0); + + jsval_t ws_proto = get_ctor_proto(js, "WeakSet", 7); + if (vtype(ws_proto) == T_OBJ) { + set_proto(js, ws_obj, ws_proto); + } + + weakset_entry_t **ws_head = (weakset_entry_t **)ANT_GC_MALLOC(sizeof(weakset_entry_t *)); + if (!ws_head) return js_mkerr(js, "out of memory"); + *ws_head = NULL; + + jsval_t ws_ptr = mkval(T_NUM, (size_t)ws_head); + jsval_t ws_key = js_mkstr(js, "__weakset", 9); + setprop(js, ws_obj, ws_key, ws_ptr); + + return ws_obj; +} + +static weakmap_entry_t** get_weakmap_from_obj(struct js *js, jsval_t obj) { + jsoff_t wm_off = lkp(js, obj, "__weakmap", 9); + if (wm_off == 0) return NULL; + jsval_t wm_val = resolveprop(js, mkval(T_PROP, wm_off)); + if (vtype(wm_val) != T_NUM) return NULL; + return (weakmap_entry_t**)(size_t)vdata(wm_val); +} + +static weakset_entry_t** get_weakset_from_obj(struct js *js, jsval_t obj) { + jsoff_t ws_off = lkp(js, obj, "__weakset", 9); + if (ws_off == 0) return NULL; + jsval_t ws_val = resolveprop(js, mkval(T_PROP, ws_off)); + if (vtype(ws_val) != T_NUM) return NULL; + return (weakset_entry_t**)(size_t)vdata(ws_val); +} + +static jsval_t weakmap_set(struct js *js, jsval_t *args, int nargs) { + if (nargs < 2) return js_mkerr(js, "WeakMap.set() requires 2 arguments"); + + jsval_t this_val = js->this_val; + weakmap_entry_t **wm_ptr = get_weakmap_from_obj(js, this_val); + if (!wm_ptr) return js_mkerr(js, "Invalid WeakMap object"); + + if (vtype(args[0]) != T_OBJ) { + return js_mkerr(js, "WeakMap key must be an object"); + } + + jsval_t key_obj = args[0]; + + weakmap_entry_t *entry; + HASH_FIND(hh, *wm_ptr, &key_obj, sizeof(jsval_t), entry); + if (entry) { + entry->value = args[1]; + } else { + entry = (weakmap_entry_t *)ANT_GC_MALLOC(sizeof(weakmap_entry_t)); + if (!entry) return js_mkerr(js, "out of memory"); + entry->key_obj = key_obj; + entry->value = args[1]; + HASH_ADD(hh, *wm_ptr, key_obj, sizeof(jsval_t), entry); + } + + return this_val; +} + +static jsval_t weakmap_get(struct js *js, jsval_t *args, int nargs) { + if (nargs < 1) return js_mkerr(js, "WeakMap.get() requires 1 argument"); + + jsval_t this_val = js->this_val; + weakmap_entry_t **wm_ptr = get_weakmap_from_obj(js, this_val); + if (!wm_ptr) return js_mkundef(); + + if (vtype(args[0]) != T_OBJ) return js_mkundef(); + + jsval_t key_obj = args[0]; + weakmap_entry_t *entry; + HASH_FIND(hh, *wm_ptr, &key_obj, sizeof(jsval_t), entry); + return entry ? entry->value : js_mkundef(); +} + +static jsval_t weakmap_has(struct js *js, jsval_t *args, int nargs) { + if (nargs < 1) return js_mkerr(js, "WeakMap.has() requires 1 argument"); + + jsval_t this_val = js->this_val; + weakmap_entry_t **wm_ptr = get_weakmap_from_obj(js, this_val); + if (!wm_ptr) return mkval(T_BOOL, 0); + + if (vtype(args[0]) != T_OBJ) return mkval(T_BOOL, 0); + + jsval_t key_obj = args[0]; + weakmap_entry_t *entry; + HASH_FIND(hh, *wm_ptr, &key_obj, sizeof(jsval_t), entry); + return mkval(T_BOOL, entry ? 1 : 0); +} + +static jsval_t weakmap_delete(struct js *js, jsval_t *args, int nargs) { + if (nargs < 1) return js_mkerr(js, "WeakMap.delete() requires 1 argument"); + + jsval_t this_val = js->this_val; + weakmap_entry_t **wm_ptr = get_weakmap_from_obj(js, this_val); + if (!wm_ptr) return mkval(T_BOOL, 0); + + if (vtype(args[0]) != T_OBJ) return mkval(T_BOOL, 0); + + jsval_t key_obj = args[0]; + weakmap_entry_t *entry; + HASH_FIND(hh, *wm_ptr, &key_obj, sizeof(jsval_t), entry); + if (entry) { + HASH_DEL(*wm_ptr, entry); + ANT_GC_FREE(entry); + return mkval(T_BOOL, 1); + } + return mkval(T_BOOL, 0); +} + +static jsval_t weakset_add(struct js *js, jsval_t *args, int nargs) { + if (nargs < 1) return js_mkerr(js, "WeakSet.add() requires 1 argument"); + + jsval_t this_val = js->this_val; + weakset_entry_t **ws_ptr = get_weakset_from_obj(js, this_val); + if (!ws_ptr) return js_mkerr(js, "Invalid WeakSet object"); + + if (vtype(args[0]) != T_OBJ) { + return js_mkerr(js, "WeakSet value must be an object"); + } + + jsval_t value_obj = args[0]; + + weakset_entry_t *entry; + HASH_FIND(hh, *ws_ptr, &value_obj, sizeof(jsval_t), entry); + + if (!entry) { + entry = (weakset_entry_t *)ANT_GC_MALLOC(sizeof(weakset_entry_t)); + if (!entry) return js_mkerr(js, "out of memory"); + entry->value_obj = value_obj; + HASH_ADD(hh, *ws_ptr, value_obj, sizeof(jsval_t), entry); + } + + return this_val; +} + +static jsval_t weakset_has(struct js *js, jsval_t *args, int nargs) { + if (nargs < 1) return js_mkerr(js, "WeakSet.has() requires 1 argument"); + + jsval_t this_val = js->this_val; + weakset_entry_t **ws_ptr = get_weakset_from_obj(js, this_val); + if (!ws_ptr) return mkval(T_BOOL, 0); + + if (vtype(args[0]) != T_OBJ) return mkval(T_BOOL, 0); + + jsval_t value_obj = args[0]; + weakset_entry_t *entry; + HASH_FIND(hh, *ws_ptr, &value_obj, sizeof(jsval_t), entry); + + return mkval(T_BOOL, entry ? 1 : 0); +} + +static jsval_t weakset_delete(struct js *js, jsval_t *args, int nargs) { + if (nargs < 1) return js_mkerr(js, "WeakSet.delete() requires 1 argument"); + + jsval_t this_val = js->this_val; + weakset_entry_t **ws_ptr = get_weakset_from_obj(js, this_val); + if (!ws_ptr) return mkval(T_BOOL, 0); + + if (vtype(args[0]) != T_OBJ) return mkval(T_BOOL, 0); + + jsval_t value_obj = args[0]; + weakset_entry_t *entry; + HASH_FIND(hh, *ws_ptr, &value_obj, sizeof(jsval_t), entry); + + if (entry) { + HASH_DEL(*ws_ptr, entry); + ANT_GC_FREE(entry); + return mkval(T_BOOL, 1); + } + return mkval(T_BOOL, 0); +} + struct js *js_create(void *buf, size_t len) { ANT_GC_INIT(); init_free_list(); @@ -10710,6 +10925,19 @@ struct js *js_create(void *buf, size_t len) { setprop(js, set_proto_obj, js_mkstr(js, "clear", 5), js_mkfun(set_clear)); setprop(js, set_proto_obj, js_mkstr(js, "size", 4), js_mkfun(set_size)); + jsval_t weakmap_proto = js_mkobj(js); + set_proto(js, weakmap_proto, object_proto); + setprop(js, weakmap_proto, js_mkstr(js, "set", 3), js_mkfun(weakmap_set)); + setprop(js, weakmap_proto, js_mkstr(js, "get", 3), js_mkfun(weakmap_get)); + setprop(js, weakmap_proto, js_mkstr(js, "has", 3), js_mkfun(weakmap_has)); + setprop(js, weakmap_proto, js_mkstr(js, "delete", 6), js_mkfun(weakmap_delete)); + + jsval_t weakset_proto = js_mkobj(js); + set_proto(js, weakset_proto, object_proto); + setprop(js, weakset_proto, js_mkstr(js, "add", 3), js_mkfun(weakset_add)); + setprop(js, weakset_proto, js_mkstr(js, "has", 3), js_mkfun(weakset_has)); + setprop(js, weakset_proto, js_mkstr(js, "delete", 6), js_mkfun(weakset_delete)); + jsval_t promise_proto = js_mkobj(js); set_proto(js, promise_proto, object_proto); setprop(js, promise_proto, js_mkstr(js, "then", 4), js_mkfun(builtin_promise_then)); @@ -10772,6 +11000,18 @@ struct js *js_create(void *buf, size_t len) { setprop(js, set_ctor_obj, js_mkstr(js, "prototype", 9), set_proto_obj); setprop(js, glob, js_mkstr(js, "Set", 3), mkval(T_FUNC, vdata(set_ctor_obj))); + jsval_t weakmap_ctor_obj = mkobj(js, 0); + set_proto(js, weakmap_ctor_obj, function_proto); + setprop(js, weakmap_ctor_obj, js_mkstr(js, "__native_func", 13), js_mkfun(builtin_WeakMap)); + setprop(js, weakmap_ctor_obj, js_mkstr(js, "prototype", 9), weakmap_proto); + setprop(js, glob, js_mkstr(js, "WeakMap", 7), mkval(T_FUNC, vdata(weakmap_ctor_obj))); + + jsval_t weakset_ctor_obj = mkobj(js, 0); + set_proto(js, weakset_ctor_obj, function_proto); + setprop(js, weakset_ctor_obj, js_mkstr(js, "__native_func", 13), js_mkfun(builtin_WeakSet)); + setprop(js, weakset_ctor_obj, js_mkstr(js, "prototype", 9), weakset_proto); + setprop(js, glob, js_mkstr(js, "WeakSet", 7), mkval(T_FUNC, vdata(weakset_ctor_obj))); + jsval_t err_ctor_obj = mkobj(js, 0); set_proto(js, err_ctor_obj, function_proto); setprop(js, err_ctor_obj, js_mkstr(js, "__native_func", 13), js_mkfun(builtin_Error)); diff --git a/tests/test_weakmap.js b/tests/test_weakmap.js new file mode 100644 index 0000000..88e1f09 --- /dev/null +++ b/tests/test_weakmap.js @@ -0,0 +1,72 @@ +// Test WeakMap functionality +console.log('=== WeakMap Tests ==='); + +const wm = new WeakMap(); + +// Test with object keys +const key1 = { id: 1 }; +const key2 = { id: 2 }; +const key3 = { id: 3 }; + +// Test set and get +wm.set(key1, 'value1'); +wm.set(key2, { data: 42 }); +wm.set(key3, true); + +console.log('Get key1:', wm.get(key1)); // Should be 'value1' +console.log('Get key2:', wm.get(key2)); // Should be { data: 42 } +console.log('Get key3:', wm.get(key3)); // Should be true + +// Test has() +console.log('Has key1:', wm.has(key1)); // Should be true +console.log('Has key2:', wm.has(key2)); // Should be true +const key4 = { id: 4 }; +console.log('Has key4 (not added):', wm.has(key4)); // Should be false + +// Test delete() +console.log('Delete key2:', wm.delete(key2)); // Should be true +console.log('Has key2 after delete:', wm.has(key2)); // Should be false +console.log('Get key2 after delete:', wm.get(key2)); // Should be undefined + +// Test that key1 and key3 are still there +console.log('Has key1 after delete key2:', wm.has(key1)); // Should be true +console.log('Has key3 after delete key2:', wm.has(key3)); // Should be true + +// Test overwrite +wm.set(key1, 'newvalue1'); +console.log('Get key1 after overwrite:', wm.get(key1)); // Should be 'newvalue1' + +// Test that primitive keys throw errors +try { + wm.set('string', 'value'); + console.log('ERROR: Should have thrown for string key'); +} catch (e) { + console.log('Correctly threw error for string key:', e.message); +} + +try { + wm.set(123, 'value'); + console.log('ERROR: Should have thrown for number key'); +} catch (e) { + console.log('Correctly threw error for number key:', e.message); +} + +try { + wm.set(null, 'value'); + console.log('ERROR: Should have thrown for null key'); +} catch (e) { + console.log('Correctly threw error for null key:', e.message); +} + +// Test get/has/delete with non-object keys (should return undefined/false) +console.log('Get with string key:', wm.get('string')); // Should be undefined +console.log('Has with number key:', wm.has(123)); // Should be false +console.log('Delete with boolean key:', wm.delete(true)); // Should be false + +// Test multiple WeakMaps +const wm2 = new WeakMap(); +wm2.set(key1, 'different value'); +console.log('Get key1 from wm:', wm.get(key1)); // Should be 'newvalue1' +console.log('Get key1 from wm2:', wm2.get(key1)); // Should be 'different value' + +console.log('WeakMap tests completed!'); diff --git a/tests/test_weakset.js b/tests/test_weakset.js new file mode 100644 index 0000000..e746c39 --- /dev/null +++ b/tests/test_weakset.js @@ -0,0 +1,85 @@ +// Test WeakSet functionality +console.log('=== WeakSet Tests ==='); + +const ws = new WeakSet(); + +// Test with object values +const obj1 = { id: 1 }; +const obj2 = { id: 2 }; +const obj3 = { id: 3 }; + +// Test add and has +ws.add(obj1); +ws.add(obj2); +ws.add(obj3); + +console.log('Has obj1:', ws.has(obj1)); // Should be true +console.log('Has obj2:', ws.has(obj2)); // Should be true +console.log('Has obj3:', ws.has(obj3)); // Should be true + +const obj4 = { id: 4 }; +console.log('Has obj4 (not added):', ws.has(obj4)); // Should be false + +// Test delete() +console.log('Delete obj2:', ws.delete(obj2)); // Should be true +console.log('Has obj2 after delete:', ws.has(obj2)); // Should be false + +// Test that obj1 and obj3 are still there +console.log('Has obj1 after delete obj2:', ws.has(obj1)); // Should be true +console.log('Has obj3 after delete obj2:', ws.has(obj3)); // Should be true + +// Test adding the same object twice (should not throw) +ws.add(obj1); +console.log('Has obj1 after re-adding:', ws.has(obj1)); // Should still be true + +// Test that primitive values throw errors +try { + ws.add('string'); + console.log('ERROR: Should have thrown for string value'); +} catch (e) { + console.log('Correctly threw error for string value:', e.message); +} + +try { + ws.add(123); + console.log('ERROR: Should have thrown for number value'); +} catch (e) { + console.log('Correctly threw error for number value:', e.message); +} + +try { + ws.add(null); + console.log('ERROR: Should have thrown for null value'); +} catch (e) { + console.log('Correctly threw error for null value:', e.message); +} + +try { + ws.add(undefined); + console.log('ERROR: Should have thrown for undefined value'); +} catch (e) { + console.log('Correctly threw error for undefined value:', e.message); +} + +// Test has/delete with non-object values (should return false) +console.log('Has with string:', ws.has('string')); // Should be false +console.log('Has with number:', ws.has(123)); // Should be false +console.log('Delete with boolean:', ws.delete(true)); // Should be false + +// Test multiple WeakSets with the same object +const ws2 = new WeakSet(); +ws2.add(obj1); +console.log('Has obj1 in ws:', ws.has(obj1)); // Should be true +console.log('Has obj1 in ws2:', ws2.has(obj1)); // Should be true + +// Delete from ws should not affect ws2 +ws.delete(obj1); +console.log('Has obj1 in ws after delete:', ws.has(obj1)); // Should be false +console.log('Has obj1 in ws2 after delete from ws:', ws2.has(obj1)); // Should be true + +// Test chaining +ws.add(obj1).add(obj2); +console.log('Has obj1 after chaining:', ws.has(obj1)); // Should be true +console.log('Has obj2 after chaining:', ws.has(obj2)); // Should be true + +console.log('WeakSet tests completed!');