jscript: Add Map.prototype.forEach implementation.
[wine.git] / dlls / jscript / set.c
blob8f355f4e4e315ee7bb1ac888a593b2a30ce1e9b1
1 /*
2 * Copyright 2021 Jacek Caban for CodeWeavers
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 #include <assert.h>
20 #include <math.h>
22 #include "jscript.h"
24 #include "wine/rbtree.h"
25 #include "wine/debug.h"
27 WINE_DEFAULT_DEBUG_CHANNEL(jscript);
29 typedef struct {
30 jsdisp_t dispex;
31 } SetInstance;
33 typedef struct {
34 jsdisp_t dispex;
35 struct wine_rb_tree map;
36 struct list entries;
37 size_t size;
38 } MapInstance;
40 static HRESULT Set_add(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
41 jsval_t *r)
43 FIXME("%p\n", jsthis);
44 return E_NOTIMPL;
47 static HRESULT Set_clear(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
48 jsval_t *r)
50 FIXME("%p\n", jsthis);
51 return E_NOTIMPL;
54 static HRESULT Set_delete(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
55 jsval_t *r)
57 FIXME("%p\n", jsthis);
58 return E_NOTIMPL;
61 static HRESULT Set_forEach(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
62 jsval_t *r)
64 FIXME("%p\n", jsthis);
65 return E_NOTIMPL;
68 static HRESULT Set_has(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
69 jsval_t *r)
71 FIXME("%p\n", jsthis);
72 return E_NOTIMPL;
75 static HRESULT Set_value(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
76 jsval_t *r)
78 FIXME("\n");
79 return E_NOTIMPL;
82 static const builtin_prop_t Set_props[] = {
83 {L"add", Set_add, PROPF_METHOD|1},
84 {L"clear", Set_clear, PROPF_METHOD},
85 {L"delete" , Set_delete, PROPF_METHOD|1},
86 {L"forEach", Set_forEach, PROPF_METHOD|1},
87 {L"has", Set_has, PROPF_METHOD|1},
90 static const builtin_info_t Set_prototype_info = {
91 JSCLASS_SET,
92 {NULL, Set_value, 0},
93 ARRAY_SIZE(Set_props),
94 Set_props,
95 NULL,
96 NULL
99 static const builtin_info_t Set_info = {
100 JSCLASS_SET,
101 {NULL, Set_value, 0},
102 0, NULL,
103 NULL,
104 NULL
107 static HRESULT Set_constructor(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
108 jsval_t *r)
110 SetInstance *set;
111 HRESULT hres;
113 switch(flags) {
114 case DISPATCH_CONSTRUCT:
115 TRACE("\n");
117 if(!(set = heap_alloc_zero(sizeof(*set))))
118 return E_OUTOFMEMORY;
120 hres = init_dispex(&set->dispex, ctx, &Set_info, ctx->set_prototype);
121 if(FAILED(hres))
122 return hres;
124 *r = jsval_obj(&set->dispex);
125 return S_OK;
127 default:
128 FIXME("unimplemented flags %x\n", flags);
129 return E_NOTIMPL;
133 struct jsval_map_entry {
134 struct wine_rb_entry entry;
135 jsval_t key;
136 jsval_t value;
139 * We need to maintain a list as well to support traversal in forEach.
140 * If the entry is removed while being processed by forEach, it's
141 * still kept in the list and released later, when it's safe.
143 struct list list_entry;
144 unsigned int ref;
145 BOOL deleted;
148 static int jsval_map_compare(const void *k, const struct wine_rb_entry *e)
150 const struct jsval_map_entry *entry = WINE_RB_ENTRY_VALUE(e, const struct jsval_map_entry, entry);
151 const jsval_t *key = k;
153 if(jsval_type(entry->key) != jsval_type(*key))
154 return (int)jsval_type(entry->key) - (int)jsval_type(*key);
156 switch(jsval_type(*key)) {
157 case JSV_UNDEFINED:
158 case JSV_NULL:
159 return 0;
160 case JSV_OBJECT:
161 if(get_object(*key) == get_object(entry->key)) return 0;
162 return get_object(*key) < get_object(entry->key) ? -1 : 1;
163 case JSV_STRING:
164 return jsstr_cmp(get_string(*key), get_string(entry->key));
165 case JSV_NUMBER:
166 if(get_number(*key) == get_number(entry->key)) return 0;
167 if(isnan(get_number(*key))) return isnan(get_number(entry->key)) ? 0 : -1;
168 if(isnan(get_number(entry->key))) return 1;
169 return get_number(*key) < get_number(entry->key) ? -1 : 1;
170 case JSV_BOOL:
171 if(get_bool(*key) == get_bool(entry->key)) return 0;
172 return get_bool(*key) ? 1 : -1;
173 default:
174 assert(0);
175 return 0;
179 static MapInstance *get_map_this(vdisp_t *jsthis)
181 if(!(jsthis->flags & VDISP_JSDISP) || !is_class(jsthis->u.jsdisp, JSCLASS_MAP)) {
182 WARN("not a Map object passed as 'this'\n");
183 return NULL;
186 return CONTAINING_RECORD(jsthis->u.jsdisp, MapInstance, dispex);
189 static struct jsval_map_entry *get_map_entry(MapInstance *map, jsval_t key)
191 struct wine_rb_entry *entry;
192 if(!(entry = wine_rb_get(&map->map, &key))) return NULL;
193 return CONTAINING_RECORD(entry, struct jsval_map_entry, entry);
196 static void grab_map_entry(struct jsval_map_entry *entry)
198 entry->ref++;
201 static void release_map_entry(struct jsval_map_entry *entry)
203 if(--entry->ref) return;
204 jsval_release(entry->key);
205 jsval_release(entry->value);
206 list_remove(&entry->list_entry);
207 heap_free(entry);
210 static void delete_map_entry(MapInstance *map, struct jsval_map_entry *entry)
212 map->size--;
213 wine_rb_remove(&map->map, &entry->entry);
214 entry->deleted = TRUE;
215 release_map_entry(entry);
218 static HRESULT Map_clear(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
219 jsval_t *r)
221 MapInstance *map;
223 if(!(map = get_map_this(jsthis))) return JS_E_MAP_EXPECTED;
225 TRACE("%p\n", map);
227 while(!list_empty(&map->entries)) {
228 struct jsval_map_entry *entry = LIST_ENTRY(list_head(&map->entries), struct jsval_map_entry, list_entry);
229 delete_map_entry(map, entry);
232 if(r) *r = jsval_undefined();
233 return S_OK;
236 static HRESULT Map_delete(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
237 jsval_t *r)
239 jsval_t key = argc >= 1 ? argv[0] : jsval_undefined();
240 struct jsval_map_entry *entry;
241 MapInstance *map;
243 if(!(map = get_map_this(jsthis))) return JS_E_MAP_EXPECTED;
245 TRACE("%p (%s)\n", map, debugstr_jsval(key));
247 if((entry = get_map_entry(map, key))) delete_map_entry(map, entry);
248 if(r) *r = jsval_bool(!!entry);
249 return S_OK;
252 static HRESULT Map_forEach(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
253 jsval_t *r)
255 jsval_t callback = argc ? argv[0] : jsval_undefined();
256 struct jsval_map_entry *entry;
257 MapInstance *map;
258 HRESULT hres;
260 if(!(map = get_map_this(jsthis))) return JS_E_MAP_EXPECTED;
262 TRACE("%p (%s)\n", map, debugstr_jsval(argc >= 1 ? argv[0] : jsval_undefined()));
264 if(!is_object_instance(callback) || !get_object(callback)) {
265 FIXME("invalid callback %s\n", debugstr_jsval(callback));
266 return E_FAIL;
269 if(argc > 1) {
270 FIXME("Unsupported argument\n");
271 return E_NOTIMPL;
274 LIST_FOR_EACH_ENTRY(entry, &map->entries, struct jsval_map_entry, list_entry) {
275 jsval_t args[2], v;
276 if(entry->deleted)
277 continue;
278 args[0] = entry->value;
279 args[1] = entry->key;
280 grab_map_entry(entry);
281 hres = disp_call_value(ctx, get_object(argv[0]), NULL, DISPATCH_METHOD,
282 ARRAY_SIZE(args), args, &v);
283 release_map_entry(entry);
284 if(FAILED(hres))
285 return hres;
286 jsval_release(v);
289 if(r) *r = jsval_undefined();
290 return S_OK;
293 static HRESULT Map_get(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
294 jsval_t *r)
296 jsval_t key = argc >= 1 ? argv[0] : jsval_undefined();
297 struct jsval_map_entry *entry;
298 MapInstance *map;
300 if(!(map = get_map_this(jsthis))) return JS_E_MAP_EXPECTED;
302 TRACE("%p (%s)\n", map, debugstr_jsval(key));
304 if(!(entry = get_map_entry(map, key))) {
305 if(r) *r = jsval_undefined();
306 return S_OK;
309 return r ? jsval_copy(entry->value, r) : S_OK;
312 static HRESULT Map_set(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
313 jsval_t *r)
315 jsval_t key = argc >= 1 ? argv[0] : jsval_undefined();
316 jsval_t value = argc >= 2 ? argv[1] : jsval_undefined();
317 struct jsval_map_entry *entry;
318 MapInstance *map;
319 HRESULT hres;
321 if(!(map = get_map_this(jsthis))) return JS_E_MAP_EXPECTED;
323 TRACE("%p (%s %s)\n", map, debugstr_jsval(key), debugstr_jsval(value));
325 if((entry = get_map_entry(map, key))) {
326 jsval_t val;
327 hres = jsval_copy(value, &val);
328 if(FAILED(hres))
329 return hres;
331 jsval_release(entry->value);
332 entry->value = val;
333 }else {
334 if(!(entry = heap_alloc_zero(sizeof(*entry)))) return E_OUTOFMEMORY;
336 hres = jsval_copy(key, &entry->key);
337 if(SUCCEEDED(hres)) {
338 hres = jsval_copy(value, &entry->value);
339 if(FAILED(hres))
340 jsval_release(entry->key);
342 if(FAILED(hres)) {
343 heap_free(entry);
344 return hres;
346 grab_map_entry(entry);
347 wine_rb_put(&map->map, &entry->key, &entry->entry);
348 list_add_tail(&map->entries, &entry->list_entry);
349 map->size++;
352 if(r) *r = jsval_undefined();
353 return S_OK;
356 static HRESULT Map_has(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
357 jsval_t *r)
359 jsval_t key = argc >= 1 ? argv[0] : jsval_undefined();
360 struct jsval_map_entry *entry;
361 MapInstance *map;
363 if(!(map = get_map_this(jsthis))) return JS_E_MAP_EXPECTED;
365 TRACE("%p (%s)\n", map, debugstr_jsval(key));
367 entry = get_map_entry(map, key);
368 if(r) *r = jsval_bool(!!entry);
369 return S_OK;
372 static HRESULT Map_get_size(script_ctx_t *ctx, jsdisp_t *jsthis, jsval_t *r)
374 MapInstance *map = (MapInstance*)jsthis;
376 TRACE("%p\n", map);
378 *r = jsval_number(map->size);
379 return S_OK;
382 static HRESULT Map_value(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
383 jsval_t *r)
385 FIXME("\n");
386 return E_NOTIMPL;
389 static void Map_destructor(jsdisp_t *dispex)
391 MapInstance *map = (MapInstance*)dispex;
393 while(!list_empty(&map->entries)) {
394 struct jsval_map_entry *entry = LIST_ENTRY(list_head(&map->entries),
395 struct jsval_map_entry, list_entry);
396 assert(!entry->deleted);
397 release_map_entry(entry);
400 heap_free(map);
402 static const builtin_prop_t Map_prototype_props[] = {
403 {L"clear", Map_clear, PROPF_METHOD},
404 {L"delete" , Map_delete, PROPF_METHOD|1},
405 {L"forEach", Map_forEach, PROPF_METHOD|1},
406 {L"get", Map_get, PROPF_METHOD|1},
407 {L"has", Map_has, PROPF_METHOD|1},
408 {L"set", Map_set, PROPF_METHOD|2},
411 static const builtin_prop_t Map_props[] = {
412 {L"size", NULL,0, Map_get_size, builtin_set_const},
415 static const builtin_info_t Map_prototype_info = {
416 JSCLASS_OBJECT,
417 {NULL, Map_value, 0},
418 ARRAY_SIZE(Map_prototype_props),
419 Map_prototype_props,
420 NULL,
421 NULL
424 static const builtin_info_t Map_info = {
425 JSCLASS_MAP,
426 {NULL, Map_value, 0},
427 ARRAY_SIZE(Map_props),
428 Map_props,
429 Map_destructor,
430 NULL
433 static HRESULT Map_constructor(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv,
434 jsval_t *r)
436 MapInstance *map;
437 HRESULT hres;
439 switch(flags) {
440 case DISPATCH_CONSTRUCT:
441 TRACE("\n");
443 if(!(map = heap_alloc_zero(sizeof(*map))))
444 return E_OUTOFMEMORY;
446 hres = init_dispex(&map->dispex, ctx, &Map_info, ctx->map_prototype);
447 if(FAILED(hres))
448 return hres;
450 wine_rb_init(&map->map, jsval_map_compare);
451 list_init(&map->entries);
452 *r = jsval_obj(&map->dispex);
453 return S_OK;
455 default:
456 FIXME("unimplemented flags %x\n", flags);
457 return E_NOTIMPL;
461 HRESULT init_set_constructor(script_ctx_t *ctx)
463 jsdisp_t *constructor;
464 HRESULT hres;
466 if(ctx->version < SCRIPTLANGUAGEVERSION_ES6)
467 return S_OK;
469 hres = create_dispex(ctx, &Set_prototype_info, ctx->object_prototype, &ctx->set_prototype);
470 if(FAILED(hres))
471 return hres;
473 hres = create_builtin_constructor(ctx, Set_constructor, L"Set", NULL,
474 PROPF_CONSTR, ctx->set_prototype, &constructor);
475 if(FAILED(hres))
476 return hres;
478 hres = jsdisp_define_data_property(ctx->global, L"Set", PROPF_WRITABLE,
479 jsval_obj(constructor));
480 jsdisp_release(constructor);
481 if(FAILED(hres))
482 return hres;
484 hres = create_dispex(ctx, &Map_prototype_info, ctx->object_prototype, &ctx->map_prototype);
485 if(FAILED(hres))
486 return hres;
488 hres = create_builtin_constructor(ctx, Map_constructor, L"Map", NULL,
489 PROPF_CONSTR, ctx->map_prototype, &constructor);
490 if(FAILED(hres))
491 return hres;
493 hres = jsdisp_define_data_property(ctx->global, L"Map", PROPF_WRITABLE,
494 jsval_obj(constructor));
495 jsdisp_release(constructor);
496 return hres;