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
= malloc((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");
152 hres
= parse_json_value(ctx
, &val
);
153 if(SUCCEEDED(hres
)) {
154 hres
= jsdisp_propput_name(obj
, prop_name
, val
);
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 struct transform_json_object_ctx
277 static jsval_t
transform_json_object(struct transform_json_object_ctx
*proc_ctx
, jsdisp_t
*holder
, jsstr_t
*name
)
279 jsval_t res
, args
[2];
282 if(!(str
= jsstr_flatten(name
)))
283 proc_ctx
->hres
= E_OUTOFMEMORY
;
285 proc_ctx
->hres
= jsdisp_propget_name(holder
, str
, &args
[1]);
286 if(FAILED(proc_ctx
->hres
))
287 return jsval_undefined();
289 if(is_object_instance(args
[1])) {
290 jsdisp_t
*obj
= to_jsdisp(get_object(args
[1]));
296 FIXME("non-JS obj in JSON object: %p\n", get_object(args
[1]));
297 proc_ctx
->hres
= E_NOTIMPL
;
299 }else if(is_class(obj
, JSCLASS_ARRAY
)) {
300 unsigned i
, length
= array_get_length(obj
);
301 WCHAR buf
[14], *buf_end
;
303 buf_end
= buf
+ ARRAY_SIZE(buf
) - 1;
305 for(i
= 0; i
< length
; i
++) {
306 str
= idx_to_str(i
, buf_end
);
307 if(!(jsstr
= jsstr_alloc(str
))) {
308 proc_ctx
->hres
= E_OUTOFMEMORY
;
311 res
= transform_json_object(proc_ctx
, obj
, jsstr
);
312 jsstr_release(jsstr
);
313 if(is_undefined(res
)) {
314 if(FAILED(proc_ctx
->hres
))
316 if(FAILED(jsdisp_get_id(obj
, str
, 0, &id
)))
318 proc_ctx
->hres
= disp_delete((IDispatch
*)&obj
->IDispatchEx_iface
, id
, &b
);
320 proc_ctx
->hres
= jsdisp_define_data_property(obj
, str
, PROPF_WRITABLE
| PROPF_ENUMERABLE
| PROPF_CONFIGURABLE
, res
);
323 if(FAILED(proc_ctx
->hres
))
327 id
= DISPID_STARTENUM
;
329 proc_ctx
->hres
= jsdisp_next_prop(obj
, id
, JSDISP_ENUM_OWN_ENUMERABLE
, &id
);
330 if(proc_ctx
->hres
== S_FALSE
)
332 if(FAILED(proc_ctx
->hres
) || FAILED(proc_ctx
->hres
= jsdisp_get_prop_name(obj
, id
, &jsstr
)))
334 res
= transform_json_object(proc_ctx
, obj
, jsstr
);
335 if(is_undefined(res
)) {
336 if(SUCCEEDED(proc_ctx
->hres
))
337 proc_ctx
->hres
= disp_delete((IDispatch
*)&obj
->IDispatchEx_iface
, id
, &b
);
339 if(!(str
= jsstr_flatten(jsstr
)))
340 proc_ctx
->hres
= E_OUTOFMEMORY
;
342 proc_ctx
->hres
= jsdisp_define_data_property(obj
, str
, PROPF_WRITABLE
| PROPF_ENUMERABLE
| PROPF_CONFIGURABLE
, res
);
345 jsstr_release(jsstr
);
346 if(FAILED(proc_ctx
->hres
))
352 args
[0] = jsval_string(name
);
353 proc_ctx
->hres
= disp_call_value(proc_ctx
->ctx
, proc_ctx
->reviver
, jsval_obj(holder
),
354 DISPATCH_METHOD
, ARRAY_SIZE(args
), args
, &res
);
356 jsval_release(args
[1]);
357 return FAILED(proc_ctx
->hres
) ? jsval_undefined() : res
;
360 /* ECMA-262 5.1 Edition 15.12.2 */
361 static HRESULT
JSON_parse(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
, jsval_t
*r
)
363 json_parse_ctx_t parse_ctx
;
370 hres
= to_flat_string(ctx
, argv
[0], &str
, &buf
);
374 TRACE("%s\n", debugstr_w(buf
));
377 parse_ctx
.end
= buf
+ jsstr_length(str
);
379 hres
= parse_json_value(&parse_ctx
, &ret
);
380 if(SUCCEEDED(hres
) && skip_spaces(&parse_ctx
)) {
381 FIXME("syntax error\n");
389 /* FIXME: check IsCallable */
390 if(argc
> 1 && is_object_instance(argv
[1])) {
391 hres
= create_object(ctx
, NULL
, &root
);
396 hres
= jsdisp_define_data_property(root
, L
"", PROPF_WRITABLE
| PROPF_ENUMERABLE
| PROPF_CONFIGURABLE
, ret
);
399 if(SUCCEEDED(hres
)) {
400 struct transform_json_object_ctx proc_ctx
= { ctx
, get_object(argv
[1]), S_OK
};
403 ret
= transform_json_object(&proc_ctx
, root
, str
);
405 hres
= proc_ctx
.hres
;
407 jsdisp_release(root
);
430 WCHAR gap
[11]; /* according to the spec, it's no longer than 10 chars */
435 static BOOL
stringify_push_obj(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
437 if(!ctx
->stack_size
) {
438 ctx
->stack
= malloc(4*sizeof(*ctx
->stack
));
442 }else if(ctx
->stack_top
== ctx
->stack_size
) {
443 jsdisp_t
**new_stack
;
445 new_stack
= realloc(ctx
->stack
, ctx
->stack_size
*2*sizeof(*ctx
->stack
));
448 ctx
->stack
= new_stack
;
449 ctx
->stack_size
*= 2;
452 ctx
->stack
[ctx
->stack_top
++] = obj
;
456 static void stringify_pop_obj(stringify_ctx_t
*ctx
)
461 static BOOL
is_on_stack(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
463 size_t i
= ctx
->stack_top
;
465 if(ctx
->stack
[i
] == obj
)
471 static BOOL
append_string_len(stringify_ctx_t
*ctx
, const WCHAR
*str
, size_t len
)
474 ctx
->buf
= malloc(len
*2*sizeof(WCHAR
));
477 ctx
->buf_size
= len
*2;
478 }else if(ctx
->buf_len
+ len
> ctx
->buf_size
) {
482 new_size
= ctx
->buf_size
* 2 + len
;
483 new_buf
= realloc(ctx
->buf
, new_size
*sizeof(WCHAR
));
487 ctx
->buf_size
= new_size
;
491 memcpy(ctx
->buf
+ ctx
->buf_len
, str
, len
*sizeof(WCHAR
));
496 static inline BOOL
append_string(stringify_ctx_t
*ctx
, const WCHAR
*str
)
498 return append_string_len(ctx
, str
, lstrlenW(str
));
501 static inline BOOL
append_char(stringify_ctx_t
*ctx
, WCHAR c
)
503 return append_string_len(ctx
, &c
, 1);
506 static inline BOOL
append_simple_quote(stringify_ctx_t
*ctx
, WCHAR c
)
508 WCHAR str
[] = {'\\',c
};
509 return append_string_len(ctx
, str
, 2);
512 static HRESULT
maybe_to_primitive(script_ctx_t
*ctx
, jsval_t val
, jsval_t
*r
)
517 if(!is_object_instance(val
) || !(obj
= iface_to_jsdisp(get_object(val
))))
518 return jsval_copy(val
, r
);
520 if(is_class(obj
, JSCLASS_NUMBER
)) {
522 hres
= to_number(ctx
, val
, &n
);
525 *r
= jsval_number(n
);
529 if(is_class(obj
, JSCLASS_STRING
)) {
531 hres
= to_string(ctx
, val
, &str
);
534 *r
= jsval_string(str
);
538 if(is_class(obj
, JSCLASS_BOOLEAN
)) {
539 *r
= jsval_bool(bool_obj_value(obj
));
548 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation Quote) */
549 static HRESULT
json_quote(stringify_ctx_t
*ctx
, const WCHAR
*ptr
, size_t len
)
551 if(!ptr
|| !append_char(ctx
, '"'))
552 return E_OUTOFMEMORY
;
558 if(!append_simple_quote(ctx
, *ptr
))
559 return E_OUTOFMEMORY
;
562 if(!append_simple_quote(ctx
, 'b'))
563 return E_OUTOFMEMORY
;
566 if(!append_simple_quote(ctx
, 'f'))
567 return E_OUTOFMEMORY
;
570 if(!append_simple_quote(ctx
, 'n'))
571 return E_OUTOFMEMORY
;
574 if(!append_simple_quote(ctx
, 'r'))
575 return E_OUTOFMEMORY
;
578 if(!append_simple_quote(ctx
, 't'))
579 return E_OUTOFMEMORY
;
584 swprintf(buf
, ARRAY_SIZE(buf
), L
"\\u%04x", *ptr
);
585 if(!append_string(ctx
, buf
))
586 return E_OUTOFMEMORY
;
588 if(!append_char(ctx
, *ptr
))
589 return E_OUTOFMEMORY
;
595 return append_char(ctx
, '"') ? S_OK
: E_OUTOFMEMORY
;
598 static inline BOOL
is_callable(jsdisp_t
*obj
)
600 return is_class(obj
, JSCLASS_FUNCTION
);
603 static HRESULT
stringify(stringify_ctx_t
*ctx
, jsdisp_t
*object
, const WCHAR
*name
);
605 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation JA) */
606 static HRESULT
stringify_array(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
608 unsigned length
, i
, j
;
612 if(is_on_stack(ctx
, obj
)) {
613 FIXME("Found a cycle\n");
617 if(!stringify_push_obj(ctx
, obj
))
618 return E_OUTOFMEMORY
;
620 if(!append_char(ctx
, '['))
621 return E_OUTOFMEMORY
;
623 length
= array_get_length(obj
);
625 for(i
=0; i
< length
; i
++) {
626 if(i
&& !append_char(ctx
, ','))
627 return E_OUTOFMEMORY
;
630 if(!append_char(ctx
, '\n'))
631 return E_OUTOFMEMORY
;
633 for(j
=0; j
< ctx
->stack_top
; j
++) {
634 if(!append_string(ctx
, ctx
->gap
))
635 return E_OUTOFMEMORY
;
639 _itow(i
, name
, ARRAY_SIZE(name
));
640 hres
= stringify(ctx
, obj
, name
);
643 if(hres
== S_FALSE
&& !append_string(ctx
, L
"null"))
644 return E_OUTOFMEMORY
;
647 if((length
&& *ctx
->gap
&& !append_char(ctx
, '\n')) || !append_char(ctx
, ']'))
648 return E_OUTOFMEMORY
;
650 stringify_pop_obj(ctx
);
654 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation JO) */
655 static HRESULT
stringify_object(stringify_ctx_t
*ctx
, jsdisp_t
*obj
)
657 DISPID dispid
= DISPID_STARTENUM
;
658 unsigned prop_cnt
= 0, i
;
663 if(is_on_stack(ctx
, obj
)) {
664 FIXME("Found a cycle\n");
668 if(!stringify_push_obj(ctx
, obj
))
669 return E_OUTOFMEMORY
;
671 if(!append_char(ctx
, '{'))
672 return E_OUTOFMEMORY
;
674 while((hres
= IDispatchEx_GetNextDispID(&obj
->IDispatchEx_iface
, fdexEnumDefault
, dispid
, &dispid
)) == S_OK
) {
675 stepback
= ctx
->buf_len
;
677 if(prop_cnt
&& !append_char(ctx
, ',')) {
678 hres
= E_OUTOFMEMORY
;
683 if(!append_char(ctx
, '\n')) {
684 hres
= E_OUTOFMEMORY
;
688 for(i
=0; i
< ctx
->stack_top
; i
++) {
689 if(!append_string(ctx
, ctx
->gap
)) {
690 hres
= E_OUTOFMEMORY
;
696 hres
= IDispatchEx_GetMemberName(&obj
->IDispatchEx_iface
, dispid
, &prop_name
);
700 hres
= json_quote(ctx
, prop_name
, SysStringLen(prop_name
));
702 SysFreeString(prop_name
);
706 if(!append_char(ctx
, ':') || (*ctx
->gap
&& !append_char(ctx
, ' '))) {
707 SysFreeString(prop_name
);
708 return E_OUTOFMEMORY
;
711 hres
= stringify(ctx
, obj
, prop_name
);
712 SysFreeString(prop_name
);
716 if(hres
== S_FALSE
) {
717 ctx
->buf_len
= stepback
;
724 if(prop_cnt
&& *ctx
->gap
) {
725 if(!append_char(ctx
, '\n'))
726 return E_OUTOFMEMORY
;
728 for(i
=1; i
< ctx
->stack_top
; i
++) {
729 if(!append_string(ctx
, ctx
->gap
)) {
730 hres
= E_OUTOFMEMORY
;
736 if(!append_char(ctx
, '}'))
737 return E_OUTOFMEMORY
;
739 stringify_pop_obj(ctx
);
743 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation Str) */
744 static HRESULT
stringify(stringify_ctx_t
*ctx
, jsdisp_t
*object
, const WCHAR
*name
)
749 hres
= jsdisp_propget_name(object
, name
, &value
);
751 return hres
== DISP_E_UNKNOWNNAME
? S_FALSE
: hres
;
753 if(is_object_instance(value
)) {
757 obj
= to_jsdisp(get_object(value
));
759 jsval_release(value
);
763 hres
= jsdisp_get_id(obj
, L
"toJSON", 0, &id
);
765 FIXME("Use toJSON.\n");
771 if(!(name_str
= jsstr_alloc(name
))) {
772 jsval_release(value
);
773 return E_OUTOFMEMORY
;
775 args
[0] = jsval_string(name_str
);
777 hres
= jsdisp_call_value(ctx
->replacer
, jsval_obj(object
), DISPATCH_METHOD
, ARRAY_SIZE(args
), args
, &v
);
778 jsstr_release(name_str
);
779 jsval_release(value
);
786 hres
= maybe_to_primitive(ctx
->ctx
, v
, &value
);
791 switch(jsval_type(value
)) {
793 if(is_null_disp(value
))
795 else if(!append_string(ctx
, L
"null"))
796 hres
= E_OUTOFMEMORY
;
799 if(!append_string(ctx
, get_bool(value
) ? L
"true" : L
"false"))
800 hres
= E_OUTOFMEMORY
;
803 jsstr_t
*str
= get_string(value
);
804 const WCHAR
*ptr
= jsstr_flatten(str
);
806 hres
= json_quote(ctx
, ptr
, jsstr_length(str
));
808 hres
= E_OUTOFMEMORY
;
812 double n
= get_number(value
);
817 /* FIXME: Optimize. There is no need for jsstr_t here. */
818 hres
= double_to_string(n
, &str
);
822 ptr
= jsstr_flatten(str
);
824 hres
= ptr
&& !append_string_len(ctx
, ptr
, jsstr_length(str
)) ? E_OUTOFMEMORY
: S_OK
;
827 if(!append_string(ctx
, L
"null"))
828 hres
= E_OUTOFMEMORY
;
835 obj
= iface_to_jsdisp(get_object(value
));
841 if(!is_callable(obj
))
842 hres
= is_class(obj
, JSCLASS_ARRAY
) ? stringify_array(ctx
, obj
) : stringify_object(ctx
, obj
);
858 jsval_release(value
);
862 /* ECMA-262 5.1 Edition 15.12.3 */
863 static HRESULT
JSON_stringify(script_ctx_t
*ctx
, jsval_t vthis
, WORD flags
, unsigned argc
, jsval_t
*argv
, jsval_t
*r
)
865 stringify_ctx_t stringify_ctx
= { ctx
};
866 jsdisp_t
*obj
= NULL
, *replacer
;
873 *r
= jsval_undefined();
877 if(argc
>= 2 && is_object_instance(argv
[1]) && (replacer
= to_jsdisp(get_object(argv
[1])))) {
878 if(is_callable(replacer
)) {
879 stringify_ctx
.replacer
= jsdisp_addref(replacer
);
880 }else if(is_class(replacer
, JSCLASS_ARRAY
)) {
881 FIXME("Array replacer not yet supported\n");
889 hres
= maybe_to_primitive(ctx
, argv
[2], &space_val
);
893 if(is_number(space_val
)) {
894 double n
= get_number(space_val
);
900 for(i
=0; i
< len
; i
++)
901 stringify_ctx
.gap
[i
] = ' ';
902 stringify_ctx
.gap
[len
] = 0;
904 }else if(is_string(space_val
)) {
905 jsstr_t
*space_str
= get_string(space_val
);
906 size_t len
= jsstr_length(space_str
);
909 jsstr_extract(space_str
, 0, len
, stringify_ctx
.gap
);
912 jsval_release(space_val
);
915 if(FAILED(hres
= create_object(ctx
, NULL
, &obj
)))
917 if(FAILED(hres
= jsdisp_propput_name(obj
, L
"", argv
[0])))
920 hres
= stringify(&stringify_ctx
, obj
, L
"");
921 if(SUCCEEDED(hres
) && r
) {
922 assert(!stringify_ctx
.stack_top
);
925 jsstr_t
*ret
= jsstr_alloc_len(stringify_ctx
.buf
, stringify_ctx
.buf_len
);
927 *r
= jsval_string(ret
);
929 hres
= E_OUTOFMEMORY
;
931 *r
= jsval_undefined();
938 if(stringify_ctx
.replacer
)
939 jsdisp_release(stringify_ctx
.replacer
);
940 free(stringify_ctx
.buf
);
941 free(stringify_ctx
.stack
);
945 static const builtin_prop_t JSON_props
[] = {
946 {L
"parse", JSON_parse
, PROPF_METHOD
|2},
947 {L
"stringify", JSON_stringify
, PROPF_METHOD
|3}
950 static const builtin_info_t JSON_info
= {
953 ARRAY_SIZE(JSON_props
),
959 HRESULT
create_json(script_ctx_t
*ctx
, jsdisp_t
**ret
)
964 json
= calloc(1, sizeof(*json
));
966 return E_OUTOFMEMORY
;
968 hres
= init_dispex_from_constr(json
, ctx
, &JSON_info
, ctx
->object_constr
);