| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 /** | |
| 6 * **DEPRECATED** Use the JSON facilities in `dart:convert` instead. | |
| 7 * | |
| 8 * Utilities for encoding and decoding JSON (JavaScript Object Notation) data. | |
| 9 */ | |
| 10 @deprecated | |
| 11 library json; | |
| 12 | |
| 13 import 'dart:convert' show JSON, StringConversionSink; | |
| 14 export 'dart:convert' show JsonUnsupportedObjectError, JsonCyclicError; | |
| 15 | |
| 16 /** | |
| 17 * **DEPRECATED** Use [JSON.decode] from `dart:convert` instead. | |
| 18 * | |
| 19 * Parses [json] and build the corresponding parsed JSON value. | |
| 20 * | |
| 21 * Parsed JSON values are of the types [num], [String], [bool], [Null], | |
| 22 * [List]s of parsed JSON values or [Map]s from [String] to parsed | |
| 23 * JSON values. | |
| 24 * | |
| 25 * The optional [reviver] function, if provided, is called once for each | |
| 26 * object or list property parsed. The arguments are the property name | |
| 27 * ([String]) or list index ([int]), and the value is the parsed value. | |
| 28 * The return value of the reviver will be used as the value of that property | |
| 29 * instead the parsed value. | |
| 30 * | |
| 31 * Throws [FormatException] if the input is not valid JSON text. | |
| 32 */ | |
| 33 @deprecated | |
| 34 parse(String json, [reviver(var key, var value)]) { | |
| 35 return JSON.decode(json, reviver: reviver); | |
| 36 } | |
| 37 | |
| 38 /** | |
| 39 * **DEPRECATED**. Use [JsonCodec] from `dart:convert` instead. | |
| 40 * | |
| 41 * Serializes [object] into a JSON string. | |
| 42 * | |
| 43 * Directly serializable values are [num], [String], [bool], and [Null], as well | |
| 44 * as some [List] and [Map] values. | |
| 45 * For [List], the elements must all be serializable. | |
| 46 * For [Map], the keys must be [String] and the values must be serializable. | |
| 47 * | |
| 48 * If a value is any other type is attempted serialized, a "toJson()" method | |
| 49 * is invoked on the object and the result, which must be a directly | |
| 50 * serializable value, is serialized instead of the original value. | |
| 51 * | |
| 52 * If the object does not support this method, throws, or returns a | |
| 53 * value that is not directly serializable, a [JsonUnsupportedObjectError] | |
| 54 * exception is thrown. If the call throws (including the case where there | |
| 55 * is no nullary "toJson" method, the error is caught and stored in the | |
| 56 * [JsonUnsupportedObjectError]'s [:cause:] field. | |
| 57 * | |
| 58 * If a [List] or [Map] contains a reference to itself, directly or through | |
| 59 * other lists or maps, it cannot be serialized and a [JsonCyclicError] is | |
| 60 * thrown. | |
| 61 * | |
| 62 * Json Objects should not change during serialization. | |
| 63 * If an object is serialized more than once, [stringify] is allowed to cache | |
| 64 * the JSON text for it. I.e., if an object changes after it is first | |
| 65 * serialized, the new values may or may not be reflected in the result. | |
| 66 */ | |
| 67 @deprecated | |
| 68 String stringify(Object object) { | |
| 69 return JSON.encode(object); | |
| 70 } | |
| 71 | |
| 72 /** | |
| 73 * **DEPRECATED**. Use [JsonCodec] from `dart:convert` instead. | |
| 74 * | |
| 75 * Serializes [object] into [output] stream. | |
| 76 * | |
| 77 * Performs the same operations as [stringify] but outputs the resulting | |
| 78 * string to an existing [StringSink] instead of creating a new [String]. | |
| 79 * | |
| 80 * If serialization fails by throwing, some data might have been added to | |
| 81 * [output], but it won't contain valid JSON text. | |
| 82 */ | |
| 83 @deprecated | |
| 84 void printOn(Object object, StringSink output) { | |
| 85 var stringConversionSink = new StringConversionSink.fromStringSink(output); | |
| 86 JSON.encoder.startChunkedConversion(stringConversionSink).add(object); | |
| 87 } | |
| 88 | |
| 89 //// Implementation /////////////////////////////////////////////////////////// | |
| 90 | |
| 91 // Simple API for JSON parsing. | |
| 92 | |
| 93 abstract class JsonListener { | |
| 94 void handleString(String value) {} | |
| 95 void handleNumber(num value) {} | |
| 96 void handleBool(bool value) {} | |
| 97 void handleNull() {} | |
| 98 void beginObject() {} | |
| 99 void propertyName() {} | |
| 100 void propertyValue() {} | |
| 101 void endObject() {} | |
| 102 void beginArray() {} | |
| 103 void arrayElement() {} | |
| 104 void endArray() {} | |
| 105 /** Called on failure to parse [source]. */ | |
| 106 void fail(String source, int position, String message) {} | |
| 107 } | |
| 108 | |
| 109 /** | |
| 110 * A [JsonListener] that builds data objects from the parser events. | |
| 111 * | |
| 112 * This is a simple stack-based object builder. It keeps the most recently | |
| 113 * seen value in a variable, and uses it depending on the following event. | |
| 114 */ | |
| 115 class BuildJsonListener extends JsonListener { | |
| 116 /** | |
| 117 * Stack used to handle nested containers. | |
| 118 * | |
| 119 * The current container is pushed on the stack when a new one is | |
| 120 * started. If the container is a [Map], there is also a current [key] | |
| 121 * which is also stored on the stack. | |
| 122 */ | |
| 123 List stack = []; | |
| 124 /** The current [Map] or [List] being built. */ | |
| 125 var currentContainer; | |
| 126 /** The most recently read property key. */ | |
| 127 String key; | |
| 128 /** The most recently read value. */ | |
| 129 var value; | |
| 130 | |
| 131 /** Pushes the currently active container (and key, if a [Map]). */ | |
| 132 void pushContainer() { | |
| 133 if (currentContainer is Map) stack.add(key); | |
| 134 stack.add(currentContainer); | |
| 135 } | |
| 136 | |
| 137 /** Pops the top container from the [stack], including a key if applicable. */ | |
| 138 void popContainer() { | |
| 139 value = currentContainer; | |
| 140 currentContainer = stack.removeLast(); | |
| 141 if (currentContainer is Map) key = stack.removeLast(); | |
| 142 } | |
| 143 | |
| 144 void handleString(String value) { this.value = value; } | |
| 145 void handleNumber(num value) { this.value = value; } | |
| 146 void handleBool(bool value) { this.value = value; } | |
| 147 void handleNull() { this.value = null; } | |
| 148 | |
| 149 void beginObject() { | |
| 150 pushContainer(); | |
| 151 currentContainer = {}; | |
| 152 } | |
| 153 | |
| 154 void propertyName() { | |
| 155 key = value; | |
| 156 value = null; | |
| 157 } | |
| 158 | |
| 159 void propertyValue() { | |
| 160 Map map = currentContainer; | |
| 161 map[key] = value; | |
| 162 key = value = null; | |
| 163 } | |
| 164 | |
| 165 void endObject() { | |
| 166 popContainer(); | |
| 167 } | |
| 168 | |
| 169 void beginArray() { | |
| 170 pushContainer(); | |
| 171 currentContainer = []; | |
| 172 } | |
| 173 | |
| 174 void arrayElement() { | |
| 175 List list = currentContainer; | |
| 176 currentContainer.add(value); | |
| 177 value = null; | |
| 178 } | |
| 179 | |
| 180 void endArray() { | |
| 181 popContainer(); | |
| 182 } | |
| 183 | |
| 184 /** Read out the final result of parsing a JSON string. */ | |
| 185 get result { | |
| 186 assert(currentContainer == null); | |
| 187 return value; | |
| 188 } | |
| 189 } | |
| 190 | |
| 191 typedef _Reviver(var key, var value); | |
| 192 | |
| 193 class ReviverJsonListener extends BuildJsonListener { | |
| 194 final _Reviver reviver; | |
| 195 ReviverJsonListener(reviver(key, value)) : this.reviver = reviver; | |
| 196 | |
| 197 void arrayElement() { | |
| 198 List list = currentContainer; | |
| 199 value = reviver(list.length, value); | |
| 200 super.arrayElement(); | |
| 201 } | |
| 202 | |
| 203 void propertyValue() { | |
| 204 value = reviver(key, value); | |
| 205 super.propertyValue(); | |
| 206 } | |
| 207 | |
| 208 get result { | |
| 209 return reviver("", value); | |
| 210 } | |
| 211 } | |
| 212 | |
| 213 class JsonParser { | |
| 214 // A simple non-recursive state-based parser for JSON. | |
| 215 // | |
| 216 // Literal values accepted in states ARRAY_EMPTY, ARRAY_COMMA, OBJECT_COLON | |
| 217 // and strings also in OBJECT_EMPTY, OBJECT_COMMA. | |
| 218 // VALUE STRING : , } ] Transitions to | |
| 219 // EMPTY X X -> END | |
| 220 // ARRAY_EMPTY X X @ -> ARRAY_VALUE / pop | |
| 221 // ARRAY_VALUE @ @ -> ARRAY_COMMA / pop | |
| 222 // ARRAY_COMMA X X -> ARRAY_VALUE | |
| 223 // OBJECT_EMPTY X @ -> OBJECT_KEY / pop | |
| 224 // OBJECT_KEY @ -> OBJECT_COLON | |
| 225 // OBJECT_COLON X X -> OBJECT_VALUE | |
| 226 // OBJECT_VALUE @ @ -> OBJECT_COMMA / pop | |
| 227 // OBJECT_COMMA X -> OBJECT_KEY | |
| 228 // END | |
| 229 // Starting a new array or object will push the current state. The "pop" | |
| 230 // above means restoring this state and then marking it as an ended value. | |
| 231 // X means generic handling, @ means special handling for just that | |
| 232 // state - that is, values are handled generically, only punctuation | |
| 233 // cares about the current state. | |
| 234 // Values for states are chosen so bits 0 and 1 tell whether | |
| 235 // a string/value is allowed, and setting bits 0 through 2 after a value | |
| 236 // gets to the next state (not empty, doesn't allow a value). | |
| 237 | |
| 238 // State building-block constants. | |
| 239 static const int INSIDE_ARRAY = 1; | |
| 240 static const int INSIDE_OBJECT = 2; | |
| 241 static const int AFTER_COLON = 3; // Always inside object. | |
| 242 | |
| 243 static const int ALLOW_STRING_MASK = 8; // Allowed if zero. | |
| 244 static const int ALLOW_VALUE_MASK = 4; // Allowed if zero. | |
| 245 static const int ALLOW_VALUE = 0; | |
| 246 static const int STRING_ONLY = 4; | |
| 247 static const int NO_VALUES = 12; | |
| 248 | |
| 249 // Objects and arrays are "empty" until their first property/element. | |
| 250 static const int EMPTY = 0; | |
| 251 static const int NON_EMPTY = 16; | |
| 252 static const int EMPTY_MASK = 16; // Empty if zero. | |
| 253 | |
| 254 | |
| 255 static const int VALUE_READ_BITS = NO_VALUES | NON_EMPTY; | |
| 256 | |
| 257 // Actual states. | |
| 258 static const int STATE_INITIAL = EMPTY | ALLOW_VALUE; | |
| 259 static const int STATE_END = NON_EMPTY | NO_VALUES; | |
| 260 | |
| 261 static const int STATE_ARRAY_EMPTY = INSIDE_ARRAY | EMPTY | ALLOW_VALUE; | |
| 262 static const int STATE_ARRAY_VALUE = INSIDE_ARRAY | NON_EMPTY | NO_VALUES; | |
| 263 static const int STATE_ARRAY_COMMA = INSIDE_ARRAY | NON_EMPTY | ALLOW_VALUE; | |
| 264 | |
| 265 static const int STATE_OBJECT_EMPTY = INSIDE_OBJECT | EMPTY | STRING_ONLY; | |
| 266 static const int STATE_OBJECT_KEY = INSIDE_OBJECT | NON_EMPTY | NO_VALUES; | |
| 267 static const int STATE_OBJECT_COLON = AFTER_COLON | NON_EMPTY | ALLOW_VALUE; | |
| 268 static const int STATE_OBJECT_VALUE = AFTER_COLON | NON_EMPTY | NO_VALUES; | |
| 269 static const int STATE_OBJECT_COMMA = INSIDE_OBJECT | NON_EMPTY | STRING_ONLY; | |
| 270 | |
| 271 // Character code constants. | |
| 272 static const int BACKSPACE = 0x08; | |
| 273 static const int TAB = 0x09; | |
| 274 static const int NEWLINE = 0x0a; | |
| 275 static const int CARRIAGE_RETURN = 0x0d; | |
| 276 static const int FORM_FEED = 0x0c; | |
| 277 static const int SPACE = 0x20; | |
| 278 static const int QUOTE = 0x22; | |
| 279 static const int PLUS = 0x2b; | |
| 280 static const int COMMA = 0x2c; | |
| 281 static const int MINUS = 0x2d; | |
| 282 static const int DECIMALPOINT = 0x2e; | |
| 283 static const int SLASH = 0x2f; | |
| 284 static const int CHAR_0 = 0x30; | |
| 285 static const int CHAR_9 = 0x39; | |
| 286 static const int COLON = 0x3a; | |
| 287 static const int CHAR_E = 0x45; | |
| 288 static const int LBRACKET = 0x5b; | |
| 289 static const int BACKSLASH = 0x5c; | |
| 290 static const int RBRACKET = 0x5d; | |
| 291 static const int CHAR_a = 0x61; | |
| 292 static const int CHAR_b = 0x62; | |
| 293 static const int CHAR_e = 0x65; | |
| 294 static const int CHAR_f = 0x66; | |
| 295 static const int CHAR_l = 0x6c; | |
| 296 static const int CHAR_n = 0x6e; | |
| 297 static const int CHAR_r = 0x72; | |
| 298 static const int CHAR_s = 0x73; | |
| 299 static const int CHAR_t = 0x74; | |
| 300 static const int CHAR_u = 0x75; | |
| 301 static const int LBRACE = 0x7b; | |
| 302 static const int RBRACE = 0x7d; | |
| 303 | |
| 304 final String source; | |
| 305 final JsonListener listener; | |
| 306 JsonParser(this.source, this.listener); | |
| 307 | |
| 308 /** Parses [source], or throws if it fails. */ | |
| 309 void parse() { | |
| 310 final List<int> states = <int>[]; | |
| 311 int state = STATE_INITIAL; | |
| 312 int position = 0; | |
| 313 int length = source.length; | |
| 314 while (position < length) { | |
| 315 int char = source.codeUnitAt(position); | |
| 316 switch (char) { | |
| 317 case SPACE: | |
| 318 case CARRIAGE_RETURN: | |
| 319 case NEWLINE: | |
| 320 case TAB: | |
| 321 position++; | |
| 322 break; | |
| 323 case QUOTE: | |
| 324 if ((state & ALLOW_STRING_MASK) != 0) fail(position); | |
| 325 position = parseString(position + 1); | |
| 326 state |= VALUE_READ_BITS; | |
| 327 break; | |
| 328 case LBRACKET: | |
| 329 if ((state & ALLOW_VALUE_MASK) != 0) fail(position); | |
| 330 listener.beginArray(); | |
| 331 states.add(state); | |
| 332 state = STATE_ARRAY_EMPTY; | |
| 333 position++; | |
| 334 break; | |
| 335 case LBRACE: | |
| 336 if ((state & ALLOW_VALUE_MASK) != 0) fail(position); | |
| 337 listener.beginObject(); | |
| 338 states.add(state); | |
| 339 state = STATE_OBJECT_EMPTY; | |
| 340 position++; | |
| 341 break; | |
| 342 case CHAR_n: | |
| 343 if ((state & ALLOW_VALUE_MASK) != 0) fail(position); | |
| 344 position = parseNull(position); | |
| 345 state |= VALUE_READ_BITS; | |
| 346 break; | |
| 347 case CHAR_f: | |
| 348 if ((state & ALLOW_VALUE_MASK) != 0) fail(position); | |
| 349 position = parseFalse(position); | |
| 350 state |= VALUE_READ_BITS; | |
| 351 break; | |
| 352 case CHAR_t: | |
| 353 if ((state & ALLOW_VALUE_MASK) != 0) fail(position); | |
| 354 position = parseTrue(position); | |
| 355 state |= VALUE_READ_BITS; | |
| 356 break; | |
| 357 case COLON: | |
| 358 if (state != STATE_OBJECT_KEY) fail(position); | |
| 359 listener.propertyName(); | |
| 360 state = STATE_OBJECT_COLON; | |
| 361 position++; | |
| 362 break; | |
| 363 case COMMA: | |
| 364 if (state == STATE_OBJECT_VALUE) { | |
| 365 listener.propertyValue(); | |
| 366 state = STATE_OBJECT_COMMA; | |
| 367 position++; | |
| 368 } else if (state == STATE_ARRAY_VALUE) { | |
| 369 listener.arrayElement(); | |
| 370 state = STATE_ARRAY_COMMA; | |
| 371 position++; | |
| 372 } else { | |
| 373 fail(position); | |
| 374 } | |
| 375 break; | |
| 376 case RBRACKET: | |
| 377 if (state == STATE_ARRAY_EMPTY) { | |
| 378 listener.endArray(); | |
| 379 } else if (state == STATE_ARRAY_VALUE) { | |
| 380 listener.arrayElement(); | |
| 381 listener.endArray(); | |
| 382 } else { | |
| 383 fail(position); | |
| 384 } | |
| 385 state = states.removeLast() | VALUE_READ_BITS; | |
| 386 position++; | |
| 387 break; | |
| 388 case RBRACE: | |
| 389 if (state == STATE_OBJECT_EMPTY) { | |
| 390 listener.endObject(); | |
| 391 } else if (state == STATE_OBJECT_VALUE) { | |
| 392 listener.propertyValue(); | |
| 393 listener.endObject(); | |
| 394 } else { | |
| 395 fail(position); | |
| 396 } | |
| 397 state = states.removeLast() | VALUE_READ_BITS; | |
| 398 position++; | |
| 399 break; | |
| 400 default: | |
| 401 if ((state & ALLOW_VALUE_MASK) != 0) fail(position); | |
| 402 position = parseNumber(char, position); | |
| 403 state |= VALUE_READ_BITS; | |
| 404 break; | |
| 405 } | |
| 406 } | |
| 407 if (state != STATE_END) fail(position); | |
| 408 } | |
| 409 | |
| 410 /** | |
| 411 * Parses a "true" literal starting at [position]. | |
| 412 * | |
| 413 * [:source[position]:] must be "t". | |
| 414 */ | |
| 415 int parseTrue(int position) { | |
| 416 assert(source.codeUnitAt(position) == CHAR_t); | |
| 417 if (source.length < position + 4) fail(position, "Unexpected identifier"); | |
| 418 if (source.codeUnitAt(position + 1) != CHAR_r || | |
| 419 source.codeUnitAt(position + 2) != CHAR_u || | |
| 420 source.codeUnitAt(position + 3) != CHAR_e) { | |
| 421 fail(position); | |
| 422 } | |
| 423 listener.handleBool(true); | |
| 424 return position + 4; | |
| 425 } | |
| 426 | |
| 427 /** | |
| 428 * Parses a "false" literal starting at [position]. | |
| 429 * | |
| 430 * [:source[position]:] must be "f". | |
| 431 */ | |
| 432 int parseFalse(int position) { | |
| 433 assert(source.codeUnitAt(position) == CHAR_f); | |
| 434 if (source.length < position + 5) fail(position, "Unexpected identifier"); | |
| 435 if (source.codeUnitAt(position + 1) != CHAR_a || | |
| 436 source.codeUnitAt(position + 2) != CHAR_l || | |
| 437 source.codeUnitAt(position + 3) != CHAR_s || | |
| 438 source.codeUnitAt(position + 4) != CHAR_e) { | |
| 439 fail(position); | |
| 440 } | |
| 441 listener.handleBool(false); | |
| 442 return position + 5; | |
| 443 } | |
| 444 | |
| 445 /** Parses a "null" literal starting at [position]. | |
| 446 * | |
| 447 * [:source[position]:] must be "n". | |
| 448 */ | |
| 449 int parseNull(int position) { | |
| 450 assert(source.codeUnitAt(position) == CHAR_n); | |
| 451 if (source.length < position + 4) fail(position, "Unexpected identifier"); | |
| 452 if (source.codeUnitAt(position + 1) != CHAR_u || | |
| 453 source.codeUnitAt(position + 2) != CHAR_l || | |
| 454 source.codeUnitAt(position + 3) != CHAR_l) { | |
| 455 fail(position); | |
| 456 } | |
| 457 listener.handleNull(); | |
| 458 return position + 4; | |
| 459 } | |
| 460 | |
| 461 int parseString(int position) { | |
| 462 // Format: '"'([^\x00-\x1f\\\"]|'\\'[bfnrt/\\"])*'"' | |
| 463 // Initial position is right after first '"'. | |
| 464 int start = position; | |
| 465 int char; | |
| 466 do { | |
| 467 if (position == source.length) { | |
| 468 fail(start - 1, "Unterminated string"); | |
| 469 } | |
| 470 char = source.codeUnitAt(position); | |
| 471 if (char == QUOTE) { | |
| 472 listener.handleString(source.substring(start, position)); | |
| 473 return position + 1; | |
| 474 } | |
| 475 if (char < SPACE) { | |
| 476 fail(position, "Control character in string"); | |
| 477 } | |
| 478 position++; | |
| 479 } while (char != BACKSLASH); | |
| 480 // Backslash escape detected. Collect character codes for rest of string. | |
| 481 int firstEscape = position - 1; | |
| 482 List<int> chars = <int>[]; | |
| 483 while (true) { | |
| 484 if (position == source.length) { | |
| 485 fail(start - 1, "Unterminated string"); | |
| 486 } | |
| 487 char = source.codeUnitAt(position); | |
| 488 switch (char) { | |
| 489 case CHAR_b: char = BACKSPACE; break; | |
| 490 case CHAR_f: char = FORM_FEED; break; | |
| 491 case CHAR_n: char = NEWLINE; break; | |
| 492 case CHAR_r: char = CARRIAGE_RETURN; break; | |
| 493 case CHAR_t: char = TAB; break; | |
| 494 case SLASH: | |
| 495 case BACKSLASH: | |
| 496 case QUOTE: | |
| 497 break; | |
| 498 case CHAR_u: | |
| 499 int hexStart = position - 1; | |
| 500 int value = 0; | |
| 501 for (int i = 0; i < 4; i++) { | |
| 502 position++; | |
| 503 if (position == source.length) { | |
| 504 fail(start - 1, "Unterminated string"); | |
| 505 } | |
| 506 char = source.codeUnitAt(position); | |
| 507 char -= 0x30; | |
| 508 if (char < 0) fail(hexStart, "Invalid unicode escape"); | |
| 509 if (char < 10) { | |
| 510 value = value * 16 + char; | |
| 511 } else { | |
| 512 char = (char | 0x20) - 0x31; | |
| 513 if (char < 0 || char > 5) { | |
| 514 fail(hexStart, "Invalid unicode escape"); | |
| 515 } | |
| 516 value = value * 16 + char + 10; | |
| 517 } | |
| 518 } | |
| 519 char = value; | |
| 520 break; | |
| 521 default: | |
| 522 if (char < SPACE) fail(position, "Control character in string"); | |
| 523 fail(position, "Unrecognized string escape"); | |
| 524 } | |
| 525 do { | |
| 526 chars.add(char); | |
| 527 position++; | |
| 528 if (position == source.length) fail(start - 1, "Unterminated string"); | |
| 529 char = source.codeUnitAt(position); | |
| 530 if (char == QUOTE) { | |
| 531 String result = new String.fromCharCodes(chars); | |
| 532 if (start < firstEscape) { | |
| 533 result = "${source.substring(start, firstEscape)}$result"; | |
| 534 } | |
| 535 listener.handleString(result); | |
| 536 return position + 1; | |
| 537 } | |
| 538 if (char < SPACE) { | |
| 539 fail(position, "Control character in string"); | |
| 540 } | |
| 541 } while (char != BACKSLASH); | |
| 542 position++; | |
| 543 } | |
| 544 } | |
| 545 | |
| 546 int _handleLiteral(start, position, isDouble) { | |
| 547 String literal = source.substring(start, position); | |
| 548 // This correctly creates -0 for doubles. | |
| 549 num value = (isDouble ? double.parse(literal) : int.parse(literal)); | |
| 550 listener.handleNumber(value); | |
| 551 return position; | |
| 552 } | |
| 553 | |
| 554 int parseNumber(int char, int position) { | |
| 555 // Format: | |
| 556 // '-'?('0'|[1-9][0-9]*)('.'[0-9]+)?([eE][+-]?[0-9]+)? | |
| 557 int start = position; | |
| 558 int length = source.length; | |
| 559 bool isDouble = false; | |
| 560 if (char == MINUS) { | |
| 561 position++; | |
| 562 if (position == length) fail(position, "Missing expected digit"); | |
| 563 char = source.codeUnitAt(position); | |
| 564 } | |
| 565 if (char < CHAR_0 || char > CHAR_9) { | |
| 566 fail(position, "Missing expected digit"); | |
| 567 } | |
| 568 if (char == CHAR_0) { | |
| 569 position++; | |
| 570 if (position == length) return _handleLiteral(start, position, false); | |
| 571 char = source.codeUnitAt(position); | |
| 572 if (CHAR_0 <= char && char <= CHAR_9) { | |
| 573 fail(position); | |
| 574 } | |
| 575 } else { | |
| 576 do { | |
| 577 position++; | |
| 578 if (position == length) return _handleLiteral(start, position, false); | |
| 579 char = source.codeUnitAt(position); | |
| 580 } while (CHAR_0 <= char && char <= CHAR_9); | |
| 581 } | |
| 582 if (char == DECIMALPOINT) { | |
| 583 isDouble = true; | |
| 584 position++; | |
| 585 if (position == length) fail(position, "Missing expected digit"); | |
| 586 char = source.codeUnitAt(position); | |
| 587 if (char < CHAR_0 || char > CHAR_9) fail(position); | |
| 588 do { | |
| 589 position++; | |
| 590 if (position == length) return _handleLiteral(start, position, true); | |
| 591 char = source.codeUnitAt(position); | |
| 592 } while (CHAR_0 <= char && char <= CHAR_9); | |
| 593 } | |
| 594 if (char == CHAR_e || char == CHAR_E) { | |
| 595 isDouble = true; | |
| 596 position++; | |
| 597 if (position == length) fail(position, "Missing expected digit"); | |
| 598 char = source.codeUnitAt(position); | |
| 599 if (char == PLUS || char == MINUS) { | |
| 600 position++; | |
| 601 if (position == length) fail(position, "Missing expected digit"); | |
| 602 char = source.codeUnitAt(position); | |
| 603 } | |
| 604 if (char < CHAR_0 || char > CHAR_9) { | |
| 605 fail(position, "Missing expected digit"); | |
| 606 } | |
| 607 do { | |
| 608 position++; | |
| 609 if (position == length) return _handleLiteral(start, position, true); | |
| 610 char = source.codeUnitAt(position); | |
| 611 } while (CHAR_0 <= char && char <= CHAR_9); | |
| 612 } | |
| 613 return _handleLiteral(start, position, isDouble); | |
| 614 } | |
| 615 | |
| 616 void fail(int position, [String message]) { | |
| 617 if (message == null) message = "Unexpected character"; | |
| 618 listener.fail(source, position, message); | |
| 619 // If the listener didn't throw, do it here. | |
| 620 String slice; | |
| 621 int sliceEnd = position + 20; | |
| 622 if (sliceEnd > source.length) { | |
| 623 slice = "'${source.substring(position)}'"; | |
| 624 } else { | |
| 625 slice = "'${source.substring(position, sliceEnd)}...'"; | |
| 626 } | |
| 627 throw new FormatException("Unexpected character at $position: $slice"); | |
| 628 } | |
| 629 } | |
| OLD | NEW |