| OLD | NEW |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2014, 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 analyzer.src.dart.scanner.scanner; | 5 library analyzer.src.dart.scanner.scanner; |
| 6 | 6 |
| 7 import 'package:analyzer/dart/ast/token.dart'; | |
| 8 import 'package:analyzer/error/error.dart'; | 7 import 'package:analyzer/error/error.dart'; |
| 9 import 'package:analyzer/error/listener.dart'; | 8 import 'package:analyzer/error/listener.dart'; |
| 10 import 'package:analyzer/src/dart/ast/token.dart'; | |
| 11 import 'package:analyzer/src/dart/error/syntactic_errors.dart'; | 9 import 'package:analyzer/src/dart/error/syntactic_errors.dart'; |
| 12 import 'package:analyzer/src/dart/scanner/reader.dart'; | 10 import 'package:analyzer/src/dart/scanner/reader.dart'; |
| 13 import 'package:analyzer/src/generated/java_engine.dart'; | |
| 14 import 'package:analyzer/src/generated/source.dart'; | 11 import 'package:analyzer/src/generated/source.dart'; |
| 15 import 'package:charcode/ascii.dart'; | 12 import 'package:front_end/src/scanner/scanner.dart' as fe; |
| 16 | 13 |
| 17 export 'package:analyzer/src/dart/error/syntactic_errors.dart'; | 14 export 'package:analyzer/src/dart/error/syntactic_errors.dart'; |
| 18 | 15 export 'package:front_end/src/scanner/scanner.dart' show KeywordState; |
| 19 /** | |
| 20 * A state in a state machine used to scan keywords. | |
| 21 */ | |
| 22 class KeywordState { | |
| 23 /** | |
| 24 * An empty transition table used by leaf states. | |
| 25 */ | |
| 26 static List<KeywordState> _EMPTY_TABLE = new List<KeywordState>(26); | |
| 27 | |
| 28 /** | |
| 29 * The initial state in the state machine. | |
| 30 */ | |
| 31 static final KeywordState KEYWORD_STATE = _createKeywordStateTable(); | |
| 32 | |
| 33 /** | |
| 34 * A table mapping characters to the states to which those characters will | |
| 35 * transition. (The index into the array is the offset from the character | |
| 36 * `'a'` to the transitioning character.) | |
| 37 */ | |
| 38 final List<KeywordState> _table; | |
| 39 | |
| 40 /** | |
| 41 * The keyword that is recognized by this state, or `null` if this state is | |
| 42 * not a terminal state. | |
| 43 */ | |
| 44 Keyword _keyword; | |
| 45 | |
| 46 /** | |
| 47 * Initialize a newly created state to have the given transitions and to | |
| 48 * recognize the keyword with the given [syntax]. | |
| 49 */ | |
| 50 KeywordState(this._table, String syntax) { | |
| 51 this._keyword = (syntax == null) ? null : Keyword.keywords[syntax]; | |
| 52 } | |
| 53 | |
| 54 /** | |
| 55 * Return the keyword that was recognized by this state, or `null` if this | |
| 56 * state does not recognized a keyword. | |
| 57 */ | |
| 58 Keyword keyword() => _keyword; | |
| 59 | |
| 60 /** | |
| 61 * Return the state that follows this state on a transition of the given | |
| 62 * [character], or `null` if there is no valid state reachable from this state | |
| 63 * with such a transition. | |
| 64 */ | |
| 65 KeywordState next(int character) => _table[character - $a]; | |
| 66 | |
| 67 /** | |
| 68 * Create the next state in the state machine where we have already recognized | |
| 69 * the subset of strings in the given array of [strings] starting at the given | |
| 70 * [offset] and having the given [length]. All of these strings have a common | |
| 71 * prefix and the next character is at the given [start] index. | |
| 72 */ | |
| 73 static KeywordState _computeKeywordStateTable( | |
| 74 int start, List<String> strings, int offset, int length) { | |
| 75 List<KeywordState> result = new List<KeywordState>(26); | |
| 76 assert(length != 0); | |
| 77 int chunk = $nul; | |
| 78 int chunkStart = -1; | |
| 79 bool isLeaf = false; | |
| 80 for (int i = offset; i < offset + length; i++) { | |
| 81 if (strings[i].length == start) { | |
| 82 isLeaf = true; | |
| 83 } | |
| 84 if (strings[i].length > start) { | |
| 85 int c = strings[i].codeUnitAt(start); | |
| 86 if (chunk != c) { | |
| 87 if (chunkStart != -1) { | |
| 88 result[chunk - $a] = _computeKeywordStateTable( | |
| 89 start + 1, strings, chunkStart, i - chunkStart); | |
| 90 } | |
| 91 chunkStart = i; | |
| 92 chunk = c; | |
| 93 } | |
| 94 } | |
| 95 } | |
| 96 if (chunkStart != -1) { | |
| 97 assert(result[chunk - $a] == null); | |
| 98 result[chunk - $a] = _computeKeywordStateTable( | |
| 99 start + 1, strings, chunkStart, offset + length - chunkStart); | |
| 100 } else { | |
| 101 assert(length == 1); | |
| 102 return new KeywordState(_EMPTY_TABLE, strings[offset]); | |
| 103 } | |
| 104 if (isLeaf) { | |
| 105 return new KeywordState(result, strings[offset]); | |
| 106 } else { | |
| 107 return new KeywordState(result, null); | |
| 108 } | |
| 109 } | |
| 110 | |
| 111 /** | |
| 112 * Create and return the initial state in the state machine. | |
| 113 */ | |
| 114 static KeywordState _createKeywordStateTable() { | |
| 115 List<Keyword> values = Keyword.values; | |
| 116 List<String> strings = new List<String>(values.length); | |
| 117 for (int i = 0; i < values.length; i++) { | |
| 118 strings[i] = values[i].syntax; | |
| 119 } | |
| 120 strings.sort(); | |
| 121 return _computeKeywordStateTable(0, strings, 0, strings.length); | |
| 122 } | |
| 123 } | |
| 124 | 16 |
| 125 /** | 17 /** |
| 126 * The class `Scanner` implements a scanner for Dart code. | 18 * The class `Scanner` implements a scanner for Dart code. |
| 127 * | 19 * |
| 128 * The lexical structure of Dart is ambiguous without knowledge of the context | 20 * The lexical structure of Dart is ambiguous without knowledge of the context |
| 129 * in which a token is being scanned. For example, without context we cannot | 21 * in which a token is being scanned. For example, without context we cannot |
| 130 * determine whether source of the form "<<" should be scanned as a single | 22 * determine whether source of the form "<<" should be scanned as a single |
| 131 * left-shift operator or as two left angle brackets. This scanner does not have | 23 * left-shift operator or as two left angle brackets. This scanner does not have |
| 132 * any context, so it always resolves such conflicts by scanning the longest | 24 * any context, so it always resolves such conflicts by scanning the longest |
| 133 * possible token. | 25 * possible token. |
| 134 */ | 26 */ |
| 135 class Scanner { | 27 class Scanner extends fe.Scanner { |
| 136 /** | 28 /** |
| 137 * The source being scanned. | 29 * The source being scanned. |
| 138 */ | 30 */ |
| 139 final Source source; | 31 final Source source; |
| 140 | 32 |
| 141 /** | 33 /** |
| 142 * The reader used to access the characters in the source. | |
| 143 */ | |
| 144 final CharacterReader _reader; | |
| 145 | |
| 146 /** | |
| 147 * The error listener that will be informed of any errors that are found | 34 * The error listener that will be informed of any errors that are found |
| 148 * during the scan. | 35 * during the scan. |
| 149 */ | 36 */ |
| 150 final AnalysisErrorListener _errorListener; | 37 final AnalysisErrorListener _errorListener; |
| 151 | 38 |
| 152 /** | 39 /** |
| 153 * The flag specifying whether documentation comments should be parsed. | |
| 154 */ | |
| 155 bool _preserveComments = true; | |
| 156 | |
| 157 /** | |
| 158 * The token pointing to the head of the linked list of tokens. | |
| 159 */ | |
| 160 Token _tokens; | |
| 161 | |
| 162 /** | |
| 163 * The last token that was scanned. | |
| 164 */ | |
| 165 Token _tail; | |
| 166 | |
| 167 /** | |
| 168 * The first token in the list of comment tokens found since the last | |
| 169 * non-comment token. | |
| 170 */ | |
| 171 Token _firstComment; | |
| 172 | |
| 173 /** | |
| 174 * The last token in the list of comment tokens found since the last | |
| 175 * non-comment token. | |
| 176 */ | |
| 177 Token _lastComment; | |
| 178 | |
| 179 /** | |
| 180 * The index of the first character of the current token. | |
| 181 */ | |
| 182 int _tokenStart = 0; | |
| 183 | |
| 184 /** | |
| 185 * A list containing the offsets of the first character of each line in the | |
| 186 * source code. | |
| 187 */ | |
| 188 List<int> _lineStarts = new List<int>(); | |
| 189 | |
| 190 /** | |
| 191 * A list, treated something like a stack, of tokens representing the | |
| 192 * beginning of a matched pair. It is used to pair the end tokens with the | |
| 193 * begin tokens. | |
| 194 */ | |
| 195 List<BeginToken> _groupingStack = new List<BeginToken>(); | |
| 196 | |
| 197 /** | |
| 198 * The index of the last item in the [_groupingStack], or `-1` if the stack is | |
| 199 * empty. | |
| 200 */ | |
| 201 int _stackEnd = -1; | |
| 202 | |
| 203 /** | |
| 204 * A flag indicating whether any unmatched groups were found during the parse. | |
| 205 */ | |
| 206 bool _hasUnmatchedGroups = false; | |
| 207 | |
| 208 /** | |
| 209 * A flag indicating whether to parse generic method comments, of the form | |
| 210 * `/*=T*/` and `/*<T>*/`. | |
| 211 */ | |
| 212 bool scanGenericMethodComments = false; | |
| 213 | |
| 214 /** | |
| 215 * A flag indicating whether the lazy compound assignment operators '&&=' and | |
| 216 * '||=' are enabled. | |
| 217 */ | |
| 218 bool scanLazyAssignmentOperators = false; | |
| 219 | |
| 220 /** | |
| 221 * Initialize a newly created scanner to scan characters from the given | 40 * Initialize a newly created scanner to scan characters from the given |
| 222 * [source]. The given character [_reader] will be used to read the characters | 41 * [source]. The given character [reader] will be used to read the characters |
| 223 * in the source. The given [_errorListener] will be informed of any errors | 42 * in the source. The given [_errorListener] will be informed of any errors |
| 224 * that are found. | 43 * that are found. |
| 225 */ | 44 */ |
| 226 Scanner(this.source, this._reader, this._errorListener) { | 45 Scanner(this.source, CharacterReader reader, this._errorListener) |
| 227 _tokens = new Token(TokenType.EOF, -1); | 46 : super(reader); |
| 228 _tokens.setNext(_tokens); | |
| 229 _tail = _tokens; | |
| 230 _tokenStart = -1; | |
| 231 _lineStarts.add(0); | |
| 232 } | |
| 233 | 47 |
| 234 /** | 48 @override |
| 235 * Return the first token in the token stream that was scanned. | 49 void reportError( |
| 236 */ | 50 ScannerErrorCode errorCode, int offset, List<Object> arguments) { |
| 237 Token get firstToken => _tokens.next; | 51 _errorListener |
| 238 | 52 .onError(new AnalysisError(source, offset, 1, errorCode, arguments)); |
| 239 /** | |
| 240 * Return `true` if any unmatched groups were found during the parse. | |
| 241 */ | |
| 242 bool get hasUnmatchedGroups => _hasUnmatchedGroups; | |
| 243 | |
| 244 /** | |
| 245 * Return an array containing the offsets of the first character of each line | |
| 246 * in the source code. | |
| 247 */ | |
| 248 List<int> get lineStarts => _lineStarts; | |
| 249 | |
| 250 /** | |
| 251 * Set whether documentation tokens should be preserved. | |
| 252 */ | |
| 253 void set preserveComments(bool preserveComments) { | |
| 254 this._preserveComments = preserveComments; | |
| 255 } | |
| 256 | |
| 257 /** | |
| 258 * Return the last token that was scanned. | |
| 259 */ | |
| 260 Token get tail => _tail; | |
| 261 | |
| 262 /** | |
| 263 * Append the given [token] to the end of the token stream being scanned. This | |
| 264 * method is intended to be used by subclasses that copy existing tokens and | |
| 265 * should not normally be used because it will fail to correctly associate any | |
| 266 * comments with the token being passed in. | |
| 267 */ | |
| 268 void appendToken(Token token) { | |
| 269 _tail = _tail.setNext(token); | |
| 270 } | |
| 271 | |
| 272 int bigSwitch(int next) { | |
| 273 _beginToken(); | |
| 274 if (next == $cr) { | |
| 275 // '\r' | |
| 276 next = _reader.advance(); | |
| 277 if (next == $lf) { | |
| 278 // '\n' | |
| 279 next = _reader.advance(); | |
| 280 } | |
| 281 recordStartOfLine(); | |
| 282 return next; | |
| 283 } else if (next == $lf) { | |
| 284 // '\n' | |
| 285 next = _reader.advance(); | |
| 286 recordStartOfLine(); | |
| 287 return next; | |
| 288 } else if (next == $tab || next == $space) { | |
| 289 // '\t' || ' ' | |
| 290 return _reader.advance(); | |
| 291 } | |
| 292 if (next == $r) { | |
| 293 // 'r' | |
| 294 int peek = _reader.peek(); | |
| 295 if (peek == $double_quote || peek == $single_quote) { | |
| 296 // '"' || "'" | |
| 297 int start = _reader.offset; | |
| 298 return _tokenizeString(_reader.advance(), start, true); | |
| 299 } | |
| 300 } | |
| 301 if ($a <= next && next <= $z) { | |
| 302 // 'a'-'z' | |
| 303 return _tokenizeKeywordOrIdentifier(next, true); | |
| 304 } | |
| 305 if (($A <= next && next <= $Z) || next == $_ || next == $$) { | |
| 306 // 'A'-'Z' || '_' || '$' | |
| 307 return _tokenizeIdentifier(next, _reader.offset, true); | |
| 308 } | |
| 309 if (next == $lt) { | |
| 310 // '<' | |
| 311 return _tokenizeLessThan(next); | |
| 312 } | |
| 313 if (next == $gt) { | |
| 314 // '>' | |
| 315 return _tokenizeGreaterThan(next); | |
| 316 } | |
| 317 if (next == $equal) { | |
| 318 // '=' | |
| 319 return _tokenizeEquals(next); | |
| 320 } | |
| 321 if (next == $exclamation) { | |
| 322 // '!' | |
| 323 return _tokenizeExclamation(next); | |
| 324 } | |
| 325 if (next == $plus) { | |
| 326 // '+' | |
| 327 return _tokenizePlus(next); | |
| 328 } | |
| 329 if (next == $minus) { | |
| 330 // '-' | |
| 331 return _tokenizeMinus(next); | |
| 332 } | |
| 333 if (next == $asterisk) { | |
| 334 // '*' | |
| 335 return _tokenizeMultiply(next); | |
| 336 } | |
| 337 if (next == $percent) { | |
| 338 // '%' | |
| 339 return _tokenizePercent(next); | |
| 340 } | |
| 341 if (next == $ampersand) { | |
| 342 // '&' | |
| 343 return _tokenizeAmpersand(next); | |
| 344 } | |
| 345 if (next == $bar) { | |
| 346 // '|' | |
| 347 return _tokenizeBar(next); | |
| 348 } | |
| 349 if (next == $caret) { | |
| 350 // '^' | |
| 351 return _tokenizeCaret(next); | |
| 352 } | |
| 353 if (next == $open_bracket) { | |
| 354 // '[' | |
| 355 return _tokenizeOpenSquareBracket(next); | |
| 356 } | |
| 357 if (next == $tilde) { | |
| 358 // '~' | |
| 359 return _tokenizeTilde(next); | |
| 360 } | |
| 361 if (next == $backslash) { | |
| 362 // '\\' | |
| 363 _appendTokenOfType(TokenType.BACKSLASH); | |
| 364 return _reader.advance(); | |
| 365 } | |
| 366 if (next == $hash) { | |
| 367 // '#' | |
| 368 return _tokenizeTag(next); | |
| 369 } | |
| 370 if (next == $open_paren) { | |
| 371 // '(' | |
| 372 _appendBeginToken(TokenType.OPEN_PAREN); | |
| 373 return _reader.advance(); | |
| 374 } | |
| 375 if (next == $close_paren) { | |
| 376 // ')' | |
| 377 _appendEndToken(TokenType.CLOSE_PAREN, TokenType.OPEN_PAREN); | |
| 378 return _reader.advance(); | |
| 379 } | |
| 380 if (next == $comma) { | |
| 381 // ',' | |
| 382 _appendTokenOfType(TokenType.COMMA); | |
| 383 return _reader.advance(); | |
| 384 } | |
| 385 if (next == $colon) { | |
| 386 // ':' | |
| 387 _appendTokenOfType(TokenType.COLON); | |
| 388 return _reader.advance(); | |
| 389 } | |
| 390 if (next == $semicolon) { | |
| 391 // ';' | |
| 392 _appendTokenOfType(TokenType.SEMICOLON); | |
| 393 return _reader.advance(); | |
| 394 } | |
| 395 if (next == $question) { | |
| 396 // '?' | |
| 397 return _tokenizeQuestion(); | |
| 398 } | |
| 399 if (next == $close_bracket) { | |
| 400 // ']' | |
| 401 _appendEndToken( | |
| 402 TokenType.CLOSE_SQUARE_BRACKET, TokenType.OPEN_SQUARE_BRACKET); | |
| 403 return _reader.advance(); | |
| 404 } | |
| 405 if (next == $backquote) { | |
| 406 // '`' | |
| 407 _appendTokenOfType(TokenType.BACKPING); | |
| 408 return _reader.advance(); | |
| 409 } | |
| 410 if (next == $lbrace) { | |
| 411 // '{' | |
| 412 _appendBeginToken(TokenType.OPEN_CURLY_BRACKET); | |
| 413 return _reader.advance(); | |
| 414 } | |
| 415 if (next == $rbrace) { | |
| 416 // '}' | |
| 417 _appendEndToken( | |
| 418 TokenType.CLOSE_CURLY_BRACKET, TokenType.OPEN_CURLY_BRACKET); | |
| 419 return _reader.advance(); | |
| 420 } | |
| 421 if (next == $slash) { | |
| 422 // '/' | |
| 423 return _tokenizeSlashOrComment(next); | |
| 424 } | |
| 425 if (next == $at) { | |
| 426 // '@' | |
| 427 _appendTokenOfType(TokenType.AT); | |
| 428 return _reader.advance(); | |
| 429 } | |
| 430 if (next == $double_quote || next == $single_quote) { | |
| 431 // '"' || "'" | |
| 432 return _tokenizeString(next, _reader.offset, false); | |
| 433 } | |
| 434 if (next == $dot) { | |
| 435 // '.' | |
| 436 return _tokenizeDotOrNumber(next); | |
| 437 } | |
| 438 if (next == $0) { | |
| 439 // '0' | |
| 440 return _tokenizeHexOrNumber(next); | |
| 441 } | |
| 442 if ($1 <= next && next <= $9) { | |
| 443 // '1'-'9' | |
| 444 return _tokenizeNumber(next); | |
| 445 } | |
| 446 if (next == -1) { | |
| 447 // EOF | |
| 448 return -1; | |
| 449 } | |
| 450 _reportError(ScannerErrorCode.ILLEGAL_CHARACTER, [next]); | |
| 451 return _reader.advance(); | |
| 452 } | |
| 453 | |
| 454 /** | |
| 455 * Record the fact that we are at the beginning of a new line in the source. | |
| 456 */ | |
| 457 void recordStartOfLine() { | |
| 458 _lineStarts.add(_reader.offset); | |
| 459 } | |
| 460 | |
| 461 /** | |
| 462 * Record that the source begins on the given [line] and [column] at the | |
| 463 * current offset as given by the reader. Both the line and the column are | |
| 464 * one-based indexes. The line starts for lines before the given line will not | |
| 465 * be correct. | |
| 466 * | |
| 467 * This method must be invoked at most one time and must be invoked before | |
| 468 * scanning begins. The values provided must be sensible. The results are | |
| 469 * undefined if these conditions are violated. | |
| 470 */ | |
| 471 void setSourceStart(int line, int column) { | |
| 472 int offset = _reader.offset; | |
| 473 if (line < 1 || column < 1 || offset < 0 || (line + column - 2) >= offset) { | |
| 474 return; | |
| 475 } | |
| 476 for (int i = 2; i < line; i++) { | |
| 477 _lineStarts.add(1); | |
| 478 } | |
| 479 _lineStarts.add(offset - column + 1); | |
| 480 } | |
| 481 | |
| 482 /** | |
| 483 * Scan the source code to produce a list of tokens representing the source, | |
| 484 * and return the first token in the list of tokens that were produced. | |
| 485 */ | |
| 486 Token tokenize() { | |
| 487 int next = _reader.advance(); | |
| 488 while (next != -1) { | |
| 489 next = bigSwitch(next); | |
| 490 } | |
| 491 _appendEofToken(); | |
| 492 return firstToken; | |
| 493 } | |
| 494 | |
| 495 void _appendBeginToken(TokenType type) { | |
| 496 BeginToken token; | |
| 497 if (_firstComment == null) { | |
| 498 token = new BeginToken(type, _tokenStart); | |
| 499 } else { | |
| 500 token = new BeginTokenWithComment(type, _tokenStart, _firstComment); | |
| 501 _firstComment = null; | |
| 502 _lastComment = null; | |
| 503 } | |
| 504 _tail = _tail.setNext(token); | |
| 505 _groupingStack.add(token); | |
| 506 _stackEnd++; | |
| 507 } | |
| 508 | |
| 509 void _appendCommentToken(TokenType type, String value) { | |
| 510 CommentToken token = null; | |
| 511 TokenType genericComment = _matchGenericMethodCommentType(value); | |
| 512 if (genericComment != null) { | |
| 513 token = new CommentToken(genericComment, value, _tokenStart); | |
| 514 } else if (!_preserveComments) { | |
| 515 // Ignore comment tokens if client specified that it doesn't need them. | |
| 516 return; | |
| 517 } else { | |
| 518 // OK, remember comment tokens. | |
| 519 if (_isDocumentationComment(value)) { | |
| 520 token = new DocumentationCommentToken(type, value, _tokenStart); | |
| 521 } else { | |
| 522 token = new CommentToken(type, value, _tokenStart); | |
| 523 } | |
| 524 } | |
| 525 if (_firstComment == null) { | |
| 526 _firstComment = token; | |
| 527 _lastComment = _firstComment; | |
| 528 } else { | |
| 529 _lastComment = _lastComment.setNext(token); | |
| 530 } | |
| 531 } | |
| 532 | |
| 533 void _appendEndToken(TokenType type, TokenType beginType) { | |
| 534 Token token; | |
| 535 if (_firstComment == null) { | |
| 536 token = new Token(type, _tokenStart); | |
| 537 } else { | |
| 538 token = new TokenWithComment(type, _tokenStart, _firstComment); | |
| 539 _firstComment = null; | |
| 540 _lastComment = null; | |
| 541 } | |
| 542 _tail = _tail.setNext(token); | |
| 543 if (_stackEnd >= 0) { | |
| 544 BeginToken begin = _groupingStack[_stackEnd]; | |
| 545 if (begin.type == beginType) { | |
| 546 begin.endToken = token; | |
| 547 _groupingStack.removeAt(_stackEnd--); | |
| 548 } | |
| 549 } | |
| 550 } | |
| 551 | |
| 552 void _appendEofToken() { | |
| 553 Token eofToken; | |
| 554 if (_firstComment == null) { | |
| 555 eofToken = new Token(TokenType.EOF, _reader.offset + 1); | |
| 556 } else { | |
| 557 eofToken = new TokenWithComment( | |
| 558 TokenType.EOF, _reader.offset + 1, _firstComment); | |
| 559 _firstComment = null; | |
| 560 _lastComment = null; | |
| 561 } | |
| 562 // The EOF token points to itself so that there is always infinite | |
| 563 // look-ahead. | |
| 564 eofToken.setNext(eofToken); | |
| 565 _tail = _tail.setNext(eofToken); | |
| 566 if (_stackEnd >= 0) { | |
| 567 _hasUnmatchedGroups = true; | |
| 568 // TODO(brianwilkerson) Fix the ungrouped tokens? | |
| 569 } | |
| 570 } | |
| 571 | |
| 572 void _appendKeywordToken(Keyword keyword) { | |
| 573 if (_firstComment == null) { | |
| 574 _tail = _tail.setNext(new KeywordToken(keyword, _tokenStart)); | |
| 575 } else { | |
| 576 _tail = _tail.setNext( | |
| 577 new KeywordTokenWithComment(keyword, _tokenStart, _firstComment)); | |
| 578 _firstComment = null; | |
| 579 _lastComment = null; | |
| 580 } | |
| 581 } | |
| 582 | |
| 583 void _appendStringToken(TokenType type, String value) { | |
| 584 if (_firstComment == null) { | |
| 585 _tail = _tail.setNext(new StringToken(type, value, _tokenStart)); | |
| 586 } else { | |
| 587 _tail = _tail.setNext( | |
| 588 new StringTokenWithComment(type, value, _tokenStart, _firstComment)); | |
| 589 _firstComment = null; | |
| 590 _lastComment = null; | |
| 591 } | |
| 592 } | |
| 593 | |
| 594 void _appendStringTokenWithOffset(TokenType type, String value, int offset) { | |
| 595 if (_firstComment == null) { | |
| 596 _tail = _tail.setNext(new StringToken(type, value, _tokenStart + offset)); | |
| 597 } else { | |
| 598 _tail = _tail.setNext(new StringTokenWithComment( | |
| 599 type, value, _tokenStart + offset, _firstComment)); | |
| 600 _firstComment = null; | |
| 601 _lastComment = null; | |
| 602 } | |
| 603 } | |
| 604 | |
| 605 void _appendTokenOfType(TokenType type) { | |
| 606 if (_firstComment == null) { | |
| 607 _tail = _tail.setNext(new Token(type, _tokenStart)); | |
| 608 } else { | |
| 609 _tail = | |
| 610 _tail.setNext(new TokenWithComment(type, _tokenStart, _firstComment)); | |
| 611 _firstComment = null; | |
| 612 _lastComment = null; | |
| 613 } | |
| 614 } | |
| 615 | |
| 616 void _appendTokenOfTypeWithOffset(TokenType type, int offset) { | |
| 617 if (_firstComment == null) { | |
| 618 _tail = _tail.setNext(new Token(type, offset)); | |
| 619 } else { | |
| 620 _tail = _tail.setNext(new TokenWithComment(type, offset, _firstComment)); | |
| 621 _firstComment = null; | |
| 622 _lastComment = null; | |
| 623 } | |
| 624 } | |
| 625 | |
| 626 void _beginToken() { | |
| 627 _tokenStart = _reader.offset; | |
| 628 } | |
| 629 | |
| 630 /** | |
| 631 * Return the beginning token corresponding to a closing brace that was found | |
| 632 * while scanning inside a string interpolation expression. Tokens that cannot | |
| 633 * be matched with the closing brace will be dropped from the stack. | |
| 634 */ | |
| 635 BeginToken _findTokenMatchingClosingBraceInInterpolationExpression() { | |
| 636 while (_stackEnd >= 0) { | |
| 637 BeginToken begin = _groupingStack[_stackEnd]; | |
| 638 if (begin.type == TokenType.OPEN_CURLY_BRACKET || | |
| 639 begin.type == TokenType.STRING_INTERPOLATION_EXPRESSION) { | |
| 640 return begin; | |
| 641 } | |
| 642 _hasUnmatchedGroups = true; | |
| 643 _groupingStack.removeAt(_stackEnd--); | |
| 644 } | |
| 645 // | |
| 646 // We should never get to this point because we wouldn't be inside a string | |
| 647 // interpolation expression unless we had previously found the start of the | |
| 648 // expression. | |
| 649 // | |
| 650 return null; | |
| 651 } | |
| 652 | |
| 653 /** | |
| 654 * Checks if [value] is the start of a generic method type annotation comment. | |
| 655 * | |
| 656 * This can either be of the form `/*<T>*/` or `/*=T*/`. The token type is | |
| 657 * returned, or null if it was not a generic method comment. | |
| 658 */ | |
| 659 TokenType _matchGenericMethodCommentType(String value) { | |
| 660 if (scanGenericMethodComments) { | |
| 661 // Match /*< and >*/ | |
| 662 if (StringUtilities.startsWith3(value, 0, $slash, $asterisk, $lt) && | |
| 663 StringUtilities.endsWith3(value, $gt, $asterisk, $slash)) { | |
| 664 return TokenType.GENERIC_METHOD_TYPE_LIST; | |
| 665 } | |
| 666 // Match /*= | |
| 667 if (StringUtilities.startsWith3(value, 0, $slash, $asterisk, $equal)) { | |
| 668 return TokenType.GENERIC_METHOD_TYPE_ASSIGN; | |
| 669 } | |
| 670 } | |
| 671 return null; | |
| 672 } | |
| 673 | |
| 674 /** | |
| 675 * Report an error at the current offset. The [errorCode] is the error code | |
| 676 * indicating the nature of the error. The [arguments] are any arguments | |
| 677 * needed to complete the error message | |
| 678 */ | |
| 679 void _reportError(ScannerErrorCode errorCode, [List<Object> arguments]) { | |
| 680 _errorListener.onError( | |
| 681 new AnalysisError(source, _reader.offset, 1, errorCode, arguments)); | |
| 682 } | |
| 683 | |
| 684 int _select(int choice, TokenType yesType, TokenType noType) { | |
| 685 int next = _reader.advance(); | |
| 686 if (next == choice) { | |
| 687 _appendTokenOfType(yesType); | |
| 688 return _reader.advance(); | |
| 689 } else { | |
| 690 _appendTokenOfType(noType); | |
| 691 return next; | |
| 692 } | |
| 693 } | |
| 694 | |
| 695 int _selectWithOffset( | |
| 696 int choice, TokenType yesType, TokenType noType, int offset) { | |
| 697 int next = _reader.advance(); | |
| 698 if (next == choice) { | |
| 699 _appendTokenOfTypeWithOffset(yesType, offset); | |
| 700 return _reader.advance(); | |
| 701 } else { | |
| 702 _appendTokenOfTypeWithOffset(noType, offset); | |
| 703 return next; | |
| 704 } | |
| 705 } | |
| 706 | |
| 707 int _tokenizeAmpersand(int next) { | |
| 708 // &&= && &= & | |
| 709 next = _reader.advance(); | |
| 710 if (next == $ampersand) { | |
| 711 next = _reader.advance(); | |
| 712 if (scanLazyAssignmentOperators && next == $equal) { | |
| 713 _appendTokenOfType(TokenType.AMPERSAND_AMPERSAND_EQ); | |
| 714 return _reader.advance(); | |
| 715 } | |
| 716 _appendTokenOfType(TokenType.AMPERSAND_AMPERSAND); | |
| 717 return next; | |
| 718 } else if (next == $equal) { | |
| 719 _appendTokenOfType(TokenType.AMPERSAND_EQ); | |
| 720 return _reader.advance(); | |
| 721 } else { | |
| 722 _appendTokenOfType(TokenType.AMPERSAND); | |
| 723 return next; | |
| 724 } | |
| 725 } | |
| 726 | |
| 727 int _tokenizeBar(int next) { | |
| 728 // ||= || |= | | |
| 729 next = _reader.advance(); | |
| 730 if (next == $bar) { | |
| 731 next = _reader.advance(); | |
| 732 if (scanLazyAssignmentOperators && next == $equal) { | |
| 733 _appendTokenOfType(TokenType.BAR_BAR_EQ); | |
| 734 return _reader.advance(); | |
| 735 } | |
| 736 _appendTokenOfType(TokenType.BAR_BAR); | |
| 737 return next; | |
| 738 } else if (next == $equal) { | |
| 739 _appendTokenOfType(TokenType.BAR_EQ); | |
| 740 return _reader.advance(); | |
| 741 } else { | |
| 742 _appendTokenOfType(TokenType.BAR); | |
| 743 return next; | |
| 744 } | |
| 745 } | |
| 746 | |
| 747 int _tokenizeCaret(int next) => | |
| 748 _select($equal, TokenType.CARET_EQ, TokenType.CARET); | |
| 749 | |
| 750 int _tokenizeDotOrNumber(int next) { | |
| 751 int start = _reader.offset; | |
| 752 next = _reader.advance(); | |
| 753 if ($0 <= next && next <= $9) { | |
| 754 return _tokenizeFractionPart(next, start); | |
| 755 } else if ($dot == next) { | |
| 756 return _select( | |
| 757 $dot, TokenType.PERIOD_PERIOD_PERIOD, TokenType.PERIOD_PERIOD); | |
| 758 } else { | |
| 759 _appendTokenOfType(TokenType.PERIOD); | |
| 760 return next; | |
| 761 } | |
| 762 } | |
| 763 | |
| 764 int _tokenizeEquals(int next) { | |
| 765 // = == => | |
| 766 next = _reader.advance(); | |
| 767 if (next == $equal) { | |
| 768 _appendTokenOfType(TokenType.EQ_EQ); | |
| 769 return _reader.advance(); | |
| 770 } else if (next == $gt) { | |
| 771 _appendTokenOfType(TokenType.FUNCTION); | |
| 772 return _reader.advance(); | |
| 773 } | |
| 774 _appendTokenOfType(TokenType.EQ); | |
| 775 return next; | |
| 776 } | |
| 777 | |
| 778 int _tokenizeExclamation(int next) { | |
| 779 // ! != | |
| 780 next = _reader.advance(); | |
| 781 if (next == $equal) { | |
| 782 _appendTokenOfType(TokenType.BANG_EQ); | |
| 783 return _reader.advance(); | |
| 784 } | |
| 785 _appendTokenOfType(TokenType.BANG); | |
| 786 return next; | |
| 787 } | |
| 788 | |
| 789 int _tokenizeExponent(int next) { | |
| 790 if (next == $plus || next == $minus) { | |
| 791 next = _reader.advance(); | |
| 792 } | |
| 793 bool hasDigits = false; | |
| 794 while (true) { | |
| 795 if ($0 <= next && next <= $9) { | |
| 796 hasDigits = true; | |
| 797 } else { | |
| 798 if (!hasDigits) { | |
| 799 _reportError(ScannerErrorCode.MISSING_DIGIT); | |
| 800 } | |
| 801 return next; | |
| 802 } | |
| 803 next = _reader.advance(); | |
| 804 } | |
| 805 } | |
| 806 | |
| 807 int _tokenizeFractionPart(int next, int start) { | |
| 808 bool done = false; | |
| 809 bool hasDigit = false; | |
| 810 LOOP: | |
| 811 while (!done) { | |
| 812 if ($0 <= next && next <= $9) { | |
| 813 hasDigit = true; | |
| 814 } else if ($e == next || $E == next) { | |
| 815 hasDigit = true; | |
| 816 next = _tokenizeExponent(_reader.advance()); | |
| 817 done = true; | |
| 818 continue LOOP; | |
| 819 } else { | |
| 820 done = true; | |
| 821 continue LOOP; | |
| 822 } | |
| 823 next = _reader.advance(); | |
| 824 } | |
| 825 if (!hasDigit) { | |
| 826 _appendStringToken(TokenType.INT, _reader.getString(start, -2)); | |
| 827 if ($dot == next) { | |
| 828 return _selectWithOffset($dot, TokenType.PERIOD_PERIOD_PERIOD, | |
| 829 TokenType.PERIOD_PERIOD, _reader.offset - 1); | |
| 830 } | |
| 831 _appendTokenOfTypeWithOffset(TokenType.PERIOD, _reader.offset - 1); | |
| 832 return bigSwitch(next); | |
| 833 } | |
| 834 _appendStringToken( | |
| 835 TokenType.DOUBLE, _reader.getString(start, next < 0 ? 0 : -1)); | |
| 836 return next; | |
| 837 } | |
| 838 | |
| 839 int _tokenizeGreaterThan(int next) { | |
| 840 // > >= >> >>= | |
| 841 next = _reader.advance(); | |
| 842 if ($equal == next) { | |
| 843 _appendTokenOfType(TokenType.GT_EQ); | |
| 844 return _reader.advance(); | |
| 845 } else if ($gt == next) { | |
| 846 next = _reader.advance(); | |
| 847 if ($equal == next) { | |
| 848 _appendTokenOfType(TokenType.GT_GT_EQ); | |
| 849 return _reader.advance(); | |
| 850 } else { | |
| 851 _appendTokenOfType(TokenType.GT_GT); | |
| 852 return next; | |
| 853 } | |
| 854 } else { | |
| 855 _appendTokenOfType(TokenType.GT); | |
| 856 return next; | |
| 857 } | |
| 858 } | |
| 859 | |
| 860 int _tokenizeHex(int next) { | |
| 861 int start = _reader.offset - 1; | |
| 862 bool hasDigits = false; | |
| 863 while (true) { | |
| 864 next = _reader.advance(); | |
| 865 if (($0 <= next && next <= $9) || | |
| 866 ($A <= next && next <= $F) || | |
| 867 ($a <= next && next <= $f)) { | |
| 868 hasDigits = true; | |
| 869 } else { | |
| 870 if (!hasDigits) { | |
| 871 _reportError(ScannerErrorCode.MISSING_HEX_DIGIT); | |
| 872 } | |
| 873 _appendStringToken( | |
| 874 TokenType.HEXADECIMAL, _reader.getString(start, next < 0 ? 0 : -1)); | |
| 875 return next; | |
| 876 } | |
| 877 } | |
| 878 } | |
| 879 | |
| 880 int _tokenizeHexOrNumber(int next) { | |
| 881 int x = _reader.peek(); | |
| 882 if (x == $x || x == $X) { | |
| 883 _reader.advance(); | |
| 884 return _tokenizeHex(x); | |
| 885 } | |
| 886 return _tokenizeNumber(next); | |
| 887 } | |
| 888 | |
| 889 int _tokenizeIdentifier(int next, int start, bool allowDollar) { | |
| 890 while (($a <= next && next <= $z) || | |
| 891 ($A <= next && next <= $Z) || | |
| 892 ($0 <= next && next <= $9) || | |
| 893 next == $_ || | |
| 894 (next == $$ && allowDollar)) { | |
| 895 next = _reader.advance(); | |
| 896 } | |
| 897 _appendStringToken( | |
| 898 TokenType.IDENTIFIER, _reader.getString(start, next < 0 ? 0 : -1)); | |
| 899 return next; | |
| 900 } | |
| 901 | |
| 902 int _tokenizeInterpolatedExpression(int next, int start) { | |
| 903 _appendBeginToken(TokenType.STRING_INTERPOLATION_EXPRESSION); | |
| 904 next = _reader.advance(); | |
| 905 while (next != -1) { | |
| 906 if (next == $rbrace) { | |
| 907 BeginToken begin = | |
| 908 _findTokenMatchingClosingBraceInInterpolationExpression(); | |
| 909 if (begin == null) { | |
| 910 _beginToken(); | |
| 911 _appendTokenOfType(TokenType.CLOSE_CURLY_BRACKET); | |
| 912 next = _reader.advance(); | |
| 913 _beginToken(); | |
| 914 return next; | |
| 915 } else if (begin.type == TokenType.OPEN_CURLY_BRACKET) { | |
| 916 _beginToken(); | |
| 917 _appendEndToken( | |
| 918 TokenType.CLOSE_CURLY_BRACKET, TokenType.OPEN_CURLY_BRACKET); | |
| 919 next = _reader.advance(); | |
| 920 _beginToken(); | |
| 921 } else if (begin.type == TokenType.STRING_INTERPOLATION_EXPRESSION) { | |
| 922 _beginToken(); | |
| 923 _appendEndToken(TokenType.CLOSE_CURLY_BRACKET, | |
| 924 TokenType.STRING_INTERPOLATION_EXPRESSION); | |
| 925 next = _reader.advance(); | |
| 926 _beginToken(); | |
| 927 return next; | |
| 928 } | |
| 929 } else { | |
| 930 next = bigSwitch(next); | |
| 931 } | |
| 932 } | |
| 933 return next; | |
| 934 } | |
| 935 | |
| 936 int _tokenizeInterpolatedIdentifier(int next, int start) { | |
| 937 _appendStringTokenWithOffset( | |
| 938 TokenType.STRING_INTERPOLATION_IDENTIFIER, "\$", 0); | |
| 939 if (($A <= next && next <= $Z) || | |
| 940 ($a <= next && next <= $z) || | |
| 941 next == $_) { | |
| 942 _beginToken(); | |
| 943 next = _tokenizeKeywordOrIdentifier(next, false); | |
| 944 } | |
| 945 _beginToken(); | |
| 946 return next; | |
| 947 } | |
| 948 | |
| 949 int _tokenizeKeywordOrIdentifier(int next, bool allowDollar) { | |
| 950 KeywordState state = KeywordState.KEYWORD_STATE; | |
| 951 int start = _reader.offset; | |
| 952 while (state != null && $a <= next && next <= $z) { | |
| 953 state = state.next(next); | |
| 954 next = _reader.advance(); | |
| 955 } | |
| 956 if (state == null || state.keyword() == null) { | |
| 957 return _tokenizeIdentifier(next, start, allowDollar); | |
| 958 } | |
| 959 if (($A <= next && next <= $Z) || | |
| 960 ($0 <= next && next <= $9) || | |
| 961 next == $_ || | |
| 962 next == $$) { | |
| 963 return _tokenizeIdentifier(next, start, allowDollar); | |
| 964 } else if (next < 128) { | |
| 965 _appendKeywordToken(state.keyword()); | |
| 966 return next; | |
| 967 } else { | |
| 968 return _tokenizeIdentifier(next, start, allowDollar); | |
| 969 } | |
| 970 } | |
| 971 | |
| 972 int _tokenizeLessThan(int next) { | |
| 973 // < <= << <<= | |
| 974 next = _reader.advance(); | |
| 975 if ($equal == next) { | |
| 976 _appendTokenOfType(TokenType.LT_EQ); | |
| 977 return _reader.advance(); | |
| 978 } else if ($lt == next) { | |
| 979 return _select($equal, TokenType.LT_LT_EQ, TokenType.LT_LT); | |
| 980 } else { | |
| 981 _appendTokenOfType(TokenType.LT); | |
| 982 return next; | |
| 983 } | |
| 984 } | |
| 985 | |
| 986 int _tokenizeMinus(int next) { | |
| 987 // - -- -= | |
| 988 next = _reader.advance(); | |
| 989 if (next == $minus) { | |
| 990 _appendTokenOfType(TokenType.MINUS_MINUS); | |
| 991 return _reader.advance(); | |
| 992 } else if (next == $equal) { | |
| 993 _appendTokenOfType(TokenType.MINUS_EQ); | |
| 994 return _reader.advance(); | |
| 995 } else { | |
| 996 _appendTokenOfType(TokenType.MINUS); | |
| 997 return next; | |
| 998 } | |
| 999 } | |
| 1000 | |
| 1001 int _tokenizeMultiLineComment(int next) { | |
| 1002 int nesting = 1; | |
| 1003 next = _reader.advance(); | |
| 1004 while (true) { | |
| 1005 if (-1 == next) { | |
| 1006 _reportError(ScannerErrorCode.UNTERMINATED_MULTI_LINE_COMMENT); | |
| 1007 _appendCommentToken( | |
| 1008 TokenType.MULTI_LINE_COMMENT, _reader.getString(_tokenStart, 0)); | |
| 1009 return next; | |
| 1010 } else if ($asterisk == next) { | |
| 1011 next = _reader.advance(); | |
| 1012 if ($slash == next) { | |
| 1013 --nesting; | |
| 1014 if (0 == nesting) { | |
| 1015 _appendCommentToken(TokenType.MULTI_LINE_COMMENT, | |
| 1016 _reader.getString(_tokenStart, 0)); | |
| 1017 return _reader.advance(); | |
| 1018 } else { | |
| 1019 next = _reader.advance(); | |
| 1020 } | |
| 1021 } | |
| 1022 } else if ($slash == next) { | |
| 1023 next = _reader.advance(); | |
| 1024 if ($asterisk == next) { | |
| 1025 next = _reader.advance(); | |
| 1026 ++nesting; | |
| 1027 } | |
| 1028 } else if (next == $cr) { | |
| 1029 next = _reader.advance(); | |
| 1030 if (next == $lf) { | |
| 1031 next = _reader.advance(); | |
| 1032 } | |
| 1033 recordStartOfLine(); | |
| 1034 } else if (next == $lf) { | |
| 1035 next = _reader.advance(); | |
| 1036 recordStartOfLine(); | |
| 1037 } else { | |
| 1038 next = _reader.advance(); | |
| 1039 } | |
| 1040 } | |
| 1041 } | |
| 1042 | |
| 1043 int _tokenizeMultiLineRawString(int quoteChar, int start) { | |
| 1044 int next = _reader.advance(); | |
| 1045 outer: | |
| 1046 while (next != -1) { | |
| 1047 while (next != quoteChar) { | |
| 1048 if (next == -1) { | |
| 1049 break outer; | |
| 1050 } else if (next == $cr) { | |
| 1051 next = _reader.advance(); | |
| 1052 if (next == $lf) { | |
| 1053 next = _reader.advance(); | |
| 1054 } | |
| 1055 recordStartOfLine(); | |
| 1056 } else if (next == $lf) { | |
| 1057 next = _reader.advance(); | |
| 1058 recordStartOfLine(); | |
| 1059 } else { | |
| 1060 next = _reader.advance(); | |
| 1061 } | |
| 1062 } | |
| 1063 next = _reader.advance(); | |
| 1064 if (next == quoteChar) { | |
| 1065 next = _reader.advance(); | |
| 1066 if (next == quoteChar) { | |
| 1067 _appendStringToken(TokenType.STRING, _reader.getString(start, 0)); | |
| 1068 return _reader.advance(); | |
| 1069 } | |
| 1070 } | |
| 1071 } | |
| 1072 _reportError(ScannerErrorCode.UNTERMINATED_STRING_LITERAL); | |
| 1073 _appendStringToken(TokenType.STRING, _reader.getString(start, 0)); | |
| 1074 return _reader.advance(); | |
| 1075 } | |
| 1076 | |
| 1077 int _tokenizeMultiLineString(int quoteChar, int start, bool raw) { | |
| 1078 if (raw) { | |
| 1079 return _tokenizeMultiLineRawString(quoteChar, start); | |
| 1080 } | |
| 1081 int next = _reader.advance(); | |
| 1082 while (next != -1) { | |
| 1083 if (next == $$) { | |
| 1084 _appendStringToken(TokenType.STRING, _reader.getString(start, -1)); | |
| 1085 next = _tokenizeStringInterpolation(start); | |
| 1086 _beginToken(); | |
| 1087 start = _reader.offset; | |
| 1088 continue; | |
| 1089 } | |
| 1090 if (next == quoteChar) { | |
| 1091 next = _reader.advance(); | |
| 1092 if (next == quoteChar) { | |
| 1093 next = _reader.advance(); | |
| 1094 if (next == quoteChar) { | |
| 1095 _appendStringToken(TokenType.STRING, _reader.getString(start, 0)); | |
| 1096 return _reader.advance(); | |
| 1097 } | |
| 1098 } | |
| 1099 continue; | |
| 1100 } | |
| 1101 if (next == $backslash) { | |
| 1102 next = _reader.advance(); | |
| 1103 if (next == -1) { | |
| 1104 break; | |
| 1105 } | |
| 1106 if (next == $cr) { | |
| 1107 next = _reader.advance(); | |
| 1108 if (next == $lf) { | |
| 1109 next = _reader.advance(); | |
| 1110 } | |
| 1111 recordStartOfLine(); | |
| 1112 } else if (next == $lf) { | |
| 1113 recordStartOfLine(); | |
| 1114 next = _reader.advance(); | |
| 1115 } else { | |
| 1116 next = _reader.advance(); | |
| 1117 } | |
| 1118 } else if (next == $cr) { | |
| 1119 next = _reader.advance(); | |
| 1120 if (next == $lf) { | |
| 1121 next = _reader.advance(); | |
| 1122 } | |
| 1123 recordStartOfLine(); | |
| 1124 } else if (next == $lf) { | |
| 1125 recordStartOfLine(); | |
| 1126 next = _reader.advance(); | |
| 1127 } else { | |
| 1128 next = _reader.advance(); | |
| 1129 } | |
| 1130 } | |
| 1131 _reportError(ScannerErrorCode.UNTERMINATED_STRING_LITERAL); | |
| 1132 if (start == _reader.offset) { | |
| 1133 _appendStringTokenWithOffset(TokenType.STRING, "", 1); | |
| 1134 } else { | |
| 1135 _appendStringToken(TokenType.STRING, _reader.getString(start, 0)); | |
| 1136 } | |
| 1137 return _reader.advance(); | |
| 1138 } | |
| 1139 | |
| 1140 int _tokenizeMultiply(int next) => | |
| 1141 _select($equal, TokenType.STAR_EQ, TokenType.STAR); | |
| 1142 | |
| 1143 int _tokenizeNumber(int next) { | |
| 1144 int start = _reader.offset; | |
| 1145 while (true) { | |
| 1146 next = _reader.advance(); | |
| 1147 if ($0 <= next && next <= $9) { | |
| 1148 continue; | |
| 1149 } else if (next == $dot) { | |
| 1150 return _tokenizeFractionPart(_reader.advance(), start); | |
| 1151 } else if (next == $e || next == $E) { | |
| 1152 return _tokenizeFractionPart(next, start); | |
| 1153 } else { | |
| 1154 _appendStringToken( | |
| 1155 TokenType.INT, _reader.getString(start, next < 0 ? 0 : -1)); | |
| 1156 return next; | |
| 1157 } | |
| 1158 } | |
| 1159 } | |
| 1160 | |
| 1161 int _tokenizeOpenSquareBracket(int next) { | |
| 1162 // [ [] []= | |
| 1163 next = _reader.advance(); | |
| 1164 if (next == $close_bracket) { | |
| 1165 return _select($equal, TokenType.INDEX_EQ, TokenType.INDEX); | |
| 1166 } else { | |
| 1167 _appendBeginToken(TokenType.OPEN_SQUARE_BRACKET); | |
| 1168 return next; | |
| 1169 } | |
| 1170 } | |
| 1171 | |
| 1172 int _tokenizePercent(int next) => | |
| 1173 _select($equal, TokenType.PERCENT_EQ, TokenType.PERCENT); | |
| 1174 | |
| 1175 int _tokenizePlus(int next) { | |
| 1176 // + ++ += | |
| 1177 next = _reader.advance(); | |
| 1178 if ($plus == next) { | |
| 1179 _appendTokenOfType(TokenType.PLUS_PLUS); | |
| 1180 return _reader.advance(); | |
| 1181 } else if ($equal == next) { | |
| 1182 _appendTokenOfType(TokenType.PLUS_EQ); | |
| 1183 return _reader.advance(); | |
| 1184 } else { | |
| 1185 _appendTokenOfType(TokenType.PLUS); | |
| 1186 return next; | |
| 1187 } | |
| 1188 } | |
| 1189 | |
| 1190 int _tokenizeQuestion() { | |
| 1191 // ? ?. ?? ??= | |
| 1192 int next = _reader.advance(); | |
| 1193 if (next == $dot) { | |
| 1194 // '.' | |
| 1195 _appendTokenOfType(TokenType.QUESTION_PERIOD); | |
| 1196 return _reader.advance(); | |
| 1197 } else if (next == $question) { | |
| 1198 // '?' | |
| 1199 next = _reader.advance(); | |
| 1200 if (next == $equal) { | |
| 1201 // '=' | |
| 1202 _appendTokenOfType(TokenType.QUESTION_QUESTION_EQ); | |
| 1203 return _reader.advance(); | |
| 1204 } else { | |
| 1205 _appendTokenOfType(TokenType.QUESTION_QUESTION); | |
| 1206 return next; | |
| 1207 } | |
| 1208 } else { | |
| 1209 _appendTokenOfType(TokenType.QUESTION); | |
| 1210 return next; | |
| 1211 } | |
| 1212 } | |
| 1213 | |
| 1214 int _tokenizeSingleLineComment(int next) { | |
| 1215 while (true) { | |
| 1216 next = _reader.advance(); | |
| 1217 if (-1 == next) { | |
| 1218 _appendCommentToken( | |
| 1219 TokenType.SINGLE_LINE_COMMENT, _reader.getString(_tokenStart, 0)); | |
| 1220 return next; | |
| 1221 } else if ($lf == next || $cr == next) { | |
| 1222 _appendCommentToken( | |
| 1223 TokenType.SINGLE_LINE_COMMENT, _reader.getString(_tokenStart, -1)); | |
| 1224 return next; | |
| 1225 } | |
| 1226 } | |
| 1227 } | |
| 1228 | |
| 1229 int _tokenizeSingleLineRawString(int next, int quoteChar, int start) { | |
| 1230 next = _reader.advance(); | |
| 1231 while (next != -1) { | |
| 1232 if (next == quoteChar) { | |
| 1233 _appendStringToken(TokenType.STRING, _reader.getString(start, 0)); | |
| 1234 return _reader.advance(); | |
| 1235 } else if (next == $cr || next == $lf) { | |
| 1236 _reportError(ScannerErrorCode.UNTERMINATED_STRING_LITERAL); | |
| 1237 _appendStringToken(TokenType.STRING, _reader.getString(start, -1)); | |
| 1238 return _reader.advance(); | |
| 1239 } | |
| 1240 next = _reader.advance(); | |
| 1241 } | |
| 1242 _reportError(ScannerErrorCode.UNTERMINATED_STRING_LITERAL); | |
| 1243 _appendStringToken(TokenType.STRING, _reader.getString(start, 0)); | |
| 1244 return _reader.advance(); | |
| 1245 } | |
| 1246 | |
| 1247 int _tokenizeSingleLineString(int next, int quoteChar, int start) { | |
| 1248 while (next != quoteChar) { | |
| 1249 if (next == $backslash) { | |
| 1250 next = _reader.advance(); | |
| 1251 } else if (next == $$) { | |
| 1252 _appendStringToken(TokenType.STRING, _reader.getString(start, -1)); | |
| 1253 next = _tokenizeStringInterpolation(start); | |
| 1254 _beginToken(); | |
| 1255 start = _reader.offset; | |
| 1256 continue; | |
| 1257 } | |
| 1258 if (next <= $cr && (next == $lf || next == $cr || next == -1)) { | |
| 1259 _reportError(ScannerErrorCode.UNTERMINATED_STRING_LITERAL); | |
| 1260 if (start == _reader.offset) { | |
| 1261 _appendStringTokenWithOffset(TokenType.STRING, "", 1); | |
| 1262 } else if (next == -1) { | |
| 1263 _appendStringToken(TokenType.STRING, _reader.getString(start, 0)); | |
| 1264 } else { | |
| 1265 _appendStringToken(TokenType.STRING, _reader.getString(start, -1)); | |
| 1266 } | |
| 1267 return _reader.advance(); | |
| 1268 } | |
| 1269 next = _reader.advance(); | |
| 1270 } | |
| 1271 _appendStringToken(TokenType.STRING, _reader.getString(start, 0)); | |
| 1272 return _reader.advance(); | |
| 1273 } | |
| 1274 | |
| 1275 int _tokenizeSlashOrComment(int next) { | |
| 1276 next = _reader.advance(); | |
| 1277 if ($asterisk == next) { | |
| 1278 return _tokenizeMultiLineComment(next); | |
| 1279 } else if ($slash == next) { | |
| 1280 return _tokenizeSingleLineComment(next); | |
| 1281 } else if ($equal == next) { | |
| 1282 _appendTokenOfType(TokenType.SLASH_EQ); | |
| 1283 return _reader.advance(); | |
| 1284 } else { | |
| 1285 _appendTokenOfType(TokenType.SLASH); | |
| 1286 return next; | |
| 1287 } | |
| 1288 } | |
| 1289 | |
| 1290 int _tokenizeString(int next, int start, bool raw) { | |
| 1291 int quoteChar = next; | |
| 1292 next = _reader.advance(); | |
| 1293 if (quoteChar == next) { | |
| 1294 next = _reader.advance(); | |
| 1295 if (quoteChar == next) { | |
| 1296 // Multiline string. | |
| 1297 return _tokenizeMultiLineString(quoteChar, start, raw); | |
| 1298 } else { | |
| 1299 // Empty string. | |
| 1300 _appendStringToken(TokenType.STRING, _reader.getString(start, -1)); | |
| 1301 return next; | |
| 1302 } | |
| 1303 } | |
| 1304 if (raw) { | |
| 1305 return _tokenizeSingleLineRawString(next, quoteChar, start); | |
| 1306 } else { | |
| 1307 return _tokenizeSingleLineString(next, quoteChar, start); | |
| 1308 } | |
| 1309 } | |
| 1310 | |
| 1311 int _tokenizeStringInterpolation(int start) { | |
| 1312 _beginToken(); | |
| 1313 int next = _reader.advance(); | |
| 1314 if (next == $lbrace) { | |
| 1315 return _tokenizeInterpolatedExpression(next, start); | |
| 1316 } else { | |
| 1317 return _tokenizeInterpolatedIdentifier(next, start); | |
| 1318 } | |
| 1319 } | |
| 1320 | |
| 1321 int _tokenizeTag(int next) { | |
| 1322 // # or #!.*[\n\r] | |
| 1323 if (_reader.offset == 0) { | |
| 1324 if (_reader.peek() == $exclamation) { | |
| 1325 do { | |
| 1326 next = _reader.advance(); | |
| 1327 } while (next != $lf && next != $cr && next > 0); | |
| 1328 _appendStringToken( | |
| 1329 TokenType.SCRIPT_TAG, _reader.getString(_tokenStart, 0)); | |
| 1330 return next; | |
| 1331 } | |
| 1332 } | |
| 1333 _appendTokenOfType(TokenType.HASH); | |
| 1334 return _reader.advance(); | |
| 1335 } | |
| 1336 | |
| 1337 int _tokenizeTilde(int next) { | |
| 1338 // ~ ~/ ~/= | |
| 1339 next = _reader.advance(); | |
| 1340 if (next == $slash) { | |
| 1341 return _select($equal, TokenType.TILDE_SLASH_EQ, TokenType.TILDE_SLASH); | |
| 1342 } else { | |
| 1343 _appendTokenOfType(TokenType.TILDE); | |
| 1344 return next; | |
| 1345 } | |
| 1346 } | |
| 1347 | |
| 1348 /** | |
| 1349 * Checks if [value] is a single-line or multi-line comment. | |
| 1350 */ | |
| 1351 static bool _isDocumentationComment(String value) { | |
| 1352 return StringUtilities.startsWith3(value, 0, $slash, $slash, $slash) || | |
| 1353 StringUtilities.startsWith3(value, 0, $slash, $asterisk, $asterisk); | |
| 1354 } | 53 } |
| 1355 } | 54 } |
| OLD | NEW |