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
);
29 static const WCHAR parseW
[] = {'p','a','r','s','e',0};
30 static const WCHAR stringifyW
[] = {'s','t','r','i','n','g','i','f','y',0};
32 static const WCHAR nullW
[] = {'n','u','l','l',0};
33 static const WCHAR trueW
[] = {'t','r','u','e',0};
34 static const WCHAR falseW
[] = {'f','a','l','s','e',0};
36 static const WCHAR toJSONW
[] = {'t','o','J','S','O','N',0};
44 static BOOL
is_json_space(WCHAR c
)
46 return c
== ' ' || c
== '\t' || c
== '\n' || c
== '\r';
49 static WCHAR
skip_spaces(json_parse_ctx_t
*ctx
)
51 while(is_json_space(*ctx
->ptr
))
56 static BOOL
is_keyword(json_parse_ctx_t
*ctx
, const WCHAR
*keyword
)
59 for(i
=0; keyword
[i
]; i
++) {
60 if(!ctx
->ptr
[i
] || keyword
[i
] != ctx
->ptr
[i
])
63 if(is_identifier_char(ctx
->ptr
[i
]))
69 /* ECMA-262 5.1 Edition 15.12.1.1 */
70 static HRESULT
parse_json_string(json_parse_ctx_t
*ctx
, WCHAR
**r
)
72 const WCHAR
*ptr
= ++ctx
->ptr
;
76 while(*ctx
->ptr
&& *ctx
->ptr
!= '"') {
77 if(*ctx
->ptr
++ == '\\')
81 FIXME("unterminated string\n");
86 buf
= heap_alloc((len
+1)*sizeof(WCHAR
));
90 memcpy(buf
, ptr
, len
*sizeof(WCHAR
));
92 if(!unescape(buf
, &len
)) {
93 FIXME("unescape failed\n");
104 /* ECMA-262 5.1 Edition 15.12.1.2 */
105 static HRESULT
parse_json_value(json_parse_ctx_t
*ctx
, jsval_t
*r
)
109 switch(skip_spaces(ctx
)) {
111 /* JSONNullLiteral */
113 if(!is_keyword(ctx
, nullW
))
118 /* JSONBooleanLiteral */
120 if(!is_keyword(ctx
, trueW
))
122 *r
= jsval_bool(TRUE
);
125 if(!is_keyword(ctx
, falseW
))
127 *r
= jsval_bool(FALSE
);
136 hres
= create_object(ctx
->ctx
, NULL
, &obj
);
141 if(skip_spaces(ctx
) == '}') {
150 hres
= parse_json_string(ctx
, &prop_name
);
154 if(skip_spaces(ctx
) != ':') {
155 FIXME("missing ':'\n");
156 heap_free(prop_name
);
161 hres
= parse_json_value(ctx
, &val
);
162 if(SUCCEEDED(hres
)) {
163 hres
= jsdisp_propput_name(obj
, prop_name
, val
);
166 heap_free(prop_name
);
170 if(skip_spaces(ctx
) == '}') {
176 if(*ctx
->ptr
++ != ',') {
177 FIXME("expected ','\n");
192 hres
= parse_json_string(ctx
, &string
);
196 /* FIXME: avoid reallocation */
197 str
= jsstr_alloc(string
);
200 return E_OUTOFMEMORY
;
202 *r
= jsval_string(str
);
212 hres
= create_array(ctx
->ctx
, 0, &array
);
217 if(skip_spaces(ctx
) == ']') {
219 *r
= jsval_obj(array
);
224 hres
= parse_json_value(ctx
, &val
);
228 hres
= jsdisp_propput_idx(array
, i
, val
);
233 if(skip_spaces(ctx
) == ']') {
235 *r
= jsval_obj(array
);
239 if(*ctx
->ptr
!= ',') {
240 FIXME("expected ','\n");
248 jsdisp_release(array
);
257 if(*ctx
->ptr
== '-') {
263 if(*ctx
->ptr
== '0' && ctx
->ptr
+ 1 < ctx
->end
&& is_digit(ctx
->ptr
[1]))
266 hres
= parse_decimal(&ctx
->ptr
, ctx
->end
, &n
);
270 *r
= jsval_number(sign
*n
);
275 FIXME("Syntax error at %s\n", debugstr_w(ctx
->ptr
));
279 /* ECMA-262 5.1 Edition 15.12.2 */
280 static HRESULT
JSON_parse(script_ctx_t
*ctx
, vdisp_t
*jsthis
, WORD flags
, unsigned argc
, jsval_t
*argv
, jsval_t
*r
)
282 json_parse_ctx_t parse_ctx
;
289 FIXME("Unsupported args\n");
293 hres
= to_flat_string(ctx
, argv
[0], &str
, &buf
);
297 TRACE("%s\n", debugstr_w(buf
));
300 parse_ctx
.end
= buf
+ jsstr_length(str
);
302 hres
= parse_json_value(&parse_ctx
, &ret
);
307 if(skip_spaces(&parse_ctx
)) {
308 FIXME("syntax error\n");
331 WCHAR gap
[11]; /* according to the spec, it's no longer than 10 chars */
334 static BOOL
stringify_push_obj(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
336 if(!ctx
->stack_size
) {
337 ctx
->stack
= heap_alloc(4*sizeof(*ctx
->stack
));
341 }else if(ctx
->stack_top
== ctx
->stack_size
) {
342 jsdisp_t
**new_stack
;
344 new_stack
= heap_realloc(ctx
->stack
, ctx
->stack_size
*2*sizeof(*ctx
->stack
));
347 ctx
->stack
= new_stack
;
348 ctx
->stack_size
*= 2;
351 ctx
->stack
[ctx
->stack_top
++] = obj
;
355 static void stringify_pop_obj(stringify_ctx_t
*ctx
)
360 static BOOL
is_on_stack(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
362 size_t i
= ctx
->stack_top
;
364 if(ctx
->stack
[i
] == obj
)
370 static BOOL
append_string_len(stringify_ctx_t
*ctx
, const WCHAR
*str
, size_t len
)
373 ctx
->buf
= heap_alloc(len
*2*sizeof(WCHAR
));
376 ctx
->buf_size
= len
*2;
377 }else if(ctx
->buf_len
+ len
> ctx
->buf_size
) {
381 new_size
= ctx
->buf_size
* 2 + len
;
382 new_buf
= heap_realloc(ctx
->buf
, new_size
*sizeof(WCHAR
));
386 ctx
->buf_size
= new_size
;
390 memcpy(ctx
->buf
+ ctx
->buf_len
, str
, len
*sizeof(WCHAR
));
395 static inline BOOL
append_string(stringify_ctx_t
*ctx
, const WCHAR
*str
)
397 return append_string_len(ctx
, str
, lstrlenW(str
));
400 static inline BOOL
append_char(stringify_ctx_t
*ctx
, WCHAR c
)
402 return append_string_len(ctx
, &c
, 1);
405 static inline BOOL
append_simple_quote(stringify_ctx_t
*ctx
, WCHAR c
)
407 WCHAR str
[] = {'\\',c
};
408 return append_string_len(ctx
, str
, 2);
411 static HRESULT
maybe_to_primitive(script_ctx_t
*ctx
, jsval_t val
, jsval_t
*r
)
416 if(!is_object_instance(val
) || !get_object(val
) || !(obj
= iface_to_jsdisp(get_object(val
))))
417 return jsval_copy(val
, r
);
419 if(is_class(obj
, JSCLASS_NUMBER
)) {
421 hres
= to_number(ctx
, val
, &n
);
424 *r
= jsval_number(n
);
428 if(is_class(obj
, JSCLASS_STRING
)) {
430 hres
= to_string(ctx
, val
, &str
);
433 *r
= jsval_string(str
);
437 if(is_class(obj
, JSCLASS_BOOLEAN
)) {
438 *r
= jsval_bool(bool_obj_value(obj
));
447 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation Quote) */
448 static HRESULT
json_quote(stringify_ctx_t
*ctx
, const WCHAR
*ptr
, size_t len
)
450 if(!ptr
|| !append_char(ctx
, '"'))
451 return E_OUTOFMEMORY
;
457 if(!append_simple_quote(ctx
, *ptr
))
458 return E_OUTOFMEMORY
;
461 if(!append_simple_quote(ctx
, 'b'))
462 return E_OUTOFMEMORY
;
465 if(!append_simple_quote(ctx
, 'f'))
466 return E_OUTOFMEMORY
;
469 if(!append_simple_quote(ctx
, 'n'))
470 return E_OUTOFMEMORY
;
473 if(!append_simple_quote(ctx
, 'r'))
474 return E_OUTOFMEMORY
;
477 if(!append_simple_quote(ctx
, 't'))
478 return E_OUTOFMEMORY
;
482 static const WCHAR formatW
[] = {'\\','u','%','0','4','x',0};
484 swprintf(buf
, ARRAY_SIZE(buf
), formatW
, *ptr
);
485 if(!append_string(ctx
, buf
))
486 return E_OUTOFMEMORY
;
488 if(!append_char(ctx
, *ptr
))
489 return E_OUTOFMEMORY
;
495 return append_char(ctx
, '"') ? S_OK
: E_OUTOFMEMORY
;
498 static inline BOOL
is_callable(jsdisp_t
*obj
)
500 return is_class(obj
, JSCLASS_FUNCTION
);
503 static HRESULT
stringify(stringify_ctx_t
*ctx
, jsval_t val
);
505 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation JA) */
506 static HRESULT
stringify_array(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
508 unsigned length
, i
, j
;
512 if(is_on_stack(ctx
, obj
)) {
513 FIXME("Found a cycle\n");
517 if(!stringify_push_obj(ctx
, obj
))
518 return E_OUTOFMEMORY
;
520 if(!append_char(ctx
, '['))
521 return E_OUTOFMEMORY
;
523 length
= array_get_length(obj
);
525 for(i
=0; i
< length
; i
++) {
526 if(i
&& !append_char(ctx
, ','))
527 return E_OUTOFMEMORY
;
530 if(!append_char(ctx
, '\n'))
531 return E_OUTOFMEMORY
;
533 for(j
=0; j
< ctx
->stack_top
; j
++) {
534 if(!append_string(ctx
, ctx
->gap
))
535 return E_OUTOFMEMORY
;
539 hres
= jsdisp_get_idx(obj
, i
, &val
);
540 if(SUCCEEDED(hres
)) {
541 hres
= stringify(ctx
, val
);
544 if(hres
== S_FALSE
&& !append_string(ctx
, nullW
))
545 return E_OUTOFMEMORY
;
546 }else if(hres
== DISP_E_UNKNOWNNAME
) {
547 if(!append_string(ctx
, nullW
))
548 return E_OUTOFMEMORY
;
554 if((length
&& *ctx
->gap
&& !append_char(ctx
, '\n')) || !append_char(ctx
, ']'))
555 return E_OUTOFMEMORY
;
557 stringify_pop_obj(ctx
);
561 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation JO) */
562 static HRESULT
stringify_object(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
564 DISPID dispid
= DISPID_STARTENUM
;
565 jsval_t val
= jsval_undefined();
566 unsigned prop_cnt
= 0, i
;
571 if(is_on_stack(ctx
, obj
)) {
572 FIXME("Found a cycle\n");
576 if(!stringify_push_obj(ctx
, obj
))
577 return E_OUTOFMEMORY
;
579 if(!append_char(ctx
, '{'))
580 return E_OUTOFMEMORY
;
582 while((hres
= IDispatchEx_GetNextDispID(&obj
->IDispatchEx_iface
, fdexEnumDefault
, dispid
, &dispid
)) == S_OK
) {
584 hres
= jsdisp_propget(obj
, dispid
, &val
);
588 if(is_undefined(val
))
591 stepback
= ctx
->buf_len
;
593 if(prop_cnt
&& !append_char(ctx
, ',')) {
594 hres
= E_OUTOFMEMORY
;
599 if(!append_char(ctx
, '\n')) {
600 hres
= E_OUTOFMEMORY
;
604 for(i
=0; i
< ctx
->stack_top
; i
++) {
605 if(!append_string(ctx
, ctx
->gap
)) {
606 hres
= E_OUTOFMEMORY
;
612 hres
= IDispatchEx_GetMemberName(&obj
->IDispatchEx_iface
, dispid
, &prop_name
);
616 hres
= json_quote(ctx
, prop_name
, SysStringLen(prop_name
));
617 SysFreeString(prop_name
);
621 if(!append_char(ctx
, ':') || (*ctx
->gap
&& !append_char(ctx
, ' '))) {
622 hres
= E_OUTOFMEMORY
;
626 hres
= stringify(ctx
, val
);
630 if(hres
== S_FALSE
) {
631 ctx
->buf_len
= stepback
;
641 if(prop_cnt
&& *ctx
->gap
) {
642 if(!append_char(ctx
, '\n'))
643 return E_OUTOFMEMORY
;
645 for(i
=1; i
< ctx
->stack_top
; i
++) {
646 if(!append_string(ctx
, ctx
->gap
)) {
647 hres
= E_OUTOFMEMORY
;
653 if(!append_char(ctx
, '}'))
654 return E_OUTOFMEMORY
;
656 stringify_pop_obj(ctx
);
660 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation Str) */
661 static HRESULT
stringify(stringify_ctx_t
*ctx
, jsval_t val
)
666 if(is_object_instance(val
) && get_object(val
)) {
670 obj
= iface_to_jsdisp(get_object(val
));
674 hres
= jsdisp_get_id(obj
, toJSONW
, 0, &id
);
677 FIXME("Use toJSON.\n");
680 /* FIXME: Support replacer replacer. */
682 hres
= maybe_to_primitive(ctx
->ctx
, val
, &value
);
686 switch(jsval_type(value
)) {
688 if(!append_string(ctx
, nullW
))
689 hres
= E_OUTOFMEMORY
;
692 if(!append_string(ctx
, get_bool(value
) ? trueW
: falseW
))
693 hres
= E_OUTOFMEMORY
;
696 jsstr_t
*str
= get_string(value
);
697 const WCHAR
*ptr
= jsstr_flatten(str
);
699 hres
= json_quote(ctx
, ptr
, jsstr_length(str
));
701 hres
= E_OUTOFMEMORY
;
705 double n
= get_number(value
);
710 /* FIXME: Optimize. There is no need for jsstr_t here. */
711 hres
= double_to_string(n
, &str
);
715 ptr
= jsstr_flatten(str
);
717 hres
= ptr
&& !append_string_len(ctx
, ptr
, jsstr_length(str
)) ? E_OUTOFMEMORY
: S_OK
;
720 if(!append_string(ctx
, nullW
))
721 hres
= E_OUTOFMEMORY
;
728 obj
= iface_to_jsdisp(get_object(value
));
734 if(!is_callable(obj
))
735 hres
= is_class(obj
, JSCLASS_ARRAY
) ? stringify_array(ctx
, obj
) : stringify_object(ctx
, obj
);
751 jsval_release(value
);
755 /* ECMA-262 5.1 Edition 15.12.3 */
756 static HRESULT
JSON_stringify(script_ctx_t
*ctx
, vdisp_t
*jsthis
, WORD flags
, unsigned argc
, jsval_t
*argv
, jsval_t
*r
)
758 stringify_ctx_t stringify_ctx
= {ctx
, NULL
,0,0, NULL
,0,0, {0}};
765 *r
= jsval_undefined();
769 if(argc
>= 2 && is_object_instance(argv
[1])) {
770 FIXME("Replacer %s not yet supported\n", debugstr_jsval(argv
[1]));
777 hres
= maybe_to_primitive(ctx
, argv
[2], &space_val
);
781 if(is_number(space_val
)) {
782 double n
= get_number(space_val
);
788 for(i
=0; i
< len
; i
++)
789 stringify_ctx
.gap
[i
] = ' ';
790 stringify_ctx
.gap
[len
] = 0;
792 }else if(is_string(space_val
)) {
793 jsstr_t
*space_str
= get_string(space_val
);
794 size_t len
= jsstr_length(space_str
);
797 jsstr_extract(space_str
, 0, len
, stringify_ctx
.gap
);
800 jsval_release(space_val
);
803 hres
= stringify(&stringify_ctx
, argv
[0]);
804 if(SUCCEEDED(hres
) && r
) {
805 assert(!stringify_ctx
.stack_top
);
808 jsstr_t
*ret
= jsstr_alloc_len(stringify_ctx
.buf
, stringify_ctx
.buf_len
);
810 *r
= jsval_string(ret
);
812 hres
= E_OUTOFMEMORY
;
814 *r
= jsval_undefined();
818 heap_free(stringify_ctx
.buf
);
819 heap_free(stringify_ctx
.stack
);
823 static const builtin_prop_t JSON_props
[] = {
824 {parseW
, JSON_parse
, PROPF_METHOD
|2},
825 {stringifyW
, JSON_stringify
, PROPF_METHOD
|3}
828 static const builtin_info_t JSON_info
= {
831 ARRAY_SIZE(JSON_props
),
837 HRESULT
create_json(script_ctx_t
*ctx
, jsdisp_t
**ret
)
842 json
= heap_alloc_zero(sizeof(*json
));
844 return E_OUTOFMEMORY
;
846 hres
= init_dispex_from_constr(json
, ctx
, &JSON_info
, ctx
->object_constr
);