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

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