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 ***** */
66 #include "jsatominlines.h"
67 #include "jsobjinlines.h"
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 PropertyStub
, /* 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 JSONParser
*jp
= js_BeginJSONParse(cx
, vp
);
123 JSBool ok
= jp
!= NULL
;
127 s
->getCharsAndLength(chars
, length
);
128 ok
= js_ConsumeJSONText(cx
, jp
, chars
, length
);
129 ok
&= !!js_FinishJSONParse(cx
, jp
, reviver
.value());
136 js_json_stringify(JSContext
*cx
, uintN argc
, Value
*vp
)
138 Value
*argv
= vp
+ 2;
139 AutoValueRooter
space(cx
);
140 AutoObjectRooter
replacer(cx
);
142 // Must throw an Error if there isn't a first arg
143 if (!JS_ConvertArguments(cx
, argc
, Jsvalify(argv
), "v / o v", vp
, replacer
.addr(), space
.addr()))
148 if (!js_Stringify(cx
, vp
, replacer
.object(), space
.value(), cb
))
151 // XXX This can never happen to nsJSON.cpp, but the JSON object
152 // needs to support returning undefined. So this is a little awkward
153 // for the API, because we want to support streaming writers.
155 JSString
*str
= js_NewStringFromCharBuffer(cx
, cb
);
167 js_TryJSON(JSContext
*cx
, Value
*vp
)
169 // Checks whether the return value implements toJSON()
172 if (vp
->isObject()) {
173 JSObject
*obj
= &vp
->toObject();
174 ok
= js_TryMethod(cx
, obj
, cx
->runtime
->atomState
.toJSONAtom
, 0, NULL
, vp
);
181 static const char quote
= '\"';
182 static const char backslash
= '\\';
183 static const char unicodeEscape
[] = "\\u00";
186 write_string(JSContext
*cx
, JSCharBuffer
&cb
, const jschar
*buf
, uint32 len
)
188 if (!cb
.append(quote
))
193 for (i
= 0; i
< len
; ++i
) {
194 if (buf
[i
] == quote
|| buf
[i
] == backslash
) {
195 if (!cb
.append(&buf
[mark
], i
- mark
) || !cb
.append(backslash
) ||
196 !cb
.append(buf
[i
])) {
200 } else if (buf
[i
] <= 31 || buf
[i
] == 127) {
201 if (!cb
.append(&buf
[mark
], i
- mark
) ||
202 !js_AppendLiteral(cb
, unicodeEscape
)) {
206 size_t len
= JS_snprintf(ubuf
, sizeof(ubuf
), "%.2x", buf
[i
]);
209 size_t wbufSize
= JS_ARRAY_LENGTH(wbuf
);
210 if (!js_InflateStringToBuffer(cx
, ubuf
, len
, wbuf
, &wbufSize
) ||
211 !cb
.append(wbuf
, wbufSize
)) {
218 if (mark
< len
&& !cb
.append(&buf
[mark
], len
- mark
))
221 return cb
.append(quote
);
224 class StringifyContext
227 StringifyContext(JSContext
*cx
, JSCharBuffer
&cb
, JSObject
*replacer
)
228 : cb(cb
), gap(cx
), replacer(replacer
), depth(0), objectStack(cx
)
231 bool initializeGap(JSContext
*cx
, const Value
&space
) {
232 AutoValueRooter
gapValue(cx
, space
);
234 if (space
.isObject()) {
235 JSObject
&obj
= space
.toObject();
236 Class
*clasp
= obj
.getClass();
237 if (clasp
== &js_NumberClass
|| clasp
== &js_StringClass
)
238 *gapValue
.addr() = obj
.getPrimitiveThis();
241 if (gapValue
.value().isString()) {
242 if (!js_ValueToCharBuffer(cx
, gapValue
.value(), gap
))
244 if (gap
.length() > 10)
246 } else if (gapValue
.value().isNumber()) {
247 jsdouble d
= gapValue
.value().isInt32()
248 ? gapValue
.value().toInt32()
249 : js_DoubleToInteger(gapValue
.value().toDouble());
251 if (d
>= 1 && !gap
.appendN(' ', uint32(d
)))
258 bool initializeStack() {
259 return objectStack
.init(16);
263 ~StringifyContext() { JS_ASSERT(objectStack
.empty()); }
270 HashSet
<JSObject
*> objectStack
;
273 static JSBool
CallReplacerFunction(JSContext
*cx
, jsid id
, JSObject
*holder
,
274 StringifyContext
*scx
, Value
*vp
);
275 static JSBool
Str(JSContext
*cx
, jsid id
, JSObject
*holder
,
276 StringifyContext
*scx
, Value
*vp
, bool callReplacer
= true);
279 WriteIndent(JSContext
*cx
, StringifyContext
*scx
, uint32 limit
)
281 if (!scx
->gap
.empty()) {
282 if (!scx
->cb
.append('\n'))
284 for (uint32 i
= 0; i
< limit
; i
++) {
285 if (!scx
->cb
.append(scx
->gap
.begin(), scx
->gap
.end()))
296 CycleDetector(StringifyContext
*scx
, JSObject
*obj
)
297 : objectStack(scx
->objectStack
), obj(obj
) {
300 bool init(JSContext
*cx
) {
301 HashSet
<JSObject
*>::AddPtr ptr
= objectStack
.lookupForAdd(obj
);
303 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, NULL
, JSMSG_CYCLIC_VALUE
, js_object_str
);
306 return objectStack
.add(ptr
, obj
);
310 objectStack
.remove(obj
);
314 HashSet
<JSObject
*> &objectStack
;
319 JO(JSContext
*cx
, Value
*vp
, StringifyContext
*scx
)
321 JSObject
*obj
= &vp
->toObject();
323 CycleDetector
detect(scx
, obj
);
324 if (!detect
.init(cx
))
327 if (!scx
->cb
.append('{'))
330 Value vec
[3] = { NullValue(), NullValue(), NullValue() };
331 AutoArrayRooter
tvr(cx
, JS_ARRAY_LENGTH(vec
), vec
);
332 Value
& outputValue
= vec
[0];
333 Value
& whitelistElement
= vec
[1];
334 AutoIdRooter
idr(cx
);
335 jsid
& id
= *idr
.addr();
337 Value
*keySource
= vp
;
338 bool usingWhitelist
= false;
340 // if the replacer is an array, we use the keys from it
341 if (scx
->replacer
&& JS_IsArrayObject(cx
, scx
->replacer
)) {
342 usingWhitelist
= true;
343 vec
[2].setObject(*scx
->replacer
);
347 JSBool memberWritten
= JS_FALSE
;
348 AutoIdVector
props(cx
);
349 if (!GetPropertyNames(cx
, &keySource
->toObject(), JSITER_OWNONLY
, props
))
352 for (size_t i
= 0, len
= props
.length(); i
< len
; i
++) {
353 outputValue
.setUndefined();
355 if (!usingWhitelist
) {
356 if (!js_ValueToStringId(cx
, IdToValue(props
[i
]), &id
))
359 // skip non-index properties
361 if (!js_IdIsIndex(props
[i
], &index
))
364 if (!scx
->replacer
->getProperty(cx
, props
[i
], &whitelistElement
))
367 if (!js_ValueToStringId(cx
, whitelistElement
, &id
))
371 // We should have a string id by this point. Either from
372 // JS_Enumerate's id array, or by converting an element
374 JS_ASSERT(JSID_IS_ATOM(id
));
376 if (!JS_GetPropertyById(cx
, obj
, id
, Jsvalify(&outputValue
)))
379 if (outputValue
.isObjectOrNull() && !js_TryJSON(cx
, &outputValue
))
382 // call this here, so we don't write out keys if the replacer function
383 // wants to elide the value.
384 if (!CallReplacerFunction(cx
, id
, obj
, scx
, &outputValue
))
387 JSType type
= JS_TypeOfValue(cx
, Jsvalify(outputValue
));
389 // elide undefined values and functions and XML
390 if (outputValue
.isUndefined() || type
== JSTYPE_FUNCTION
|| type
== JSTYPE_XML
)
393 // output a comma unless this is the first member to write
394 if (memberWritten
&& !scx
->cb
.append(','))
396 memberWritten
= JS_TRUE
;
398 if (!WriteIndent(cx
, scx
, scx
->depth
))
401 // Be careful below, this string is weakly rooted
402 JSString
*s
= js_ValueToString(cx
, IdToValue(id
));
408 s
->getCharsAndLength(chars
, length
);
409 if (!write_string(cx
, scx
->cb
, chars
, length
) ||
410 !scx
->cb
.append(':') ||
411 !(scx
->gap
.empty() || scx
->cb
.append(' ')) ||
412 !Str(cx
, id
, obj
, scx
, &outputValue
, true)) {
417 if (memberWritten
&& !WriteIndent(cx
, scx
, scx
->depth
- 1))
420 return scx
->cb
.append('}');
424 JA(JSContext
*cx
, Value
*vp
, StringifyContext
*scx
)
426 JSObject
*obj
= &vp
->toObject();
428 CycleDetector
detect(scx
, obj
);
429 if (!detect
.init(cx
))
432 if (!scx
->cb
.append('['))
436 if (!js_GetLengthProperty(cx
, obj
, &length
))
439 if (length
!= 0 && !WriteIndent(cx
, scx
, scx
->depth
))
442 AutoValueRooter
outputValue(cx
);
446 for (i
= 0; i
< length
; i
++) {
449 if (!obj
->getProperty(cx
, id
, outputValue
.addr()))
452 if (!Str(cx
, id
, obj
, scx
, outputValue
.addr()))
455 if (outputValue
.value().isUndefined()) {
456 if (!js_AppendLiteral(scx
->cb
, "null"))
460 if (i
< length
- 1) {
461 if (!scx
->cb
.append(','))
463 if (!WriteIndent(cx
, scx
, scx
->depth
))
468 if (length
!= 0 && !WriteIndent(cx
, scx
, scx
->depth
- 1))
471 return scx
->cb
.append(']');
475 CallReplacerFunction(JSContext
*cx
, jsid id
, JSObject
*holder
, StringifyContext
*scx
, Value
*vp
)
477 if (scx
->replacer
&& scx
->replacer
->isCallable()) {
478 Value vec
[2] = { IdToValue(id
), *vp
};
479 if (!JS_CallFunctionValue(cx
, holder
, OBJECT_TO_JSVAL(scx
->replacer
),
480 2, Jsvalify(vec
), Jsvalify(vp
))) {
489 Str(JSContext
*cx
, jsid id
, JSObject
*holder
, StringifyContext
*scx
, Value
*vp
, bool callReplacer
)
491 JS_CHECK_RECURSION(cx
, return JS_FALSE
);
493 if (vp
->isObject() && !js_TryJSON(cx
, vp
))
496 if (callReplacer
&& !CallReplacerFunction(cx
, id
, holder
, scx
, vp
))
499 // catches string and number objects with no toJSON
500 if (vp
->isObject()) {
501 JSObject
*obj
= &vp
->toObject();
502 Class
*clasp
= obj
->getClass();
503 if (clasp
== &js_StringClass
|| clasp
== &js_NumberClass
)
504 *vp
= obj
->getPrimitiveThis();
507 if (vp
->isString()) {
510 vp
->toString()->getCharsAndLength(chars
, length
);
511 return write_string(cx
, scx
->cb
, chars
, length
);
515 return js_AppendLiteral(scx
->cb
, "null");
518 if (vp
->isBoolean()) {
519 return vp
->toBoolean() ? js_AppendLiteral(scx
->cb
, "true")
520 : js_AppendLiteral(scx
->cb
, "false");
523 if (vp
->isNumber()) {
524 if (vp
->isDouble()) {
525 jsdouble d
= vp
->toDouble();
526 if (!JSDOUBLE_IS_FINITE(d
))
527 return js_AppendLiteral(scx
->cb
, "null");
530 char numBuf
[DTOSTR_STANDARD_BUFFER_SIZE
], *numStr
;
531 jsdouble d
= vp
->isInt32() ? jsdouble(vp
->toInt32()) : vp
->toDouble();
532 numStr
= js_dtostr(JS_THREAD_DATA(cx
)->dtoaState
, numBuf
, sizeof numBuf
,
533 DTOSTR_STANDARD
, 0, d
);
535 JS_ReportOutOfMemory(cx
);
539 jschar dstr
[DTOSTR_STANDARD_BUFFER_SIZE
];
540 size_t dbufSize
= DTOSTR_STANDARD_BUFFER_SIZE
;
541 if (!js_InflateStringToBuffer(cx
, numStr
, strlen(numStr
), dstr
, &dbufSize
))
544 return scx
->cb
.append(dstr
, dbufSize
);
547 if (vp
->isObject() && !IsFunctionObject(*vp
) && !IsXML(*vp
)) {
551 ok
= (JS_IsArrayObject(cx
, &vp
->toObject()) ? JA
: JO
)(cx
, vp
, scx
);
562 js_Stringify(JSContext
*cx
, Value
*vp
, JSObject
*replacer
, const Value
&space
,
565 StringifyContext
scx(cx
, cb
, replacer
);
566 if (!scx
.initializeGap(cx
, space
) || !scx
.initializeStack())
569 JSObject
*obj
= NewBuiltinClassInstance(cx
, &js_ObjectClass
);
573 AutoObjectRooter
tvr(cx
, obj
);
574 if (!obj
->defineProperty(cx
, ATOM_TO_JSID(cx
->runtime
->atomState
.emptyAtom
),
575 *vp
, NULL
, NULL
, JSPROP_ENUMERATE
)) {
579 return Str(cx
, ATOM_TO_JSID(cx
->runtime
->atomState
.emptyAtom
), obj
, &scx
, vp
);
582 // helper to determine whether a character could be part of a number
583 static JSBool
IsNumChar(jschar c
)
585 return ((c
<= '9' && c
>= '0') || c
== '.' || c
== '-' || c
== '+' || c
== 'e' || c
== 'E');
588 static JSBool
HandleData(JSContext
*cx
, JSONParser
*jp
, JSONDataType type
);
589 static JSBool
PopState(JSContext
*cx
, JSONParser
*jp
);
592 Walk(JSContext
*cx
, jsid id
, JSObject
*holder
, const Value
&reviver
, Value
*vp
)
594 JS_CHECK_RECURSION(cx
, return false);
596 if (!holder
->getProperty(cx
, id
, vp
))
601 if (vp
->isObject() && !(obj
= &vp
->toObject())->isCallable()) {
602 AutoValueRooter
propValue(cx
);
606 if (!js_GetLengthProperty(cx
, obj
, &length
))
609 for (jsuint i
= 0; i
< length
; i
++) {
611 if (!js_IndexToId(cx
, i
, &index
))
614 if (!Walk(cx
, index
, obj
, reviver
, propValue
.addr()))
617 if (!obj
->defineProperty(cx
, index
, propValue
.value(), NULL
, NULL
, JSPROP_ENUMERATE
))
621 AutoIdVector
props(cx
);
622 if (!GetPropertyNames(cx
, obj
, JSITER_OWNONLY
, props
))
625 for (size_t i
= 0, len
= props
.length(); i
< len
; i
++) {
626 jsid idName
= props
[i
];
627 if (!Walk(cx
, idName
, obj
, reviver
, propValue
.addr()))
629 if (propValue
.value().isUndefined()) {
630 if (!js_DeleteProperty(cx
, obj
, idName
, propValue
.addr()))
633 if (!obj
->defineProperty(cx
, idName
, propValue
.value(), NULL
, NULL
,
642 // return reviver.call(holder, key, value);
643 const Value
&value
= *vp
;
644 JSString
*key
= js_ValueToString(cx
, IdToValue(id
));
648 Value vec
[2] = { StringValue(key
), value
};
650 if (!JS_CallFunctionValue(cx
, holder
, Jsvalify(reviver
),
651 2, Jsvalify(vec
), Jsvalify(&reviverResult
))) {
660 JSONParseError(JSONParser
*jp
, JSContext
*cx
)
662 if (!jp
->suppressErrors
)
663 JS_ReportErrorNumber(cx
, js_GetErrorMessage
, NULL
, JSMSG_JSON_BAD_PARSE
);
668 Revive(JSContext
*cx
, const Value
&reviver
, Value
*vp
)
671 JSObject
*obj
= NewBuiltinClassInstance(cx
, &js_ObjectClass
);
675 AutoObjectRooter
tvr(cx
, obj
);
676 if (!obj
->defineProperty(cx
, ATOM_TO_JSID(cx
->runtime
->atomState
.emptyAtom
),
677 *vp
, NULL
, NULL
, JSPROP_ENUMERATE
)) {
681 return Walk(cx
, ATOM_TO_JSID(cx
->runtime
->atomState
.emptyAtom
), obj
, reviver
, vp
);
685 js_BeginJSONParse(JSContext
*cx
, Value
*rootVal
, bool suppressErrors
/*= false*/)
690 JSObject
*arr
= js_NewArrayObject(cx
, 0, NULL
);
694 JSONParser
*jp
= cx
->create
<JSONParser
>(cx
);
698 jp
->objectStack
= arr
;
699 if (!JS_AddNamedObjectRoot(cx
, &jp
->objectStack
, "JSON parse stack"))
702 jp
->statep
= jp
->stateStack
;
703 *jp
->statep
= JSON_PARSE_STATE_INIT
;
704 jp
->rootVal
= rootVal
;
705 jp
->suppressErrors
= suppressErrors
;
710 js_FinishJSONParse(cx
, jp
, NullValue());
715 js_FinishJSONParse(JSContext
*cx
, JSONParser
*jp
, const Value
&reviver
)
720 JSBool early_ok
= JS_TRUE
;
722 // Check for unprocessed primitives at the root. This doesn't happen for
723 // strings because a closing quote triggers value processing.
724 if ((jp
->statep
- jp
->stateStack
) == 1) {
725 if (*jp
->statep
== JSON_PARSE_STATE_KEYWORD
) {
726 early_ok
= HandleData(cx
, jp
, JSON_DATA_KEYWORD
);
729 } else if (*jp
->statep
== JSON_PARSE_STATE_NUMBER
) {
730 early_ok
= HandleData(cx
, jp
, JSON_DATA_NUMBER
);
736 // This internal API is infallible, in spite of its JSBool return type.
737 js_RemoveRoot(cx
->runtime
, &jp
->objectStack
);
739 bool ok
= *jp
->statep
== JSON_PARSE_STATE_FINISHED
;
740 Value
*vp
= jp
->rootVal
;
745 JSONParseError(jp
, cx
);
746 } else if (reviver
.isObject() && reviver
.toObject().isCallable()) {
747 ok
= Revive(cx
, reviver
, vp
);
756 PushState(JSContext
*cx
, JSONParser
*jp
, JSONParserState state
)
758 if (*jp
->statep
== JSON_PARSE_STATE_FINISHED
) {
760 return JSONParseError(jp
, cx
);
764 if ((uint32
)(jp
->statep
- jp
->stateStack
) >= JS_ARRAY_LENGTH(jp
->stateStack
)) {
766 return JSONParseError(jp
, cx
);
775 PopState(JSContext
*cx
, JSONParser
*jp
)
778 if (jp
->statep
< jp
->stateStack
) {
779 jp
->statep
= jp
->stateStack
;
780 return JSONParseError(jp
, cx
);
783 if (*jp
->statep
== JSON_PARSE_STATE_INIT
)
784 *jp
->statep
= JSON_PARSE_STATE_FINISHED
;
790 PushValue(JSContext
*cx
, JSONParser
*jp
, JSObject
*parent
, const Value
&value
)
793 if (parent
->isArray()) {
795 ok
= js_GetLengthProperty(cx
, parent
, &len
);
798 if (!js_IndexToId(cx
, len
, &index
))
800 ok
= parent
->defineProperty(cx
, index
, value
, NULL
, NULL
, JSPROP_ENUMERATE
);
803 ok
= JS_DefineUCProperty(cx
, parent
, jp
->objectKey
.begin(),
804 jp
->objectKey
.length(), Jsvalify(value
),
805 NULL
, NULL
, JSPROP_ENUMERATE
);
806 jp
->objectKey
.clear();
813 PushObject(JSContext
*cx
, JSONParser
*jp
, JSObject
*obj
)
816 if (!js_GetLengthProperty(cx
, jp
->objectStack
, &len
))
818 if (len
>= JSON_MAX_DEPTH
)
819 return JSONParseError(jp
, cx
);
821 AutoObjectRooter
tvr(cx
, obj
);
822 Value v
= ObjectOrNullValue(obj
);
824 // Check if this is the root object
827 // This property must be enumerable to keep the array dense
828 if (!jp
->objectStack
->defineProperty(cx
, INT_TO_JSID(0), *jp
->rootVal
,
829 NULL
, NULL
, JSPROP_ENUMERATE
)) {
836 if (!jp
->objectStack
->getProperty(cx
, INT_TO_JSID(len
- 1), &p
))
839 JSObject
*parent
= &p
.toObject();
840 if (!PushValue(cx
, jp
, parent
, v
))
843 // This property must be enumerable to keep the array dense
844 if (!jp
->objectStack
->defineProperty(cx
, INT_TO_JSID(len
), v
,
845 NULL
, NULL
, JSPROP_ENUMERATE
)) {
853 OpenObject(JSContext
*cx
, JSONParser
*jp
)
855 JSObject
*obj
= NewBuiltinClassInstance(cx
, &js_ObjectClass
);
859 return PushObject(cx
, jp
, obj
);
863 OpenArray(JSContext
*cx
, JSONParser
*jp
)
865 // Add an array to an existing array or object
866 JSObject
*arr
= js_NewArrayObject(cx
, 0, NULL
);
870 return PushObject(cx
, jp
, arr
);
874 CloseObject(JSContext
*cx
, JSONParser
*jp
)
877 if (!js_GetLengthProperty(cx
, jp
->objectStack
, &len
))
879 if (!js_SetLengthProperty(cx
, jp
->objectStack
, len
- 1))
886 CloseArray(JSContext
*cx
, JSONParser
*jp
)
888 return CloseObject(cx
, jp
);
892 PushPrimitive(JSContext
*cx
, JSONParser
*jp
, const Value
&value
)
894 AutoValueRooter
tvr(cx
, value
);
897 if (!js_GetLengthProperty(cx
, jp
->objectStack
, &len
))
902 if (!jp
->objectStack
->getProperty(cx
, INT_TO_JSID(len
- 1), &o
))
905 return PushValue(cx
, jp
, &o
.toObject(), value
);
908 // root value must be primitive
909 *jp
->rootVal
= value
;
914 HandleNumber(JSContext
*cx
, JSONParser
*jp
, const jschar
*buf
, uint32 len
)
918 if (!js_strtod(cx
, buf
, buf
+ len
, &ep
, &val
))
920 if (ep
!= buf
+ len
) {
922 return JSONParseError(jp
, cx
);
925 return PushPrimitive(cx
, jp
, DoubleValue(val
));
929 HandleString(JSContext
*cx
, JSONParser
*jp
, const jschar
*buf
, uint32 len
)
931 JSString
*str
= js_NewStringCopyN(cx
, buf
, len
);
935 return PushPrimitive(cx
, jp
, StringValue(str
));
939 HandleKeyword(JSContext
*cx
, JSONParser
*jp
, const jschar
*buf
, uint32 len
)
942 TokenKind tt
= js_CheckKeyword(buf
, len
);
943 if (tt
!= TOK_PRIMARY
) {
945 return JSONParseError(jp
, cx
);
950 } else if (buf
[0] == 't') {
951 keyword
.setBoolean(true);
952 } else if (buf
[0] == 'f') {
953 keyword
.setBoolean(false);
955 return JSONParseError(jp
, cx
);
958 return PushPrimitive(cx
, jp
, keyword
);
962 HandleData(JSContext
*cx
, JSONParser
*jp
, JSONDataType type
)
967 case JSON_DATA_STRING
:
968 ok
= HandleString(cx
, jp
, jp
->buffer
.begin(), jp
->buffer
.length());
971 case JSON_DATA_KEYSTRING
:
972 ok
= jp
->objectKey
.append(jp
->buffer
.begin(), jp
->buffer
.end());
975 case JSON_DATA_NUMBER
:
976 ok
= HandleNumber(cx
, jp
, jp
->buffer
.begin(), jp
->buffer
.length());
980 JS_ASSERT(type
== JSON_DATA_KEYWORD
);
981 ok
= HandleKeyword(cx
, jp
, jp
->buffer
.begin(), jp
->buffer
.length());
991 js_ConsumeJSONText(JSContext
*cx
, JSONParser
*jp
, const jschar
*data
, uint32 len
)
995 if (*jp
->statep
== JSON_PARSE_STATE_INIT
) {
996 PushState(cx
, jp
, JSON_PARSE_STATE_VALUE
);
999 for (i
= 0; i
< len
; i
++) {
1001 switch (*jp
->statep
) {
1002 case JSON_PARSE_STATE_VALUE
:
1005 if (!PopState(cx
, jp
))
1008 if (*jp
->statep
!= JSON_PARSE_STATE_ARRAY
)
1009 return JSONParseError(jp
, cx
);
1011 if (!CloseArray(cx
, jp
) || !PopState(cx
, jp
))
1018 // we should only find these in OBJECT_KEY state
1019 return JSONParseError(jp
, cx
);
1023 *jp
->statep
= JSON_PARSE_STATE_STRING
;
1028 *jp
->statep
= JSON_PARSE_STATE_NUMBER
;
1029 if (!jp
->buffer
.append(c
))
1035 *jp
->statep
= JSON_PARSE_STATE_KEYWORD
;
1036 if (!jp
->buffer
.append(c
))
1041 // fall through in case the value is an object or array
1042 case JSON_PARSE_STATE_OBJECT_VALUE
:
1044 *jp
->statep
= JSON_PARSE_STATE_OBJECT
;
1045 if (!OpenObject(cx
, jp
) || !PushState(cx
, jp
, JSON_PARSE_STATE_OBJECT_PAIR
))
1047 } else if (c
== '[') {
1048 *jp
->statep
= JSON_PARSE_STATE_ARRAY
;
1049 if (!OpenArray(cx
, jp
) || !PushState(cx
, jp
, JSON_PARSE_STATE_VALUE
))
1051 } else if (!JS_ISXMLSPACE(c
)) {
1052 return JSONParseError(jp
, cx
);
1056 case JSON_PARSE_STATE_OBJECT
:
1058 if (!CloseObject(cx
, jp
) || !PopState(cx
, jp
))
1060 } else if (c
== ',') {
1061 if (!PushState(cx
, jp
, JSON_PARSE_STATE_OBJECT_PAIR
))
1063 } else if (c
== ']' || !JS_ISXMLSPACE(c
)) {
1064 return JSONParseError(jp
, cx
);
1068 case JSON_PARSE_STATE_ARRAY
:
1070 if (!CloseArray(cx
, jp
) || !PopState(cx
, jp
))
1072 } else if (c
== ',') {
1073 if (!PushState(cx
, jp
, JSON_PARSE_STATE_VALUE
))
1075 } else if (!JS_ISXMLSPACE(c
)) {
1076 return JSONParseError(jp
, cx
);
1080 case JSON_PARSE_STATE_OBJECT_PAIR
:
1082 // we want to be waiting for a : when the string has been read
1083 *jp
->statep
= JSON_PARSE_STATE_OBJECT_IN_PAIR
;
1084 if (!PushState(cx
, jp
, JSON_PARSE_STATE_STRING
))
1086 } else if (c
== '}') {
1087 // pop off the object pair state and the object state
1088 if (!CloseObject(cx
, jp
) || !PopState(cx
, jp
) || !PopState(cx
, jp
))
1090 } else if (c
== ']' || !JS_ISXMLSPACE(c
)) {
1091 return JSONParseError(jp
, cx
);
1095 case JSON_PARSE_STATE_OBJECT_IN_PAIR
:
1097 *jp
->statep
= JSON_PARSE_STATE_VALUE
;
1098 } else if (!JS_ISXMLSPACE(c
)) {
1099 return JSONParseError(jp
, cx
);
1103 case JSON_PARSE_STATE_STRING
:
1105 if (!PopState(cx
, jp
))
1108 if (*jp
->statep
== JSON_PARSE_STATE_OBJECT_IN_PAIR
) {
1109 jdt
= JSON_DATA_KEYSTRING
;
1111 jdt
= JSON_DATA_STRING
;
1113 if (!HandleData(cx
, jp
, jdt
))
1115 } else if (c
== '\\') {
1116 *jp
->statep
= JSON_PARSE_STATE_STRING_ESCAPE
;
1117 } else if (c
< 31) {
1118 // The JSON lexical grammer does not allow a JSONStringCharacter to be
1119 // any of the Unicode characters U+0000 thru U+001F (control characters).
1120 return JSONParseError(jp
, cx
);
1122 if (!jp
->buffer
.append(c
))
1127 case JSON_PARSE_STATE_STRING_ESCAPE
:
1133 case 'b' : c
= '\b'; break;
1134 case 'f' : c
= '\f'; break;
1135 case 'n' : c
= '\n'; break;
1136 case 'r' : c
= '\r'; break;
1137 case 't' : c
= '\t'; break;
1142 *jp
->statep
= JSON_PARSE_STATE_STRING_HEX
;
1145 return JSONParseError(jp
, cx
);
1149 if (!jp
->buffer
.append(c
))
1151 *jp
->statep
= JSON_PARSE_STATE_STRING
;
1154 case JSON_PARSE_STATE_STRING_HEX
:
1155 if (('0' <= c
) && (c
<= '9')) {
1156 jp
->hexChar
= (jp
->hexChar
<< 4) | (c
- '0');
1157 } else if (('a' <= c
) && (c
<= 'f')) {
1158 jp
->hexChar
= (jp
->hexChar
<< 4) | (c
- 'a' + 0x0a);
1159 } else if (('A' <= c
) && (c
<= 'F')) {
1160 jp
->hexChar
= (jp
->hexChar
<< 4) | (c
- 'A' + 0x0a);
1162 return JSONParseError(jp
, cx
);
1165 if (++(jp
->numHex
) == 4) {
1166 if (!jp
->buffer
.append(jp
->hexChar
))
1170 *jp
->statep
= JSON_PARSE_STATE_STRING
;
1174 case JSON_PARSE_STATE_KEYWORD
:
1176 if (!jp
->buffer
.append(c
))
1179 // this character isn't part of the keyword, process it again
1181 if (!PopState(cx
, jp
))
1184 if (!HandleData(cx
, jp
, JSON_DATA_KEYWORD
))
1189 case JSON_PARSE_STATE_NUMBER
:
1191 if (!jp
->buffer
.append(c
))
1194 // this character isn't part of the number, process it again
1196 if (!PopState(cx
, jp
))
1198 if (!HandleData(cx
, jp
, JSON_DATA_NUMBER
))
1203 case JSON_PARSE_STATE_FINISHED
:
1204 if (!JS_ISXMLSPACE(c
)) {
1206 return JSONParseError(jp
, cx
);
1211 JS_NOT_REACHED("Invalid JSON parser state");
1220 json_toSource(JSContext
*cx
, uintN argc
, Value
*vp
)
1222 vp
->setString(ATOM_TO_STRING(CLASS_ATOM(cx
, JSON
)));
1227 static JSFunctionSpec json_static_methods
[] = {
1229 JS_FN(js_toSource_str
, json_toSource
, 0, 0),
1231 JS_FN("parse", js_json_parse
, 2, 0),
1232 JS_FN("stringify", js_json_stringify
, 3, 0),
1237 js_InitJSONClass(JSContext
*cx
, JSObject
*obj
)
1241 JSON
= NewNonFunction
<WithProto::Class
>(cx
, &js_JSONClass
, NULL
, obj
);
1244 if (!JS_DefineProperty(cx
, obj
, js_JSON_str
, OBJECT_TO_JSVAL(JSON
),
1245 JS_PropertyStub
, JS_PropertyStub
, 0))
1248 if (!JS_DefineFunctions(cx
, JSON
, json_static_methods
))