Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2016, 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 fasta.analyzer.token_utils; | 5 library fasta.analyzer.token_utils; |
| 6 | 6 |
| 7 import 'package:front_end/src/fasta/parser/error_kind.dart' show | 7 import 'package:front_end/src/fasta/parser/error_kind.dart' show |
| 8 ErrorKind; | 8 ErrorKind; |
| 9 | 9 |
| 10 import 'package:front_end/src/fasta/scanner/error_token.dart' show | 10 import 'package:front_end/src/fasta/scanner/error_token.dart' show |
| (...skipping 27 matching lines...) Expand all Loading... | |
| 38 | 38 |
| 39 import 'package:front_end/src/scanner/errors.dart' as analyzer show | 39 import 'package:front_end/src/scanner/errors.dart' as analyzer show |
| 40 ScannerErrorCode; | 40 ScannerErrorCode; |
| 41 | 41 |
| 42 import 'package:analyzer/dart/ast/token.dart' show | 42 import 'package:analyzer/dart/ast/token.dart' show |
| 43 TokenType; | 43 TokenType; |
| 44 | 44 |
| 45 import '../errors.dart' show | 45 import '../errors.dart' show |
| 46 internalError; | 46 internalError; |
| 47 | 47 |
| 48 /// Converts a stream of Fasta tokens (starting with [token] and continuing to | 48 /// Class capable of converting a stream of Fasta tokens to a stream of analyzer |
| 49 /// EOF) to a stream of analyzer tokens. | 49 /// tokens. |
| 50 /// | 50 /// |
| 51 /// If any error tokens are found in the stream, they are reported using the | 51 /// This is a class rather than an ordinary method so that it can be subclassed |
| 52 /// [reportError] callback. | 52 /// in tests. |
| 53 analyzer.Token toAnalyzerTokenStream( | 53 /// |
| 54 Token token, | 54 /// TODO(paulberry,ahe): Fasta includes comments directly in the token |
| 55 void reportError(analyzer.ScannerErrorCode errorCode, int offset, | 55 /// stream, rather than pointing to them via a "precedingComment" pointer, as |
| 56 List<Object> arguments)) { | 56 /// analyzer does. This seems like it will complicate parsing and other |
| 57 var analyzerTokenHead = new analyzer.Token(null, 0); | 57 /// operations. |
| 58 analyzerTokenHead.previous = analyzerTokenHead; | 58 class ToAnalyzerTokenStreamConverter { |
| 59 var analyzerTokenTail = analyzerTokenHead; | 59 /// Synthetic token pointing to the first token in the analyzer token stream. |
| 60 // TODO(paulberry,ahe): Fasta includes comments directly in the token | 60 analyzer.Token _analyzerTokenHead; |
| 61 // stream, rather than pointing to them via a "precedingComment" pointer, as | |
| 62 // analyzer does. This seems like it will complicate parsing and other | |
| 63 // operations. | |
| 64 analyzer.CommentToken currentCommentHead; | |
| 65 analyzer.CommentToken currentCommentTail; | |
| 66 | 61 |
| 67 // Both fasta and analyzer have links from a "BeginToken" to its matching | 62 /// The most recently generated analyzer token, or [_analyzerTokenHead] if no |
| 68 // "EndToken" in a group (like parentheses and braces). However, fasta may | 63 /// tokens have been generated yet. |
| 69 // contain synthetic tokens from error recovery that are not mapped to the | 64 analyzer.Token _analyzerTokenTail; |
| 70 // analyzer token stream. We use these stacks to create the appropriate links | |
| 71 // for non-synthetic tokens in the way analyzer expects. | |
| 72 | 65 |
| 73 // Note: beginTokenStack and endTokenStack are seeded with a sentinel value | 66 /// If a sequence of consecutive comment tokens is being processed, the first |
| 74 // so that we don't have to check if they're empty. | 67 /// translated analyzer comment token. Otherwise `null`. |
| 75 var beginTokenStack = <analyzer.BeginToken>[null]; | 68 analyzer.CommentToken _currentCommentHead; |
| 76 var endTokenStack = <Token>[null]; | |
| 77 | 69 |
| 78 void matchGroups(Token token, analyzer.Token translatedToken) { | 70 /// If a sequence of consecutive comment tokens is being processed, the last |
| 79 if (identical(endTokenStack.last, token)) { | 71 /// translated analyzer comment token. Otherwise `null`. |
| 80 beginTokenStack.last.endToken = translatedToken; | 72 analyzer.CommentToken _currentCommentTail; |
| 81 beginTokenStack.removeLast(); | 73 |
| 82 endTokenStack.removeLast(); | 74 /// Stack of analyzer "begin" tokens which need to be linked up to |
| 75 /// corresponding "end" tokens once those tokens are translated. | |
| 76 /// | |
| 77 /// The first element of this list is always a sentinel `null` value so that | |
| 78 /// we don't have to check if it is empty. | |
| 79 /// | |
| 80 /// See additional documentation in [_matchGroups]. | |
| 81 List<analyzer.BeginToken> _beginTokenStack; | |
| 82 | |
| 83 /// Stack of fasta "end" tokens corresponding to the tokens in | |
| 84 /// [_endTokenStack]. | |
| 85 /// | |
| 86 /// The first element of this list is always a sentinel `null` value so that | |
| 87 /// we don't have to check if it is empty. | |
| 88 /// | |
| 89 /// See additional documentation in [_matchGroups]. | |
| 90 List<Token> _endTokenStack; | |
| 91 | |
| 92 /// Converts a stream of Fasta tokens (starting with [token] and continuing to | |
| 93 /// EOF) to a stream of analyzer tokens. | |
| 94 analyzer.Token convertTokens(Token token) { | |
| 95 _analyzerTokenHead = new analyzer.Token(null, 0); | |
| 96 _analyzerTokenHead.previous = _analyzerTokenHead; | |
| 97 _analyzerTokenTail = _analyzerTokenHead; | |
| 98 _currentCommentHead = null; | |
| 99 _currentCommentTail = null; | |
| 100 _beginTokenStack = [null]; | |
| 101 _endTokenStack = <Token>[null]; | |
| 102 | |
| 103 while (true) { | |
|
ahe
2017/02/15 22:09:20
Very very optional: I'm always looking for infinit
Paul Berry
2017/02/16 02:01:23
Acknowledged.
| |
| 104 if (token.info.kind == BAD_INPUT_TOKEN) { | |
| 105 ErrorToken errorToken = token; | |
| 106 _translateErrorToken(errorToken); | |
| 107 } else if (token.info.kind == COMMENT_TOKEN) { | |
| 108 var translatedToken = translateCommentToken(token); | |
| 109 if (_currentCommentHead == null) { | |
| 110 _currentCommentHead = _currentCommentTail = translatedToken; | |
| 111 } else { | |
| 112 _currentCommentTail.setNext(translatedToken); | |
| 113 _currentCommentTail = translatedToken; | |
| 114 } | |
| 115 } else { | |
| 116 var translatedToken = translateToken(token, _currentCommentHead); | |
| 117 _matchGroups(token, translatedToken); | |
| 118 translatedToken.setNext(translatedToken); | |
| 119 _currentCommentHead = _currentCommentTail = null; | |
| 120 _analyzerTokenTail.setNext(translatedToken); | |
| 121 translatedToken.previous = _analyzerTokenTail; | |
| 122 _analyzerTokenTail = translatedToken; | |
| 123 } | |
| 124 if (token.isEof) { | |
| 125 return _analyzerTokenHead.next; | |
| 126 } | |
| 127 token = token.next; | |
| 128 } | |
| 129 } | |
| 130 | |
| 131 /// Handles an error found during [convertTokens]. | |
| 132 /// | |
| 133 /// Intended to be overridden by derived classes; by default, does nothing. | |
| 134 void reportError(analyzer.ScannerErrorCode errorCode, int offset, | |
| 135 List<Object> arguments) {} | |
| 136 | |
| 137 /// Translates a single fasta comment token to the corresponding analyzer | |
| 138 /// token. | |
| 139 analyzer.CommentToken translateCommentToken(Token token) { | |
| 140 // TODO(paulberry,ahe): It would be nice if the scanner gave us an | |
| 141 // easier way to distinguish between the two types of comment. | |
| 142 var type = token.value.startsWith('/*') | |
| 143 ? TokenType.MULTI_LINE_COMMENT | |
| 144 : TokenType.SINGLE_LINE_COMMENT; | |
| 145 return new analyzer.CommentToken(type, token.value, token.charOffset); | |
| 146 } | |
| 147 | |
| 148 /// Translates a single fasta non-comment token to the corresponding analyzer | |
| 149 /// token. | |
| 150 /// | |
| 151 /// [precedingComments] is not `null`, the translated token is pointed to it. | |
| 152 analyzer.Token translateToken( | |
| 153 Token token, analyzer.CommentToken precedingComments) => | |
| 154 toAnalyzerToken(token, precedingComments); | |
| 155 | |
| 156 /// Creates appropriate begin/end token links based on the fact that [token] | |
| 157 /// was translated to [translatedToken]. | |
| 158 /// | |
| 159 /// Background: both fasta and analyzer have links from a "BeginToken" to its | |
| 160 /// matching "EndToken" in a group (like parentheses and braces). However, | |
| 161 /// fasta may contain synthetic tokens from error recovery that are not mapped | |
| 162 /// to the analyzer token stream. We use [_beginTokenStack] and | |
| 163 /// [_endTokenStack] to create the appropriate links for non-synthetic tokens | |
| 164 /// in the way analyzer expects. | |
|
ahe
2017/02/15 22:09:20
FYI: If you haven't noticed it already: there's do
Paul Berry
2017/02/16 02:01:23
Thanks for the pointer!
| |
| 165 void _matchGroups(Token token, analyzer.Token translatedToken) { | |
| 166 if (identical(_endTokenStack.last, token)) { | |
| 167 _beginTokenStack.last.endToken = translatedToken; | |
| 168 _beginTokenStack.removeLast(); | |
| 169 _endTokenStack.removeLast(); | |
| 83 } | 170 } |
| 84 // Synthetic end tokens use the same offset as the begin token. | 171 // Synthetic end tokens use the same offset as the begin token. |
| 85 if (translatedToken is analyzer.BeginToken && | 172 if (translatedToken is analyzer.BeginToken && |
| 86 token is BeginGroupToken && | 173 token is BeginGroupToken && |
| 87 token.endGroup != null && | 174 token.endGroup != null && |
| 88 token.endGroup.charOffset != token.charOffset) { | 175 token.endGroup.charOffset != token.charOffset) { |
| 89 beginTokenStack.add(translatedToken); | 176 _beginTokenStack.add(translatedToken); |
| 90 endTokenStack.add(token.endGroup); | 177 _endTokenStack.add(token.endGroup); |
| 91 } | 178 } |
| 92 } | 179 } |
| 93 | 180 |
| 94 while (true) { | 181 /// Translates the given error [token] into an analyzer error and reports it |
| 95 if (token.info.kind == BAD_INPUT_TOKEN) { | 182 /// using [reportError]. |
| 96 ErrorToken errorToken = token; | 183 void _translateErrorToken(ErrorToken token) { |
| 97 _translateErrorToken(errorToken, reportError); | 184 int charOffset = token.charOffset; |
| 98 } else if (token.info.kind == COMMENT_TOKEN) { | 185 // TODO(paulberry,ahe): why is endOffset sometimes null? |
| 99 // TODO(paulberry,ahe): It would be nice if the scanner gave us an | 186 int endOffset = token.endOffset ?? charOffset; |
| 100 // easier way to distinguish between the two types of comment. | 187 void _makeError( |
| 101 var type = token.value.startsWith('/*') | 188 analyzer.ScannerErrorCode errorCode, List<Object> arguments) { |
| 102 ? TokenType.MULTI_LINE_COMMENT | 189 if (_isAtEnd(token, charOffset)) { |
| 103 : TokenType.SINGLE_LINE_COMMENT; | 190 // Analyzer never generates an error message past the end of the input, |
| 104 var translatedToken = | 191 // since such an error would not be visible in an editor. |
| 105 new analyzer.CommentToken(type, token.value, token.charOffset); | 192 // TODO(paulberry,ahe): would it make sense to replicate this behavior |
| 106 if (currentCommentHead == null) { | 193 // in fasta, or move it elsewhere in analyzer? |
| 107 currentCommentHead = currentCommentTail = translatedToken; | 194 charOffset--; |
| 108 } else { | |
| 109 currentCommentTail.setNext(translatedToken); | |
| 110 currentCommentTail = translatedToken; | |
| 111 } | 195 } |
| 112 } else { | 196 reportError(errorCode, charOffset, arguments); |
| 113 var translatedToken = toAnalyzerToken(token, currentCommentHead); | |
| 114 matchGroups(token, translatedToken); | |
| 115 translatedToken.setNext(translatedToken); | |
| 116 currentCommentHead = currentCommentTail = null; | |
| 117 analyzerTokenTail.setNext(translatedToken); | |
| 118 translatedToken.previous = analyzerTokenTail; | |
| 119 analyzerTokenTail = translatedToken; | |
| 120 } | 197 } |
| 121 if (token.isEof) { | 198 |
| 122 return analyzerTokenHead.next; | 199 var errorCode = token.errorCode; |
| 200 switch (errorCode) { | |
| 201 case ErrorKind.UnterminatedString: | |
| 202 // TODO(paulberry,ahe): Fasta reports the error location as the entire | |
| 203 // string; analyzer expects the end of the string. | |
| 204 charOffset = endOffset; | |
| 205 return _makeError( | |
| 206 analyzer.ScannerErrorCode.UNTERMINATED_STRING_LITERAL, null); | |
| 207 case ErrorKind.UnmatchedToken: | |
| 208 return null; | |
| 209 case ErrorKind.UnterminatedComment: | |
| 210 // TODO(paulberry,ahe): Fasta reports the error location as the entire | |
| 211 // comment; analyzer expects the end of the comment. | |
| 212 charOffset = endOffset; | |
| 213 return _makeError( | |
| 214 analyzer.ScannerErrorCode.UNTERMINATED_MULTI_LINE_COMMENT, null); | |
| 215 case ErrorKind.MissingExponent: | |
| 216 // TODO(paulberry,ahe): Fasta reports the error location as the entire | |
| 217 // number; analyzer expects the end of the number. | |
| 218 charOffset = endOffset; | |
| 219 return _makeError(analyzer.ScannerErrorCode.MISSING_DIGIT, null); | |
| 220 case ErrorKind.ExpectedHexDigit: | |
| 221 // TODO(paulberry,ahe): Fasta reports the error location as the entire | |
| 222 // number; analyzer expects the end of the number. | |
| 223 charOffset = endOffset; | |
| 224 return _makeError(analyzer.ScannerErrorCode.MISSING_HEX_DIGIT, null); | |
| 225 case ErrorKind.NonAsciiIdentifier: | |
| 226 case ErrorKind.NonAsciiWhitespace: | |
| 227 return _makeError( | |
| 228 analyzer.ScannerErrorCode.ILLEGAL_CHARACTER, [token.character]); | |
| 229 case ErrorKind.UnexpectedDollarInString: | |
| 230 return null; | |
| 231 default: | |
| 232 throw new UnimplementedError('$errorCode'); | |
| 123 } | 233 } |
| 124 token = token.next; | |
| 125 } | 234 } |
| 126 } | 235 } |
| 127 | 236 |
| 128 /// Converts a stream of Analyzer tokens (starting with [token] and continuing | 237 /// Converts a stream of Analyzer tokens (starting with [token] and continuing |
| 129 /// to EOF) to a stream of Fasta tokens. | 238 /// to EOF) to a stream of Fasta tokens. |
| 130 /// | 239 /// |
| 131 /// TODO(paulberry): Analyzer tokens do not record error conditions, so a round | 240 /// TODO(paulberry): Analyzer tokens do not record error conditions, so a round |
| 132 /// trip through this function and [toAnalyzerTokenStream] will lose error | 241 /// trip through this function and [toAnalyzerTokenStream] will lose error |
| 133 /// information. | 242 /// information. |
| 134 Token fromAnalyzerTokenStream(analyzer.Token analyzerToken) { | 243 Token fromAnalyzerTokenStream(analyzer.Token analyzerToken) { |
| (...skipping 244 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 379 // If we've found an EOF token, its charOffset indicates where the end of | 488 // If we've found an EOF token, its charOffset indicates where the end of |
| 380 // the input is. | 489 // the input is. |
| 381 if (token.isEof) return token.charOffset == charOffset; | 490 if (token.isEof) return token.charOffset == charOffset; |
| 382 // If we've found a non-error token, then we know there is additional input | 491 // If we've found a non-error token, then we know there is additional input |
| 383 // text after [charOffset]. | 492 // text after [charOffset]. |
| 384 if (token.info.kind != BAD_INPUT_TOKEN) return false; | 493 if (token.info.kind != BAD_INPUT_TOKEN) return false; |
| 385 // Otherwise keep looking. | 494 // Otherwise keep looking. |
| 386 } | 495 } |
| 387 } | 496 } |
| 388 | 497 |
| 389 /// Translates the given error [token] into an analyzer error and reports it | |
| 390 /// using [reportError]. | |
| 391 void _translateErrorToken( | |
| 392 ErrorToken token, | |
| 393 void reportError(analyzer.ScannerErrorCode errorCode, int offset, | |
| 394 List<Object> arguments)) { | |
| 395 int charOffset = token.charOffset; | |
| 396 // TODO(paulberry,ahe): why is endOffset sometimes null? | |
| 397 int endOffset = token.endOffset ?? charOffset; | |
| 398 void _makeError(analyzer.ScannerErrorCode errorCode, List<Object> arguments) { | |
| 399 if (_isAtEnd(token, charOffset)) { | |
| 400 // Analyzer never generates an error message past the end of the input, | |
| 401 // since such an error would not be visible in an editor. | |
| 402 // TODO(paulberry,ahe): would it make sense to replicate this behavior | |
| 403 // in fasta, or move it elsewhere in analyzer? | |
| 404 charOffset--; | |
| 405 } | |
| 406 reportError(errorCode, charOffset, arguments); | |
| 407 } | |
| 408 | |
| 409 var errorCode = token.errorCode; | |
| 410 switch (errorCode) { | |
| 411 case ErrorKind.UnterminatedString: | |
| 412 // TODO(paulberry,ahe): Fasta reports the error location as the entire | |
| 413 // string; analyzer expects the end of the string. | |
| 414 charOffset = endOffset; | |
| 415 return _makeError( | |
| 416 analyzer.ScannerErrorCode.UNTERMINATED_STRING_LITERAL, null); | |
| 417 case ErrorKind.UnmatchedToken: | |
| 418 return null; | |
| 419 case ErrorKind.UnterminatedComment: | |
| 420 // TODO(paulberry,ahe): Fasta reports the error location as the entire | |
| 421 // comment; analyzer expects the end of the comment. | |
| 422 charOffset = endOffset; | |
| 423 return _makeError( | |
| 424 analyzer.ScannerErrorCode.UNTERMINATED_MULTI_LINE_COMMENT, null); | |
| 425 case ErrorKind.MissingExponent: | |
| 426 // TODO(paulberry,ahe): Fasta reports the error location as the entire | |
| 427 // number; analyzer expects the end of the number. | |
| 428 charOffset = endOffset; | |
| 429 return _makeError(analyzer.ScannerErrorCode.MISSING_DIGIT, null); | |
| 430 case ErrorKind.ExpectedHexDigit: | |
| 431 // TODO(paulberry,ahe): Fasta reports the error location as the entire | |
| 432 // number; analyzer expects the end of the number. | |
| 433 charOffset = endOffset; | |
| 434 return _makeError(analyzer.ScannerErrorCode.MISSING_HEX_DIGIT, null); | |
| 435 case ErrorKind.NonAsciiIdentifier: | |
| 436 case ErrorKind.NonAsciiWhitespace: | |
| 437 return _makeError( | |
| 438 analyzer.ScannerErrorCode.ILLEGAL_CHARACTER, [token.character]); | |
| 439 case ErrorKind.UnexpectedDollarInString: | |
| 440 return null; | |
| 441 default: | |
| 442 throw new UnimplementedError('$errorCode'); | |
| 443 } | |
| 444 } | |
| 445 | |
| 446 analyzer.Token toAnalyzerToken(Token token, | 498 analyzer.Token toAnalyzerToken(Token token, |
| 447 [analyzer.CommentToken commentToken]) { | 499 [analyzer.CommentToken commentToken]) { |
| 448 if (token == null) return null; | 500 if (token == null) return null; |
| 449 analyzer.Token makeStringToken(TokenType tokenType) { | 501 analyzer.Token makeStringToken(TokenType tokenType) { |
| 450 if (commentToken == null) { | 502 if (commentToken == null) { |
| 451 return new analyzer.StringToken(tokenType, token.value, token.charOffset); | 503 return new analyzer.StringToken(tokenType, token.value, token.charOffset); |
| 452 } else { | 504 } else { |
| 453 return new analyzer.StringTokenWithComment( | 505 return new analyzer.StringTokenWithComment( |
| 454 tokenType, token.value, token.charOffset, commentToken); | 506 tokenType, token.value, token.charOffset, commentToken); |
| 455 } | 507 } |
| (...skipping 200 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 656 case BACKSLASH_TOKEN: return TokenType.BACKSLASH; | 708 case BACKSLASH_TOKEN: return TokenType.BACKSLASH; |
| 657 case PERIOD_PERIOD_PERIOD_TOKEN: return TokenType.PERIOD_PERIOD_PERIOD; | 709 case PERIOD_PERIOD_PERIOD_TOKEN: return TokenType.PERIOD_PERIOD_PERIOD; |
| 658 // case GENERIC_METHOD_TYPE_LIST_TOKEN: | 710 // case GENERIC_METHOD_TYPE_LIST_TOKEN: |
| 659 // return TokenType.GENERIC_METHOD_TYPE_LIST; | 711 // return TokenType.GENERIC_METHOD_TYPE_LIST; |
| 660 // case GENERIC_METHOD_TYPE_ASSIGN_TOKEN: | 712 // case GENERIC_METHOD_TYPE_ASSIGN_TOKEN: |
| 661 // return TokenType.GENERIC_METHOD_TYPE_ASSIGN; | 713 // return TokenType.GENERIC_METHOD_TYPE_ASSIGN; |
| 662 default: | 714 default: |
| 663 return internalError("Unhandled token ${token.info}"); | 715 return internalError("Unhandled token ${token.info}"); |
| 664 } | 716 } |
| 665 } | 717 } |
| OLD | NEW |