blob: 63573dff140af5e9d653bc44961f909a3e4cf30a [file] [log] [blame]
// Copyright (c) 2011, 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.
class Tokenizer extends CSSTokenizerBase {
TokenKind cssTokens;
bool _selectorParsing;
Tokenizer(SourceFile source, bool skipWhitespace, [int index = 0])
: super(source, skipWhitespace, index), _selectorParsing = false {
cssTokens = new TokenKind();
}
int get startIndex => _startIndex;
Token next() {
// keep track of our starting position
_startIndex = _index;
if (_interpStack != null && _interpStack.depth == 0) {
var istack = _interpStack;
_interpStack = _interpStack.pop();
/* TODO(terry): Enable for variable and string interpolation.
* if (istack.isMultiline) {
* return finishMultilineStringBody(istack.quote);
* } else {
* return finishStringBody(istack.quote);
* }
*/
}
int ch;
ch = _nextChar();
switch(ch) {
case 0:
return _finishToken(TokenKind.END_OF_FILE);
case cssTokens.tokens[TokenKind.SPACE]:
case cssTokens.tokens[TokenKind.TAB]:
case cssTokens.tokens[TokenKind.NEWLINE]:
case cssTokens.tokens[TokenKind.RETURN]:
return finishWhitespace();
case cssTokens.tokens[TokenKind.END_OF_FILE]:
return _finishToken(TokenKind.END_OF_FILE);
case cssTokens.tokens[TokenKind.AT]:
return _finishToken(TokenKind.AT);
case cssTokens.tokens[TokenKind.DOT]:
int start = _startIndex; // Start where the dot started.
if (maybeEatDigit()) {
// looks like a number dot followed by digit(s).
Token number = finishNumber();
if (number.kind == TokenKind.INTEGER) {
// It's a number but it's preceeded by a dot, so make it a double.
_startIndex = start;
return _finishToken(TokenKind.DOUBLE);
} else {
// Don't allow dot followed by a double (e.g, '..1').
return _errorToken();
}
} else {
// It's really a dot.
return _finishToken(TokenKind.DOT);
}
case cssTokens.tokens[TokenKind.LPAREN]:
return _finishToken(TokenKind.LPAREN);
case cssTokens.tokens[TokenKind.RPAREN]:
return _finishToken(TokenKind.RPAREN);
case cssTokens.tokens[TokenKind.LBRACE]:
return _finishToken(TokenKind.LBRACE);
case cssTokens.tokens[TokenKind.RBRACE]:
return _finishToken(TokenKind.RBRACE);
case cssTokens.tokens[TokenKind.LBRACK]:
return _finishToken(TokenKind.LBRACK);
case cssTokens.tokens[TokenKind.RBRACK]:
return _finishToken(TokenKind.RBRACK);
case cssTokens.tokens[TokenKind.HASH]:
return _finishToken(TokenKind.HASH);
case cssTokens.tokens[TokenKind.PLUS]:
if (maybeEatDigit()) {
return finishNumber();
} else {
return _finishToken(TokenKind.PLUS);
}
case cssTokens.tokens[TokenKind.MINUS]:
if (maybeEatDigit()) {
return finishNumber();
} else if (TokenizerHelpers.isIdentifierStart(ch)) {
return this.finishIdentifier(ch);
} else {
return _finishToken(TokenKind.MINUS);
}
case cssTokens.tokens[TokenKind.GREATER]:
return _finishToken(TokenKind.GREATER);
case cssTokens.tokens[TokenKind.TILDE]:
if (_maybeEatChar(cssTokens.tokens[TokenKind.EQUALS])) {
return _finishToken(TokenKind.INCLUDES); // ~=
} else {
return _finishToken(TokenKind.TILDE);
}
case cssTokens.tokens[TokenKind.ASTERISK]:
if (_maybeEatChar(cssTokens.tokens[TokenKind.EQUALS])) {
return _finishToken(TokenKind.SUBSTRING_MATCH); // *=
} else {
return _finishToken(TokenKind.ASTERISK);
}
case cssTokens.tokens[TokenKind.NAMESPACE]:
return _finishToken(TokenKind.NAMESPACE);
case cssTokens.tokens[TokenKind.COLON]:
return _finishToken(TokenKind.COLON);
case cssTokens.tokens[TokenKind.COMMA]:
return _finishToken(TokenKind.COMMA);
case cssTokens.tokens[TokenKind.SEMICOLON]:
return _finishToken(TokenKind.SEMICOLON);
case cssTokens.tokens[TokenKind.PERCENT]:
return _finishToken(TokenKind.PERCENT);
case cssTokens.tokens[TokenKind.SINGLE_QUOTE]:
return _finishToken(TokenKind.SINGLE_QUOTE);
case cssTokens.tokens[TokenKind.DOUBLE_QUOTE]:
return _finishToken(TokenKind.DOUBLE_QUOTE);
case cssTokens.tokens[TokenKind.SLASH]:
if (_maybeEatChar(cssTokens.tokens[TokenKind.ASTERISK])) {
return finishMultiLineComment();
} else {
return _finishToken(TokenKind.SLASH);
}
case cssTokens.tokens[TokenKind.LESS]: // <!--
if (_maybeEatChar(cssTokens.tokens[TokenKind.BANG]) &&
_maybeEatChar(cssTokens.tokens[TokenKind.MINUS]) &&
_maybeEatChar(cssTokens.tokens[TokenKind.MINUS])) {
return finishMultiLineComment();
} else {
return _finishToken(TokenKind.LESS);
}
case cssTokens.tokens[TokenKind.EQUALS]:
return _finishToken(TokenKind.EQUALS);
case cssTokens.tokens[TokenKind.OR]:
if (_maybeEatChar(cssTokens.tokens[TokenKind.EQUALS])) {
return _finishToken(TokenKind.DASH_MATCH); // |=
} else {
return _finishToken(TokenKind.OR);
}
case cssTokens.tokens[TokenKind.CARET]:
if (_maybeEatChar(cssTokens.tokens[TokenKind.EQUALS])) {
return _finishToken(TokenKind.PREFIX_MATCH); // ^=
} else {
return _finishToken(TokenKind.CARET);
}
case cssTokens.tokens[TokenKind.DOLLAR]:
if (_maybeEatChar(cssTokens.tokens[TokenKind.EQUALS])) {
return _finishToken(TokenKind.SUFFIX_MATCH); // $=
} else {
return _finishToken(TokenKind.DOLLAR);
}
case cssTokens.tokens[TokenKind.BANG]:
Token tok = finishIdentifier(ch);
return (tok == null) ? _finishToken(TokenKind.BANG) : tok;
default:
if (TokenizerHelpers.isIdentifierStart(ch)) {
return this.finishIdentifier(ch);
} else if (TokenizerHelpers.isDigit(ch)) {
return this.finishNumber();
} else {
return _errorToken();
}
}
}
// TODO(jmesserly): we need a way to emit human readable error messages from
// the tokenizer.
Token _errorToken([String message = null]) {
return _finishToken(TokenKind.ERROR);
}
int getIdentifierKind() {
// Is the identifier a unit type?
int tokId = TokenKind.matchUnits(_text, _startIndex, _index - _startIndex);
if (tokId == -1) {
// No, is it a directive?
tokId = TokenKind.matchDirectives(
_text, _startIndex, _index - _startIndex);
}
if (tokId == -1) {
tokId = (_text.substring(_startIndex, _index) == '!important') ?
TokenKind.IMPORTANT : -1;
}
return tokId >= 0 ? tokId : TokenKind.IDENTIFIER;
}
// Need to override so CSS version of isIdentifierPart is used.
Token finishIdentifier(int ch) {
while (_index < _text.length) {
// if (!TokenizerHelpers.isIdentifierPart(_text.codeUnitAt(_index++))) {
if (!TokenizerHelpers.isIdentifierPart(_text.codeUnitAt(_index))) {
// _index--;
break;
} else {
_index += 1;
}
}
if (_interpStack != null && _interpStack.depth == -1) {
_interpStack.depth = 0;
}
int kind = getIdentifierKind();
if (kind == TokenKind.IDENTIFIER) {
return _finishToken(TokenKind.IDENTIFIER);
} else {
return _finishToken(kind);
}
}
Token finishImportant() {
}
Token finishNumber() {
eatDigits();
if (_peekChar() == 46/*.*/) {
// Handle the case of 1.toString().
_nextChar();
if (TokenizerHelpers.isDigit(_peekChar())) {
eatDigits();
return _finishToken(TokenKind.DOUBLE);
} else {
_index -= 1;
}
}
return _finishToken(TokenKind.INTEGER);
}
bool maybeEatDigit() {
if (_index < _text.length
&& TokenizerHelpers.isDigit(_text.codeUnitAt(_index))) {
_index += 1;
return true;
}
return false;
}
void eatHexDigits() {
while (_index < _text.length) {
if (TokenizerHelpers.isHexDigit(_text.codeUnitAt(_index))) {
_index += 1;
} else {
return;
}
}
}
bool maybeEatHexDigit() {
if (_index < _text.length
&& TokenizerHelpers.isHexDigit(_text.codeUnitAt(_index))) {
_index += 1;
return true;
}
return false;
}
Token finishMultiLineComment() {
while (true) {
int ch = _nextChar();
if (ch == 0) {
return _finishToken(TokenKind.INCOMPLETE_COMMENT);
} else if (ch == 42/*'*'*/) {
if (_maybeEatChar(47/*'/'*/)) {
if (_skipWhitespace) {
return next();
} else {
return _finishToken(TokenKind.COMMENT);
}
}
} else if (ch == cssTokens.tokens[TokenKind.MINUS]) {
/* Check if close part of Comment Definition --> (CDC). */
if (_maybeEatChar(cssTokens.tokens[TokenKind.MINUS])) {
if (_maybeEatChar(cssTokens.tokens[TokenKind.GREATER])) {
if (_skipWhitespace) {
return next();
} else {
return _finishToken(TokenKind.HTML_COMMENT);
}
}
}
}
}
return _errorToken();
}
}
/** Static helper methods. */
/** Static helper methods. */
class TokenizerHelpers {
static bool isIdentifierStart(int c) {
return ((c >= 97/*a*/ && c <= 122/*z*/) || (c >= 65/*A*/ && c <= 90/*Z*/) ||
c == 95/*_*/ || c == 45 /*-*/);
}
static bool isDigit(int c) {
return (c >= 48/*0*/ && c <= 57/*9*/);
}
static bool isHexDigit(int c) {
return (isDigit(c) || (c >= 97/*a*/ && c <= 102/*f*/)
|| (c >= 65/*A*/ && c <= 70/*F*/));
}
static bool isIdentifierPart(int c) {
return (isIdentifierStart(c) || isDigit(c) || c == 45 /*-*/);
}
}