blob: a29b1e65c1a379f917288119e6c0026848e9559d [file] [log] [blame]
/*
* Copyright (c) 2012, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except
* in compliance with the License. You may obtain a copy of the License at
*
* http://www.eclipse.org/legal/epl-v10.html
*
* Unless required by applicable law or agreed to in writing, software distributed under the License
* is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
* or implied. See the License for the specific language governing permissions and limitations under
* the License.
*/
package com.google.dart.engine.scanner;
import com.google.dart.engine.error.AnalysisError;
import com.google.dart.engine.error.AnalysisErrorListener;
import com.google.dart.engine.source.Source;
import com.google.dart.engine.utilities.collection.IntList;
import com.google.dart.engine.utilities.instrumentation.Instrumentation;
import com.google.dart.engine.utilities.instrumentation.InstrumentationBuilder;
import java.util.ArrayList;
import java.util.List;
/**
* The class {@code Scanner} implements a scanner for Dart code.
* <p>
* 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.
*
* @coverage dart.engine.parser
*/
public class Scanner {
/**
* The source being scanned.
*/
private final Source source;
/**
* The reader used to access the characters in the source.
*/
private CharacterReader reader;
/**
* The error listener that will be informed of any errors that are found during the scan.
*/
private AnalysisErrorListener errorListener;
/**
* The flag specifying if documentation comments should be parsed.
*/
private boolean preserveComments = true;
/**
* The token pointing to the head of the linked list of tokens.
*/
private final Token tokens;
/**
* The last token that was scanned.
*/
private Token tail;
/**
* The first token in the list of comment tokens found since the last non-comment token.
*/
private Token firstComment;
/**
* The last token in the list of comment tokens found since the last non-comment token.
*/
private Token lastComment;
/**
* The index of the first character of the current token.
*/
private int tokenStart;
/**
* A list containing the offsets of the first character of each line in the source code.
*/
private IntList lineStarts = new IntList(1024);
/**
* 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.
*/
private List<BeginToken> groupingStack = new ArrayList<BeginToken>(128);
/**
* The index of the last item in the {@link #groupingStack}, or {@code -1} if the stack is empty.
*/
private int stackEnd = -1;
/**
* A flag indicating whether any unmatched groups were found during the parse.
*/
private boolean hasUnmatchedGroups = false;
/**
* Initialize a newly created scanner.
*
* @param source the source being scanned
* @param reader the character reader used to read the characters in the source
* @param errorListener the error listener that will be informed of any errors that are found
*/
public Scanner(Source source, CharacterReader reader, AnalysisErrorListener errorListener) {
this.source = source;
this.reader = reader;
this.errorListener = errorListener;
tokens = new Token(TokenType.EOF, -1);
tokens.setNext(tokens);
tail = tokens;
tokenStart = -1;
lineStarts.add(0);
}
/**
* Return an array containing the offsets of the first character of each line in the source code.
*
* @return an array containing the offsets of the first character of each line in the source code
*/
public int[] getLineStarts() {
return lineStarts.toArray();
}
/**
* Return {@code true} if any unmatched groups were found during the parse.
*
* @return {@code true} if any unmatched groups were found during the parse
*/
public boolean hasUnmatchedGroups() {
return hasUnmatchedGroups;
}
/**
* Set whether documentation tokens should be scanned.
*
* @param preserveComments {@code true} if documentation tokens should be scanned
*/
public void setPreserveComments(boolean preserveComments) {
this.preserveComments = preserveComments;
}
/**
* Record that the source begins on the given line and column at the current offset as given by
* the reader. The line starts for lines before the given line will not be correct.
* <p>
* 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.
*
* @param line the one-based index of the line containing the first character of the source
* @param column the one-based index of the column in which the first character of the source
* occurs
*/
public void setSourceStart(int line, int column) {
int offset = reader.getOffset();
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.
*
* @return the first token in the list of tokens that were produced
*/
public Token tokenize() {
InstrumentationBuilder instrumentation = Instrumentation.builder("dart.engine.AbstractScanner.tokenize");
int tokenCounter = 0;
try {
int next = reader.advance();
while (next != -1) {
tokenCounter++;
next = bigSwitch(next);
}
appendEofToken();
instrumentation.metric("tokensCount", tokenCounter);
return getFirstToken();
} finally {
instrumentation.log(2); //Log if over 1ms
}
}
/**
* 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.
*
* @param token the token to be appended
*/
protected void appendToken(Token token) {
tail = tail.setNext(token);
}
protected int bigSwitch(int next) {
beginToken();
if (next == '\r') {
next = reader.advance();
if (next == '\n') {
next = reader.advance();
}
recordStartOfLine();
return next;
} else if (next == '\n') {
next = reader.advance();
recordStartOfLine();
return next;
} else if (next == '\t' || next == ' ') {
return reader.advance();
}
if (next == 'r') {
int peek = reader.peek();
if (peek == '"' || peek == '\'') {
int start = reader.getOffset();
return tokenizeString(reader.advance(), start, true);
}
}
if ('a' <= next && next <= 'z') {
return tokenizeKeywordOrIdentifier(next, true);
}
if (('A' <= next && next <= 'Z') || next == '_' || next == '$') {
return tokenizeIdentifier(next, reader.getOffset(), true);
}
if (next == '<') {
return tokenizeLessThan(next);
}
if (next == '>') {
return tokenizeGreaterThan(next);
}
if (next == '=') {
return tokenizeEquals(next);
}
if (next == '!') {
return tokenizeExclamation(next);
}
if (next == '+') {
return tokenizePlus(next);
}
if (next == '-') {
return tokenizeMinus(next);
}
if (next == '*') {
return tokenizeMultiply(next);
}
if (next == '%') {
return tokenizePercent(next);
}
if (next == '&') {
return tokenizeAmpersand(next);
}
if (next == '|') {
return tokenizeBar(next);
}
if (next == '^') {
return tokenizeCaret(next);
}
if (next == '[') {
return tokenizeOpenSquareBracket(next);
}
if (next == '~') {
return tokenizeTilde(next);
}
if (next == '\\') {
appendTokenOfType(TokenType.BACKSLASH);
return reader.advance();
}
if (next == '#') {
return tokenizeTag(next);
}
if (next == '(') {
appendBeginToken(TokenType.OPEN_PAREN);
return reader.advance();
}
if (next == ')') {
appendEndToken(TokenType.CLOSE_PAREN, TokenType.OPEN_PAREN);
return reader.advance();
}
if (next == ',') {
appendTokenOfType(TokenType.COMMA);
return reader.advance();
}
if (next == ':') {
appendTokenOfType(TokenType.COLON);
return reader.advance();
}
if (next == ';') {
appendTokenOfType(TokenType.SEMICOLON);
return reader.advance();
}
if (next == '?') {
appendTokenOfType(TokenType.QUESTION);
return reader.advance();
}
if (next == ']') {
appendEndToken(TokenType.CLOSE_SQUARE_BRACKET, TokenType.OPEN_SQUARE_BRACKET);
return reader.advance();
}
if (next == '`') {
appendTokenOfType(TokenType.BACKPING);
return reader.advance();
}
if (next == '{') {
appendBeginToken(TokenType.OPEN_CURLY_BRACKET);
return reader.advance();
}
if (next == '}') {
appendEndToken(TokenType.CLOSE_CURLY_BRACKET, TokenType.OPEN_CURLY_BRACKET);
return reader.advance();
}
if (next == '/') {
return tokenizeSlashOrComment(next);
}
if (next == '@') {
appendTokenOfType(TokenType.AT);
return reader.advance();
}
if (next == '"' || next == '\'') {
return tokenizeString(next, reader.getOffset(), false);
}
if (next == '.') {
return tokenizeDotOrNumber(next);
}
if (next == '0') {
return tokenizeHexOrNumber(next);
}
if ('1' <= next && next <= '9') {
return tokenizeNumber(next);
}
if (next == -1) {
return -1;
}
reportError(ScannerErrorCode.ILLEGAL_CHARACTER, Integer.valueOf(next));
return reader.advance();
}
/**
* Return the first token in the token stream that was scanned.
*
* @return the first token in the token stream that was scanned
*/
protected Token getFirstToken() {
return tokens.getNext();
}
/**
* Return the last token that was scanned.
*
* @return the last token that was scanned
*/
protected Token getTail() {
return tail;
}
/**
* Record the fact that we are at the beginning of a new line in the source.
*/
protected void recordStartOfLine() {
lineStarts.add(reader.getOffset());
}
private 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++;
}
private 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.
if (firstComment == null) {
firstComment = new StringToken(type, value, tokenStart);
lastComment = firstComment;
} else {
lastComment = lastComment.setNext(new StringToken(type, value, tokenStart));
}
}
private 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.get(stackEnd);
if (begin.getType() == beginType) {
begin.setEndToken(token);
groupingStack.remove(stackEnd--);
}
}
}
private void appendEofToken() {
Token eofToken;
if (firstComment == null) {
eofToken = new Token(TokenType.EOF, reader.getOffset() + 1);
} else {
eofToken = new TokenWithComment(TokenType.EOF, reader.getOffset() + 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?
}
}
private 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;
}
}
private 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;
}
}
private 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;
}
}
private 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;
}
}
private 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;
}
}
private void beginToken() {
tokenStart = reader.getOffset();
}
/**
* 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.
*
* @return the token to be paired with the closing brace
*/
private BeginToken findTokenMatchingClosingBraceInInterpolationExpression() {
while (stackEnd >= 0) {
BeginToken begin = groupingStack.get(stackEnd);
if (begin.getType() == TokenType.OPEN_CURLY_BRACKET
|| begin.getType() == TokenType.STRING_INTERPOLATION_EXPRESSION) {
return begin;
}
hasUnmatchedGroups = true;
groupingStack.remove(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;
}
/**
* Return the source being scanned.
*
* @return the source being scanned
*/
private Source getSource() {
return source;
}
/**
* Report an error at the current offset.
*
* @param errorCode the error code indicating the nature of the error
* @param arguments any arguments needed to complete the error message
*/
private void reportError(ScannerErrorCode errorCode, Object... arguments) {
errorListener.onError(new AnalysisError(
getSource(),
reader.getOffset(),
1,
errorCode,
arguments));
}
private int select(char choice, TokenType yesType, TokenType noType) {
int next = reader.advance();
if (next == choice) {
appendTokenOfType(yesType);
return reader.advance();
} else {
appendTokenOfType(noType);
return next;
}
}
private int selectWithOffset(char 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;
}
}
private int tokenizeAmpersand(int next) {
// && &= &
next = reader.advance();
if (next == '&') {
appendTokenOfType(TokenType.AMPERSAND_AMPERSAND);
return reader.advance();
} else if (next == '=') {
appendTokenOfType(TokenType.AMPERSAND_EQ);
return reader.advance();
} else {
appendTokenOfType(TokenType.AMPERSAND);
return next;
}
}
private int tokenizeBar(int next) {
// | || |=
next = reader.advance();
if (next == '|') {
appendTokenOfType(TokenType.BAR_BAR);
return reader.advance();
} else if (next == '=') {
appendTokenOfType(TokenType.BAR_EQ);
return reader.advance();
} else {
appendTokenOfType(TokenType.BAR);
return next;
}
}
private int tokenizeCaret(int next) {
// ^ ^=
return select('=', TokenType.CARET_EQ, TokenType.CARET);
}
private int tokenizeDotOrNumber(int next) {
int start = reader.getOffset();
next = reader.advance();
if (('0' <= next && next <= '9')) {
return tokenizeFractionPart(next, start);
} else if ('.' == next) {
return select('.', TokenType.PERIOD_PERIOD_PERIOD, TokenType.PERIOD_PERIOD);
} else {
appendTokenOfType(TokenType.PERIOD);
return next;
}
}
private int tokenizeEquals(int next) {
// = == =>
next = reader.advance();
if (next == '=') {
appendTokenOfType(TokenType.EQ_EQ);
return reader.advance();
} else if (next == '>') {
appendTokenOfType(TokenType.FUNCTION);
return reader.advance();
}
appendTokenOfType(TokenType.EQ);
return next;
}
private int tokenizeExclamation(int next) {
// ! !=
next = reader.advance();
if (next == '=') {
appendTokenOfType(TokenType.BANG_EQ);
return reader.advance();
}
appendTokenOfType(TokenType.BANG);
return next;
}
private int tokenizeExponent(int next) {
if (next == '+' || next == '-') {
next = reader.advance();
}
boolean hasDigits = false;
while (true) {
if ('0' <= next && next <= '9') {
hasDigits = true;
} else {
if (!hasDigits) {
reportError(ScannerErrorCode.MISSING_DIGIT);
}
return next;
}
next = reader.advance();
}
}
private int tokenizeFractionPart(int next, int start) {
boolean done = false;
boolean hasDigit = false;
LOOP : while (!done) {
if ('0' <= next && next <= '9') {
hasDigit = true;
} else if ('e' == next || 'E' == 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 ('.' == next) {
return selectWithOffset(
'.',
TokenType.PERIOD_PERIOD_PERIOD,
TokenType.PERIOD_PERIOD,
reader.getOffset() - 1);
}
appendTokenOfTypeWithOffset(TokenType.PERIOD, reader.getOffset() - 1);
return bigSwitch(next);
}
appendStringToken(TokenType.DOUBLE, reader.getString(start, next < 0 ? 0 : -1));
return next;
}
private int tokenizeGreaterThan(int next) {
// > >= >> >>=
next = reader.advance();
if ('=' == next) {
appendTokenOfType(TokenType.GT_EQ);
return reader.advance();
} else if ('>' == next) {
next = reader.advance();
if ('=' == next) {
appendTokenOfType(TokenType.GT_GT_EQ);
return reader.advance();
} else {
appendTokenOfType(TokenType.GT_GT);
return next;
}
} else {
appendTokenOfType(TokenType.GT);
return next;
}
}
private int tokenizeHex(int next) {
int start = reader.getOffset() - 1;
boolean hasDigits = false;
while (true) {
next = reader.advance();
if (('0' <= next && next <= '9') || ('A' <= next && next <= 'F')
|| ('a' <= next && next <= 'f')) {
hasDigits = true;
} else {
if (!hasDigits) {
reportError(ScannerErrorCode.MISSING_HEX_DIGIT);
}
appendStringToken(TokenType.HEXADECIMAL, reader.getString(start, next < 0 ? 0 : -1));
return next;
}
}
}
private int tokenizeHexOrNumber(int next) {
int x = reader.peek();
if (x == 'x' || x == 'X') {
reader.advance();
return tokenizeHex(x);
}
return tokenizeNumber(next);
}
private int tokenizeIdentifier(int next, int start, boolean allowDollar) {
while (('a' <= next && next <= 'z') || ('A' <= next && next <= 'Z')
|| ('0' <= next && next <= '9') || next == '_' || (next == '$' && allowDollar)) {
next = reader.advance();
}
appendStringToken(TokenType.IDENTIFIER, reader.getString(start, next < 0 ? 0 : -1));
return next;
}
private int tokenizeInterpolatedExpression(int next, int start) {
appendBeginToken(TokenType.STRING_INTERPOLATION_EXPRESSION);
next = reader.advance();
while (next != -1) {
if (next == '}') {
BeginToken begin = findTokenMatchingClosingBraceInInterpolationExpression();
if (begin == null) {
beginToken();
appendTokenOfType(TokenType.CLOSE_CURLY_BRACKET);
next = reader.advance();
beginToken();
return next;
} else if (begin.getType() == TokenType.OPEN_CURLY_BRACKET) {
beginToken();
appendEndToken(TokenType.CLOSE_CURLY_BRACKET, TokenType.OPEN_CURLY_BRACKET);
next = reader.advance();
beginToken();
} else if (begin.getType() == 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;
}
private int tokenizeInterpolatedIdentifier(int next, int start) {
appendStringTokenWithOffset(TokenType.STRING_INTERPOLATION_IDENTIFIER, "$", 0);
if ((('A' <= next && next <= 'Z') || ('a' <= next && next <= 'z') || next == '_')) {
beginToken();
next = tokenizeKeywordOrIdentifier(next, false);
}
beginToken();
return next;
}
private int tokenizeKeywordOrIdentifier(int next, boolean allowDollar) {
KeywordState state = KeywordState.KEYWORD_STATE;
int start = reader.getOffset();
while (state != null && 'a' <= next && next <= 'z') {
state = state.next((char) next);
next = reader.advance();
}
if (state == null || state.keyword() == null) {
return tokenizeIdentifier(next, start, allowDollar);
}
if (('A' <= next && next <= 'Z') || ('0' <= next && next <= '9') || next == '_' || next == '$') {
return tokenizeIdentifier(next, start, allowDollar);
} else if (next < 128) {
appendKeywordToken(state.keyword());
return next;
} else {
return tokenizeIdentifier(next, start, allowDollar);
}
}
private int tokenizeLessThan(int next) {
// < <= << <<=
next = reader.advance();
if ('=' == next) {
appendTokenOfType(TokenType.LT_EQ);
return reader.advance();
} else if ('<' == next) {
return select('=', TokenType.LT_LT_EQ, TokenType.LT_LT);
} else {
appendTokenOfType(TokenType.LT);
return next;
}
}
private int tokenizeMinus(int next) {
// - -- -=
next = reader.advance();
if (next == '-') {
appendTokenOfType(TokenType.MINUS_MINUS);
return reader.advance();
} else if (next == '=') {
appendTokenOfType(TokenType.MINUS_EQ);
return reader.advance();
} else {
appendTokenOfType(TokenType.MINUS);
return next;
}
}
private 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 ('*' == next) {
next = reader.advance();
if ('/' == next) {
--nesting;
if (0 == nesting) {
appendCommentToken(TokenType.MULTI_LINE_COMMENT, reader.getString(tokenStart, 0));
return reader.advance();
} else {
next = reader.advance();
}
}
} else if ('/' == next) {
next = reader.advance();
if ('*' == next) {
next = reader.advance();
++nesting;
}
} else if (next == '\r') {
next = reader.advance();
if (next == '\n') {
next = reader.advance();
}
recordStartOfLine();
} else if (next == '\n') {
recordStartOfLine();
next = reader.advance();
} else {
next = reader.advance();
}
}
}
private int tokenizeMultiLineRawString(int quoteChar, int start) {
int next = reader.advance();
outer : while (next != -1) {
while (next != quoteChar) {
next = reader.advance();
if (next == -1) {
break outer;
} else if (next == '\r') {
next = reader.advance();
if (next == '\n') {
next = reader.advance();
}
recordStartOfLine();
} else if (next == '\n') {
recordStartOfLine();
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();
}
private int tokenizeMultiLineString(int quoteChar, int start, boolean raw) {
if (raw) {
return tokenizeMultiLineRawString(quoteChar, start);
}
int next = reader.advance();
while (next != -1) {
if (next == '$') {
appendStringToken(TokenType.STRING, reader.getString(start, -1));
next = tokenizeStringInterpolation(start);
beginToken();
start = reader.getOffset();
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 == '\\') {
next = reader.advance();
if (next == -1) {
break;
}
if (next == '\r') {
next = reader.advance();
if (next == '\n') {
next = reader.advance();
}
recordStartOfLine();
} else if (next == '\n') {
recordStartOfLine();
next = reader.advance();
} else {
next = reader.advance();
}
} else if (next == '\r') {
next = reader.advance();
if (next == '\n') {
next = reader.advance();
}
recordStartOfLine();
} else if (next == '\n') {
recordStartOfLine();
next = reader.advance();
} else {
next = reader.advance();
}
}
reportError(ScannerErrorCode.UNTERMINATED_STRING_LITERAL);
if (start == reader.getOffset()) {
appendStringTokenWithOffset(TokenType.STRING, "", 1);
} else {
appendStringToken(TokenType.STRING, reader.getString(start, 0));
}
return reader.advance();
}
private int tokenizeMultiply(int next) {
// * *=
return select('=', TokenType.STAR_EQ, TokenType.STAR);
}
private int tokenizeNumber(int next) {
int start = reader.getOffset();
while (true) {
next = reader.advance();
if ('0' <= next && next <= '9') {
continue;
} else if (next == '.') {
return tokenizeFractionPart(reader.advance(), start);
} else if (next == 'e' || next == 'E') {
return tokenizeFractionPart(next, start);
} else {
appendStringToken(TokenType.INT, reader.getString(start, next < 0 ? 0 : -1));
return next;
}
}
}
private int tokenizeOpenSquareBracket(int next) {
// [ [] []=
next = reader.advance();
if (next == ']') {
return select('=', TokenType.INDEX_EQ, TokenType.INDEX);
} else {
appendBeginToken(TokenType.OPEN_SQUARE_BRACKET);
return next;
}
}
private int tokenizePercent(int next) {
// % %=
return select('=', TokenType.PERCENT_EQ, TokenType.PERCENT);
}
private int tokenizePlus(int next) {
// + ++ +=
next = reader.advance();
if ('+' == next) {
appendTokenOfType(TokenType.PLUS_PLUS);
return reader.advance();
} else if ('=' == next) {
appendTokenOfType(TokenType.PLUS_EQ);
return reader.advance();
} else {
appendTokenOfType(TokenType.PLUS);
return next;
}
}
private 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 ('\n' == next || '\r' == next) {
appendCommentToken(TokenType.SINGLE_LINE_COMMENT, reader.getString(tokenStart, -1));
return next;
}
}
}
private 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 == '\r' || next == '\n') {
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();
}
private int tokenizeSingleLineString(int next, int quoteChar, int start) {
while (next != quoteChar) {
if (next == '\\') {
next = reader.advance();
} else if (next == '$') {
appendStringToken(TokenType.STRING, reader.getString(start, -1));
next = tokenizeStringInterpolation(start);
beginToken();
start = reader.getOffset();
continue;
}
if (next <= '\r' && (next == '\n' || next == '\r' || next == -1)) {
reportError(ScannerErrorCode.UNTERMINATED_STRING_LITERAL);
if (start == reader.getOffset()) {
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();
}
private int tokenizeSlashOrComment(int next) {
next = reader.advance();
if ('*' == next) {
return tokenizeMultiLineComment(next);
} else if ('/' == next) {
return tokenizeSingleLineComment(next);
} else if ('=' == next) {
appendTokenOfType(TokenType.SLASH_EQ);
return reader.advance();
} else {
appendTokenOfType(TokenType.SLASH);
return next;
}
}
private int tokenizeString(int next, int start, boolean 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);
}
}
private int tokenizeStringInterpolation(int start) {
beginToken();
int next = reader.advance();
if (next == '{') {
return tokenizeInterpolatedExpression(next, start);
} else {
return tokenizeInterpolatedIdentifier(next, start);
}
}
private int tokenizeTag(int next) {
// # or #!.*[\n\r]
if (reader.getOffset() == 0) {
if (reader.peek() == '!') {
do {
next = reader.advance();
} while (next != '\n' && next != '\r' && next > 0);
appendStringToken(TokenType.SCRIPT_TAG, reader.getString(tokenStart, 0));
return next;
}
}
appendTokenOfType(TokenType.HASH);
return reader.advance();
}
private int tokenizeTilde(int next) {
// ~ ~/ ~/=
next = reader.advance();
if (next == '/') {
return select('=', TokenType.TILDE_SLASH_EQ, TokenType.TILDE_SLASH);
} else {
appendTokenOfType(TokenType.TILDE);
return next;
}
}
}