| Index: pkg/csslib/lib/parser.dart
|
| diff --git a/pkg/csslib/lib/parser.dart b/pkg/csslib/lib/parser.dart
|
| index 2539d94de14da2dbded62a2b7ea95a1e943ef12e..272268b5cf22c543edeec66bd52fd7bcd2c3a893 100644
|
| --- a/pkg/csslib/lib/parser.dart
|
| +++ b/pkg/csslib/lib/parser.dart
|
| @@ -30,6 +30,7 @@ class ParserState extends TokenizerState {
|
| : super(tokenizer);
|
| }
|
|
|
| +// TODO(jmesserly): this should not be global
|
| void _createMessages({List<Message> errors, List<String> options}) {
|
| if (errors == null) errors = [];
|
|
|
| @@ -45,7 +46,7 @@ bool get isChecked => messages.options.checked;
|
|
|
| // TODO(terry): Remove nested name parameter.
|
| /** Parse and analyze the CSS file. */
|
| -StyleSheet compile(var input, {List<Message> errors, List<String> options,
|
| +StyleSheet compile(input, {List<Message> errors, List<String> options,
|
| bool nested: true,
|
| bool polyfill: false,
|
| List<StyleSheet> includes: null}) {
|
| @@ -85,13 +86,12 @@ void analyze(List<StyleSheet> styleSheets,
|
| * or [List<int>] of bytes and returns a [StyleSheet] AST. The optional
|
| * [errors] list will contain each error/warning as a [Message].
|
| */
|
| -StyleSheet parse(var input, {List<Message> errors, List<String> options}) {
|
| +StyleSheet parse(input, {List<Message> errors, List<String> options}) {
|
| var source = _inputAsString(input);
|
|
|
| _createMessages(errors: errors, options: options);
|
|
|
| var file = new SourceFile.text(null, source);
|
| -
|
| return new _Parser(file, source).parse();
|
| }
|
|
|
| @@ -100,17 +100,32 @@ StyleSheet parse(var input, {List<Message> errors, List<String> options}) {
|
| * or [List<int>] of bytes and returns a [StyleSheet] AST. The optional
|
| * [errors] list will contain each error/warning as a [Message].
|
| */
|
| -StyleSheet selector(var input, {List<Message> errors}) {
|
| +// TODO(jmesserly): should rename "parseSelector" and return Selector
|
| +StyleSheet selector(input, {List<Message> errors}) {
|
| var source = _inputAsString(input);
|
|
|
| _createMessages(errors: errors);
|
|
|
| var file = new SourceFile.text(null, source);
|
| + return (new _Parser(file, source)
|
| + ..tokenizer.inSelector = true)
|
| + .parseSelector();
|
| +}
|
| +
|
| +SelectorGroup parseSelectorGroup(input, {List<Message> errors}) {
|
| + var source = _inputAsString(input);
|
| +
|
| + _createMessages(errors: errors);
|
|
|
| - return new _Parser(file, source).parseSelector();
|
| + var file = new SourceFile.text(null, source);
|
| + return (new _Parser(file, source)
|
| + // TODO(jmesserly): this fix should be applied to the parser. It's tricky
|
| + // because by the time the flag is set one token has already been fetched.
|
| + ..tokenizer.inSelector = true)
|
| + .processSelectorGroup();
|
| }
|
|
|
| -String _inputAsString(var input) {
|
| +String _inputAsString(input) {
|
| String source;
|
|
|
| if (input is String) {
|
| @@ -147,6 +162,7 @@ String _inputAsString(var input) {
|
| class Parser {
|
| final _Parser _parser;
|
|
|
| + // TODO(jmesserly): having file and text is redundant.
|
| Parser(SourceFile file, String text, {int start: 0, String baseUrl}) :
|
| _parser = new _Parser(file, text, start: start, baseUrl: baseUrl);
|
|
|
| @@ -1174,6 +1190,7 @@ class _Parser {
|
| SelectorGroup processSelectorGroup() {
|
| List<Selector> selectors = [];
|
| int start = _peekToken.start;
|
| +
|
| do {
|
| Selector selector = processSelector();
|
| if (selector != null) {
|
| @@ -1413,18 +1430,32 @@ class _Parser {
|
| }
|
|
|
| // Functional pseudo?
|
| - if (_maybeEat(TokenKind.LPAREN)) {
|
| +
|
| + if (_peekToken.kind == TokenKind.LPAREN) {
|
| +
|
| if (!pseudoElement && pseudoName.name.toLowerCase() == 'not') {
|
| + _eat(TokenKind.LPAREN);
|
| +
|
| // Negation : ':NOT(' S* negation_arg S* ')'
|
| var negArg = simpleSelector();
|
|
|
| _eat(TokenKind.RPAREN);
|
| return new NegationSelector(negArg, _makeSpan(start));
|
| } else {
|
| + // Special parsing for expressions in pseudo functions. Minus is used
|
| + // as operator not identifier.
|
| + // TODO(jmesserly): we need to flip this before we eat the "(" as the
|
| + // next token will be fetched when we do that. I think we should try to
|
| + // refactor so we don't need this boolean; it seems fragile.
|
| + tokenizer.inSelectorExpression = true;
|
| + _eat(TokenKind.LPAREN);
|
| +
|
| // Handle function expression.
|
| var span = _makeSpan(start);
|
| var expr = processSelectorExpression();
|
|
|
| + tokenizer.inSelectorExpression = false;
|
| +
|
| // Used during selector look-a-head if not a SelectorExpression is
|
| // bad.
|
| if (expr is! SelectorExpression) {
|
| @@ -1463,27 +1494,23 @@ class _Parser {
|
| processSelectorExpression() {
|
| var start = _peekToken.start;
|
|
|
| - var expression = new SelectorExpression(_makeSpan(start));
|
| + var expressions = [];
|
|
|
| Token termToken;
|
| var value;
|
|
|
| - // Special parsing for expressions in pseudo functions. Minus is used as
|
| - // operator not identifier.
|
| - tokenizer.selectorExpression = true;
|
| -
|
| var keepParsing = true;
|
| while (keepParsing) {
|
| switch (_peek()) {
|
| case TokenKind.PLUS:
|
| start = _peekToken.start;
|
| termToken = _next();
|
| - expression.add(new OperatorPlus(_makeSpan(start)));
|
| + expressions.add(new OperatorPlus(_makeSpan(start)));
|
| break;
|
| case TokenKind.MINUS:
|
| start = _peekToken.start;
|
| termToken = _next();
|
| - expression.add(new OperatorMinus(_makeSpan(start)));
|
| + expressions.add(new OperatorMinus(_makeSpan(start)));
|
| break;
|
| case TokenKind.INTEGER:
|
| termToken = _next();
|
| @@ -1517,15 +1544,13 @@ class _Parser {
|
| if (unitTerm == null) {
|
| unitTerm = new LiteralTerm(value, value.name, _makeSpan(start));
|
| }
|
| - expression.add(unitTerm);
|
| + expressions.add(unitTerm);
|
|
|
| value = null;
|
| }
|
| }
|
|
|
| - tokenizer.selectorExpression = false;
|
| -
|
| - return expression;
|
| + return new SelectorExpression(expressions, _makeSpan(start));
|
| }
|
|
|
| // Attribute grammar:
|
| @@ -2343,6 +2368,12 @@ class _Parser {
|
|
|
| // URI term sucks up everything inside of quotes(' or ") or between parens
|
| var stopToken = urlString ? TokenKind.RPAREN : -1;
|
| +
|
| + // Note: disable skipping whitespace tokens inside a string.
|
| + // TODO(jmesserly): the layering here feels wrong.
|
| + var skipWhitespace = tokenizer._skipWhitespace;
|
| + tokenizer._skipWhitespace = false;
|
| +
|
| switch (_peek()) {
|
| case TokenKind.SINGLE_QUOTE:
|
| stopToken = TokenKind.SINGLE_QUOTE;
|
| @@ -2369,20 +2400,20 @@ class _Parser {
|
|
|
| // Gobble up everything until we hit our stop token.
|
| var runningStart = _peekToken.start;
|
| +
|
| + var stringValue = new StringBuffer();
|
| while (_peek() != stopToken && _peek() != TokenKind.END_OF_FILE) {
|
| - var tok = _next();
|
| + stringValue.write(_next().text);
|
| }
|
|
|
| - // All characters between quotes is the string.
|
| - var end = _peekToken.end;
|
| - var stringValue = (_peekToken.span as FileSpan).file.getText(start,
|
| - end - 1);
|
| + tokenizer._skipWhitespace = skipWhitespace;
|
|
|
| + // All characters between quotes is the string.
|
| if (stopToken != TokenKind.RPAREN) {
|
| _next(); // Skip the SINGLE_QUOTE or DOUBLE_QUOTE;
|
| }
|
|
|
| - return stringValue;
|
| + return stringValue.toString();
|
| }
|
|
|
| // TODO(terry): Should probably understand IE's non-standard filter syntax to
|
|
|