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
= heap_alloc((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");
147 heap_free(prop_name
);
152 hres
= parse_json_value(ctx
, &val
);
153 if(SUCCEEDED(hres
)) {
154 hres
= jsdisp_propput_name(obj
, prop_name
, val
);
157 heap_free(prop_name
);
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 /* ECMA-262 5.1 Edition 15.12.2 */
271 static HRESULT
JSON_parse(script_ctx_t
*ctx
, vdisp_t
*jsthis
, WORD flags
, unsigned argc
, jsval_t
*argv
, jsval_t
*r
)
273 json_parse_ctx_t parse_ctx
;
280 FIXME("Unsupported args\n");
284 hres
= to_flat_string(ctx
, argv
[0], &str
, &buf
);
288 TRACE("%s\n", debugstr_w(buf
));
291 parse_ctx
.end
= buf
+ jsstr_length(str
);
293 hres
= parse_json_value(&parse_ctx
, &ret
);
294 if(SUCCEEDED(hres
) && skip_spaces(&parse_ctx
)) {
295 FIXME("syntax error\n");
320 WCHAR gap
[11]; /* according to the spec, it's no longer than 10 chars */
325 static BOOL
stringify_push_obj(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
327 if(!ctx
->stack_size
) {
328 ctx
->stack
= heap_alloc(4*sizeof(*ctx
->stack
));
332 }else if(ctx
->stack_top
== ctx
->stack_size
) {
333 jsdisp_t
**new_stack
;
335 new_stack
= heap_realloc(ctx
->stack
, ctx
->stack_size
*2*sizeof(*ctx
->stack
));
338 ctx
->stack
= new_stack
;
339 ctx
->stack_size
*= 2;
342 ctx
->stack
[ctx
->stack_top
++] = obj
;
346 static void stringify_pop_obj(stringify_ctx_t
*ctx
)
351 static BOOL
is_on_stack(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
353 size_t i
= ctx
->stack_top
;
355 if(ctx
->stack
[i
] == obj
)
361 static BOOL
append_string_len(stringify_ctx_t
*ctx
, const WCHAR
*str
, size_t len
)
364 ctx
->buf
= heap_alloc(len
*2*sizeof(WCHAR
));
367 ctx
->buf_size
= len
*2;
368 }else if(ctx
->buf_len
+ len
> ctx
->buf_size
) {
372 new_size
= ctx
->buf_size
* 2 + len
;
373 new_buf
= heap_realloc(ctx
->buf
, new_size
*sizeof(WCHAR
));
377 ctx
->buf_size
= new_size
;
381 memcpy(ctx
->buf
+ ctx
->buf_len
, str
, len
*sizeof(WCHAR
));
386 static inline BOOL
append_string(stringify_ctx_t
*ctx
, const WCHAR
*str
)
388 return append_string_len(ctx
, str
, lstrlenW(str
));
391 static inline BOOL
append_char(stringify_ctx_t
*ctx
, WCHAR c
)
393 return append_string_len(ctx
, &c
, 1);
396 static inline BOOL
append_simple_quote(stringify_ctx_t
*ctx
, WCHAR c
)
398 WCHAR str
[] = {'\\',c
};
399 return append_string_len(ctx
, str
, 2);
402 static HRESULT
maybe_to_primitive(script_ctx_t
*ctx
, jsval_t val
, jsval_t
*r
)
407 if(!is_object_instance(val
) || !get_object(val
) || !(obj
= iface_to_jsdisp(get_object(val
))))
408 return jsval_copy(val
, r
);
410 if(is_class(obj
, JSCLASS_NUMBER
)) {
412 hres
= to_number(ctx
, val
, &n
);
415 *r
= jsval_number(n
);
419 if(is_class(obj
, JSCLASS_STRING
)) {
421 hres
= to_string(ctx
, val
, &str
);
424 *r
= jsval_string(str
);
428 if(is_class(obj
, JSCLASS_BOOLEAN
)) {
429 *r
= jsval_bool(bool_obj_value(obj
));
438 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation Quote) */
439 static HRESULT
json_quote(stringify_ctx_t
*ctx
, const WCHAR
*ptr
, size_t len
)
441 if(!ptr
|| !append_char(ctx
, '"'))
442 return E_OUTOFMEMORY
;
448 if(!append_simple_quote(ctx
, *ptr
))
449 return E_OUTOFMEMORY
;
452 if(!append_simple_quote(ctx
, 'b'))
453 return E_OUTOFMEMORY
;
456 if(!append_simple_quote(ctx
, 'f'))
457 return E_OUTOFMEMORY
;
460 if(!append_simple_quote(ctx
, 'n'))
461 return E_OUTOFMEMORY
;
464 if(!append_simple_quote(ctx
, 'r'))
465 return E_OUTOFMEMORY
;
468 if(!append_simple_quote(ctx
, 't'))
469 return E_OUTOFMEMORY
;
474 swprintf(buf
, ARRAY_SIZE(buf
), L
"\\u%04x", *ptr
);
475 if(!append_string(ctx
, buf
))
476 return E_OUTOFMEMORY
;
478 if(!append_char(ctx
, *ptr
))
479 return E_OUTOFMEMORY
;
485 return append_char(ctx
, '"') ? S_OK
: E_OUTOFMEMORY
;
488 static inline BOOL
is_callable(jsdisp_t
*obj
)
490 return is_class(obj
, JSCLASS_FUNCTION
);
493 static HRESULT
stringify(stringify_ctx_t
*ctx
, jsdisp_t
*object
, const WCHAR
*name
);
495 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation JA) */
496 static HRESULT
stringify_array(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
498 unsigned length
, i
, j
;
502 if(is_on_stack(ctx
, obj
)) {
503 FIXME("Found a cycle\n");
507 if(!stringify_push_obj(ctx
, obj
))
508 return E_OUTOFMEMORY
;
510 if(!append_char(ctx
, '['))
511 return E_OUTOFMEMORY
;
513 length
= array_get_length(obj
);
515 for(i
=0; i
< length
; i
++) {
516 if(i
&& !append_char(ctx
, ','))
517 return E_OUTOFMEMORY
;
520 if(!append_char(ctx
, '\n'))
521 return E_OUTOFMEMORY
;
523 for(j
=0; j
< ctx
->stack_top
; j
++) {
524 if(!append_string(ctx
, ctx
->gap
))
525 return E_OUTOFMEMORY
;
529 _itow(i
, name
, ARRAY_SIZE(name
));
530 hres
= stringify(ctx
, obj
, name
);
533 if(hres
== S_FALSE
&& !append_string(ctx
, L
"null"))
534 return E_OUTOFMEMORY
;
537 if((length
&& *ctx
->gap
&& !append_char(ctx
, '\n')) || !append_char(ctx
, ']'))
538 return E_OUTOFMEMORY
;
540 stringify_pop_obj(ctx
);
544 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation JO) */
545 static HRESULT
stringify_object(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
547 DISPID dispid
= DISPID_STARTENUM
;
548 unsigned prop_cnt
= 0, i
;
553 if(is_on_stack(ctx
, obj
)) {
554 FIXME("Found a cycle\n");
558 if(!stringify_push_obj(ctx
, obj
))
559 return E_OUTOFMEMORY
;
561 if(!append_char(ctx
, '{'))
562 return E_OUTOFMEMORY
;
564 while((hres
= IDispatchEx_GetNextDispID(&obj
->IDispatchEx_iface
, fdexEnumDefault
, dispid
, &dispid
)) == S_OK
) {
565 stepback
= ctx
->buf_len
;
567 if(prop_cnt
&& !append_char(ctx
, ',')) {
568 hres
= E_OUTOFMEMORY
;
573 if(!append_char(ctx
, '\n')) {
574 hres
= E_OUTOFMEMORY
;
578 for(i
=0; i
< ctx
->stack_top
; i
++) {
579 if(!append_string(ctx
, ctx
->gap
)) {
580 hres
= E_OUTOFMEMORY
;
586 hres
= IDispatchEx_GetMemberName(&obj
->IDispatchEx_iface
, dispid
, &prop_name
);
590 hres
= json_quote(ctx
, prop_name
, SysStringLen(prop_name
));
592 SysFreeString(prop_name
);
596 if(!append_char(ctx
, ':') || (*ctx
->gap
&& !append_char(ctx
, ' '))) {
597 SysFreeString(prop_name
);
598 return E_OUTOFMEMORY
;
601 hres
= stringify(ctx
, obj
, prop_name
);
602 SysFreeString(prop_name
);
606 if(hres
== S_FALSE
) {
607 ctx
->buf_len
= stepback
;
614 if(prop_cnt
&& *ctx
->gap
) {
615 if(!append_char(ctx
, '\n'))
616 return E_OUTOFMEMORY
;
618 for(i
=1; i
< ctx
->stack_top
; i
++) {
619 if(!append_string(ctx
, ctx
->gap
)) {
620 hres
= E_OUTOFMEMORY
;
626 if(!append_char(ctx
, '}'))
627 return E_OUTOFMEMORY
;
629 stringify_pop_obj(ctx
);
633 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation Str) */
634 static HRESULT
stringify(stringify_ctx_t
*ctx
, jsdisp_t
*object
, const WCHAR
*name
)
639 hres
= jsdisp_propget_name(object
, name
, &value
);
641 return hres
== DISP_E_UNKNOWNNAME
? S_FALSE
: hres
;
643 if(is_object_instance(value
) && get_object(value
)) {
647 obj
= iface_to_jsdisp(get_object(value
));
649 jsval_release(value
);
653 hres
= jsdisp_get_id(obj
, L
"toJSON", 0, &id
);
656 FIXME("Use toJSON.\n");
662 if(!(name_str
= jsstr_alloc(name
))) {
663 jsval_release(value
);
664 return E_OUTOFMEMORY
;
666 args
[0] = jsval_string(name_str
);
668 hres
= jsdisp_call_value(ctx
->replacer
, to_disp(object
), DISPATCH_METHOD
, ARRAY_SIZE(args
), args
, &v
);
669 jsstr_release(name_str
);
670 jsval_release(value
);
677 hres
= maybe_to_primitive(ctx
->ctx
, v
, &value
);
682 switch(jsval_type(value
)) {
684 if(!append_string(ctx
, L
"null"))
685 hres
= E_OUTOFMEMORY
;
688 if(!append_string(ctx
, get_bool(value
) ? L
"true" : L
"false"))
689 hres
= E_OUTOFMEMORY
;
692 jsstr_t
*str
= get_string(value
);
693 const WCHAR
*ptr
= jsstr_flatten(str
);
695 hres
= json_quote(ctx
, ptr
, jsstr_length(str
));
697 hres
= E_OUTOFMEMORY
;
701 double n
= get_number(value
);
706 /* FIXME: Optimize. There is no need for jsstr_t here. */
707 hres
= double_to_string(n
, &str
);
711 ptr
= jsstr_flatten(str
);
713 hres
= ptr
&& !append_string_len(ctx
, ptr
, jsstr_length(str
)) ? E_OUTOFMEMORY
: S_OK
;
716 if(!append_string(ctx
, L
"null"))
717 hres
= E_OUTOFMEMORY
;
724 obj
= iface_to_jsdisp(get_object(value
));
730 if(!is_callable(obj
))
731 hres
= is_class(obj
, JSCLASS_ARRAY
) ? stringify_array(ctx
, obj
) : stringify_object(ctx
, obj
);
747 jsval_release(value
);
751 /* ECMA-262 5.1 Edition 15.12.3 */
752 static HRESULT
JSON_stringify(script_ctx_t
*ctx
, vdisp_t
*jsthis
, WORD flags
, unsigned argc
, jsval_t
*argv
, jsval_t
*r
)
754 stringify_ctx_t stringify_ctx
= { ctx
};
755 jsdisp_t
*obj
= NULL
, *replacer
;
762 *r
= jsval_undefined();
766 if(argc
>= 2 && is_object_instance(argv
[1]) && get_object(argv
[1]) &&
767 (replacer
= to_jsdisp(get_object(argv
[1])))) {
768 if(is_callable(replacer
)) {
769 stringify_ctx
.replacer
= jsdisp_addref(replacer
);
770 }else if(is_class(replacer
, JSCLASS_ARRAY
)) {
771 FIXME("Array replacer not yet supported\n");
779 hres
= maybe_to_primitive(ctx
, argv
[2], &space_val
);
783 if(is_number(space_val
)) {
784 double n
= get_number(space_val
);
790 for(i
=0; i
< len
; i
++)
791 stringify_ctx
.gap
[i
] = ' ';
792 stringify_ctx
.gap
[len
] = 0;
794 }else if(is_string(space_val
)) {
795 jsstr_t
*space_str
= get_string(space_val
);
796 size_t len
= jsstr_length(space_str
);
799 jsstr_extract(space_str
, 0, len
, stringify_ctx
.gap
);
802 jsval_release(space_val
);
805 if(FAILED(hres
= create_object(ctx
, NULL
, &obj
)))
807 if(FAILED(hres
= jsdisp_propput_name(obj
, L
"", argv
[0])))
810 hres
= stringify(&stringify_ctx
, obj
, L
"");
811 if(SUCCEEDED(hres
) && r
) {
812 assert(!stringify_ctx
.stack_top
);
815 jsstr_t
*ret
= jsstr_alloc_len(stringify_ctx
.buf
, stringify_ctx
.buf_len
);
817 *r
= jsval_string(ret
);
819 hres
= E_OUTOFMEMORY
;
821 *r
= jsval_undefined();
828 if(stringify_ctx
.replacer
)
829 jsdisp_release(stringify_ctx
.replacer
);
830 heap_free(stringify_ctx
.buf
);
831 heap_free(stringify_ctx
.stack
);
835 static const builtin_prop_t JSON_props
[] = {
836 {L
"parse", JSON_parse
, PROPF_METHOD
|2},
837 {L
"stringify", JSON_stringify
, PROPF_METHOD
|3}
840 static const builtin_info_t JSON_info
= {
843 ARRAY_SIZE(JSON_props
),
849 HRESULT
create_json(script_ctx_t
*ctx
, jsdisp_t
**ret
)
854 json
= heap_alloc_zero(sizeof(*json
));
856 return E_OUTOFMEMORY
;
858 hres
= init_dispex_from_constr(json
, ctx
, &JSON_info
, ctx
->object_constr
);