mf/session: Implement support for sinks that provide sample allocators.
[wine.git] / dlls / jscript / json.c
blob433c7858cb6e8a953bde6db76d1b1ca892dae537
1 /*
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
19 #include <math.h>
20 #include <assert.h>
22 #include "jscript.h"
23 #include "parser.h"
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};
35 typedef struct {
36 const WCHAR *ptr;
37 const WCHAR *end;
38 script_ctx_t *ctx;
39 } json_parse_ctx_t;
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))
49 ctx->ptr++;
50 return *ctx->ptr;
53 static BOOL is_keyword(json_parse_ctx_t *ctx, const WCHAR *keyword)
55 unsigned i;
56 for(i=0; keyword[i]; i++) {
57 if(!ctx->ptr[i] || keyword[i] != ctx->ptr[i])
58 return FALSE;
60 if(is_identifier_char(ctx->ptr[i]))
61 return FALSE;
62 ctx->ptr += i;
63 return TRUE;
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;
70 size_t len;
71 WCHAR *buf;
73 while(*ctx->ptr && *ctx->ptr != '"') {
74 if(*ctx->ptr++ == '\\')
75 ctx->ptr++;
77 if(!*ctx->ptr) {
78 FIXME("unterminated string\n");
79 return E_FAIL;
82 len = ctx->ptr-ptr;
83 buf = heap_alloc((len+1)*sizeof(WCHAR));
84 if(!buf)
85 return E_OUTOFMEMORY;
86 if(len)
87 memcpy(buf, ptr, len*sizeof(WCHAR));
89 if(!unescape(buf, &len)) {
90 FIXME("unescape failed\n");
91 heap_free(buf);
92 return E_FAIL;
95 buf[len] = 0;
96 ctx->ptr++;
97 *r = buf;
98 return S_OK;
101 /* ECMA-262 5.1 Edition 15.12.1.2 */
102 static HRESULT parse_json_value(json_parse_ctx_t *ctx, jsval_t *r)
104 HRESULT hres;
106 switch(skip_spaces(ctx)) {
108 /* JSONNullLiteral */
109 case 'n':
110 if(!is_keyword(ctx, nullW))
111 break;
112 *r = jsval_null();
113 return S_OK;
115 /* JSONBooleanLiteral */
116 case 't':
117 if(!is_keyword(ctx, trueW))
118 break;
119 *r = jsval_bool(TRUE);
120 return S_OK;
121 case 'f':
122 if(!is_keyword(ctx, falseW))
123 break;
124 *r = jsval_bool(FALSE);
125 return S_OK;
127 /* JSONObject */
128 case '{': {
129 WCHAR *prop_name;
130 jsdisp_t *obj;
131 jsval_t val;
133 hres = create_object(ctx->ctx, NULL, &obj);
134 if(FAILED(hres))
135 return hres;
137 ctx->ptr++;
138 if(skip_spaces(ctx) == '}') {
139 ctx->ptr++;
140 *r = jsval_obj(obj);
141 return S_OK;
144 while(1) {
145 if(*ctx->ptr != '"')
146 break;
147 hres = parse_json_string(ctx, &prop_name);
148 if(FAILED(hres))
149 break;
151 if(skip_spaces(ctx) != ':') {
152 FIXME("missing ':'\n");
153 heap_free(prop_name);
154 break;
157 ctx->ptr++;
158 hres = parse_json_value(ctx, &val);
159 if(SUCCEEDED(hres)) {
160 hres = jsdisp_propput_name(obj, prop_name, val);
161 jsval_release(val);
163 heap_free(prop_name);
164 if(FAILED(hres))
165 break;
167 if(skip_spaces(ctx) == '}') {
168 ctx->ptr++;
169 *r = jsval_obj(obj);
170 return S_OK;
173 if(*ctx->ptr++ != ',') {
174 FIXME("expected ','\n");
175 break;
177 skip_spaces(ctx);
180 jsdisp_release(obj);
181 break;
184 /* JSONString */
185 case '"': {
186 WCHAR *string;
187 jsstr_t *str;
189 hres = parse_json_string(ctx, &string);
190 if(FAILED(hres))
191 return hres;
193 /* FIXME: avoid reallocation */
194 str = jsstr_alloc(string);
195 heap_free(string);
196 if(!str)
197 return E_OUTOFMEMORY;
199 *r = jsval_string(str);
200 return S_OK;
203 /* JSONArray */
204 case '[': {
205 jsdisp_t *array;
206 unsigned i = 0;
207 jsval_t val;
209 hres = create_array(ctx->ctx, 0, &array);
210 if(FAILED(hres))
211 return hres;
213 ctx->ptr++;
214 if(skip_spaces(ctx) == ']') {
215 ctx->ptr++;
216 *r = jsval_obj(array);
217 return S_OK;
220 while(1) {
221 hres = parse_json_value(ctx, &val);
222 if(FAILED(hres))
223 break;
225 hres = jsdisp_propput_idx(array, i, val);
226 jsval_release(val);
227 if(FAILED(hres))
228 break;
230 if(skip_spaces(ctx) == ']') {
231 ctx->ptr++;
232 *r = jsval_obj(array);
233 return S_OK;
236 if(*ctx->ptr != ',') {
237 FIXME("expected ','\n");
238 break;
241 ctx->ptr++;
242 i++;
245 jsdisp_release(array);
246 break;
249 /* JSONNumber */
250 default: {
251 int sign = 1;
252 double n;
254 if(*ctx->ptr == '-') {
255 sign = -1;
256 ctx->ptr++;
257 skip_spaces(ctx);
260 if(*ctx->ptr == '0' && ctx->ptr + 1 < ctx->end && is_digit(ctx->ptr[1]))
261 break;
263 hres = parse_decimal(&ctx->ptr, ctx->end, &n);
264 if(FAILED(hres))
265 break;
267 *r = jsval_number(sign*n);
268 return S_OK;
272 FIXME("Syntax error at %s\n", debugstr_w(ctx->ptr));
273 return E_FAIL;
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;
280 const WCHAR *buf;
281 jsstr_t *str;
282 jsval_t ret;
283 HRESULT hres;
285 if(argc != 1) {
286 FIXME("Unsupported args\n");
287 return E_INVALIDARG;
290 hres = to_flat_string(ctx, argv[0], &str, &buf);
291 if(FAILED(hres))
292 return hres;
294 TRACE("%s\n", debugstr_w(buf));
296 parse_ctx.ptr = buf;
297 parse_ctx.end = buf + jsstr_length(str);
298 parse_ctx.ctx = ctx;
299 hres = parse_json_value(&parse_ctx, &ret);
300 jsstr_release(str);
301 if(FAILED(hres))
302 return hres;
304 if(skip_spaces(&parse_ctx)) {
305 FIXME("syntax error\n");
306 jsval_release(ret);
307 return E_FAIL;
310 if(r)
311 *r = ret;
312 else
313 jsval_release(ret);
314 return S_OK;
317 typedef struct {
318 script_ctx_t *ctx;
320 WCHAR *buf;
321 size_t buf_size;
322 size_t buf_len;
324 jsdisp_t **stack;
325 size_t stack_top;
326 size_t stack_size;
328 WCHAR gap[11]; /* according to the spec, it's no longer than 10 chars */
329 } stringify_ctx_t;
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));
335 if(!ctx->stack)
336 return FALSE;
337 ctx->stack_size = 4;
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));
342 if(!new_stack)
343 return FALSE;
344 ctx->stack = new_stack;
345 ctx->stack_size *= 2;
348 ctx->stack[ctx->stack_top++] = obj;
349 return TRUE;
352 static void stringify_pop_obj(stringify_ctx_t *ctx)
354 ctx->stack_top--;
357 static BOOL is_on_stack(stringify_ctx_t *ctx, jsdisp_t *obj)
359 size_t i = ctx->stack_top;
360 while(i--) {
361 if(ctx->stack[i] == obj)
362 return TRUE;
364 return FALSE;
367 static BOOL append_string_len(stringify_ctx_t *ctx, const WCHAR *str, size_t len)
369 if(!ctx->buf_size) {
370 ctx->buf = heap_alloc(len*2*sizeof(WCHAR));
371 if(!ctx->buf)
372 return FALSE;
373 ctx->buf_size = len*2;
374 }else if(ctx->buf_len + len > ctx->buf_size) {
375 WCHAR *new_buf;
376 size_t new_size;
378 new_size = ctx->buf_size * 2 + len;
379 new_buf = heap_realloc(ctx->buf, new_size*sizeof(WCHAR));
380 if(!new_buf)
381 return FALSE;
382 ctx->buf = new_buf;
383 ctx->buf_size = new_size;
386 if(len)
387 memcpy(ctx->buf + ctx->buf_len, str, len*sizeof(WCHAR));
388 ctx->buf_len += len;
389 return TRUE;
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)
410 jsdisp_t *obj;
411 HRESULT hres;
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)) {
417 double n;
418 hres = to_number(ctx, val, &n);
419 jsdisp_release(obj);
420 if(SUCCEEDED(hres))
421 *r = jsval_number(n);
422 return hres;
425 if(is_class(obj, JSCLASS_STRING)) {
426 jsstr_t *str;
427 hres = to_string(ctx, val, &str);
428 jsdisp_release(obj);
429 if(SUCCEEDED(hres))
430 *r = jsval_string(str);
431 return hres;
434 if(is_class(obj, JSCLASS_BOOLEAN)) {
435 *r = jsval_bool(bool_obj_value(obj));
436 jsdisp_release(obj);
437 return S_OK;
440 *r = jsval_obj(obj);
441 return S_OK;
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;
450 while(len--) {
451 switch(*ptr) {
452 case '"':
453 case '\\':
454 if(!append_simple_quote(ctx, *ptr))
455 return E_OUTOFMEMORY;
456 break;
457 case '\b':
458 if(!append_simple_quote(ctx, 'b'))
459 return E_OUTOFMEMORY;
460 break;
461 case '\f':
462 if(!append_simple_quote(ctx, 'f'))
463 return E_OUTOFMEMORY;
464 break;
465 case '\n':
466 if(!append_simple_quote(ctx, 'n'))
467 return E_OUTOFMEMORY;
468 break;
469 case '\r':
470 if(!append_simple_quote(ctx, 'r'))
471 return E_OUTOFMEMORY;
472 break;
473 case '\t':
474 if(!append_simple_quote(ctx, 't'))
475 return E_OUTOFMEMORY;
476 break;
477 default:
478 if(*ptr < ' ') {
479 static const WCHAR formatW[] = {'\\','u','%','0','4','x',0};
480 WCHAR buf[7];
481 swprintf(buf, ARRAY_SIZE(buf), formatW, *ptr);
482 if(!append_string(ctx, buf))
483 return E_OUTOFMEMORY;
484 }else {
485 if(!append_char(ctx, *ptr))
486 return E_OUTOFMEMORY;
489 ptr++;
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;
506 jsval_t val;
507 HRESULT hres;
509 if(is_on_stack(ctx, obj)) {
510 FIXME("Found a cycle\n");
511 return E_FAIL;
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;
526 if(*ctx->gap) {
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);
539 if(FAILED(hres))
540 return hres;
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;
546 }else {
547 return hres;
551 if((length && *ctx->gap && !append_char(ctx, '\n')) || !append_char(ctx, ']'))
552 return E_OUTOFMEMORY;
554 stringify_pop_obj(ctx);
555 return S_OK;
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;
564 size_t stepback;
565 BSTR prop_name;
566 HRESULT hres;
568 if(is_on_stack(ctx, obj)) {
569 FIXME("Found a cycle\n");
570 return E_FAIL;
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) {
580 jsval_release(val);
581 hres = jsdisp_propget(obj, dispid, &val);
582 if(FAILED(hres))
583 return hres;
585 if(is_undefined(val))
586 continue;
588 stepback = ctx->buf_len;
590 if(prop_cnt && !append_char(ctx, ',')) {
591 hres = E_OUTOFMEMORY;
592 break;
595 if(*ctx->gap) {
596 if(!append_char(ctx, '\n')) {
597 hres = E_OUTOFMEMORY;
598 break;
601 for(i=0; i < ctx->stack_top; i++) {
602 if(!append_string(ctx, ctx->gap)) {
603 hres = E_OUTOFMEMORY;
604 break;
609 hres = IDispatchEx_GetMemberName(&obj->IDispatchEx_iface, dispid, &prop_name);
610 if(FAILED(hres))
611 break;
613 hres = json_quote(ctx, prop_name, SysStringLen(prop_name));
614 SysFreeString(prop_name);
615 if(FAILED(hres))
616 break;
618 if(!append_char(ctx, ':') || (*ctx->gap && !append_char(ctx, ' '))) {
619 hres = E_OUTOFMEMORY;
620 break;
623 hres = stringify(ctx, val);
624 if(FAILED(hres))
625 break;
627 if(hres == S_FALSE) {
628 ctx->buf_len = stepback;
629 continue;
632 prop_cnt++;
634 jsval_release(val);
635 if(FAILED(hres))
636 return hres;
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;
645 break;
650 if(!append_char(ctx, '}'))
651 return E_OUTOFMEMORY;
653 stringify_pop_obj(ctx);
654 return S_OK;
657 /* ECMA-262 5.1 Edition 15.12.3 (abstract operation Str) */
658 static HRESULT stringify(stringify_ctx_t *ctx, jsval_t val)
660 jsval_t value;
661 HRESULT hres;
663 if(is_object_instance(val) && get_object(val)) {
664 jsdisp_t *obj;
665 DISPID id;
667 obj = iface_to_jsdisp(get_object(val));
668 if(!obj)
669 return S_FALSE;
671 hres = jsdisp_get_id(obj, toJSONW, 0, &id);
672 jsdisp_release(obj);
673 if(hres == S_OK)
674 FIXME("Use toJSON.\n");
677 /* FIXME: Support replacer replacer. */
679 hres = maybe_to_primitive(ctx->ctx, val, &value);
680 if(FAILED(hres))
681 return hres;
683 switch(jsval_type(value)) {
684 case JSV_NULL:
685 if(!append_string(ctx, nullW))
686 hres = E_OUTOFMEMORY;
687 break;
688 case JSV_BOOL:
689 if(!append_string(ctx, get_bool(value) ? trueW : falseW))
690 hres = E_OUTOFMEMORY;
691 break;
692 case JSV_STRING: {
693 jsstr_t *str = get_string(value);
694 const WCHAR *ptr = jsstr_flatten(str);
695 if(ptr)
696 hres = json_quote(ctx, ptr, jsstr_length(str));
697 else
698 hres = E_OUTOFMEMORY;
699 break;
701 case JSV_NUMBER: {
702 double n = get_number(value);
703 if(is_finite(n)) {
704 const WCHAR *ptr;
705 jsstr_t *str;
707 /* FIXME: Optimize. There is no need for jsstr_t here. */
708 hres = double_to_string(n, &str);
709 if(FAILED(hres))
710 break;
712 ptr = jsstr_flatten(str);
713 assert(ptr != NULL);
714 hres = ptr && !append_string_len(ctx, ptr, jsstr_length(str)) ? E_OUTOFMEMORY : S_OK;
715 jsstr_release(str);
716 }else {
717 if(!append_string(ctx, nullW))
718 hres = E_OUTOFMEMORY;
720 break;
722 case JSV_OBJECT: {
723 jsdisp_t *obj;
725 obj = iface_to_jsdisp(get_object(value));
726 if(!obj) {
727 hres = S_FALSE;
728 break;
731 if(!is_callable(obj))
732 hres = is_class(obj, JSCLASS_ARRAY) ? stringify_array(ctx, obj) : stringify_object(ctx, obj);
733 else
734 hres = S_FALSE;
736 jsdisp_release(obj);
737 break;
739 case JSV_UNDEFINED:
740 hres = S_FALSE;
741 break;
742 case JSV_VARIANT:
743 FIXME("VARIANT\n");
744 hres = E_NOTIMPL;
745 break;
748 jsval_release(value);
749 return hres;
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}};
756 HRESULT hres;
758 TRACE("\n");
760 if(!argc) {
761 if(r)
762 *r = jsval_undefined();
763 return S_OK;
766 if(argc >= 2 && is_object_instance(argv[1])) {
767 FIXME("Replacer %s not yet supported\n", debugstr_jsval(argv[1]));
768 return E_NOTIMPL;
771 if(argc >= 3) {
772 jsval_t space_val;
774 hres = maybe_to_primitive(ctx, argv[2], &space_val);
775 if(FAILED(hres))
776 return hres;
778 if(is_number(space_val)) {
779 double n = get_number(space_val);
780 if(n >= 1) {
781 int i, len;
782 if(n > 10)
783 n = 10;
784 len = floor(n);
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);
792 if(len > 10)
793 len = 10;
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);
804 if(hres == S_OK) {
805 jsstr_t *ret = jsstr_alloc_len(stringify_ctx.buf, stringify_ctx.buf_len);
806 if(ret)
807 *r = jsval_string(ret);
808 else
809 hres = E_OUTOFMEMORY;
810 }else {
811 *r = jsval_undefined();
815 heap_free(stringify_ctx.buf);
816 heap_free(stringify_ctx.stack);
817 return hres;
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 = {
826 JSCLASS_JSON,
827 {NULL, NULL, 0},
828 ARRAY_SIZE(JSON_props),
829 JSON_props,
830 NULL,
831 NULL
834 HRESULT create_json(script_ctx_t *ctx, jsdisp_t **ret)
836 jsdisp_t *json;
837 HRESULT hres;
839 json = heap_alloc_zero(sizeof(*json));
840 if(!json)
841 return E_OUTOFMEMORY;
843 hres = init_dispex_from_constr(json, ctx, &JSON_info, ctx->object_constr);
844 if(FAILED(hres)) {
845 heap_free(json);
846 return hres;
849 *ret = json;
850 return S_OK;