Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(879)

Unified Diff: utils/css/parser.dart

Issue 8498020: Beginning of CSS parser using frog parsering infrastructure. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Minor cleanup. Created 9 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: utils/css/parser.dart
diff --git a/utils/css/parser.dart b/utils/css/parser.dart
new file mode 100644
index 0000000000000000000000000000000000000000..aa36c49612861a68247fb0b236a47231fc2d9bf0
--- /dev/null
+++ b/utils/css/parser.dart
@@ -0,0 +1,394 @@
+// 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.
+
+/**
+ * A simple recursive descent parser for CSS.
+ */
+class Parser {
+ Tokenizer tokenizer;
+
+ final lang.SourceFile source;
+
+ lang.Token _previousToken;
+ lang.Token _peekToken;
+
+ Parser(this.source, [int startOffset = 0]) {
+ tokenizer = new Tokenizer(source, true, startOffset);
+ _peekToken = tokenizer.next();
+ _previousToken = null;
+ }
+
+ /** 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;
+ }
+
+ lang.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 (var e) {
+ message = 'parsing error expected $expected';
+ }
+ _error(message, tok.span);
+ }
+
+ void _error(String message, [lang.SourceSpan location=null]) {
+ if (location === null) {
+ location = _peekToken.span;
+ }
+
+ lang.world.fatal(message, location); // syntax errors are fatal for now
+ }
+
+ lang.SourceSpan _makeSpan(int start) {
+ return new lang.SourceSpan(source, start, _previousToken.end);
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ // Top level productions
+ ///////////////////////////////////////////////////////////////////
+
+ List<SelectorGroup> preprocess() {
nweiz 2011/11/23 00:59:44 I don't like "preprocess" here either. This method
+ List<SelectorGroup> groups = [];
+ while (!_maybeEat(TokenKind.END_OF_FILE)) {
+ do {
+ int start = _peekToken.start;
+ groups.add(new SelectorGroup(selector(),
+ _makeSpan(start)));
+ } while (_maybeEat(TokenKind.COMMA));
+ }
+
+ return groups;
+ }
+
+ // Templates are @{selectors} single line nothing else.
+ SelectorGroup template() {
+ SelectorGroup selectorGroup = null;
+ if (!isPrematureEndOfFile()) {
+ selectorGroup = templateExpression();
+ }
+
+ return selectorGroup;
+ }
+
+ /*
+ * Expect @{css_expression}
+ */
+ templateExpression() {
+ int start = _peekToken.start;
+
+ _eat(TokenKind.AT);
+ _eat(TokenKind.LBRACE);
+
+ SelectorGroup group = new SelectorGroup(selector(),
+ _makeSpan(start));
+
+ _eat(TokenKind.RBRACE);
+
+ return group;
+ }
+
+ int classNameCheck(var selector, int matches) {
+ if (selector.isCombinatorDescendant() ||
+ (selector.isCombinatorNone() && matches == 0)) {
+ if (matches < 0) {
+ String tooMany = selector.toString();
+ throw new CssSelectorException(
+ 'Can not mix Id selector with class selector(s). Id ' +
+ 'selector must be singleton too many starting at $tooMany');
+ }
+
+ return matches + 1;
+ } else {
+ String error = selector.toString();
+ throw new CssSelectorException(
+ 'Selectors can not have combinators (>, +, or ~) before $error');
+ }
+ }
+
+ int elementIdCheck(var selector, int matches) {
+ if (selector.isCombinatorNone() && matches == 0) {
+ // Perfect just one element id returns matches of -1.
+ return -1;
+ } else if (selector.isCombinatorDescendant()) {
+ String tooMany = selector.toString();
+ throw new CssSelectorException(
+ 'Use of Id selector must be singleton starting at $tooMany');
+ } else {
+ String error = selector.toString();
+ throw new CssSelectorException(
+ 'Selectors can not have combinators (>, +, or ~) before $error');
+ }
+ }
+
+ // Validate the @{css expression} only .class and #elementId are valid inside
+ // of @{...}.
+ validateTemplate(List<lang.Node> selectors, CssWorld cssWorld) {
+ var errorSelector; // signal which selector didn't match.
nweiz 2011/11/23 00:59:44 Unused variable.
+ bool found = false; // signal if a selector is matched.
+
+ int matches = 0; // < 0 IdSelectors, > 0 ClassSelector
+ for (selector in selectors) {
+ found = false;
+ if (selector is ClassSelector) {
+ // Any class name starting with an underscore is a private class name
+ // that doesn't have to match the world of known classes.
+ if (!selector.name.startsWith('_')) {
+ // TODO(terry): For now iterate through all classes look for faster
+ // mechanism hash map, etc.
+ for (className in cssWorld.classes) {
+ if (selector.name == className) {
+ matches = classNameCheck(selector, matches);
+ found = true; // .class found.
+ break;
+ }
+ }
+ } else {
+ // Don't check any class name that is prefixed with an underscore.
+ // However, signal as found and bump up matches; it's a valid class
+ // name.
+ matches = classNameCheck(selector, matches);
+ found = true; // ._class are always okay.
+ }
+ } else if (selector is IdSelector) {
+ // Any element id starting with an underscore is a private element id
+ // that doesn't have to match the world of known elemtn ids.
+ if (!selector.name.startsWith('_')) {
+ for (id in cssWorld.ids) {
+ if (selector.name == id) {
+ matches = elementIdCheck(selector, matches);
+ found = true; // #id found.
+ break;
+ }
+ }
+ } else {
+ // Don't check any element ID that is prefixed with an underscore.
+ // However, signal as found and bump up matches; it's a valid element
+ // ID.
+ matches = elementIdCheck(selector, matches);
+ found = true; // #_id are always okay
+ }
+ } else {
+ String badSelector = selector.toString();
+ throw new CssSelectorException(
+ 'Invalid template selector $badSelector');
+ }
+
+ if (!found) {
+ String unknownName = selector.toString();
+ throw new CssSelectorException('Unknown selector name $unknownName');
+ }
+ }
+
+ // Every selector must match.
+ assert((matches >= 0 ? matches : -matches) == selectors.length);
+ }
+
+ ///////////////////////////////////////////////////////////////////
+ // Productions
+ ///////////////////////////////////////////////////////////////////
+
+ selector() {
+ List<SimpleSelector> simpleSelectors = [];
+ while (true) {
+ // First item is never descendant make sure it's COMBINATOR_NONE.
+ var selectorItem = simpleSelectorSequence(simpleSelectors.length == 0);
+ if (selectorItem != null) {
+ simpleSelectors.add(selectorItem);
+ } else {
+ break;
+ }
+ }
+
+ return simpleSelectors;
+ }
+
+ simpleSelectorSequence(bool forceCombinatorNone) {
nweiz 2011/11/23 00:59:44 While this technically works by treating sequences
+ int combinatorType = TokenKind.COMBINATOR_NONE;
+ switch (_peek()) {
+ case TokenKind.COMBINATOR_PLUS:
+ _eat(TokenKind.COMBINATOR_PLUS);
+ combinatorType = TokenKind.COMBINATOR_PLUS;
+ break;
+ case TokenKind.COMBINATOR_GREATER:
+ _eat(TokenKind.COMBINATOR_GREATER);
+ combinatorType = TokenKind.COMBINATOR_GREATER;
+ break;
+ case TokenKind.COMBINATOR_TILDE:
+ _eat(TokenKind.COMBINATOR_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;
+ }
+ }
+
+ return simpleSelector(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(int combinator) {
+ // 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.
+ 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 (first == null) {
+ // Check for HASH | class | attrib | pseudo | negation
+ return simpleSelectorTail(combinator);
+ }
+
+ // Could be a namespace?
+ var isNamespace = _maybeEat(TokenKind.NAMESPACE);
+ if (isNamespace) {
+ 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), combinator);
+ } else {
+ return new ElementSelector(first, _makeSpan(start), combinator);
+ }
+ }
+
+ simpleSelectorTail(int combinator) {
+ // 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), combinator);
+ case TokenKind.DOT:
+ _eat(TokenKind.DOT);
+ return new ClassSelector(identifier(), _makeSpan(start), combinator);
+ case TokenKind.PSEUDO:
+ // :pseudo-class ::pseudo-element
+ // TODO(terry): '::' should be token.
+ _eat(TokenKind.PSEUDO);
+ bool pseudoClass = _peek() != TokenKind.PSEUDO;
+ 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 pseudoClass ?
+ new PseudoClassSelector(name, _makeSpan(start), combinator) :
+ new PseudoElementSelector(name, _makeSpan(start), combinator);
+
+ // TODO(terry): attrib, negation.
+ }
+ }
+
+ 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));
+ }
+}
« no previous file with comments | « utils/css/cssworld.dart ('k') | utils/css/test.dart » ('j') | utils/css/tree.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698