2 * Copyright 2016 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
25 #include "wine/debug.h"
27 WINE_DEFAULT_DEBUG_CHANNEL(jscript
);
35 static BOOL
is_json_space(WCHAR c
)
37 return c
== ' ' || c
== '\t' || c
== '\n' || c
== '\r';
40 static WCHAR
skip_spaces(json_parse_ctx_t
*ctx
)
42 while(is_json_space(*ctx
->ptr
))
47 static BOOL
is_keyword(json_parse_ctx_t
*ctx
, const WCHAR
*keyword
)
50 for(i
=0; keyword
[i
]; i
++) {
51 if(!ctx
->ptr
[i
] || keyword
[i
] != ctx
->ptr
[i
])
54 if(is_identifier_char(ctx
->ptr
[i
]))
60 /* ECMA-262 5.1 Edition 15.12.1.1 */
61 static HRESULT
parse_json_string(json_parse_ctx_t
*ctx
, WCHAR
**r
)
63 const WCHAR
*ptr
= ++ctx
->ptr
;
67 while(*ctx
->ptr
&& *ctx
->ptr
!= '"') {
68 if(*ctx
->ptr
++ == '\\')
72 FIXME("unterminated string\n");
77 buf
= malloc((len
+1)*sizeof(WCHAR
));
81 memcpy(buf
, ptr
, len
*sizeof(WCHAR
));
83 if(!unescape(buf
, &len
)) {
84 FIXME("unescape failed\n");
95 /* ECMA-262 5.1 Edition 15.12.1.2 */
96 static HRESULT
parse_json_value(json_parse_ctx_t
*ctx
, jsval_t
*r
)
100 switch(skip_spaces(ctx
)) {
102 /* JSONNullLiteral */
104 if(!is_keyword(ctx
, L
"null"))
109 /* JSONBooleanLiteral */
111 if(!is_keyword(ctx
, L
"true"))
113 *r
= jsval_bool(TRUE
);
116 if(!is_keyword(ctx
, L
"false"))
118 *r
= jsval_bool(FALSE
);
127 hres
= create_object(ctx
->ctx
, NULL
, &obj
);
132 if(skip_spaces(ctx
) == '}') {
141 hres
= parse_json_string(ctx
, &prop_name
);
145 if(skip_spaces(ctx
) != ':') {
146 FIXME("missing ':'\n");
152 hres
= parse_json_value(ctx
, &val
);
153 if(SUCCEEDED(hres
)) {
154 hres
= jsdisp_propput_name(obj
, prop_name
, val
);
161 if(skip_spaces(ctx
) == '}') {
167 if(*ctx
->ptr
++ != ',') {
168 FIXME("expected ','\n");
183 hres
= parse_json_string(ctx
, &string
);
187 /* FIXME: avoid reallocation */
188 str
= jsstr_alloc(string
);
191 return E_OUTOFMEMORY
;
193 *r
= jsval_string(str
);
203 hres
= create_array(ctx
->ctx
, 0, &array
);
208 if(skip_spaces(ctx
) == ']') {
210 *r
= jsval_obj(array
);
215 hres
= parse_json_value(ctx
, &val
);
219 hres
= jsdisp_propput_idx(array
, i
, val
);
224 if(skip_spaces(ctx
) == ']') {
226 *r
= jsval_obj(array
);
230 if(*ctx
->ptr
!= ',') {
231 FIXME("expected ','\n");
239 jsdisp_release(array
);
248 if(*ctx
->ptr
== '-') {
254 if(*ctx
->ptr
== '0' && ctx
->ptr
+ 1 < ctx
->end
&& is_digit(ctx
->ptr
[1]))
257 hres
= parse_decimal(&ctx
->ptr
, ctx
->end
, &n
);
261 *r
= jsval_number(sign
*n
);
266 FIXME("Syntax error at %s\n", debugstr_w(ctx
->ptr
));
270 struct transform_json_object_ctx
277 static jsval_t
transform_json_object(struct transform_json_object_ctx
*proc_ctx
, jsdisp_t
*holder
, jsstr_t
*name
)
279 jsval_t res
, args
[2];
282 if(!(str
= jsstr_flatten(name
)))
283 proc_ctx
->hres
= E_OUTOFMEMORY
;
285 proc_ctx
->hres
= jsdisp_propget_name(holder
, str
, &args
[1]);
286 if(FAILED(proc_ctx
->hres
))
287 return jsval_undefined();
289 if(is_object_instance(args
[1])) {
290 jsdisp_t
*obj
= to_jsdisp(get_object(args
[1]));
296 FIXME("non-JS obj in JSON object: %p\n", get_object(args
[1]));
297 proc_ctx
->hres
= E_NOTIMPL
;
298 return jsval_undefined();
299 }else if(is_class(obj
, JSCLASS_ARRAY
)) {
300 unsigned i
, length
= array_get_length(obj
);
301 WCHAR buf
[14], *buf_end
;
303 buf_end
= buf
+ ARRAY_SIZE(buf
) - 1;
305 for(i
= 0; i
< length
; i
++) {
306 str
= idx_to_str(i
, buf_end
);
307 if(!(jsstr
= jsstr_alloc(str
))) {
308 proc_ctx
->hres
= E_OUTOFMEMORY
;
309 return jsval_undefined();
311 res
= transform_json_object(proc_ctx
, obj
, jsstr
);
312 jsstr_release(jsstr
);
313 if(is_undefined(res
)) {
314 if(FAILED(proc_ctx
->hres
))
315 return jsval_undefined();
316 if(FAILED(jsdisp_get_id(obj
, str
, 0, &id
)))
318 proc_ctx
->hres
= disp_delete((IDispatch
*)&obj
->IDispatchEx_iface
, id
, &b
);
320 proc_ctx
->hres
= jsdisp_define_data_property(obj
, str
, PROPF_WRITABLE
| PROPF_ENUMERABLE
| PROPF_CONFIGURABLE
, res
);
323 if(FAILED(proc_ctx
->hres
))
324 return jsval_undefined();
327 id
= DISPID_STARTENUM
;
329 proc_ctx
->hres
= jsdisp_next_prop(obj
, id
, JSDISP_ENUM_OWN_ENUMERABLE
, &id
);
330 if(proc_ctx
->hres
== S_FALSE
)
332 if(FAILED(proc_ctx
->hres
) || FAILED(proc_ctx
->hres
= jsdisp_get_prop_name(obj
, id
, &jsstr
)))
333 return jsval_undefined();
334 res
= transform_json_object(proc_ctx
, obj
, jsstr
);
335 if(is_undefined(res
)) {
336 if(SUCCEEDED(proc_ctx
->hres
))
337 proc_ctx
->hres
= disp_delete((IDispatch
*)&obj
->IDispatchEx_iface
, id
, &b
);
339 if(!(str
= jsstr_flatten(jsstr
)))
340 proc_ctx
->hres
= E_OUTOFMEMORY
;
342 proc_ctx
->hres
= jsdisp_define_data_property(obj
, str
, PROPF_WRITABLE
| PROPF_ENUMERABLE
| PROPF_CONFIGURABLE
, res
);
345 jsstr_release(jsstr
);
346 if(FAILED(proc_ctx
->hres
))
347 return jsval_undefined();
352 args
[0] = jsval_string(name
);
353 proc_ctx
->hres
= disp_call_value(proc_ctx
->ctx
, proc_ctx
->reviver
, jsval_obj(holder
),
354 DISPATCH_METHOD
, ARRAY_SIZE(args
), args
, &res
);
355 return FAILED(proc_ctx
->hres
) ? jsval_undefined() : res
;
358 /* ECMA-262 5.1 Edition 15.12.2 */
359 static HRESULT
JSON_parse(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
, jsval_t
*r
)
361 json_parse_ctx_t parse_ctx
;
368 hres
= to_flat_string(ctx
, argv
[0], &str
, &buf
);
372 TRACE("%s\n", debugstr_w(buf
));
375 parse_ctx
.end
= buf
+ jsstr_length(str
);
377 hres
= parse_json_value(&parse_ctx
, &ret
);
378 if(SUCCEEDED(hres
) && skip_spaces(&parse_ctx
)) {
379 FIXME("syntax error\n");
387 /* FIXME: check IsCallable */
388 if(argc
> 1 && is_object_instance(argv
[1])) {
389 hres
= create_object(ctx
, NULL
, &root
);
394 hres
= jsdisp_define_data_property(root
, L
"", PROPF_WRITABLE
| PROPF_ENUMERABLE
| PROPF_CONFIGURABLE
, ret
);
397 if(SUCCEEDED(hres
)) {
398 struct transform_json_object_ctx proc_ctx
= { ctx
, get_object(argv
[1]), S_OK
};
401 ret
= transform_json_object(&proc_ctx
, root
, str
);
403 hres
= proc_ctx
.hres
;
405 jsdisp_release(root
);
428 WCHAR gap
[11]; /* according to the spec, it's no longer than 10 chars */
433 static BOOL
stringify_push_obj(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
435 if(!ctx
->stack_size
) {
436 ctx
->stack
= malloc(4*sizeof(*ctx
->stack
));
440 }else if(ctx
->stack_top
== ctx
->stack_size
) {
441 jsdisp_t
**new_stack
;
443 new_stack
= realloc(ctx
->stack
, ctx
->stack_size
*2*sizeof(*ctx
->stack
));
446 ctx
->stack
= new_stack
;
447 ctx
->stack_size
*= 2;
450 ctx
->stack
[ctx
->stack_top
++] = obj
;
454 static void stringify_pop_obj(stringify_ctx_t
*ctx
)
459 static BOOL
is_on_stack(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
461 size_t i
= ctx
->stack_top
;
463 if(ctx
->stack
[i
] == obj
)
469 static BOOL
append_string_len(stringify_ctx_t
*ctx
, const WCHAR
*str
, size_t len
)
472 ctx
->buf
= malloc(len
*2*sizeof(WCHAR
));
475 ctx
->buf_size
= len
*2;
476 }else if(ctx
->buf_len
+ len
> ctx
->buf_size
) {
480 new_size
= ctx
->buf_size
* 2 + len
;
481 new_buf
= realloc(ctx
->buf
, new_size
*sizeof(WCHAR
));
485 ctx
->buf_size
= new_size
;
489 memcpy(ctx
->buf
+ ctx
->buf_len
, str
, len
*sizeof(WCHAR
));
494 static inline BOOL
append_string(stringify_ctx_t
*ctx
, const WCHAR
*str
)
496 return append_string_len(ctx
, str
, lstrlenW(str
));
499 static inline BOOL
append_char(stringify_ctx_t
*ctx
, WCHAR c
)
501 return append_string_len(ctx
, &c
, 1);
504 static inline BOOL
append_simple_quote(stringify_ctx_t
*ctx
, WCHAR c
)
506 WCHAR str
[] = {'\\',c
};
507 return append_string_len(ctx
, str
, 2);
510 static HRESULT
maybe_to_primitive(script_ctx_t
*ctx
, jsval_t val
, jsval_t
*r
)
515 if(!is_object_instance(val
) || !(obj
= iface_to_jsdisp(get_object(val
))))
516 return jsval_copy(val
, r
);
518 if(is_class(obj
, JSCLASS_NUMBER
)) {
520 hres
= to_number(ctx
, val
, &n
);
523 *r
= jsval_number(n
);
527 if(is_class(obj
, JSCLASS_STRING
)) {
529 hres
= to_string(ctx
, val
, &str
);
532 *r
= jsval_string(str
);
536 if(is_class(obj
, JSCLASS_BOOLEAN
)) {
537 *r
= jsval_bool(bool_obj_value(obj
));
546 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation Quote) */
547 static HRESULT
json_quote(stringify_ctx_t
*ctx
, const WCHAR
*ptr
, size_t len
)
549 if(!ptr
|| !append_char(ctx
, '"'))
550 return E_OUTOFMEMORY
;
556 if(!append_simple_quote(ctx
, *ptr
))
557 return E_OUTOFMEMORY
;
560 if(!append_simple_quote(ctx
, 'b'))
561 return E_OUTOFMEMORY
;
564 if(!append_simple_quote(ctx
, 'f'))
565 return E_OUTOFMEMORY
;
568 if(!append_simple_quote(ctx
, 'n'))
569 return E_OUTOFMEMORY
;
572 if(!append_simple_quote(ctx
, 'r'))
573 return E_OUTOFMEMORY
;
576 if(!append_simple_quote(ctx
, 't'))
577 return E_OUTOFMEMORY
;
582 swprintf(buf
, ARRAY_SIZE(buf
), L
"\\u%04x", *ptr
);
583 if(!append_string(ctx
, buf
))
584 return E_OUTOFMEMORY
;
586 if(!append_char(ctx
, *ptr
))
587 return E_OUTOFMEMORY
;
593 return append_char(ctx
, '"') ? S_OK
: E_OUTOFMEMORY
;
596 static inline BOOL
is_callable(jsdisp_t
*obj
)
598 return is_class(obj
, JSCLASS_FUNCTION
);
601 static HRESULT
stringify(stringify_ctx_t
*ctx
, jsdisp_t
*object
, const WCHAR
*name
);
603 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation JA) */
604 static HRESULT
stringify_array(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
606 unsigned length
, i
, j
;
610 if(is_on_stack(ctx
, obj
)) {
611 FIXME("Found a cycle\n");
615 if(!stringify_push_obj(ctx
, obj
))
616 return E_OUTOFMEMORY
;
618 if(!append_char(ctx
, '['))
619 return E_OUTOFMEMORY
;
621 length
= array_get_length(obj
);
623 for(i
=0; i
< length
; i
++) {
624 if(i
&& !append_char(ctx
, ','))
625 return E_OUTOFMEMORY
;
628 if(!append_char(ctx
, '\n'))
629 return E_OUTOFMEMORY
;
631 for(j
=0; j
< ctx
->stack_top
; j
++) {
632 if(!append_string(ctx
, ctx
->gap
))
633 return E_OUTOFMEMORY
;
637 _itow(i
, name
, ARRAY_SIZE(name
));
638 hres
= stringify(ctx
, obj
, name
);
641 if(hres
== S_FALSE
&& !append_string(ctx
, L
"null"))
642 return E_OUTOFMEMORY
;
645 if((length
&& *ctx
->gap
&& !append_char(ctx
, '\n')) || !append_char(ctx
, ']'))
646 return E_OUTOFMEMORY
;
648 stringify_pop_obj(ctx
);
652 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation JO) */
653 static HRESULT
stringify_object(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
655 DISPID dispid
= DISPID_STARTENUM
;
656 unsigned prop_cnt
= 0, i
;
661 if(is_on_stack(ctx
, obj
)) {
662 FIXME("Found a cycle\n");
666 if(!stringify_push_obj(ctx
, obj
))
667 return E_OUTOFMEMORY
;
669 if(!append_char(ctx
, '{'))
670 return E_OUTOFMEMORY
;
672 while((hres
= IDispatchEx_GetNextDispID(&obj
->IDispatchEx_iface
, fdexEnumDefault
, dispid
, &dispid
)) == S_OK
) {
673 stepback
= ctx
->buf_len
;
675 if(prop_cnt
&& !append_char(ctx
, ',')) {
676 hres
= E_OUTOFMEMORY
;
681 if(!append_char(ctx
, '\n')) {
682 hres
= E_OUTOFMEMORY
;
686 for(i
=0; i
< ctx
->stack_top
; i
++) {
687 if(!append_string(ctx
, ctx
->gap
)) {
688 hres
= E_OUTOFMEMORY
;
694 hres
= IDispatchEx_GetMemberName(&obj
->IDispatchEx_iface
, dispid
, &prop_name
);
698 hres
= json_quote(ctx
, prop_name
, SysStringLen(prop_name
));
700 SysFreeString(prop_name
);
704 if(!append_char(ctx
, ':') || (*ctx
->gap
&& !append_char(ctx
, ' '))) {
705 SysFreeString(prop_name
);
706 return E_OUTOFMEMORY
;
709 hres
= stringify(ctx
, obj
, prop_name
);
710 SysFreeString(prop_name
);
714 if(hres
== S_FALSE
) {
715 ctx
->buf_len
= stepback
;
722 if(prop_cnt
&& *ctx
->gap
) {
723 if(!append_char(ctx
, '\n'))
724 return E_OUTOFMEMORY
;
726 for(i
=1; i
< ctx
->stack_top
; i
++) {
727 if(!append_string(ctx
, ctx
->gap
)) {
728 hres
= E_OUTOFMEMORY
;
734 if(!append_char(ctx
, '}'))
735 return E_OUTOFMEMORY
;
737 stringify_pop_obj(ctx
);
741 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation Str) */
742 static HRESULT
stringify(stringify_ctx_t
*ctx
, jsdisp_t
*object
, const WCHAR
*name
)
747 hres
= jsdisp_propget_name(object
, name
, &value
);
749 return hres
== DISP_E_UNKNOWNNAME
? S_FALSE
: hres
;
751 if(is_object_instance(value
)) {
755 obj
= to_jsdisp(get_object(value
));
757 jsval_release(value
);
761 hres
= jsdisp_get_id(obj
, L
"toJSON", 0, &id
);
763 FIXME("Use toJSON.\n");
769 if(!(name_str
= jsstr_alloc(name
))) {
770 jsval_release(value
);
771 return E_OUTOFMEMORY
;
773 args
[0] = jsval_string(name_str
);
775 hres
= jsdisp_call_value(ctx
->replacer
, jsval_obj(object
), DISPATCH_METHOD
, ARRAY_SIZE(args
), args
, &v
);
776 jsstr_release(name_str
);
777 jsval_release(value
);
784 hres
= maybe_to_primitive(ctx
->ctx
, v
, &value
);
789 switch(jsval_type(value
)) {
791 if(is_null_disp(value
))
793 else if(!append_string(ctx
, L
"null"))
794 hres
= E_OUTOFMEMORY
;
797 if(!append_string(ctx
, get_bool(value
) ? L
"true" : L
"false"))
798 hres
= E_OUTOFMEMORY
;
801 jsstr_t
*str
= get_string(value
);
802 const WCHAR
*ptr
= jsstr_flatten(str
);
804 hres
= json_quote(ctx
, ptr
, jsstr_length(str
));
806 hres
= E_OUTOFMEMORY
;
810 double n
= get_number(value
);
815 /* FIXME: Optimize. There is no need for jsstr_t here. */
816 hres
= double_to_string(n
, &str
);
820 ptr
= jsstr_flatten(str
);
822 hres
= ptr
&& !append_string_len(ctx
, ptr
, jsstr_length(str
)) ? E_OUTOFMEMORY
: S_OK
;
825 if(!append_string(ctx
, L
"null"))
826 hres
= E_OUTOFMEMORY
;
833 obj
= iface_to_jsdisp(get_object(value
));
839 if(!is_callable(obj
))
840 hres
= is_class(obj
, JSCLASS_ARRAY
) ? stringify_array(ctx
, obj
) : stringify_object(ctx
, obj
);
856 jsval_release(value
);
860 /* ECMA-262 5.1 Edition 15.12.3 */
861 static HRESULT
JSON_stringify(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
, jsval_t
*r
)
863 stringify_ctx_t stringify_ctx
= { ctx
};
864 jsdisp_t
*obj
= NULL
, *replacer
;
871 *r
= jsval_undefined();
875 if(argc
>= 2 && is_object_instance(argv
[1]) && (replacer
= to_jsdisp(get_object(argv
[1])))) {
876 if(is_callable(replacer
)) {
877 stringify_ctx
.replacer
= jsdisp_addref(replacer
);
878 }else if(is_class(replacer
, JSCLASS_ARRAY
)) {
879 FIXME("Array replacer not yet supported\n");
887 hres
= maybe_to_primitive(ctx
, argv
[2], &space_val
);
891 if(is_number(space_val
)) {
892 double n
= get_number(space_val
);
898 for(i
=0; i
< len
; i
++)
899 stringify_ctx
.gap
[i
] = ' ';
900 stringify_ctx
.gap
[len
] = 0;
902 }else if(is_string(space_val
)) {
903 jsstr_t
*space_str
= get_string(space_val
);
904 size_t len
= jsstr_length(space_str
);
907 jsstr_extract(space_str
, 0, len
, stringify_ctx
.gap
);
910 jsval_release(space_val
);
913 if(FAILED(hres
= create_object(ctx
, NULL
, &obj
)))
915 if(FAILED(hres
= jsdisp_propput_name(obj
, L
"", argv
[0])))
918 hres
= stringify(&stringify_ctx
, obj
, L
"");
919 if(SUCCEEDED(hres
) && r
) {
920 assert(!stringify_ctx
.stack_top
);
923 jsstr_t
*ret
= jsstr_alloc_len(stringify_ctx
.buf
, stringify_ctx
.buf_len
);
925 *r
= jsval_string(ret
);
927 hres
= E_OUTOFMEMORY
;
929 *r
= jsval_undefined();
936 if(stringify_ctx
.replacer
)
937 jsdisp_release(stringify_ctx
.replacer
);
938 free(stringify_ctx
.buf
);
939 free(stringify_ctx
.stack
);
943 static const builtin_prop_t JSON_props
[] = {
944 {L
"parse", JSON_parse
, PROPF_METHOD
|2},
945 {L
"stringify", JSON_stringify
, PROPF_METHOD
|3}
948 static const builtin_info_t JSON_info
= {
951 ARRAY_SIZE(JSON_props
),
957 HRESULT
create_json(script_ctx_t
*ctx
, jsdisp_t
**ret
)
962 json
= calloc(1, sizeof(*json
));
964 return E_OUTOFMEMORY
;
966 hres
= init_dispex_from_constr(json
, ctx
, &JSON_info
, ctx
->object_constr
);