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

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

Issue 10230004: Optimize the JSON parser by tokenizing while parsing and using jump-table. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Pull out a few more constants. Created 8 years, 7 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 | « no previous file | tests/utils/src/JsonTest.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) 2011, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2011, 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("json"); 5 #library("json");
6 6
7 // Pure Dart implementation of JSON protocol. 7 // Pure Dart implementation of JSON protocol.
8 8
9 /** 9 /**
10 * Utility class to parse JSON and serialize objects to JSON. 10 * Utility class to parse JSON and serialize objects to JSON.
11 */ 11 */
12 class JSON { 12 class JSON {
13 /** 13 /**
14 * Parses [:json:] and build the corresponding object. 14 * Parses [:json:] and build the corresponding object.
15 */ 15 */
16 static parse(String json) { 16 static parse(String json) {
17 return JsonParser.parse(json); 17 return _JsonParser.parse(json);
18 } 18 }
19 19
20 /** 20 /**
21 * Serializes [:object:] into JSON string. 21 * Serializes [:object:] into JSON string.
22 */ 22 */
23 static String stringify(Object object) { 23 static String stringify(Object object) {
24 return JsonStringifier.stringify(object); 24 return JsonStringifier.stringify(object);
25 } 25 }
26 } 26 }
27 27
28 //// Implementation /////////////////////////////////////////////////////////// 28 //// Implementation ///////////////////////////////////////////////////////////
29 29
30 /** 30 class JSONParseException {
31 * Union-like class for JSON tokens. 31 JSONParseException(int position, String message) :
32 */ 32 position = position,
33 class JsonToken { 33 message = 'JSONParseException: $message, at offset $position';
34 static final int STRING = 0; 34
35 static final int NUMBER = 1; 35 String toString() => message;
36 static final int NULL = 2; 36
37 static final int FALSE = 3; 37 final String message;
38 static final int TRUE = 4; 38 final int position;
39 static final int RBRACKET = 5;
40 static final int LBRACKET = 6;
41 static final int RBRACE = 7;
42 static final int LBRACE = 8;
43 static final int COLON = 9;
44 static final int COMMA = 10;
45
46 final int kind;
47 final String _s;
48 final num _n;
49
50 String get str() {
51 assert(kind == STRING);
52 return _s;
53 }
54
55 num get number() {
56 assert(kind == NUMBER);
57 return _n;
58 }
59
60 const JsonToken._internal(this.kind, this._s, this._n);
61
62 factory JsonToken.string(String s) {
63 return new JsonToken._internal(STRING, s, 0);
64 }
65 factory JsonToken.number(num n) {
66 return new JsonToken._internal(NUMBER, '', n);
67 }
68 factory JsonToken.atom(int kind) {
69 return new JsonToken._internal(kind, '', 0);
70 }
71
72 String toString() {
73 switch (kind) {
74 case STRING:
75 return 'STRING(${str})';
76
77 case NUMBER:
78 return 'NUMBER(${number})';
79
80 case NULL:
81 return 'ATOM(null)';
82
83 case FALSE:
84 return 'ATOM(false)';
85
86 case TRUE:
87 return 'ATOM(true)';
88
89 case RBRACKET:
90 return 'ATOM(])';
91
92 case LBRACKET:
93 return 'ATOM([)';
94
95 case RBRACE:
96 return 'ATOM(})';
97
98 case LBRACE:
99 return 'ATOM({)';
100
101 case COLON:
102 return 'ATOM(:)';
103
104 case COMMA:
105 return 'ATOM(,)';
106 }
107 }
108 } 39 }
109 40
110 typedef bool Predicate(int c); 41 class _JsonParser {
111 42 static final int BACKSPACE = 8;
112 class JsonTokenizer { 43 static final int TAB = 9;
113 static final int BACKSPACE = 8; // '\b'.charCodeAt(0) 44 static final int NEW_LINE = 10;
114 static final int TAB = 9; // '\t'.charCodeAt(0) 45 static final int FORM_FEED = 12;
115 static final int NEW_LINE = 10; // '\n'.charCodeAt(0) 46 static final int CARRIAGE_RETURN = 13;
116 static final int FORM_FEED = 12; // '\f'.charCodeAt(0) 47 static final int SPACE = 32;
117 static final int CARRIAGE_RETURN = 13; // '\r'.charCodeAt(0) 48 static final int QUOTE = 34;
118 static final int SPACE = 32; // ' '.charCodeAt(0) 49 static final int PLUS = 43;
119 static final int QUOTE = 34; // '"'.charCodeAt(0) 50 static final int COMMA = 44;
120 static final int PLUS = 43; // '+'.charCodeAt(0) 51 static final int MINUS = 45;
121 static final int COMMA = 44; // ','.charCodeAt(0) 52 static final int DOT = 46;
122 static final int MINUS = 45; // '-'.charCodeAt(0) 53 static final int SLASH = 47;
123 static final int DOT = 46; // '.'.charCodeAt(0) 54 static final int CHAR_0 = 48;
124 static final int SLASH = 47; // '/'.charCodeAt(0) 55 static final int CHAR_1 = 49;
125 static final int ZERO = 48; // '0'.charCodeAt(0) 56 static final int CHAR_2 = 50;
126 static final int NINE = 57; // '9'.charCodeAt(0) 57 static final int CHAR_3 = 51;
127 static final int COLON = 58; // ':'.charCodeAt(0) 58 static final int CHAR_4 = 52;
128 static final int A_BIG = 65; // 'A'.charCodeAt(0) 59 static final int CHAR_5 = 53;
129 static final int E_BIG = 69; // 'E'.charCodeAt(0) 60 static final int CHAR_6 = 54;
130 static final int Z_BIG = 90; // 'Z'.charCodeAt(0) 61 static final int CHAR_7 = 55;
131 static final int LBRACKET = 91; // '['.charCodeAt(0) 62 static final int CHAR_8 = 56;
132 static final int BACKSLASH = 92; // '\\'.charCodeAt(0) 63 static final int CHAR_9 = 57;
133 static final int RBRACKET = 93; // ']'.charCodeAt(0) 64 static final int COLON = 58;
134 static final int A_SMALL = 97; // 'a'.charCodeAt(0) 65 static final int CHAR_CAPITAL_E = 69;
135 static final int B_SMALL = 98; // 'b'.charCodeAt(0) 66 static final int LBRACKET = 91;
136 static final int E_SMALL = 101; // 'e'.charCodeAt(0) 67 static final int BACKSLASH = 92;
137 static final int F_SMALL = 102; // 'f'.charCodeAt(0) 68 static final int RBRACKET = 93;
138 static final int N_SMALL = 110; // 'n'.charCodeAt(0) 69 static final int CHAR_B = 98;
139 static final int R_SMALL = 114; // 'r'.charCodeAt(0) 70 static final int CHAR_E = 101;
140 static final int T_SMALL = 116; // 't'.charCodeAt(0) 71 static final int CHAR_F = 102;
141 static final int U_SMALL = 117; // 'u'.charCodeAt(0) 72 static final int CHAR_N = 110;
142 static final int Z_SMALL = 122; // 'z'.charCodeAt(0) 73 static final int CHAR_R = 114;
143 static final int LBRACE = 123; // '{'.charCodeAt(0) 74 static final int CHAR_T = 116;
144 static final int RBRACE = 125; // '}'.charCodeAt(0) 75 static final int CHAR_U = 117;
145 76 static final int LBRACE = 123;
146 JsonTokenizer(String s) : _s = '${s} ', _pos = 0, _len = s.length + 1; 77 static final int RBRACE = 125;
147 78
148 /** 79 static final int STRING_LITERAL = QUOTE;
149 * Fetches next token or [:null:] if the stream has been exhausted. 80 static final int NUMBER_LITERAL = MINUS;
150 */ 81 static final int NULL_LITERAL = CHAR_N;
151 JsonToken next() { 82 static final int FALSE_LITERAL = CHAR_F;
152 while (_pos < _len && isWhitespace(_s.charCodeAt(_pos))) { 83 static final int TRUE_LITERAL = CHAR_T;
153 _pos++; 84
154 } 85 static final int WHITESPACE = SPACE;
155 if (_pos == _len) { 86
156 return null; 87 static final int LAST_ASCII = RBRACE;
157 } 88
158 89 static final String NULL_STRING = "null";
159 final int cur = _s.charCodeAt(_pos); 90 static final String TRUE_STRING = "true";
160 switch (true) { 91 static final String FALSE_STRING = "false";
161 case cur == QUOTE: 92
162 _pos++; 93
163 List<int> charCodes = new List<int>(); 94 static parse(String json) {
164 while (_pos < _len) { 95 return new _JsonParser._internal(json)._parseToplevel();
165 int c = _s.charCodeAt(_pos); 96 }
166 if (c == QUOTE) { 97
167 break; 98 _JsonParser._internal(String this.json) {
168 } 99 if (tokens !== null) return;
169 if (c == BACKSLASH) { 100
170 _pos++; 101 // Use a list as jump-table, faster then switch and if.
171 if (_pos == _len) { 102 tokens = new List<int>(LAST_ASCII + 1);
172 throw '\\ at the end'; 103 tokens[TAB] = WHITESPACE;
173 } 104 tokens[NEW_LINE] = WHITESPACE;
174 105 tokens[CARRIAGE_RETURN] = WHITESPACE;
175 switch (_s[_pos]) { 106 tokens[SPACE] = WHITESPACE;
176 case '"': 107 tokens[CHAR_0] = NUMBER_LITERAL;
177 c = QUOTE; 108 tokens[CHAR_1] = NUMBER_LITERAL;
178 break; 109 tokens[CHAR_2] = NUMBER_LITERAL;
179 case '\\': 110 tokens[CHAR_3] = NUMBER_LITERAL;
180 c = BACKSLASH; 111 tokens[CHAR_4] = NUMBER_LITERAL;
181 break; 112 tokens[CHAR_5] = NUMBER_LITERAL;
182 case '/': 113 tokens[CHAR_6] = NUMBER_LITERAL;
183 c = SLASH; 114 tokens[CHAR_7] = NUMBER_LITERAL;
184 break; 115 tokens[CHAR_8] = NUMBER_LITERAL;
185 case 'b': 116 tokens[CHAR_9] = NUMBER_LITERAL;
186 c = BACKSPACE; 117 tokens[MINUS] = NUMBER_LITERAL;
187 break; 118 tokens[LBRACE] = LBRACE;
188 case 'n': 119 tokens[RBRACE] = RBRACE;
189 c = NEW_LINE; 120 tokens[LBRACKET] = LBRACKET;
190 break; 121 tokens[RBRACKET] = RBRACKET;
191 case 'r': 122 tokens[QUOTE] = STRING_LITERAL;
192 c = CARRIAGE_RETURN; 123 tokens[COLON] = COLON;
193 break; 124 tokens[COMMA] = COMMA;
194 case 'f': 125 tokens[CHAR_N] = NULL_LITERAL;
195 c = FORM_FEED; 126 tokens[CHAR_T] = TRUE_LITERAL;
196 break; 127 tokens[CHAR_F] = FALSE_LITERAL;
197 case 't': 128 }
198 c = TAB; 129
199 break; 130 _parseToplevel() {
200 case 'u': 131 final result = _parseValue();
201 if (_pos + 5 > _len) { 132 if (_token() !== null) {
202 throw 'Invalid unicode esacape sequence:' 133 _error('Junk at the end of JSON input');
203 '\\${_s.substring(_pos, _len)}'; 134 }
204 } 135 return result;
205 final codeString = _s.substring(_pos + 1, _pos + 5); 136 }
206 c = Math.parseInt('0x${codeString}'); 137
207 if (c >= 128) { 138 _parseValue() {
208 // TODO(jmessery): the VM doesn't support 2-byte strings yet 139 final int token = _token();
209 // see runtime/lib/string.cc:49 140 if (token === null) {
210 // So instead we replace these characters with '?' 141 _error('Nothing to parse');
211 c = '?'.charCodeAt(0); 142 }
212 } 143 switch (token) {
213 _pos += 4; 144 case STRING_LITERAL: return _parseString();
214 break; 145 case NUMBER_LITERAL: return _parseNumber();
215 default: 146 case NULL_LITERAL: return _expectKeyword(NULL_STRING, null);
216 throw 'Invalid esacape sequence: \\${_s[_pos]}'; 147 case FALSE_LITERAL: return _expectKeyword(FALSE_STRING, false);
217 } 148 case TRUE_LITERAL: return _expectKeyword(TRUE_STRING, true);
218 } 149 case LBRACE: return _parseObject();
219 charCodes.add(c); 150 case LBRACKET: return _parseList();
220 _pos++;
221 }
222 if (_pos == _len) {
223 throw 'Unmatched quote';
224 }
225
226 final String body = new String.fromCharCodes(charCodes);
227 _pos++;
228 return new JsonToken.string(body);
229
230 case cur == MINUS || isDigit(cur):
231 skipDigits() {
232 _scanWhile((int c) => isDigit(c), 'Invalid number');
233 }
234
235 int c = cur;
236 final int startPos = _pos;
237 int value = 0;
238 bool isNegative = false;
239 if (c == MINUS) {
240 isNegative = true;
241 _pos++;
242 c = _s.charCodeAt(_pos);
243 }
244 while (isDigit(c)) {
245 value = value * 10 + c - ZERO;
246 _pos++;
247 c = _s.charCodeAt(_pos);
248 }
249
250 if (c != DOT) {
251 if (c != E_SMALL && cur != E_BIG) {
252 if (isNegative) value = -value;
253 return new JsonToken.number(value);
254 }
255 } else {
256 _pos++;
257 skipDigits();
258 c = _s.charCodeAt(_pos);
259 }
260
261 if (c == E_SMALL || c == E_BIG) {
262 // TODO: consider keeping E+ as an integer.
263 _pos++;
264 c = _s.charCodeAt(_pos);
265 if (c == PLUS || c == MINUS) {
266 _pos++;
267 }
268 skipDigits();
269 }
270
271 final String body = _s.substring(startPos, _pos);
272 return new JsonToken.number(Math.parseDouble(body));
273
274 case cur == LBRACE:
275 _pos++;
276 return new JsonToken.atom(JsonToken.LBRACE);
277
278 case cur == RBRACE:
279 _pos++;
280 return new JsonToken.atom(JsonToken.RBRACE);
281
282 case cur == LBRACKET:
283 _pos++;
284 return new JsonToken.atom(JsonToken.LBRACKET);
285
286 case cur == RBRACKET:
287 _pos++;
288 return new JsonToken.atom(JsonToken.RBRACKET);
289
290 case cur == COMMA:
291 _pos++;
292 return new JsonToken.atom(JsonToken.COMMA);
293
294 case cur == COLON:
295 _pos++;
296 return new JsonToken.atom(JsonToken.COLON);
297
298 case isLetter(cur):
299 final int startPos = _pos;
300 _pos++;
301 while (_pos < _len && isLetter(_s.charCodeAt(_pos))) {
302 _pos++;
303 }
304 final String body = _s.substring(startPos, _pos);
305 switch (body) {
306 case 'null':
307 return new JsonToken.atom(JsonToken.NULL);
308
309 case 'false':
310 return new JsonToken.atom(JsonToken.FALSE);
311
312 case 'true':
313 return new JsonToken.atom(JsonToken.TRUE);
314
315 default:
316 throw 'Unexpected sequence ${body}';
317 }
318 // TODO: Bogous, to please DartVM.
319 return null;
320 151
321 default: 152 default:
322 throw 'Invalid token'; 153 _error('Unexpected token');
323 } 154 }
324 } 155 }
325 156
326 final String _s; 157 Object _expectKeyword(String word, Object value) {
327 int _pos; 158 for (int i = 0; i < word.length; i++) {
328 final int _len; 159 // Implicit end check in _char().
329 160 if (_char() != word.charCodeAt(i)) _error("Expected keyword '$word'");
330 void _scanWhile(Predicate predicate, String errorMsg) { 161 position++;
331 while (_pos < _len && predicate(_s.charCodeAt(_pos))) { 162 }
332 _pos++; 163 return value;
333 }
334 if (_pos == _len) {
335 throw errorMsg;
336 }
337 }
338
339 // TODO other kind of whitespace.
340 static bool isWhitespace(int c) {
341 return c == SPACE || c == TAB || c == NEW_LINE || c == CARRIAGE_RETURN;
342 }
343 static bool isDigit(int c) {
344 return (ZERO <= c) && (c <= NINE);
345 }
346 static bool isLetter(int c) {
347 return ((A_SMALL <= c) && (c <= Z_SMALL)) || ((A_BIG <= c) && (c <= Z_BIG)) ;
348 }
349 }
350
351 class JsonParser {
352 static parse(String json) {
353 return new JsonParser._internal(json)._parseToplevel();
354 }
355
356 final JsonTokenizer _tokenizer;
357
358 JsonParser._internal(String json) : _tokenizer = new JsonTokenizer(json) {}
359
360 _parseToplevel() {
361 JsonToken token = _tokenizer.next();
362 final result = _parseValue(token);
363 token = _tokenizer.next();
364 if (token !== null) {
365 throw 'Junk at the end';
366 }
367 return result;
368 }
369
370 _parseValue(final JsonToken token) {
371 if (token === null) {
372 throw 'Nothing to parse';
373 }
374 switch (token.kind) {
375 case JsonToken.STRING:
376 return token.str;
377
378 case JsonToken.NUMBER:
379 return token.number;
380
381 case JsonToken.NULL:
382 return null;
383
384 case JsonToken.FALSE:
385 return false;
386
387 case JsonToken.TRUE:
388 return true;
389
390 case JsonToken.LBRACE:
391 return _parseObject();
392
393 case JsonToken.LBRACKET:
394 return _parseList();
395
396 default:
397 throw 'Unexpected token: ${token}';
398 }
399 } 164 }
400 165
401 _parseObject() { 166 _parseObject() {
402 final object = {}; 167 final object = {};
403 168
404 _parseSequence(JsonToken.RBRACE, (JsonToken token) { 169 position++; // Eat '{'.
405 _assertTokenKind(token, JsonToken.STRING); 170
406 final String key = token.str; 171 if (!_isToken(RBRACE)) {
407 172 while (true) {
408 token = _tokenizer.next(); 173 final String key = _parseString();
409 _assertTokenKind(token, JsonToken.COLON); 174 if (!_isToken(COLON)) _error("Expected ':' when parsing object");
410 175 position++;
411 token = _tokenizer.next(); 176 object[key] = _parseValue();
412 final value = _parseValue(token); 177
413 178 if (!_isToken(COMMA)) break;
414 object[key] = value; 179 position++; // Skip ','.
415 }); 180 };
181
182 if (!_isToken(RBRACE)) _error("Expected '}' at end of object");
183 }
184 position++;
416 185
417 return object; 186 return object;
418 } 187 }
419 188
420 _parseList() { 189 _parseList() {
421 final list = []; 190 final list = [];
422 191
423 _parseSequence(JsonToken.RBRACKET, (JsonToken token) { 192 position++; // Eat '['.
424 final value = _parseValue(token); 193
425 list.add(value); 194 if (!_isToken(RBRACKET)) {
426 }); 195 while (true) {
196 list.add(_parseValue());
197
198 if (!_isToken(COMMA)) break;
199 position++;
200 };
201
202 if (!_isToken(RBRACKET)) _error("Expected ']' at end of list");
203 }
204 position++;
427 205
428 return list; 206 return list;
429 } 207 }
430 208
431 void _parseSequence(int endTokenKind, void parseElement(JsonToken token)) { 209 String _parseString() {
432 JsonToken token = _tokenizer.next(); 210 if (!_isToken(STRING_LITERAL)) _error("Expected string literal");
433 if (token === null) { 211
434 throw 'Unexpected end of stream'; 212 position++; // Eat '"'.
435 } 213
436 if (token.kind == endTokenKind) { 214 List<int> charCodes = new List<int>();
437 return; 215 while (true) {
438 } 216 int c = _char();
439 217 if (c == QUOTE) {
440 parseElement(token); 218 position++;
441 219 break;
442 token = _tokenizer.next(); 220 }
443 if (token === null) { 221 if (c == BACKSLASH) {
444 throw 'Expected either comma or terminator'; 222 position++;
445 } 223 if (position == json.length) {
446 while (token.kind != endTokenKind) { 224 _error('\\ at the end of input');
447 _assertTokenKind(token, JsonToken.COMMA); 225 }
448 226
449 token = _tokenizer.next(); 227 switch (_char()) {
450 parseElement(token); 228 case QUOTE:
451 229 c = QUOTE;
452 token = _tokenizer.next(); 230 break;
453 } 231 case BACKSLASH:
454 } 232 c = BACKSLASH;
455 233 break;
456 void _assertTokenKind(JsonToken token, int kind) { 234 case SLASH:
457 if (token === null || token.kind != kind) { 235 c = SLASH;
458 throw 'Unexpected token kind: token = ${token}, expected kind = ${kind}'; 236 break;
459 } 237 case CHAR_B:
460 } 238 c = BACKSPACE;
461 239 break;
462 // TODO: consider factor out error throwing code and build more complicated 240 case CHAR_N:
463 // data structure to provide more info for a caller. 241 c = NEW_LINE;
242 break;
243 case CHAR_R:
244 c = CARRIAGE_RETURN;
245 break;
246 case CHAR_F:
247 c = FORM_FEED;
248 break;
249 case CHAR_T:
250 c = TAB;
251 break;
252 case CHAR_U:
253 if (position + 5 > json.length) {
254 _error('Invalid unicode esacape sequence');
255 }
256 final codeString = json.substring(position + 1, position + 5);
257 try {
258 c = Math.parseInt('0x${codeString}');
259 } catch (var e) {
260 _error('Invalid unicode esacape sequence');
261 }
262 position += 4;
263 break;
264 default:
265 _error('Invalid esacape sequence in string literal');
266 }
267 }
268 charCodes.add(c);
269 position++;
270 }
271
272 return new String.fromCharCodes(charCodes);
273 }
274
275 num _parseNumber() {
276 if (!_isToken(NUMBER_LITERAL)) _error("Expected number literal");
277
278 final int startPos = position;
279 if (_isChar(MINUS)) position++;
280 if (_isChar(CHAR_0)) {
281 position++;
282 } else if (_isDigit()) {
283 position++;
284 while (_isDigit()) position++;
285 }
Lasse Reichstein Nielsen 2012/04/27 07:26:36 You need to bail out here if the first character a
Anders Johnsen 2012/04/27 07:32:33 Ah yes, I forgot to finish this if...else statemen
286
287 bool isInt = true;
288 if (_isChar(DOT)) {
289 position++;
290 if (_isDigit()) {
291 isInt = false;
292 while (_isDigit()) position++;
293 } else {
294 position--; // No digit, backtrack.
295 }
296 }
297
298 if (_isChar(CHAR_E) || _isChar(CHAR_CAPITAL_E)) {
299 int backtrackTo = position;
300 position++;
301 if (_isChar(MINUS) || _isChar(PLUS)) position++;
302 if (_isDigit()) {
303 position++;
304 isInt = false;
305 while (_isDigit()) position++;
306 } else {
307 position = backtrackTo; // No digit, backtrack.
308 }
309 }
310
311 String number = json.substring(startPos, position);
312 if (isInt) {
313 return Math.parseInt(number);
314 } else {
315 return Math.parseDouble(number);
316 }
317 }
318
319 bool _isChar(int char) => _char() == char;
320
321 bool _isDigit() {
322 int char = _char();
323 return char >= CHAR_0 && char <= CHAR_9;
324 }
325
326 bool _isToken(int tokenKind) => _token() == tokenKind;
327
328 int _char() {
329 if (position >= json.length) {
330 _error("Unexpected end of JSON stream");
331 }
332 return json.charCodeAt(position);
333 }
334
335 int _token() {
336 while (true) {
337 if (position >= json.length) return null;
338 int char = json.charCodeAt(position);
339 int token = tokens[char];
340 if (token === WHITESPACE) {
341 position++;
342 continue;
343 }
344 if (token === null) _error("Invalid JSON token");
345 return token;
346 }
347 }
348
349 void _error(String message) {
350 throw new JSONParseException(position, message);
351 }
352
353 final String json;
354 int position = 0;
355 static List<int> tokens;
464 } 356 }
465 357
466 // TODO: proper base class. 358 // TODO: proper base class.
467 class JsonUnsupportedObjectType { 359 class JsonUnsupportedObjectType {
468 const JsonUnsupportedObjectType(); 360 const JsonUnsupportedObjectType();
469 } 361 }
470 362
471 class JsonStringifier { 363 class JsonStringifier {
472 static String stringify(final object) { 364 static String stringify(final object) {
473 JsonStringifier stringifier = new JsonStringifier._internal(); 365 JsonStringifier stringifier = new JsonStringifier._internal();
(...skipping 26 matching lines...) Expand all
500 static int _hexDigit(int x) => x < 10 ? 48 + x : 87 + x; 392 static int _hexDigit(int x) => x < 10 ? 48 + x : 87 + x;
501 393
502 static void _escape(StringBuffer sb, String s) { 394 static void _escape(StringBuffer sb, String s) {
503 final int length = s.length; 395 final int length = s.length;
504 bool needsEscape = false; 396 bool needsEscape = false;
505 final charCodes = new List<int>(); 397 final charCodes = new List<int>();
506 for (int i = 0; i < length; i++) { 398 for (int i = 0; i < length; i++) {
507 int charCode = s.charCodeAt(i); 399 int charCode = s.charCodeAt(i);
508 if (charCode < 32) { 400 if (charCode < 32) {
509 needsEscape = true; 401 needsEscape = true;
510 charCodes.add(JsonTokenizer.BACKSLASH); 402 charCodes.add(_JsonParser.BACKSLASH);
511 switch (charCode) { 403 switch (charCode) {
512 case JsonTokenizer.BACKSPACE: 404 case _JsonParser.BACKSPACE:
513 charCodes.add(JsonTokenizer.B_SMALL); 405 charCodes.add(_JsonParser.CHAR_B);
514 break; 406 break;
515 case JsonTokenizer.TAB: 407 case _JsonParser.TAB:
516 charCodes.add(JsonTokenizer.T_SMALL); 408 charCodes.add(_JsonParser.CHAR_T);
517 break; 409 break;
518 case JsonTokenizer.NEW_LINE: 410 case _JsonParser.NEW_LINE:
519 charCodes.add(JsonTokenizer.N_SMALL); 411 charCodes.add(_JsonParser.CHAR_N);
520 break; 412 break;
521 case JsonTokenizer.FORM_FEED: 413 case _JsonParser.FORM_FEED:
522 charCodes.add(JsonTokenizer.F_SMALL); 414 charCodes.add(_JsonParser.CHAR_F);
523 break; 415 break;
524 case JsonTokenizer.CARRIAGE_RETURN: 416 case _JsonParser.CARRIAGE_RETURN:
525 charCodes.add(JsonTokenizer.R_SMALL); 417 charCodes.add(_JsonParser.CHAR_R);
526 break; 418 break;
527 default: 419 default:
528 charCodes.add(JsonTokenizer.U_SMALL); 420 charCodes.add(_JsonParser.CHAR_U);
529 charCodes.add(_hexDigit((charCode >> 12) & 0xf)); 421 charCodes.add(_hexDigit((charCode >> 12) & 0xf));
530 charCodes.add(_hexDigit((charCode >> 8) & 0xf)); 422 charCodes.add(_hexDigit((charCode >> 8) & 0xf));
531 charCodes.add(_hexDigit((charCode >> 4) & 0xf)); 423 charCodes.add(_hexDigit((charCode >> 4) & 0xf));
532 charCodes.add(_hexDigit(charCode & 0xf)); 424 charCodes.add(_hexDigit(charCode & 0xf));
533 break; 425 break;
534 } 426 }
535 } else if (charCode == JsonTokenizer.QUOTE || 427 } else if (charCode == _JsonParser.QUOTE ||
536 charCode == JsonTokenizer.BACKSLASH) { 428 charCode == _JsonParser.BACKSLASH) {
537 needsEscape = true; 429 needsEscape = true;
538 charCodes.add(JsonTokenizer.BACKSLASH); 430 charCodes.add(_JsonParser.BACKSLASH);
539 charCodes.add(charCode); 431 charCodes.add(charCode);
540 } else { 432 } else {
541 charCodes.add(charCode); 433 charCodes.add(charCode);
542 } 434 }
543 } 435 }
544 sb.add(needsEscape ? new String.fromCharCodes(charCodes) : s); 436 sb.add(needsEscape ? new String.fromCharCodes(charCodes) : s);
545 } 437 }
546 438
547 void _checkCycle(final object) { 439 void _checkCycle(final object) {
548 // TODO: use Iterables. 440 // TODO: use Iterables.
(...skipping 64 matching lines...) Expand 10 before | Expand all | Expand 10 after
613 }); 505 });
614 _sb.add('}'); 506 _sb.add('}');
615 _seen.removeLast(); 507 _seen.removeLast();
616 return; 508 return;
617 509
618 default: 510 default:
619 throw const JsonUnsupportedObjectType(); 511 throw const JsonUnsupportedObjectType();
620 } 512 }
621 } 513 }
622 } 514 }
OLDNEW
« no previous file with comments | « no previous file | tests/utils/src/JsonTest.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698