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
= heap_alloc_zero(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 jsval_map_entry
*entry
;
187 IDispatch
*context_obj
= NULL
;
190 if(!argc
|| !is_object_instance(argv
[0])) {
191 FIXME("invalid callback %s\n", debugstr_jsval(argc
? argv
[0] : jsval_undefined()));
195 if(argc
> 1 && !is_undefined(argv
[1])) {
196 if(!is_object_instance(argv
[1])) {
197 FIXME("Unsupported context this %s\n", debugstr_jsval(argv
[1]));
200 context_obj
= get_object(argv
[1]);
203 LIST_FOR_EACH_ENTRY(entry
, &map
->entries
, struct jsval_map_entry
, list_entry
) {
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_obj
,
212 DISPATCH_METHOD
, ARRAY_SIZE(args
), args
, &v
);
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
,
229 hres
= get_map_this(ctx
, vthis
, &map
);
235 while(!list_empty(&map
->entries
)) {
236 struct jsval_map_entry
*entry
= LIST_ENTRY(list_head(&map
->entries
), struct jsval_map_entry
, list_entry
);
237 delete_map_entry(map
, entry
);
240 if(r
) *r
= jsval_undefined();
244 static HRESULT
Map_delete(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
247 jsval_t key
= argc
>= 1 ? argv
[0] : jsval_undefined();
248 struct jsval_map_entry
*entry
;
252 hres
= get_map_this(ctx
, vthis
, &map
);
256 TRACE("%p (%s)\n", map
, debugstr_jsval(key
));
258 if((entry
= get_map_entry(map
, key
))) delete_map_entry(map
, entry
);
259 if(r
) *r
= jsval_bool(!!entry
);
263 static HRESULT
Map_forEach(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
269 hres
= get_map_this(ctx
, vthis
, &map
);
273 TRACE("%p (%s)\n", map
, debugstr_jsval(argc
>= 1 ? argv
[0] : jsval_undefined()));
275 return iterate_map(map
, ctx
, argc
, argv
, r
);
278 static HRESULT
Map_get(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
281 jsval_t key
= argc
>= 1 ? argv
[0] : jsval_undefined();
282 struct jsval_map_entry
*entry
;
286 hres
= get_map_this(ctx
, vthis
, &map
);
290 TRACE("%p (%s)\n", map
, debugstr_jsval(key
));
292 if(!(entry
= get_map_entry(map
, key
))) {
293 if(r
) *r
= jsval_undefined();
297 return r
? jsval_copy(entry
->value
, r
) : S_OK
;
300 static HRESULT
Map_set(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
303 jsval_t key
= argc
>= 1 ? argv
[0] : jsval_undefined();
304 jsval_t value
= argc
>= 2 ? argv
[1] : jsval_undefined();
308 hres
= get_map_this(ctx
, vthis
, &map
);
312 TRACE("%p (%s %s)\n", map
, debugstr_jsval(key
), debugstr_jsval(value
));
314 return set_map_entry(map
, key
, value
, r
);
317 static HRESULT
Map_has(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
320 jsval_t key
= argc
>= 1 ? argv
[0] : jsval_undefined();
321 struct jsval_map_entry
*entry
;
325 hres
= get_map_this(ctx
, vthis
, &map
);
329 TRACE("%p (%s)\n", map
, debugstr_jsval(key
));
331 entry
= get_map_entry(map
, key
);
332 if(r
) *r
= jsval_bool(!!entry
);
336 static HRESULT
Map_get_size(script_ctx_t
*ctx
, jsdisp_t
*jsthis
, jsval_t
*r
)
338 MapInstance
*map
= (MapInstance
*)jsthis
;
342 *r
= jsval_number(map
->size
);
346 static HRESULT
Map_value(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
353 static void Map_destructor(jsdisp_t
*dispex
)
355 MapInstance
*map
= (MapInstance
*)dispex
;
357 while(!list_empty(&map
->entries
)) {
358 struct jsval_map_entry
*entry
= LIST_ENTRY(list_head(&map
->entries
),
359 struct jsval_map_entry
, list_entry
);
360 assert(!entry
->deleted
);
361 release_map_entry(entry
);
366 static const builtin_prop_t Map_prototype_props
[] = {
367 {L
"clear", Map_clear
, PROPF_METHOD
},
368 {L
"delete" , Map_delete
, PROPF_METHOD
|1},
369 {L
"forEach", Map_forEach
, PROPF_METHOD
|1},
370 {L
"get", Map_get
, PROPF_METHOD
|1},
371 {L
"has", Map_has
, PROPF_METHOD
|1},
372 {L
"set", Map_set
, PROPF_METHOD
|2},
375 static const builtin_prop_t Map_props
[] = {
376 {L
"size", NULL
,0, Map_get_size
, builtin_set_const
},
379 static const builtin_info_t Map_prototype_info
= {
382 ARRAY_SIZE(Map_prototype_props
),
388 static const builtin_info_t Map_info
= {
391 ARRAY_SIZE(Map_props
),
397 static HRESULT
Map_constructor(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
404 case DISPATCH_CONSTRUCT
:
409 if(!(map
= heap_alloc_zero(sizeof(*map
))))
410 return E_OUTOFMEMORY
;
412 hres
= init_dispex(&map
->dispex
, ctx
, &Map_info
, ctx
->map_prototype
);
416 wine_rb_init(&map
->map
, jsval_map_compare
);
417 list_init(&map
->entries
);
418 *r
= jsval_obj(&map
->dispex
);
422 FIXME("unimplemented flags %x\n", flags
);
427 static HRESULT
Set_add(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
430 jsval_t key
= argc
? argv
[0] : jsval_undefined();
434 hres
= get_set_this(ctx
, vthis
, &set
);
438 TRACE("%p (%s)\n", set
, debugstr_jsval(key
));
440 return set_map_entry(set
, key
, key
, r
);
443 static HRESULT
Set_clear(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
449 hres
= get_set_this(ctx
, vthis
, &set
);
455 while(!list_empty(&set
->entries
)) {
456 struct jsval_map_entry
*entry
= LIST_ENTRY(list_head(&set
->entries
), struct jsval_map_entry
, list_entry
);
457 delete_map_entry(set
, entry
);
460 if(r
) *r
= jsval_undefined();
464 static HRESULT
Set_delete(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
467 jsval_t key
= argc
? argv
[0] : jsval_undefined();
468 struct jsval_map_entry
*entry
;
472 hres
= get_set_this(ctx
, vthis
, &set
);
476 TRACE("%p (%s)\n", set
, debugstr_jsval(key
));
478 if((entry
= get_map_entry(set
, key
))) delete_map_entry(set
, entry
);
479 if(r
) *r
= jsval_bool(!!entry
);
483 static HRESULT
Set_forEach(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
489 hres
= get_set_this(ctx
, vthis
, &set
);
493 TRACE("%p (%s)\n", set
, debugstr_jsval(argc
? argv
[0] : jsval_undefined()));
495 return iterate_map(set
, ctx
, argc
, argv
, r
);
498 static HRESULT
Set_has(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
501 jsval_t key
= argc
? argv
[0] : jsval_undefined();
502 struct jsval_map_entry
*entry
;
506 hres
= get_set_this(ctx
, vthis
, &set
);
510 TRACE("%p (%s)\n", set
, debugstr_jsval(key
));
512 entry
= get_map_entry(set
, key
);
513 if(r
) *r
= jsval_bool(!!entry
);
517 static HRESULT
Set_value(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
524 static const builtin_prop_t Set_prototype_props
[] = {
525 {L
"add", Set_add
, PROPF_METHOD
|1},
526 {L
"clear", Set_clear
, PROPF_METHOD
},
527 {L
"delete" , Set_delete
, PROPF_METHOD
|1},
528 {L
"forEach", Set_forEach
, PROPF_METHOD
|1},
529 {L
"has", Set_has
, PROPF_METHOD
|1},
532 static const builtin_info_t Set_prototype_info
= {
535 ARRAY_SIZE(Set_prototype_props
),
541 static const builtin_info_t Set_info
= {
544 ARRAY_SIZE(Map_props
),
550 static HRESULT
Set_constructor(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
,
557 case DISPATCH_CONSTRUCT
:
562 if(!(set
= heap_alloc_zero(sizeof(*set
))))
563 return E_OUTOFMEMORY
;
565 hres
= init_dispex(&set
->dispex
, ctx
, &Set_info
, ctx
->set_prototype
);
569 wine_rb_init(&set
->map
, jsval_map_compare
);
570 list_init(&set
->entries
);
571 *r
= jsval_obj(&set
->dispex
);
575 FIXME("unimplemented flags %x\n", flags
);
580 HRESULT
init_set_constructor(script_ctx_t
*ctx
)
582 jsdisp_t
*constructor
;
585 if(ctx
->version
< SCRIPTLANGUAGEVERSION_ES6
)
588 hres
= create_dispex(ctx
, &Set_prototype_info
, ctx
->object_prototype
, &ctx
->set_prototype
);
592 hres
= create_builtin_constructor(ctx
, Set_constructor
, L
"Set", NULL
,
593 PROPF_CONSTR
, ctx
->set_prototype
, &constructor
);
597 hres
= jsdisp_define_data_property(ctx
->global
, L
"Set", PROPF_WRITABLE
,
598 jsval_obj(constructor
));
599 jsdisp_release(constructor
);
603 hres
= create_dispex(ctx
, &Map_prototype_info
, ctx
->object_prototype
, &ctx
->map_prototype
);
607 hres
= create_builtin_constructor(ctx
, Map_constructor
, L
"Map", NULL
,
608 PROPF_CONSTR
, ctx
->map_prototype
, &constructor
);
612 hres
= jsdisp_define_data_property(ctx
->global
, L
"Map", PROPF_WRITABLE
,
613 jsval_obj(constructor
));
614 jsdisp_release(constructor
);