Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(120)

Side by Side Diff: sdk/lib/json/json.dart

Issue 11783009: Big merge from experimental to bleeding edge. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « sdk/lib/isolate/timer.dart ('k') | sdk/lib/math/base.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 library dart.json; 5 library dart.json;
6 6
7 import 'dart:math';
8
9 // JSON parsing and serialization. 7 // JSON parsing and serialization.
10 8
11 /** 9 /**
12 * Error thrown by JSON serialization if an object cannot be serialized. 10 * Error thrown by JSON serialization if an object cannot be serialized.
13 * 11 *
14 * The [unsupportedObject] field holds that object that failed to be serialized. 12 * The [unsupportedObject] field holds that object that failed to be serialized.
15 * 13 *
16 * If an isn't directly serializable, the serializer calls the 'toJson' method 14 * If an object isn't directly serializable, the serializer calls the 'toJson'
17 * on the object. If that call fails, the error will be stored in the [cause] 15 * method on the object. If that call fails, the error will be stored in the
18 * field. If the call returns an object that isn't directly serializable, 16 * [cause] field. If the call returns an object that isn't directly
19 * the [cause] will be null. 17 * serializable, the [cause] will be null.
20 */ 18 */
21 class JsonUnsupportedObjectError { 19 class JsonUnsupportedObjectError implements Error {
22 // TODO: proper base class.
23 /** The object that could not be serialized. */ 20 /** The object that could not be serialized. */
24 final unsupportedObject; 21 final unsupportedObject;
25 /** The exception thrown by object's [:toJson:] method, if any. */ 22 /** The exception thrown by object's [:toJson:] method, if any. */
26 final cause; 23 final cause;
27 JsonUnsupportedObjectError(this.unsupportedObject) : cause = null; 24 JsonUnsupportedObjectError(this.unsupportedObject) : cause = null;
28 JsonUnsupportedObjectError.withCause(this.unsupportedObject, this.cause); 25 JsonUnsupportedObjectError.withCause(this.unsupportedObject, this.cause);
29 26
30 String toString() { 27 String toString() {
31 if (cause != null) { 28 if (cause != null) {
32 return "Calling toJson method on object failed."; 29 return "Calling toJson method on object failed.";
33 } else { 30 } else {
34 return "Object toJson method returns non-serializable value."; 31 return "Object toJson method returns non-serializable value.";
35 } 32 }
36 } 33 }
37 } 34 }
38 35
39 36
40 /** 37 /**
41 * Utility class to parse JSON and serialize objects to JSON. 38 * Parses [json] and build the corresponding parsed JSON value.
39 *
40 * Parsed JSON values are of the types [num], [String], [bool], [Null],
41 * [List]s of parsed JSON values or [Map]s from [String] to parsed
42 * JSON values.
43 *
44 * Throws [FormatException] if the input is not valid JSON text.
42 */ 45 */
43 class JSON { 46 parse(String json, [reviver(var key, var value)]) {
47 BuildJsonListener listener;
48 if (reviver == null) {
49 listener = new BuildJsonListener();
50 } else {
51 listener = new ReviverJsonListener(reviver);
52 }
53 new JsonParser(json, listener).parse();
54 return listener.result;
55 }
56
57 /**
58 * Serializes [object] into a JSON string.
59 *
60 * Directly serializable types are [num], [String], [bool], [Null], [List]
61 * and [Map].
62 * For [List], the elements must all be serializable.
63 * For [Map], the keys must be [String] and the values must be serializable.
64 * If a value is any other type is attempted serialized, a "toJson()" method
65 * is invoked on the object and the result, which must be a directly
66 * serializable type, is serialized instead of the original value.
67 * If the object does not support this method, throws, or returns a
68 * value that is not directly serializable, a [JsonUnsupportedObjectError]
69 * exception is thrown. If the call throws (including the case where there
70 * is no nullary "toJson" method, the error is caught and stored in the
71 * [JsonUnsupportedObjectError]'s [:cause:] field.
72 *Json
73 * Objects should not change during serialization.
74 * If an object is serialized more than once, [stringify] is allowed to cache
75 * the JSON text for it. I.e., if an object changes after it is first
76 * serialized, the new values may or may not be reflected in the result.
77 */
78 String stringify(Object object) {
79 return _JsonStringifier.stringify(object);
80 }
81
82 /**
83 * Serializes [object] into [output] stream.
84 *
85 * Performs the same operations as [stringify] but outputs the resulting
86 * string to an existing [StringBuffer] instead of creating a new [String].
87 *
88 * If serialization fails by throwing, some data might have been added to
89 * [output], but it won't contain valid JSON text.
90 */
91 void printOn(Object object, StringBuffer output) {
92 return _JsonStringifier.printOn(object, output);
93 }
94
95 //// Implementation ///////////////////////////////////////////////////////////
96
97 // Simple API for JSON parsing.
98
99 abstract class JsonListener {
100 void handleString(String value) {}
101 void handleNumber(num value) {}
102 void handleBool(bool value) {}
103 void handleNull() {}
104 void beginObject() {}
105 void propertyName() {}
106 void propertyValue() {}
107 void endObject() {}
108 void beginArray() {}
109 void arrayElement() {}
110 void endArray() {}
111 /** Called on failure to parse [source]. */
112 void fail(String source, int position, String message) {}
113 }
114
115 /**
116 * A [JsonListener] that builds data objects from the parser events.
117 *
118 * This is a simple stack-based object builder. It keeps the most recently
119 * seen value in a variable, and uses it depending on the following event.
120 */
121 class BuildJsonListener extends JsonListener {
44 /** 122 /**
45 * Parses [json] and build the corresponding parsed JSON value. 123 * Stack used to handle nested containers.
46 * 124 *
47 * Parsed JSON values are of the types [num], [String], [bool], [Null], 125 * The current container is pushed on the stack when a new one is
48 * [List]s of parsed JSON values or [Map]s from [String] to parsed 126 * started. If the container is a [Map], there is also a current [key]
49 * JSON values. 127 * which is also stored on the stack.
128 */
129 List stack = [];
130 /** The current [Map] or [List] being built. */
131 var currentContainer;
132 /** The most recently read property key. */
133 String key;
134 /** The most recently read value. */
135 var value;
136
137 /** Pushes the currently active container (and key, if a [Map]). */
138 void pushContainer() {
139 if (currentContainer is Map) stack.add(key);
140 stack.add(currentContainer);
141 }
142
143 /** Pops the top container from the [stack], including a key if applicable. */
144 void popContainer() {
145 value = currentContainer;
146 currentContainer = stack.removeLast();
147 if (currentContainer is Map) key = stack.removeLast();
148 }
149
150 void handleString(String value) { this.value = value; }
151 void handleNumber(num value) { this.value = value; }
152 void handleBool(bool value) { this.value = value; }
153 void handleNull() { this.value = value; }
154
155 void beginObject() {
156 pushContainer();
157 currentContainer = {};
158 }
159
160 void propertyName() {
161 key = value;
162 value = null;
163 }
164
165 void propertyValue() {
166 Map map = currentContainer;
167 map[key] = value;
168 key = value = null;
169 }
170
171 void endObject() {
172 popContainer();
173 }
174
175 void beginArray() {
176 pushContainer();
177 currentContainer = [];
178 }
179
180 void arrayElement() {
181 List list = currentContainer;
182 currentContainer.add(value);
183 value = null;
184 }
185
186 void endArray() {
187 popContainer();
188 }
189
190 /** Read out the final result of parsing a JSON string. */
191 get result {
192 assert(currentContainer == null);
193 return value;
194 }
195 }
196
197 typedef _Reviver(var key, var value);
198
199 class ReviverJsonListener extends BuildJsonListener {
200 final _Reviver reviver;
201 ReviverJsonListener(reviver(key, value)) : this.reviver = reviver;
202
203 void arrayElement() {
204 List list = currentContainer;
205 value = reviver(list.length, value);
206 super.arrayElement();
207 }
208
209 void propertyValue() {
210 value = reviver(key, value);
211 super.propertyValue();
212 }
213
214 get result {
215 return reviver("", value);
216 }
217 }
218
219 class JsonParser {
220 // A simple non-recursive state-based parser for JSON.
221 //
222 // Literal values accepted in states ARRAY_EMPTY, ARRAY_COMMA, OBJECT_COLON
223 // and strings also in OBJECT_EMPTY, OBJECT_COMMA.
224 // VALUE STRING : , } ] Transitions to
225 // EMPTY X X -> END
226 // ARRAY_EMPTY X X @ -> ARRAY_VALUE / pop
227 // ARRAY_VALUE @ @ -> ARRAY_COMMA / pop
228 // ARRAY_COMMA X X -> ARRAY_VALUE
229 // OBJECT_EMPTY X @ -> OBJECT_KEY / pop
230 // OBJECT_KEY @ -> OBJECT_COLON
231 // OBJECT_COLON X X -> OBJECT_VALUE
232 // OBJECT_VALUE @ @ -> OBJECT_COMMA / pop
233 // OBJECT_COMMA X -> OBJECT_KEY
234 // END
235 // Starting a new array or object will push the current state. The "pop"
236 // above means restoring this state and then marking it as an ended value.
237 // X means generic handling, @ means special handling for just that
238 // state - that is, values are handled generically, only punctuation
239 // cares about the current state.
240 // Values for states are chosen so bits 0 and 1 tell whether
241 // a string/value is allowed, and setting bits 0 through 2 after a value
242 // gets to the next state (not empty, doesn't allow a value).
243
244 // State building-block constants.
245 static const int INSIDE_ARRAY = 1;
246 static const int INSIDE_OBJECT = 2;
247 static const int AFTER_COLON = 3; // Always inside object.
248
249 static const int ALLOW_STRING_MASK = 8; // Allowed if zero.
250 static const int ALLOW_VALUE_MASK = 4; // Allowed if zero.
251 static const int ALLOW_VALUE = 0;
252 static const int STRING_ONLY = 4;
253 static const int NO_VALUES = 12;
254
255 // Objects and arrays are "empty" until their first property/element.
256 static const int EMPTY = 0;
257 static const int NON_EMPTY = 16;
258 static const int EMPTY_MASK = 16; // Empty if zero.
259
260
261 static const int VALUE_READ_BITS = NO_VALUES | NON_EMPTY;
262
263 // Actual states.
264 static const int STATE_INITIAL = EMPTY | ALLOW_VALUE;
265 static const int STATE_END = NON_EMPTY | NO_VALUES;
266
267 static const int STATE_ARRAY_EMPTY = INSIDE_ARRAY | EMPTY | ALLOW_VALUE;
268 static const int STATE_ARRAY_VALUE = INSIDE_ARRAY | NON_EMPTY | NO_VALUES;
269 static const int STATE_ARRAY_COMMA = INSIDE_ARRAY | NON_EMPTY | ALLOW_VALUE;
270
271 static const int STATE_OBJECT_EMPTY = INSIDE_OBJECT | EMPTY | STRING_ONLY;
272 static const int STATE_OBJECT_KEY = INSIDE_OBJECT | NON_EMPTY | NO_VALUES;
273 static const int STATE_OBJECT_COLON = AFTER_COLON | NON_EMPTY | ALLOW_VALUE;
274 static const int STATE_OBJECT_VALUE = AFTER_COLON | NON_EMPTY | NO_VALUES;
275 static const int STATE_OBJECT_COMMA = INSIDE_OBJECT | NON_EMPTY | STRING_ONLY;
276
277 // Character code constants.
278 static const int BACKSPACE = 0x08;
279 static const int TAB = 0x09;
280 static const int NEWLINE = 0x0a;
281 static const int CARRIAGE_RETURN = 0x0d;
282 static const int FORM_FEED = 0x0c;
283 static const int SPACE = 0x20;
284 static const int QUOTE = 0x22;
285 static const int PLUS = 0x2b;
286 static const int COMMA = 0x2c;
287 static const int MINUS = 0x2d;
288 static const int DECIMALPOINT = 0x2e;
289 static const int SLASH = 0x2f;
290 static const int CHAR_0 = 0x30;
291 static const int CHAR_9 = 0x39;
292 static const int COLON = 0x3a;
293 static const int CHAR_E = 0x45;
294 static const int LBRACKET = 0x5b;
295 static const int BACKSLASH = 0x5c;
296 static const int RBRACKET = 0x5d;
297 static const int CHAR_a = 0x61;
298 static const int CHAR_b = 0x62;
299 static const int CHAR_e = 0x65;
300 static const int CHAR_f = 0x66;
301 static const int CHAR_l = 0x6c;
302 static const int CHAR_n = 0x6e;
303 static const int CHAR_r = 0x72;
304 static const int CHAR_s = 0x73;
305 static const int CHAR_t = 0x74;
306 static const int CHAR_u = 0x75;
307 static const int LBRACE = 0x7b;
308 static const int RBRACE = 0x7d;
309
310 final String source;
311 final JsonListener listener;
312 JsonParser(this.source, this.listener);
313
314 /** Parses [source], or throws if it fails. */
315 void parse() {
316 final List<int> states = <int>[];
317 int state = STATE_INITIAL;
318 int position = 0;
319 int length = source.length;
320 while (position < length) {
321 int char = source.charCodeAt(position);
322 switch (char) {
323 case SPACE:
324 case CARRIAGE_RETURN:
325 case NEWLINE:
326 case TAB:
327 position++;
328 break;
329 case QUOTE:
330 if ((state & ALLOW_STRING_MASK) != 0) fail(position);
331 position = parseString(position + 1);
332 state |= VALUE_READ_BITS;
333 break;
334 case LBRACKET:
335 if ((state & ALLOW_VALUE_MASK) != 0) fail(position);
336 listener.beginArray();
337 states.add(state);
338 state = STATE_ARRAY_EMPTY;
339 position++;
340 break;
341 case LBRACE:
342 if ((state & ALLOW_VALUE_MASK) != 0) fail(position);
343 listener.beginObject();
344 states.add(state);
345 state = STATE_OBJECT_EMPTY;
346 position++;
347 break;
348 case CHAR_n:
349 if ((state & ALLOW_VALUE_MASK) != 0) fail(position);
350 position = parseNull(position);
351 state |= VALUE_READ_BITS;
352 break;
353 case CHAR_f:
354 if ((state & ALLOW_VALUE_MASK) != 0) fail(position);
355 position = parseFalse(position);
356 state |= VALUE_READ_BITS;
357 break;
358 case CHAR_t:
359 if ((state & ALLOW_VALUE_MASK) != 0) fail(position);
360 position = parseTrue(position);
361 state |= VALUE_READ_BITS;
362 break;
363 case COLON:
364 if (state != STATE_OBJECT_KEY) fail(position);
365 listener.propertyName();
366 state = STATE_OBJECT_COLON;
367 position++;
368 break;
369 case COMMA:
370 if (state == STATE_OBJECT_VALUE) {
371 listener.propertyValue();
372 state = STATE_OBJECT_COMMA;
373 position++;
374 } else if (state == STATE_ARRAY_VALUE) {
375 listener.arrayElement();
376 state = STATE_ARRAY_COMMA;
377 position++;
378 } else {
379 fail(position);
380 }
381 break;
382 case RBRACKET:
383 if (state == STATE_ARRAY_EMPTY) {
384 listener.endArray();
385 } else if (state == STATE_ARRAY_VALUE) {
386 listener.arrayElement();
387 listener.endArray();
388 } else {
389 fail(position);
390 }
391 state = states.removeLast() | VALUE_READ_BITS;
392 position++;
393 break;
394 case RBRACE:
395 if (state == STATE_OBJECT_EMPTY) {
396 listener.endObject();
397 } else if (state == STATE_OBJECT_VALUE) {
398 listener.propertyValue();
399 listener.endObject();
400 } else {
401 fail(position);
402 }
403 state = states.removeLast() | VALUE_READ_BITS;
404 position++;
405 break;
406 default:
407 if ((state & ALLOW_VALUE_MASK) != 0) fail(position);
408 position = parseNumber(char, position);
409 state |= VALUE_READ_BITS;
410 break;
411 }
412 }
413 if (state != STATE_END) fail(position);
414 }
415
416 /**
417 * Parses a "true" literal starting at [position].
50 * 418 *
51 * Throws [JSONParseException] if the input is not valid JSON text. 419 * [:source[position]:] must be "t".
52 */ 420 */
53 static parse(String json) { 421 int parseTrue(int position) {
54 return _JsonParser.parse(json); 422 assert(source.charCodeAt(position) == CHAR_t);
423 if (source.length < position + 4) fail(position, "Unexpected identifier");
424 if (source.charCodeAt(position + 1) != CHAR_r ||
425 source.charCodeAt(position + 2) != CHAR_u ||
426 source.charCodeAt(position + 3) != CHAR_e) {
427 fail(position);
428 }
429 listener.handleBool(true);
430 return position + 4;
55 } 431 }
56 432
57 /** 433 /**
58 * Serializes [object] into a JSON string. 434 * Parses a "false" literal starting at [position].
59 * 435 *
60 * Directly serializable types are [num], [String], [bool], [Null], [List] 436 * [:source[position]:] must be "f".
61 * and [Map]. 437 */
62 * For [List], the elements must all be serializable. 438 int parseFalse(int position) {
63 * For [Map], the keys must be [String] and the values must be serializable. 439 assert(source.charCodeAt(position) == CHAR_f);
64 * If a value is any other type is attempted serialized, a "toJson()" method 440 if (source.length < position + 5) fail(position, "Unexpected identifier");
65 * is invoked on the object and the result, which must be a directly 441 if (source.charCodeAt(position + 1) != CHAR_a ||
66 * serializable type, is serialized instead of the original value. 442 source.charCodeAt(position + 2) != CHAR_l ||
67 * If the object does not support this method, throws, or returns a 443 source.charCodeAt(position + 3) != CHAR_s ||
68 * value that is not directly serializable, a [JsonUnsupportedObjectError] 444 source.charCodeAt(position + 4) != CHAR_e) {
69 * exception is thrown. If the call throws (including the case where there 445 fail(position);
70 * is no nullary "toJson" method, the error is caught and stored in the 446 }
71 * [JsonUnsupportedObjectError]'s [:cause:] field. 447 listener.handleBool(false);
448 return position + 5;
449 }
450
451 /** Parses a "null" literal starting at [position].
72 * 452 *
73 * Objects should not change during serialization. 453 * [:source[position]:] must be "n".
74 * If an object is serialized more than once, [stringify] is allowed to cache
75 * the JSON text for it. I.e., if an object changes after it is first
76 * serialized, the new values may or may not be reflected in the result.
77 */ 454 */
78 static String stringify(Object object) { 455 int parseNull(int position) {
79 return _JsonStringifier.stringify(object); 456 assert(source.charCodeAt(position) == CHAR_n);
80 } 457 if (source.length < position + 4) fail(position, "Unexpected identifier");
81 458 if (source.charCodeAt(position + 1) != CHAR_u ||
82 /** 459 source.charCodeAt(position + 2) != CHAR_l ||
83 * Serializes [object] into [output] stream. 460 source.charCodeAt(position + 3) != CHAR_l) {
84 * 461 fail(position);
85 * Performs the same operations as [stringify] but outputs the resulting 462 }
86 * string to an existing [StringBuffer] instead of creating a new [String]. 463 listener.handleNull();
87 * 464 return position + 4;
88 * If serialization fails by throwing, some data might have been added to 465 }
89 * [output], but it won't contain valid JSON text. 466
90 */ 467 int parseString(int position) {
91 static void printOn(Object object, StringBuffer output) { 468 // Format: '"'([^\x00-\x1f\\\"]|'\\'[bfnrt/\\"])*'"'
92 return _JsonStringifier.printOn(object, output); 469 // Initial position is right after first '"'.
470 int start = position;
471 int char;
472 do {
473 if (position == source.length) {
474 fail(start - 1, "Unterminated string");
475 }
476 char = source.charCodeAt(position);
477 if (char == QUOTE) {
478 listener.handleString(source.substring(start, position));
479 return position + 1;
480 }
481 if (char < SPACE) {
482 fail(position, "Control character in string");
483 }
484 position++;
485 } while (char != BACKSLASH);
486 // Backslash escape detected. Collect character codes for rest of string.
487 int firstEscape = position - 1;
488 List<int> chars = <int>[];
489 while (true) {
490 if (position == source.length) {
491 fail(start - 1, "Unterminated string");
492 }
493 char = source.charCodeAt(position);
494 switch (char) {
495 case CHAR_b: char = BACKSPACE; break;
496 case CHAR_f: char = FORM_FEED; break;
497 case CHAR_n: char = NEWLINE; break;
498 case CHAR_r: char = CARRIAGE_RETURN; break;
499 case CHAR_t: char = TAB; break;
500 case SLASH:
501 case BACKSLASH:
502 case QUOTE:
503 break;
504 case CHAR_u: {
505 int hexStart = position - 1;
506 int value = 0;
507 for (int i = 0; i < 4; i++) {
508 position++;
509 if (position == source.length) {
510 fail(start - 1, "Unterminated string");
511 }
512 char = source.charCodeAt(position);
513 char -= 0x30;
514 if (char < 0) fail(hexStart, "Invalid unicode escape");
515 if (char < 10) {
516 value = value * 16 + char;
517 } else {
518 char = (char | 0x20) - 0x31;
519 if (char < 0 || char > 5) {
520 fail(hexStart, "Invalid unicode escape");
521 }
522 value = value * 16 + char + 10;
523 }
524 }
525 char = value;
526 break;
527 }
528 default:
529 if (char < SPACE) fail(position, "Control character in string");
530 fail(position, "Unrecognized string escape");
531 }
532 do {
533 chars.add(char);
534 position++;
535 if (position == source.length) fail(start - 1, "Unterminated string");
536 char = source.charCodeAt(position);
537 if (char == QUOTE) {
538 String result = new String.fromCharCodes(chars);
539 if (start < firstEscape) {
540 result = "${source.substring(start, firstEscape)}$result";
541 }
542 listener.handleString(result);
543 return position + 1;
544 }
545 if (char < SPACE) {
546 fail(position, "Control character in string");
547 }
548 } while (char != BACKSLASH);
549 position++;
550 }
551 }
552
553 int parseNumber(int char, int position) {
554 // Format:
555 // '-'?('0'|[1-9][0-9]*)('.'[0-9]+)?([eE][+-]?[0-9]+)?
556 int start = position;
557 int length = source.length;
558 bool isDouble = false;
559 if (char == MINUS) {
560 position++;
561 if (position == length) fail(position, "Missing expected digit");
562 char = source.charCodeAt(position);
563 }
564 if (char < CHAR_0 || char > CHAR_9) {
565 fail(position, "Missing expected digit");
566 }
567 int handleLiteral(position) {
568 String literal = source.substring(start, position);
569 // This correctly creates -0 for doubles.
570 num value = (isDouble ? double.parse(literal) : int.parse(literal));
571 listener.handleNumber(value);
572 return position;
573 }
574 if (char == CHAR_0) {
575 position++;
576 if (position == length) return handleLiteral(position);
577 char = source.charCodeAt(position);
578 if (CHAR_0 <= char && char <= CHAR_9) {
579 fail(position);
580 }
581 } else {
582 do {
583 position++;
584 if (position == length) return handleLiteral(position);
585 char = source.charCodeAt(position);
586 } while (CHAR_0 <= char && char <= CHAR_9);
587 }
588 if (char == DECIMALPOINT) {
589 isDouble = true;
590 position++;
591 if (position == length) fail(position, "Missing expected digit");
592 char = source.charCodeAt(position);
593 if (char < CHAR_0 || char > CHAR_9) fail(position);
594 do {
595 position++;
596 if (position == length) return handleLiteral(position);
597 char = source.charCodeAt(position);
598 } while (CHAR_0 <= char && char <= CHAR_9);
599 }
600 if (char == CHAR_e || char == CHAR_E) {
601 isDouble = true;
602 position++;
603 if (position == length) fail(position, "Missing expected digit");
604 char = source.charCodeAt(position);
605 if (char == PLUS || char == MINUS) {
606 position++;
607 if (position == length) fail(position, "Missing expected digit");
608 char = source.charCodeAt(position);
609 }
610 if (char < CHAR_0 || char > CHAR_9) {
611 fail(position, "Missing expected digit");
612 }
613 do {
614 position++;
615 if (position == length) return handleLiteral(position);
616 char = source.charCodeAt(position);
617 } while (CHAR_0 <= char && char <= CHAR_9);
618 }
619 return handleLiteral(position);
620 }
621
622 void fail(int position, [String message]) {
623 if (message == null) message = "Unexpected character";
624 listener.fail(source, position, message);
625 // If the listener didn't throw, do it here.
626 String slice;
627 int sliceEnd = position + 20;
628 if (sliceEnd > source.length) {
629 slice = "'${source.substring(position)}'";
630 } else {
631 slice = "'${source.substring(position, sliceEnd)}...'";
632 }
633 throw new FormatException("Unexpected character at $position: $slice");
93 } 634 }
94 } 635 }
95 636
96 //// Implementation ///////////////////////////////////////////////////////////
97
98 // TODO(ajohnsen): Introduce when we have a common exception interface for json.
99 class JSONParseException {
100 JSONParseException(int position, String message) :
101 position = position,
102 message = 'JSONParseException: $message, at offset $position';
103
104 String toString() => message;
105
106 final String message;
107 final int position;
108 }
109
110 class _JsonParser {
111 static const int BACKSPACE = 8;
112 static const int TAB = 9;
113 static const int NEW_LINE = 10;
114 static const int FORM_FEED = 12;
115 static const int CARRIAGE_RETURN = 13;
116 static const int SPACE = 32;
117 static const int QUOTE = 34;
118 static const int PLUS = 43;
119 static const int COMMA = 44;
120 static const int MINUS = 45;
121 static const int DOT = 46;
122 static const int SLASH = 47;
123 static const int CHAR_0 = 48;
124 static const int CHAR_1 = 49;
125 static const int CHAR_2 = 50;
126 static const int CHAR_3 = 51;
127 static const int CHAR_4 = 52;
128 static const int CHAR_5 = 53;
129 static const int CHAR_6 = 54;
130 static const int CHAR_7 = 55;
131 static const int CHAR_8 = 56;
132 static const int CHAR_9 = 57;
133 static const int COLON = 58;
134 static const int CHAR_CAPITAL_E = 69;
135 static const int LBRACKET = 91;
136 static const int BACKSLASH = 92;
137 static const int RBRACKET = 93;
138 static const int CHAR_B = 98;
139 static const int CHAR_E = 101;
140 static const int CHAR_F = 102;
141 static const int CHAR_N = 110;
142 static const int CHAR_R = 114;
143 static const int CHAR_T = 116;
144 static const int CHAR_U = 117;
145 static const int LBRACE = 123;
146 static const int RBRACE = 125;
147
148 static const int STRING_LITERAL = QUOTE;
149 static const int NUMBER_LITERAL = MINUS;
150 static const int NULL_LITERAL = CHAR_N;
151 static const int FALSE_LITERAL = CHAR_F;
152 static const int TRUE_LITERAL = CHAR_T;
153
154 static const int WHITESPACE = SPACE;
155
156 static const int LAST_ASCII = RBRACE;
157
158 static const String NULL_STRING = "null";
159 static const String TRUE_STRING = "true";
160 static const String FALSE_STRING = "false";
161
162 static List<int> tokens;
163
164 final String json;
165 final int length;
166 int position = 0;
167
168 static parse(String json) {
169 return new _JsonParser(json).parseToplevel();
170 }
171
172 _JsonParser(String json)
173 : json = json,
174 length = json.length {
175 if (tokens != null) return;
176
177 // Use a list as jump-table. It is faster than switch and if.
178 tokens = new List<int>(LAST_ASCII + 1);
179 tokens[TAB] = WHITESPACE;
180 tokens[NEW_LINE] = WHITESPACE;
181 tokens[CARRIAGE_RETURN] = WHITESPACE;
182 tokens[SPACE] = WHITESPACE;
183 tokens[CHAR_0] = NUMBER_LITERAL;
184 tokens[CHAR_1] = NUMBER_LITERAL;
185 tokens[CHAR_2] = NUMBER_LITERAL;
186 tokens[CHAR_3] = NUMBER_LITERAL;
187 tokens[CHAR_4] = NUMBER_LITERAL;
188 tokens[CHAR_5] = NUMBER_LITERAL;
189 tokens[CHAR_6] = NUMBER_LITERAL;
190 tokens[CHAR_7] = NUMBER_LITERAL;
191 tokens[CHAR_8] = NUMBER_LITERAL;
192 tokens[CHAR_9] = NUMBER_LITERAL;
193 tokens[MINUS] = NUMBER_LITERAL;
194 tokens[LBRACE] = LBRACE;
195 tokens[RBRACE] = RBRACE;
196 tokens[LBRACKET] = LBRACKET;
197 tokens[RBRACKET] = RBRACKET;
198 tokens[QUOTE] = STRING_LITERAL;
199 tokens[COLON] = COLON;
200 tokens[COMMA] = COMMA;
201 tokens[CHAR_N] = NULL_LITERAL;
202 tokens[CHAR_T] = TRUE_LITERAL;
203 tokens[CHAR_F] = FALSE_LITERAL;
204 }
205
206 parseToplevel() {
207 final result = parseValue();
208 if (token() != null) {
209 error('Junk at the end of JSON input');
210 }
211 return result;
212 }
213
214 parseValue() {
215 final int token = token();
216 if (token == null) {
217 error('Nothing to parse');
218 }
219 switch (token) {
220 case STRING_LITERAL: return parseString();
221 case NUMBER_LITERAL: return parseNumber();
222 case NULL_LITERAL: return expectKeyword(NULL_STRING, null);
223 case FALSE_LITERAL: return expectKeyword(FALSE_STRING, false);
224 case TRUE_LITERAL: return expectKeyword(TRUE_STRING, true);
225 case LBRACE: return parseObject();
226 case LBRACKET: return parseList();
227
228 default:
229 error('Unexpected token');
230 }
231 }
232
233 Object expectKeyword(String word, Object value) {
234 for (int i = 0; i < word.length; i++) {
235 // Implicit end check in char().
236 if (char() != word.charCodeAt(i)) error("Expected keyword '$word'");
237 position++;
238 }
239 return value;
240 }
241
242 parseObject() {
243 final object = {};
244
245 position++; // Eat '{'.
246
247 if (!isToken(RBRACE)) {
248 while (true) {
249 final String key = parseString();
250 if (!isToken(COLON)) error("Expected ':' when parsing object");
251 position++;
252 object[key] = parseValue();
253
254 if (!isToken(COMMA)) break;
255 position++; // Skip ','.
256 };
257
258 if (!isToken(RBRACE)) error("Expected '}' at end of object");
259 }
260 position++;
261
262 return object;
263 }
264
265 parseList() {
266 final list = [];
267
268 position++; // Eat '['.
269
270 if (!isToken(RBRACKET)) {
271 while (true) {
272 list.add(parseValue());
273
274 if (!isToken(COMMA)) break;
275 position++;
276 };
277
278 if (!isToken(RBRACKET)) error("Expected ']' at end of list");
279 }
280 position++;
281
282 return list;
283 }
284
285 String parseString() {
286 if (!isToken(STRING_LITERAL)) error("Expected string literal");
287
288 position++; // Eat '"'.
289
290 List<int> charCodes = new List<int>();
291 while (true) {
292 int c = char();
293 if (c == QUOTE) {
294 position++;
295 break;
296 }
297 if (c == BACKSLASH) {
298 position++;
299 if (position == length) {
300 error('\\ at the end of input');
301 }
302
303 switch (char()) {
304 case QUOTE:
305 c = QUOTE;
306 break;
307 case BACKSLASH:
308 c = BACKSLASH;
309 break;
310 case SLASH:
311 c = SLASH;
312 break;
313 case CHAR_B:
314 c = BACKSPACE;
315 break;
316 case CHAR_N:
317 c = NEW_LINE;
318 break;
319 case CHAR_R:
320 c = CARRIAGE_RETURN;
321 break;
322 case CHAR_F:
323 c = FORM_FEED;
324 break;
325 case CHAR_T:
326 c = TAB;
327 break;
328 case CHAR_U:
329 if (position + 5 > length) {
330 error('Invalid unicode esacape sequence');
331 }
332 final codeString = json.substring(position + 1, position + 5);
333 try {
334 c = int.parse('0x${codeString}');
335 } catch (e) {
336 error('Invalid unicode esacape sequence');
337 }
338 position += 4;
339 break;
340 default:
341 error('Invalid esacape sequence in string literal');
342 }
343 }
344 charCodes.add(c);
345 position++;
346 }
347
348 return new String.fromCharCodes(charCodes);
349 }
350
351 num parseNumber() {
352 if (!isToken(NUMBER_LITERAL)) error('Expected number literal');
353
354 final int startPos = position;
355 int char = char();
356 if (identical(char, MINUS)) char = nextChar();
357 if (identical(char, CHAR_0)) {
358 char = nextChar();
359 } else if (isDigit(char)) {
360 char = nextChar();
361 while (isDigit(char)) char = nextChar();
362 } else {
363 error('Expected digit when parsing number');
364 }
365
366 bool isInt = true;
367 if (identical(char, DOT)) {
368 char = nextChar();
369 if (isDigit(char)) {
370 char = nextChar();
371 isInt = false;
372 while (isDigit(char)) char = nextChar();
373 } else {
374 error('Expected digit following comma');
375 }
376 }
377
378 if (identical(char, CHAR_E) || identical(char, CHAR_CAPITAL_E)) {
379 char = nextChar();
380 if (identical(char, MINUS) || identical(char, PLUS)) char = nextChar();
381 if (isDigit(char)) {
382 char = nextChar();
383 isInt = false;
384 while (isDigit(char)) char = nextChar();
385 } else {
386 error('Expected digit following \'e\' or \'E\'');
387 }
388 }
389
390 String number = json.substring(startPos, position);
391 if (isInt) {
392 return int.parse(number);
393 } else {
394 return double.parse(number);
395 }
396 }
397
398 bool isChar(int char) {
399 if (position >= length) return false;
400 return json.charCodeAt(position) == char;
401 }
402
403 bool isDigit(int char) {
404 return char >= CHAR_0 && char <= CHAR_9;
405 }
406
407 bool isToken(int tokenKind) => token() == tokenKind;
408
409 int char() {
410 if (position >= length) {
411 error('Unexpected end of JSON stream');
412 }
413 return json.charCodeAt(position);
414 }
415
416 int nextChar() {
417 position++;
418 if (position >= length) return 0;
419 return json.charCodeAt(position);
420 }
421
422 int token() {
423 while (true) {
424 if (position >= length) return null;
425 int char = json.charCodeAt(position);
426 int token = tokens[char];
427 if (identical(token, WHITESPACE)) {
428 position++;
429 continue;
430 }
431 if (token == null) return 0;
432 return token;
433 }
434 }
435
436 void error(String message) {
437 throw message;
438 }
439 }
440 637
441 class _JsonStringifier { 638 class _JsonStringifier {
442 StringBuffer sb; 639 StringBuffer sb;
443 List<Object> seen; // TODO: that should be identity set. 640 List<Object> seen; // TODO: that should be identity set.
444 641
445 _JsonStringifier(this.sb) : seen = []; 642 _JsonStringifier(this.sb) : seen = [];
446 643
447 static String stringify(final object) { 644 static String stringify(final object) {
448 StringBuffer output = new StringBuffer(); 645 StringBuffer output = new StringBuffer();
449 _JsonStringifier stringifier = new _JsonStringifier(output); 646 _JsonStringifier stringifier = new _JsonStringifier(output);
(...skipping 14 matching lines...) Expand all
464 static int hexDigit(int x) => x < 10 ? 48 + x : 87 + x; 661 static int hexDigit(int x) => x < 10 ? 48 + x : 87 + x;
465 662
466 static void escape(StringBuffer sb, String s) { 663 static void escape(StringBuffer sb, String s) {
467 final int length = s.length; 664 final int length = s.length;
468 bool needsEscape = false; 665 bool needsEscape = false;
469 final charCodes = new List<int>(); 666 final charCodes = new List<int>();
470 for (int i = 0; i < length; i++) { 667 for (int i = 0; i < length; i++) {
471 int charCode = s.charCodeAt(i); 668 int charCode = s.charCodeAt(i);
472 if (charCode < 32) { 669 if (charCode < 32) {
473 needsEscape = true; 670 needsEscape = true;
474 charCodes.add(_JsonParser.BACKSLASH); 671 charCodes.add(JsonParser.BACKSLASH);
475 switch (charCode) { 672 switch (charCode) {
476 case _JsonParser.BACKSPACE: 673 case JsonParser.BACKSPACE:
477 charCodes.add(_JsonParser.CHAR_B); 674 charCodes.add(JsonParser.CHAR_b);
478 break; 675 break;
479 case _JsonParser.TAB: 676 case JsonParser.TAB:
480 charCodes.add(_JsonParser.CHAR_T); 677 charCodes.add(JsonParser.CHAR_t);
481 break; 678 break;
482 case _JsonParser.NEW_LINE: 679 case JsonParser.NEWLINE:
483 charCodes.add(_JsonParser.CHAR_N); 680 charCodes.add(JsonParser.CHAR_n);
484 break; 681 break;
485 case _JsonParser.FORM_FEED: 682 case JsonParser.FORM_FEED:
486 charCodes.add(_JsonParser.CHAR_F); 683 charCodes.add(JsonParser.CHAR_f);
487 break; 684 break;
488 case _JsonParser.CARRIAGE_RETURN: 685 case JsonParser.CARRIAGE_RETURN:
489 charCodes.add(_JsonParser.CHAR_R); 686 charCodes.add(JsonParser.CHAR_r);
490 break; 687 break;
491 default: 688 default:
492 charCodes.add(_JsonParser.CHAR_U); 689 charCodes.add(JsonParser.CHAR_u);
493 charCodes.add(hexDigit((charCode >> 12) & 0xf)); 690 charCodes.add(hexDigit((charCode >> 12) & 0xf));
494 charCodes.add(hexDigit((charCode >> 8) & 0xf)); 691 charCodes.add(hexDigit((charCode >> 8) & 0xf));
495 charCodes.add(hexDigit((charCode >> 4) & 0xf)); 692 charCodes.add(hexDigit((charCode >> 4) & 0xf));
496 charCodes.add(hexDigit(charCode & 0xf)); 693 charCodes.add(hexDigit(charCode & 0xf));
497 break; 694 break;
498 } 695 }
499 } else if (charCode == _JsonParser.QUOTE || 696 } else if (charCode == JsonParser.QUOTE ||
500 charCode == _JsonParser.BACKSLASH) { 697 charCode == JsonParser.BACKSLASH) {
501 needsEscape = true; 698 needsEscape = true;
502 charCodes.add(_JsonParser.BACKSLASH); 699 charCodes.add(JsonParser.BACKSLASH);
503 charCodes.add(charCode); 700 charCodes.add(charCode);
504 } else { 701 } else {
505 charCodes.add(charCode); 702 charCodes.add(charCode);
506 } 703 }
507 } 704 }
508 sb.add(needsEscape ? new String.fromCharCodes(charCodes) : s); 705 sb.add(needsEscape ? new String.fromCharCodes(charCodes) : s);
509 } 706 }
510 707
511 void checkCycle(final object) { 708 void checkCycle(final object) {
512 // TODO: use Iterables. 709 // TODO: use Iterables.
(...skipping 80 matching lines...) Expand 10 before | Expand all | Expand 10 after
593 first = false; 790 first = false;
594 }); 791 });
595 sb.add('}'); 792 sb.add('}');
596 seen.removeLast(); 793 seen.removeLast();
597 return true; 794 return true;
598 } else { 795 } else {
599 return false; 796 return false;
600 } 797 }
601 } 798 }
602 } 799 }
OLDNEW
« no previous file with comments | « sdk/lib/isolate/timer.dart ('k') | sdk/lib/math/base.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698