Index: utils/template/tokenizer.dart |
diff --git a/utils/template/tokenizer.dart b/utils/template/tokenizer.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..ae071e673ede9013dd5702d6bda6b1013657f831 |
--- /dev/null |
+++ b/utils/template/tokenizer.dart |
@@ -0,0 +1,302 @@ |
+// 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 TokenizerBase { |
+ TokenKind tmplTokens; |
+ |
+ bool _selectorParsing; |
+ |
+ Tokenizer(SourceFile source, bool skipWhitespace, [int index = 0]) |
+ : super(source, skipWhitespace, index), _selectorParsing = false { |
+ tmplTokens = new TokenKind(); |
+ } |
+ |
+ int get startIndex() => _startIndex; |
+ void set index(int idx) { |
+ _index = idx; |
+ } |
+ |
+ Token next([bool inTag = true]) { |
+ // 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 tmplTokens.tokens[TokenKind.SPACE]: |
+ case tmplTokens.tokens[TokenKind.TAB]: |
+ case tmplTokens.tokens[TokenKind.NEWLINE]: |
+ case tmplTokens.tokens[TokenKind.RETURN]: |
+ return finishWhitespace(); |
+ case tmplTokens.tokens[TokenKind.END_OF_FILE]: |
+ return _finishToken(TokenKind.END_OF_FILE); |
+ case tmplTokens.tokens[TokenKind.LPAREN]: |
+ return _finishToken(TokenKind.LPAREN); |
+ case tmplTokens.tokens[TokenKind.RPAREN]: |
+ return _finishToken(TokenKind.RPAREN); |
+ case tmplTokens.tokens[TokenKind.COMMA]: |
+ return _finishToken(TokenKind.COMMA); |
+ case tmplTokens.tokens[TokenKind.LBRACE]: |
+ return _finishToken(TokenKind.LBRACE); |
+ case tmplTokens.tokens[TokenKind.RBRACE]: |
+ return _finishToken(TokenKind.RBRACE); |
+ case tmplTokens.tokens[TokenKind.LESS_THAN]: |
+ return _finishToken(TokenKind.LESS_THAN); |
+ case tmplTokens.tokens[TokenKind.GREATER_THAN]: |
+ return _finishToken(TokenKind.GREATER_THAN); |
+ case tmplTokens.tokens[TokenKind.EQUAL]: |
+ if (inTag) { |
+ if (_maybeEatChar(tmplTokens.tokens[TokenKind.SINGLE_QUOTE])) { |
+ return finishQuotedAttrValue( |
+ tmplTokens.tokens[TokenKind.SINGLE_QUOTE]); |
+ } else if (_maybeEatChar(tmplTokens.tokens[TokenKind.DOUBLE_QUOTE])) { |
+ return finishQuotedAttrValue( |
+ tmplTokens.tokens[TokenKind.DOUBLE_QUOTE]); |
+ } else if (isAttributeValueStart(_peekChar())) { |
+ return finishAttrValue(); |
+ } |
+ } |
+ return _finishToken(TokenKind.EQUAL); |
+ case tmplTokens.tokens[TokenKind.SLASH]: |
+ if (_maybeEatChar(tmplTokens.tokens[TokenKind.GREATER_THAN])) { |
+ return _finishToken(TokenKind.END_NO_SCOPE_TAG); // /> |
+ } else { |
+ return _finishToken(TokenKind.SLASH); |
+ } |
+ case tmplTokens.tokens[TokenKind.DOLLAR]: |
+ if (_maybeEatChar(tmplTokens.tokens[TokenKind.LBRACE])) { |
+ if (_maybeEatChar(tmplTokens.tokens[TokenKind.HASH])) { |
+ return _finishToken(TokenKind.START_COMMAND); // ${# |
+ } else if (_maybeEatChar(tmplTokens.tokens[TokenKind.SLASH])) { |
+ return _finishToken(TokenKind.END_COMMAND); // ${/ |
+ } else { |
+ return _finishToken(TokenKind.START_EXPRESSION); // ${ |
+ } |
+ } else { |
+ return _finishToken(TokenKind.DOLLAR); |
+ } |
+ |
+ default: |
+ if (TokenizerHelpers.isIdentifierStart(ch)) { |
+ return this.finishIdentifier(); |
+ } else if (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 an element? |
+ int tokId = TokenKind.matchElements(_text, _startIndex, |
+ _index - _startIndex); |
+ if (tokId == -1) { |
+ // No, is it an attribute? |
+// tokId = TokenKind.matchAttributes(_text, _startIndex, _index - _startIndex); |
+ } |
+ if (tokId == -1) { |
+ tokId = TokenKind.matchKeywords(_text, _startIndex, _index - _startIndex); |
+ } |
+ |
+ return tokId >= 0 ? tokId : TokenKind.IDENTIFIER; |
+ } |
+ |
+ // Need to override so CSS version of isIdentifierPart is used. |
+ Token finishIdentifier() { |
+ while (_index < _text.length) { |
+// if (!TokenizerHelpers.isIdentifierPart(_text.charCodeAt(_index++))) { |
+ if (!TokenizerHelpers.isIdentifierPart(_text.charCodeAt(_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 _makeAttributeValueToken(List<int> buf) { |
+ final s = new String.fromCharCodes(buf); |
+ return new LiteralToken(TokenKind.ATTR_VALUE, _source, _startIndex, _index, |
+ s); |
+ } |
+ |
+ /* quote if -1 signals to read upto first whitespace otherwise read upto |
+ * single or double quote char. |
+ */ |
+ Token finishQuotedAttrValue([int quote = -1]) { |
+ var buf = new List<int>(); |
+ while (true) { |
+ int ch = _nextChar(); |
+ if (ch == quote) { |
+ return _makeAttributeValueToken(buf); |
+ } else if (ch == 0) { |
+ return _errorToken(); |
+ } else { |
+ buf.add(ch); |
+ } |
+ } |
+ } |
+ |
+ Token finishAttrValue() { |
+ var buf = new List<int>(); |
+ while (true) { |
+ int ch = _peekChar(); |
+ if (isWhitespace(ch) || isSlash(ch) || isCloseTag(ch)) { |
+ return _makeAttributeValueToken(buf); |
+ } else if (ch == 0) { |
+ return _errorToken(); |
+ } else { |
+ buf.add(_nextChar()); |
+ } |
+ } |
+ } |
+ |
+ Token finishNumber() { |
+ eatDigits(); |
+ |
+ if (_peekChar() == 46/*.*/) { |
+ // Handle the case of 1.toString(). |
+ _nextChar(); |
+ if (isDigit(_peekChar())) { |
+ eatDigits(); |
+ return _finishToken(TokenKind.DOUBLE); |
+ } else { |
+ _index -= 1; |
+ } |
+ } |
+ |
+ return _finishToken(TokenKind.INTEGER); |
+ } |
+ |
+ bool maybeEatDigit() { |
+ if (_index < _text.length && isDigit(_text.charCodeAt(_index))) { |
+ _index += 1; |
+ return true; |
+ } |
+ return false; |
+ } |
+ |
+ void eatHexDigits() { |
+ while (_index < _text.length) { |
+ if (isHexDigit(_text.charCodeAt(_index))) { |
+ _index += 1; |
+ } else { |
+ return; |
+ } |
+ } |
+ } |
+ |
+ bool maybeEatHexDigit() { |
+ if (_index < _text.length && isHexDigit(_text.charCodeAt(_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 == tmplTokens.tokens[TokenKind.MINUS]) { |
+ /* Check if close part of Comment Definition --> (CDC). */ |
+ if (_maybeEatChar(tmplTokens.tokens[TokenKind.MINUS])) { |
+ if (_maybeEatChar(tmplTokens.tokens[TokenKind.GREATER_THAN])) { |
+ if (_skipWhitespace) { |
+ return next(); |
+ } else { |
+ return _finishToken(TokenKind.HTML_COMMENT); |
+ } |
+ } |
+ } |
+ } |
+ } |
+ return _errorToken(); |
+ } |
+ |
+} |
+ |
+ |
+/** 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/*_*/); |
+ } |
+ |
+ 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 isWhitespace(int c) { |
+ return (c == 32/*' '*/ || c == 9/*'\t'*/ || c == 10/*'\n'*/ || |
+ c == 13/*'\r'*/); |
+ } |
+ |
+ static bool isIdentifierPart(int c) { |
+ return (isIdentifierStart(c) || isDigit(c) || c == 45/*-*/ || |
+ c == 58/*:*/ || c == 46/*.*/); |
+ } |
+ |
+ static bool isInterpIdentifierPart(int c) { |
+ return (isIdentifierStart(c) || isDigit(c)); |
+ } |
+ |
+ static bool isAttributeValueStart(int c) { |
+ return !isWhitespace(c) && !isSlash(c) && !isCloseTag(c); |
+ } |
+ |
+ static bool isSlash(int c) { |
+ return (c == 47/* / */); |
+ } |
+ |
+ static bool isCloseTag(int c) { |
+ return (c == 62/* > */); |
+ } |
+} |