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
);
298 if(skip_spaces(&parse_ctx
)) {
299 FIXME("syntax error\n");
322 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
, jsval_t val
);
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 hres
= jsdisp_get_idx(obj
, i
, &val
);
530 if(SUCCEEDED(hres
)) {
531 hres
= stringify(ctx
, val
);
534 if(hres
== S_FALSE
&& !append_string(ctx
, L
"null"))
535 return E_OUTOFMEMORY
;
536 }else if(hres
== DISP_E_UNKNOWNNAME
) {
537 if(!append_string(ctx
, L
"null"))
538 return E_OUTOFMEMORY
;
544 if((length
&& *ctx
->gap
&& !append_char(ctx
, '\n')) || !append_char(ctx
, ']'))
545 return E_OUTOFMEMORY
;
547 stringify_pop_obj(ctx
);
551 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation JO) */
552 static HRESULT
stringify_object(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
554 DISPID dispid
= DISPID_STARTENUM
;
555 jsval_t val
= jsval_undefined();
556 unsigned prop_cnt
= 0, i
;
561 if(is_on_stack(ctx
, obj
)) {
562 FIXME("Found a cycle\n");
566 if(!stringify_push_obj(ctx
, obj
))
567 return E_OUTOFMEMORY
;
569 if(!append_char(ctx
, '{'))
570 return E_OUTOFMEMORY
;
572 while((hres
= IDispatchEx_GetNextDispID(&obj
->IDispatchEx_iface
, fdexEnumDefault
, dispid
, &dispid
)) == S_OK
) {
574 hres
= jsdisp_propget(obj
, dispid
, &val
);
578 if(is_undefined(val
))
581 stepback
= ctx
->buf_len
;
583 if(prop_cnt
&& !append_char(ctx
, ',')) {
584 hres
= E_OUTOFMEMORY
;
589 if(!append_char(ctx
, '\n')) {
590 hres
= E_OUTOFMEMORY
;
594 for(i
=0; i
< ctx
->stack_top
; i
++) {
595 if(!append_string(ctx
, ctx
->gap
)) {
596 hres
= E_OUTOFMEMORY
;
602 hres
= IDispatchEx_GetMemberName(&obj
->IDispatchEx_iface
, dispid
, &prop_name
);
606 hres
= json_quote(ctx
, prop_name
, SysStringLen(prop_name
));
607 SysFreeString(prop_name
);
611 if(!append_char(ctx
, ':') || (*ctx
->gap
&& !append_char(ctx
, ' '))) {
612 hres
= E_OUTOFMEMORY
;
616 hres
= stringify(ctx
, val
);
620 if(hres
== S_FALSE
) {
621 ctx
->buf_len
= stepback
;
631 if(prop_cnt
&& *ctx
->gap
) {
632 if(!append_char(ctx
, '\n'))
633 return E_OUTOFMEMORY
;
635 for(i
=1; i
< ctx
->stack_top
; i
++) {
636 if(!append_string(ctx
, ctx
->gap
)) {
637 hres
= E_OUTOFMEMORY
;
643 if(!append_char(ctx
, '}'))
644 return E_OUTOFMEMORY
;
646 stringify_pop_obj(ctx
);
650 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation Str) */
651 static HRESULT
stringify(stringify_ctx_t
*ctx
, jsval_t val
)
656 if(is_object_instance(val
) && get_object(val
)) {
660 obj
= iface_to_jsdisp(get_object(val
));
664 hres
= jsdisp_get_id(obj
, L
"toJSON", 0, &id
);
667 FIXME("Use toJSON.\n");
670 /* FIXME: Support replacer replacer. */
672 hres
= maybe_to_primitive(ctx
->ctx
, val
, &value
);
676 switch(jsval_type(value
)) {
678 if(!append_string(ctx
, L
"null"))
679 hres
= E_OUTOFMEMORY
;
682 if(!append_string(ctx
, get_bool(value
) ? L
"true" : L
"false"))
683 hres
= E_OUTOFMEMORY
;
686 jsstr_t
*str
= get_string(value
);
687 const WCHAR
*ptr
= jsstr_flatten(str
);
689 hres
= json_quote(ctx
, ptr
, jsstr_length(str
));
691 hres
= E_OUTOFMEMORY
;
695 double n
= get_number(value
);
700 /* FIXME: Optimize. There is no need for jsstr_t here. */
701 hres
= double_to_string(n
, &str
);
705 ptr
= jsstr_flatten(str
);
707 hres
= ptr
&& !append_string_len(ctx
, ptr
, jsstr_length(str
)) ? E_OUTOFMEMORY
: S_OK
;
710 if(!append_string(ctx
, L
"null"))
711 hres
= E_OUTOFMEMORY
;
718 obj
= iface_to_jsdisp(get_object(value
));
724 if(!is_callable(obj
))
725 hres
= is_class(obj
, JSCLASS_ARRAY
) ? stringify_array(ctx
, obj
) : stringify_object(ctx
, obj
);
741 jsval_release(value
);
745 /* ECMA-262 5.1 Edition 15.12.3 */
746 static HRESULT
JSON_stringify(script_ctx_t
*ctx
, vdisp_t
*jsthis
, WORD flags
, unsigned argc
, jsval_t
*argv
, jsval_t
*r
)
748 stringify_ctx_t stringify_ctx
= {ctx
, NULL
,0,0, NULL
,0,0, {0}};
755 *r
= jsval_undefined();
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 {L
"parse", JSON_parse
, PROPF_METHOD
|2},
815 {L
"stringify", JSON_stringify
, PROPF_METHOD
|3}
818 static const builtin_info_t JSON_info
= {
821 ARRAY_SIZE(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
);