Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(276)

Side by Side Diff: pkg/front_end/lib/src/fasta/analyzer/token_utils.dart

Issue 2693403002: Change toAnalyzerTokenStream into a class. (Closed)
Patch Set: Rework Created 3 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | pkg/front_end/test/scanner_fasta_test.dart » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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
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
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 }
OLDNEW
« no previous file with comments | « no previous file | pkg/front_end/test/scanner_fasta_test.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698