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"
26 #include "wine/unicode.h"
28 WINE_DEFAULT_DEBUG_CHANNEL(jscript
);
30 static const WCHAR parseW
[] = {'p','a','r','s','e',0};
31 static const WCHAR stringifyW
[] = {'s','t','r','i','n','g','i','f','y',0};
33 static const WCHAR nullW
[] = {'n','u','l','l',0};
34 static const WCHAR trueW
[] = {'t','r','u','e',0};
35 static const WCHAR falseW
[] = {'f','a','l','s','e',0};
37 static const WCHAR toJSONW
[] = {'t','o','J','S','O','N',0};
45 static BOOL
is_json_space(WCHAR c
)
47 return c
== ' ' || c
== '\t' || c
== '\n' || c
== '\r';
50 static WCHAR
skip_spaces(json_parse_ctx_t
*ctx
)
52 while(is_json_space(*ctx
->ptr
))
57 static BOOL
is_keyword(json_parse_ctx_t
*ctx
, const WCHAR
*keyword
)
60 for(i
=0; keyword
[i
]; i
++) {
61 if(!ctx
->ptr
[i
] || keyword
[i
] != ctx
->ptr
[i
])
64 if(is_identifier_char(ctx
->ptr
[i
]))
70 /* ECMA-262 5.1 Edition 15.12.1.1 */
71 static HRESULT
parse_json_string(json_parse_ctx_t
*ctx
, WCHAR
**r
)
73 const WCHAR
*ptr
= ++ctx
->ptr
;
77 while(*ctx
->ptr
&& *ctx
->ptr
!= '"') {
78 if(*ctx
->ptr
++ == '\\')
82 FIXME("unterminated string\n");
87 buf
= heap_alloc((len
+1)*sizeof(WCHAR
));
91 memcpy(buf
, ptr
, len
*sizeof(WCHAR
));
95 FIXME("unescape failed\n");
105 /* ECMA-262 5.1 Edition 15.12.1.2 */
106 static HRESULT
parse_json_value(json_parse_ctx_t
*ctx
, jsval_t
*r
)
110 switch(skip_spaces(ctx
)) {
112 /* JSONNullLiteral */
114 if(!is_keyword(ctx
, nullW
))
119 /* JSONBooleanLiteral */
121 if(!is_keyword(ctx
, trueW
))
123 *r
= jsval_bool(TRUE
);
126 if(!is_keyword(ctx
, falseW
))
128 *r
= jsval_bool(FALSE
);
137 hres
= create_object(ctx
->ctx
, NULL
, &obj
);
142 if(skip_spaces(ctx
) == '}') {
151 hres
= parse_json_string(ctx
, &prop_name
);
155 if(skip_spaces(ctx
) != ':') {
156 FIXME("missing ':'\n");
157 heap_free(prop_name
);
162 hres
= parse_json_value(ctx
, &val
);
163 if(SUCCEEDED(hres
)) {
164 hres
= jsdisp_propput_name(obj
, prop_name
, val
);
167 heap_free(prop_name
);
171 if(skip_spaces(ctx
) == '}') {
177 if(*ctx
->ptr
++ != ',') {
178 FIXME("expected ','\n");
193 hres
= parse_json_string(ctx
, &string
);
197 /* FIXME: avoid reallocation */
198 str
= jsstr_alloc(string
);
201 return E_OUTOFMEMORY
;
203 *r
= jsval_string(str
);
213 hres
= create_array(ctx
->ctx
, 0, &array
);
218 if(skip_spaces(ctx
) == ']') {
220 *r
= jsval_obj(array
);
225 hres
= parse_json_value(ctx
, &val
);
229 hres
= jsdisp_propput_idx(array
, i
, val
);
234 if(skip_spaces(ctx
) == ']') {
236 *r
= jsval_obj(array
);
240 if(*ctx
->ptr
!= ',') {
241 FIXME("expected ','\n");
249 jsdisp_release(array
);
258 if(*ctx
->ptr
== '-') {
264 if(!isdigitW(*ctx
->ptr
))
267 if(*ctx
->ptr
== '0') {
270 if(is_identifier_char(*ctx
->ptr
))
273 hres
= parse_decimal(&ctx
->ptr
, ctx
->end
, &n
);
278 *r
= jsval_number(sign
*n
);
283 FIXME("Syntax error at %s\n", debugstr_w(ctx
->ptr
));
287 /* ECMA-262 5.1 Edition 15.12.2 */
288 static HRESULT
JSON_parse(script_ctx_t
*ctx
, vdisp_t
*jsthis
, WORD flags
, unsigned argc
, jsval_t
*argv
, jsval_t
*r
)
290 json_parse_ctx_t parse_ctx
;
297 FIXME("Unsupported args\n");
301 hres
= to_flat_string(ctx
, argv
[0], &str
, &buf
);
305 TRACE("%s\n", debugstr_w(buf
));
308 parse_ctx
.end
= buf
+ jsstr_length(str
);
310 hres
= parse_json_value(&parse_ctx
, &ret
);
315 if(skip_spaces(&parse_ctx
)) {
316 FIXME("syntax error\n");
339 WCHAR gap
[11]; /* according to the spec, it's no longer than 10 chars */
342 static BOOL
stringify_push_obj(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
344 if(!ctx
->stack_size
) {
345 ctx
->stack
= heap_alloc(4*sizeof(*ctx
->stack
));
349 }else if(ctx
->stack_top
== ctx
->stack_size
) {
350 jsdisp_t
**new_stack
;
352 new_stack
= heap_realloc(ctx
->stack
, ctx
->stack_size
*2*sizeof(*ctx
->stack
));
355 ctx
->stack
= new_stack
;
356 ctx
->stack_size
*= 2;
359 ctx
->stack
[ctx
->stack_top
++] = obj
;
363 static void stringify_pop_obj(stringify_ctx_t
*ctx
)
368 static BOOL
is_on_stack(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
370 size_t i
= ctx
->stack_top
;
372 if(ctx
->stack
[i
] == obj
)
378 static BOOL
append_string_len(stringify_ctx_t
*ctx
, const WCHAR
*str
, size_t len
)
381 ctx
->buf
= heap_alloc(len
*2*sizeof(WCHAR
));
384 ctx
->buf_size
= len
*2;
385 }else if(ctx
->buf_len
+ len
> ctx
->buf_size
) {
389 new_size
= ctx
->buf_size
* 2 + len
;
390 new_buf
= heap_realloc(ctx
->buf
, new_size
*sizeof(WCHAR
));
394 ctx
->buf_size
= new_size
;
398 memcpy(ctx
->buf
+ ctx
->buf_len
, str
, len
*sizeof(WCHAR
));
403 static inline BOOL
append_string(stringify_ctx_t
*ctx
, const WCHAR
*str
)
405 return append_string_len(ctx
, str
, strlenW(str
));
408 static inline BOOL
append_char(stringify_ctx_t
*ctx
, WCHAR c
)
410 return append_string_len(ctx
, &c
, 1);
413 static inline BOOL
append_simple_quote(stringify_ctx_t
*ctx
, WCHAR c
)
415 WCHAR str
[] = {'\\',c
};
416 return append_string_len(ctx
, str
, 2);
419 static HRESULT
maybe_to_primitive(script_ctx_t
*ctx
, jsval_t val
, jsval_t
*r
)
424 if(!is_object_instance(val
) || !get_object(val
) || !(obj
= iface_to_jsdisp(get_object(val
))))
425 return jsval_copy(val
, r
);
427 if(is_class(obj
, JSCLASS_NUMBER
)) {
429 hres
= to_number(ctx
, val
, &n
);
432 *r
= jsval_number(n
);
436 if(is_class(obj
, JSCLASS_STRING
)) {
438 hres
= to_string(ctx
, val
, &str
);
441 *r
= jsval_string(str
);
445 if(is_class(obj
, JSCLASS_BOOLEAN
)) {
446 *r
= jsval_bool(bool_obj_value(obj
));
455 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation Quote) */
456 static HRESULT
json_quote(stringify_ctx_t
*ctx
, const WCHAR
*ptr
, size_t len
)
458 if(!ptr
|| !append_char(ctx
, '"'))
459 return E_OUTOFMEMORY
;
465 if(!append_simple_quote(ctx
, *ptr
))
466 return E_OUTOFMEMORY
;
469 if(!append_simple_quote(ctx
, 'b'))
470 return E_OUTOFMEMORY
;
473 if(!append_simple_quote(ctx
, 'f'))
474 return E_OUTOFMEMORY
;
477 if(!append_simple_quote(ctx
, 'n'))
478 return E_OUTOFMEMORY
;
481 if(!append_simple_quote(ctx
, 'r'))
482 return E_OUTOFMEMORY
;
485 if(!append_simple_quote(ctx
, 't'))
486 return E_OUTOFMEMORY
;
490 const WCHAR formatW
[] = {'\\','u','%','0','4','x',0};
492 sprintfW(buf
, formatW
, *ptr
);
493 if(!append_string(ctx
, buf
))
494 return E_OUTOFMEMORY
;
496 if(!append_char(ctx
, *ptr
))
497 return E_OUTOFMEMORY
;
503 return append_char(ctx
, '"') ? S_OK
: E_OUTOFMEMORY
;
506 static inline BOOL
is_callable(jsdisp_t
*obj
)
508 return is_class(obj
, JSCLASS_FUNCTION
);
511 static HRESULT
stringify(stringify_ctx_t
*ctx
, jsval_t val
);
513 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation JA) */
514 static HRESULT
stringify_array(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
516 unsigned length
, i
, j
;
520 if(is_on_stack(ctx
, obj
)) {
521 FIXME("Found a cycle\n");
525 if(!stringify_push_obj(ctx
, obj
))
526 return E_OUTOFMEMORY
;
528 if(!append_char(ctx
, '['))
529 return E_OUTOFMEMORY
;
531 length
= array_get_length(obj
);
533 for(i
=0; i
< length
; i
++) {
534 if(i
&& !append_char(ctx
, ','))
535 return E_OUTOFMEMORY
;
538 if(!append_char(ctx
, '\n'))
539 return E_OUTOFMEMORY
;
541 for(j
=0; j
< ctx
->stack_top
; j
++) {
542 if(!append_string(ctx
, ctx
->gap
))
543 return E_OUTOFMEMORY
;
547 hres
= jsdisp_get_idx(obj
, i
, &val
);
551 hres
= stringify(ctx
, val
);
555 if(hres
== S_FALSE
&& !append_string(ctx
, nullW
))
556 return E_OUTOFMEMORY
;
559 if((length
&& *ctx
->gap
&& !append_char(ctx
, '\n')) || !append_char(ctx
, ']'))
560 return E_OUTOFMEMORY
;
562 stringify_pop_obj(ctx
);
566 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation JO) */
567 static HRESULT
stringify_object(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
569 DISPID dispid
= DISPID_STARTENUM
;
570 jsval_t val
= jsval_undefined();
571 unsigned prop_cnt
= 0, i
;
576 if(is_on_stack(ctx
, obj
)) {
577 FIXME("Found a cycle\n");
581 if(!stringify_push_obj(ctx
, obj
))
582 return E_OUTOFMEMORY
;
584 if(!append_char(ctx
, '{'))
585 return E_OUTOFMEMORY
;
587 while((hres
= IDispatchEx_GetNextDispID(&obj
->IDispatchEx_iface
, fdexEnumDefault
, dispid
, &dispid
)) == S_OK
) {
589 hres
= jsdisp_propget(obj
, dispid
, &val
);
593 if(is_undefined(val
))
596 stepback
= ctx
->buf_len
;
598 if(prop_cnt
&& !append_char(ctx
, ',')) {
599 hres
= E_OUTOFMEMORY
;
604 if(!append_char(ctx
, '\n')) {
605 hres
= E_OUTOFMEMORY
;
609 for(i
=0; i
< ctx
->stack_top
; i
++) {
610 if(!append_string(ctx
, ctx
->gap
)) {
611 hres
= E_OUTOFMEMORY
;
617 hres
= IDispatchEx_GetMemberName(&obj
->IDispatchEx_iface
, dispid
, &prop_name
);
621 hres
= json_quote(ctx
, prop_name
, SysStringLen(prop_name
));
622 SysFreeString(prop_name
);
626 if(!append_char(ctx
, ':') || (*ctx
->gap
&& !append_char(ctx
, ' '))) {
627 hres
= E_OUTOFMEMORY
;
631 hres
= stringify(ctx
, val
);
635 if(hres
== S_FALSE
) {
636 ctx
->buf_len
= stepback
;
646 if(prop_cnt
&& *ctx
->gap
) {
647 if(!append_char(ctx
, '\n'))
648 return E_OUTOFMEMORY
;
650 for(i
=1; i
< ctx
->stack_top
; i
++) {
651 if(!append_string(ctx
, ctx
->gap
)) {
652 hres
= E_OUTOFMEMORY
;
658 if(!append_char(ctx
, '}'))
659 return E_OUTOFMEMORY
;
661 stringify_pop_obj(ctx
);
665 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation Str) */
666 static HRESULT
stringify(stringify_ctx_t
*ctx
, jsval_t val
)
671 if(is_object_instance(val
) && get_object(val
)) {
675 obj
= iface_to_jsdisp(get_object(val
));
679 hres
= jsdisp_get_id(obj
, toJSONW
, 0, &id
);
682 FIXME("Use toJSON.\n");
685 /* FIXME: Support replacer replacer. */
687 hres
= maybe_to_primitive(ctx
->ctx
, val
, &value
);
691 switch(jsval_type(value
)) {
693 if(!append_string(ctx
, nullW
))
694 hres
= E_OUTOFMEMORY
;
697 if(!append_string(ctx
, get_bool(value
) ? trueW
: falseW
))
698 hres
= E_OUTOFMEMORY
;
701 jsstr_t
*str
= get_string(value
);
702 const WCHAR
*ptr
= jsstr_flatten(str
);
704 hres
= json_quote(ctx
, ptr
, jsstr_length(str
));
706 hres
= E_OUTOFMEMORY
;
710 double n
= get_number(value
);
715 /* FIXME: Optimize. There is no need for jsstr_t here. */
716 hres
= double_to_string(n
, &str
);
720 ptr
= jsstr_flatten(str
);
722 hres
= ptr
&& !append_string_len(ctx
, ptr
, jsstr_length(str
)) ? E_OUTOFMEMORY
: S_OK
;
725 if(!append_string(ctx
, nullW
))
726 hres
= E_OUTOFMEMORY
;
733 obj
= iface_to_jsdisp(get_object(value
));
739 if(!is_callable(obj
))
740 hres
= is_class(obj
, JSCLASS_ARRAY
) ? stringify_array(ctx
, obj
) : stringify_object(ctx
, obj
);
756 jsval_release(value
);
760 /* ECMA-262 5.1 Edition 15.12.3 */
761 static HRESULT
JSON_stringify(script_ctx_t
*ctx
, vdisp_t
*jsthis
, WORD flags
, unsigned argc
, jsval_t
*argv
, jsval_t
*r
)
763 stringify_ctx_t stringify_ctx
= {ctx
, NULL
,0,0, NULL
,0,0, {0}};
768 if(argc
>= 2 && is_object_instance(argv
[1])) {
769 FIXME("Replacer %s not yet supported\n", debugstr_jsval(argv
[1]));
776 hres
= maybe_to_primitive(ctx
, argv
[2], &space_val
);
780 if(is_number(space_val
)) {
781 double n
= get_number(space_val
);
787 for(i
=0; i
< len
; i
++)
788 stringify_ctx
.gap
[i
] = ' ';
789 stringify_ctx
.gap
[len
] = 0;
791 }else if(is_string(space_val
)) {
792 jsstr_t
*space_str
= get_string(space_val
);
793 size_t len
= jsstr_length(space_str
);
796 jsstr_extract(space_str
, 0, len
, stringify_ctx
.gap
);
799 jsval_release(space_val
);
802 hres
= stringify(&stringify_ctx
, argv
[0]);
803 if(SUCCEEDED(hres
) && r
) {
804 assert(!stringify_ctx
.stack_top
);
807 jsstr_t
*ret
= jsstr_alloc_len(stringify_ctx
.buf
, stringify_ctx
.buf_len
);
809 *r
= jsval_string(ret
);
811 hres
= E_OUTOFMEMORY
;
813 *r
= jsval_undefined();
817 heap_free(stringify_ctx
.buf
);
818 heap_free(stringify_ctx
.stack
);
822 static const builtin_prop_t JSON_props
[] = {
823 {parseW
, JSON_parse
, PROPF_METHOD
|2},
824 {stringifyW
, JSON_stringify
, PROPF_METHOD
|3}
827 static const builtin_info_t JSON_info
= {
830 sizeof(JSON_props
)/sizeof(*JSON_props
),
836 HRESULT
create_json(script_ctx_t
*ctx
, jsdisp_t
**ret
)
841 json
= heap_alloc_zero(sizeof(*json
));
843 return E_OUTOFMEMORY
;
845 hres
= init_dispex_from_constr(json
, ctx
, &JSON_info
, ctx
->object_constr
);