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) |
| 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 int char = _char(); |
| 281 if (_isChar(CHAR_0)) { | 283 if (char === MINUS) char = _nextChar(); |
| 282 position++; | 284 if (char === CHAR_0) { |
| 285 char = _nextChar(); | |
| 283 } else if (_isDigit()) { | 286 } else if (_isDigit()) { |
| 284 position++; | 287 char = _nextChar(); |
| 285 while (_isDigit()) position++; | 288 while (_isDigit()) char = _nextChar(); |
| 286 } else { | 289 } else { |
| 287 _error("Expected digit when parsing number"); | 290 _error('Expected digit when parsing number'); |
| 288 } | 291 } |
| 289 | 292 |
| 290 bool isInt = true; | 293 bool isInt = true; |
| 291 if (_isChar(DOT)) { | 294 if (char === DOT) { |
| 292 position++; | 295 char = _nextChar(); |
| 293 if (_isDigit()) { | 296 if (_isDigit()) { |
| 297 char = _nextChar(); | |
| 294 isInt = false; | 298 isInt = false; |
| 295 while (_isDigit()) position++; | 299 while (_isDigit()) char = _nextChar(); |
| 296 } else { | 300 } else { |
| 297 position--; // No digit, backtrack. | 301 _error('Expected digit following comma'); |
| 298 } | 302 } |
| 299 } | 303 } |
| 300 | 304 |
| 301 if (_isChar(CHAR_E) || _isChar(CHAR_CAPITAL_E)) { | 305 if (char === CHAR_E || char === CHAR_CAPITAL_E) { |
| 302 int backtrackTo = position; | 306 char = _nextChar(); |
| 303 position++; | 307 if (char === MINUS || char === PLUS) position++; |
| 304 if (_isChar(MINUS) || _isChar(PLUS)) position++; | |
| 305 if (_isDigit()) { | 308 if (_isDigit()) { |
| 306 position++; | 309 char = _nextChar(); |
|
Anton Muhin
2012/04/28 09:30:35
char might be either the code or false, is this in
Anders Johnsen
2012/04/28 09:35:56
Nope, nice catch! :)
| |
| 307 isInt = false; | 310 isInt = false; |
| 308 while (_isDigit()) position++; | 311 while (_isDigit()) char = _nextChar(); |
| 309 } else { | 312 } else { |
| 310 position = backtrackTo; // No digit, backtrack. | 313 _error('Expected digit following \'e\' or \'E\''); |
| 311 } | 314 } |
| 312 } | 315 } |
| 313 | 316 |
| 314 String number = json.substring(startPos, position); | 317 String number = json.substring(startPos, position); |
| 315 if (isInt) { | 318 if (isInt) { |
| 316 return Math.parseInt(number); | 319 return Math.parseInt(number); |
| 317 } else { | 320 } else { |
| 318 return Math.parseDouble(number); | 321 return Math.parseDouble(number); |
| 319 } | 322 } |
| 320 } | 323 } |
| 321 | 324 |
| 322 bool _isChar(int char) => _char() == char; | 325 bool _isChar(int char) { |
| 326 if (position >= length) return false; | |
| 327 return json.charCodeAt(position) == char; | |
| 328 } | |
| 323 | 329 |
| 324 bool _isDigit() { | 330 bool _isDigit() { |
| 325 int char = _char(); | 331 if (position >= length) return false; |
| 332 int char = json.charCodeAt(position); | |
| 326 return char >= CHAR_0 && char <= CHAR_9; | 333 return char >= CHAR_0 && char <= CHAR_9; |
| 327 } | 334 } |
| 328 | 335 |
| 329 bool _isToken(int tokenKind) => _token() == tokenKind; | 336 bool _isToken(int tokenKind) => _token() == tokenKind; |
| 330 | 337 |
| 331 int _char() { | 338 int _char() { |
| 332 if (position >= json.length) { | 339 if (position >= length) { |
| 333 _error("Unexpected end of JSON stream"); | 340 _error('Unexpected end of JSON stream'); |
| 334 } | 341 } |
| 335 return json.charCodeAt(position); | 342 return json.charCodeAt(position); |
| 336 } | 343 } |
| 337 | 344 |
| 345 int _nextChar() { | |
| 346 position++; | |
| 347 if (position >= length) return false; | |
| 348 return json.charCodeAt(position); | |
| 349 } | |
| 350 | |
| 351 | |
| 352 int _charNoError() { | |
| 353 if (position >= length) return 0; | |
| 354 return json.charCodeAt(position); | |
| 355 } | |
| 356 | |
| 338 int _token() { | 357 int _token() { |
| 339 while (true) { | 358 while (true) { |
| 340 if (position >= json.length) return null; | 359 if (position >= length) return null; |
| 341 int char = json.charCodeAt(position); | 360 int char = json.charCodeAt(position); |
| 342 int token = tokens[char]; | 361 int token = tokens[char]; |
| 343 if (token === WHITESPACE) { | 362 if (token === WHITESPACE) { |
| 344 position++; | 363 position++; |
| 345 continue; | 364 continue; |
| 346 } | 365 } |
| 347 if (token === null) _error("Invalid JSON token"); | 366 if (token === null) return 0; |
| 348 return token; | 367 return token; |
| 349 } | 368 } |
| 350 } | 369 } |
| 351 | 370 |
| 352 void _error(String message) { | 371 void _error(String message) { |
| 353 throw message; | 372 throw message; |
| 354 } | 373 } |
| 355 | 374 |
| 356 final String json; | 375 final String json; |
| 376 final int length; | |
| 357 int position = 0; | 377 int position = 0; |
| 358 static List<int> tokens; | 378 static List<int> tokens; |
| 359 } | 379 } |
| 360 | 380 |
| 361 // TODO: proper base class. | 381 // TODO: proper base class. |
| 362 class JsonUnsupportedObjectType { | 382 class JsonUnsupportedObjectType { |
| 363 const JsonUnsupportedObjectType(); | 383 const JsonUnsupportedObjectType(); |
| 364 } | 384 } |
| 365 | 385 |
| 366 class JsonStringifier { | 386 class JsonStringifier { |
| (...skipping 141 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 508 }); | 528 }); |
| 509 _sb.add('}'); | 529 _sb.add('}'); |
| 510 _seen.removeLast(); | 530 _seen.removeLast(); |
| 511 return; | 531 return; |
| 512 | 532 |
| 513 default: | 533 default: |
| 514 throw const JsonUnsupportedObjectType(); | 534 throw const JsonUnsupportedObjectType(); |
| 515 } | 535 } |
| 516 } | 536 } |
| 517 } | 537 } |
| OLD | NEW |