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 nullW
[] = {'n','u','l','l',0};
30 static const WCHAR trueW
[] = {'t','r','u','e',0};
31 static const WCHAR falseW
[] = {'f','a','l','s','e',0};
33 static const WCHAR toJSONW
[] = {'t','o','J','S','O','N',0};
41 static BOOL
is_json_space(WCHAR c
)
43 return c
== ' ' || c
== '\t' || c
== '\n' || c
== '\r';
46 static WCHAR
skip_spaces(json_parse_ctx_t
*ctx
)
48 while(is_json_space(*ctx
->ptr
))
53 static BOOL
is_keyword(json_parse_ctx_t
*ctx
, const WCHAR
*keyword
)
56 for(i
=0; keyword
[i
]; i
++) {
57 if(!ctx
->ptr
[i
] || keyword
[i
] != ctx
->ptr
[i
])
60 if(is_identifier_char(ctx
->ptr
[i
]))
66 /* ECMA-262 5.1 Edition 15.12.1.1 */
67 static HRESULT
parse_json_string(json_parse_ctx_t
*ctx
, WCHAR
**r
)
69 const WCHAR
*ptr
= ++ctx
->ptr
;
73 while(*ctx
->ptr
&& *ctx
->ptr
!= '"') {
74 if(*ctx
->ptr
++ == '\\')
78 FIXME("unterminated string\n");
83 buf
= heap_alloc((len
+1)*sizeof(WCHAR
));
87 memcpy(buf
, ptr
, len
*sizeof(WCHAR
));
89 if(!unescape(buf
, &len
)) {
90 FIXME("unescape failed\n");
101 /* ECMA-262 5.1 Edition 15.12.1.2 */
102 static HRESULT
parse_json_value(json_parse_ctx_t
*ctx
, jsval_t
*r
)
106 switch(skip_spaces(ctx
)) {
108 /* JSONNullLiteral */
110 if(!is_keyword(ctx
, nullW
))
115 /* JSONBooleanLiteral */
117 if(!is_keyword(ctx
, trueW
))
119 *r
= jsval_bool(TRUE
);
122 if(!is_keyword(ctx
, falseW
))
124 *r
= jsval_bool(FALSE
);
133 hres
= create_object(ctx
->ctx
, NULL
, &obj
);
138 if(skip_spaces(ctx
) == '}') {
147 hres
= parse_json_string(ctx
, &prop_name
);
151 if(skip_spaces(ctx
) != ':') {
152 FIXME("missing ':'\n");
153 heap_free(prop_name
);
158 hres
= parse_json_value(ctx
, &val
);
159 if(SUCCEEDED(hres
)) {
160 hres
= jsdisp_propput_name(obj
, prop_name
, val
);
163 heap_free(prop_name
);
167 if(skip_spaces(ctx
) == '}') {
173 if(*ctx
->ptr
++ != ',') {
174 FIXME("expected ','\n");
189 hres
= parse_json_string(ctx
, &string
);
193 /* FIXME: avoid reallocation */
194 str
= jsstr_alloc(string
);
197 return E_OUTOFMEMORY
;
199 *r
= jsval_string(str
);
209 hres
= create_array(ctx
->ctx
, 0, &array
);
214 if(skip_spaces(ctx
) == ']') {
216 *r
= jsval_obj(array
);
221 hres
= parse_json_value(ctx
, &val
);
225 hres
= jsdisp_propput_idx(array
, i
, val
);
230 if(skip_spaces(ctx
) == ']') {
232 *r
= jsval_obj(array
);
236 if(*ctx
->ptr
!= ',') {
237 FIXME("expected ','\n");
245 jsdisp_release(array
);
254 if(*ctx
->ptr
== '-') {
260 if(*ctx
->ptr
== '0' && ctx
->ptr
+ 1 < ctx
->end
&& is_digit(ctx
->ptr
[1]))
263 hres
= parse_decimal(&ctx
->ptr
, ctx
->end
, &n
);
267 *r
= jsval_number(sign
*n
);
272 FIXME("Syntax error at %s\n", debugstr_w(ctx
->ptr
));
276 /* ECMA-262 5.1 Edition 15.12.2 */
277 static HRESULT
JSON_parse(script_ctx_t
*ctx
, vdisp_t
*jsthis
, WORD flags
, unsigned argc
, jsval_t
*argv
, jsval_t
*r
)
279 json_parse_ctx_t parse_ctx
;
286 FIXME("Unsupported args\n");
290 hres
= to_flat_string(ctx
, argv
[0], &str
, &buf
);
294 TRACE("%s\n", debugstr_w(buf
));
297 parse_ctx
.end
= buf
+ jsstr_length(str
);
299 hres
= parse_json_value(&parse_ctx
, &ret
);
304 if(skip_spaces(&parse_ctx
)) {
305 FIXME("syntax error\n");
328 WCHAR gap
[11]; /* according to the spec, it's no longer than 10 chars */
331 static BOOL
stringify_push_obj(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
333 if(!ctx
->stack_size
) {
334 ctx
->stack
= heap_alloc(4*sizeof(*ctx
->stack
));
338 }else if(ctx
->stack_top
== ctx
->stack_size
) {
339 jsdisp_t
**new_stack
;
341 new_stack
= heap_realloc(ctx
->stack
, ctx
->stack_size
*2*sizeof(*ctx
->stack
));
344 ctx
->stack
= new_stack
;
345 ctx
->stack_size
*= 2;
348 ctx
->stack
[ctx
->stack_top
++] = obj
;
352 static void stringify_pop_obj(stringify_ctx_t
*ctx
)
357 static BOOL
is_on_stack(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
359 size_t i
= ctx
->stack_top
;
361 if(ctx
->stack
[i
] == obj
)
367 static BOOL
append_string_len(stringify_ctx_t
*ctx
, const WCHAR
*str
, size_t len
)
370 ctx
->buf
= heap_alloc(len
*2*sizeof(WCHAR
));
373 ctx
->buf_size
= len
*2;
374 }else if(ctx
->buf_len
+ len
> ctx
->buf_size
) {
378 new_size
= ctx
->buf_size
* 2 + len
;
379 new_buf
= heap_realloc(ctx
->buf
, new_size
*sizeof(WCHAR
));
383 ctx
->buf_size
= new_size
;
387 memcpy(ctx
->buf
+ ctx
->buf_len
, str
, len
*sizeof(WCHAR
));
392 static inline BOOL
append_string(stringify_ctx_t
*ctx
, const WCHAR
*str
)
394 return append_string_len(ctx
, str
, lstrlenW(str
));
397 static inline BOOL
append_char(stringify_ctx_t
*ctx
, WCHAR c
)
399 return append_string_len(ctx
, &c
, 1);
402 static inline BOOL
append_simple_quote(stringify_ctx_t
*ctx
, WCHAR c
)
404 WCHAR str
[] = {'\\',c
};
405 return append_string_len(ctx
, str
, 2);
408 static HRESULT
maybe_to_primitive(script_ctx_t
*ctx
, jsval_t val
, jsval_t
*r
)
413 if(!is_object_instance(val
) || !get_object(val
) || !(obj
= iface_to_jsdisp(get_object(val
))))
414 return jsval_copy(val
, r
);
416 if(is_class(obj
, JSCLASS_NUMBER
)) {
418 hres
= to_number(ctx
, val
, &n
);
421 *r
= jsval_number(n
);
425 if(is_class(obj
, JSCLASS_STRING
)) {
427 hres
= to_string(ctx
, val
, &str
);
430 *r
= jsval_string(str
);
434 if(is_class(obj
, JSCLASS_BOOLEAN
)) {
435 *r
= jsval_bool(bool_obj_value(obj
));
444 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation Quote) */
445 static HRESULT
json_quote(stringify_ctx_t
*ctx
, const WCHAR
*ptr
, size_t len
)
447 if(!ptr
|| !append_char(ctx
, '"'))
448 return E_OUTOFMEMORY
;
454 if(!append_simple_quote(ctx
, *ptr
))
455 return E_OUTOFMEMORY
;
458 if(!append_simple_quote(ctx
, 'b'))
459 return E_OUTOFMEMORY
;
462 if(!append_simple_quote(ctx
, 'f'))
463 return E_OUTOFMEMORY
;
466 if(!append_simple_quote(ctx
, 'n'))
467 return E_OUTOFMEMORY
;
470 if(!append_simple_quote(ctx
, 'r'))
471 return E_OUTOFMEMORY
;
474 if(!append_simple_quote(ctx
, 't'))
475 return E_OUTOFMEMORY
;
479 static const WCHAR formatW
[] = {'\\','u','%','0','4','x',0};
481 swprintf(buf
, ARRAY_SIZE(buf
), formatW
, *ptr
);
482 if(!append_string(ctx
, buf
))
483 return E_OUTOFMEMORY
;
485 if(!append_char(ctx
, *ptr
))
486 return E_OUTOFMEMORY
;
492 return append_char(ctx
, '"') ? S_OK
: E_OUTOFMEMORY
;
495 static inline BOOL
is_callable(jsdisp_t
*obj
)
497 return is_class(obj
, JSCLASS_FUNCTION
);
500 static HRESULT
stringify(stringify_ctx_t
*ctx
, jsval_t val
);
502 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation JA) */
503 static HRESULT
stringify_array(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
505 unsigned length
, i
, j
;
509 if(is_on_stack(ctx
, obj
)) {
510 FIXME("Found a cycle\n");
514 if(!stringify_push_obj(ctx
, obj
))
515 return E_OUTOFMEMORY
;
517 if(!append_char(ctx
, '['))
518 return E_OUTOFMEMORY
;
520 length
= array_get_length(obj
);
522 for(i
=0; i
< length
; i
++) {
523 if(i
&& !append_char(ctx
, ','))
524 return E_OUTOFMEMORY
;
527 if(!append_char(ctx
, '\n'))
528 return E_OUTOFMEMORY
;
530 for(j
=0; j
< ctx
->stack_top
; j
++) {
531 if(!append_string(ctx
, ctx
->gap
))
532 return E_OUTOFMEMORY
;
536 hres
= jsdisp_get_idx(obj
, i
, &val
);
537 if(SUCCEEDED(hres
)) {
538 hres
= stringify(ctx
, val
);
541 if(hres
== S_FALSE
&& !append_string(ctx
, nullW
))
542 return E_OUTOFMEMORY
;
543 }else if(hres
== DISP_E_UNKNOWNNAME
) {
544 if(!append_string(ctx
, nullW
))
545 return E_OUTOFMEMORY
;
551 if((length
&& *ctx
->gap
&& !append_char(ctx
, '\n')) || !append_char(ctx
, ']'))
552 return E_OUTOFMEMORY
;
554 stringify_pop_obj(ctx
);
558 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation JO) */
559 static HRESULT
stringify_object(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
561 DISPID dispid
= DISPID_STARTENUM
;
562 jsval_t val
= jsval_undefined();
563 unsigned prop_cnt
= 0, i
;
568 if(is_on_stack(ctx
, obj
)) {
569 FIXME("Found a cycle\n");
573 if(!stringify_push_obj(ctx
, obj
))
574 return E_OUTOFMEMORY
;
576 if(!append_char(ctx
, '{'))
577 return E_OUTOFMEMORY
;
579 while((hres
= IDispatchEx_GetNextDispID(&obj
->IDispatchEx_iface
, fdexEnumDefault
, dispid
, &dispid
)) == S_OK
) {
581 hres
= jsdisp_propget(obj
, dispid
, &val
);
585 if(is_undefined(val
))
588 stepback
= ctx
->buf_len
;
590 if(prop_cnt
&& !append_char(ctx
, ',')) {
591 hres
= E_OUTOFMEMORY
;
596 if(!append_char(ctx
, '\n')) {
597 hres
= E_OUTOFMEMORY
;
601 for(i
=0; i
< ctx
->stack_top
; i
++) {
602 if(!append_string(ctx
, ctx
->gap
)) {
603 hres
= E_OUTOFMEMORY
;
609 hres
= IDispatchEx_GetMemberName(&obj
->IDispatchEx_iface
, dispid
, &prop_name
);
613 hres
= json_quote(ctx
, prop_name
, SysStringLen(prop_name
));
614 SysFreeString(prop_name
);
618 if(!append_char(ctx
, ':') || (*ctx
->gap
&& !append_char(ctx
, ' '))) {
619 hres
= E_OUTOFMEMORY
;
623 hres
= stringify(ctx
, val
);
627 if(hres
== S_FALSE
) {
628 ctx
->buf_len
= stepback
;
638 if(prop_cnt
&& *ctx
->gap
) {
639 if(!append_char(ctx
, '\n'))
640 return E_OUTOFMEMORY
;
642 for(i
=1; i
< ctx
->stack_top
; i
++) {
643 if(!append_string(ctx
, ctx
->gap
)) {
644 hres
= E_OUTOFMEMORY
;
650 if(!append_char(ctx
, '}'))
651 return E_OUTOFMEMORY
;
653 stringify_pop_obj(ctx
);
657 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation Str) */
658 static HRESULT
stringify(stringify_ctx_t
*ctx
, jsval_t val
)
663 if(is_object_instance(val
) && get_object(val
)) {
667 obj
= iface_to_jsdisp(get_object(val
));
671 hres
= jsdisp_get_id(obj
, toJSONW
, 0, &id
);
674 FIXME("Use toJSON.\n");
677 /* FIXME: Support replacer replacer. */
679 hres
= maybe_to_primitive(ctx
->ctx
, val
, &value
);
683 switch(jsval_type(value
)) {
685 if(!append_string(ctx
, nullW
))
686 hres
= E_OUTOFMEMORY
;
689 if(!append_string(ctx
, get_bool(value
) ? trueW
: falseW
))
690 hres
= E_OUTOFMEMORY
;
693 jsstr_t
*str
= get_string(value
);
694 const WCHAR
*ptr
= jsstr_flatten(str
);
696 hres
= json_quote(ctx
, ptr
, jsstr_length(str
));
698 hres
= E_OUTOFMEMORY
;
702 double n
= get_number(value
);
707 /* FIXME: Optimize. There is no need for jsstr_t here. */
708 hres
= double_to_string(n
, &str
);
712 ptr
= jsstr_flatten(str
);
714 hres
= ptr
&& !append_string_len(ctx
, ptr
, jsstr_length(str
)) ? E_OUTOFMEMORY
: S_OK
;
717 if(!append_string(ctx
, nullW
))
718 hres
= E_OUTOFMEMORY
;
725 obj
= iface_to_jsdisp(get_object(value
));
731 if(!is_callable(obj
))
732 hres
= is_class(obj
, JSCLASS_ARRAY
) ? stringify_array(ctx
, obj
) : stringify_object(ctx
, obj
);
748 jsval_release(value
);
752 /* ECMA-262 5.1 Edition 15.12.3 */
753 static HRESULT
JSON_stringify(script_ctx_t
*ctx
, vdisp_t
*jsthis
, WORD flags
, unsigned argc
, jsval_t
*argv
, jsval_t
*r
)
755 stringify_ctx_t stringify_ctx
= {ctx
, NULL
,0,0, NULL
,0,0, {0}};
762 *r
= jsval_undefined();
766 if(argc
>= 2 && is_object_instance(argv
[1])) {
767 FIXME("Replacer %s not yet supported\n", debugstr_jsval(argv
[1]));
774 hres
= maybe_to_primitive(ctx
, argv
[2], &space_val
);
778 if(is_number(space_val
)) {
779 double n
= get_number(space_val
);
785 for(i
=0; i
< len
; i
++)
786 stringify_ctx
.gap
[i
] = ' ';
787 stringify_ctx
.gap
[len
] = 0;
789 }else if(is_string(space_val
)) {
790 jsstr_t
*space_str
= get_string(space_val
);
791 size_t len
= jsstr_length(space_str
);
794 jsstr_extract(space_str
, 0, len
, stringify_ctx
.gap
);
797 jsval_release(space_val
);
800 hres
= stringify(&stringify_ctx
, argv
[0]);
801 if(SUCCEEDED(hres
) && r
) {
802 assert(!stringify_ctx
.stack_top
);
805 jsstr_t
*ret
= jsstr_alloc_len(stringify_ctx
.buf
, stringify_ctx
.buf_len
);
807 *r
= jsval_string(ret
);
809 hres
= E_OUTOFMEMORY
;
811 *r
= jsval_undefined();
815 heap_free(stringify_ctx
.buf
);
816 heap_free(stringify_ctx
.stack
);
820 static const builtin_prop_t JSON_props
[] = {
821 {L
"parse", JSON_parse
, PROPF_METHOD
|2},
822 {L
"stringify", JSON_stringify
, PROPF_METHOD
|3}
825 static const builtin_info_t JSON_info
= {
828 ARRAY_SIZE(JSON_props
),
834 HRESULT
create_json(script_ctx_t
*ctx
, jsdisp_t
**ret
)
839 json
= heap_alloc_zero(sizeof(*json
));
841 return E_OUTOFMEMORY
;
843 hres
= init_dispex_from_constr(json
, ctx
, &JSON_info
, ctx
->object_constr
);