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
24 #include "wine/rbtree.h"
25 #include "wine/debug.h"
27 WINE_DEFAULT_DEBUG_CHANNEL(jscript
);
31 struct wine_rb_tree map
;
36 struct jsval_map_entry
{
37 struct wine_rb_entry entry
;
42 * We need to maintain a list as well to support traversal in forEach.
43 * If the entry is removed while being processed by forEach, it's
44 * still kept in the list and released later, when it's safe.
46 struct list list_entry
;
51 static int jsval_map_compare(const void *k
, const struct wine_rb_entry
*e
)
53 const struct jsval_map_entry
*entry
= WINE_RB_ENTRY_VALUE(e
, const struct jsval_map_entry
, entry
);
54 const jsval_t
*key
= k
;
60 if(jsval_type(entry
->key
) != jsval_type(*key
))
61 return (int)jsval_type(entry
->key
) - (int)jsval_type(*key
);
63 switch(jsval_type(*key
)) {
68 if(get_object(*key
) == get_object(entry
->key
)) return 0;
69 return get_object(*key
) < get_object(entry
->key
) ? -1 : 1;
71 return jsstr_cmp(get_string(*key
), get_string(entry
->key
));
73 if(isnan(get_number(*key
))) return isnan(get_number(entry
->key
)) ? 0 : -1;
74 if(isnan(get_number(entry
->key
))) return 1;
76 /* native treats -0 differently than 0, so need to compare bitwise */
77 bits1
.d
= get_number(*key
);
78 bits2
.d
= get_number(entry
->key
);
79 return (bits1
.n
== bits2
.n
) ? 0 : (bits1
.n
< bits2
.n
? -1 : 1);
81 if(get_bool(*key
) == get_bool(entry
->key
)) return 0;
82 return get_bool(*key
) ? 1 : -1;
89 static HRESULT
get_map_this(script_ctx_t
*ctx
, jsval_t vthis
, MapInstance
**ret
)
93 if(!is_object_instance(vthis
))
94 return JS_E_OBJECT_EXPECTED
;
95 if(!(jsdisp
= to_jsdisp(get_object(vthis
))) || !is_class(jsdisp
, JSCLASS_MAP
)) {
96 WARN("not a Map object passed as 'this'\n");
97 return throw_error(ctx
, JS_E_WRONG_THIS
, L
"Map");
100 *ret
= CONTAINING_RECORD(jsdisp
, MapInstance
, dispex
);
104 static HRESULT
get_set_this(script_ctx_t
*ctx
, jsval_t vthis
, MapInstance
**ret
)
108 if(!is_object_instance(vthis
))
109 return JS_E_OBJECT_EXPECTED
;
110 if(!(jsdisp
= to_jsdisp(get_object(vthis
))) || !is_class(jsdisp
, JSCLASS_SET
)) {
111 WARN("not a Set object passed as 'this'\n");
112 return throw_error(ctx
, JS_E_WRONG_THIS
, L
"Set");
115 *ret
= CONTAINING_RECORD(jsdisp
, MapInstance
, dispex
);
119 static struct jsval_map_entry
*get_map_entry(MapInstance
*map
, jsval_t key
)
121 struct wine_rb_entry
*entry
;
122 if(!(entry
= wine_rb_get(&map
->map
, &key
))) return NULL
;
123 return CONTAINING_RECORD(entry
, struct jsval_map_entry
, entry
);
126 static void grab_map_entry(struct jsval_map_entry
*entry
)
131 static void release_map_entry(struct jsval_map_entry
*entry
)
133 if(--entry
->ref
) return;
134 jsval_release(entry
->key
);
135 jsval_release(entry
->value
);
136 list_remove(&entry
->list_entry
);
140 static void delete_map_entry(MapInstance
*map
, struct jsval_map_entry
*entry
)
143 wine_rb_remove(&map
->map
, &entry
->entry
);
144 entry
->deleted
= TRUE
;
145 release_map_entry(entry
);
148 static HRESULT
set_map_entry(MapInstance
*map
, jsval_t key
, jsval_t value
, jsval_t
*r
)
150 struct jsval_map_entry
*entry
;
153 if((entry
= get_map_entry(map
, key
))) {
155 hres
= jsval_copy(value
, &val
);
159 jsval_release(entry
->value
);
162 if(!(entry
= calloc(1, sizeof(*entry
)))) return E_OUTOFMEMORY
;
164 hres
= jsval_copy(key
, &entry
->key
);
165 if(SUCCEEDED(hres
)) {
166 hres
= jsval_copy(value
, &entry
->value
);
168 jsval_release(entry
->key
);
174 grab_map_entry(entry
);
175 wine_rb_put(&map
->map
, &entry
->key
, &entry
->entry
);
176 list_add_tail(&map
->entries
, &entry
->list_entry
);
180 if(r
) *r
= jsval_undefined();
184 static HRESULT
iterate_map(MapInstance
*map
, script_ctx_t
*ctx
, unsigned argc
, jsval_t
*argv
, jsval_t
*r
)
186 struct list
*iter
= list_head(&map
->entries
);
187 jsval_t context_this
= jsval_undefined();
190 if(!argc
|| !is_object_instance(argv
[0])) {
191 FIXME("invalid callback %s\n", debugstr_jsval(argc
? argv
[0] : jsval_undefined()));
196 context_this
= argv
[1];
199 struct jsval_map_entry
*entry
= LIST_ENTRY(iter
, struct jsval_map_entry
, list_entry
);
203 iter
= list_next(&map
->entries
, iter
);
207 args
[0] = entry
->value
;
208 args
[1] = entry
->key
;
209 args
[2] = jsval_obj(&map
->dispex
);
210 grab_map_entry(entry
);
211 hres
= disp_call_value(ctx
, get_object(argv
[0]), context_this
, DISPATCH_METHOD
, ARRAY_SIZE(args
), args
, &v
);
212 iter
= list_next(&map
->entries
, iter
);
213 release_map_entry(entry
);
219 if(r
) *r
= jsval_undefined();
223 static HRESULT
Map_clear(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
226 struct jsval_map_entry
*entry
, *entry2
;
230 hres
= get_map_this(ctx
, vthis
, &map
);
236 LIST_FOR_EACH_ENTRY_SAFE(entry
, entry2
, &map
->entries
, struct jsval_map_entry
, list_entry
)
237 delete_map_entry(map
, entry
);
239 if(r
) *r
= jsval_undefined();
243 static HRESULT
Map_delete(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
246 jsval_t key
= argc
>= 1 ? argv
[0] : jsval_undefined();
247 struct jsval_map_entry
*entry
;
251 hres
= get_map_this(ctx
, vthis
, &map
);
255 TRACE("%p (%s)\n", map
, debugstr_jsval(key
));
257 if((entry
= get_map_entry(map
, key
))) delete_map_entry(map
, entry
);
258 if(r
) *r
= jsval_bool(!!entry
);
262 static HRESULT
Map_forEach(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
268 hres
= get_map_this(ctx
, vthis
, &map
);
272 TRACE("%p (%s)\n", map
, debugstr_jsval(argc
>= 1 ? argv
[0] : jsval_undefined()));
274 return iterate_map(map
, ctx
, argc
, argv
, r
);
277 static HRESULT
Map_get(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
280 jsval_t key
= argc
>= 1 ? argv
[0] : jsval_undefined();
281 struct jsval_map_entry
*entry
;
285 hres
= get_map_this(ctx
, vthis
, &map
);
289 TRACE("%p (%s)\n", map
, debugstr_jsval(key
));
291 if(!(entry
= get_map_entry(map
, key
))) {
292 if(r
) *r
= jsval_undefined();
296 return r
? jsval_copy(entry
->value
, r
) : S_OK
;
299 static HRESULT
Map_set(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
302 jsval_t key
= argc
>= 1 ? argv
[0] : jsval_undefined();
303 jsval_t value
= argc
>= 2 ? argv
[1] : jsval_undefined();
307 hres
= get_map_this(ctx
, vthis
, &map
);
311 TRACE("%p (%s %s)\n", map
, debugstr_jsval(key
), debugstr_jsval(value
));
313 return set_map_entry(map
, key
, value
, r
);
316 static HRESULT
Map_has(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
319 jsval_t key
= argc
>= 1 ? argv
[0] : jsval_undefined();
320 struct jsval_map_entry
*entry
;
324 hres
= get_map_this(ctx
, vthis
, &map
);
328 TRACE("%p (%s)\n", map
, debugstr_jsval(key
));
330 entry
= get_map_entry(map
, key
);
331 if(r
) *r
= jsval_bool(!!entry
);
335 static HRESULT
Map_get_size(script_ctx_t
*ctx
, jsdisp_t
*jsthis
, jsval_t
*r
)
337 MapInstance
*map
= (MapInstance
*)jsthis
;
341 *r
= jsval_number(map
->size
);
345 static HRESULT
Map_value(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
352 static void Map_destructor(jsdisp_t
*dispex
)
354 MapInstance
*map
= (MapInstance
*)dispex
;
356 while(!list_empty(&map
->entries
)) {
357 struct jsval_map_entry
*entry
= LIST_ENTRY(list_head(&map
->entries
),
358 struct jsval_map_entry
, list_entry
);
359 assert(!entry
->deleted
);
360 release_map_entry(entry
);
366 static HRESULT
Map_gc_traverse(struct gc_ctx
*gc_ctx
, enum gc_traverse_op op
, jsdisp_t
*dispex
)
368 MapInstance
*map
= (MapInstance
*)dispex
;
369 struct jsval_map_entry
*entry
, *entry2
;
372 if(op
== GC_TRAVERSE_UNLINK
) {
373 LIST_FOR_EACH_ENTRY_SAFE(entry
, entry2
, &map
->entries
, struct jsval_map_entry
, list_entry
)
374 release_map_entry(entry
);
375 wine_rb_destroy(&map
->map
, NULL
, NULL
);
379 LIST_FOR_EACH_ENTRY(entry
, &map
->entries
, struct jsval_map_entry
, list_entry
) {
380 hres
= gc_process_linked_val(gc_ctx
, op
, dispex
, &entry
->key
);
383 hres
= gc_process_linked_val(gc_ctx
, op
, dispex
, &entry
->value
);
390 static const builtin_prop_t Map_prototype_props
[] = {
391 {L
"clear", Map_clear
, PROPF_METHOD
},
392 {L
"delete" , Map_delete
, PROPF_METHOD
|1},
393 {L
"forEach", Map_forEach
, PROPF_METHOD
|1},
394 {L
"get", Map_get
, PROPF_METHOD
|1},
395 {L
"has", Map_has
, PROPF_METHOD
|1},
396 {L
"set", Map_set
, PROPF_METHOD
|2},
399 static const builtin_prop_t Map_props
[] = {
400 {L
"size", NULL
,0, Map_get_size
, builtin_set_const
},
403 static const builtin_info_t Map_prototype_info
= {
406 ARRAY_SIZE(Map_prototype_props
),
412 static const builtin_info_t Map_info
= {
415 ARRAY_SIZE(Map_props
),
425 static HRESULT
Map_constructor(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
432 case DISPATCH_CONSTRUCT
:
437 if(!(map
= calloc(1, sizeof(*map
))))
438 return E_OUTOFMEMORY
;
440 hres
= init_dispex(&map
->dispex
, ctx
, &Map_info
, ctx
->map_prototype
);
444 wine_rb_init(&map
->map
, jsval_map_compare
);
445 list_init(&map
->entries
);
446 *r
= jsval_obj(&map
->dispex
);
449 case DISPATCH_METHOD
:
450 return throw_error(ctx
, JS_E_WRONG_THIS
, L
"Map");
453 FIXME("unimplemented flags %x\n", flags
);
458 static HRESULT
Set_add(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
461 jsval_t key
= argc
? argv
[0] : jsval_undefined();
465 hres
= get_set_this(ctx
, vthis
, &set
);
469 TRACE("%p (%s)\n", set
, debugstr_jsval(key
));
471 return set_map_entry(set
, key
, key
, r
);
474 static HRESULT
Set_clear(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
477 struct jsval_map_entry
*entry
, *entry2
;
481 hres
= get_set_this(ctx
, vthis
, &set
);
487 LIST_FOR_EACH_ENTRY_SAFE(entry
, entry2
, &set
->entries
, struct jsval_map_entry
, list_entry
)
488 delete_map_entry(set
, entry
);
490 if(r
) *r
= jsval_undefined();
494 static HRESULT
Set_delete(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
497 jsval_t key
= argc
? argv
[0] : jsval_undefined();
498 struct jsval_map_entry
*entry
;
502 hres
= get_set_this(ctx
, vthis
, &set
);
506 TRACE("%p (%s)\n", set
, debugstr_jsval(key
));
508 if((entry
= get_map_entry(set
, key
))) delete_map_entry(set
, entry
);
509 if(r
) *r
= jsval_bool(!!entry
);
513 static HRESULT
Set_forEach(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
519 hres
= get_set_this(ctx
, vthis
, &set
);
523 TRACE("%p (%s)\n", set
, debugstr_jsval(argc
? argv
[0] : jsval_undefined()));
525 return iterate_map(set
, ctx
, argc
, argv
, r
);
528 static HRESULT
Set_has(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
531 jsval_t key
= argc
? argv
[0] : jsval_undefined();
532 struct jsval_map_entry
*entry
;
536 hres
= get_set_this(ctx
, vthis
, &set
);
540 TRACE("%p (%s)\n", set
, debugstr_jsval(key
));
542 entry
= get_map_entry(set
, key
);
543 if(r
) *r
= jsval_bool(!!entry
);
547 static HRESULT
Set_value(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
554 static const builtin_prop_t Set_prototype_props
[] = {
555 {L
"add", Set_add
, PROPF_METHOD
|1},
556 {L
"clear", Set_clear
, PROPF_METHOD
},
557 {L
"delete" , Set_delete
, PROPF_METHOD
|1},
558 {L
"forEach", Set_forEach
, PROPF_METHOD
|1},
559 {L
"has", Set_has
, PROPF_METHOD
|1},
562 static const builtin_info_t Set_prototype_info
= {
565 ARRAY_SIZE(Set_prototype_props
),
571 static const builtin_info_t Set_info
= {
574 ARRAY_SIZE(Map_props
),
584 static HRESULT
Set_constructor(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
591 case DISPATCH_CONSTRUCT
:
596 if(!(set
= calloc(1, sizeof(*set
))))
597 return E_OUTOFMEMORY
;
599 hres
= init_dispex(&set
->dispex
, ctx
, &Set_info
, ctx
->set_prototype
);
603 wine_rb_init(&set
->map
, jsval_map_compare
);
604 list_init(&set
->entries
);
605 *r
= jsval_obj(&set
->dispex
);
608 case DISPATCH_METHOD
:
609 return throw_error(ctx
, JS_E_WRONG_THIS
, L
"Set");
612 FIXME("unimplemented flags %x\n", flags
);
617 HRESULT
init_set_constructor(script_ctx_t
*ctx
)
619 jsdisp_t
*constructor
;
622 if(ctx
->version
< SCRIPTLANGUAGEVERSION_ES6
)
625 hres
= create_dispex(ctx
, &Set_prototype_info
, ctx
->object_prototype
, &ctx
->set_prototype
);
629 hres
= create_builtin_constructor(ctx
, Set_constructor
, L
"Set", NULL
,
630 PROPF_CONSTR
, ctx
->set_prototype
, &constructor
);
634 hres
= jsdisp_define_data_property(ctx
->global
, L
"Set", PROPF_WRITABLE
,
635 jsval_obj(constructor
));
636 jsdisp_release(constructor
);
640 hres
= create_dispex(ctx
, &Map_prototype_info
, ctx
->object_prototype
, &ctx
->map_prototype
);
644 hres
= create_builtin_constructor(ctx
, Map_constructor
, L
"Map", NULL
,
645 PROPF_CONSTR
, ctx
->map_prototype
, &constructor
);
649 hres
= jsdisp_define_data_property(ctx
->global
, L
"Map", PROPF_WRITABLE
,
650 jsval_obj(constructor
));
651 jsdisp_release(constructor
);