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
21 static const WCHAR parseW
[] = {'p','a','r','s','e',0};
22 static const WCHAR stringifyW
[] = {'s','t','r','i','n','g','i','f','y',0};
24 static const WCHAR nullW
[] = {'n','u','l','l',0};
25 static const WCHAR trueW
[] = {'t','r','u','e',0};
26 static const WCHAR falseW
[] = {'f','a','l','s','e',0};
28 static const WCHAR toJSONW
[] = {'t','o','J','S','O','N',0};
36 static BOOL
is_json_space(WCHAR c
)
38 return c
== ' ' || c
== '\t' || c
== '\n' || c
== '\r';
41 static WCHAR
skip_spaces(json_parse_ctx_t
*ctx
)
43 while(is_json_space(*ctx
->ptr
))
48 static BOOL
is_keyword(json_parse_ctx_t
*ctx
, const WCHAR
*keyword
)
51 for(i
=0; keyword
[i
]; i
++) {
52 if(!ctx
->ptr
[i
] || keyword
[i
] != ctx
->ptr
[i
])
55 if(is_identifier_char(ctx
->ptr
[i
]))
61 /* ECMA-262 5.1 Edition 15.12.1.1 */
62 static HRESULT
parse_json_string(json_parse_ctx_t
*ctx
, WCHAR
**r
)
64 const WCHAR
*ptr
= ++ctx
->ptr
;
68 while(*ctx
->ptr
&& *ctx
->ptr
!= '"') {
69 if(*ctx
->ptr
++ == '\\')
73 FIXME("unterminated string\n");
78 buf
= heap_alloc((len
+1)*sizeof(WCHAR
));
82 memcpy(buf
, ptr
, len
*sizeof(WCHAR
));
86 FIXME("unescape failed\n");
96 /* ECMA-262 5.1 Edition 15.12.1.2 */
97 static HRESULT
parse_json_value(json_parse_ctx_t
*ctx
, jsval_t
*r
)
101 switch(skip_spaces(ctx
)) {
103 /* JSONNullLiteral */
105 if(!is_keyword(ctx
, nullW
))
110 /* JSONBooleanLiteral */
112 if(!is_keyword(ctx
, trueW
))
114 *r
= jsval_bool(TRUE
);
117 if(!is_keyword(ctx
, falseW
))
119 *r
= jsval_bool(FALSE
);
128 hres
= create_object(ctx
->ctx
, NULL
, &obj
);
133 if(skip_spaces(ctx
) == '}') {
142 hres
= parse_json_string(ctx
, &prop_name
);
146 if(skip_spaces(ctx
) != ':') {
147 FIXME("missing ':'\n");
148 heap_free(prop_name
);
153 hres
= parse_json_value(ctx
, &val
);
154 if(SUCCEEDED(hres
)) {
155 hres
= jsdisp_propput_name(obj
, prop_name
, val
);
158 heap_free(prop_name
);
162 if(skip_spaces(ctx
) == '}') {
168 if(*ctx
->ptr
++ != ',') {
169 FIXME("expected ','\n");
184 hres
= parse_json_string(ctx
, &string
);
188 /* FIXME: avoid reallocation */
189 str
= jsstr_alloc(string
);
192 return E_OUTOFMEMORY
;
194 *r
= jsval_string(str
);
204 hres
= create_array(ctx
->ctx
, 0, &array
);
209 if(skip_spaces(ctx
) == ']') {
211 *r
= jsval_obj(array
);
216 hres
= parse_json_value(ctx
, &val
);
220 hres
= jsdisp_propput_idx(array
, i
, val
);
225 if(skip_spaces(ctx
) == ']') {
227 *r
= jsval_obj(array
);
231 if(*ctx
->ptr
!= ',') {
232 FIXME("expected ','\n");
240 jsdisp_release(array
);
249 if(*ctx
->ptr
== '-') {
255 if(!isdigitW(*ctx
->ptr
))
258 if(*ctx
->ptr
== '0') {
261 if(is_identifier_char(*ctx
->ptr
))
264 hres
= parse_decimal(&ctx
->ptr
, ctx
->end
, &n
);
269 *r
= jsval_number(sign
*n
);
274 FIXME("Syntax error at %s\n", debugstr_w(ctx
->ptr
));
278 /* ECMA-262 5.1 Edition 15.12.2 */
279 static HRESULT
JSON_parse(script_ctx_t
*ctx
, vdisp_t
*jsthis
, WORD flags
, unsigned argc
, jsval_t
*argv
, jsval_t
*r
)
281 json_parse_ctx_t parse_ctx
;
288 FIXME("Unsupported args\n");
292 hres
= to_flat_string(ctx
, argv
[0], &str
, &buf
);
296 TRACE("%s\n", debugstr_w(buf
));
299 parse_ctx
.end
= buf
+ jsstr_length(str
);
301 hres
= parse_json_value(&parse_ctx
, &ret
);
306 if(skip_spaces(&parse_ctx
)) {
307 FIXME("syntax error\n");
330 WCHAR gap
[11]; /* according to the spec, it's no longer than 10 chars */
333 static BOOL
stringify_push_obj(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
335 if(!ctx
->stack_size
) {
336 ctx
->stack
= heap_alloc(4*sizeof(*ctx
->stack
));
340 }else if(ctx
->stack_top
== ctx
->stack_size
) {
341 jsdisp_t
**new_stack
;
343 new_stack
= heap_realloc(ctx
->stack
, ctx
->stack_size
*2*sizeof(*ctx
->stack
));
346 ctx
->stack
= new_stack
;
347 ctx
->stack_size
*= 2;
350 ctx
->stack
[ctx
->stack_top
++] = obj
;
354 static void stringify_pop_obj(stringify_ctx_t
*ctx
)
359 static BOOL
is_on_stack(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
361 size_t i
= ctx
->stack_top
;
363 if(ctx
->stack
[i
] == obj
)
369 static BOOL
append_string_len(stringify_ctx_t
*ctx
, const WCHAR
*str
, size_t len
)
372 ctx
->buf
= heap_alloc(len
*2*sizeof(WCHAR
));
375 ctx
->buf_size
= len
*2;
376 }else if(ctx
->buf_len
+ len
> ctx
->buf_size
) {
380 new_size
= ctx
->buf_size
* 2 + len
;
381 new_buf
= heap_realloc(ctx
->buf
, new_size
*sizeof(WCHAR
));
385 ctx
->buf_size
= new_size
;
389 memcpy(ctx
->buf
+ ctx
->buf_len
, str
, len
*sizeof(WCHAR
));
394 static inline BOOL
append_string(stringify_ctx_t
*ctx
, const WCHAR
*str
)
396 return append_string_len(ctx
, str
, strlenW(str
));
399 static inline BOOL
append_char(stringify_ctx_t
*ctx
, WCHAR c
)
401 return append_string_len(ctx
, &c
, 1);
404 static inline BOOL
append_simple_quote(stringify_ctx_t
*ctx
, WCHAR c
)
406 WCHAR str
[] = {'\\',c
};
407 return append_string_len(ctx
, str
, 2);
410 static HRESULT
maybe_to_primitive(script_ctx_t
*ctx
, jsval_t val
, jsval_t
*r
)
415 if(!is_object_instance(val
) || !get_object(val
) || !(obj
= iface_to_jsdisp(get_object(val
))))
416 return jsval_copy(val
, r
);
418 if(is_class(obj
, JSCLASS_NUMBER
)) {
420 hres
= to_number(ctx
, val
, &n
);
423 *r
= jsval_number(n
);
427 if(is_class(obj
, JSCLASS_STRING
)) {
429 hres
= to_string(ctx
, val
, &str
);
432 *r
= jsval_string(str
);
436 if(is_class(obj
, JSCLASS_BOOLEAN
)) {
437 *r
= jsval_bool(bool_obj_value(obj
));
446 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation Quote) */
447 static HRESULT
json_quote(stringify_ctx_t
*ctx
, const WCHAR
*ptr
, size_t len
)
449 if(!ptr
|| !append_char(ctx
, '"'))
450 return E_OUTOFMEMORY
;
456 if(!append_simple_quote(ctx
, *ptr
))
457 return E_OUTOFMEMORY
;
460 if(!append_simple_quote(ctx
, 'b'))
461 return E_OUTOFMEMORY
;
464 if(!append_simple_quote(ctx
, 'f'))
465 return E_OUTOFMEMORY
;
468 if(!append_simple_quote(ctx
, 'n'))
469 return E_OUTOFMEMORY
;
472 if(!append_simple_quote(ctx
, 'r'))
473 return E_OUTOFMEMORY
;
476 if(!append_simple_quote(ctx
, 't'))
477 return E_OUTOFMEMORY
;
481 const WCHAR formatW
[] = {'\\','u','%','0','4','x',0};
483 sprintfW(buf
, formatW
, *ptr
);
484 if(!append_string(ctx
, buf
))
485 return E_OUTOFMEMORY
;
487 if(!append_char(ctx
, *ptr
))
488 return E_OUTOFMEMORY
;
494 return append_char(ctx
, '"') ? S_OK
: E_OUTOFMEMORY
;
497 static inline BOOL
is_callable(jsdisp_t
*obj
)
499 return is_class(obj
, JSCLASS_FUNCTION
);
502 static HRESULT
stringify(stringify_ctx_t
*ctx
, jsval_t val
);
504 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation JA) */
505 static HRESULT
stringify_array(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
507 unsigned length
, i
, j
;
511 if(is_on_stack(ctx
, obj
)) {
512 FIXME("Found a cycle\n");
516 if(!stringify_push_obj(ctx
, obj
))
517 return E_OUTOFMEMORY
;
519 if(!append_char(ctx
, '['))
520 return E_OUTOFMEMORY
;
522 length
= array_get_length(obj
);
524 for(i
=0; i
< length
; i
++) {
525 if(i
&& !append_char(ctx
, ','))
526 return E_OUTOFMEMORY
;
529 if(!append_char(ctx
, '\n'))
530 return E_OUTOFMEMORY
;
532 for(j
=0; j
< ctx
->stack_top
; j
++) {
533 if(!append_string(ctx
, ctx
->gap
))
534 return E_OUTOFMEMORY
;
538 hres
= jsdisp_get_idx(obj
, i
, &val
);
542 hres
= stringify(ctx
, val
);
546 if(hres
== S_FALSE
&& !append_string(ctx
, nullW
))
547 return E_OUTOFMEMORY
;
550 if((length
&& *ctx
->gap
&& !append_char(ctx
, '\n')) || !append_char(ctx
, ']'))
551 return E_OUTOFMEMORY
;
553 stringify_pop_obj(ctx
);
557 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation JO) */
558 static HRESULT
stringify_object(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
560 DISPID dispid
= DISPID_STARTENUM
;
561 jsval_t val
= jsval_undefined();
562 unsigned prop_cnt
= 0, i
;
567 if(is_on_stack(ctx
, obj
)) {
568 FIXME("Found a cycle\n");
572 if(!stringify_push_obj(ctx
, obj
))
573 return E_OUTOFMEMORY
;
575 if(!append_char(ctx
, '{'))
576 return E_OUTOFMEMORY
;
578 while((hres
= IDispatchEx_GetNextDispID(&obj
->IDispatchEx_iface
, fdexEnumDefault
, dispid
, &dispid
)) == S_OK
) {
580 hres
= jsdisp_propget(obj
, dispid
, &val
);
584 if(is_undefined(val
))
587 stepback
= ctx
->buf_len
;
589 if(prop_cnt
&& !append_char(ctx
, ',')) {
590 hres
= E_OUTOFMEMORY
;
595 if(!append_char(ctx
, '\n')) {
596 hres
= E_OUTOFMEMORY
;
600 for(i
=0; i
< ctx
->stack_top
; i
++) {
601 if(!append_string(ctx
, ctx
->gap
)) {
602 hres
= E_OUTOFMEMORY
;
608 hres
= IDispatchEx_GetMemberName(&obj
->IDispatchEx_iface
, dispid
, &prop_name
);
612 hres
= json_quote(ctx
, prop_name
, SysStringLen(prop_name
));
613 SysFreeString(prop_name
);
617 if(!append_char(ctx
, ':') || (*ctx
->gap
&& !append_char(ctx
, ' '))) {
618 hres
= E_OUTOFMEMORY
;
622 hres
= stringify(ctx
, val
);
626 if(hres
== S_FALSE
) {
627 ctx
->buf_len
= stepback
;
637 if(prop_cnt
&& *ctx
->gap
) {
638 if(!append_char(ctx
, '\n'))
639 return E_OUTOFMEMORY
;
641 for(i
=1; i
< ctx
->stack_top
; i
++) {
642 if(!append_string(ctx
, ctx
->gap
)) {
643 hres
= E_OUTOFMEMORY
;
649 if(!append_char(ctx
, '}'))
650 return E_OUTOFMEMORY
;
652 stringify_pop_obj(ctx
);
656 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation Str) */
657 static HRESULT
stringify(stringify_ctx_t
*ctx
, jsval_t val
)
662 if(is_object_instance(val
) && get_object(val
)) {
666 obj
= iface_to_jsdisp(get_object(val
));
670 hres
= jsdisp_get_id(obj
, toJSONW
, 0, &id
);
673 FIXME("Use toJSON.\n");
676 /* FIXME: Support replacer replacer. */
678 hres
= maybe_to_primitive(ctx
->ctx
, val
, &value
);
682 switch(jsval_type(value
)) {
684 if(!append_string(ctx
, nullW
))
685 hres
= E_OUTOFMEMORY
;
688 if(!append_string(ctx
, get_bool(value
) ? trueW
: falseW
))
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
, nullW
))
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
, NULL
,0,0, NULL
,0,0, {0}};
759 if(argc
>= 2 && is_object_instance(argv
[1])) {
760 FIXME("Replacer %s not yet supported\n", debugstr_jsval(argv
[1]));
767 hres
= maybe_to_primitive(ctx
, argv
[2], &space_val
);
771 if(is_number(space_val
)) {
772 double n
= get_number(space_val
);
778 for(i
=0; i
< len
; i
++)
779 stringify_ctx
.gap
[i
] = ' ';
780 stringify_ctx
.gap
[len
] = 0;
782 }else if(is_string(space_val
)) {
783 jsstr_t
*space_str
= get_string(space_val
);
784 size_t len
= jsstr_length(space_str
);
787 jsstr_extract(space_str
, 0, len
, stringify_ctx
.gap
);
790 jsval_release(space_val
);
793 hres
= stringify(&stringify_ctx
, argv
[0]);
794 if(SUCCEEDED(hres
) && r
) {
795 assert(!stringify_ctx
.stack_top
);
798 jsstr_t
*ret
= jsstr_alloc_len(stringify_ctx
.buf
, stringify_ctx
.buf_len
);
800 *r
= jsval_string(ret
);
802 hres
= E_OUTOFMEMORY
;
804 *r
= jsval_undefined();
808 heap_free(stringify_ctx
.buf
);
809 heap_free(stringify_ctx
.stack
);
813 static const builtin_prop_t JSON_props
[] = {
814 {parseW
, JSON_parse
, PROPF_METHOD
|2},
815 {stringifyW
, JSON_stringify
, PROPF_METHOD
|3}
818 static const builtin_info_t JSON_info
= {
821 sizeof(JSON_props
)/sizeof(*JSON_props
),
827 HRESULT
create_json(script_ctx_t
*ctx
, jsdisp_t
**ret
)
832 json
= heap_alloc_zero(sizeof(*json
));
834 return E_OUTOFMEMORY
;
836 hres
= init_dispex_from_constr(json
, ctx
, &JSON_info
, ctx
->object_constr
);