Improve the speed and memory efficiency of csslib parsing.
In particular, this avoids calling [FileSpan.start] and [FileSpan.end]
where possible, since they lazily instantiate objects.
R=sra@google.com
Review URL: https://codereview.chromium.org//751453004
git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/csslib@42080 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 392de58..7c044aa 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.11.0+3
+
+* Improve the speed and memory efficiency of parsing.
+
## 0.11.0+2
* Fix another test that was failing on IE10.
diff --git a/lib/parser.dart b/lib/parser.dart
index 282fe59..6060166 100644
--- a/lib/parser.dart
+++ b/lib/parser.dart
@@ -196,7 +196,7 @@
StyleSheet parse() {
List<TreeNode> productions = [];
- int start = _peekToken.start;
+ var start = _peekToken.span;
while (!_maybeEat(TokenKind.END_OF_FILE) && !_peekKind(TokenKind.RBRACE)) {
// TODO(terry): Need to handle charset.
var directive = processDirective();
@@ -222,7 +222,7 @@
StyleSheet parseSelector() {
List<TreeNode> productions = [];
- int start = _peekToken.start;
+ var start = _peekToken.span;
while (!_maybeEat(TokenKind.END_OF_FILE) && !_peekKind(TokenKind.RBRACE)) {
var selector = processSelector();
if (selector != null) {
@@ -334,12 +334,14 @@
messages.warning(message, location);
}
- SourceSpan _makeSpan(int start) {
+ SourceSpan _makeSpan(FileSpan start) {
// TODO(terry): there are places where we are creating spans before we eat
- // the tokens, so using _previousToken.end is not always valid.
- var end = _previousToken != null && _previousToken.end >= start
- ? _previousToken.end : _peekToken.end;
- return file.span(start, end);
+ // the tokens, so using _previousToken is not always valid.
+ // TODO(nweiz): use < rather than compareTo when SourceSpan supports it.
+ if (_previousToken == null || _previousToken.span.compareTo(start) < 0) {
+ return start;
+ }
+ return start.expand(_previousToken.span);
}
///////////////////////////////////////////////////////////////////
@@ -389,7 +391,7 @@
// Grammar: [ONLY | NOT]? S* media_type S*
// [ AND S* MediaExpr ]* | MediaExpr [ AND S* MediaExpr ]*
- int start = _peekToken.start;
+ var start = _peekToken.span;
// Is it a unary media operator?
var op = _peekToken.text;
@@ -408,7 +410,7 @@
}
}
_next();
- start = _peekToken.start;
+ start = _peekToken.span;
}
var type;
@@ -441,14 +443,14 @@
}
MediaExpression processMediaExpression([bool andOperator = false]) {
- int start = _peekToken.start;
+ var start = _peekToken.span;
// Grammar: '(' S* media_feature S* [ ':' S* expr ]? ')' S*
if (_maybeEat(TokenKind.LPAREN)) {
if (_peekIdentifier()) {
var feature = identifier(); // Media feature.
while (_maybeEat(TokenKind.COLON)) {
- int startExpr = _peekToken.start;
+ var startExpr = _peekToken.span;
var exprs = processExpr();
if (_maybeEat(TokenKind.RPAREN)) {
return new MediaExpression(andOperator, feature, exprs,
@@ -484,7 +486,7 @@
* content '@content'
*/
processDirective() {
- int start = _peekToken.start;
+ var start = _peekToken.span;
var tokId = processVariableOrDirective();
if (tokId is VarDefinitionDirective) return tokId;
@@ -703,7 +705,7 @@
List<TreeNode> productions = [];
- start = _peekToken.start;
+ start = _peekToken.span;
while (!_maybeEat(TokenKind.END_OF_FILE)) {
RuleSet ruleset = processRuleSet();
if (ruleset == null) {
@@ -755,7 +757,7 @@
namespaceUri, _makeSpan(start));
case TokenKind.DIRECTIVE_MIXIN:
- return processMixin(start);
+ return processMixin();
case TokenKind.DIRECTIVE_INCLUDE:
return processInclude( _makeSpan(start));
@@ -778,7 +780,7 @@
* [ruleset | property | directive]*
* '}'
*/
- MixinDefinition processMixin(int start) {
+ MixinDefinition processMixin() {
_next();
var name = identifier();
@@ -793,7 +795,7 @@
if (varDef is VarDefinitionDirective || varDef is VarDefinition) {
params.add(varDef);
} else if (mustHaveParam) {
- _warning("Expecting parameter", _makeSpan(_peekToken.start));
+ _warning("Expecting parameter", _makeSpan(_peekToken.span));
keepGoing = false;
}
if (_maybeEat(TokenKind.COMMA)) {
@@ -810,7 +812,7 @@
List<TreeNode> declarations = [];
var mixinDirective;
- start = _peekToken.start;
+ var start = _peekToken.span;
while (!_maybeEat(TokenKind.END_OF_FILE)) {
var directive = processDirective();
if (directive != null) {
@@ -833,7 +835,7 @@
include.span));
} else {
_warning("Error mixing of top-level vs declarations mixins",
- _makeSpan(include));
+ _makeSpan(include.span));
}
});
declGroup.declarations.insertAll(0, newDecls);
@@ -882,7 +884,7 @@
* return the token id of a directive or -1 if neither.
*/
processVariableOrDirective({bool mixinParameter: false}) {
- int start = _peekToken.start;
+ var start = _peekToken.span;
var tokId = _peek();
// Handle case for @ directive (where there's a whitespace between the @
@@ -1046,7 +1048,7 @@
}
DeclarationGroup processDeclarations({bool checkBrace: true}) {
- int start = _peekToken.start;
+ var start = _peekToken.span;
if (checkBrace) _eat(TokenKind.LBRACE);
@@ -1104,7 +1106,7 @@
List<DeclarationGroup> processMarginsDeclarations() {
List groups = [];
- int start = _peekToken.start;
+ var start = _peekToken.span;
_eat(TokenKind.LBRACE);
@@ -1189,7 +1191,7 @@
SelectorGroup processSelectorGroup() {
List<Selector> selectors = [];
- int start = _peekToken.start;
+ var start = _peekToken.span;
do {
Selector selector = processSelector();
@@ -1208,7 +1210,7 @@
*/
Selector processSelector() {
var simpleSequences = <SimpleSelectorSequence>[];
- var start = _peekToken.start;
+ var start = _peekToken.span;
while (true) {
// First item is never descendant make sure it's COMBINATOR_NONE.
var selectorItem = simpleSelectorSequence(simpleSequences.length == 0);
@@ -1225,7 +1227,7 @@
}
simpleSelectorSequence(bool forceCombinatorNone) {
- var start = _peekToken.start;
+ var start = _peekToken.span;
var combinatorType = TokenKind.COMBINATOR_NONE;
var thisOperator = false;
@@ -1301,12 +1303,12 @@
// code.
// TODO(terry): Need to handle attribute namespace too.
var first;
- int start = _peekToken.start;
+ var start = _peekToken.span;
switch (_peek()) {
case TokenKind.ASTERISK:
// Mark as universal namespace.
var tok = _next();
- first = new Wildcard(_makeSpan(tok.start));
+ first = new Wildcard(_makeSpan(tok.span));
break;
case TokenKind.IDENTIFIER:
first = identifier();
@@ -1329,7 +1331,7 @@
case TokenKind.ASTERISK:
// Mark as universal element
var tok = _next();
- element = new Wildcard(_makeSpan(tok.start));
+ element = new Wildcard(_makeSpan(tok.span));
break;
case TokenKind.IDENTIFIER:
element = identifier();
@@ -1366,7 +1368,7 @@
*/
simpleSelectorTail() {
// Check for HASH | class | attrib | pseudo | negation
- var start = _peekToken.start;
+ var start = _peekToken.span;
switch (_peek()) {
case TokenKind.HASH:
_eat(TokenKind.HASH);
@@ -1413,7 +1415,7 @@
}
}
- processPseudoSelector(int start) {
+ processPseudoSelector(FileSpan start) {
// :pseudo-class ::pseudo-element
// TODO(terry): '::' should be token.
_eat(TokenKind.COLON);
@@ -1492,7 +1494,7 @@
* NUMBER {num}
*/
processSelectorExpression() {
- var start = _peekToken.start;
+ var start = _peekToken.span;
var expressions = [];
@@ -1503,12 +1505,12 @@
while (keepParsing) {
switch (_peek()) {
case TokenKind.PLUS:
- start = _peekToken.start;
+ start = _peekToken.span;
termToken = _next();
expressions.add(new OperatorPlus(_makeSpan(start)));
break;
case TokenKind.MINUS:
- start = _peekToken.start;
+ start = _peekToken.span;
termToken = _next();
expressions.add(new OperatorMinus(_makeSpan(start)));
break;
@@ -1573,7 +1575,7 @@
//
//
AttributeSelector processAttribute() {
- var start = _peekToken.start;
+ var start = _peekToken.span;
if (_maybeEat(TokenKind.LBRACK)) {
var attrName = identifier();
@@ -1629,7 +1631,7 @@
Declaration processDeclaration(List dartStyles) {
Declaration decl;
- int start = _peekToken.start;
+ var start = _peekToken.span;
// IE7 hack of * before property name if so the property is IE7 or below.
var ie7 = _peekKind(TokenKind.ASTERISK);
@@ -1681,7 +1683,7 @@
simpleSequences.add(selector);
}
if (_peekKind(TokenKind.COLON)) {
- var pseudoSelector = processPseudoSelector(_peekToken.start);
+ var pseudoSelector = processPseudoSelector(_peekToken.span);
if (pseudoSelector is PseudoElementSelector ||
pseudoSelector is PseudoClassSelector) {
simpleSequences.add(pseudoSelector);
@@ -2033,7 +2035,7 @@
// term: (see processTerm)
//
Expressions processExpr([bool ieFilter = false]) {
- var start = _peekToken.start;
+ var start = _peekToken.span;
var expressions = new Expressions(_makeSpan(start));
var keepGoing = true;
@@ -2041,7 +2043,7 @@
while (keepGoing && (expr = processTerm(ieFilter)) != null) {
var op;
- var opStart = _peekToken.start;
+ var opStart = _peekToken.span;
switch (_peek()) {
case TokenKind.SLASH:
@@ -2053,7 +2055,7 @@
case TokenKind.BACKSLASH:
// Backslash outside of string; detected IE8 or older signaled by \9 at
// end of an expression.
- var ie8Start = _peekToken.start;
+ var ie8Start = _peekToken.span;
_next();
if (_peekKind(TokenKind.INTEGER)) {
@@ -2117,7 +2119,7 @@
// function: IDENT '(' expr ')'
//
processTerm([bool ieFilter = false]) {
- var start = _peekToken.start;
+ var start = _peekToken.span;
Token t; // token for term's value
var value; // value of term (numeric values)
@@ -2364,7 +2366,7 @@
}
String processQuotedString([bool urlString = false]) {
- var start = _peekToken.start;
+ var start = _peekToken.span;
// URI term sucks up everything inside of quotes(' or ") or between parens
var stopToken = urlString ? TokenKind.RPAREN : -1;
@@ -2377,19 +2379,19 @@
switch (_peek()) {
case TokenKind.SINGLE_QUOTE:
stopToken = TokenKind.SINGLE_QUOTE;
- start = _peekToken.start + 1; // Skip the quote might have whitespace.
- _next(); // Skip the SINGLE_QUOTE.
+ _next(); // Skip the SINGLE_QUOTE.
+ start = _peekToken.span;
break;
case TokenKind.DOUBLE_QUOTE:
stopToken = TokenKind.DOUBLE_QUOTE;
- start = _peekToken.start + 1; // Skip the quote might have whitespace.
- _next(); // Skip the DOUBLE_QUOTE.
+ _next(); // Skip the DOUBLE_QUOTE.
+ start = _peekToken.span;
break;
default:
if (urlString) {
if (_peek() == TokenKind.LPAREN) {
_next(); // Skip the LPAREN.
- start = _peekToken.start;
+ start = _peekToken.span;
}
stopToken = TokenKind.RPAREN;
} else {
@@ -2399,8 +2401,6 @@
}
// 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) {
stringValue.write(_next().text);
@@ -2426,7 +2426,7 @@
* We'll just parse everything after the 'progid:' look for the left paren
* then parse to the right paren ignoring everything in between.
*/
- processIEFilter(int startAfterProgidColon) {
+ processIEFilter(FileSpan startAfterProgidColon) {
var parens = 0;
while (_peek() != TokenKind.END_OF_FILE) {
@@ -2438,7 +2438,7 @@
case TokenKind.RPAREN:
_eat(TokenKind.RPAREN);
if (--parens == 0) {
- var tok = tokenizer.makeIEFilter(startAfterProgidColon,
+ var tok = tokenizer.makeIEFilter(startAfterProgidColon.start.offset,
_peekToken.start);
return new LiteralTerm(tok.text, tok.text, tok.span);
}
@@ -2454,7 +2454,7 @@
// function: IDENT '(' expr ')'
//
processFunction(Identifier func) {
- var start = _peekToken.start;
+ var start = _peekToken.span;
var name = func.name;
@@ -2519,10 +2519,10 @@
if (isChecked) {
_warning('expected identifier, but found $tok', tok.span);
}
- return new Identifier("", _makeSpan(tok.start));
+ return new Identifier("", _makeSpan(tok.span));
}
- return new Identifier(tok.text, _makeSpan(tok.start));
+ return new Identifier(tok.text, _makeSpan(tok.span));
}
// TODO(terry): Move this to base <= 36 and into shared code.
diff --git a/lib/src/token.dart b/lib/src/token.dart
index debd301..b78fbcc 100644
--- a/lib/src/token.dart
+++ b/lib/src/token.dart
@@ -12,7 +12,7 @@
final int kind;
/** The location where this token was parsed from. */
- final SourceSpan span;
+ final FileSpan span;
/** The start offset of this token. */
int get start => span.start.offset;
@@ -43,13 +43,13 @@
/** A token containing a parsed literal value. */
class LiteralToken extends Token {
var value;
- LiteralToken(int kind, SourceSpan span, this.value) : super(kind, span);
+ LiteralToken(int kind, FileSpan span, this.value) : super(kind, span);
}
/** A token containing error information. */
class ErrorToken extends Token {
String message;
- ErrorToken(int kind, SourceSpan span, this.message) : super(kind, span);
+ ErrorToken(int kind, FileSpan span, this.message) : super(kind, span);
}
/**
@@ -61,6 +61,6 @@
class IdentifierToken extends Token {
final String text;
- IdentifierToken(this.text, int kind, SourceSpan span)
+ IdentifierToken(this.text, int kind, FileSpan span)
: super(kind, span);
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 1c18c70..fdd238f 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: csslib
-version: 0.11.0+2
+version: 0.11.0+3
author: Polymer.dart Team <web-ui-dev@dartlang.org>
description: A library for parsing CSS.
homepage: https://www.dartlang.org