Chromium Code Reviews| OLD | NEW |
|---|---|
| 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. |
| (...skipping 78 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 89 | 89 |
| 90 static final String NULL_STRING = "null"; | 90 static final String NULL_STRING = "null"; |
| 91 static final String TRUE_STRING = "true"; | 91 static final String TRUE_STRING = "true"; |
| 92 static final String FALSE_STRING = "false"; | 92 static final String FALSE_STRING = "false"; |
| 93 | 93 |
| 94 | 94 |
| 95 static parse(String json) { | 95 static parse(String json) { |
| 96 return new _JsonParser._internal(json)._parseToplevel(); | 96 return new _JsonParser._internal(json)._parseToplevel(); |
| 97 } | 97 } |
| 98 | 98 |
| 99 _JsonParser._internal(String json) : this.json = '${json} ' { | 99 _JsonParser._internal(String json) |
|
Anton Muhin
2012/04/28 09:09:24
nit: ._internal(this.json) ?
Anders Johnsen
2012/04/28 09:28:26
Not possible when extracting length in initializer
| |
| 100 : json = json, | |
| 101 length = json.length { | |
| 100 if (tokens !== null) return; | 102 if (tokens !== null) return; |
| 101 | 103 |
| 102 // Use a list as jump-table, faster then switch and if. | 104 // Use a list as jump-table, faster then switch and if. |
| 103 tokens = new List<int>(LAST_ASCII + 1); | 105 tokens = new List<int>(LAST_ASCII + 1); |
| 104 tokens[TAB] = WHITESPACE; | 106 tokens[TAB] = WHITESPACE; |
| 105 tokens[NEW_LINE] = WHITESPACE; | 107 tokens[NEW_LINE] = WHITESPACE; |
| 106 tokens[CARRIAGE_RETURN] = WHITESPACE; | 108 tokens[CARRIAGE_RETURN] = WHITESPACE; |
| 107 tokens[SPACE] = WHITESPACE; | 109 tokens[SPACE] = WHITESPACE; |
| 108 tokens[CHAR_0] = NUMBER_LITERAL; | 110 tokens[CHAR_0] = NUMBER_LITERAL; |
| 109 tokens[CHAR_1] = NUMBER_LITERAL; | 111 tokens[CHAR_1] = NUMBER_LITERAL; |
| (...skipping 104 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 214 | 216 |
| 215 List<int> charCodes = new List<int>(); | 217 List<int> charCodes = new List<int>(); |
| 216 while (true) { | 218 while (true) { |
| 217 int c = _char(); | 219 int c = _char(); |
| 218 if (c == QUOTE) { | 220 if (c == QUOTE) { |
| 219 position++; | 221 position++; |
| 220 break; | 222 break; |
| 221 } | 223 } |
| 222 if (c == BACKSLASH) { | 224 if (c == BACKSLASH) { |
| 223 position++; | 225 position++; |
| 224 if (position == json.length) { | 226 if (position == length) { |
| 225 _error('\\ at the end of input'); | 227 _error('\\ at the end of input'); |
| 226 } | 228 } |
| 227 | 229 |
| 228 switch (_char()) { | 230 switch (_char()) { |
| 229 case QUOTE: | 231 case QUOTE: |
| 230 c = QUOTE; | 232 c = QUOTE; |
| 231 break; | 233 break; |
| 232 case BACKSLASH: | 234 case BACKSLASH: |
| 233 c = BACKSLASH; | 235 c = BACKSLASH; |
| 234 break; | 236 break; |
| 235 case SLASH: | 237 case SLASH: |
| 236 c = SLASH; | 238 c = SLASH; |
| 237 break; | 239 break; |
| 238 case CHAR_B: | 240 case CHAR_B: |
| 239 c = BACKSPACE; | 241 c = BACKSPACE; |
| 240 break; | 242 break; |
| 241 case CHAR_N: | 243 case CHAR_N: |
| 242 c = NEW_LINE; | 244 c = NEW_LINE; |
| 243 break; | 245 break; |
| 244 case CHAR_R: | 246 case CHAR_R: |
| 245 c = CARRIAGE_RETURN; | 247 c = CARRIAGE_RETURN; |
| 246 break; | 248 break; |
| 247 case CHAR_F: | 249 case CHAR_F: |
| 248 c = FORM_FEED; | 250 c = FORM_FEED; |
| 249 break; | 251 break; |
| 250 case CHAR_T: | 252 case CHAR_T: |
| 251 c = TAB; | 253 c = TAB; |
| 252 break; | 254 break; |
| 253 case CHAR_U: | 255 case CHAR_U: |
| 254 if (position + 5 > json.length) { | 256 if (position + 5 > length) { |
| 255 _error('Invalid unicode esacape sequence'); | 257 _error('Invalid unicode esacape sequence'); |
| 256 } | 258 } |
| 257 final codeString = json.substring(position + 1, position + 5); | 259 final codeString = json.substring(position + 1, position + 5); |
| 258 try { | 260 try { |
| 259 c = Math.parseInt('0x${codeString}'); | 261 c = Math.parseInt('0x${codeString}'); |
| 260 } catch (var e) { | 262 } catch (var e) { |
| 261 _error('Invalid unicode esacape sequence'); | 263 _error('Invalid unicode esacape sequence'); |
| 262 } | 264 } |
| 263 position += 4; | 265 position += 4; |
| 264 break; | 266 break; |
| 265 default: | 267 default: |
| 266 _error('Invalid esacape sequence in string literal'); | 268 _error('Invalid esacape sequence in string literal'); |
| 267 } | 269 } |
| 268 } | 270 } |
| 269 charCodes.add(c); | 271 charCodes.add(c); |
| 270 position++; | 272 position++; |
| 271 } | 273 } |
| 272 | 274 |
| 273 return new String.fromCharCodes(charCodes); | 275 return new String.fromCharCodes(charCodes); |
| 274 } | 276 } |
| 275 | 277 |
| 276 num _parseNumber() { | 278 num _parseNumber() { |
| 277 if (!_isToken(NUMBER_LITERAL)) _error("Expected number literal"); | 279 if (!_isToken(NUMBER_LITERAL)) _error("Expected number literal"); |
| 278 | 280 |
| 279 final int startPos = position; | 281 final int startPos = position; |
| 280 if (_isChar(MINUS)) position++; | 282 if (_isChar(MINUS)) position++; |
|
Anton Muhin
2012/04/28 09:09:24
just an idea: maybe fetch char once with _char() a
Anders Johnsen
2012/04/28 09:28:26
Done.
| |
| 281 if (_isChar(CHAR_0)) { | 283 if (_isChar(CHAR_0)) { |
| 282 position++; | 284 position++; |
| 283 } else if (_isDigit()) { | 285 } else if (_isDigit()) { |
| 284 position++; | 286 position++; |
| 285 while (_isDigit()) position++; | 287 while (_isDigit()) position++; |
| 286 } else { | 288 } else { |
| 287 _error("Expected digit when parsing number"); | 289 _error("Expected digit when parsing number"); |
| 288 } | 290 } |
| 289 | 291 |
| 290 bool isInt = true; | 292 bool isInt = true; |
| 291 if (_isChar(DOT)) { | 293 if (_isChar(DOT)) { |
| 292 position++; | 294 position++; |
| 293 if (_isDigit()) { | 295 if (_isDigit()) { |
| 296 position++; | |
| 294 isInt = false; | 297 isInt = false; |
| 295 while (_isDigit()) position++; | 298 while (_isDigit()) position++; |
| 296 } else { | 299 } else { |
| 297 position--; // No digit, backtrack. | 300 _error("Expected digit following comma"); |
|
Anton Muhin
2012/04/28 09:09:24
nit: single quotes
Anton Muhin
2012/04/28 09:09:24
is it a correct thing, may we double check with JS
Anders Johnsen
2012/04/28 09:28:26
Done.
Anders Johnsen
2012/04/28 09:28:26
There is not token in JSON that can follow a numbe
| |
| 298 } | 301 } |
| 299 } | 302 } |
| 300 | 303 |
| 301 if (_isChar(CHAR_E) || _isChar(CHAR_CAPITAL_E)) { | 304 if (_isChar(CHAR_E) || _isChar(CHAR_CAPITAL_E)) { |
|
Anton Muhin
2012/04/28 09:09:24
ditto here: fetch char once and then do both check
Anders Johnsen
2012/04/28 09:28:26
Done.
| |
| 302 int backtrackTo = position; | |
| 303 position++; | 305 position++; |
| 304 if (_isChar(MINUS) || _isChar(PLUS)) position++; | 306 if (_isChar(MINUS) || _isChar(PLUS)) position++; |
|
Anton Muhin
2012/04/28 09:09:24
ditto
Anders Johnsen
2012/04/28 09:28:26
Done.
| |
| 305 if (_isDigit()) { | 307 if (_isDigit()) { |
| 306 position++; | 308 position++; |
| 307 isInt = false; | 309 isInt = false; |
| 308 while (_isDigit()) position++; | 310 while (_isDigit()) position++; |
| 309 } else { | 311 } else { |
| 310 position = backtrackTo; // No digit, backtrack. | 312 _error("Expected digit following 'e' or 'E'"); |
|
Anton Muhin
2012/04/28 09:09:24
ditto nit for ''
Anders Johnsen
2012/04/28 09:28:26
Done.
| |
| 311 } | 313 } |
| 312 } | 314 } |
| 313 | 315 |
| 314 String number = json.substring(startPos, position); | 316 String number = json.substring(startPos, position); |
| 315 if (isInt) { | 317 if (isInt) { |
| 316 return Math.parseInt(number); | 318 return Math.parseInt(number); |
| 317 } else { | 319 } else { |
| 318 return Math.parseDouble(number); | 320 return Math.parseDouble(number); |
| 319 } | 321 } |
| 320 } | 322 } |
| 321 | 323 |
| 322 bool _isChar(int char) => _char() == char; | 324 bool _isChar(int char) { |
| 325 if (position >= length) return false; | |
| 326 return json.charCodeAt(position) == char; | |
| 327 } | |
| 323 | 328 |
| 324 bool _isDigit() { | 329 bool _isDigit() { |
| 325 int char = _char(); | 330 if (position >= length) return false; |
| 331 int char = json.charCodeAt(position); | |
| 326 return char >= CHAR_0 && char <= CHAR_9; | 332 return char >= CHAR_0 && char <= CHAR_9; |
| 327 } | 333 } |
| 328 | 334 |
| 329 bool _isToken(int tokenKind) => _token() == tokenKind; | 335 bool _isToken(int tokenKind) => _token() == tokenKind; |
| 330 | 336 |
| 331 int _char() { | 337 int _char() { |
| 332 if (position >= json.length) { | 338 if (position >= length) { |
| 333 _error("Unexpected end of JSON stream"); | 339 _error("Unexpected end of JSON stream"); |
| 334 } | 340 } |
| 335 return json.charCodeAt(position); | 341 return json.charCodeAt(position); |
| 336 } | 342 } |
| 337 | 343 |
| 338 int _token() { | 344 int _token() { |
| 339 while (true) { | 345 while (true) { |
| 340 if (position >= json.length) return null; | 346 if (position >= length) return null; |
| 341 int char = json.charCodeAt(position); | 347 int char = json.charCodeAt(position); |
| 342 int token = tokens[char]; | 348 int token = tokens[char]; |
| 343 if (token === WHITESPACE) { | 349 if (token === WHITESPACE) { |
| 344 position++; | 350 position++; |
| 345 continue; | 351 continue; |
| 346 } | 352 } |
| 347 if (token === null) _error("Invalid JSON token"); | 353 if (token === null) return 0; |
| 348 return token; | 354 return token; |
| 349 } | 355 } |
| 350 } | 356 } |
| 351 | 357 |
| 352 void _error(String message) { | 358 void _error(String message) { |
| 353 throw message; | 359 throw message; |
| 354 } | 360 } |
| 355 | 361 |
| 356 final String json; | 362 final String json; |
| 363 final int length; | |
| 357 int position = 0; | 364 int position = 0; |
| 358 static List<int> tokens; | 365 static List<int> tokens; |
| 359 } | 366 } |
| 360 | 367 |
| 361 // TODO: proper base class. | 368 // TODO: proper base class. |
| 362 class JsonUnsupportedObjectType { | 369 class JsonUnsupportedObjectType { |
| 363 const JsonUnsupportedObjectType(); | 370 const JsonUnsupportedObjectType(); |
| 364 } | 371 } |
| 365 | 372 |
| 366 class JsonStringifier { | 373 class JsonStringifier { |
| (...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 508 }); | 515 }); |
| 509 _sb.add('}'); | 516 _sb.add('}'); |
| 510 _seen.removeLast(); | 517 _seen.removeLast(); |
| 511 return; | 518 return; |
| 512 | 519 |
| 513 default: | 520 default: |
| 514 throw const JsonUnsupportedObjectType(); | 521 throw const JsonUnsupportedObjectType(); |
| 515 } | 522 } |
| 516 } | 523 } |
| 517 } | 524 } |
| OLD | NEW |