Bug 469739 - Add support for displaying Vista UAC shield icon; r=joe sr=vladimir
[wine-gecko.git] / js / src / json.cpp
blobe6cd28ab8a7248c3ffb74886675f2f2a02e2ad49
1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 * The contents of this file are subject to the Mozilla Public License Version
5 * 1.1 (the "License"); you may not use this file except in compliance with
6 * the License. You may obtain a copy of the License at
7 * http://www.mozilla.org/MPL/
9 * Software distributed under the License is distributed on an "AS IS" basis,
10 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11 * for the specific language governing rights and limitations under the
12 * License.
14 * The Original Code is SpiderMonkey JSON.
16 * The Initial Developer of the Original Code is
17 * Mozilla Corporation.
18 * Portions created by the Initial Developer are Copyright (C) 1998-1999
19 * the Initial Developer. All Rights Reserved.
21 * Contributor(s):
22 * Robert Sayre <sayrer@gmail.com>
24 * Alternatively, the contents of this file may be used under the terms of
25 * either of the GNU General Public License Version 2 or later (the "GPL"),
26 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
27 * in which case the provisions of the GPL or the LGPL are applicable instead
28 * of those above. If you wish to allow use of your version of this file only
29 * under the terms of either the GPL or the LGPL, and not to allow others to
30 * use your version of this file under the terms of the MPL, indicate your
31 * decision by deleting the provisions above and replace them with the notice
32 * and other provisions required by the GPL or the LGPL. If you do not delete
33 * the provisions above, a recipient may use your version of this file under
34 * the terms of any one of the MPL, the GPL or the LGPL.
36 * ***** END LICENSE BLOCK ***** */
39 #include "jsapi.h"
40 #include "jsarena.h"
41 #include "jsarray.h"
42 #include "jsatom.h"
43 #include "jsbool.h"
44 #include "jscntxt.h"
45 #include "jsdtoa.h"
46 #include "jsinterp.h"
47 #include "jsiter.h"
48 #include "jsnum.h"
49 #include "jsobj.h"
50 #include "jsprf.h"
51 #include "jsscan.h"
52 #include "jsstr.h"
53 #include "jstypes.h"
54 #include "jsutil.h"
56 #include "json.h"
58 JSClass js_JSONClass = {
59 js_JSON_str,
60 JSCLASS_HAS_CACHED_PROTO(JSProto_JSON),
61 JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
62 JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
63 JSCLASS_NO_OPTIONAL_MEMBERS
66 JSBool
67 js_json_parse(JSContext *cx, uintN argc, jsval *vp)
69 JSString *s = NULL;
70 jsval *argv = vp + 2;
72 // Must throw an Error if there isn't a first arg
73 if (!JS_ConvertArguments(cx, argc, argv, "S", &s))
74 return JS_FALSE;
77 JSONParser *jp = js_BeginJSONParse(cx, vp);
78 JSBool ok = jp != NULL;
80 if (ok) {
81 ok = js_ConsumeJSONText(cx, jp, JS_GetStringChars(s), JS_GetStringLength(s));
82 ok &= js_FinishJSONParse(cx, jp);
85 if (!ok)
86 JS_ReportError(cx, "Error parsing JSON");
88 return ok;
91 class StringifyClosure : JSAutoTempValueRooter
93 public:
94 StringifyClosure(JSContext *cx, size_t len, jsval *vec)
95 : JSAutoTempValueRooter(cx, len, vec), cx(cx), s(vec)
99 JSContext *cx;
100 jsval *s;
103 static JSBool
104 WriteCallback(const jschar *buf, uint32 len, void *data)
106 StringifyClosure *sc = static_cast<StringifyClosure*>(data);
107 JSString *s1 = JSVAL_TO_STRING(sc->s[0]);
108 JSString *s2 = js_NewStringCopyN(sc->cx, buf, len);
109 if (!s2)
110 return JS_FALSE;
112 sc->s[1] = STRING_TO_JSVAL(s2);
114 s1 = js_ConcatStrings(sc->cx, s1, s2);
115 if (!s1)
116 return JS_FALSE;
118 sc->s[0] = STRING_TO_JSVAL(s1);
119 sc->s[1] = JSVAL_VOID;
121 return JS_TRUE;
124 JSBool
125 js_json_stringify(JSContext *cx, uintN argc, jsval *vp)
127 JSObject *obj;
128 jsval *argv = vp + 2;
130 // Must throw an Error if there isn't a first arg
131 if (!JS_ConvertArguments(cx, argc, argv, "o", &obj))
132 return JS_FALSE;
134 // Only use objects and arrays as the root for now
135 *vp = OBJECT_TO_JSVAL(obj);
137 JSBool ok = js_TryJSON(cx, vp);
138 JSType type;
139 if (!ok ||
140 JSVAL_IS_PRIMITIVE(*vp) ||
141 ((type = JS_TypeOfValue(cx, *vp)) == JSTYPE_FUNCTION ||
142 type == JSTYPE_XML)) {
143 JS_ReportError(cx, "Invalid argument");
144 return JS_FALSE;
147 JSString *s = JS_NewStringCopyN(cx, "", 0);
148 if (!s)
149 ok = JS_FALSE;
151 if (ok) {
152 jsval vec[2] = {STRING_TO_JSVAL(s), JSVAL_VOID};
153 StringifyClosure sc(cx, 2, vec);
154 JSAutoTempValueRooter resultTvr(cx, 1, sc.s);
155 ok = js_Stringify(cx, vp, NULL, &WriteCallback, &sc, 0);
156 *vp = *sc.s;
159 return ok;
162 JSBool
163 js_TryJSON(JSContext *cx, jsval *vp)
165 // Checks whether the return value implements toJSON()
166 JSBool ok = JS_TRUE;
168 if (!JSVAL_IS_PRIMITIVE(*vp)) {
169 JSObject *obj = JSVAL_TO_OBJECT(*vp);
170 ok = js_TryMethod(cx, obj, cx->runtime->atomState.toJSONAtom, 0, NULL, vp);
173 return ok;
177 static const jschar quote = jschar('"');
178 static const jschar backslash = jschar('\\');
179 static const jschar unicodeEscape[] = {'\\', 'u', '0', '0'};
181 static JSBool
182 write_string(JSContext *cx, JSONWriteCallback callback, void *data, const jschar *buf, uint32 len)
184 if (!callback(&quote, 1, data))
185 return JS_FALSE;
187 uint32 mark = 0;
188 uint32 i;
189 for (i = 0; i < len; ++i) {
190 if (buf[i] == quote || buf[i] == backslash) {
191 if (!callback(&buf[mark], i - mark, data) || !callback(&backslash, 1, data) ||
192 !callback(&buf[i], 1, data)) {
193 return JS_FALSE;
195 mark = i + 1;
196 } else if (buf[i] <= 31 || buf[i] == 127) {
197 if (!callback(&buf[mark], i - mark, data) || !callback(unicodeEscape, 4, data))
198 return JS_FALSE;
199 char ubuf[3];
200 size_t len = JS_snprintf(ubuf, sizeof(ubuf), "%.2x", buf[i]);
201 JS_ASSERT(len == 2);
202 jschar wbuf[3];
203 size_t wbufSize = JS_ARRAY_LENGTH(wbuf);
204 if (!js_InflateStringToBuffer(cx, ubuf, len, wbuf, &wbufSize) ||
205 !callback(wbuf, wbufSize, data)) {
206 return JS_FALSE;
208 mark = i + 1;
212 if (mark < len && !callback(&buf[mark], len - mark, data))
213 return JS_FALSE;
215 if (!callback(&quote, 1, data))
216 return JS_FALSE;
218 return JS_TRUE;
221 JSBool
222 js_Stringify(JSContext *cx, jsval *vp, JSObject *replacer,
223 JSONWriteCallback callback, void *data, uint32 depth)
225 if (depth > JSON_MAX_DEPTH)
226 return JS_FALSE; /* encoding error */
228 JSBool ok = JS_TRUE;
229 JSObject *obj = JSVAL_TO_OBJECT(*vp);
230 JSBool isArray = JS_IsArrayObject(cx, obj);
231 jschar output = jschar(isArray ? '[' : '{');
232 if (!callback(&output, 1, data))
233 return JS_FALSE;
235 JSObject *iterObj = NULL;
236 jsint i = 0;
237 jsuint length = 0;
239 if (isArray) {
240 if (!js_GetLengthProperty(cx, obj, &length))
241 return JS_FALSE;
242 } else {
243 if (!js_ValueToIterator(cx, JSITER_ENUMERATE, vp))
244 return JS_FALSE;
245 iterObj = JSVAL_TO_OBJECT(*vp);
248 jsval outputValue = JSVAL_VOID;
249 JSAutoTempValueRooter tvr(cx, 1, &outputValue);
251 jsval key;
252 JSBool memberWritten = JS_FALSE;
253 do {
254 outputValue = JSVAL_VOID;
256 if (isArray) {
257 if ((jsuint)i >= length)
258 break;
259 ok = OBJ_GET_PROPERTY(cx, obj, INT_TO_JSID(i), &outputValue);
260 i++;
261 } else {
262 ok = js_CallIteratorNext(cx, iterObj, &key);
263 if (!ok)
264 break;
265 if (key == JSVAL_HOLE)
266 break;
268 JSString *ks;
269 if (JSVAL_IS_STRING(key)) {
270 ks = JSVAL_TO_STRING(key);
271 } else {
272 ks = js_ValueToString(cx, key);
273 if (!ks) {
274 ok = JS_FALSE;
275 break;
279 ok = JS_GetUCProperty(cx, obj, JS_GetStringChars(ks),
280 JS_GetStringLength(ks), &outputValue);
283 if (!ok)
284 break;
286 // if this is an array, holes are transmitted as null
287 if (isArray && outputValue == JSVAL_VOID) {
288 outputValue = JSVAL_NULL;
289 } else if (JSVAL_IS_OBJECT(outputValue)) {
290 ok = js_TryJSON(cx, &outputValue);
291 if (!ok)
292 break;
295 // elide undefined values
296 if (outputValue == JSVAL_VOID)
297 continue;
299 // output a comma unless this is the first member to write
300 if (memberWritten) {
301 output = jschar(',');
302 ok = callback(&output, 1, data);
303 if (!ok)
304 break;
306 memberWritten = JS_TRUE;
308 JSType type = JS_TypeOfValue(cx, outputValue);
310 // Can't encode these types, so drop them
311 if (type == JSTYPE_FUNCTION || type == JSTYPE_XML)
312 break;
314 // Be careful below, this string is weakly rooted.
315 JSString *s;
317 // If this isn't an array, we need to output a key
318 if (!isArray) {
319 s = js_ValueToString(cx, key);
320 if (!s) {
321 ok = JS_FALSE;
322 break;
325 ok = write_string(cx, callback, data, JS_GetStringChars(s), JS_GetStringLength(s));
326 if (!ok)
327 break;
329 output = jschar(':');
330 ok = callback(&output, 1, data);
331 if (!ok)
332 break;
335 if (!JSVAL_IS_PRIMITIVE(outputValue)) {
336 // recurse
337 ok = js_Stringify(cx, &outputValue, replacer, callback, data, depth + 1);
338 } else {
339 JSString *outputString;
340 s = js_ValueToString(cx, outputValue);
341 if (!s) {
342 ok = JS_FALSE;
343 break;
346 if (type == JSTYPE_STRING) {
347 ok = write_string(cx, callback, data, JS_GetStringChars(s), JS_GetStringLength(s));
348 if (!ok)
349 break;
351 continue;
354 if (type == JSTYPE_NUMBER) {
355 if (JSVAL_IS_DOUBLE(outputValue)) {
356 jsdouble d = *JSVAL_TO_DOUBLE(outputValue);
357 if (!JSDOUBLE_IS_FINITE(d))
358 outputString = JS_NewStringCopyN(cx, "null", 4);
359 else
360 outputString = s;
361 } else {
362 outputString = s;
364 } else if (type == JSTYPE_BOOLEAN) {
365 outputString = s;
366 } else if (JSVAL_IS_NULL(outputValue)) {
367 outputString = JS_NewStringCopyN(cx, "null", 4);
368 } else {
369 ok = JS_FALSE; // encoding error
370 break;
373 if (!outputString) {
374 ok = JS_FALSE;
375 break;
378 ok = callback(JS_GetStringChars(outputString), JS_GetStringLength(outputString), data);
380 } while (ok);
382 if (iterObj) {
383 // Always close the iterator, but make sure not to stomp on OK
384 ok &= js_CloseIterator(cx, *vp);
385 // encoding error or propagate? FIXME: Bug 408838.
388 if (!ok) {
389 JS_ReportError(cx, "Error during JSON encoding");
390 return JS_FALSE;
393 output = jschar(isArray ? ']' : '}');
394 ok = callback(&output, 1, data);
396 return ok;
399 // helper to determine whether a character could be part of a number
400 static JSBool IsNumChar(jschar c)
402 return ((c <= '9' && c >= '0') || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E');
405 JSONParser *
406 js_BeginJSONParse(JSContext *cx, jsval *rootVal)
408 if (!cx)
409 return NULL;
411 JSObject *arr = js_NewArrayObject(cx, 0, NULL);
412 if (!arr)
413 return NULL;
415 JSONParser *jp = (JSONParser*) JS_malloc(cx, sizeof(JSONParser));
416 if (!jp)
417 return NULL;
418 jp->buffer = NULL;
420 jp->objectStack = arr;
421 if (!js_AddRoot(cx, &jp->objectStack, "JSON parse stack"))
422 goto bad;
424 jp->hexChar = 0;
425 jp->numHex = 0;
426 jp->statep = jp->stateStack;
427 *jp->statep = JSON_PARSE_STATE_INIT;
428 jp->rootVal = rootVal;
430 jp->objectKey = (JSStringBuffer*) JS_malloc(cx, sizeof(JSStringBuffer));
431 if (!jp->objectKey)
432 goto bad;
433 js_InitStringBuffer(jp->objectKey);
435 jp->buffer = (JSStringBuffer*) JS_malloc(cx, sizeof(JSStringBuffer));
436 if (!jp->buffer)
437 goto bad;
438 js_InitStringBuffer(jp->buffer);
440 return jp;
441 bad:
442 JS_free(cx, jp->buffer);
443 JS_free(cx, jp);
444 return NULL;
447 JSBool
448 js_FinishJSONParse(JSContext *cx, JSONParser *jp)
450 if (!jp)
451 return JS_TRUE;
453 if (jp->objectKey)
454 js_FinishStringBuffer(jp->objectKey);
455 JS_free(cx, jp->objectKey);
457 if (jp->buffer)
458 js_FinishStringBuffer(jp->buffer);
459 JS_free(cx, jp->buffer);
461 if (!js_RemoveRoot(cx->runtime, &jp->objectStack))
462 return JS_FALSE;
463 JSBool ok = *jp->statep == JSON_PARSE_STATE_FINISHED;
464 JS_free(cx, jp);
466 return ok;
469 static JSBool
470 PushState(JSONParser *jp, JSONParserState state)
472 if (*jp->statep == JSON_PARSE_STATE_FINISHED)
473 return JS_FALSE; // extra input
475 jp->statep++;
476 if ((uint32)(jp->statep - jp->stateStack) >= JS_ARRAY_LENGTH(jp->stateStack))
477 return JS_FALSE; // too deep
479 *jp->statep = state;
481 return JS_TRUE;
484 static JSBool
485 PopState(JSONParser *jp)
487 jp->statep--;
488 if (jp->statep < jp->stateStack) {
489 jp->statep = jp->stateStack;
490 return JS_FALSE;
493 if (*jp->statep == JSON_PARSE_STATE_INIT)
494 *jp->statep = JSON_PARSE_STATE_FINISHED;
496 return JS_TRUE;
499 static JSBool
500 PushValue(JSContext *cx, JSONParser *jp, JSObject *parent, jsval value)
502 JSAutoTempValueRooter tvr(cx, 1, &value);
504 JSBool ok;
505 if (OBJ_IS_ARRAY(cx, parent)) {
506 jsuint len;
507 ok = js_GetLengthProperty(cx, parent, &len);
508 if (ok) {
509 ok = OBJ_DEFINE_PROPERTY(cx, parent, INT_TO_JSID(len), value,
510 NULL, NULL, JSPROP_ENUMERATE, NULL);
512 } else {
513 ok = JS_DefineUCProperty(cx, parent, jp->objectKey->base,
514 STRING_BUFFER_OFFSET(jp->objectKey), value,
515 NULL, NULL, JSPROP_ENUMERATE);
516 js_FinishStringBuffer(jp->objectKey);
517 js_InitStringBuffer(jp->objectKey);
520 return ok;
523 static JSBool
524 PushObject(JSContext *cx, JSONParser *jp, JSObject *obj)
526 jsuint len;
527 if (!js_GetLengthProperty(cx, jp->objectStack, &len))
528 return JS_FALSE;
529 if (len >= JSON_MAX_DEPTH)
530 return JS_FALSE; // decoding error
532 jsval v = OBJECT_TO_JSVAL(obj);
533 JSAutoTempValueRooter tvr(cx, v);
535 // Check if this is the root object
536 if (len == 0) {
537 *jp->rootVal = v;
538 // This property must be enumerable to keep the array dense
539 if (!OBJ_DEFINE_PROPERTY(cx, jp->objectStack, INT_TO_JSID(0), *jp->rootVal,
540 NULL, NULL, JSPROP_ENUMERATE, NULL)) {
541 return JS_FALSE;
543 return JS_TRUE;
546 jsval p;
547 if (!OBJ_GET_PROPERTY(cx, jp->objectStack, INT_TO_JSID(len - 1), &p))
548 return JS_FALSE;
550 JS_ASSERT(JSVAL_IS_OBJECT(p));
551 JSObject *parent = JSVAL_TO_OBJECT(p);
552 if (!PushValue(cx, jp, parent, OBJECT_TO_JSVAL(obj)))
553 return JS_FALSE;
555 // This property must be enumerable to keep the array dense
556 if (!OBJ_DEFINE_PROPERTY(cx, jp->objectStack, INT_TO_JSID(len), v,
557 NULL, NULL, JSPROP_ENUMERATE, NULL)) {
558 return JS_FALSE;
561 return JS_TRUE;
564 static JSObject *
565 GetTopOfObjectStack(JSContext *cx, JSONParser *jp)
567 jsuint length;
568 if (!js_GetLengthProperty(cx, jp->objectStack, &length))
569 return NULL;
571 jsval o;
572 if (!OBJ_GET_PROPERTY(cx, jp->objectStack, INT_TO_JSID(length - 1), &o))
573 return NULL;
575 JS_ASSERT(!JSVAL_IS_PRIMITIVE(o));
576 return JSVAL_TO_OBJECT(o);
579 static JSBool
580 OpenObject(JSContext *cx, JSONParser *jp)
582 JSObject *obj = js_NewObject(cx, &js_ObjectClass, NULL, NULL, 0);
583 if (!obj)
584 return JS_FALSE;
586 return PushObject(cx, jp, obj);
589 static JSBool
590 OpenArray(JSContext *cx, JSONParser *jp)
592 // Add an array to an existing array or object
593 JSObject *arr = js_NewArrayObject(cx, 0, NULL);
594 if (!arr)
595 return JS_FALSE;
597 return PushObject(cx, jp, arr);
600 static JSBool
601 CloseObject(JSContext *cx, JSONParser *jp)
603 jsuint len;
604 if (!js_GetLengthProperty(cx, jp->objectStack, &len))
605 return JS_FALSE;
606 if (!js_SetLengthProperty(cx, jp->objectStack, len - 1))
607 return JS_FALSE;
609 return JS_TRUE;
612 static JSBool
613 CloseArray(JSContext *cx, JSONParser *jp)
615 return CloseObject(cx, jp);
618 static JSBool
619 HandleNumber(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len)
621 const jschar *ep;
622 double val;
623 if (!js_strtod(cx, buf, buf + len, &ep, &val) || ep != buf + len)
624 return JS_FALSE;
626 JSBool ok;
627 jsval numVal;
628 JSObject *obj = GetTopOfObjectStack(cx, jp);
629 if (obj && JS_NewNumberValue(cx, val, &numVal))
630 ok = PushValue(cx, jp, obj, numVal);
631 else
632 ok = JS_FALSE; // decode error
634 return ok;
637 static JSBool
638 HandleString(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len)
640 JSObject *obj = GetTopOfObjectStack(cx, jp);
641 JSString *str = js_NewStringCopyN(cx, buf, len);
642 if (!obj || !str)
643 return JS_FALSE;
645 return PushValue(cx, jp, obj, STRING_TO_JSVAL(str));
648 static JSBool
649 HandleKeyword(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len)
651 jsval keyword;
652 JSTokenType tt = js_CheckKeyword(buf, len);
653 if (tt != TOK_PRIMARY)
654 return JS_FALSE;
656 if (buf[0] == 'n')
657 keyword = JSVAL_NULL;
658 else if (buf[0] == 't')
659 keyword = JSVAL_TRUE;
660 else if (buf[0] == 'f')
661 keyword = JSVAL_FALSE;
662 else
663 return JS_FALSE;
665 JSObject *obj = GetTopOfObjectStack(cx, jp);
666 if (!obj)
667 return JS_FALSE;
669 return PushValue(cx, jp, obj, keyword);
672 static JSBool
673 HandleData(JSContext *cx, JSONParser *jp, JSONDataType type)
675 JSBool ok = JS_FALSE;
677 if (!STRING_BUFFER_OK(jp->buffer))
678 return JS_FALSE;
680 switch (type) {
681 case JSON_DATA_STRING:
682 ok = HandleString(cx, jp, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer));
683 break;
685 case JSON_DATA_KEYSTRING:
686 js_AppendUCString(jp->objectKey, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer));
687 ok = STRING_BUFFER_OK(jp->objectKey);
688 break;
690 case JSON_DATA_NUMBER:
691 ok = HandleNumber(cx, jp, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer));
692 break;
694 case JSON_DATA_KEYWORD:
695 ok = HandleKeyword(cx, jp, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer));
696 break;
698 default:
699 JS_NOT_REACHED("Should have a JSON data type");
702 js_FinishStringBuffer(jp->buffer);
703 js_InitStringBuffer(jp->buffer);
705 return ok;
708 JSBool
709 js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len)
711 uint32 i;
713 if (*jp->statep == JSON_PARSE_STATE_INIT) {
714 PushState(jp, JSON_PARSE_STATE_OBJECT_VALUE);
717 for (i = 0; i < len; i++) {
718 jschar c = data[i];
719 switch (*jp->statep) {
720 case JSON_PARSE_STATE_VALUE :
721 if (c == ']') {
722 // empty array
723 if (!PopState(jp))
724 return JS_FALSE;
725 if (*jp->statep != JSON_PARSE_STATE_ARRAY)
726 return JS_FALSE; // unexpected char
727 if (!CloseArray(cx, jp) || !PopState(jp))
728 return JS_FALSE;
729 break;
732 if (c == '}') {
733 // we should only find these in OBJECT_KEY state
734 return JS_FALSE; // unexpected failure
737 if (c == '"') {
738 *jp->statep = JSON_PARSE_STATE_STRING;
739 break;
742 if (IsNumChar(c)) {
743 *jp->statep = JSON_PARSE_STATE_NUMBER;
744 js_AppendChar(jp->buffer, c);
745 break;
748 if (JS7_ISLET(c)) {
749 *jp->statep = JSON_PARSE_STATE_KEYWORD;
750 js_AppendChar(jp->buffer, c);
751 break;
754 // fall through in case the value is an object or array
755 case JSON_PARSE_STATE_OBJECT_VALUE :
756 if (c == '{') {
757 *jp->statep = JSON_PARSE_STATE_OBJECT;
758 if (!OpenObject(cx, jp) || !PushState(jp, JSON_PARSE_STATE_OBJECT_PAIR))
759 return JS_FALSE;
760 } else if (c == '[') {
761 *jp->statep = JSON_PARSE_STATE_ARRAY;
762 if (!OpenArray(cx, jp) || !PushState(jp, JSON_PARSE_STATE_VALUE))
763 return JS_FALSE;
764 } else if (!JS_ISXMLSPACE(c)) {
765 return JS_FALSE; // unexpected
767 break;
769 case JSON_PARSE_STATE_OBJECT :
770 if (c == '}') {
771 if (!CloseObject(cx, jp) || !PopState(jp))
772 return JS_FALSE;
773 } else if (c == ',') {
774 if (!PushState(jp, JSON_PARSE_STATE_OBJECT_PAIR))
775 return JS_FALSE;
776 } else if (c == ']' || !JS_ISXMLSPACE(c)) {
777 return JS_FALSE; // unexpected
779 break;
781 case JSON_PARSE_STATE_ARRAY :
782 if (c == ']') {
783 if (!CloseArray(cx, jp) || !PopState(jp))
784 return JS_FALSE;
785 } else if (c == ',') {
786 if (!PushState(jp, JSON_PARSE_STATE_VALUE))
787 return JS_FALSE;
788 } else if (!JS_ISXMLSPACE(c)) {
789 return JS_FALSE; // unexpected
791 break;
793 case JSON_PARSE_STATE_OBJECT_PAIR :
794 if (c == '"') {
795 // we want to be waiting for a : when the string has been read
796 *jp->statep = JSON_PARSE_STATE_OBJECT_IN_PAIR;
797 if (!PushState(jp, JSON_PARSE_STATE_STRING))
798 return JS_FALSE;
799 } else if (c == '}') {
800 // pop off the object pair state and the object state
801 if (!CloseObject(cx, jp) || !PopState(jp) || !PopState(jp))
802 return JS_FALSE;
803 } else if (c == ']' || !JS_ISXMLSPACE(c)) {
804 return JS_FALSE; // unexpected
806 break;
808 case JSON_PARSE_STATE_OBJECT_IN_PAIR:
809 if (c == ':') {
810 *jp->statep = JSON_PARSE_STATE_VALUE;
811 } else if (!JS_ISXMLSPACE(c)) {
812 return JS_FALSE; // unexpected
814 break;
816 case JSON_PARSE_STATE_STRING:
817 if (c == '"') {
818 if (!PopState(jp))
819 return JS_FALSE;
820 JSONDataType jdt;
821 if (*jp->statep == JSON_PARSE_STATE_OBJECT_IN_PAIR) {
822 jdt = JSON_DATA_KEYSTRING;
823 } else {
824 jdt = JSON_DATA_STRING;
826 if (!HandleData(cx, jp, jdt))
827 return JS_FALSE;
828 } else if (c == '\\') {
829 *jp->statep = JSON_PARSE_STATE_STRING_ESCAPE;
830 } else {
831 js_AppendChar(jp->buffer, c);
833 break;
835 case JSON_PARSE_STATE_STRING_ESCAPE:
836 switch (c) {
837 case '"':
838 case '\\':
839 case '/':
840 break;
841 case 'b' : c = '\b'; break;
842 case 'f' : c = '\f'; break;
843 case 'n' : c = '\n'; break;
844 case 'r' : c = '\r'; break;
845 case 't' : c = '\t'; break;
846 default :
847 if (c == 'u') {
848 jp->numHex = 0;
849 jp->hexChar = 0;
850 *jp->statep = JSON_PARSE_STATE_STRING_HEX;
851 continue;
852 } else {
853 return JS_FALSE; // unexpected
857 js_AppendChar(jp->buffer, c);
858 *jp->statep = JSON_PARSE_STATE_STRING;
859 break;
861 case JSON_PARSE_STATE_STRING_HEX:
862 if (('0' <= c) && (c <= '9'))
863 jp->hexChar = (jp->hexChar << 4) | (c - '0');
864 else if (('a' <= c) && (c <= 'f'))
865 jp->hexChar = (jp->hexChar << 4) | (c - 'a' + 0x0a);
866 else if (('A' <= c) && (c <= 'F'))
867 jp->hexChar = (jp->hexChar << 4) | (c - 'A' + 0x0a);
868 else
869 return JS_FALSE; // unexpected
871 if (++(jp->numHex) == 4) {
872 js_AppendChar(jp->buffer, jp->hexChar);
873 jp->hexChar = 0;
874 jp->numHex = 0;
875 *jp->statep = JSON_PARSE_STATE_STRING;
877 break;
879 case JSON_PARSE_STATE_KEYWORD:
880 if (JS7_ISLET(c)) {
881 js_AppendChar(jp->buffer, c);
882 } else {
883 // this character isn't part of the keyword, process it again
884 i--;
885 if (!PopState(jp))
886 return JS_FALSE;
888 if (!HandleData(cx, jp, JSON_DATA_KEYWORD))
889 return JS_FALSE;
891 break;
893 case JSON_PARSE_STATE_NUMBER:
894 if (IsNumChar(c)) {
895 js_AppendChar(jp->buffer, c);
896 } else {
897 // this character isn't part of the number, process it again
898 i--;
899 if (!PopState(jp))
900 return JS_FALSE;
901 if (!HandleData(cx, jp, JSON_DATA_NUMBER))
902 return JS_FALSE;
904 break;
906 case JSON_PARSE_STATE_FINISHED:
907 if (!JS_ISXMLSPACE(c))
908 return JS_FALSE; // extra input
910 break;
912 default:
913 JS_NOT_REACHED("Invalid JSON parser state");
917 return JS_TRUE;
920 #if JS_HAS_TOSOURCE
921 static JSBool
922 json_toSource(JSContext *cx, uintN argc, jsval *vp)
924 *vp = ATOM_KEY(CLASS_ATOM(cx, JSON));
925 return JS_TRUE;
927 #endif
929 static JSFunctionSpec json_static_methods[] = {
930 #if JS_HAS_TOSOURCE
931 JS_FN(js_toSource_str, json_toSource, 0, 0),
932 #endif
933 JS_FN("parse", js_json_parse, 0, 0),
934 JS_FN("stringify", js_json_stringify, 0, 0),
935 JS_FS_END
938 JSObject *
939 js_InitJSONClass(JSContext *cx, JSObject *obj)
941 JSObject *JSON;
943 JSON = JS_NewObject(cx, &js_JSONClass, NULL, obj);
944 if (!JSON)
945 return NULL;
946 if (!JS_DefineProperty(cx, obj, js_JSON_str, OBJECT_TO_JSVAL(JSON),
947 JS_PropertyStub, JS_PropertyStub, 0))
948 return NULL;
950 if (!JS_DefineFunctions(cx, JSON, json_static_methods))
951 return NULL;
953 return JSON;