| Index: utils/css/parser.dart
|
| diff --git a/utils/css/parser.dart b/utils/css/parser.dart
|
| deleted file mode 100644
|
| index a865bdf2760b62390c041d0bfa8b4ff4cad85e23..0000000000000000000000000000000000000000
|
| --- a/utils/css/parser.dart
|
| +++ /dev/null
|
| @@ -1,1017 +0,0 @@
|
| -// 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
|
| -
|
| -/**
|
| - * A simple recursive descent parser for CSS.
|
| - */
|
| -class Parser {
|
| - Tokenizer tokenizer;
|
| -
|
| - var _fs; // If non-null filesystem to read files.
|
| - String _basePath; // Base path of CSS file.
|
| -
|
| - final SourceFile source;
|
| -
|
| - Token _previousToken;
|
| - Token _peekToken;
|
| -
|
| -// Communicating errors back to template parser.
|
| -// TODO(terry): Need a better mechanism (e.g., common World).
|
| - var _erroMsgRedirector;
|
| -
|
| - Parser(this.source, [int start = 0, this._fs = null, this._basePath = null]) {
|
| - tokenizer = new Tokenizer(source, true, start);
|
| - _peekToken = tokenizer.next();
|
| - _previousToken = null;
|
| - }
|
| -
|
| - // Main entry point for parsing an entire CSS file.
|
| - // If nestedCSS is true when we're back at processing directives from top and
|
| - // we encounter a } then stop we're inside of a template e.g.,
|
| - //
|
| - // template ... {
|
| - // css {
|
| - // .item {
|
| - // left: 10px;
|
| - // }
|
| - // }
|
| - // <div>...</div>
|
| - // }
|
| - //
|
| - Stylesheet parse([bool nestedCSS = false, var erroMsgRedirector = null]) {
|
| - // TODO(terry): Hack for migrating CSS errors back to template errors.
|
| - _erroMsgRedirector = erroMsgRedirector;
|
| -
|
| - List<ASTNode> productions = [];
|
| -
|
| - int start = _peekToken.start;
|
| - while (!_maybeEat(TokenKind.END_OF_FILE) &&
|
| - (!nestedCSS && !_peekKind(TokenKind.RBRACE))) {
|
| - // TODO(terry): Need to handle charset, import, media and page.
|
| - var directive = processDirective();
|
| - if (directive != null) {
|
| - productions.add(directive);
|
| - } else {
|
| - RuleSet ruleset = processRuleSet();
|
| - if (ruleset != null) {
|
| - productions.add(ruleset);
|
| - } else {
|
| - break;
|
| - }
|
| - }
|
| - }
|
| -
|
| - return new Stylesheet(productions, _makeSpan(start));
|
| - }
|
| -
|
| - /** Generate an error if [source] has not been completely consumed. */
|
| - void checkEndOfFile() {
|
| - _eat(TokenKind.END_OF_FILE);
|
| - }
|
| -
|
| - /** Guard to break out of parser when an unexpected end of file is found. */
|
| - // TODO(jimhug): Failure to call this method can lead to inifinite parser
|
| - // loops. Consider embracing exceptions for more errors to reduce
|
| - // the danger here.
|
| - bool isPrematureEndOfFile() {
|
| - if (_maybeEat(TokenKind.END_OF_FILE)) {
|
| - _error('unexpected end of file', _peekToken.span);
|
| - return true;
|
| - } else {
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - ///////////////////////////////////////////////////////////////////
|
| - // Basic support methods
|
| - ///////////////////////////////////////////////////////////////////
|
| - int _peek() {
|
| - return _peekToken.kind;
|
| - }
|
| -
|
| - Token _next() {
|
| - _previousToken = _peekToken;
|
| - _peekToken = tokenizer.next();
|
| - return _previousToken;
|
| - }
|
| -
|
| - bool _peekKind(int kind) {
|
| - return _peekToken.kind == kind;
|
| - }
|
| -
|
| - /* Is the next token a legal identifier? This includes pseudo-keywords. */
|
| - bool _peekIdentifier() {
|
| - return TokenKind.isIdentifier(_peekToken.kind);
|
| - }
|
| -
|
| - bool _maybeEat(int kind) {
|
| - if (_peekToken.kind == kind) {
|
| - _previousToken = _peekToken;
|
| - _peekToken = tokenizer.next();
|
| - return true;
|
| - } else {
|
| - return false;
|
| - }
|
| - }
|
| -
|
| - void _eat(int kind) {
|
| - if (!_maybeEat(kind)) {
|
| - _errorExpected(TokenKind.kindToString(kind));
|
| - }
|
| - }
|
| -
|
| - void _eatSemicolon() {
|
| - _eat(TokenKind.SEMICOLON);
|
| - }
|
| -
|
| - void _errorExpected(String expected) {
|
| - var tok = _next();
|
| - var message;
|
| - try {
|
| - message = 'expected $expected, but found $tok';
|
| - } catch (e) {
|
| - message = 'parsing error expected $expected';
|
| - }
|
| - _error(message, tok.span);
|
| - }
|
| -
|
| - void _error(String message, [SourceSpan location=null]) {
|
| - if (location == null) {
|
| - location = _peekToken.span;
|
| - }
|
| -
|
| - if (_erroMsgRedirector == null) {
|
| - world.fatal(message, location); // syntax errors are fatal for now
|
| - } else {
|
| - String text = "";
|
| - if (location != null) {
|
| - text = location.toMessageString("");
|
| - }
|
| - _erroMsgRedirector.displayError("CSS error: \r${text}\r${message}");
|
| - }
|
| - }
|
| -
|
| - void _warning(String message, [SourceSpan location=null]) {
|
| - if (location == null) {
|
| - location = _peekToken.span;
|
| - }
|
| -
|
| - world.warning(message, location);
|
| - }
|
| -
|
| - SourceSpan _makeSpan(int start) {
|
| - return new SourceSpan(source, start, _previousToken.end);
|
| - }
|
| -
|
| - ///////////////////////////////////////////////////////////////////
|
| - // Top level productions
|
| - ///////////////////////////////////////////////////////////////////
|
| -
|
| - // Templates are @{selectors} single line nothing else.
|
| - SelectorGroup parseTemplate() {
|
| - SelectorGroup selectorGroup = null;
|
| - if (!isPrematureEndOfFile()) {
|
| - selectorGroup = templateExpression();
|
| - }
|
| -
|
| - return selectorGroup;
|
| - }
|
| -
|
| - /*
|
| - * Expect @{css_expression}
|
| - */
|
| - templateExpression() {
|
| - List<Selector> selectors = [];
|
| -
|
| - int start = _peekToken.start;
|
| -
|
| - _eat(TokenKind.AT);
|
| - _eat(TokenKind.LBRACE);
|
| -
|
| - selectors.add(processSelector());
|
| - SelectorGroup group = new SelectorGroup(selectors, _makeSpan(start));
|
| -
|
| - _eat(TokenKind.RBRACE);
|
| -
|
| - return group;
|
| - }
|
| -
|
| - ///////////////////////////////////////////////////////////////////
|
| - // Productions
|
| - ///////////////////////////////////////////////////////////////////
|
| -
|
| - processMedia([bool oneRequired = false]) {
|
| - List<String> media = [];
|
| -
|
| - while (_peekIdentifier()) {
|
| - // We have some media types.
|
| - var medium = identifier(); // Medium ident.
|
| - media.add(medium);
|
| - if (!_maybeEat(TokenKind.COMMA)) {
|
| - // No more media types exit now.
|
| - break;
|
| - }
|
| - }
|
| -
|
| - if (oneRequired && media.length == 0) {
|
| - _error('at least one media type required', _peekToken.span);
|
| - }
|
| -
|
| - return media;
|
| - }
|
| -
|
| - // Directive grammar:
|
| - //
|
| - // import: '@import' [string | URI] media_list?
|
| - // media: '@media' media_list '{' ruleset '}'
|
| - // page: '@page' [':' IDENT]? '{' declarations '}'
|
| - // include: '@include' [string | URI]
|
| - // stylet: '@stylet' IDENT '{' ruleset '}'
|
| - // media_list: IDENT [',' IDENT]
|
| - // keyframes: '@-webkit-keyframes ...' (see grammar below).
|
| - // font_face: '@font-face' '{' declarations '}'
|
| - //
|
| - processDirective() {
|
| - int start = _peekToken.start;
|
| -
|
| - if (_maybeEat(TokenKind.AT)) {
|
| - switch (_peek()) {
|
| - case TokenKind.DIRECTIVE_IMPORT:
|
| - _next();
|
| -
|
| - String importStr;
|
| - if (_peekIdentifier()) {
|
| - var func = processFunction(identifier());
|
| - if (func is UriTerm) {
|
| - importStr = func.text;
|
| - }
|
| - } else {
|
| - importStr = processQuotedString(false);
|
| - }
|
| -
|
| - // Any medias?
|
| - List<String> medias = processMedia();
|
| -
|
| - if (importStr == null) {
|
| - _error('missing import string', _peekToken.span);
|
| - }
|
| - return new ImportDirective(importStr, medias, _makeSpan(start));
|
| - case TokenKind.DIRECTIVE_MEDIA:
|
| - _next();
|
| -
|
| - // Any medias?
|
| - List<String> media = processMedia(true);
|
| - RuleSet ruleset;
|
| -
|
| - if (_maybeEat(TokenKind.LBRACE)) {
|
| - ruleset = processRuleSet();
|
| - if (!_maybeEat(TokenKind.RBRACE)) {
|
| - _error('expected } after ruleset for @media', _peekToken.span);
|
| - }
|
| - } else {
|
| - _error('expected { after media before ruleset', _peekToken.span);
|
| - }
|
| - return new MediaDirective(media, ruleset, _makeSpan(start));
|
| - case TokenKind.DIRECTIVE_PAGE:
|
| - _next();
|
| -
|
| - // Any pseudo page?
|
| - var pseudoPage;
|
| - if (_maybeEat(TokenKind.COLON)) {
|
| - if (_peekIdentifier()) {
|
| - pseudoPage = identifier();
|
| - }
|
| - }
|
| - return new PageDirective(pseudoPage, processDeclarations(),
|
| - _makeSpan(start));
|
| - case TokenKind.DIRECTIVE_KEYFRAMES:
|
| - /* Key frames grammar:
|
| - *
|
| - * @-webkit-keyframes [IDENT|STRING] '{' keyframes-blocks '}';
|
| - *
|
| - * keyframes-blocks:
|
| - * [keyframe-selectors '{' declarations '}']* ;
|
| - *
|
| - * keyframe-selectors:
|
| - * ['from'|'to'|PERCENTAGE] [',' ['from'|'to'|PERCENTAGE] ]* ;
|
| - */
|
| - _next();
|
| -
|
| - var name;
|
| - if (_peekIdentifier()) {
|
| - name = identifier();
|
| - }
|
| -
|
| - _eat(TokenKind.LBRACE);
|
| -
|
| - KeyFrameDirective kf = new KeyFrameDirective(name, _makeSpan(start));
|
| -
|
| - do {
|
| - Expressions selectors = new Expressions(_makeSpan(start));
|
| -
|
| - do {
|
| - var term = processTerm();
|
| -
|
| - // TODO(terry): Only allow from, to and PERCENTAGE ...
|
| -
|
| - selectors.add(term);
|
| - } while (_maybeEat(TokenKind.COMMA));
|
| -
|
| - kf.add(new KeyFrameBlock(selectors, processDeclarations(),
|
| - _makeSpan(start)));
|
| -
|
| - } while (!_maybeEat(TokenKind.RBRACE));
|
| -
|
| - return kf;
|
| - case TokenKind.DIRECTIVE_FONTFACE:
|
| - _next();
|
| -
|
| - List<Declaration> decls = [];
|
| -
|
| - // TODO(terry): To Be Implemented
|
| -
|
| - return new FontFaceDirective(decls, _makeSpan(start));
|
| - case TokenKind.DIRECTIVE_INCLUDE:
|
| - _next();
|
| - String filename = processQuotedString(false);
|
| - if (_fs != null) {
|
| - // Does CSS file exist?
|
| - if (_fs.fileExists('${_basePath}${filename}')) {
|
| - String basePath = "";
|
| - int idx = filename.lastIndexOf('/');
|
| - if (idx >= 0) {
|
| - basePath = filename.substring(0, idx + 1);
|
| - }
|
| - basePath = '${_basePath}${basePath}';
|
| - // Yes, let's parse this file as well.
|
| - String fullFN = '${basePath}${filename}';
|
| - String contents = _fs.readAll(fullFN);
|
| - Parser parser = new Parser(new SourceFile(fullFN, contents), 0,
|
| - _fs, basePath);
|
| - Stylesheet stylesheet = parser.parse();
|
| - return new IncludeDirective(filename, stylesheet, _makeSpan(start));
|
| - }
|
| -
|
| - _error('file doesn\'t exist ${filename}', _peekToken.span);
|
| - }
|
| -
|
| - print("WARNING: @include doesn't work for uitest");
|
| - return new IncludeDirective(filename, null, _makeSpan(start));
|
| - case TokenKind.DIRECTIVE_STYLET:
|
| - /* Stylet grammar:
|
| - *
|
| - * @stylet IDENT '{'
|
| - * ruleset
|
| - * '}'
|
| - */
|
| - _next();
|
| -
|
| - var name;
|
| - if (_peekIdentifier()) {
|
| - name = identifier();
|
| - }
|
| -
|
| - _eat(TokenKind.LBRACE);
|
| -
|
| - List<ASTNode> productions = [];
|
| -
|
| - start = _peekToken.start;
|
| - while (!_maybeEat(TokenKind.END_OF_FILE)) {
|
| - RuleSet ruleset = processRuleSet();
|
| - if (ruleset == null) {
|
| - break;
|
| - }
|
| - productions.add(ruleset);
|
| - }
|
| -
|
| - _eat(TokenKind.RBRACE);
|
| -
|
| - return new StyletDirective(name, productions, _makeSpan(start));
|
| - default:
|
| - _error('unknown directive, found $_peekToken', _peekToken.span);
|
| - }
|
| - }
|
| - }
|
| -
|
| - RuleSet processRuleSet() {
|
| - int start = _peekToken.start;
|
| -
|
| - SelectorGroup selGroup = processSelectorGroup();
|
| - if (selGroup != null) {
|
| - return new RuleSet(selGroup, processDeclarations(), _makeSpan(start));
|
| - }
|
| - }
|
| -
|
| - DeclarationGroup processDeclarations() {
|
| - int start = _peekToken.start;
|
| -
|
| - _eat(TokenKind.LBRACE);
|
| -
|
| - List<Declaration> decls = [];
|
| - do {
|
| - Declaration decl = processDeclaration();
|
| - if (decl != null) {
|
| - decls.add(decl);
|
| - }
|
| - } while (_maybeEat(TokenKind.SEMICOLON));
|
| -
|
| - _eat(TokenKind.RBRACE);
|
| -
|
| - return new DeclarationGroup(decls, _makeSpan(start));
|
| - }
|
| -
|
| - SelectorGroup processSelectorGroup() {
|
| - List<Selector> selectors = [];
|
| - int start = _peekToken.start;
|
| - do {
|
| - Selector selector = processSelector();
|
| - if (selector != null) {
|
| - selectors.add(selector);
|
| - }
|
| - } while (_maybeEat(TokenKind.COMMA));
|
| -
|
| - if (selectors.length > 0) {
|
| - return new SelectorGroup(selectors, _makeSpan(start));
|
| - }
|
| - }
|
| -
|
| - /* Return list of selectors
|
| - *
|
| - */
|
| - processSelector() {
|
| - List<SimpleSelectorSequence> simpleSequences = [];
|
| - int start = _peekToken.start;
|
| - while (true) {
|
| - // First item is never descendant make sure it's COMBINATOR_NONE.
|
| - var selectorItem = simpleSelectorSequence(simpleSequences.length == 0);
|
| - if (selectorItem != null) {
|
| - simpleSequences.add(selectorItem);
|
| - } else {
|
| - break;
|
| - }
|
| - }
|
| -
|
| - if (simpleSequences.length > 0) {
|
| - return new Selector(simpleSequences, _makeSpan(start));
|
| - }
|
| - }
|
| -
|
| - simpleSelectorSequence(bool forceCombinatorNone) {
|
| - int start = _peekToken.start;
|
| - int combinatorType = TokenKind.COMBINATOR_NONE;
|
| -
|
| - switch (_peek()) {
|
| - case TokenKind.PLUS:
|
| - _eat(TokenKind.PLUS);
|
| - combinatorType = TokenKind.COMBINATOR_PLUS;
|
| - break;
|
| - case TokenKind.GREATER:
|
| - _eat(TokenKind.GREATER);
|
| - combinatorType = TokenKind.COMBINATOR_GREATER;
|
| - break;
|
| - case TokenKind.TILDE:
|
| - _eat(TokenKind.TILDE);
|
| - combinatorType = TokenKind.COMBINATOR_TILDE;
|
| - break;
|
| - }
|
| -
|
| - // Check if WHITESPACE existed between tokens if so we're descendent.
|
| - if (combinatorType == TokenKind.COMBINATOR_NONE && !forceCombinatorNone) {
|
| - if (this._previousToken != null &&
|
| - this._previousToken.end != this._peekToken.start) {
|
| - combinatorType = TokenKind.COMBINATOR_DESCENDANT;
|
| - }
|
| - }
|
| -
|
| - var simpleSel = simpleSelector();
|
| - if (simpleSel != null) {
|
| - return new SimpleSelectorSequence(simpleSel, _makeSpan(start),
|
| - combinatorType);
|
| - }
|
| - }
|
| -
|
| - /**
|
| - * Simple selector grammar:
|
| - *
|
| - * simple_selector_sequence
|
| - * : [ type_selector | universal ]
|
| - * [ HASH | class | attrib | pseudo | negation ]*
|
| - * | [ HASH | class | attrib | pseudo | negation ]+
|
| - * type_selector
|
| - * : [ namespace_prefix ]? element_name
|
| - * namespace_prefix
|
| - * : [ IDENT | '*' ]? '|'
|
| - * element_name
|
| - * : IDENT
|
| - * universal
|
| - * : [ namespace_prefix ]? '*'
|
| - * class
|
| - * : '.' IDENT
|
| - */
|
| - simpleSelector() {
|
| - // TODO(terry): Nathan makes a good point parsing of namespace and element
|
| - // are essentially the same (asterisk or identifier) other
|
| - // than the error message for element. Should consolidate the
|
| - // code.
|
| - // TODO(terry): Need to handle attribute namespace too.
|
| - var first;
|
| - int start = _peekToken.start;
|
| - switch (_peek()) {
|
| - case TokenKind.ASTERISK:
|
| - // Mark as universal namespace.
|
| - var tok = _next();
|
| - first = new Wildcard(_makeSpan(tok.start));
|
| - break;
|
| - case TokenKind.IDENTIFIER:
|
| - int startIdent = _peekToken.start;
|
| - first = identifier();
|
| - break;
|
| - }
|
| -
|
| - if (_maybeEat(TokenKind.NAMESPACE)) {
|
| - var element;
|
| - switch (_peek()) {
|
| - case TokenKind.ASTERISK:
|
| - // Mark as universal element
|
| - var tok = _next();
|
| - element = new Wildcard(_makeSpan(tok.start));
|
| - break;
|
| - case TokenKind.IDENTIFIER:
|
| - element = identifier();
|
| - break;
|
| - default:
|
| - _error('expected element name or universal(*), but found $_peekToken',
|
| - _peekToken.span);
|
| - }
|
| -
|
| - return new NamespaceSelector(first,
|
| - new ElementSelector(element, element.span), _makeSpan(start));
|
| - } else if (first != null) {
|
| - return new ElementSelector(first, _makeSpan(start));
|
| - } else {
|
| - // Check for HASH | class | attrib | pseudo | negation
|
| - return simpleSelectorTail();
|
| - }
|
| - }
|
| -
|
| - simpleSelectorTail() {
|
| - // Check for HASH | class | attrib | pseudo | negation
|
| - int start = _peekToken.start;
|
| - switch (_peek()) {
|
| - case TokenKind.HASH:
|
| - _eat(TokenKind.HASH);
|
| - return new IdSelector(identifier(), _makeSpan(start));
|
| - case TokenKind.DOT:
|
| - _eat(TokenKind.DOT);
|
| - return new ClassSelector(identifier(), _makeSpan(start));
|
| - case TokenKind.COLON:
|
| - // :pseudo-class ::pseudo-element
|
| - // TODO(terry): '::' should be token.
|
| - _eat(TokenKind.COLON);
|
| - bool pseudoElement = _maybeEat(TokenKind.COLON);
|
| - var name = identifier();
|
| - // TODO(terry): Need to handle specific pseudo class/element name and
|
| - // backward compatible names that are : as well as :: as well as
|
| - // parameters.
|
| - return pseudoElement ?
|
| - new PseudoElementSelector(name, _makeSpan(start)) :
|
| - new PseudoClassSelector(name, _makeSpan(start));
|
| - case TokenKind.LBRACK:
|
| - return processAttribute();
|
| - }
|
| - }
|
| -
|
| - // Attribute grammar:
|
| - //
|
| - // attributes :
|
| - // '[' S* IDENT S* [ ATTRIB_MATCHES S* [ IDENT | STRING ] S* ]? ']'
|
| - //
|
| - // ATTRIB_MATCHES :
|
| - // [ '=' | INCLUDES | DASHMATCH | PREFIXMATCH | SUFFIXMATCH | SUBSTRMATCH ]
|
| - //
|
| - // INCLUDES: '~='
|
| - //
|
| - // DASHMATCH: '|='
|
| - //
|
| - // PREFIXMATCH: '^='
|
| - //
|
| - // SUFFIXMATCH: '$='
|
| - //
|
| - // SUBSTRMATCH: '*='
|
| - //
|
| - //
|
| - processAttribute() {
|
| - int start = _peekToken.start;
|
| -
|
| - if (_maybeEat(TokenKind.LBRACK)) {
|
| - var attrName = identifier();
|
| -
|
| - int op = TokenKind.NO_MATCH;
|
| - switch (_peek()) {
|
| - case TokenKind.EQUALS:
|
| - case TokenKind.INCLUDES: // ~=
|
| - case TokenKind.DASH_MATCH: // |=
|
| - case TokenKind.PREFIX_MATCH: // ^=
|
| - case TokenKind.SUFFIX_MATCH: // $=
|
| - case TokenKind.SUBSTRING_MATCH: // *=
|
| - op = _peek();
|
| - _next();
|
| - break;
|
| - }
|
| -
|
| - String value;
|
| - if (op != TokenKind.NO_MATCH) {
|
| - // Operator hit so we require a value too.
|
| - if (_peekIdentifier()) {
|
| - value = identifier();
|
| - } else {
|
| - value = processQuotedString(false);
|
| - }
|
| -
|
| - if (value == null) {
|
| - _error('expected attribute value string or ident', _peekToken.span);
|
| - }
|
| - }
|
| -
|
| - _eat(TokenKind.RBRACK);
|
| -
|
| - return new AttributeSelector(attrName, op, value, _makeSpan(start));
|
| - }
|
| - }
|
| -
|
| - // Declaration grammar:
|
| - //
|
| - // declaration: property ':' expr prio?
|
| - //
|
| - // property: IDENT
|
| - // prio: !important
|
| - // expr: (see processExpr)
|
| - //
|
| - processDeclaration() {
|
| - Declaration decl;
|
| -
|
| - int start = _peekToken.start;
|
| -
|
| - // IDENT ':' expr '!important'?
|
| - if (TokenKind.isIdentifier(_peekToken.kind)) {
|
| - var propertyIdent = identifier();
|
| - _eat(TokenKind.COLON);
|
| -
|
| - decl = new Declaration(propertyIdent, processExpr(), _makeSpan(start));
|
| -
|
| - // Handle !important (prio)
|
| - decl.important = _maybeEat(TokenKind.IMPORTANT);
|
| - }
|
| -
|
| - return decl;
|
| - }
|
| -
|
| - // Expression grammar:
|
| - //
|
| - // expression: term [ operator? term]*
|
| - //
|
| - // operator: '/' | ','
|
| - // term: (see processTerm)
|
| - //
|
| - processExpr() {
|
| - int start = _peekToken.start;
|
| - Expressions expressions = new Expressions(_makeSpan(start));
|
| -
|
| - bool keepGoing = true;
|
| - var expr;
|
| - while (keepGoing && (expr = processTerm()) != null) {
|
| - var op;
|
| -
|
| - int opStart = _peekToken.start;
|
| -
|
| - switch (_peek()) {
|
| - case TokenKind.SLASH:
|
| - op = new OperatorSlash(_makeSpan(opStart));
|
| - break;
|
| - case TokenKind.COMMA:
|
| - op = new OperatorComma(_makeSpan(opStart));
|
| - break;
|
| - }
|
| -
|
| - if (expr != null) {
|
| - expressions.add(expr);
|
| - } else {
|
| - keepGoing = false;
|
| - }
|
| -
|
| - if (op != null) {
|
| - expressions.add(op);
|
| - _next();
|
| - }
|
| - }
|
| -
|
| - return expressions;
|
| - }
|
| -
|
| - // Term grammar:
|
| - //
|
| - // term:
|
| - // unary_operator?
|
| - // [ term_value ]
|
| - // | STRING S* | IDENT S* | URI S* | UNICODERANGE S* | hexcolor
|
| - //
|
| - // term_value:
|
| - // NUMBER S* | PERCENTAGE S* | LENGTH S* | EMS S* | EXS S* | ANGLE S* |
|
| - // TIME S* | FREQ S* | function
|
| - //
|
| - // NUMBER: {num}
|
| - // PERCENTAGE: {num}%
|
| - // LENGTH: {num}['px' | 'cm' | 'mm' | 'in' | 'pt' | 'pc']
|
| - // EMS: {num}'em'
|
| - // EXS: {num}'ex'
|
| - // ANGLE: {num}['deg' | 'rad' | 'grad']
|
| - // TIME: {num}['ms' | 's']
|
| - // FREQ: {num}['hz' | 'khz']
|
| - // function: IDENT '(' expr ')'
|
| - //
|
| - processTerm() {
|
| - int start = _peekToken.start;
|
| - Token t; // token for term's value
|
| - var value; // value of term (numeric values)
|
| -
|
| - var unary = "";
|
| -
|
| - switch (_peek()) {
|
| - case TokenKind.HASH:
|
| - this._eat(TokenKind.HASH);
|
| - String hexText;
|
| - if (_peekKind(TokenKind.INTEGER)) {
|
| - String hexText1 = _peekToken.text;
|
| - _next();
|
| - if (_peekIdentifier()) {
|
| - hexText = '${hexText1}${identifier().name}';
|
| - } else {
|
| - hexText = hexText1;
|
| - }
|
| - } else if (_peekIdentifier()) {
|
| - hexText = identifier().name;
|
| - } else {
|
| - _errorExpected("hex number");
|
| - }
|
| -
|
| - try {
|
| - int hexValue = parseHex(hexText);
|
| - return new HexColorTerm(hexValue, hexText, _makeSpan(start));
|
| - } on HexNumberException catch (hne) {
|
| - _error('Bad hex number', _makeSpan(start));
|
| - }
|
| - break;
|
| - case TokenKind.INTEGER:
|
| - t = _next();
|
| - value = int.parse("${unary}${t.text}");
|
| - break;
|
| - case TokenKind.DOUBLE:
|
| - t = _next();
|
| - value = double.parse("${unary}${t.text}");
|
| - break;
|
| - case TokenKind.SINGLE_QUOTE:
|
| - case TokenKind.DOUBLE_QUOTE:
|
| - value = processQuotedString(false);
|
| - value = '"${value}"';
|
| - return new LiteralTerm(value, value, _makeSpan(start));
|
| - case TokenKind.LPAREN:
|
| - _next();
|
| -
|
| - GroupTerm group = new GroupTerm(_makeSpan(start));
|
| -
|
| - do {
|
| - var term = processTerm();
|
| - if (term != null && term is LiteralTerm) {
|
| - group.add(term);
|
| - }
|
| - } while (!_maybeEat(TokenKind.RPAREN));
|
| -
|
| - return group;
|
| - case TokenKind.LBRACK:
|
| - _next();
|
| -
|
| - var term = processTerm();
|
| - if (!(term is NumberTerm)) {
|
| - _error('Expecting a positive number', _makeSpan(start));
|
| - }
|
| -
|
| - _eat(TokenKind.RBRACK);
|
| -
|
| - return new ItemTerm(term.value, term.text, _makeSpan(start));
|
| - case TokenKind.IDENTIFIER:
|
| - var nameValue = identifier(); // Snarf up the ident we'll remap, maybe.
|
| -
|
| - if (_maybeEat(TokenKind.LPAREN)) {
|
| - // FUNCTION
|
| - return processFunction(nameValue);
|
| - } else {
|
| - // TODO(terry): Need to have a list of known identifiers today only
|
| - // 'from' is special.
|
| - if (nameValue.name == 'from') {
|
| - return new LiteralTerm(nameValue, nameValue.name, _makeSpan(start));
|
| - }
|
| -
|
| - // What kind of identifier is it?
|
| - try {
|
| - // Named color?
|
| - int colorValue = TokenKind.matchColorName(nameValue.name);
|
| -
|
| - // Yes, process the color as an RGB value.
|
| - String rgbColor = TokenKind.decimalToHex(colorValue, 3);
|
| - try {
|
| - colorValue = parseHex(rgbColor);
|
| - } on HexNumberException catch (hne) {
|
| - _error('Bad hex number', _makeSpan(start));
|
| - }
|
| - return new HexColorTerm(colorValue, rgbColor, _makeSpan(start));
|
| - } catch (error) {
|
| - if (error is NoColorMatchException) {
|
| - // TODO(terry): Other named things to match with validator?
|
| -
|
| - // TODO(terry): Disable call to _warning need one World class for
|
| - // both CSS parser and other parser (e.g., template)
|
| - // so all warnings, errors, options, etc. are driven
|
| - // from the one World.
|
| -// _warning('Unknown property value ${error.name}', _makeSpan(start));
|
| - return new LiteralTerm(nameValue, nameValue.name, _makeSpan(start));
|
| - }
|
| - }
|
| - }
|
| - }
|
| -
|
| - var term;
|
| - var unitType = this._peek();
|
| -
|
| - switch (unitType) {
|
| - case TokenKind.UNIT_EM:
|
| - term = new EmTerm(value, t.text, _makeSpan(start));
|
| - _next(); // Skip the unit
|
| - break;
|
| - case TokenKind.UNIT_EX:
|
| - term = new ExTerm(value, t.text, _makeSpan(start));
|
| - _next(); // Skip the unit
|
| - break;
|
| - case TokenKind.UNIT_LENGTH_PX:
|
| - case TokenKind.UNIT_LENGTH_CM:
|
| - case TokenKind.UNIT_LENGTH_MM:
|
| - case TokenKind.UNIT_LENGTH_IN:
|
| - case TokenKind.UNIT_LENGTH_PT:
|
| - case TokenKind.UNIT_LENGTH_PC:
|
| - term = new LengthTerm(value, t.text, _makeSpan(start), unitType);
|
| - _next(); // Skip the unit
|
| - break;
|
| - case TokenKind.UNIT_ANGLE_DEG:
|
| - case TokenKind.UNIT_ANGLE_RAD:
|
| - case TokenKind.UNIT_ANGLE_GRAD:
|
| - term = new AngleTerm(value, t.text, _makeSpan(start), unitType);
|
| - _next(); // Skip the unit
|
| - break;
|
| - case TokenKind.UNIT_TIME_MS:
|
| - case TokenKind.UNIT_TIME_S:
|
| - term = new TimeTerm(value, t.text, _makeSpan(start), unitType);
|
| - _next(); // Skip the unit
|
| - break;
|
| - case TokenKind.UNIT_FREQ_HZ:
|
| - case TokenKind.UNIT_FREQ_KHZ:
|
| - term = new FreqTerm(value, t.text, _makeSpan(start), unitType);
|
| - _next(); // Skip the unit
|
| - break;
|
| - case TokenKind.PERCENT:
|
| - term = new PercentageTerm(value, t.text, _makeSpan(start));
|
| - _next(); // Skip the %
|
| - break;
|
| - case TokenKind.UNIT_FRACTION:
|
| - term = new FractionTerm(value, t.text, _makeSpan(start));
|
| - _next(); // Skip the unit
|
| - break;
|
| - default:
|
| - if (value != null) {
|
| - term = new NumberTerm(value, t.text, _makeSpan(start));
|
| - }
|
| - }
|
| -
|
| - return term;
|
| - }
|
| -
|
| - processQuotedString([bool urlString = false]) {
|
| - int start = _peekToken.start;
|
| -
|
| - // URI term sucks up everything inside of quotes(' or ") or between parens
|
| - int stopToken = urlString ? TokenKind.RPAREN : -1;
|
| - switch (_peek()) {
|
| - case TokenKind.SINGLE_QUOTE:
|
| - stopToken = TokenKind.SINGLE_QUOTE;
|
| - _next(); // Skip the SINGLE_QUOTE.
|
| - break;
|
| - case TokenKind.DOUBLE_QUOTE:
|
| - stopToken = TokenKind.DOUBLE_QUOTE;
|
| - _next(); // Skip the DOUBLE_QUOTE.
|
| - break;
|
| - default:
|
| - if (urlString) {
|
| - if (_peek() == TokenKind.LPAREN) {
|
| - _next(); // Skip the LPAREN.
|
| - }
|
| - stopToken = TokenKind.RPAREN;
|
| - } else {
|
| - _error('unexpected string', _makeSpan(start));
|
| - }
|
| - }
|
| -
|
| - StringBuffer stringValue = new StringBuffer();
|
| -
|
| - // Gobble up everything until we hit our stop token.
|
| - int runningStart = _peekToken.start;
|
| - while (_peek() != stopToken && _peek() != TokenKind.END_OF_FILE) {
|
| - var tok = _next();
|
| - stringValue.write(tok.text);
|
| - }
|
| -
|
| - if (stopToken != TokenKind.RPAREN) {
|
| - _next(); // Skip the SINGLE_QUOTE or DOUBLE_QUOTE;
|
| - }
|
| -
|
| - return stringValue.toString();
|
| - }
|
| -
|
| - // Function grammar:
|
| - //
|
| - // function: IDENT '(' expr ')'
|
| - //
|
| - processFunction(Identifier func) {
|
| - int start = _peekToken.start;
|
| -
|
| - String name = func.name;
|
| -
|
| - switch (name) {
|
| - case 'url':
|
| - // URI term sucks up everything inside of quotes(' or ") or between parens
|
| - String urlParam = processQuotedString(true);
|
| -
|
| - // TODO(terry): Better error messge and checking for mismatched quotes.
|
| - if (_peek() == TokenKind.END_OF_FILE) {
|
| - _error("problem parsing URI", _peekToken.span);
|
| - }
|
| -
|
| - if (_peek() == TokenKind.RPAREN) {
|
| - _next();
|
| - }
|
| -
|
| - return new UriTerm(urlParam, _makeSpan(start));
|
| - case 'calc':
|
| - // TODO(terry): Implement expression handling...
|
| - break;
|
| - default:
|
| - var expr = processExpr();
|
| - if (!_maybeEat(TokenKind.RPAREN)) {
|
| - _error("problem parsing function expected ), ", _peekToken.span);
|
| - }
|
| -
|
| - return new FunctionTerm(name, name, expr, _makeSpan(start));
|
| - }
|
| -
|
| - return null;
|
| - }
|
| -
|
| - identifier() {
|
| - var tok = _next();
|
| - if (!TokenKind.isIdentifier(tok.kind)) {
|
| - _error('expected identifier, but found $tok', tok.span);
|
| - }
|
| -
|
| - return new Identifier(tok.text, _makeSpan(tok.start));
|
| - }
|
| -
|
| - // TODO(terry): Move this to base <= 36 and into shared code.
|
| - static int _hexDigit(int c) {
|
| - if(c >= 48/*0*/ && c <= 57/*9*/) {
|
| - return c - 48;
|
| - } else if (c >= 97/*a*/ && c <= 102/*f*/) {
|
| - return c - 87;
|
| - } else if (c >= 65/*A*/ && c <= 70/*F*/) {
|
| - return c - 55;
|
| - } else {
|
| - return -1;
|
| - }
|
| - }
|
| -
|
| - static int parseHex(String hex) {
|
| - var result = 0;
|
| -
|
| - for (int i = 0; i < hex.length; i++) {
|
| - var digit = _hexDigit(hex.codeUnitAt(i));
|
| - if (digit < 0) {
|
| - throw new HexNumberException();
|
| - }
|
| - result = (result << 4) + digit;
|
| - }
|
| -
|
| - return result;
|
| - }
|
| -}
|
| -
|
| -/** Not a hex number. */
|
| -class HexNumberException implements Exception {
|
| - HexNumberException();
|
| -}
|
| -
|
|
|