Files
sdk/pkg/csslib/lib/src/tokenizer.dart
T
jmesserly@google.com 9e5b240923 move csslib into dart svn
deleted files that duplicate those in the Dart repository (LICENSE, pubspec.yaml, codereview.settings, .gitignore)

Otherwise, just changed pkg.status and pubspec

R=terry@google.com

Review URL: https://codereview.chromium.org//23168002

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@26155 260f80e4-7a28-3924-810f-c04153c831b5
2013-08-14 21:11:30 +00:00

410 lines
13 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) 2012, 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.
part of csslib.parser;
class Tokenizer extends TokenizerBase {
/** U+ prefix for unicode characters. */
final UNICODE_U = 'U'.codeUnitAt(0);
final UNICODE_LOWER_U = 'u'.codeUnitAt(0);
final UNICODE_PLUS = '+'.codeUnitAt(0);
final QUESTION_MARK = '?'.codeUnitAt(0);
/** CDATA keyword. */
final List CDATA_NAME = 'CDATA'.codeUnits;
Tokenizer(SourceFile file, String text, bool skipWhitespace,
[int index = 0])
: super(file, text, skipWhitespace, index);
Token next({unicodeRange: false}) {
// keep track of our starting position
_startIndex = _index;
int ch;
ch = _nextChar();
switch (ch) {
case TokenChar.NEWLINE:
case TokenChar.RETURN:
case TokenChar.SPACE:
case TokenChar.TAB:
return finishWhitespace();
case TokenChar.END_OF_FILE:
return _finishToken(TokenKind.END_OF_FILE);
case TokenChar.AT:
int peekCh = _peekChar();
if (TokenizerHelpers.isIdentifierStart(peekCh)) {
var oldIndex = _index;
var oldStartIndex = _startIndex;
_startIndex = _index;
ch = _nextChar();
Token ident = this.finishIdentifier(ch);
// Is it a directive?
int tokId = TokenKind.matchDirectives(_text, _startIndex,
_index - _startIndex);
if (tokId == -1) {
// No, is it a margin directive?
tokId = TokenKind.matchMarginDirectives(_text, _startIndex,
_index - _startIndex);
}
if (tokId != -1) {
return _finishToken(tokId);
} else {
// Didn't find a CSS directive or margin directive so the @name is
// probably the Less definition '@name: value_variable_definition'.
_startIndex = oldStartIndex;
_index = oldIndex;
}
}
return _finishToken(TokenKind.AT);
case TokenChar.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();
}
}
// It's really a dot.
return _finishToken(TokenKind.DOT);
case TokenChar.LPAREN:
return _finishToken(TokenKind.LPAREN);
case TokenChar.RPAREN:
return _finishToken(TokenKind.RPAREN);
case TokenChar.LBRACE:
return _finishToken(TokenKind.LBRACE);
case TokenChar.RBRACE:
return _finishToken(TokenKind.RBRACE);
case TokenChar.LBRACK:
return _finishToken(TokenKind.LBRACK);
case TokenChar.RBRACK:
if (_maybeEatChar(TokenChar.RBRACK) &&
_maybeEatChar(TokenChar.GREATER)) {
// ]]>
return next();
}
return _finishToken(TokenKind.RBRACK);
case TokenChar.HASH:
return _finishToken(TokenKind.HASH);
case TokenChar.PLUS:
if (maybeEatDigit()) return finishNumber();
return _finishToken(TokenKind.PLUS);
case TokenChar.MINUS:
if (selectorExpression || unicodeRange) {
// If parsing in pseudo function expression then minus is an operator
// not part of identifier e.g., interval value range (e.g. U+400-4ff)
// or minus operator in selector expression.
return _finishToken(TokenKind.MINUS);
} else if (maybeEatDigit()) {
return finishNumber();
} else if (TokenizerHelpers.isIdentifierStart(ch)) {
return this.finishIdentifier(ch);
}
return _finishToken(TokenKind.MINUS);
case TokenChar.GREATER:
return _finishToken(TokenKind.GREATER);
case TokenChar.TILDE:
if (_maybeEatChar(TokenChar.EQUALS)) {
return _finishToken(TokenKind.INCLUDES); // ~=
}
return _finishToken(TokenKind.TILDE);
case TokenChar.ASTERISK:
if (_maybeEatChar(TokenChar.EQUALS)) {
return _finishToken(TokenKind.SUBSTRING_MATCH); // *=
}
return _finishToken(TokenKind.ASTERISK);
case TokenChar.AMPERSAND:
return _finishToken(TokenKind.AMPERSAND);
case TokenChar.NAMESPACE:
return _finishToken(TokenKind.NAMESPACE);
case TokenChar.COLON:
return _finishToken(TokenKind.COLON);
case TokenChar.COMMA:
return _finishToken(TokenKind.COMMA);
case TokenChar.SEMICOLON:
return _finishToken(TokenKind.SEMICOLON);
case TokenChar.PERCENT:
return _finishToken(TokenKind.PERCENT);
case TokenChar.SINGLE_QUOTE:
return _finishToken(TokenKind.SINGLE_QUOTE);
case TokenChar.DOUBLE_QUOTE:
return _finishToken(TokenKind.DOUBLE_QUOTE);
case TokenChar.SLASH:
if (_maybeEatChar(TokenChar.ASTERISK)) return finishMultiLineComment();
return _finishToken(TokenKind.SLASH);
case TokenChar.LESS: // <!--
if (_maybeEatChar(TokenChar.BANG)) {
if (_maybeEatChar(TokenChar.MINUS) &&
_maybeEatChar(TokenChar.MINUS)) {
return finishMultiLineComment();
} else if (_maybeEatChar(TokenChar.LBRACK) &&
_maybeEatChar(CDATA_NAME[0]) &&
_maybeEatChar(CDATA_NAME[1]) &&
_maybeEatChar(CDATA_NAME[2]) &&
_maybeEatChar(CDATA_NAME[3]) &&
_maybeEatChar(CDATA_NAME[4]) &&
_maybeEatChar(TokenChar.LBRACK)) {
// <![CDATA[
return next();
}
}
return _finishToken(TokenKind.LESS);
case TokenChar.EQUALS:
return _finishToken(TokenKind.EQUALS);
case TokenChar.OR:
if (_maybeEatChar(TokenChar.EQUALS)) {
return _finishToken(TokenKind.DASH_MATCH); // |=
}
return _finishToken(TokenKind.OR);
case TokenChar.CARET:
if (_maybeEatChar(TokenChar.EQUALS)) {
return _finishToken(TokenKind.PREFIX_MATCH); // ^=
}
return _finishToken(TokenKind.CARET);
case TokenChar.DOLLAR:
if (_maybeEatChar(TokenChar.EQUALS)) {
return _finishToken(TokenKind.SUFFIX_MATCH); // $=
}
return _finishToken(TokenKind.DOLLAR);
case TokenChar.BANG:
Token tok = finishIdentifier(ch);
return (tok == null) ? _finishToken(TokenKind.BANG) : tok;
case TokenChar.BACKSLASH:
return _finishToken(TokenKind.BACKSLASH);
default:
if (unicodeRange) {
// Three types of unicode ranges:
// - single code point (e.g. U+416)
// - interval value range (e.g. U+400-4ff)
// - range where trailing ? characters imply any digit value
// (e.g. U+4??)
if (maybeEatHexDigit()) {
var t = finishHexNumber();
// Any question marks then it's a HEX_RANGE not HEX_NUMBER.
if (maybeEatQuestionMark()) finishUnicodeRange();
return t;
} else if (maybeEatQuestionMark()) {
// HEX_RANGE U+N???
return finishUnicodeRange();
} else {
return _errorToken();
}
} else if ((ch == UNICODE_U || ch == UNICODE_LOWER_U) &&
(_peekChar() == UNICODE_PLUS)) {
// Unicode range: U+uNumber[-U+uNumber]
// uNumber = 0..10FFFF
_nextChar(); // Skip +
_startIndex = _index; // Starts at the number
return _finishToken(TokenKind.UNICODE_RANGE);
} else if (varDef(ch)) {
return _finishToken(TokenKind.VAR_DEFINITION);
} else if (varUsage(ch)) {
return _finishToken(TokenKind.VAR_USAGE);
} else if (TokenizerHelpers.isIdentifierStart(ch)) {
return finishIdentifier(ch);
} else if (TokenizerHelpers.isDigit(ch)) {
return finishNumber();
}
return _errorToken();
}
}
bool varDef(int ch) {
return ch == 'v'.codeUnitAt(0) && _maybeEatChar('a'.codeUnitAt(0)) &&
_maybeEatChar('r'.codeUnitAt(0)) && _maybeEatChar('-'.codeUnitAt(0));
}
bool varUsage(int ch) {
return ch == 'v'.codeUnitAt(0) && _maybeEatChar('a'.codeUnitAt(0)) &&
_maybeEatChar('r'.codeUnitAt(0)) && (_peekChar() == '-'.codeUnitAt(0));
}
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) {
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 parsing in pseudo function expression then minus is an operator
// not part of identifier.
var isIdentifier = selectorExpression
? TokenizerHelpers.isIdentifierPartExpr(_text.codeUnitAt(_index))
: TokenizerHelpers.isIdentifierPart(_text.codeUnitAt(_index));
if (!isIdentifier) {
break;
} else {
_index += 1;
}
}
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;
}
Token finishHexNumber() {
eatHexDigits();
return _finishToken(TokenKind.HEX_INTEGER);
}
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;
}
bool maybeEatQuestionMark() {
if (_index < _text.length &&
_text.codeUnitAt(_index) == QUESTION_MARK) {
_index += 1;
return true;
}
return false;
}
void eatQuestionMarks() {
while (_index < _text.length) {
if (_text.codeUnitAt(_index) == QUESTION_MARK) {
_index += 1;
} else {
return;
}
}
}
Token finishUnicodeRange() {
eatQuestionMarks();
return _finishToken(TokenKind.HEX_RANGE);
}
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 == TokenChar.MINUS) {
/* Check if close part of Comment Definition --> (CDC). */
if (_maybeEatChar(TokenChar.MINUS)) {
if (_maybeEatChar(TokenChar.GREATER)) {
if (_skipWhitespace) {
return next();
} else {
return _finishToken(TokenKind.HTML_COMMENT);
}
}
}
}
}
return _errorToken();
}
}
/** Static helper methods. */
class TokenizerHelpers {
static bool isIdentifierStart(int c) {
return isIdentifierStartExpr(c) || 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 isIdentifierPartExpr(c) || c == 45 /*-*/;
}
/** Pseudo function expressions identifiers can't have a minus sign. */
static bool isIdentifierStartExpr(int c) {
return ((c >= 97/*a*/ && c <= 122/*z*/) || (c >= 65/*A*/ && c <= 90/*Z*/) ||
c == 95/*_*/);
}
/** Pseudo function expressions identifiers can't have a minus sign. */
static bool isIdentifierPartExpr(int c) {
return (isIdentifierStartExpr(c) || isDigit(c));
}
}