Linter Demo Errors: 0Warnings: 14File: /home/fstrocco/Dart/dart/benchmark/analyzer/lib/src/generated/scanner.dart // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. library engine.scanner; import 'dart:collection'; import 'error.dart'; import 'java_engine.dart'; import 'source.dart'; /** * The opening half of a grouping pair of tokens. This is used for curly * brackets ('{'), parentheses ('('), and square brackets ('['). */ class BeginToken extends Token { /** * The token that corresponds to this token. */ Token endToken; /** * Initialize a newly created token to have the given [type] at the given * [offset]. */ BeginToken(TokenType type, int offset) : super(type, offset) { assert(type == TokenType.OPEN_CURLY_BRACKET || type == TokenType.OPEN_PAREN || type == TokenType.OPEN_SQUARE_BRACKET || type == TokenType.STRING_INTERPOLATION_EXPRESSION); } @override Token copy() => new BeginToken(type, offset); } /** * A begin token that is preceded by comments. */ class BeginTokenWithComment extends BeginToken { /** * The first comment in the list of comments that precede this token. */ CommentToken _precedingComment; /** * Initialize a newly created token to have the given [type] at the given * [offset] and to be preceded by the comments reachable from the given * [comment]. */ BeginTokenWithComment(TokenType type, int offset, this._precedingComment) : super(type, offset) { _setCommentParent(_precedingComment); } CommentToken get precedingComments => _precedingComment; void set precedingComments(CommentToken comment) { _precedingComment = comment; _setCommentParent(_precedingComment); } @override void applyDelta(int delta) { super.applyDelta(delta); Token token = precedingComments; while (token != null) { token.applyDelta(delta); token = token.next; } } @override Token copy() => new BeginTokenWithComment(type, offset, copyComments(precedingComments)); } /** * A [CharacterReader] that reads a range of characters from another character * reader. */ class CharacterRangeReader extends CharacterReader { /** * The reader from which the characters are actually being read. */ final CharacterReader baseReader; /** * The last character to be read. */ final int endIndex; /** * Initialize a newly created reader to read the characters from the given * [baseReader] between the [startIndex] inclusive to [endIndex] exclusive. */ CharacterRangeReader(this.baseReader, int startIndex, this.endIndex) { baseReader.offset = startIndex - 1; } @override int get offset => baseReader.offset; @override void set offset(int offset) { baseReader.offset = offset; } @override int advance() { if (baseReader.offset + 1 >= endIndex) { return -1; } return baseReader.advance(); } @override String getString(int start, int endDelta) => baseReader.getString(start, endDelta); @override int peek() { if (baseReader.offset + 1 >= endIndex) { return -1; } return baseReader.peek(); } } /** * An object used by the scanner to read the characters to be scanned. */ abstract class CharacterReader { /** * The current offset relative to the beginning of the source. Return the * initial offset if the scanner has not yet scanned the source code, and one * (1) past the end of the source code if the entire source code has been * scanned. */ int get offset; /** * Set the current offset relative to the beginning of the source to the given * [offset]. The new offset must be between the initial offset and one (1) * past the end of the source code. */ void set offset(int offset); /** * Advance the current position and return the character at the new current * position. */ int advance(); /** * Return the substring of the source code between the [start] offset and the * modified current position. The current position is modified by adding the * [endDelta], which is the number of characters after the current location to * be included in the string, or the number of characters before the current * location to be excluded if the offset is negative. */ String getString(int start, int endDelta); /** * Return the character at the current position without changing the current * position. */ int peek(); } /** * A [CharacterReader] that reads characters from a character sequence. */ class CharSequenceReader implements CharacterReader { /** * The sequence from which characters will be read. */ final String _sequence; /** * The number of characters in the string. */ int _stringLength = 0; /** * The index, relative to the string, of the last character that was read. */ int _charOffset = 0; /** * Initialize a newly created reader to read the characters in the given * [_sequence]. */ CharSequenceReader(this._sequence) { this._stringLength = _sequence.length; this._charOffset = -1; } @override int get offset => _charOffset; @override void set offset(int offset) { _charOffset = offset; } @override int advance() { if (_charOffset + 1 >= _stringLength) { return -1; } return _sequence.codeUnitAt(++_charOffset); } @override String getString(int start, int endDelta) => _sequence.substring(start, _charOffset + 1 + endDelta).toString(); @override int peek() { if (_charOffset + 1 >= _stringLength) { return -1; } return _sequence.codeUnitAt(_charOffset + 1); } } /** * A token representing a comment. */ class CommentToken extends StringToken { /** * The [Token] that contains this comment. */ Token parent; /** * Initialize a newly created token to represent a token of the given [type] * with the given [value] at the given [offset]. */ CommentToken(TokenType type, String value, int offset) : super(type, value, offset); @override CommentToken copy() => new CommentToken(type, _value, offset); } /** * A documentation comment token. */ class DocumentationCommentToken extends CommentToken { /** * The references embedded within the documentation comment. * This list will be empty unless this is a documentation comment that has * references embedded within it. */ final List references = []; /** * Initialize a newly created token to represent a token of the given [type] * with the given [value] at the given [offset]. */ DocumentationCommentToken(TokenType type, String value, int offset) : super(type, value, offset); @override CommentToken copy() => new DocumentationCommentToken(type, _value, offset); } /** * The keywords in the Dart programming language. */ class Keyword { static const Keyword ASSERT = const Keyword('ASSERT', "assert"); static const Keyword BREAK = const Keyword('BREAK', "break"); static const Keyword CASE = const Keyword('CASE', "case"); static const Keyword CATCH = const Keyword('CATCH', "catch"); static const Keyword CLASS = const Keyword('CLASS', "class"); static const Keyword CONST = const Keyword('CONST', "const"); static const Keyword CONTINUE = const Keyword('CONTINUE', "continue"); static const Keyword DEFAULT = const Keyword('DEFAULT', "default"); static const Keyword DO = const Keyword('DO', "do"); static const Keyword ELSE = const Keyword('ELSE', "else"); static const Keyword ENUM = const Keyword('ENUM', "enum"); static const Keyword EXTENDS = const Keyword('EXTENDS', "extends"); static const Keyword FALSE = const Keyword('FALSE', "false"); static const Keyword FINAL = const Keyword('FINAL', "final"); static const Keyword FINALLY = const Keyword('FINALLY', "finally"); static const Keyword FOR = const Keyword('FOR', "for"); static const Keyword IF = const Keyword('IF', "if"); static const Keyword IN = const Keyword('IN', "in"); static const Keyword IS = const Keyword('IS', "is"); static const Keyword NEW = const Keyword('NEW', "new"); static const Keyword NULL = const Keyword('NULL', "null"); static const Keyword RETHROW = const Keyword('RETHROW', "rethrow"); static const Keyword RETURN = const Keyword('RETURN', "return"); static const Keyword SUPER = const Keyword('SUPER', "super"); static const Keyword SWITCH = const Keyword('SWITCH', "switch"); static const Keyword THIS = const Keyword('THIS', "this"); static const Keyword THROW = const Keyword('THROW', "throw"); static const Keyword TRUE = const Keyword('TRUE', "true"); static const Keyword TRY = const Keyword('TRY', "try"); static const Keyword VAR = const Keyword('VAR', "var"); static const Keyword VOID = const Keyword('VOID', "void"); static const Keyword WHILE = const Keyword('WHILE', "while"); static const Keyword WITH = const Keyword('WITH', "with"); static const Keyword ABSTRACT = const Keyword('ABSTRACT', "abstract", true); static const Keyword AS = const Keyword('AS', "as", true); static const Keyword DEFERRED = const Keyword('DEFERRED', "deferred", true); static const Keyword DYNAMIC = const Keyword('DYNAMIC', "dynamic", true); static const Keyword EXPORT = const Keyword('EXPORT', "export", true); static const Keyword EXTERNAL = const Keyword('EXTERNAL', "external", true); static const Keyword FACTORY = const Keyword('FACTORY', "factory", true); static const Keyword GET = const Keyword('GET', "get", true); static const Keyword IMPLEMENTS = const Keyword('IMPLEMENTS', "implements", true); static const Keyword IMPORT = const Keyword('IMPORT', "import", true); static const Keyword LIBRARY = const Keyword('LIBRARY', "library", true); static const Keyword OPERATOR = const Keyword('OPERATOR', "operator", true); static const Keyword PART = const Keyword('PART', "part", true); static const Keyword SET = const Keyword('SET', "set", true); static const Keyword STATIC = const Keyword('STATIC', "static", true); static const Keyword TYPEDEF = const Keyword('TYPEDEF', "typedef", true); static const List values = const [ ASSERT, BREAK, CASE, CATCH, CLASS, CONST, CONTINUE, DEFAULT, DO, ELSE, ENUM, EXTENDS, FALSE, FINAL, FINALLY, FOR, IF, IN, IS, NEW, NULL, RETHROW, RETURN, SUPER, SWITCH, THIS, THROW, TRUE, TRY, VAR, VOID, WHILE, WITH, ABSTRACT, AS, DEFERRED, DYNAMIC, EXPORT, EXTERNAL, FACTORY, GET, IMPLEMENTS, IMPORT, LIBRARY, OPERATOR, PART, SET, STATIC, TYPEDEF ]; /** * A table mapping the lexemes of keywords to the corresponding keyword. */ static final Map keywords = _createKeywordMap(); /** * The name of the keyword type. */ final String name; /** * The lexeme for the keyword. */ final String syntax; /** * A flag indicating whether the keyword is a pseudo-keyword. Pseudo keywords * can be used as identifiers. */ final bool isPseudoKeyword; /** * Initialize a newly created keyword to have the given [name] and [syntax]. * The keyword is a pseudo-keyword if the [isPseudoKeyword] flag is `true`. */ const Keyword(this.name, this.syntax, [this.isPseudoKeyword = false]); @override String toString() => name; /** * Create a table mapping the lexemes of keywords to the corresponding keyword * and return the table that was created. */ static Map _createKeywordMap() { LinkedHashMap result = new LinkedHashMap(); for (Keyword keyword in values) { result[keyword.syntax] = keyword; } return result; } } /** * A state in a state machine used to scan keywords. */ class KeywordState { /** * An empty transition table used by leaf states. */ static List _EMPTY_TABLE = new List(26); /** * The initial state in the state machine. */ static final KeywordState KEYWORD_STATE = _createKeywordStateTable(); /** * A table mapping characters to the states to which those characters will * transition. (The index into the array is the offset from the character * `'a'` to the transitioning character.) */ final List _table; /** * The keyword that is recognized by this state, or `null` if this state is * not a terminal state. */ Keyword _keyword; /** * Initialize a newly created state to have the given transitions and to * recognize the keyword with the given [syntax]. */ KeywordState(this._table, String syntax) { this._keyword = (syntax == null) ? null : Keyword.keywords[syntax]; } /** * Return the keyword that was recognized by this state, or `null` if this * state does not recognized a keyword. */ Keyword keyword() => _keyword; /** * Return the state that follows this state on a transition of the given * [character], or `null` if there is no valid state reachable from this state * with such a transition. */ KeywordState next(int character) => _table[character - 0x61]; /** * Create the next state in the state machine where we have already recognized * the subset of strings in the given array of [strings] starting at the given * [offset] and having the given [length]. All of these strings have a common * prefix and the next character is at the given [start] index. */ static KeywordState _computeKeywordStateTable( int start, List strings, int offset, int length) { List result = new List(26); assert(length != 0); int chunk = 0x0; int chunkStart = -1; bool isLeaf = false; for (int i = offset; i < offset + length; i++) { if (strings[i].length == start) { isLeaf = true; } if (strings[i].length > start) { int c = strings[i].codeUnitAt(start); if (chunk != c) { if (chunkStart != -1) { result[chunk - 0x61] = _computeKeywordStateTable( start + 1, strings, chunkStart, i - chunkStart); } chunkStart = i; chunk = c; } } } if (chunkStart != -1) { assert(result[chunk - 0x61] == null); result[chunk - 0x61] = _computeKeywordStateTable( start + 1, strings, chunkStart, offset + length - chunkStart); } else { assert(length == 1); return new KeywordState(_EMPTY_TABLE, strings[offset]); } if (isLeaf) { return new KeywordState(result, strings[offset]); } else { return new KeywordState(result, null); } } /** * Create and return the initial state in the state machine. */ static KeywordState _createKeywordStateTable() { List values = Keyword.values; List strings = new List(values.length); for (int i = 0; i < values.length; i++) { strings[i] = values[i].syntax; } strings.sort(); return _computeKeywordStateTable(0, strings, 0, strings.length); } } /** * A token representing a keyword in the language. */ class KeywordToken extends Token { /** * The keyword being represented by this token. */ final Keyword keyword; /** * Initialize a newly created token to represent the given [keyword] at the * given [offset]. */ KeywordToken(this.keyword, int offset) : super(TokenType.KEYWORD, offset); @override String get lexeme => keyword.syntax; @override Token copy() => new KeywordToken(keyword, offset); @override Keyword value() => keyword; } /** * A keyword token that is preceded by comments. */ class KeywordTokenWithComment extends KeywordToken { /** * The first comment in the list of comments that precede this token. */ CommentToken _precedingComment; /** * Initialize a newly created token to to represent the given [keyword] at the * given [offset] and to be preceded by the comments reachable from the given * [comment]. */ KeywordTokenWithComment(Keyword keyword, int offset, this._precedingComment) : super(keyword, offset) { _setCommentParent(_precedingComment); } CommentToken get precedingComments => _precedingComment; void set precedingComments(CommentToken comment) { _precedingComment = comment; _setCommentParent(_precedingComment); } @override void applyDelta(int delta) { super.applyDelta(delta); Token token = precedingComments; while (token != null) { token.applyDelta(delta); token = token.next; } } @override Token copy() => new KeywordTokenWithComment( keyword, offset, copyComments(precedingComments)); } /** * The class `Scanner` implements a scanner for Dart code. * * The lexical structure of Dart is ambiguous without knowledge of the context * in which a token is being scanned. For example, without context we cannot * determine whether source of the form "<<" should be scanned as a single * left-shift operator or as two left angle brackets. This scanner does not have * any context, so it always resolves such conflicts by scanning the longest * possible token. */ class Scanner { /** * The source being scanned. */ final Source source; /** * The reader used to access the characters in the source. */ final CharacterReader _reader; /** * The error listener that will be informed of any errors that are found * during the scan. */ final AnalysisErrorListener _errorListener; /** * The flag specifying whether documentation comments should be parsed. */ bool _preserveComments = true; /** * The token pointing to the head of the linked list of tokens. */ Token _tokens; /** * The last token that was scanned. */ Token _tail; /** * The first token in the list of comment tokens found since the last * non-comment token. */ Token _firstComment; /** * The last token in the list of comment tokens found since the last * non-comment token. */ Token _lastComment; /** * The index of the first character of the current token. */ int _tokenStart = 0; /** * A list containing the offsets of the first character of each line in the * source code. */ List _lineStarts = new List(); /** * A list, treated something like a stack, of tokens representing the * beginning of a matched pair. It is used to pair the end tokens with the * begin tokens. */ List _groupingStack = new List(); /** * The index of the last item in the [_groupingStack], or `-1` if the stack is * empty. */ int _stackEnd = -1; /** * A flag indicating whether any unmatched groups were found during the parse. */ bool _hasUnmatchedGroups = false; /** * A flag indicating whether null-aware operators ('?.', '??', and '??=') * should be tokenized. */ bool enableNullAwareOperators = false; /** * Initialize a newly created scanner to scan characters from the given * [source]. The given character [_reader] will be used to read the characters * in the source. The given [_errorListener] will be informed of any errors * that are found. */ Scanner(this.source, this._reader, this._errorListener) { _tokens = new Token(TokenType.EOF, -1); _tokens.setNext(_tokens); _tail = _tokens; _tokenStart = -1; _lineStarts.add(0); } /** * Return the first token in the token stream that was scanned. */ Token get firstToken => _tokens.next; /** * Return `true` if any unmatched groups were found during the parse. */ bool get hasUnmatchedGroups => _hasUnmatchedGroups; /** * Return an array containing the offsets of the first character of each line * in the source code. */ List get lineStarts => _lineStarts; /** * Set whether documentation tokens should be preserved. */ void set preserveComments(bool preserveComments) { this._preserveComments = preserveComments; } /** * Return the last token that was scanned. */ Token get tail => _tail; /** * Append the given [token] to the end of the token stream being scanned. This * method is intended to be used by subclasses that copy existing tokens and * should not normally be used because it will fail to correctly associate any * comments with the token being passed in. */ void appendToken(Token token) { _tail = _tail.setNext(token); } int bigSwitch(int next) { _beginToken(); if (next == 0xD) { // '\r' next = _reader.advance(); if (next == 0xA) { // '\n' next = _reader.advance(); } recordStartOfLine(); return next; } else if (next == 0xA) { // '\n' next = _reader.advance(); recordStartOfLine(); return next; } else if (next == 0x9 || next == 0x20) { // '\t' || ' ' return _reader.advance(); } if (next == 0x72) { // 'r' int peek = _reader.peek(); if (peek == 0x22 || peek == 0x27) { // '"' || "'" int start = _reader.offset; return _tokenizeString(_reader.advance(), start, true); } } if (0x61 <= next && next <= 0x7A) { // 'a'-'z' return _tokenizeKeywordOrIdentifier(next, true); } if ((0x41 <= next && next <= 0x5A) || next == 0x5F || next == 0x24) { // 'A'-'Z' || '_' || '$' return _tokenizeIdentifier(next, _reader.offset, true); } if (next == 0x3C) { // '<' return _tokenizeLessThan(next); } if (next == 0x3E) { // '>' return _tokenizeGreaterThan(next); } if (next == 0x3D) { // '=' return _tokenizeEquals(next); } if (next == 0x21) { // '!' return _tokenizeExclamation(next); } if (next == 0x2B) { // '+' return _tokenizePlus(next); } if (next == 0x2D) { // '-' return _tokenizeMinus(next); } if (next == 0x2A) { // '*' return _tokenizeMultiply(next); } if (next == 0x25) { // '%' return _tokenizePercent(next); } if (next == 0x26) { // '&' return _tokenizeAmpersand(next); } if (next == 0x7C) { // '|' return _tokenizeBar(next); } if (next == 0x5E) { // '^' return _tokenizeCaret(next); } if (next == 0x5B) { // '[' return _tokenizeOpenSquareBracket(next); } if (next == 0x7E) { // '~' return _tokenizeTilde(next); } if (next == 0x5C) { // '\\' _appendTokenOfType(TokenType.BACKSLASH); return _reader.advance(); } if (next == 0x23) { // '#' return _tokenizeTag(next); } if (next == 0x28) { // '(' _appendBeginToken(TokenType.OPEN_PAREN); return _reader.advance(); } if (next == 0x29) { // ')' _appendEndToken(TokenType.CLOSE_PAREN, TokenType.OPEN_PAREN); return _reader.advance(); } if (next == 0x2C) { // ',' _appendTokenOfType(TokenType.COMMA); return _reader.advance(); } if (next == 0x3A) { // ':' _appendTokenOfType(TokenType.COLON); return _reader.advance(); } if (next == 0x3B) { // ';' _appendTokenOfType(TokenType.SEMICOLON); return _reader.advance(); } if (next == 0x3F) { // '?' return _tokenizeQuestion(); } if (next == 0x5D) { // ']' _appendEndToken( TokenType.CLOSE_SQUARE_BRACKET, TokenType.OPEN_SQUARE_BRACKET); return _reader.advance(); } if (next == 0x60) { // '`' _appendTokenOfType(TokenType.BACKPING); return _reader.advance(); } if (next == 0x7B) { // '{' _appendBeginToken(TokenType.OPEN_CURLY_BRACKET); return _reader.advance(); } if (next == 0x7D) { // '}' _appendEndToken( TokenType.CLOSE_CURLY_BRACKET, TokenType.OPEN_CURLY_BRACKET); return _reader.advance(); } if (next == 0x2F) { // '/' return _tokenizeSlashOrComment(next); } if (next == 0x40) { // '@' _appendTokenOfType(TokenType.AT); return _reader.advance(); } if (next == 0x22 || next == 0x27) { // '"' || "'" return _tokenizeString(next, _reader.offset, false); } if (next == 0x2E) { // '.' return _tokenizeDotOrNumber(next); } if (next == 0x30) { // '0' return _tokenizeHexOrNumber(next); } if (0x31 <= next && next <= 0x39) { // '1'-'9' return _tokenizeNumber(next); } if (next == -1) { // EOF return -1; } _reportError(ScannerErrorCode.ILLEGAL_CHARACTER, [next]); return _reader.advance(); } /** * Record the fact that we are at the beginning of a new line in the source. */ void recordStartOfLine() { _lineStarts.add(_reader.offset); } /** * Record that the source begins on the given [line] and [column] at the * current offset as given by the reader. Both the line and the column are * one-based indexes. The line starts for lines before the given line will not * be correct. * * This method must be invoked at most one time and must be invoked before * scanning begins. The values provided must be sensible. The results are * undefined if these conditions are violated. */ void setSourceStart(int line, int column) { int offset = _reader.offset; if (line < 1 || column < 1 || offset < 0 || (line + column - 2) >= offset) { return; } for (int i = 2; i < line; i++) { _lineStarts.add(1); } _lineStarts.add(offset - column + 1); } /** * Scan the source code to produce a list of tokens representing the source, * and return the first token in the list of tokens that were produced. */ Token tokenize() { int next = _reader.advance(); while (next != -1) { next = bigSwitch(next); } _appendEofToken(); return firstToken; } void _appendBeginToken(TokenType type) { BeginToken token; if (_firstComment == null) { token = new BeginToken(type, _tokenStart); } else { token = new BeginTokenWithComment(type, _tokenStart, _firstComment); _firstComment = null; _lastComment = null; } _tail = _tail.setNext(token); _groupingStack.add(token); _stackEnd++; } void _appendCommentToken(TokenType type, String value) { // Ignore comment tokens if client specified that it doesn't need them. if (!_preserveComments) { return; } // OK, remember comment tokens. CommentToken token; if (_isDocumentationComment(value)) { token = new DocumentationCommentToken(type, value, _tokenStart); } else { token = new CommentToken(type, value, _tokenStart); } if (_firstComment == null) { _firstComment = token; _lastComment = _firstComment; } else { _lastComment = _lastComment.setNext(token); } } void _appendEndToken(TokenType type, TokenType beginType) { Token token; if (_firstComment == null) { token = new Token(type, _tokenStart); } else { token = new TokenWithComment(type, _tokenStart, _firstComment); _firstComment = null; _lastComment = null; } _tail = _tail.setNext(token); if (_stackEnd >= 0) { BeginToken begin = _groupingStack[_stackEnd]; if (begin.type == beginType) { begin.endToken = token; _groupingStack.removeAt(_stackEnd--); } } } void _appendEofToken() { Token eofToken; if (_firstComment == null) { eofToken = new Token(TokenType.EOF, _reader.offset + 1); } else { eofToken = new TokenWithComment( TokenType.EOF, _reader.offset + 1, _firstComment); _firstComment = null; _lastComment = null; } // The EOF token points to itself so that there is always infinite // look-ahead. eofToken.setNext(eofToken); _tail = _tail.setNext(eofToken); if (_stackEnd >= 0) { _hasUnmatchedGroups = true; // TODO(brianwilkerson) Fix the ungrouped tokens? } } void _appendKeywordToken(Keyword keyword) { if (_firstComment == null) { _tail = _tail.setNext(new KeywordToken(keyword, _tokenStart)); } else { _tail = _tail.setNext( new KeywordTokenWithComment(keyword, _tokenStart, _firstComment)); _firstComment = null; _lastComment = null; } } void _appendStringToken(TokenType type, String value) { if (_firstComment == null) { _tail = _tail.setNext(new StringToken(type, value, _tokenStart)); } else { _tail = _tail.setNext( new StringTokenWithComment(type, value, _tokenStart, _firstComment)); _firstComment = null; _lastComment = null; } } void _appendStringTokenWithOffset(TokenType type, String value, int offset) { if (_firstComment == null) { _tail = _tail.setNext(new StringToken(type, value, _tokenStart + offset)); } else { _tail = _tail.setNext(new StringTokenWithComment( type, value, _tokenStart + offset, _firstComment)); _firstComment = null; _lastComment = null; } } void _appendTokenOfType(TokenType type) { if (_firstComment == null) { _tail = _tail.setNext(new Token(type, _tokenStart)); } else { _tail = _tail.setNext(new TokenWithComment(type, _tokenStart, _firstComment)); _firstComment = null; _lastComment = null; } } void _appendTokenOfTypeWithOffset(TokenType type, int offset) { if (_firstComment == null) { _tail = _tail.setNext(new Token(type, offset)); } else { _tail = _tail.setNext(new TokenWithComment(type, offset, _firstComment)); _firstComment = null; _lastComment = null; } } void _beginToken() { _tokenStart = _reader.offset; } /** * Return the beginning token corresponding to a closing brace that was found * while scanning inside a string interpolation expression. Tokens that cannot * be matched with the closing brace will be dropped from the stack. */ BeginToken _findTokenMatchingClosingBraceInInterpolationExpression() { while (_stackEnd >= 0) { BeginToken begin = _groupingStack[_stackEnd]; if (begin.type == TokenType.OPEN_CURLY_BRACKET || begin.type == TokenType.STRING_INTERPOLATION_EXPRESSION) { return begin; } _hasUnmatchedGroups = true; _groupingStack.removeAt(_stackEnd--); } // // We should never get to this point because we wouldn't be inside a string // interpolation expression unless we had previously found the start of the // expression. // return null; } /** * Report an error at the current offset. The [errorCode] is the error code * indicating the nature of the error. The [arguments] are any arguments * needed to complete the error message */ void _reportError(ScannerErrorCode errorCode, [List arguments]) { _errorListener.onError(new AnalysisError.con2( source, _reader.offset, 1, errorCode, arguments)); } int _select(int choice, TokenType yesType, TokenType noType) { int next = _reader.advance(); if (next == choice) { _appendTokenOfType(yesType); return _reader.advance(); } else { _appendTokenOfType(noType); return next; } } int _selectWithOffset( int choice, TokenType yesType, TokenType noType, int offset) { int next = _reader.advance(); if (next == choice) { _appendTokenOfTypeWithOffset(yesType, offset); return _reader.advance(); } else { _appendTokenOfTypeWithOffset(noType, offset); return next; } } int _tokenizeAmpersand(int next) { // && &= & next = _reader.advance(); if (next == 0x26) { _appendTokenOfType(TokenType.AMPERSAND_AMPERSAND); return _reader.advance(); } else if (next == 0x3D) { _appendTokenOfType(TokenType.AMPERSAND_EQ); return _reader.advance(); } else { _appendTokenOfType(TokenType.AMPERSAND); return next; } } int _tokenizeBar(int next) { // | || |= next = _reader.advance(); if (next == 0x7C) { _appendTokenOfType(TokenType.BAR_BAR); return _reader.advance(); } else if (next == 0x3D) { _appendTokenOfType(TokenType.BAR_EQ); return _reader.advance(); } else { _appendTokenOfType(TokenType.BAR); return next; } } int _tokenizeCaret(int next) => _select(0x3D, TokenType.CARET_EQ, TokenType.CARET); int _tokenizeDotOrNumber(int next) { int start = _reader.offset; next = _reader.advance(); if (0x30 <= next && next <= 0x39) { return _tokenizeFractionPart(next, start); } else if (0x2E == next) { return _select( 0x2E, TokenType.PERIOD_PERIOD_PERIOD, TokenType.PERIOD_PERIOD); } else { _appendTokenOfType(TokenType.PERIOD); return next; } } int _tokenizeEquals(int next) { // = == => next = _reader.advance(); if (next == 0x3D) { _appendTokenOfType(TokenType.EQ_EQ); return _reader.advance(); } else if (next == 0x3E) { _appendTokenOfType(TokenType.FUNCTION); return _reader.advance(); } _appendTokenOfType(TokenType.EQ); return next; } int _tokenizeExclamation(int next) { // ! != next = _reader.advance(); if (next == 0x3D) { _appendTokenOfType(TokenType.BANG_EQ); return _reader.advance(); } _appendTokenOfType(TokenType.BANG); return next; } int _tokenizeExponent(int next) { if (next == 0x2B || next == 0x2D) { next = _reader.advance(); } bool hasDigits = false; while (true) { if (0x30 <= next && next <= 0x39) { hasDigits = true; } else { if (!hasDigits) { _reportError(ScannerErrorCode.MISSING_DIGIT); } return next; } next = _reader.advance(); } } int _tokenizeFractionPart(int next, int start) { bool done = false; bool hasDigit = false; LOOP: while (!done) { if (0x30 <= next && next <= 0x39) { hasDigit = true; } else if (0x65 == next || 0x45 == next) { hasDigit = true; next = _tokenizeExponent(_reader.advance()); done = true; continue LOOP; } else { done = true; continue LOOP; } next = _reader.advance(); } if (!hasDigit) { _appendStringToken(TokenType.INT, _reader.getString(start, -2)); if (0x2E == next) { return _selectWithOffset(0x2E, TokenType.PERIOD_PERIOD_PERIOD, TokenType.PERIOD_PERIOD, _reader.offset - 1); } _appendTokenOfTypeWithOffset(TokenType.PERIOD, _reader.offset - 1); return bigSwitch(next); } _appendStringToken( TokenType.DOUBLE, _reader.getString(start, next < 0 ? 0 : -1)); return next; } int _tokenizeGreaterThan(int next) { // > >= >> >>= next = _reader.advance(); if (0x3D == next) { _appendTokenOfType(TokenType.GT_EQ); return _reader.advance(); } else if (0x3E == next) { next = _reader.advance(); if (0x3D == next) { _appendTokenOfType(TokenType.GT_GT_EQ); return _reader.advance(); } else { _appendTokenOfType(TokenType.GT_GT); return next; } } else { _appendTokenOfType(TokenType.GT); return next; } } int _tokenizeHex(int next) { int start = _reader.offset - 1; bool hasDigits = false; while (true) { next = _reader.advance(); if ((0x30 <= next && next <= 0x39) || (0x41 <= next && next <= 0x46) || (0x61 <= next && next <= 0x66)) { hasDigits = true; } else { if (!hasDigits) { _reportError(ScannerErrorCode.MISSING_HEX_DIGIT); } _appendStringToken( TokenType.HEXADECIMAL, _reader.getString(start, next < 0 ? 0 : -1)); return next; } } } int _tokenizeHexOrNumber(int next) { int x = _reader.peek(); if (x == 0x78 || x == 0x58) { _reader.advance(); return _tokenizeHex(x); } return _tokenizeNumber(next); } int _tokenizeIdentifier(int next, int start, bool allowDollar) { while ((0x61 <= next && next <= 0x7A) || (0x41 <= next && next <= 0x5A) || (0x30 <= next && next <= 0x39) || next == 0x5F || (next == 0x24 && allowDollar)) { next = _reader.advance(); } _appendStringToken( TokenType.IDENTIFIER, _reader.getString(start, next < 0 ? 0 : -1)); return next; } int _tokenizeInterpolatedExpression(int next, int start) { _appendBeginToken(TokenType.STRING_INTERPOLATION_EXPRESSION); next = _reader.advance(); while (next != -1) { if (next == 0x7D) { BeginToken begin = _findTokenMatchingClosingBraceInInterpolationExpression(); if (begin == null) { _beginToken(); _appendTokenOfType(TokenType.CLOSE_CURLY_BRACKET); next = _reader.advance(); _beginToken(); return next; } else if (begin.type == TokenType.OPEN_CURLY_BRACKET) { _beginToken(); _appendEndToken( TokenType.CLOSE_CURLY_BRACKET, TokenType.OPEN_CURLY_BRACKET); next = _reader.advance(); _beginToken(); } else if (begin.type == TokenType.STRING_INTERPOLATION_EXPRESSION) { _beginToken(); _appendEndToken(TokenType.CLOSE_CURLY_BRACKET, TokenType.STRING_INTERPOLATION_EXPRESSION); next = _reader.advance(); _beginToken(); return next; } } else { next = bigSwitch(next); } } return next; } int _tokenizeInterpolatedIdentifier(int next, int start) { _appendStringTokenWithOffset( TokenType.STRING_INTERPOLATION_IDENTIFIER, "\$", 0); if ((0x41 <= next && next <= 0x5A) || (0x61 <= next && next <= 0x7A) || next == 0x5F) { _beginToken(); next = _tokenizeKeywordOrIdentifier(next, false); } _beginToken(); return next; } int _tokenizeKeywordOrIdentifier(int next, bool allowDollar) { KeywordState state = KeywordState.KEYWORD_STATE; int start = _reader.offset; while (state != null && 0x61 <= next && next <= 0x7A) { state = state.next(next); next = _reader.advance(); } if (state == null || state.keyword() == null) { return _tokenizeIdentifier(next, start, allowDollar); } if ((0x41 <= next && next <= 0x5A) || (0x30 <= next && next <= 0x39) || next == 0x5F || next == 0x24) { return _tokenizeIdentifier(next, start, allowDollar); } else if (next < 128) { _appendKeywordToken(state.keyword()); return next; } else { return _tokenizeIdentifier(next, start, allowDollar); } } int _tokenizeLessThan(int next) { // < <= << <<= next = _reader.advance(); if (0x3D == next) { _appendTokenOfType(TokenType.LT_EQ); return _reader.advance(); } else if (0x3C == next) { return _select(0x3D, TokenType.LT_LT_EQ, TokenType.LT_LT); } else { _appendTokenOfType(TokenType.LT); return next; } } int _tokenizeMinus(int next) { // - -- -= next = _reader.advance(); if (next == 0x2D) { _appendTokenOfType(TokenType.MINUS_MINUS); return _reader.advance(); } else if (next == 0x3D) { _appendTokenOfType(TokenType.MINUS_EQ); return _reader.advance(); } else { _appendTokenOfType(TokenType.MINUS); return next; } } int _tokenizeMultiLineComment(int next) { int nesting = 1; next = _reader.advance(); while (true) { if (-1 == next) { _reportError(ScannerErrorCode.UNTERMINATED_MULTI_LINE_COMMENT); _appendCommentToken( TokenType.MULTI_LINE_COMMENT, _reader.getString(_tokenStart, 0)); return next; } else if (0x2A == next) { next = _reader.advance(); if (0x2F == next) { --nesting; if (0 == nesting) { _appendCommentToken(TokenType.MULTI_LINE_COMMENT, _reader.getString(_tokenStart, 0)); return _reader.advance(); } else { next = _reader.advance(); } } } else if (0x2F == next) { next = _reader.advance(); if (0x2A == next) { next = _reader.advance(); ++nesting; } } else if (next == 0xD) { next = _reader.advance(); if (next == 0xA) { next = _reader.advance(); } recordStartOfLine(); } else if (next == 0xA) { recordStartOfLine(); next = _reader.advance(); } else { next = _reader.advance(); } } } int _tokenizeMultiLineRawString(int quoteChar, int start) { int next = _reader.advance(); outer: while (next != -1) { while (next != quoteChar) { if (next == -1) { break outer; } else if (next == 0xD) { next = _reader.advance(); if (next == 0xA) { next = _reader.advance(); } recordStartOfLine(); } else if (next == 0xA) { next = _reader.advance(); recordStartOfLine(); } else { next = _reader.advance(); } } next = _reader.advance(); if (next == quoteChar) { next = _reader.advance(); if (next == quoteChar) { _appendStringToken(TokenType.STRING, _reader.getString(start, 0)); return _reader.advance(); } } } _reportError(ScannerErrorCode.UNTERMINATED_STRING_LITERAL); _appendStringToken(TokenType.STRING, _reader.getString(start, 0)); return _reader.advance(); } int _tokenizeMultiLineString(int quoteChar, int start, bool raw) { if (raw) { return _tokenizeMultiLineRawString(quoteChar, start); } int next = _reader.advance(); while (next != -1) { if (next == 0x24) { _appendStringToken(TokenType.STRING, _reader.getString(start, -1)); next = _tokenizeStringInterpolation(start); _beginToken(); start = _reader.offset; continue; } if (next == quoteChar) { next = _reader.advance(); if (next == quoteChar) { next = _reader.advance(); if (next == quoteChar) { _appendStringToken(TokenType.STRING, _reader.getString(start, 0)); return _reader.advance(); } } continue; } if (next == 0x5C) { next = _reader.advance(); if (next == -1) { break; } if (next == 0xD) { next = _reader.advance(); if (next == 0xA) { next = _reader.advance(); } recordStartOfLine(); } else if (next == 0xA) { recordStartOfLine(); next = _reader.advance(); } else { next = _reader.advance(); } } else if (next == 0xD) { next = _reader.advance(); if (next == 0xA) { next = _reader.advance(); } recordStartOfLine(); } else if (next == 0xA) { recordStartOfLine(); next = _reader.advance(); } else { next = _reader.advance(); } } _reportError(ScannerErrorCode.UNTERMINATED_STRING_LITERAL); if (start == _reader.offset) { _appendStringTokenWithOffset(TokenType.STRING, "", 1); } else { _appendStringToken(TokenType.STRING, _reader.getString(start, 0)); } return _reader.advance(); } int _tokenizeMultiply(int next) => _select(0x3D, TokenType.STAR_EQ, TokenType.STAR); int _tokenizeNumber(int next) { int start = _reader.offset; while (true) { next = _reader.advance(); if (0x30 <= next && next <= 0x39) { continue; } else if (next == 0x2E) { return _tokenizeFractionPart(_reader.advance(), start); } else if (next == 0x65 || next == 0x45) { return _tokenizeFractionPart(next, start); } else { _appendStringToken( TokenType.INT, _reader.getString(start, next < 0 ? 0 : -1)); return next; } } } int _tokenizeOpenSquareBracket(int next) { // [ [] []= next = _reader.advance(); if (next == 0x5D) { return _select(0x3D, TokenType.INDEX_EQ, TokenType.INDEX); } else { _appendBeginToken(TokenType.OPEN_SQUARE_BRACKET); return next; } } int _tokenizePercent(int next) => _select(0x3D, TokenType.PERCENT_EQ, TokenType.PERCENT); int _tokenizePlus(int next) { // + ++ += next = _reader.advance(); if (0x2B == next) { _appendTokenOfType(TokenType.PLUS_PLUS); return _reader.advance(); } else if (0x3D == next) { _appendTokenOfType(TokenType.PLUS_EQ); return _reader.advance(); } else { _appendTokenOfType(TokenType.PLUS); return next; } } int _tokenizeQuestion() { // ? ?. ?? ??= int next = _reader.advance(); if (enableNullAwareOperators && next == 0x2E) { // '.' _appendTokenOfType(TokenType.QUESTION_PERIOD); return _reader.advance(); } else if (enableNullAwareOperators && next == 0x3F) { // '?' next = _reader.advance(); if (next == 0x3D) { // '=' _appendTokenOfType(TokenType.QUESTION_QUESTION_EQ); return _reader.advance(); } else { _appendTokenOfType(TokenType.QUESTION_QUESTION); return next; } } else { _appendTokenOfType(TokenType.QUESTION); return next; } } int _tokenizeSingleLineComment(int next) { while (true) { next = _reader.advance(); if (-1 == next) { _appendCommentToken( TokenType.SINGLE_LINE_COMMENT, _reader.getString(_tokenStart, 0)); return next; } else if (0xA == next || 0xD == next) { _appendCommentToken( TokenType.SINGLE_LINE_COMMENT, _reader.getString(_tokenStart, -1)); return next; } } } int _tokenizeSingleLineRawString(int next, int quoteChar, int start) { next = _reader.advance(); while (next != -1) { if (next == quoteChar) { _appendStringToken(TokenType.STRING, _reader.getString(start, 0)); return _reader.advance(); } else if (next == 0xD || next == 0xA) { _reportError(ScannerErrorCode.UNTERMINATED_STRING_LITERAL); _appendStringToken(TokenType.STRING, _reader.getString(start, -1)); return _reader.advance(); } next = _reader.advance(); } _reportError(ScannerErrorCode.UNTERMINATED_STRING_LITERAL); _appendStringToken(TokenType.STRING, _reader.getString(start, 0)); return _reader.advance(); } int _tokenizeSingleLineString(int next, int quoteChar, int start) { while (next != quoteChar) { if (next == 0x5C) { next = _reader.advance(); } else if (next == 0x24) { _appendStringToken(TokenType.STRING, _reader.getString(start, -1)); next = _tokenizeStringInterpolation(start); _beginToken(); start = _reader.offset; continue; } if (next <= 0xD && (next == 0xA || next == 0xD || next == -1)) { _reportError(ScannerErrorCode.UNTERMINATED_STRING_LITERAL); if (start == _reader.offset) { _appendStringTokenWithOffset(TokenType.STRING, "", 1); } else if (next == -1) { _appendStringToken(TokenType.STRING, _reader.getString(start, 0)); } else { _appendStringToken(TokenType.STRING, _reader.getString(start, -1)); } return _reader.advance(); } next = _reader.advance(); } _appendStringToken(TokenType.STRING, _reader.getString(start, 0)); return _reader.advance(); } int _tokenizeSlashOrComment(int next) { next = _reader.advance(); if (0x2A == next) { return _tokenizeMultiLineComment(next); } else if (0x2F == next) { return _tokenizeSingleLineComment(next); } else if (0x3D == next) { _appendTokenOfType(TokenType.SLASH_EQ); return _reader.advance(); } else { _appendTokenOfType(TokenType.SLASH); return next; } } int _tokenizeString(int next, int start, bool raw) { int quoteChar = next; next = _reader.advance(); if (quoteChar == next) { next = _reader.advance(); if (quoteChar == next) { // Multiline string. return _tokenizeMultiLineString(quoteChar, start, raw); } else { // Empty string. _appendStringToken(TokenType.STRING, _reader.getString(start, -1)); return next; } } if (raw) { return _tokenizeSingleLineRawString(next, quoteChar, start); } else { return _tokenizeSingleLineString(next, quoteChar, start); } } int _tokenizeStringInterpolation(int start) { _beginToken(); int next = _reader.advance(); if (next == 0x7B) { return _tokenizeInterpolatedExpression(next, start); } else { return _tokenizeInterpolatedIdentifier(next, start); } } int _tokenizeTag(int next) { // # or #!.*[\n\r] if (_reader.offset == 0) { if (_reader.peek() == 0x21) { do { next = _reader.advance(); } while (next != 0xA && next != 0xD && next > 0); _appendStringToken( TokenType.SCRIPT_TAG, _reader.getString(_tokenStart, 0)); return next; } } _appendTokenOfType(TokenType.HASH); return _reader.advance(); } int _tokenizeTilde(int next) { // ~ ~/ ~/= next = _reader.advance(); if (next == 0x2F) { return _select(0x3D, TokenType.TILDE_SLASH_EQ, TokenType.TILDE_SLASH); } else { _appendTokenOfType(TokenType.TILDE); return next; } } /** * Checks if [value] is a single-line or multi-line comment. */ static bool _isDocumentationComment(String value) { return StringUtilities.startsWith3(value, 0, 0x2F, 0x2F, 0x2F) || StringUtilities.startsWith3(value, 0, 0x2F, 0x2A, 0x2A); } } /** * The error codes used for errors detected by the scanner. */ class ScannerErrorCode extends ErrorCode { static const ScannerErrorCode ILLEGAL_CHARACTER = const ScannerErrorCode('ILLEGAL_CHARACTER', "Illegal character {0}"); static const ScannerErrorCode MISSING_DIGIT = const ScannerErrorCode('MISSING_DIGIT', "Decimal digit expected"); static const ScannerErrorCode MISSING_HEX_DIGIT = const ScannerErrorCode('MISSING_HEX_DIGIT', "Hexidecimal digit expected"); static const ScannerErrorCode MISSING_QUOTE = const ScannerErrorCode('MISSING_QUOTE', "Expected quote (' or \")"); static const ScannerErrorCode UNABLE_GET_CONTENT = const ScannerErrorCode( 'UNABLE_GET_CONTENT', "Unable to get content: {0}"); static const ScannerErrorCode UNTERMINATED_MULTI_LINE_COMMENT = const ScannerErrorCode( 'UNTERMINATED_MULTI_LINE_COMMENT', "Unterminated multi-line comment"); static const ScannerErrorCode UNTERMINATED_STRING_LITERAL = const ScannerErrorCode( 'UNTERMINATED_STRING_LITERAL', "Unterminated string literal"); /** * Initialize a newly created error code to have the given [name]. The message * associated with the error will be created from the given [message] * template. The correction associated with the error will be created from the * given [correction] template. */ const ScannerErrorCode(String name, String message, [String correction]) : super(name, message, correction); @override ErrorSeverity get errorSeverity => ErrorSeverity.ERROR; @override ErrorType get type => ErrorType.SYNTACTIC_ERROR; } /** * A token whose value is independent of it's type. */ class StringToken extends Token { /** * The lexeme represented by this token. */ String _value; /** * Initialize a newly created token to represent a token of the given [type] * with the given [value] at the given [offset]. */ StringToken(TokenType type, String value, int offset) : super(type, offset) { this._value = StringUtilities.intern(value); } @override String get lexeme => _value; @override Token copy() => new StringToken(type, _value, offset); @override String value() => _value; } /** * A string token that is preceded by comments. */ class StringTokenWithComment extends StringToken { /** * The first comment in the list of comments that precede this token. */ CommentToken _precedingComment; /** * Initialize a newly created token to have the given [type] at the given * [offset] and to be preceded by the comments reachable from the given * [comment]. */ StringTokenWithComment( TokenType type, String value, int offset, this._precedingComment) : super(type, value, offset) { _setCommentParent(_precedingComment); } CommentToken get precedingComments => _precedingComment; void set precedingComments(CommentToken comment) { _precedingComment = comment; _setCommentParent(_precedingComment); } @override void applyDelta(int delta) { super.applyDelta(delta); Token token = precedingComments; while (token != null) { token.applyDelta(delta); token = token.next; } } @override Token copy() => new StringTokenWithComment( type, lexeme, offset, copyComments(precedingComments)); } /** * A [CharacterReader] that reads characters from a character sequence, but adds * a delta when reporting the current character offset so that the character * sequence can be a subsequence from a larger sequence. */ class SubSequenceReader extends CharSequenceReader { /** * The offset from the beginning of the file to the beginning of the source * being scanned. */ final int _offsetDelta; /** * Initialize a newly created reader to read the characters in the given * [sequence]. The [_offsetDelta] is the offset from the beginning of the file * to the beginning of the source being scanned */ SubSequenceReader(String sequence, this._offsetDelta) : super(sequence); @override int get offset => _offsetDelta + super.offset; @override void set offset(int offset) { super.offset = offset - _offsetDelta; } @override String getString(int start, int endDelta) => super.getString(start - _offsetDelta, endDelta); } /** * A token whose value is independent of it's type. */ class SyntheticStringToken extends StringToken { /** * Initialize a newly created token to represent a token of the given [type] * with the given [value] at the given [offset]. */ SyntheticStringToken(TokenType type, String value, int offset) : super(type, value, offset); @override bool get isSynthetic => true; } /** * A token that was scanned from the input. Each token knows which tokens * precede and follow it, acting as a link in a doubly linked list of tokens. */ class Token { /** * The type of the token. */ final TokenType type; /** * The offset from the beginning of the file to the first character in the * token. */ int offset = 0; /** * The previous token in the token stream. */ Token previous; /** * The next token in the token stream. */ Token _next; /** * Initialize a newly created token to have the given [type] and [offset]. */ Token(this.type, int offset) { this.offset = offset; } /** * Return the offset from the beginning of the file to the character after the * last character of the token. */ int get end => offset + length; /** * Return `true` if this token represents an operator. */ bool get isOperator => type.isOperator; /** * Return `true` if this token is a synthetic token. A synthetic token is a * token that was introduced by the parser in order to recover from an error * in the code. */ bool get isSynthetic => length == 0; /** * Return `true` if this token represents an operator that can be defined by * users. */ bool get isUserDefinableOperator => type.isUserDefinableOperator; /** * Return the number of characters in the node's source range. */ int get length => lexeme.length; /** * Return the lexeme that represents this token. */ String get lexeme => type.lexeme; /** * Return the next token in the token stream. */ Token get next => _next; /** * Return the first comment in the list of comments that precede this token, * or `null` if there are no comments preceding this token. Additional * comments can be reached by following the token stream using [next] until * `null` is returned. * * For example, if the original contents were "/* one */ /* two */ id", then * the first preceding comment token will have a lexeme of "/* one */" and * the next comment token will have a lexeme of "/* two */". */ CommentToken get precedingComments => null; /** * Apply (add) the given [delta] to this token's offset. */ void applyDelta(int delta) { offset += delta; } /** * Return a newly created token that is a copy of this token but that is not a * part of any token stream. */ Token copy() => new Token(type, offset); /** * Copy a linked list of comment tokens identical to the given comment tokens. */ Token copyComments(Token token) { if (token == null) { return null; } Token head = token.copy(); Token tail = head; token = token.next; while (token != null) { tail = tail.setNext(token.copy()); token = token.next; } return head; } /** * Return `true` if this token has any one of the given [types]. */ bool matchesAny(List types) { for (TokenType type in types) { if (this.type == type) { return true; } } return false; } /** * Set the next token in the token stream to the given [token]. This has the * side-effect of setting this token to be the previous token for the given * token. Return the token that was passed in. */ Token setNext(Token token) { _next = token; token.previous = this; return token; } /** * Set the next token in the token stream to the given token without changing * which token is the previous token for the given token. Return the token * that was passed in. */ Token setNextWithoutSettingPrevious(Token token) { _next = token; return token; } @override String toString() => lexeme; /** * Return the value of this token. For keyword tokens, this is the keyword * associated with the token, for other tokens it is the lexeme associated * with the token. */ Object value() => type.lexeme; /** * Sets the `parent` property to `this` for the given [comment] and all the * next tokens. */ void _setCommentParent(CommentToken comment) { while (comment != null) { comment.parent = this; comment = comment.next; } } /** * Compare the given [tokens] to find the token that appears first in the * source being parsed. That is, return the left-most of all of the tokens. * The list must be non-`null`, but the elements of the list are allowed to be * `null`. Return the token with the smallest offset, or `null` if the list is * empty or if all of the elements of the list are `null`. */ static Token lexicallyFirst(List tokens) { Token first = null; int offset = -1; for (Token token in tokens) { if (token != null && (offset < 0 || token.offset < offset)) { first = token; offset = token.offset; } } return first; } } /** * The classes (or groups) of tokens with a similar use. */ class TokenClass { /** * A value used to indicate that the token type is not part of any specific * class of token. */ static const TokenClass NO_CLASS = const TokenClass('NO_CLASS'); /** * A value used to indicate that the token type is an additive operator. */ static const TokenClass ADDITIVE_OPERATOR = const TokenClass('ADDITIVE_OPERATOR', 13); /** * A value used to indicate that the token type is an assignment operator. */ static const TokenClass ASSIGNMENT_OPERATOR = const TokenClass('ASSIGNMENT_OPERATOR', 1); /** * A value used to indicate that the token type is a bitwise-and operator. */ static const TokenClass BITWISE_AND_OPERATOR = const TokenClass('BITWISE_AND_OPERATOR', 11); /** * A value used to indicate that the token type is a bitwise-or operator. */ static const TokenClass BITWISE_OR_OPERATOR = const TokenClass('BITWISE_OR_OPERATOR', 9); /** * A value used to indicate that the token type is a bitwise-xor operator. */ static const TokenClass BITWISE_XOR_OPERATOR = const TokenClass('BITWISE_XOR_OPERATOR', 10); /** * A value used to indicate that the token type is a cascade operator. */ static const TokenClass CASCADE_OPERATOR = const TokenClass('CASCADE_OPERATOR', 2); /** * A value used to indicate that the token type is a conditional operator. */ static const TokenClass CONDITIONAL_OPERATOR = const TokenClass('CONDITIONAL_OPERATOR', 3); /** * A value used to indicate that the token type is an equality operator. */ static const TokenClass EQUALITY_OPERATOR = const TokenClass('EQUALITY_OPERATOR', 7); /** * A value used to indicate that the token type is an if-null operator. */ static const TokenClass IF_NULL_OPERATOR = const TokenClass('IF_NULL_OPERATOR', 4); /** * A value used to indicate that the token type is a logical-and operator. */ static const TokenClass LOGICAL_AND_OPERATOR = const TokenClass('LOGICAL_AND_OPERATOR', 6); /** * A value used to indicate that the token type is a logical-or operator. */ static const TokenClass LOGICAL_OR_OPERATOR = const TokenClass('LOGICAL_OR_OPERATOR', 5); /** * A value used to indicate that the token type is a multiplicative operator. */ static const TokenClass MULTIPLICATIVE_OPERATOR = const TokenClass('MULTIPLICATIVE_OPERATOR', 14); /** * A value used to indicate that the token type is a relational operator. */ static const TokenClass RELATIONAL_OPERATOR = const TokenClass('RELATIONAL_OPERATOR', 8); /** * A value used to indicate that the token type is a shift operator. */ static const TokenClass SHIFT_OPERATOR = const TokenClass('SHIFT_OPERATOR', 12); /** * A value used to indicate that the token type is a unary operator. */ static const TokenClass UNARY_POSTFIX_OPERATOR = const TokenClass('UNARY_POSTFIX_OPERATOR', 16); /** * A value used to indicate that the token type is a unary operator. */ static const TokenClass UNARY_PREFIX_OPERATOR = const TokenClass('UNARY_PREFIX_OPERATOR', 15); /** * The name of the token class. */ final String name; /** * The precedence of tokens of this class, or `0` if the such tokens do not * represent an operator. */ final int precedence; const TokenClass(this.name, [this.precedence = 0]); @override String toString() => name; } /** * The types of tokens that can be returned by the scanner. */ class TokenType { /** * The type of the token that marks the end of the input. */ static const TokenType EOF = const TokenType_EOF('EOF'); static const TokenType DOUBLE = const TokenType('DOUBLE'); static const TokenType HEXADECIMAL = const TokenType('HEXADECIMAL'); static const TokenType IDENTIFIER = const TokenType('IDENTIFIER'); static const TokenType INT = const TokenType('INT'); static const TokenType KEYWORD = const TokenType('KEYWORD'); static const TokenType MULTI_LINE_COMMENT = const TokenType('MULTI_LINE_COMMENT'); static const TokenType SCRIPT_TAG = const TokenType('SCRIPT_TAG'); static const TokenType SINGLE_LINE_COMMENT = const TokenType('SINGLE_LINE_COMMENT'); static const TokenType STRING = const TokenType('STRING'); static const TokenType AMPERSAND = const TokenType('AMPERSAND', TokenClass.BITWISE_AND_OPERATOR, "&"); static const TokenType AMPERSAND_AMPERSAND = const TokenType( 'AMPERSAND_AMPERSAND', TokenClass.LOGICAL_AND_OPERATOR, "&&"); static const TokenType AMPERSAND_EQ = const TokenType('AMPERSAND_EQ', TokenClass.ASSIGNMENT_OPERATOR, "&="); static const TokenType AT = const TokenType('AT', TokenClass.NO_CLASS, "@"); static const TokenType BANG = const TokenType('BANG', TokenClass.UNARY_PREFIX_OPERATOR, "!"); static const TokenType BANG_EQ = const TokenType('BANG_EQ', TokenClass.EQUALITY_OPERATOR, "!="); static const TokenType BAR = const TokenType('BAR', TokenClass.BITWISE_OR_OPERATOR, "|"); static const TokenType BAR_BAR = const TokenType('BAR_BAR', TokenClass.LOGICAL_OR_OPERATOR, "||"); static const TokenType BAR_EQ = const TokenType('BAR_EQ', TokenClass.ASSIGNMENT_OPERATOR, "|="); static const TokenType COLON = const TokenType('COLON', TokenClass.NO_CLASS, ":"); static const TokenType COMMA = const TokenType('COMMA', TokenClass.NO_CLASS, ","); static const TokenType CARET = const TokenType('CARET', TokenClass.BITWISE_XOR_OPERATOR, "^"); static const TokenType CARET_EQ = const TokenType('CARET_EQ', TokenClass.ASSIGNMENT_OPERATOR, "^="); static const TokenType CLOSE_CURLY_BRACKET = const TokenType('CLOSE_CURLY_BRACKET', TokenClass.NO_CLASS, "}"); static const TokenType CLOSE_PAREN = const TokenType('CLOSE_PAREN', TokenClass.NO_CLASS, ")"); static const TokenType CLOSE_SQUARE_BRACKET = const TokenType('CLOSE_SQUARE_BRACKET', TokenClass.NO_CLASS, "]"); static const TokenType EQ = const TokenType('EQ', TokenClass.ASSIGNMENT_OPERATOR, "="); static const TokenType EQ_EQ = const TokenType('EQ_EQ', TokenClass.EQUALITY_OPERATOR, "=="); static const TokenType FUNCTION = const TokenType('FUNCTION', TokenClass.NO_CLASS, "=>"); static const TokenType GT = const TokenType('GT', TokenClass.RELATIONAL_OPERATOR, ">"); static const TokenType GT_EQ = const TokenType('GT_EQ', TokenClass.RELATIONAL_OPERATOR, ">="); static const TokenType GT_GT = const TokenType('GT_GT', TokenClass.SHIFT_OPERATOR, ">>"); static const TokenType GT_GT_EQ = const TokenType('GT_GT_EQ', TokenClass.ASSIGNMENT_OPERATOR, ">>="); static const TokenType HASH = const TokenType('HASH', TokenClass.NO_CLASS, "#"); static const TokenType INDEX = const TokenType('INDEX', TokenClass.UNARY_POSTFIX_OPERATOR, "[]"); static const TokenType INDEX_EQ = const TokenType('INDEX_EQ', TokenClass.UNARY_POSTFIX_OPERATOR, "[]="); static const TokenType IS = const TokenType('IS', TokenClass.RELATIONAL_OPERATOR, "is"); static const TokenType LT = const TokenType('LT', TokenClass.RELATIONAL_OPERATOR, "<"); static const TokenType LT_EQ = const TokenType('LT_EQ', TokenClass.RELATIONAL_OPERATOR, "<="); static const TokenType LT_LT = const TokenType('LT_LT', TokenClass.SHIFT_OPERATOR, "<<"); static const TokenType LT_LT_EQ = const TokenType('LT_LT_EQ', TokenClass.ASSIGNMENT_OPERATOR, "<<="); static const TokenType MINUS = const TokenType('MINUS', TokenClass.ADDITIVE_OPERATOR, "-"); static const TokenType MINUS_EQ = const TokenType('MINUS_EQ', TokenClass.ASSIGNMENT_OPERATOR, "-="); static const TokenType MINUS_MINUS = const TokenType('MINUS_MINUS', TokenClass.UNARY_PREFIX_OPERATOR, "--"); static const TokenType OPEN_CURLY_BRACKET = const TokenType('OPEN_CURLY_BRACKET', TokenClass.NO_CLASS, "{"); static const TokenType OPEN_PAREN = const TokenType('OPEN_PAREN', TokenClass.UNARY_POSTFIX_OPERATOR, "("); static const TokenType OPEN_SQUARE_BRACKET = const TokenType( 'OPEN_SQUARE_BRACKET', TokenClass.UNARY_POSTFIX_OPERATOR, "["); static const TokenType PERCENT = const TokenType('PERCENT', TokenClass.MULTIPLICATIVE_OPERATOR, "%"); static const TokenType PERCENT_EQ = const TokenType('PERCENT_EQ', TokenClass.ASSIGNMENT_OPERATOR, "%="); static const TokenType PERIOD = const TokenType('PERIOD', TokenClass.UNARY_POSTFIX_OPERATOR, "."); static const TokenType PERIOD_PERIOD = const TokenType('PERIOD_PERIOD', TokenClass.CASCADE_OPERATOR, ".."); static const TokenType PLUS = const TokenType('PLUS', TokenClass.ADDITIVE_OPERATOR, "+"); static const TokenType PLUS_EQ = const TokenType('PLUS_EQ', TokenClass.ASSIGNMENT_OPERATOR, "+="); static const TokenType PLUS_PLUS = const TokenType('PLUS_PLUS', TokenClass.UNARY_PREFIX_OPERATOR, "++"); static const TokenType QUESTION = const TokenType('QUESTION', TokenClass.CONDITIONAL_OPERATOR, "?"); static const TokenType QUESTION_PERIOD = const TokenType( 'QUESTION_PERIOD', TokenClass.UNARY_POSTFIX_OPERATOR, '?.'); static const TokenType QUESTION_QUESTION = const TokenType('QUESTION_QUESTION', TokenClass.IF_NULL_OPERATOR, '??'); static const TokenType QUESTION_QUESTION_EQ = const TokenType( 'QUESTION_QUESTION_EQ', TokenClass.ASSIGNMENT_OPERATOR, '??='); static const TokenType SEMICOLON = const TokenType('SEMICOLON', TokenClass.NO_CLASS, ";"); static const TokenType SLASH = const TokenType('SLASH', TokenClass.MULTIPLICATIVE_OPERATOR, "/"); static const TokenType SLASH_EQ = const TokenType('SLASH_EQ', TokenClass.ASSIGNMENT_OPERATOR, "/="); static const TokenType STAR = const TokenType('STAR', TokenClass.MULTIPLICATIVE_OPERATOR, "*"); static const TokenType STAR_EQ = const TokenType('STAR_EQ', TokenClass.ASSIGNMENT_OPERATOR, "*="); static const TokenType STRING_INTERPOLATION_EXPRESSION = const TokenType( 'STRING_INTERPOLATION_EXPRESSION', TokenClass.NO_CLASS, "\${"); static const TokenType STRING_INTERPOLATION_IDENTIFIER = const TokenType( 'STRING_INTERPOLATION_IDENTIFIER', TokenClass.NO_CLASS, "\$"); static const TokenType TILDE = const TokenType('TILDE', TokenClass.UNARY_PREFIX_OPERATOR, "~"); static const TokenType TILDE_SLASH = const TokenType('TILDE_SLASH', TokenClass.MULTIPLICATIVE_OPERATOR, "~/"); static const TokenType TILDE_SLASH_EQ = const TokenType('TILDE_SLASH_EQ', TokenClass.ASSIGNMENT_OPERATOR, "~/="); static const TokenType BACKPING = const TokenType('BACKPING', TokenClass.NO_CLASS, "`"); static const TokenType BACKSLASH = const TokenType('BACKSLASH', TokenClass.NO_CLASS, "\\"); static const TokenType PERIOD_PERIOD_PERIOD = const TokenType('PERIOD_PERIOD_PERIOD', TokenClass.NO_CLASS, "..."); /** * The class of the token. */ final TokenClass _tokenClass; /** * The name of the token type. */ final String name; /** * The lexeme that defines this type of token, or `null` if there is more than * one possible lexeme for this type of token. */ final String lexeme; const TokenType(this.name, [this._tokenClass = TokenClass.NO_CLASS, this.lexeme = null]); /** * Return `true` if this type of token represents an additive operator. */ bool get isAdditiveOperator => _tokenClass == TokenClass.ADDITIVE_OPERATOR; /** * Return `true` if this type of token represents an assignment operator. */ bool get isAssignmentOperator => _tokenClass == TokenClass.ASSIGNMENT_OPERATOR; /** * Return `true` if this type of token represents an associative operator. An * associative operator is an operator for which the following equality is * true: `(a * b) * c == a * (b * c)`. In other words, if the result of * applying the operator to multiple operands does not depend on the order in * which those applications occur. * * Note: This method considers the logical-and and logical-or operators to be * associative, even though the order in which the application of those * operators can have an effect because evaluation of the right-hand operand * is conditional. */ bool get isAssociativeOperator => this == AMPERSAND || this == AMPERSAND_AMPERSAND || this == BAR || this == BAR_BAR || this == CARET || this == PLUS || this == STAR; /** * Return `true` if this type of token represents an equality operator. */ bool get isEqualityOperator => _tokenClass == TokenClass.EQUALITY_OPERATOR; /** * Return `true` if this type of token represents an increment operator. */ bool get isIncrementOperator => identical(lexeme, "++") || identical(lexeme, "--"); /** * Return `true` if this type of token represents a multiplicative operator. */ bool get isMultiplicativeOperator => _tokenClass == TokenClass.MULTIPLICATIVE_OPERATOR; /** * Return `true` if this token type represents an operator. */ bool get isOperator => _tokenClass != TokenClass.NO_CLASS && this != OPEN_PAREN && this != OPEN_SQUARE_BRACKET && this != PERIOD; /** * Return `true` if this type of token represents a relational operator. */ bool get isRelationalOperator => _tokenClass == TokenClass.RELATIONAL_OPERATOR; /** * Return `true` if this type of token represents a shift operator. */ bool get isShiftOperator => _tokenClass == TokenClass.SHIFT_OPERATOR; /** * Return `true` if this type of token represents a unary postfix operator. */ bool get isUnaryPostfixOperator => _tokenClass == TokenClass.UNARY_POSTFIX_OPERATOR; /** * Return `true` if this type of token represents a unary prefix operator. */ bool get isUnaryPrefixOperator => _tokenClass == TokenClass.UNARY_PREFIX_OPERATOR; /** * Return `true` if this token type represents an operator that can be defined * by users. */ bool get isUserDefinableOperator => identical(lexeme, "==") || identical(lexeme, "~") || identical(lexeme, "[]") || identical(lexeme, "[]=") || identical(lexeme, "*") || identical(lexeme, "/") || identical(lexeme, "%") || identical(lexeme, "~/") || identical(lexeme, "+") || identical(lexeme, "-") || identical(lexeme, "<<") || identical(lexeme, ">>") || identical(lexeme, ">=") || identical(lexeme, ">") || identical(lexeme, "<=") || identical(lexeme, "<") || identical(lexeme, "&") || identical(lexeme, "^") || identical(lexeme, "|"); /** * Return the precedence of the token, or `0` if the token does not represent * an operator. */ int get precedence => _tokenClass.precedence; @override String toString() => name; } class TokenType_EOF extends TokenType { const TokenType_EOF(String name) : super(name, TokenClass.NO_CLASS, ""); @override String toString() => "-eof-"; } /** * A normal token that is preceded by comments. */ class TokenWithComment extends Token { /** * The first comment in the list of comments that precede this token. */ CommentToken _precedingComment; /** * Initialize a newly created token to have the given [type] at the given * [offset] and to be preceded by the comments reachable from the given * [comment]. */ TokenWithComment(TokenType type, int offset, this._precedingComment) : super(type, offset) { _setCommentParent(_precedingComment); } CommentToken get precedingComments => _precedingComment; void set precedingComments(CommentToken comment) { _precedingComment = comment; _setCommentParent(_precedingComment); } @override Token copy() => new TokenWithComment(type, offset, precedingComments); }