/[jscoverage]/trunk/js/json.cpp
ViewVC logotype

Contents of /trunk/js/json.cpp

Parent Directory Parent Directory | Revision Log Revision Log


Revision 399 - (show annotations)
Tue Dec 9 03:37:47 2008 UTC (11 years, 10 months ago) by siliconforks
File size: 27869 byte(s)
Use SpiderMonkey from Firefox 3.1b2.

1 /* ***** BEGIN LICENSE BLOCK *****
2 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
3 *
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/
8 *
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.
13 *
14 * The Original Code is SpiderMonkey JSON.
15 *
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.
20 *
21 * Contributor(s):
22 * Robert Sayre <sayrer@gmail.com>
23 *
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.
35 *
36 * ***** END LICENSE BLOCK ***** */
37
38
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"
55
56 #include "json.h"
57
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
64 };
65
66 JSBool
67 js_json_parse(JSContext *cx, uintN argc, jsval *vp)
68 {
69 JSString *s = NULL;
70 jsval *argv = vp + 2;
71
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;
75
76
77 JSONParser *jp = js_BeginJSONParse(cx, vp);
78 JSBool ok = jp != NULL;
79
80 if (ok) {
81 ok = js_ConsumeJSONText(cx, jp, JS_GetStringChars(s), JS_GetStringLength(s));
82 ok &= js_FinishJSONParse(cx, jp);
83 }
84
85 if (!ok)
86 JS_ReportError(cx, "Error parsing JSON");
87
88 return ok;
89 }
90
91 class StringifyClosure : JSAutoTempValueRooter
92 {
93 public:
94 StringifyClosure(JSContext *cx, size_t len, jsval *vec)
95 : JSAutoTempValueRooter(cx, len, vec), cx(cx), s(vec)
96 {
97 }
98
99 JSContext *cx;
100 jsval *s;
101 };
102
103 static JSBool
104 WriteCallback(const jschar *buf, uint32 len, void *data)
105 {
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;
111
112 sc->s[1] = STRING_TO_JSVAL(s2);
113
114 s1 = js_ConcatStrings(sc->cx, s1, s2);
115 if (!s1)
116 return JS_FALSE;
117
118 sc->s[0] = STRING_TO_JSVAL(s1);
119 sc->s[1] = JSVAL_VOID;
120
121 return JS_TRUE;
122 }
123
124 JSBool
125 js_json_stringify(JSContext *cx, uintN argc, jsval *vp)
126 {
127 JSObject *obj;
128 jsval *argv = vp + 2;
129
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;
133
134 // Only use objects and arrays as the root for now
135 *vp = OBJECT_TO_JSVAL(obj);
136
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;
145 }
146
147 JSString *s = JS_NewStringCopyN(cx, "", 0);
148 if (!s)
149 ok = JS_FALSE;
150
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;
157 }
158
159 return ok;
160 }
161
162 JSBool
163 js_TryJSON(JSContext *cx, jsval *vp)
164 {
165 // Checks whether the return value implements toJSON()
166 JSBool ok = JS_TRUE;
167
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);
171 }
172
173 return ok;
174 }
175
176
177 static const jschar quote = jschar('"');
178 static const jschar backslash = jschar('\\');
179 static const jschar unicodeEscape[] = {'\\', 'u', '0', '0'};
180
181 static JSBool
182 write_string(JSContext *cx, JSONWriteCallback callback, void *data, const jschar *buf, uint32 len)
183 {
184 if (!callback(&quote, 1, data))
185 return JS_FALSE;
186
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;
194 }
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;
207 }
208 mark = i + 1;
209 }
210 }
211
212 if (mark < len && !callback(&buf[mark], len - mark, data))
213 return JS_FALSE;
214
215 if (!callback(&quote, 1, data))
216 return JS_FALSE;
217
218 return JS_TRUE;
219 }
220
221 JSBool
222 js_Stringify(JSContext *cx, jsval *vp, JSObject *replacer,
223 JSONWriteCallback callback, void *data, uint32 depth)
224 {
225 if (depth > JSON_MAX_DEPTH)
226 return JS_FALSE; /* encoding error */
227
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;
234
235 JSObject *iterObj = NULL;
236 jsint i = 0;
237 jsuint length = 0;
238
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);
246 }
247
248 jsval outputValue = JSVAL_VOID;
249 JSAutoTempValueRooter tvr(cx, 1, &outputValue);
250
251 jsval key;
252 JSBool memberWritten = JS_FALSE;
253 do {
254 outputValue = JSVAL_VOID;
255
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;
267
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;
276 }
277 }
278
279 ok = JS_GetUCProperty(cx, obj, JS_GetStringChars(ks),
280 JS_GetStringLength(ks), &outputValue);
281 }
282
283 if (!ok)
284 break;
285
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;
293 }
294
295 // elide undefined values
296 if (outputValue == JSVAL_VOID)
297 continue;
298
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;
305 }
306 memberWritten = JS_TRUE;
307
308 JSType type = JS_TypeOfValue(cx, outputValue);
309
310 // Can't encode these types, so drop them
311 if (type == JSTYPE_FUNCTION || type == JSTYPE_XML)
312 break;
313
314 // Be careful below, this string is weakly rooted.
315 JSString *s;
316
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;
323 }
324
325 ok = write_string(cx, callback, data, JS_GetStringChars(s), JS_GetStringLength(s));
326 if (!ok)
327 break;
328
329 output = jschar(':');
330 ok = callback(&output, 1, data);
331 if (!ok)
332 break;
333 }
334
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;
344 }
345
346 if (type == JSTYPE_STRING) {
347 ok = write_string(cx, callback, data, JS_GetStringChars(s), JS_GetStringLength(s));
348 if (!ok)
349 break;
350
351 continue;
352 }
353
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;
363 }
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;
371 }
372
373 if (!outputString) {
374 ok = JS_FALSE;
375 break;
376 }
377
378 ok = callback(JS_GetStringChars(outputString), JS_GetStringLength(outputString), data);
379 }
380 } while (ok);
381
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.
386 }
387
388 if (!ok) {
389 JS_ReportError(cx, "Error during JSON encoding");
390 return JS_FALSE;
391 }
392
393 output = jschar(isArray ? ']' : '}');
394 ok = callback(&output, 1, data);
395
396 return ok;
397 }
398
399 // helper to determine whether a character could be part of a number
400 static JSBool IsNumChar(jschar c)
401 {
402 return ((c <= '9' && c >= '0') || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E');
403 }
404
405 JSONParser *
406 js_BeginJSONParse(JSContext *cx, jsval *rootVal)
407 {
408 if (!cx)
409 return NULL;
410
411 JSObject *arr = js_NewArrayObject(cx, 0, NULL);
412 if (!arr)
413 return NULL;
414
415 JSONParser *jp = (JSONParser*) JS_malloc(cx, sizeof(JSONParser));
416 if (!jp)
417 return NULL;
418 jp->buffer = NULL;
419
420 jp->objectStack = arr;
421 if (!js_AddRoot(cx, &jp->objectStack, "JSON parse stack"))
422 goto bad;
423
424 jp->hexChar = 0;
425 jp->numHex = 0;
426 jp->statep = jp->stateStack;
427 *jp->statep = JSON_PARSE_STATE_INIT;
428 jp->rootVal = rootVal;
429
430 jp->objectKey = (JSStringBuffer*) JS_malloc(cx, sizeof(JSStringBuffer));
431 if (!jp->objectKey)
432 goto bad;
433 js_InitStringBuffer(jp->objectKey);
434
435 jp->buffer = (JSStringBuffer*) JS_malloc(cx, sizeof(JSStringBuffer));
436 if (!jp->buffer)
437 goto bad;
438 js_InitStringBuffer(jp->buffer);
439
440 return jp;
441 bad:
442 JS_free(cx, jp->buffer);
443 JS_free(cx, jp);
444 return NULL;
445 }
446
447 JSBool
448 js_FinishJSONParse(JSContext *cx, JSONParser *jp)
449 {
450 if (!jp)
451 return JS_TRUE;
452
453 if (jp->objectKey)
454 js_FinishStringBuffer(jp->objectKey);
455 JS_free(cx, jp->objectKey);
456
457 if (jp->buffer)
458 js_FinishStringBuffer(jp->buffer);
459 JS_free(cx, jp->buffer);
460
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);
465
466 return ok;
467 }
468
469 static JSBool
470 PushState(JSONParser *jp, JSONParserState state)
471 {
472 if (*jp->statep == JSON_PARSE_STATE_FINISHED)
473 return JS_FALSE; // extra input
474
475 jp->statep++;
476 if ((uint32)(jp->statep - jp->stateStack) >= JS_ARRAY_LENGTH(jp->stateStack))
477 return JS_FALSE; // too deep
478
479 *jp->statep = state;
480
481 return JS_TRUE;
482 }
483
484 static JSBool
485 PopState(JSONParser *jp)
486 {
487 jp->statep--;
488 if (jp->statep < jp->stateStack) {
489 jp->statep = jp->stateStack;
490 return JS_FALSE;
491 }
492
493 if (*jp->statep == JSON_PARSE_STATE_INIT)
494 *jp->statep = JSON_PARSE_STATE_FINISHED;
495
496 return JS_TRUE;
497 }
498
499 static JSBool
500 PushValue(JSContext *cx, JSONParser *jp, JSObject *parent, jsval value)
501 {
502 JSAutoTempValueRooter tvr(cx, 1, &value);
503
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);
511 }
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);
518 }
519
520 return ok;
521 }
522
523 static JSBool
524 PushObject(JSContext *cx, JSONParser *jp, JSObject *obj)
525 {
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
531
532 jsval v = OBJECT_TO_JSVAL(obj);
533 JSAutoTempValueRooter tvr(cx, v);
534
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;
542 }
543 return JS_TRUE;
544 }
545
546 jsval p;
547 if (!OBJ_GET_PROPERTY(cx, jp->objectStack, INT_TO_JSID(len - 1), &p))
548 return JS_FALSE;
549
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;
554
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;
559 }
560
561 return JS_TRUE;
562 }
563
564 static JSObject *
565 GetTopOfObjectStack(JSContext *cx, JSONParser *jp)
566 {
567 jsuint length;
568 if (!js_GetLengthProperty(cx, jp->objectStack, &length))
569 return NULL;
570
571 jsval o;
572 if (!OBJ_GET_PROPERTY(cx, jp->objectStack, INT_TO_JSID(length - 1), &o))
573 return NULL;
574
575 JS_ASSERT(!JSVAL_IS_PRIMITIVE(o));
576 return JSVAL_TO_OBJECT(o);
577 }
578
579 static JSBool
580 OpenObject(JSContext *cx, JSONParser *jp)
581 {
582 JSObject *obj = js_NewObject(cx, &js_ObjectClass, NULL, NULL, 0);
583 if (!obj)
584 return JS_FALSE;
585
586 return PushObject(cx, jp, obj);
587 }
588
589 static JSBool
590 OpenArray(JSContext *cx, JSONParser *jp)
591 {
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;
596
597 return PushObject(cx, jp, arr);
598 }
599
600 static JSBool
601 CloseObject(JSContext *cx, JSONParser *jp)
602 {
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;
608
609 return JS_TRUE;
610 }
611
612 static JSBool
613 CloseArray(JSContext *cx, JSONParser *jp)
614 {
615 return CloseObject(cx, jp);
616 }
617
618 static JSBool
619 HandleNumber(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len)
620 {
621 const jschar *ep;
622 double val;
623 if (!js_strtod(cx, buf, buf + len, &ep, &val) || ep != buf + len)
624 return JS_FALSE;
625
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
633
634 return ok;
635 }
636
637 static JSBool
638 HandleString(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len)
639 {
640 JSObject *obj = GetTopOfObjectStack(cx, jp);
641 JSString *str = js_NewStringCopyN(cx, buf, len);
642 if (!obj || !str)
643 return JS_FALSE;
644
645 return PushValue(cx, jp, obj, STRING_TO_JSVAL(str));
646 }
647
648 static JSBool
649 HandleKeyword(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len)
650 {
651 jsval keyword;
652 JSTokenType tt = js_CheckKeyword(buf, len);
653 if (tt != TOK_PRIMARY)
654 return JS_FALSE;
655
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;
664
665 JSObject *obj = GetTopOfObjectStack(cx, jp);
666 if (!obj)
667 return JS_FALSE;
668
669 return PushValue(cx, jp, obj, keyword);
670 }
671
672 static JSBool
673 HandleData(JSContext *cx, JSONParser *jp, JSONDataType type)
674 {
675 JSBool ok = JS_FALSE;
676
677 if (!STRING_BUFFER_OK(jp->buffer))
678 return JS_FALSE;
679
680 switch (type) {
681 case JSON_DATA_STRING:
682 ok = HandleString(cx, jp, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer));
683 break;
684
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;
689
690 case JSON_DATA_NUMBER:
691 ok = HandleNumber(cx, jp, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer));
692 break;
693
694 case JSON_DATA_KEYWORD:
695 ok = HandleKeyword(cx, jp, jp->buffer->base, STRING_BUFFER_OFFSET(jp->buffer));
696 break;
697
698 default:
699 JS_NOT_REACHED("Should have a JSON data type");
700 }
701
702 js_FinishStringBuffer(jp->buffer);
703 js_InitStringBuffer(jp->buffer);
704
705 return ok;
706 }
707
708 JSBool
709 js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len)
710 {
711 uint32 i;
712
713 if (*jp->statep == JSON_PARSE_STATE_INIT) {
714 PushState(jp, JSON_PARSE_STATE_OBJECT_VALUE);
715 }
716
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;
730 }
731
732 if (c == '}') {
733 // we should only find these in OBJECT_KEY state
734 return JS_FALSE; // unexpected failure
735 }
736
737 if (c == '"') {
738 *jp->statep = JSON_PARSE_STATE_STRING;
739 break;
740 }
741
742 if (IsNumChar(c)) {
743 *jp->statep = JSON_PARSE_STATE_NUMBER;
744 js_AppendChar(jp->buffer, c);
745 break;
746 }
747
748 if (JS7_ISLET(c)) {
749 *jp->statep = JSON_PARSE_STATE_KEYWORD;
750 js_AppendChar(jp->buffer, c);
751 break;
752 }
753
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
766 }
767 break;
768
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
778 }
779 break;
780
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
790 }
791 break;
792
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
805 }
806 break;
807
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
813 }
814 break;
815
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;
825 }
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);
832 }
833 break;
834
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
854 }
855 }
856
857 js_AppendChar(jp->buffer, c);
858 *jp->statep = JSON_PARSE_STATE_STRING;
859 break;
860
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
870
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;
876 }
877 break;
878
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;
887
888 if (!HandleData(cx, jp, JSON_DATA_KEYWORD))
889 return JS_FALSE;
890 }
891 break;
892
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;
903 }
904 break;
905
906 case JSON_PARSE_STATE_FINISHED:
907 if (!JS_ISXMLSPACE(c))
908 return JS_FALSE; // extra input
909
910 break;
911
912 default:
913 JS_NOT_REACHED("Invalid JSON parser state");
914 }
915 }
916
917 return JS_TRUE;
918 }
919
920 #if JS_HAS_TOSOURCE
921 static JSBool
922 json_toSource(JSContext *cx, uintN argc, jsval *vp)
923 {
924 *vp = ATOM_KEY(CLASS_ATOM(cx, JSON));
925 return JS_TRUE;
926 }
927 #endif
928
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
936 };
937
938 JSObject *
939 js_InitJSONClass(JSContext *cx, JSObject *obj)
940 {
941 JSObject *JSON;
942
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;
949
950 if (!JS_DefineFunctions(cx, JSON, json_static_methods))
951 return NULL;
952
953 return JSON;
954 }

  ViewVC Help
Powered by ViewVC 1.1.24