1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 * vim: set ts=8 sw=4 et tw=99:
4 * ***** BEGIN LICENSE BLOCK *****
5 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
7 * The contents of this file are subject to the Mozilla Public License Version
8 * 1.1 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://www.mozilla.org/MPL/
12 * Software distributed under the License is distributed on an "AS IS" basis,
13 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14 * for the specific language governing rights and limitations under the
17 * The Original Code is SpiderMonkey JSON.
19 * The Initial Developer of the Original Code is
20 * Mozilla Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 1998-1999
22 * the Initial Developer. All Rights Reserved.
25 * Robert Sayre <sayrer@gmail.com>
26 * Dave Camp <dcamp@mozilla.com>
28 * Alternatively, the contents of this file may be used under the terms of
29 * either of the GNU General Public License Version 2 or later (the "GPL"),
30 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
31 * in which case the provisions of the GPL or the LGPL are applicable instead
32 * of those above. If you wish to allow use of your version of this file only
33 * under the terms of either the GPL or the LGPL, and not to allow others to
34 * use your version of this file under the terms of the MPL, indicate your
35 * decision by deleting the provisions above and replace them with the notice
36 * and other provisions required by the GPL or the LGPL. If you do not delete
37 * the provisions above, a recipient may use your version of this file under
38 * the terms of any one of the MPL, the GPL or the LGPL.
40 * ***** END LICENSE BLOCK ***** */
65 #include "jsatominlines.h"
66 #include "jsobjinlines.h"
69 using namespace js::gc
;
73 #pragma warning(disable:4351)
78 JSONParser(JSContext
*cx
)
79 : hexChar(), numHex(), statep(), stateStack(), rootVal(), objectStack(),
80 objectKey(cx
), buffer(cx
), suppressErrors(false)
83 /* Used while handling \uNNNN in strings */
87 JSONParserState
*statep
;
88 JSONParserState stateStack
[JSON_MAX_DEPTH
];
90 JSObject
*objectStack
;
91 js::Vector
<jschar
, 8> objectKey
;
92 js::Vector
<jschar
, 8> buffer
;
100 Class js_JSONClass
= {
102 JSCLASS_HAS_CACHED_PROTO(JSProto_JSON
),
103 PropertyStub
, /* addProperty */
104 PropertyStub
, /* delProperty */
105 PropertyStub
, /* getProperty */
106 StrictPropertyStub
, /* setProperty */
113 js_json_parse(JSContext
*cx
, uintN argc
, Value
*vp
)
116 Value
*argv
= vp
+ 2;
117 AutoValueRooter
reviver(cx
);
119 if (!JS_ConvertArguments(cx
, argc
, Jsvalify(argv
), "S / v", &s
, reviver
.addr()))
122 JSLinearString
*linearStr
= s
->ensureLinear(cx
);
126 JSONParser
*jp
= js_BeginJSONParse(cx
, vp
);
127 JSBool ok
= jp
!= NULL
;
129 const jschar
*chars
= linearStr
->chars();
130 size_t length
= linearStr
->length();
131 ok
= js_ConsumeJSONText(cx
, jp
, chars
, length
);
132 ok
&= !!js_FinishJSONParse(cx
, jp
, reviver
.value());
139 js_json_stringify(JSContext
*cx
, uintN argc
, Value
*vp
)
141 Value
*argv
= vp
+ 2;
142 AutoValueRooter
space(cx
);
143 AutoObjectRooter
replacer(cx
);
145 // Must throw an Error if there isn't a first arg
146 if (!JS_ConvertArguments(cx
, argc
, Jsvalify(argv
), "v / o v", vp
, replacer
.addr(), space
.addr()))
151 if (!js_Stringify(cx
, vp
, replacer
.object(), space
.value(), sb
))
154 // XXX This can never happen to nsJSON.cpp, but the JSON object
155 // needs to support returning undefined. So this is a little awkward
156 // for the API, because we want to support streaming writers.
158 JSString
*str
= sb
.finishString();
170 js_TryJSON(JSContext
*cx
, Value
*vp
)
172 // Checks whether the return value implements toJSON()
175 if (vp
->isObject()) {
176 JSObject
*obj
= &vp
->toObject();
177 ok
= js_TryMethod(cx
, obj
, cx
->runtime
->atomState
.toJSONAtom
, 0, NULL
, vp
);
184 static const char quote
= '\"';
185 static const char backslash
= '\\';
186 static const char unicodeEscape
[] = "\\u00";
189 write_string(JSContext
*cx
, StringBuffer
&sb
, const jschar
*buf
, uint32 len
)
191 if (!sb
.append(quote
))
196 for (i
= 0; i
< len
; ++i
) {
197 if (buf
[i
] == quote
|| buf
[i
] == backslash
) {
198 if (!sb
.append(&buf
[mark
], i
- mark
) || !sb
.append(backslash
) ||
199 !sb
.append(buf
[i
])) {
203 } else if (buf
[i
] <= 31 || buf
[i
] == 127) {
204 if (!sb
.append(&buf
[mark
], i
- mark
) ||
205 !sb
.append(unicodeEscape
)) {
209 size_t len
= JS_snprintf(ubuf
, sizeof(ubuf
), "%.2x", buf
[i
]);
212 size_t wbufSize
= JS_ARRAY_LENGTH(wbuf
);
213 if (!js_InflateStringToBuffer(cx
, ubuf
, len
, wbuf
, &wbufSize
) ||
214 !sb
.append(wbuf
, wbufSize
)) {
221 if (mark
< len
&& !sb
.append(&buf
[mark
], len
- mark
))
224 return sb
.append(quote
);
227 class StringifyContext
230 StringifyContext(JSContext
*cx
, StringBuffer
&sb
, JSObject
*replacer
)
231 : sb(sb
), gap(cx
), replacer(replacer
), depth(0), objectStack(cx
)
234 bool initializeGap(JSContext
*cx
, const Value
&space
) {
235 AutoValueRooter
gapValue(cx
, space
);
237 if (space
.isObject()) {
238 JSObject
&obj
= space
.toObject();
239 Class
*clasp
= obj
.getClass();
240 if (clasp
== &js_NumberClass
|| clasp
== &js_StringClass
)
241 *gapValue
.addr() = obj
.getPrimitiveThis();
244 if (gapValue
.value().isString()) {
245 if (!ValueToStringBuffer(cx
, gapValue
.value(), gap
))
247 if (gap
.length() > 10)
249 } else if (gapValue
.value().isNumber()) {
250 jsdouble d
= gapValue
.value().isInt32()
251 ? gapValue
.value().toInt32()
252 : js_DoubleToInteger(gapValue
.value().toDouble());
254 if (d
>= 1 && !gap
.appendN(' ', uint32(d
)))
261 bool initializeStack() {
262 return objectStack
.init(16);
266 ~StringifyContext() { JS_ASSERT(objectStack
.empty()); }
273 HashSet
<JSObject
*> objectStack
;
276 static JSBool
CallReplacerFunction(JSContext
*cx
, jsid id
, JSObject
*holder
,
277 StringifyContext
*scx
, Value
*vp
);
278 static JSBool
Str(JSContext
*cx
, jsid id
, JSObject
*holder
,
279 StringifyContext
*scx
, Value
*vp
, bool callReplacer
= true);
282 WriteIndent(JSContext
*cx
, StringifyContext
*scx
, uint32 limit
)
284 if (!scx
->gap
.empty()) {
285 if (!scx
->sb
.append('\n'))
287 for (uint32 i
= 0; i
< limit
; i
++) {
288 if (!scx
->sb
.append(scx
->gap
.begin(), scx
->gap
.end()))
299 CycleDetector(StringifyContext
*scx
, JSObject
*obj
)
300 : objectStack(scx
->objectStack
), obj(obj
) {
303 bool init(JSContext
*cx
) {
304 HashSet
<JSObject
*>::AddPtr ptr
= objectStack
.lookupForAdd(obj
);
306 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, NULL
, JSMSG_CYCLIC_VALUE
, js_object_str
);
309 return objectStack
.add(ptr
, obj
);
313 objectStack
.remove(obj
);
317 HashSet
<JSObject
*> &objectStack
;
322 JO(JSContext
*cx
, Value
*vp
, StringifyContext
*scx
)
324 JSObject
*obj
= &vp
->toObject();
326 CycleDetector
detect(scx
, obj
);
327 if (!detect
.init(cx
))
330 if (!scx
->sb
.append('{'))
333 Value vec
[3] = { NullValue(), NullValue(), NullValue() };
334 AutoArrayRooter
tvr(cx
, JS_ARRAY_LENGTH(vec
), vec
);
335 Value
& outputValue
= vec
[0];
336 Value
& whitelistElement
= vec
[1];
337 AutoIdRooter
idr(cx
);
338 jsid
& id
= *idr
.addr();
340 Value
*keySource
= vp
;
341 bool usingWhitelist
= false;
343 // if the replacer is an array, we use the keys from it
344 if (scx
->replacer
&& JS_IsArrayObject(cx
, scx
->replacer
)) {
345 usingWhitelist
= true;
346 vec
[2].setObject(*scx
->replacer
);
350 JSBool memberWritten
= JS_FALSE
;
351 AutoIdVector
props(cx
);
352 if (!GetPropertyNames(cx
, &keySource
->toObject(), JSITER_OWNONLY
, &props
))
355 for (size_t i
= 0, len
= props
.length(); i
< len
; i
++) {
356 outputValue
.setUndefined();
358 if (!usingWhitelist
) {
359 if (!js_ValueToStringId(cx
, IdToValue(props
[i
]), &id
))
362 // skip non-index properties
364 if (!js_IdIsIndex(props
[i
], &index
))
367 if (!scx
->replacer
->getProperty(cx
, props
[i
], &whitelistElement
))
370 if (!js_ValueToStringId(cx
, whitelistElement
, &id
))
374 // We should have a string id by this point. Either from
375 // JS_Enumerate's id array, or by converting an element
377 JS_ASSERT(JSID_IS_ATOM(id
));
379 if (!JS_GetPropertyById(cx
, obj
, id
, Jsvalify(&outputValue
)))
382 if (outputValue
.isObjectOrNull() && !js_TryJSON(cx
, &outputValue
))
385 // call this here, so we don't write out keys if the replacer function
386 // wants to elide the value.
387 if (!CallReplacerFunction(cx
, id
, obj
, scx
, &outputValue
))
390 JSType type
= JS_TypeOfValue(cx
, Jsvalify(outputValue
));
392 // elide undefined values and functions and XML
393 if (outputValue
.isUndefined() || type
== JSTYPE_FUNCTION
|| type
== JSTYPE_XML
)
396 // output a comma unless this is the first member to write
397 if (memberWritten
&& !scx
->sb
.append(','))
399 memberWritten
= JS_TRUE
;
401 if (!WriteIndent(cx
, scx
, scx
->depth
))
404 // Be careful below, this string is weakly rooted
405 JSString
*s
= js_ValueToString(cx
, IdToValue(id
));
409 size_t length
= s
->length();
410 const jschar
*chars
= s
->getChars(cx
);
414 if (!write_string(cx
, scx
->sb
, chars
, length
) ||
415 !scx
->sb
.append(':') ||
416 !(scx
->gap
.empty() || scx
->sb
.append(' ')) ||
417 !Str(cx
, id
, obj
, scx
, &outputValue
, true)) {
422 if (memberWritten
&& !WriteIndent(cx
, scx
, scx
->depth
- 1))
425 return scx
->sb
.append('}');
429 JA(JSContext
*cx
, Value
*vp
, StringifyContext
*scx
)
431 JSObject
*obj
= &vp
->toObject();
433 CycleDetector
detect(scx
, obj
);
434 if (!detect
.init(cx
))
437 if (!scx
->sb
.append('['))
441 if (!js_GetLengthProperty(cx
, obj
, &length
))
444 if (length
!= 0 && !WriteIndent(cx
, scx
, scx
->depth
))
447 AutoValueRooter
outputValue(cx
);
451 for (i
= 0; i
< length
; i
++) {
454 if (!obj
->getProperty(cx
, id
, outputValue
.addr()))
457 if (!Str(cx
, id
, obj
, scx
, outputValue
.addr()))
460 if (outputValue
.value().isUndefined()) {
461 if (!scx
->sb
.append("null"))
465 if (i
< length
- 1) {
466 if (!scx
->sb
.append(','))
468 if (!WriteIndent(cx
, scx
, scx
->depth
))
473 if (length
!= 0 && !WriteIndent(cx
, scx
, scx
->depth
- 1))
476 return scx
->sb
.append(']');
480 CallReplacerFunction(JSContext
*cx
, jsid id
, JSObject
*holder
, StringifyContext
*scx
, Value
*vp
)
482 if (scx
->replacer
&& scx
->replacer
->isCallable()) {
483 Value vec
[2] = { IdToValue(id
), *vp
};
484 if (!JS_CallFunctionValue(cx
, holder
, OBJECT_TO_JSVAL(scx
->replacer
),
485 2, Jsvalify(vec
), Jsvalify(vp
))) {
494 Str(JSContext
*cx
, jsid id
, JSObject
*holder
, StringifyContext
*scx
, Value
*vp
, bool callReplacer
)
496 JS_CHECK_RECURSION(cx
, return JS_FALSE
);
498 if (vp
->isObject() && !js_TryJSON(cx
, vp
))
501 if (callReplacer
&& !CallReplacerFunction(cx
, id
, holder
, scx
, vp
))
504 // catches string and number objects with no toJSON
505 if (vp
->isObject()) {
506 JSObject
*obj
= &vp
->toObject();
507 Class
*clasp
= obj
->getClass();
508 if (clasp
== &js_StringClass
|| clasp
== &js_NumberClass
)
509 *vp
= obj
->getPrimitiveThis();
512 if (vp
->isString()) {
513 JSString
*str
= vp
->toString();
514 size_t length
= str
->length();
515 const jschar
*chars
= str
->getChars(cx
);
518 return write_string(cx
, scx
->sb
, chars
, length
);
522 return scx
->sb
.append("null");
525 return vp
->toBoolean() ? scx
->sb
.append("true") : scx
->sb
.append("false");
527 if (vp
->isNumber()) {
528 if (vp
->isDouble()) {
529 jsdouble d
= vp
->toDouble();
530 if (!JSDOUBLE_IS_FINITE(d
))
531 return scx
->sb
.append("null");
535 if (!NumberValueToStringBuffer(cx
, *vp
, sb
))
538 return scx
->sb
.append(sb
.begin(), sb
.length());
541 if (vp
->isObject() && !IsFunctionObject(*vp
) && !IsXML(*vp
)) {
545 ok
= (JS_IsArrayObject(cx
, &vp
->toObject()) ? JA
: JO
)(cx
, vp
, scx
);
556 js_Stringify(JSContext
*cx
, Value
*vp
, JSObject
*replacer
, const Value
&space
,
559 StringifyContext
scx(cx
, sb
, replacer
);
560 if (!scx
.initializeGap(cx
, space
) || !scx
.initializeStack())
563 JSObject
*obj
= NewBuiltinClassInstance(cx
, &js_ObjectClass
);
567 AutoObjectRooter
tvr(cx
, obj
);
568 if (!obj
->defineProperty(cx
, ATOM_TO_JSID(cx
->runtime
->atomState
.emptyAtom
),
569 *vp
, NULL
, NULL
, JSPROP_ENUMERATE
)) {
573 return Str(cx
, ATOM_TO_JSID(cx
->runtime
->atomState
.emptyAtom
), obj
, &scx
, vp
);
576 // helper to determine whether a character could be part of a number
577 static JSBool
IsNumChar(jschar c
)
579 return ((c
<= '9' && c
>= '0') || c
== '.' || c
== '-' || c
== '+' || c
== 'e' || c
== 'E');
582 static JSBool
HandleDataString(JSContext
*cx
, JSONParser
*jp
);
583 static JSBool
HandleDataKeyString(JSContext
*cx
, JSONParser
*jp
);
584 static JSBool
HandleDataNumber(JSContext
*cx
, JSONParser
*jp
);
585 static JSBool
HandleDataKeyword(JSContext
*cx
, JSONParser
*jp
);
586 static JSBool
PopState(JSContext
*cx
, JSONParser
*jp
);
589 Walk(JSContext
*cx
, jsid id
, JSObject
*holder
, const Value
&reviver
, Value
*vp
)
591 JS_CHECK_RECURSION(cx
, return false);
593 if (!holder
->getProperty(cx
, id
, vp
))
598 if (vp
->isObject() && !(obj
= &vp
->toObject())->isCallable()) {
599 AutoValueRooter
propValue(cx
);
603 if (!js_GetLengthProperty(cx
, obj
, &length
))
606 for (jsuint i
= 0; i
< length
; i
++) {
608 if (!js_IndexToId(cx
, i
, &index
))
611 if (!Walk(cx
, index
, obj
, reviver
, propValue
.addr()))
614 if (!obj
->defineProperty(cx
, index
, propValue
.value(), NULL
, NULL
, JSPROP_ENUMERATE
))
618 AutoIdVector
props(cx
);
619 if (!GetPropertyNames(cx
, obj
, JSITER_OWNONLY
, &props
))
622 for (size_t i
= 0, len
= props
.length(); i
< len
; i
++) {
623 jsid idName
= props
[i
];
624 if (!Walk(cx
, idName
, obj
, reviver
, propValue
.addr()))
626 if (propValue
.value().isUndefined()) {
627 if (!js_DeleteProperty(cx
, obj
, idName
, propValue
.addr(), false))
630 if (!obj
->defineProperty(cx
, idName
, propValue
.value(), NULL
, NULL
,
639 // return reviver.call(holder, key, value);
640 const Value
&value
= *vp
;
641 JSString
*key
= js_ValueToString(cx
, IdToValue(id
));
645 Value vec
[2] = { StringValue(key
), value
};
647 if (!JS_CallFunctionValue(cx
, holder
, Jsvalify(reviver
),
648 2, Jsvalify(vec
), Jsvalify(&reviverResult
))) {
657 JSONParseError(JSONParser
*jp
, JSContext
*cx
)
659 if (!jp
->suppressErrors
)
660 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, NULL
, JSMSG_JSON_BAD_PARSE
);
665 Revive(JSContext
*cx
, const Value
&reviver
, Value
*vp
)
668 JSObject
*obj
= NewBuiltinClassInstance(cx
, &js_ObjectClass
);
672 AutoObjectRooter
tvr(cx
, obj
);
673 if (!obj
->defineProperty(cx
, ATOM_TO_JSID(cx
->runtime
->atomState
.emptyAtom
),
674 *vp
, NULL
, NULL
, JSPROP_ENUMERATE
)) {
678 return Walk(cx
, ATOM_TO_JSID(cx
->runtime
->atomState
.emptyAtom
), obj
, reviver
, vp
);
682 js_BeginJSONParse(JSContext
*cx
, Value
*rootVal
, bool suppressErrors
/*= false*/)
687 JSObject
*arr
= NewDenseEmptyArray(cx
);
691 JSONParser
*jp
= cx
->create
<JSONParser
>(cx
);
695 jp
->objectStack
= arr
;
696 if (!JS_AddNamedObjectRoot(cx
, &jp
->objectStack
, "JSON parse stack"))
699 jp
->statep
= jp
->stateStack
;
700 *jp
->statep
= JSON_PARSE_STATE_INIT
;
701 jp
->rootVal
= rootVal
;
702 jp
->suppressErrors
= suppressErrors
;
707 js_FinishJSONParse(cx
, jp
, NullValue());
712 js_FinishJSONParse(JSContext
*cx
, JSONParser
*jp
, const Value
&reviver
)
717 JSBool early_ok
= JS_TRUE
;
719 // Check for unprocessed primitives at the root. This doesn't happen for
720 // strings because a closing quote triggers value processing.
721 if ((jp
->statep
- jp
->stateStack
) == 1) {
722 if (*jp
->statep
== JSON_PARSE_STATE_KEYWORD
) {
723 early_ok
= HandleDataKeyword(cx
, jp
);
726 } else if (*jp
->statep
== JSON_PARSE_STATE_NUMBER
) {
727 early_ok
= HandleDataNumber(cx
, jp
);
733 // This internal API is infallible, in spite of its JSBool return type.
734 js_RemoveRoot(cx
->runtime
, &jp
->objectStack
);
736 bool ok
= *jp
->statep
== JSON_PARSE_STATE_FINISHED
;
737 Value
*vp
= jp
->rootVal
;
742 JSONParseError(jp
, cx
);
743 } else if (reviver
.isObject() && reviver
.toObject().isCallable()) {
744 ok
= Revive(cx
, reviver
, vp
);
753 PushState(JSContext
*cx
, JSONParser
*jp
, JSONParserState state
)
755 if (*jp
->statep
== JSON_PARSE_STATE_FINISHED
) {
757 return JSONParseError(jp
, cx
);
761 if ((uint32
)(jp
->statep
- jp
->stateStack
) >= JS_ARRAY_LENGTH(jp
->stateStack
)) {
763 return JSONParseError(jp
, cx
);
772 PopState(JSContext
*cx
, JSONParser
*jp
)
775 if (jp
->statep
< jp
->stateStack
) {
776 jp
->statep
= jp
->stateStack
;
777 return JSONParseError(jp
, cx
);
780 if (*jp
->statep
== JSON_PARSE_STATE_INIT
)
781 *jp
->statep
= JSON_PARSE_STATE_FINISHED
;
787 PushValue(JSContext
*cx
, JSONParser
*jp
, JSObject
*parent
, const Value
&value
)
790 if (parent
->isArray()) {
792 ok
= js_GetLengthProperty(cx
, parent
, &len
);
795 if (!js_IndexToId(cx
, len
, &index
))
797 ok
= parent
->defineProperty(cx
, index
, value
, NULL
, NULL
, JSPROP_ENUMERATE
);
800 ok
= JS_DefineUCProperty(cx
, parent
, jp
->objectKey
.begin(),
801 jp
->objectKey
.length(), Jsvalify(value
),
802 NULL
, NULL
, JSPROP_ENUMERATE
);
803 jp
->objectKey
.clear();
810 PushObject(JSContext
*cx
, JSONParser
*jp
, JSObject
*obj
)
813 if (!js_GetLengthProperty(cx
, jp
->objectStack
, &len
))
815 if (len
>= JSON_MAX_DEPTH
)
816 return JSONParseError(jp
, cx
);
818 AutoObjectRooter
tvr(cx
, obj
);
819 Value v
= ObjectOrNullValue(obj
);
821 // Check if this is the root object
824 // This property must be enumerable to keep the array dense
825 if (!jp
->objectStack
->defineProperty(cx
, INT_TO_JSID(0), *jp
->rootVal
,
826 NULL
, NULL
, JSPROP_ENUMERATE
)) {
833 if (!jp
->objectStack
->getProperty(cx
, INT_TO_JSID(len
- 1), &p
))
836 JSObject
*parent
= &p
.toObject();
837 if (!PushValue(cx
, jp
, parent
, v
))
840 // This property must be enumerable to keep the array dense
841 if (!jp
->objectStack
->defineProperty(cx
, INT_TO_JSID(len
), v
,
842 NULL
, NULL
, JSPROP_ENUMERATE
)) {
850 OpenObject(JSContext
*cx
, JSONParser
*jp
)
852 JSObject
*obj
= NewBuiltinClassInstance(cx
, &js_ObjectClass
);
856 return PushObject(cx
, jp
, obj
);
860 OpenArray(JSContext
*cx
, JSONParser
*jp
)
862 // Add an array to an existing array or object
863 JSObject
*arr
= NewDenseEmptyArray(cx
);
867 return PushObject(cx
, jp
, arr
);
871 CloseObject(JSContext
*cx
, JSONParser
*jp
)
874 if (!js_GetLengthProperty(cx
, jp
->objectStack
, &len
))
876 if (!js_SetLengthProperty(cx
, jp
->objectStack
, len
- 1))
883 CloseArray(JSContext
*cx
, JSONParser
*jp
)
885 return CloseObject(cx
, jp
);
889 PushPrimitive(JSContext
*cx
, JSONParser
*jp
, const Value
&value
)
891 AutoValueRooter
tvr(cx
, value
);
894 if (!js_GetLengthProperty(cx
, jp
->objectStack
, &len
))
899 if (!jp
->objectStack
->getProperty(cx
, INT_TO_JSID(len
- 1), &o
))
902 return PushValue(cx
, jp
, &o
.toObject(), value
);
905 // root value must be primitive
906 *jp
->rootVal
= value
;
911 HandleNumber(JSContext
*cx
, JSONParser
*jp
, const jschar
*buf
, uint32 len
)
915 if (!js_strtod(cx
, buf
, buf
+ len
, &ep
, &val
))
917 if (ep
!= buf
+ len
) {
919 return JSONParseError(jp
, cx
);
922 return PushPrimitive(cx
, jp
, NumberValue(val
));
926 HandleString(JSContext
*cx
, JSONParser
*jp
, const jschar
*buf
, uint32 len
)
928 JSString
*str
= js_NewStringCopyN(cx
, buf
, len
);
932 return PushPrimitive(cx
, jp
, StringValue(str
));
936 HandleKeyword(JSContext
*cx
, JSONParser
*jp
, const jschar
*buf
, uint32 len
)
938 const KeywordInfo
*ki
= FindKeyword(buf
, len
);
939 if (!ki
|| ki
->tokentype
!= TOK_PRIMARY
) {
941 return JSONParseError(jp
, cx
);
947 } else if (buf
[0] == 't') {
948 keyword
.setBoolean(true);
949 } else if (buf
[0] == 'f') {
950 keyword
.setBoolean(false);
952 return JSONParseError(jp
, cx
);
955 return PushPrimitive(cx
, jp
, keyword
);
959 HandleDataString(JSContext
*cx
, JSONParser
*jp
)
961 JSBool ok
= HandleString(cx
, jp
, jp
->buffer
.begin(), jp
->buffer
.length());
968 HandleDataKeyString(JSContext
*cx
, JSONParser
*jp
)
970 JSBool ok
= jp
->objectKey
.append(jp
->buffer
.begin(), jp
->buffer
.end());
977 HandleDataNumber(JSContext
*cx
, JSONParser
*jp
)
979 JSBool ok
= HandleNumber(cx
, jp
, jp
->buffer
.begin(), jp
->buffer
.length());
986 HandleDataKeyword(JSContext
*cx
, JSONParser
*jp
)
988 JSBool ok
= HandleKeyword(cx
, jp
, jp
->buffer
.begin(), jp
->buffer
.length());
995 js_ConsumeJSONText(JSContext
*cx
, JSONParser
*jp
, const jschar
*data
, uint32 len
,
996 DecodingMode decodingMode
)
1000 if (*jp
->statep
== JSON_PARSE_STATE_INIT
) {
1001 PushState(cx
, jp
, JSON_PARSE_STATE_VALUE
);
1004 for (uint32 i
= 0; i
< len
; i
++) {
1006 switch (*jp
->statep
) {
1007 case JSON_PARSE_STATE_ARRAY_INITIAL_VALUE
:
1009 if (!PopState(cx
, jp
))
1011 JS_ASSERT(*jp
->statep
== JSON_PARSE_STATE_ARRAY_AFTER_ELEMENT
);
1012 if (!CloseArray(cx
, jp
) || !PopState(cx
, jp
))
1016 // fall through if non-empty array or whitespace
1018 case JSON_PARSE_STATE_VALUE
:
1020 *jp
->statep
= JSON_PARSE_STATE_STRING
;
1025 *jp
->statep
= JSON_PARSE_STATE_NUMBER
;
1026 if (!jp
->buffer
.append(c
))
1032 *jp
->statep
= JSON_PARSE_STATE_KEYWORD
;
1033 if (!jp
->buffer
.append(c
))
1039 *jp
->statep
= JSON_PARSE_STATE_OBJECT_AFTER_PAIR
;
1040 if (!OpenObject(cx
, jp
) || !PushState(cx
, jp
, JSON_PARSE_STATE_OBJECT_INITIAL_PAIR
))
1042 } else if (c
== '[') {
1043 *jp
->statep
= JSON_PARSE_STATE_ARRAY_AFTER_ELEMENT
;
1044 if (!OpenArray(cx
, jp
) || !PushState(cx
, jp
, JSON_PARSE_STATE_ARRAY_INITIAL_VALUE
))
1046 } else if (JS_ISXMLSPACE(c
)) {
1048 } else if (decodingMode
== LEGACY
&& c
== ']') {
1049 if (!PopState(cx
, jp
))
1051 JS_ASSERT(*jp
->statep
== JSON_PARSE_STATE_ARRAY_AFTER_ELEMENT
);
1052 if (!CloseArray(cx
, jp
) || !PopState(cx
, jp
))
1055 return JSONParseError(jp
, cx
);
1059 case JSON_PARSE_STATE_ARRAY_AFTER_ELEMENT
:
1061 if (!PushState(cx
, jp
, JSON_PARSE_STATE_VALUE
))
1063 } else if (c
== ']') {
1064 if (!CloseArray(cx
, jp
) || !PopState(cx
, jp
))
1066 } else if (!JS_ISXMLSPACE(c
)) {
1067 return JSONParseError(jp
, cx
);
1071 case JSON_PARSE_STATE_OBJECT_AFTER_PAIR
:
1073 if (!PushState(cx
, jp
, JSON_PARSE_STATE_OBJECT_PAIR
))
1075 } else if (c
== '}') {
1076 if (!CloseObject(cx
, jp
) || !PopState(cx
, jp
))
1078 } else if (!JS_ISXMLSPACE(c
)) {
1079 return JSONParseError(jp
, cx
);
1083 case JSON_PARSE_STATE_OBJECT_INITIAL_PAIR
:
1085 if (!PopState(cx
, jp
))
1087 JS_ASSERT(*jp
->statep
== JSON_PARSE_STATE_OBJECT_AFTER_PAIR
);
1088 if (!CloseObject(cx
, jp
) || !PopState(cx
, jp
))
1092 // fall through if non-empty object or whitespace
1094 case JSON_PARSE_STATE_OBJECT_PAIR
:
1096 // we want to be waiting for a : when the string has been read
1097 *jp
->statep
= JSON_PARSE_STATE_OBJECT_IN_PAIR
;
1098 if (!PushState(cx
, jp
, JSON_PARSE_STATE_STRING
))
1100 } else if (JS_ISXMLSPACE(c
)) {
1102 } else if (decodingMode
== LEGACY
&& c
== '}') {
1103 if (!PopState(cx
, jp
))
1105 JS_ASSERT(*jp
->statep
== JSON_PARSE_STATE_OBJECT_AFTER_PAIR
);
1106 if (!CloseObject(cx
, jp
) || !PopState(cx
, jp
))
1109 return JSONParseError(jp
, cx
);
1113 case JSON_PARSE_STATE_OBJECT_IN_PAIR
:
1115 *jp
->statep
= JSON_PARSE_STATE_VALUE
;
1116 } else if (!JS_ISXMLSPACE(c
)) {
1117 return JSONParseError(jp
, cx
);
1121 case JSON_PARSE_STATE_STRING
:
1123 if (!PopState(cx
, jp
))
1125 if (*jp
->statep
== JSON_PARSE_STATE_OBJECT_IN_PAIR
) {
1126 if (!HandleDataKeyString(cx
, jp
))
1129 if (!HandleDataString(cx
, jp
))
1132 } else if (c
== '\\') {
1133 *jp
->statep
= JSON_PARSE_STATE_STRING_ESCAPE
;
1134 } else if (c
<= 0x1F) {
1135 // The JSON lexical grammer does not allow a JSONStringCharacter to be
1136 // any of the Unicode characters U+0000 thru U+001F (control characters).
1137 return JSONParseError(jp
, cx
);
1139 if (!jp
->buffer
.append(c
))
1144 case JSON_PARSE_STATE_STRING_ESCAPE
:
1150 case 'b' : c
= '\b'; break;
1151 case 'f' : c
= '\f'; break;
1152 case 'n' : c
= '\n'; break;
1153 case 'r' : c
= '\r'; break;
1154 case 't' : c
= '\t'; break;
1159 *jp
->statep
= JSON_PARSE_STATE_STRING_HEX
;
1162 return JSONParseError(jp
, cx
);
1166 if (!jp
->buffer
.append(c
))
1168 *jp
->statep
= JSON_PARSE_STATE_STRING
;
1171 case JSON_PARSE_STATE_STRING_HEX
:
1172 if (('0' <= c
) && (c
<= '9')) {
1173 jp
->hexChar
= (jp
->hexChar
<< 4) | (c
- '0');
1174 } else if (('a' <= c
) && (c
<= 'f')) {
1175 jp
->hexChar
= (jp
->hexChar
<< 4) | (c
- 'a' + 0x0a);
1176 } else if (('A' <= c
) && (c
<= 'F')) {
1177 jp
->hexChar
= (jp
->hexChar
<< 4) | (c
- 'A' + 0x0a);
1179 return JSONParseError(jp
, cx
);
1182 if (++(jp
->numHex
) == 4) {
1183 if (!jp
->buffer
.append(jp
->hexChar
))
1187 *jp
->statep
= JSON_PARSE_STATE_STRING
;
1191 case JSON_PARSE_STATE_KEYWORD
:
1193 if (!jp
->buffer
.append(c
))
1196 // this character isn't part of the keyword, process it again
1198 if (!PopState(cx
, jp
))
1201 if (!HandleDataKeyword(cx
, jp
))
1206 case JSON_PARSE_STATE_NUMBER
:
1208 if (!jp
->buffer
.append(c
))
1211 // this character isn't part of the number, process it again
1213 if (!PopState(cx
, jp
))
1215 if (!HandleDataNumber(cx
, jp
))
1220 case JSON_PARSE_STATE_FINISHED
:
1221 if (!JS_ISXMLSPACE(c
)) {
1223 return JSONParseError(jp
, cx
);
1228 JS_NOT_REACHED("Invalid JSON parser state");
1237 json_toSource(JSContext
*cx
, uintN argc
, Value
*vp
)
1239 vp
->setString(ATOM_TO_STRING(CLASS_ATOM(cx
, JSON
)));
1244 static JSFunctionSpec json_static_methods
[] = {
1246 JS_FN(js_toSource_str
, json_toSource
, 0, 0),
1248 JS_FN("parse", js_json_parse
, 2, 0),
1249 JS_FN("stringify", js_json_stringify
, 3, 0),
1254 js_InitJSONClass(JSContext
*cx
, JSObject
*obj
)
1258 JSON
= NewNonFunction
<WithProto::Class
>(cx
, &js_JSONClass
, NULL
, obj
);
1261 if (!JS_DefineProperty(cx
, obj
, js_JSON_str
, OBJECT_TO_JSVAL(JSON
),
1262 JS_PropertyStub
, JS_StrictPropertyStub
, 0))
1265 if (!JS_DefineFunctions(cx
, JSON
, json_static_methods
))