xref: /reactos/dll/win32/jscript/json.c (revision 7eead935)
1 /*
2  * Copyright 2016 Jacek Caban for CodeWeavers
3  *
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.
8  *
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.
13  *
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
17  */
18 
19 #include <math.h>
20 #include <assert.h>
21 
22 #include "jscript.h"
23 #include "parser.h"
24 
25 #include "wine/debug.h"
26 #include "wine/unicode.h"
27 
28 WINE_DEFAULT_DEBUG_CHANNEL(jscript);
29 
30 static const WCHAR parseW[] = {'p','a','r','s','e',0};
31 static const WCHAR stringifyW[] = {'s','t','r','i','n','g','i','f','y',0};
32 
33 static const WCHAR nullW[] = {'n','u','l','l',0};
34 static const WCHAR trueW[] = {'t','r','u','e',0};
35 static const WCHAR falseW[] = {'f','a','l','s','e',0};
36 
37 static const WCHAR toJSONW[] = {'t','o','J','S','O','N',0};
38 
39 typedef struct {
40     const WCHAR *ptr;
41     const WCHAR *end;
42     script_ctx_t *ctx;
43 } json_parse_ctx_t;
44 
45 static BOOL is_json_space(WCHAR c)
46 {
47     return c == ' ' || c == '\t' || c == '\n' || c == '\r';
48 }
49 
50 static WCHAR skip_spaces(json_parse_ctx_t *ctx)
51 {
52     while(is_json_space(*ctx->ptr))
53         ctx->ptr++;
54     return *ctx->ptr;
55 }
56 
57 static BOOL is_keyword(json_parse_ctx_t *ctx, const WCHAR *keyword)
58 {
59     unsigned i;
60     for(i=0; keyword[i]; i++) {
61         if(!ctx->ptr[i] || keyword[i] != ctx->ptr[i])
62             return FALSE;
63     }
64     if(is_identifier_char(ctx->ptr[i]))
65         return FALSE;
66     ctx->ptr += i;
67     return TRUE;
68 }
69 
70 /* ECMA-262 5.1 Edition    15.12.1.1 */
71 static HRESULT parse_json_string(json_parse_ctx_t *ctx, WCHAR **r)
72 {
73     const WCHAR *ptr = ++ctx->ptr;
74     size_t len;
75     WCHAR *buf;
76 
77     while(*ctx->ptr && *ctx->ptr != '"') {
78         if(*ctx->ptr++ == '\\')
79             ctx->ptr++;
80     }
81     if(!*ctx->ptr) {
82         FIXME("unterminated string\n");
83         return E_FAIL;
84     }
85 
86     len = ctx->ptr-ptr;
87     buf = heap_alloc((len+1)*sizeof(WCHAR));
88     if(!buf)
89         return E_OUTOFMEMORY;
90     if(len)
91         memcpy(buf, ptr, len*sizeof(WCHAR));
92     buf[len] = 0;
93 
94     if(!unescape(buf)) {
95         FIXME("unescape failed\n");
96         heap_free(buf);
97         return E_FAIL;
98     }
99 
100     ctx->ptr++;
101     *r = buf;
102     return S_OK;
103 }
104 
105 /* ECMA-262 5.1 Edition    15.12.1.2 */
106 static HRESULT parse_json_value(json_parse_ctx_t *ctx, jsval_t *r)
107 {
108     HRESULT hres;
109 
110     switch(skip_spaces(ctx)) {
111 
112     /* JSONNullLiteral */
113     case 'n':
114         if(!is_keyword(ctx, nullW))
115             break;
116         *r = jsval_null();
117         return S_OK;
118 
119     /* JSONBooleanLiteral */
120     case 't':
121         if(!is_keyword(ctx, trueW))
122             break;
123         *r = jsval_bool(TRUE);
124         return S_OK;
125     case 'f':
126         if(!is_keyword(ctx, falseW))
127             break;
128         *r = jsval_bool(FALSE);
129         return S_OK;
130 
131     /* JSONObject */
132     case '{': {
133         WCHAR *prop_name;
134         jsdisp_t *obj;
135         jsval_t val;
136 
137         hres = create_object(ctx->ctx, NULL, &obj);
138         if(FAILED(hres))
139             return hres;
140 
141         ctx->ptr++;
142         if(skip_spaces(ctx) == '}') {
143             ctx->ptr++;
144             *r = jsval_obj(obj);
145             return S_OK;
146         }
147 
148         while(1) {
149             if(*ctx->ptr != '"')
150                 break;
151             hres = parse_json_string(ctx, &prop_name);
152             if(FAILED(hres))
153                 break;
154 
155             if(skip_spaces(ctx) != ':') {
156                 FIXME("missing ':'\n");
157                 heap_free(prop_name);
158                 break;
159             }
160 
161             ctx->ptr++;
162             hres = parse_json_value(ctx, &val);
163             if(SUCCEEDED(hres)) {
164                 hres = jsdisp_propput_name(obj, prop_name, val);
165                 jsval_release(val);
166             }
167             heap_free(prop_name);
168             if(FAILED(hres))
169                 break;
170 
171             if(skip_spaces(ctx) == '}') {
172                 ctx->ptr++;
173                 *r = jsval_obj(obj);
174                 return S_OK;
175             }
176 
177             if(*ctx->ptr++ != ',') {
178                 FIXME("expected ','\n");
179                 break;
180             }
181             skip_spaces(ctx);
182         }
183 
184         jsdisp_release(obj);
185         break;
186     }
187 
188     /* JSONString */
189     case '"': {
190         WCHAR *string;
191         jsstr_t *str;
192 
193         hres = parse_json_string(ctx, &string);
194         if(FAILED(hres))
195             return hres;
196 
197         /* FIXME: avoid reallocation */
198         str = jsstr_alloc(string);
199         heap_free(string);
200         if(!str)
201             return E_OUTOFMEMORY;
202 
203         *r = jsval_string(str);
204         return S_OK;
205     }
206 
207     /* JSONArray */
208     case '[': {
209         jsdisp_t *array;
210         unsigned i = 0;
211         jsval_t val;
212 
213         hres = create_array(ctx->ctx, 0, &array);
214         if(FAILED(hres))
215             return hres;
216 
217         ctx->ptr++;
218         if(skip_spaces(ctx) == ']') {
219             ctx->ptr++;
220             *r = jsval_obj(array);
221             return S_OK;
222         }
223 
224         while(1) {
225             hres = parse_json_value(ctx, &val);
226             if(FAILED(hres))
227                 break;
228 
229             hres = jsdisp_propput_idx(array, i, val);
230             jsval_release(val);
231             if(FAILED(hres))
232                 break;
233 
234             if(skip_spaces(ctx) == ']') {
235                 ctx->ptr++;
236                 *r = jsval_obj(array);
237                 return S_OK;
238             }
239 
240             if(*ctx->ptr != ',') {
241                 FIXME("expected ','\n");
242                 break;
243             }
244 
245             ctx->ptr++;
246             i++;
247         }
248 
249         jsdisp_release(array);
250         break;
251     }
252 
253     /* JSONNumber */
254     default: {
255         int sign = 1;
256         double n;
257 
258         if(*ctx->ptr == '-') {
259             sign = -1;
260             ctx->ptr++;
261             skip_spaces(ctx);
262         }
263 
264         if(!isdigitW(*ctx->ptr))
265             break;
266 
267         if(*ctx->ptr == '0') {
268             ctx->ptr++;
269             n = 0;
270             if(is_identifier_char(*ctx->ptr))
271                 break;
272         }else {
273             hres = parse_decimal(&ctx->ptr, ctx->end, &n);
274             if(FAILED(hres))
275                 return hres;
276         }
277 
278         *r = jsval_number(sign*n);
279         return S_OK;
280     }
281     }
282 
283     FIXME("Syntax error at %s\n", debugstr_w(ctx->ptr));
284     return E_FAIL;
285 }
286 
287 /* ECMA-262 5.1 Edition    15.12.2 */
288 static HRESULT JSON_parse(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r)
289 {
290     json_parse_ctx_t parse_ctx;
291     const WCHAR *buf;
292     jsstr_t *str;
293     jsval_t ret;
294     HRESULT hres;
295 
296     if(argc != 1) {
297         FIXME("Unsupported args\n");
298         return E_INVALIDARG;
299     }
300 
301     hres = to_flat_string(ctx, argv[0], &str, &buf);
302     if(FAILED(hres))
303         return hres;
304 
305     TRACE("%s\n", debugstr_w(buf));
306 
307     parse_ctx.ptr = buf;
308     parse_ctx.end = buf + jsstr_length(str);
309     parse_ctx.ctx = ctx;
310     hres = parse_json_value(&parse_ctx, &ret);
311     jsstr_release(str);
312     if(FAILED(hres))
313         return hres;
314 
315     if(skip_spaces(&parse_ctx)) {
316         FIXME("syntax error\n");
317         jsval_release(ret);
318         return E_FAIL;
319     }
320 
321     if(r)
322         *r = ret;
323     else
324         jsval_release(ret);
325     return S_OK;
326 }
327 
328 typedef struct {
329     script_ctx_t *ctx;
330 
331     WCHAR *buf;
332     size_t buf_size;
333     size_t buf_len;
334 
335     jsdisp_t **stack;
336     size_t stack_top;
337     size_t stack_size;
338 
339     WCHAR gap[11]; /* according to the spec, it's no longer than 10 chars */
340 } stringify_ctx_t;
341 
342 static BOOL stringify_push_obj(stringify_ctx_t *ctx, jsdisp_t *obj)
343 {
344     if(!ctx->stack_size) {
345         ctx->stack = heap_alloc(4*sizeof(*ctx->stack));
346         if(!ctx->stack)
347             return FALSE;
348         ctx->stack_size = 4;
349     }else if(ctx->stack_top == ctx->stack_size) {
350         jsdisp_t **new_stack;
351 
352         new_stack = heap_realloc(ctx->stack, ctx->stack_size*2*sizeof(*ctx->stack));
353         if(!new_stack)
354             return FALSE;
355         ctx->stack = new_stack;
356         ctx->stack_size *= 2;
357     }
358 
359     ctx->stack[ctx->stack_top++] = obj;
360     return TRUE;
361 }
362 
363 static void stringify_pop_obj(stringify_ctx_t *ctx)
364 {
365     ctx->stack_top--;
366 }
367 
368 static BOOL is_on_stack(stringify_ctx_t *ctx, jsdisp_t *obj)
369 {
370     size_t i = ctx->stack_top;
371     while(i--) {
372         if(ctx->stack[i] == obj)
373             return TRUE;
374     }
375     return FALSE;
376 }
377 
378 static BOOL append_string_len(stringify_ctx_t *ctx, const WCHAR *str, size_t len)
379 {
380     if(!ctx->buf_size) {
381         ctx->buf = heap_alloc(len*2*sizeof(WCHAR));
382         if(!ctx->buf)
383             return FALSE;
384         ctx->buf_size = len*2;
385     }else if(ctx->buf_len + len > ctx->buf_size) {
386         WCHAR *new_buf;
387         size_t new_size;
388 
389         new_size = ctx->buf_size * 2 + len;
390         new_buf = heap_realloc(ctx->buf, new_size*sizeof(WCHAR));
391         if(!new_buf)
392             return FALSE;
393         ctx->buf = new_buf;
394         ctx->buf_size = new_size;
395     }
396 
397     if(len)
398         memcpy(ctx->buf + ctx->buf_len, str, len*sizeof(WCHAR));
399     ctx->buf_len += len;
400     return TRUE;
401 }
402 
403 static inline BOOL append_string(stringify_ctx_t *ctx, const WCHAR *str)
404 {
405     return append_string_len(ctx, str, strlenW(str));
406 }
407 
408 static inline BOOL append_char(stringify_ctx_t *ctx, WCHAR c)
409 {
410     return append_string_len(ctx, &c, 1);
411 }
412 
413 static inline BOOL append_simple_quote(stringify_ctx_t *ctx, WCHAR c)
414 {
415     WCHAR str[] = {'\\',c};
416     return append_string_len(ctx, str, 2);
417 }
418 
419 static HRESULT maybe_to_primitive(script_ctx_t *ctx, jsval_t val, jsval_t *r)
420 {
421     jsdisp_t *obj;
422     HRESULT hres;
423 
424     if(!is_object_instance(val) || !get_object(val) || !(obj = iface_to_jsdisp(get_object(val))))
425         return jsval_copy(val, r);
426 
427     if(is_class(obj, JSCLASS_NUMBER)) {
428         double n;
429         hres = to_number(ctx, val, &n);
430         jsdisp_release(obj);
431         if(SUCCEEDED(hres))
432             *r = jsval_number(n);
433         return hres;
434     }
435 
436     if(is_class(obj, JSCLASS_STRING)) {
437         jsstr_t *str;
438         hres = to_string(ctx, val, &str);
439         jsdisp_release(obj);
440         if(SUCCEEDED(hres))
441             *r = jsval_string(str);
442         return hres;
443     }
444 
445     if(is_class(obj, JSCLASS_BOOLEAN)) {
446         *r = jsval_bool(bool_obj_value(obj));
447         jsdisp_release(obj);
448         return S_OK;
449     }
450 
451     *r = jsval_obj(obj);
452     return S_OK;
453 }
454 
455 /* ECMA-262 5.1 Edition    15.12.3 (abstract operation Quote) */
456 static HRESULT json_quote(stringify_ctx_t *ctx, const WCHAR *ptr, size_t len)
457 {
458     if(!ptr || !append_char(ctx, '"'))
459         return E_OUTOFMEMORY;
460 
461     while(len--) {
462         switch(*ptr) {
463         case '"':
464         case '\\':
465             if(!append_simple_quote(ctx, *ptr))
466                 return E_OUTOFMEMORY;
467             break;
468         case '\b':
469             if(!append_simple_quote(ctx, 'b'))
470                 return E_OUTOFMEMORY;
471             break;
472         case '\f':
473             if(!append_simple_quote(ctx, 'f'))
474                 return E_OUTOFMEMORY;
475             break;
476         case '\n':
477             if(!append_simple_quote(ctx, 'n'))
478                 return E_OUTOFMEMORY;
479             break;
480         case '\r':
481             if(!append_simple_quote(ctx, 'r'))
482                 return E_OUTOFMEMORY;
483             break;
484         case '\t':
485             if(!append_simple_quote(ctx, 't'))
486                 return E_OUTOFMEMORY;
487             break;
488         default:
489             if(*ptr < ' ') {
490                 static const WCHAR formatW[] = {'\\','u','%','0','4','x',0};
491                 WCHAR buf[7];
492                 sprintfW(buf, formatW, *ptr);
493                 if(!append_string(ctx, buf))
494                     return E_OUTOFMEMORY;
495             }else {
496                 if(!append_char(ctx, *ptr))
497                     return E_OUTOFMEMORY;
498             }
499         }
500         ptr++;
501     }
502 
503     return append_char(ctx, '"') ? S_OK : E_OUTOFMEMORY;
504 }
505 
506 static inline BOOL is_callable(jsdisp_t *obj)
507 {
508     return is_class(obj, JSCLASS_FUNCTION);
509 }
510 
511 static HRESULT stringify(stringify_ctx_t *ctx, jsval_t val);
512 
513 /* ECMA-262 5.1 Edition    15.12.3 (abstract operation JA) */
514 static HRESULT stringify_array(stringify_ctx_t *ctx, jsdisp_t *obj)
515 {
516     unsigned length, i, j;
517     jsval_t val;
518     HRESULT hres;
519 
520     if(is_on_stack(ctx, obj)) {
521         FIXME("Found a cycle\n");
522         return E_FAIL;
523     }
524 
525     if(!stringify_push_obj(ctx, obj))
526         return E_OUTOFMEMORY;
527 
528     if(!append_char(ctx, '['))
529         return E_OUTOFMEMORY;
530 
531     length = array_get_length(obj);
532 
533     for(i=0; i < length; i++) {
534         if(i && !append_char(ctx, ','))
535             return E_OUTOFMEMORY;
536 
537         if(*ctx->gap) {
538             if(!append_char(ctx, '\n'))
539                 return E_OUTOFMEMORY;
540 
541             for(j=0; j < ctx->stack_top; j++) {
542                 if(!append_string(ctx, ctx->gap))
543                     return E_OUTOFMEMORY;
544             }
545         }
546 
547         hres = jsdisp_get_idx(obj, i, &val);
548         if(SUCCEEDED(hres)) {
549             hres = stringify(ctx, val);
550             if(FAILED(hres))
551                 return hres;
552             if(hres == S_FALSE && !append_string(ctx, nullW))
553                 return E_OUTOFMEMORY;
554         }else if(hres == DISP_E_UNKNOWNNAME) {
555             if(!append_string(ctx, nullW))
556                 return E_OUTOFMEMORY;
557         }else {
558             return hres;
559         }
560     }
561 
562     if((length && *ctx->gap && !append_char(ctx, '\n')) || !append_char(ctx, ']'))
563         return E_OUTOFMEMORY;
564 
565     stringify_pop_obj(ctx);
566     return S_OK;
567 }
568 
569 /* ECMA-262 5.1 Edition    15.12.3 (abstract operation JO) */
570 static HRESULT stringify_object(stringify_ctx_t *ctx, jsdisp_t *obj)
571 {
572     DISPID dispid = DISPID_STARTENUM;
573     jsval_t val = jsval_undefined();
574     unsigned prop_cnt = 0, i;
575     size_t stepback;
576     BSTR prop_name;
577     HRESULT hres;
578 
579     if(is_on_stack(ctx, obj)) {
580         FIXME("Found a cycle\n");
581         return E_FAIL;
582     }
583 
584     if(!stringify_push_obj(ctx, obj))
585         return E_OUTOFMEMORY;
586 
587     if(!append_char(ctx, '{'))
588         return E_OUTOFMEMORY;
589 
590     while((hres = IDispatchEx_GetNextDispID(&obj->IDispatchEx_iface, fdexEnumDefault, dispid, &dispid)) == S_OK) {
591         jsval_release(val);
592         hres = jsdisp_propget(obj, dispid, &val);
593         if(FAILED(hres))
594             return hres;
595 
596         if(is_undefined(val))
597             continue;
598 
599         stepback = ctx->buf_len;
600 
601         if(prop_cnt && !append_char(ctx, ',')) {
602             hres = E_OUTOFMEMORY;
603             break;
604         }
605 
606         if(*ctx->gap) {
607             if(!append_char(ctx, '\n')) {
608                 hres = E_OUTOFMEMORY;
609                 break;
610             }
611 
612             for(i=0; i < ctx->stack_top; i++) {
613                 if(!append_string(ctx, ctx->gap)) {
614                     hres = E_OUTOFMEMORY;
615                     break;
616                 }
617             }
618         }
619 
620         hres = IDispatchEx_GetMemberName(&obj->IDispatchEx_iface, dispid, &prop_name);
621         if(FAILED(hres))
622             break;
623 
624         hres = json_quote(ctx, prop_name, SysStringLen(prop_name));
625         SysFreeString(prop_name);
626         if(FAILED(hres))
627             break;
628 
629         if(!append_char(ctx, ':') || (*ctx->gap && !append_char(ctx, ' '))) {
630             hres = E_OUTOFMEMORY;
631             break;
632         }
633 
634         hres = stringify(ctx, val);
635         if(FAILED(hres))
636             break;
637 
638         if(hres == S_FALSE) {
639             ctx->buf_len = stepback;
640             continue;
641         }
642 
643         prop_cnt++;
644     }
645     jsval_release(val);
646     if(FAILED(hres))
647         return hres;
648 
649     if(prop_cnt && *ctx->gap) {
650         if(!append_char(ctx, '\n'))
651             return E_OUTOFMEMORY;
652 
653         for(i=1; i < ctx->stack_top; i++) {
654             if(!append_string(ctx, ctx->gap)) {
655                 hres = E_OUTOFMEMORY;
656                 break;
657             }
658         }
659     }
660 
661     if(!append_char(ctx, '}'))
662         return E_OUTOFMEMORY;
663 
664     stringify_pop_obj(ctx);
665     return S_OK;
666 }
667 
668 /* ECMA-262 5.1 Edition    15.12.3 (abstract operation Str) */
669 static HRESULT stringify(stringify_ctx_t *ctx, jsval_t val)
670 {
671     jsval_t value;
672     HRESULT hres;
673 
674     if(is_object_instance(val) && get_object(val)) {
675         jsdisp_t *obj;
676         DISPID id;
677 
678         obj = iface_to_jsdisp(get_object(val));
679         if(!obj)
680             return S_FALSE;
681 
682         hres = jsdisp_get_id(obj, toJSONW, 0, &id);
683         jsdisp_release(obj);
684         if(hres == S_OK)
685             FIXME("Use toJSON.\n");
686     }
687 
688     /* FIXME: Support replacer replacer. */
689 
690     hres = maybe_to_primitive(ctx->ctx, val, &value);
691     if(FAILED(hres))
692         return hres;
693 
694     switch(jsval_type(value)) {
695     case JSV_NULL:
696         if(!append_string(ctx, nullW))
697             hres = E_OUTOFMEMORY;
698         break;
699     case JSV_BOOL:
700         if(!append_string(ctx, get_bool(value) ? trueW : falseW))
701             hres = E_OUTOFMEMORY;
702         break;
703     case JSV_STRING: {
704         jsstr_t *str = get_string(value);
705         const WCHAR *ptr = jsstr_flatten(str);
706         if(ptr)
707             hres = json_quote(ctx, ptr, jsstr_length(str));
708         else
709             hres = E_OUTOFMEMORY;
710         break;
711     }
712     case JSV_NUMBER: {
713         double n = get_number(value);
714         if(is_finite(n)) {
715             const WCHAR *ptr;
716             jsstr_t *str;
717 
718             /* FIXME: Optimize. There is no need for jsstr_t here. */
719             hres = double_to_string(n, &str);
720             if(FAILED(hres))
721                 break;
722 
723             ptr = jsstr_flatten(str);
724             assert(ptr != NULL);
725             hres = ptr && !append_string_len(ctx, ptr, jsstr_length(str)) ? E_OUTOFMEMORY : S_OK;
726             jsstr_release(str);
727         }else {
728             if(!append_string(ctx, nullW))
729                 hres = E_OUTOFMEMORY;
730         }
731         break;
732     }
733     case JSV_OBJECT: {
734         jsdisp_t *obj;
735 
736         obj = iface_to_jsdisp(get_object(value));
737         if(!obj) {
738             hres = S_FALSE;
739             break;
740         }
741 
742         if(!is_callable(obj))
743             hres = is_class(obj, JSCLASS_ARRAY) ? stringify_array(ctx, obj) : stringify_object(ctx, obj);
744         else
745             hres = S_FALSE;
746 
747         jsdisp_release(obj);
748         break;
749     }
750     case JSV_UNDEFINED:
751         hres = S_FALSE;
752         break;
753     case JSV_VARIANT:
754         FIXME("VARIANT\n");
755         hres = E_NOTIMPL;
756         break;
757     }
758 
759     jsval_release(value);
760     return hres;
761 }
762 
763 /* ECMA-262 5.1 Edition    15.12.3 */
764 static HRESULT JSON_stringify(script_ctx_t *ctx, vdisp_t *jsthis, WORD flags, unsigned argc, jsval_t *argv, jsval_t *r)
765 {
766     stringify_ctx_t stringify_ctx = {ctx, NULL,0,0, NULL,0,0, {0}};
767     HRESULT hres;
768 
769     TRACE("\n");
770 
771     if(!argc) {
772         if(r)
773             *r = jsval_undefined();
774         return S_OK;
775     }
776 
777     if(argc >= 2 && is_object_instance(argv[1])) {
778         FIXME("Replacer %s not yet supported\n", debugstr_jsval(argv[1]));
779         return E_NOTIMPL;
780     }
781 
782     if(argc >= 3) {
783         jsval_t space_val;
784 
785         hres = maybe_to_primitive(ctx, argv[2], &space_val);
786         if(FAILED(hres))
787             return hres;
788 
789         if(is_number(space_val)) {
790             double n = get_number(space_val);
791             if(n >= 1) {
792                 int i, len;
793                 if(n > 10)
794                     n = 10;
795                 len = floor(n);
796                 for(i=0; i < len; i++)
797                     stringify_ctx.gap[i] = ' ';
798                 stringify_ctx.gap[len] = 0;
799             }
800         }else if(is_string(space_val)) {
801             jsstr_t *space_str = get_string(space_val);
802             size_t len = jsstr_length(space_str);
803             if(len > 10)
804                 len = 10;
805             jsstr_extract(space_str, 0, len, stringify_ctx.gap);
806         }
807 
808         jsval_release(space_val);
809     }
810 
811     hres = stringify(&stringify_ctx, argv[0]);
812     if(SUCCEEDED(hres) && r) {
813         assert(!stringify_ctx.stack_top);
814 
815         if(hres == S_OK) {
816             jsstr_t *ret = jsstr_alloc_len(stringify_ctx.buf, stringify_ctx.buf_len);
817             if(ret)
818                 *r = jsval_string(ret);
819             else
820                 hres = E_OUTOFMEMORY;
821         }else {
822             *r = jsval_undefined();
823         }
824     }
825 
826     heap_free(stringify_ctx.buf);
827     heap_free(stringify_ctx.stack);
828     return hres;
829 }
830 
831 static const builtin_prop_t JSON_props[] = {
832     {parseW,     JSON_parse,     PROPF_METHOD|2},
833     {stringifyW, JSON_stringify, PROPF_METHOD|3}
834 };
835 
836 static const builtin_info_t JSON_info = {
837     JSCLASS_JSON,
838     {NULL, NULL, 0},
839     ARRAY_SIZE(JSON_props),
840     JSON_props,
841     NULL,
842     NULL
843 };
844 
845 HRESULT create_json(script_ctx_t *ctx, jsdisp_t **ret)
846 {
847     jsdisp_t *json;
848     HRESULT hres;
849 
850     json = heap_alloc_zero(sizeof(*json));
851     if(!json)
852         return E_OUTOFMEMORY;
853 
854     hres = init_dispex_from_constr(json, ctx, &JSON_info, ctx->object_constr);
855     if(FAILED(hres)) {
856         heap_free(json);
857         return hres;
858     }
859 
860     *ret = json;
861     return S_OK;
862 }
863