Bug 449522 - Context menu for HTML5 <video> elements. r=gavin, ui-r=boriss
[wine-gecko.git] / js / src / json.cpp
blob954acf34a54b376af18fef0ba50a73b0a4c152b1
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 struct StringifyClosure
93 StringifyClosure(JSContext *aCx, jsval *str) : cx(aCx), s(str)
97 JSContext *cx;
98 jsval *s;
101 static JSBool
102 WriteCallback(const jschar *buf, uint32 len, void *data)
104 StringifyClosure *sc = static_cast<StringifyClosure*>(data);
105 JSString *s1 = JSVAL_TO_STRING(*sc->s);
106 JSString *s2 = js_NewStringCopyN(sc->cx, buf, len);
107 if (!s2)
108 return JS_FALSE;
110 s1 = js_ConcatStrings(sc->cx, s1, s2);
111 if (!s1)
112 return JS_FALSE;
114 *sc->s = STRING_TO_JSVAL(s1);
115 return JS_TRUE;
118 JSBool
119 js_json_stringify(JSContext *cx, uintN argc, jsval *vp)
121 JSObject *obj;
122 jsval *argv = vp + 2;
124 // Must throw an Error if there isn't a first arg
125 if (!JS_ConvertArguments(cx, argc, argv, "o", &obj))
126 return JS_FALSE;
128 // Only use objects and arrays as the root for now
129 jsval v = OBJECT_TO_JSVAL(obj);
130 JSBool ok = js_TryJSON(cx, &v);
131 JSType type;
132 if (!(ok && !JSVAL_IS_PRIMITIVE(v) &&
133 (type = JS_TypeOfValue(cx, v)) != JSTYPE_FUNCTION &&
134 type != JSTYPE_XML)) {
135 JS_ReportError(cx, "Invalid argument.");
136 return JS_FALSE;
139 JSString *s = JS_NewStringCopyN(cx, "", 0);
140 if (!s)
141 ok = JS_FALSE;
143 if (ok) {
144 jsval sv = STRING_TO_JSVAL(s);
145 StringifyClosure sc(cx, &sv);
146 JSAutoTempValueRooter tvr(cx, 1, sc.s);
147 ok = js_Stringify(cx, &v, NULL, &WriteCallback, &sc, 0);
148 *vp = *sc.s;
151 return ok;
154 JSBool
155 js_TryJSON(JSContext *cx, jsval *vp)
157 // Checks whether the return value implements toJSON()
158 JSBool ok = JS_TRUE;
160 if (!JSVAL_IS_PRIMITIVE(*vp)) {
161 JSObject *obj = JSVAL_TO_OBJECT(*vp);
162 ok = js_TryMethod(cx, obj, cx->runtime->atomState.toJSONAtom, 0, NULL, vp);
165 return ok;
169 static const jschar quote = jschar('"');
170 static const jschar backslash = jschar('\\');
171 static const jschar unicodeEscape[] = {'\\', 'u', '0', '0'};
173 static JSBool
174 write_string(JSContext *cx, JSONWriteCallback callback, void *data, const jschar *buf, uint32 len)
176 if (!callback(&quote, 1, data))
177 return JS_FALSE;
179 uint32 mark = 0;
180 uint32 i;
181 for (i = 0; i < len; ++i) {
182 if (buf[i] == quote || buf[i] == backslash) {
183 if (!callback(&buf[mark], i - mark, data) || !callback(&backslash, 1, data) ||
184 !callback(&buf[i], 1, data)) {
185 return JS_FALSE;
187 mark = i + 1;
188 } else if (buf[i] <= 31 || buf[i] == 127) {
189 if (!callback(&buf[mark], i - mark, data) || !callback(unicodeEscape, 4, data))
190 return JS_FALSE;
191 char ubuf[10];
192 unsigned int len = JS_snprintf(ubuf, sizeof(ubuf), "%.2x", buf[i]);
193 JS_ASSERT(len == 2);
194 // TODO: don't allocate a JSString just to inflate (js_InflateStringToBuffer on static?)
195 JSString *us = JS_NewStringCopyN(cx, ubuf, len);
196 if (!callback(JS_GetStringChars(us), len, data))
197 return JS_FALSE;
198 mark = i + 1;
202 if (mark < len && !callback(&buf[mark], len - mark, data))
203 return JS_FALSE;
205 if (!callback(&quote, 1, data))
206 return JS_FALSE;
208 return JS_TRUE;
211 JSBool
212 js_Stringify(JSContext *cx, jsval *vp, JSObject *replacer,
213 JSONWriteCallback callback, void *data, uint32 depth)
215 if (depth > JSON_MAX_DEPTH)
216 return JS_FALSE; /* encoding error */
218 JSBool ok = JS_TRUE;
219 JSObject *obj = JSVAL_TO_OBJECT(*vp);
220 JSBool isArray = JS_IsArrayObject(cx, obj);
221 jschar output = jschar(isArray ? '[' : '{');
222 if (!callback(&output, 1, data))
223 return JS_FALSE;
225 JSObject *iterObj = NULL;
226 jsint i = 0;
227 jsuint length = 0;
229 if (isArray) {
230 if (!js_GetLengthProperty(cx, obj, &length))
231 return JS_FALSE;
232 } else {
233 if (!js_ValueToIterator(cx, JSITER_ENUMERATE, vp))
234 return JS_FALSE;
235 iterObj = JSVAL_TO_OBJECT(*vp);
238 jsval outputValue = JSVAL_VOID;
239 JSAutoTempValueRooter tvr(cx, 1, &outputValue);
241 jsval key;
242 JSBool memberWritten = JS_FALSE;
243 do {
244 outputValue = JSVAL_VOID;
246 if (isArray) {
247 if ((jsuint)i >= length)
248 break;
249 ok = JS_GetElement(cx, obj, i++, &outputValue);
250 } else {
251 ok = js_CallIteratorNext(cx, iterObj, &key);
252 if (!ok)
253 break;
254 if (key == JSVAL_HOLE)
255 break;
257 JSString *ks;
258 if (JSVAL_IS_STRING(key)) {
259 ks = JSVAL_TO_STRING(key);
260 } else {
261 ks = js_ValueToString(cx, key);
262 if (!ks) {
263 ok = JS_FALSE;
264 break;
268 ok = JS_GetUCProperty(cx, obj, JS_GetStringChars(ks),
269 JS_GetStringLength(ks), &outputValue);
272 if (!ok)
273 break;
275 // if this is an array, holes are transmitted as null
276 if (isArray && outputValue == JSVAL_VOID) {
277 outputValue = JSVAL_NULL;
278 } else if (JSVAL_IS_OBJECT(outputValue)) {
279 ok = js_TryJSON(cx, &outputValue);
280 if (!ok)
281 break;
284 // elide undefined values
285 if (outputValue == JSVAL_VOID)
286 continue;
288 // output a comma unless this is the first member to write
289 if (memberWritten) {
290 output = jschar(',');
291 ok = callback(&output, 1, data);
292 if (!ok)
293 break;
295 memberWritten = JS_TRUE;
297 JSType type = JS_TypeOfValue(cx, outputValue);
299 // Can't encode these types, so drop them
300 if (type == JSTYPE_FUNCTION || type == JSTYPE_XML)
301 break;
303 // Be careful below, this string is weakly rooted.
304 JSString *s;
306 // If this isn't an array, we need to output a key
307 if (!isArray) {
308 s = js_ValueToString(cx, key);
309 if (!s) {
310 ok = JS_FALSE;
311 break;
314 ok = write_string(cx, callback, data, JS_GetStringChars(s), JS_GetStringLength(s));
315 if (!ok)
316 break;
318 output = jschar(':');
319 ok = callback(&output, 1, data);
320 if (!ok)
321 break;
324 if (!JSVAL_IS_PRIMITIVE(outputValue)) {
325 // recurse
326 ok = js_Stringify(cx, &outputValue, replacer, callback, data, depth + 1);
327 } else {
328 JSString *outputString;
329 s = js_ValueToString(cx, outputValue);
330 if (!s) {
331 ok = JS_FALSE;
332 break;
335 if (type == JSTYPE_STRING) {
336 ok = write_string(cx, callback, data, JS_GetStringChars(s), JS_GetStringLength(s));
337 if (!ok)
338 break;
340 continue;
343 if (type == JSTYPE_NUMBER) {
344 if (JSVAL_IS_DOUBLE(outputValue)) {
345 jsdouble d = *JSVAL_TO_DOUBLE(outputValue);
346 if (!JSDOUBLE_IS_FINITE(d))
347 outputString = JS_NewStringCopyN(cx, "null", 4);
348 else
349 outputString = s;
350 } else {
351 outputString = s;
353 } else if (type == JSTYPE_BOOLEAN) {
354 outputString = s;
355 } else if (JSVAL_IS_NULL(outputValue)) {
356 outputString = JS_NewStringCopyN(cx, "null", 4);
357 } else {
358 ok = JS_FALSE; // encoding error
359 break;
362 ok = callback(JS_GetStringChars(outputString), JS_GetStringLength(outputString), data);
364 } while (ok);
366 if (iterObj) {
367 // Always close the iterator, but make sure not to stomp on OK
368 ok &= js_CloseIterator(cx, *vp);
369 // encoding error or propagate? FIXME: Bug 408838.
372 if (!ok) {
373 JS_ReportError(cx, "Error during JSON encoding.");
374 return JS_FALSE;
377 output = jschar(isArray ? ']' : '}');
378 ok = callback(&output, 1, data);
380 return ok;
383 // helper to determine whether a character could be part of a number
384 static JSBool IsNumChar(jschar c)
386 return ((c <= '9' && c >= '0') || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E');
389 JSONParser *
390 js_BeginJSONParse(JSContext *cx, jsval *rootVal)
392 if (!cx)
393 return NULL;
395 JSObject *arr = js_NewArrayObject(cx, 0, NULL);
396 if (!arr)
397 return NULL;
399 JSONParser *jp = (JSONParser*) JS_malloc(cx, sizeof(JSONParser));
400 if (!jp)
401 return NULL;
402 jp->buffer = NULL;
404 jp->objectStack = arr;
405 if (!js_AddRoot(cx, &jp->objectStack, "JSON parse stack"))
406 goto bad;
408 jp->hexChar = 0;
409 jp->numHex = 0;
410 jp->statep = jp->stateStack;
411 *jp->statep = JSON_PARSE_STATE_INIT;
412 jp->rootVal = rootVal;
413 jp->objectKey = NULL;
414 jp->buffer = (JSStringBuffer*) JS_malloc(cx, sizeof(JSStringBuffer));
415 if (!jp->buffer)
416 goto bad;
417 js_InitStringBuffer(jp->buffer);
419 return jp;
420 bad:
421 JS_free(cx, jp->buffer);
422 JS_free(cx, jp);
423 return NULL;
426 JSBool
427 js_FinishJSONParse(JSContext *cx, JSONParser *jp)
429 if (!jp)
430 return JS_TRUE;
432 if (jp->buffer)
433 js_FinishStringBuffer(jp->buffer);
435 JS_free(cx, jp->buffer);
436 if (!js_RemoveRoot(cx->runtime, &jp->objectStack))
437 return JS_FALSE;
438 JSBool ok = *jp->statep == JSON_PARSE_STATE_FINISHED;
439 JS_free(cx, jp);
441 return ok;
444 static JSBool
445 PushState(JSONParser *jp, JSONParserState state)
447 if (*jp->statep == JSON_PARSE_STATE_FINISHED)
448 return JS_FALSE; // extra input
450 jp->statep++;
451 if ((uint32)(jp->statep - jp->stateStack) >= JS_ARRAY_LENGTH(jp->stateStack))
452 return JS_FALSE; // too deep
454 *jp->statep = state;
456 return JS_TRUE;
459 static JSBool
460 PopState(JSONParser *jp)
462 jp->statep--;
463 if (jp->statep < jp->stateStack) {
464 jp->statep = jp->stateStack;
465 return JS_FALSE;
468 if (*jp->statep == JSON_PARSE_STATE_INIT)
469 *jp->statep = JSON_PARSE_STATE_FINISHED;
471 return JS_TRUE;
474 static JSBool
475 PushValue(JSContext *cx, JSONParser *jp, JSObject *parent, jsval value)
477 JSAutoTempValueRooter tvr(cx, 1, &value);
479 JSBool ok;
480 if (OBJ_IS_ARRAY(cx, parent)) {
481 jsuint len;
482 ok = js_GetLengthProperty(cx, parent, &len);
483 if (ok)
484 ok = JS_SetElement(cx, parent, len, &value);
485 } else {
486 ok = JS_DefineUCProperty(cx, parent, JS_GetStringChars(jp->objectKey),
487 JS_GetStringLength(jp->objectKey), value,
488 NULL, NULL, JSPROP_ENUMERATE);
491 return ok;
494 static JSBool
495 PushObject(JSContext *cx, JSONParser *jp, JSObject *obj)
497 jsuint len;
498 if (!js_GetLengthProperty(cx, jp->objectStack, &len))
499 return JS_FALSE;
500 if (len >= JSON_MAX_DEPTH)
501 return JS_FALSE; // decoding error
503 jsval v = OBJECT_TO_JSVAL(obj);
505 // Check if this is the root object
506 if (len == 0) {
507 *jp->rootVal = v;
508 if (!JS_SetElement(cx, jp->objectStack, 0, jp->rootVal))
509 return JS_FALSE;
510 return JS_TRUE;
513 jsval p;
514 if (!JS_GetElement(cx, jp->objectStack, len - 1, &p))
515 return JS_FALSE;
516 JS_ASSERT(JSVAL_IS_OBJECT(p));
517 JSObject *parent = JSVAL_TO_OBJECT(p);
518 if (!PushValue(cx, jp, parent, OBJECT_TO_JSVAL(obj)))
519 return JS_FALSE;
521 if (!JS_SetElement(cx, jp->objectStack, len, &v))
522 return JS_FALSE;
524 return JS_TRUE;
527 static JSObject *
528 GetTopOfObjectStack(JSContext *cx, JSONParser *jp)
530 jsuint length;
531 if (!js_GetLengthProperty(cx, jp->objectStack, &length))
532 return NULL;
534 jsval o;
535 if (!JS_GetElement(cx, jp->objectStack, length - 1, &o))
536 return NULL;
538 JS_ASSERT(!JSVAL_IS_PRIMITIVE(o));
539 return JSVAL_TO_OBJECT(o);
542 static JSBool
543 OpenObject(JSContext *cx, JSONParser *jp)
545 JSObject *obj = js_NewObject(cx, &js_ObjectClass, NULL, NULL, 0);
546 if (!obj)
547 return JS_FALSE;
549 return PushObject(cx, jp, obj);
552 static JSBool
553 OpenArray(JSContext *cx, JSONParser *jp)
555 // Add an array to an existing array or object
556 JSObject *arr = js_NewArrayObject(cx, 0, NULL);
557 if (!arr)
558 return JS_FALSE;
560 return PushObject(cx, jp, arr);
563 static JSBool
564 CloseObject(JSContext *cx, JSONParser *jp)
566 jsuint len;
567 if (!js_GetLengthProperty(cx, jp->objectStack, &len))
568 return JS_FALSE;
569 if (!js_SetLengthProperty(cx, jp->objectStack, len - 1))
570 return JS_FALSE;
572 return JS_TRUE;
575 static JSBool
576 CloseArray(JSContext *cx, JSONParser *jp)
578 return CloseObject(cx, jp);
581 static JSBool
582 HandleNumber(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len)
584 const jschar *ep;
585 double val;
586 if (!js_strtod(cx, buf, buf + len, &ep, &val) || ep != buf + len)
587 return JS_FALSE;
589 JSBool ok;
590 jsval numVal;
591 JSObject *obj = GetTopOfObjectStack(cx, jp);
592 if (obj && JS_NewNumberValue(cx, val, &numVal))
593 ok = PushValue(cx, jp, obj, numVal);
594 else
595 ok = JS_FALSE; // decode error
597 return ok;
600 static JSBool
601 HandleString(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len)
603 JSObject *obj = GetTopOfObjectStack(cx, jp);
604 JSString *str = js_NewStringCopyN(cx, buf, len);
605 if (!obj || !str)
606 return JS_FALSE;
608 return PushValue(cx, jp, obj, STRING_TO_JSVAL(str));
611 static JSBool
612 HandleKeyword(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len)
614 jsval keyword;
615 JSTokenType tt = js_CheckKeyword(buf, len);
616 if (tt != TOK_PRIMARY)
617 return JS_FALSE;
619 if (buf[0] == 'n')
620 keyword = JSVAL_NULL;
621 else if (buf[0] == 't')
622 keyword = JSVAL_TRUE;
623 else if (buf[0] == 'f')
624 keyword = JSVAL_FALSE;
625 else
626 return JS_FALSE;
628 JSObject *obj = GetTopOfObjectStack(cx, jp);
629 if (!obj)
630 return JS_FALSE;
632 return PushValue(cx, jp, obj, keyword);
635 static JSBool
636 HandleData(JSContext *cx, JSONParser *jp, JSONDataType type, const jschar *buf, uint32 len)
638 JSBool ok = JS_FALSE;
640 switch (type) {
641 case JSON_DATA_STRING:
642 ok = HandleString(cx, jp, buf, len);
643 break;
645 case JSON_DATA_KEYSTRING:
646 jp->objectKey = js_NewStringCopyN(cx, buf, len);
647 ok = JS_TRUE;
648 break;
650 case JSON_DATA_NUMBER:
651 ok = HandleNumber(cx, jp, buf, len);
652 break;
654 case JSON_DATA_KEYWORD:
655 ok = HandleKeyword(cx, jp, buf, len);
656 break;
658 default:
659 JS_NOT_REACHED("Should have a JSON data type");
662 js_FinishStringBuffer(jp->buffer);
663 js_InitStringBuffer(jp->buffer);
665 return ok;
668 JSBool
669 js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len)
671 uint32 i;
673 if (*jp->statep == JSON_PARSE_STATE_INIT) {
674 PushState(jp, JSON_PARSE_STATE_OBJECT_VALUE);
677 for (i = 0; i < len; i++) {
678 jschar c = data[i];
679 switch (*jp->statep) {
680 case JSON_PARSE_STATE_VALUE :
681 if (c == ']') {
682 // empty array
683 if (!PopState(jp))
684 return JS_FALSE;
685 if (*jp->statep != JSON_PARSE_STATE_ARRAY)
686 return JS_FALSE; // unexpected char
687 if (!CloseArray(cx, jp) || !PopState(jp))
688 return JS_FALSE;
689 break;
692 if (c == '}') {
693 // we should only find these in OBJECT_KEY state
694 return JS_FALSE; // unexpected failure
697 if (c == '"') {
698 *jp->statep = JSON_PARSE_STATE_STRING;
699 break;
702 if (IsNumChar(c)) {
703 *jp->statep = JSON_PARSE_STATE_NUMBER;
704 js_AppendChar(jp->buffer, c);
705 break;
708 if (JS7_ISLET(c)) {
709 *jp->statep = JSON_PARSE_STATE_KEYWORD;
710 js_AppendChar(jp->buffer, c);
711 break;
714 // fall through in case the value is an object or array
715 case JSON_PARSE_STATE_OBJECT_VALUE :
716 if (c == '{') {
717 *jp->statep = JSON_PARSE_STATE_OBJECT;
718 if (!OpenObject(cx, jp) || !PushState(jp, JSON_PARSE_STATE_OBJECT_PAIR))
719 return JS_FALSE;
720 } else if (c == '[') {
721 *jp->statep = JSON_PARSE_STATE_ARRAY;
722 if (!OpenArray(cx, jp) || !PushState(jp, JSON_PARSE_STATE_VALUE))
723 return JS_FALSE;
724 } else if (!JS_ISXMLSPACE(c)) {
725 return JS_FALSE; // unexpected
727 break;
729 case JSON_PARSE_STATE_OBJECT :
730 if (c == '}') {
731 if (!CloseObject(cx, jp) || !PopState(jp))
732 return JS_FALSE;
733 } else if (c == ',') {
734 if (!PushState(jp, JSON_PARSE_STATE_OBJECT_PAIR))
735 return JS_FALSE;
736 } else if (c == ']' || !JS_ISXMLSPACE(c)) {
737 return JS_FALSE; // unexpected
739 break;
741 case JSON_PARSE_STATE_ARRAY :
742 if (c == ']') {
743 if (!CloseArray(cx, jp) || !PopState(jp))
744 return JS_FALSE;
745 } else if (c == ',') {
746 if (!PushState(jp, JSON_PARSE_STATE_VALUE))
747 return JS_FALSE;
748 } else if (!JS_ISXMLSPACE(c)) {
749 return JS_FALSE; // unexpected
751 break;
753 case JSON_PARSE_STATE_OBJECT_PAIR :
754 if (c == '"') {
755 // we want to be waiting for a : when the string has been read
756 *jp->statep = JSON_PARSE_STATE_OBJECT_IN_PAIR;
757 if (!PushState(jp, JSON_PARSE_STATE_STRING))
758 return JS_FALSE;
759 } else if (c == '}') {
760 // pop off the object pair state and the object state
761 if (!CloseObject(cx, jp) || !PopState(jp) || !PopState(jp))
762 return JS_FALSE;
763 } else if (c == ']' || !JS_ISXMLSPACE(c)) {
764 return JS_FALSE; // unexpected
766 break;
768 case JSON_PARSE_STATE_OBJECT_IN_PAIR:
769 if (c == ':') {
770 *jp->statep = JSON_PARSE_STATE_VALUE;
771 } else if (!JS_ISXMLSPACE(c)) {
772 return JS_FALSE; // unexpected
774 break;
776 case JSON_PARSE_STATE_STRING:
777 if (c == '"') {
778 if (!PopState(jp))
779 return JS_FALSE;
780 JSONDataType jdt;
781 if (*jp->statep == JSON_PARSE_STATE_OBJECT_IN_PAIR) {
782 jdt = JSON_DATA_KEYSTRING;
783 } else {
784 jdt = JSON_DATA_STRING;
786 if (!HandleData(cx, jp, jdt, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer)))
787 return JS_FALSE;
788 } else if (c == '\\') {
789 *jp->statep = JSON_PARSE_STATE_STRING_ESCAPE;
790 } else {
791 js_AppendChar(jp->buffer, c);
793 break;
795 case JSON_PARSE_STATE_STRING_ESCAPE:
796 switch (c) {
797 case '"':
798 case '\\':
799 case '/':
800 break;
801 case 'b' : c = '\b'; break;
802 case 'f' : c = '\f'; break;
803 case 'n' : c = '\n'; break;
804 case 'r' : c = '\r'; break;
805 case 't' : c = '\t'; break;
806 default :
807 if (c == 'u') {
808 jp->numHex = 0;
809 jp->hexChar = 0;
810 *jp->statep = JSON_PARSE_STATE_STRING_HEX;
811 continue;
812 } else {
813 return JS_FALSE; // unexpected
817 js_AppendChar(jp->buffer, c);
818 *jp->statep = JSON_PARSE_STATE_STRING;
819 break;
821 case JSON_PARSE_STATE_STRING_HEX:
822 if (('0' <= c) && (c <= '9'))
823 jp->hexChar = (jp->hexChar << 4) | (c - '0');
824 else if (('a' <= c) && (c <= 'f'))
825 jp->hexChar = (jp->hexChar << 4) | (c - 'a' + 0x0a);
826 else if (('A' <= c) && (c <= 'F'))
827 jp->hexChar = (jp->hexChar << 4) | (c - 'A' + 0x0a);
828 else
829 return JS_FALSE; // unexpected
831 if (++(jp->numHex) == 4) {
832 js_AppendChar(jp->buffer, jp->hexChar);
833 jp->hexChar = 0;
834 jp->numHex = 0;
835 *jp->statep = JSON_PARSE_STATE_STRING;
837 break;
839 case JSON_PARSE_STATE_KEYWORD:
840 if (JS7_ISLET(c)) {
841 js_AppendChar(jp->buffer, c);
842 } else {
843 // this character isn't part of the keyword, process it again
844 i--;
845 if (!PopState(jp))
846 return JS_FALSE;
848 if (!HandleData(cx, jp, JSON_DATA_KEYWORD, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer)))
849 return JS_FALSE;
851 break;
853 case JSON_PARSE_STATE_NUMBER:
854 if (IsNumChar(c)) {
855 js_AppendChar(jp->buffer, c);
856 } else {
857 // this character isn't part of the number, process it again
858 i--;
859 if (!PopState(jp))
860 return JS_FALSE;
861 if (!HandleData(cx, jp, JSON_DATA_NUMBER, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer)))
862 return JS_FALSE;
864 break;
866 case JSON_PARSE_STATE_FINISHED:
867 if (!JS_ISXMLSPACE(c))
868 return JS_FALSE; // extra input
870 break;
872 default:
873 JS_NOT_REACHED("Invalid JSON parser state");
877 return JS_TRUE;
880 #if JS_HAS_TOSOURCE
881 static JSBool
882 json_toSource(JSContext *cx, uintN argc, jsval *vp)
884 *vp = ATOM_KEY(CLASS_ATOM(cx, JSON));
885 return JS_TRUE;
887 #endif
889 static JSFunctionSpec json_static_methods[] = {
890 #if JS_HAS_TOSOURCE
891 JS_FN(js_toSource_str, json_toSource, 0, 0),
892 #endif
893 JS_FN("parse", js_json_parse, 0, 0),
894 JS_FN("stringify", js_json_stringify, 0, 0),
895 JS_FS_END
898 JSObject *
899 js_InitJSONClass(JSContext *cx, JSObject *obj)
901 JSObject *JSON;
903 JSON = JS_NewObject(cx, &js_JSONClass, NULL, obj);
904 if (!JSON)
905 return NULL;
906 if (!JS_DefineProperty(cx, obj, js_JSON_str, OBJECT_TO_JSVAL(JSON),
907 JS_PropertyStub, JS_PropertyStub, 0))
908 return NULL;
910 if (!JS_DefineFunctions(cx, JSON, json_static_methods))
911 return NULL;
913 return JSON;