Add partial support for calc
The expression is not fully parsed into the AST.
closes https://github.com/dart-lang/csslib/issues/17
R=kevmoo@google.com, yjbanov@google.com, kevinmoo@google.com
Review URL: https://codereview.chromium.org//1407333002 .
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4d14f0b..b4ee9e1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,8 @@
+## 0.12.2
+
+ * Fix to handle calc functions however, the expressions are treated as a
+ LiteralTerm and not fully parsed into the AST.
+
## 0.12.1
* Fix to handling of escapes in strings.
diff --git a/lib/parser.dart b/lib/parser.dart
index a715a9a..b3a22d6 100644
--- a/lib/parser.dart
+++ b/lib/parser.dart
@@ -2188,6 +2188,8 @@
var nameValue = identifier(); // Snarf up the ident we'll remap, maybe.
if (!ieFilter && _maybeEat(TokenKind.LPAREN)) {
+ var calc = processCalc(nameValue);
+ if (calc != null) return calc;
// FUNCTION
return processFunction(nameValue);
}
@@ -2442,6 +2444,64 @@
}
}
+ // TODO(terry): Hack to gobble up the calc expression as a string looking
+ // for the matching RPAREN the expression is not parsed into the
+ // AST.
+ //
+ // grammar should be:
+ //
+ // <calc()> = calc( <calc-sum> )
+ // <calc-sum> = <calc-product> [ [ '+' | '-' ] <calc-product> ]*
+ // <calc-product> = <calc-value> [ '*' <calc-value> | '/' <number> ]*
+ // <calc-value> = <number> | <dimension> | <percentage> | ( <calc-sum> )
+ //
+ String processCalcExpression() {
+ var inString = tokenizer._inString;
+ tokenizer._inString = false;
+
+ // Gobble up everything until we hit our stop token.
+ var stringValue = new StringBuffer();
+ var left = 1;
+ var matchingParens = false;
+ while (_peek() != TokenKind.END_OF_FILE && !matchingParens) {
+ var token = _peek();
+ if (token == TokenKind.LPAREN)
+ left++;
+ else if (token == TokenKind.RPAREN)
+ left--;
+
+ matchingParens = left == 0;
+ if (!matchingParens) stringValue.write(_next().text);
+ }
+
+ if (!matchingParens) {
+ _error("problem parsing function expected ), ", _peekToken.span);
+ }
+
+ tokenizer._inString = inString;
+
+ return stringValue.toString();
+ }
+
+ CalcTerm processCalc(Identifier func) {
+ var start = _peekToken.span;
+
+ var name = func.name;
+ if (name == 'calc') {
+ // TODO(terry): Implement expression parsing properly.
+ String expression = processCalcExpression();
+ var calcExpr = new LiteralTerm(expression, expression, _makeSpan(start));
+
+ if (!_maybeEat(TokenKind.RPAREN)) {
+ _error("problem parsing function expected ), ", _peekToken.span);
+ }
+
+ return new CalcTerm(name, name, calcExpr, _makeSpan(start));
+ }
+
+ return null;
+ }
+
// Function grammar:
//
// function: IDENT '(' expr ')'
@@ -2466,9 +2526,6 @@
}
return new UriTerm(urlParam, _makeSpan(start));
- case 'calc':
- // TODO(terry): Implement expression handling...
- break;
case 'var':
// TODO(terry): Consider handling var in IE specific filter/progid. This
// will require parsing entire IE specific syntax e.g.,
diff --git a/lib/src/css_printer.dart b/lib/src/css_printer.dart
index 125b5ae..a62ca47 100644
--- a/lib/src/css_printer.dart
+++ b/lib/src/css_printer.dart
@@ -38,6 +38,12 @@
// flag for obfuscation.
bool get _isTesting => !prettyPrint;
+ void visitCalcTerm(CalcTerm node) {
+ emit('${node.text}(');
+ node.expr.visit(this);
+ emit(')');
+ }
+
void visitCssComment(CssComment node) {
emit('/* ${node.comment} */');
}
diff --git a/lib/src/tree.dart b/lib/src/tree.dart
index 75e9629..ba8370e 100644
--- a/lib/src/tree.dart
+++ b/lib/src/tree.dart
@@ -44,6 +44,21 @@
String get name => 'not';
}
+// calc(...)
+// TODO(terry): Hack to handle calc however the expressions should be fully
+// parsed and in the AST.
+class CalcTerm extends LiteralTerm {
+ final LiteralTerm expr;
+
+ CalcTerm(var value, String t, this.expr, SourceSpan span)
+ : super(value, t, span);
+
+ CalcTerm clone() => new CalcTerm(value, text, expr.clone(), span);
+ visit(VisitorBase visitor) => visitor.visitCalcTerm(this);
+
+ String toString() => "$text($expr)";
+}
+
// /* .... */
class CssComment extends TreeNode {
final String comment;
diff --git a/lib/src/tree_printer.dart b/lib/src/tree_printer.dart
index 030a868..9b0a6c2 100644
--- a/lib/src/tree_printer.dart
+++ b/lib/src/tree_printer.dart
@@ -46,6 +46,13 @@
heading('Directive', node);
}
+ void visitCalcTerm(CalcTerm node) {
+ heading('CalcTerm', node);
+ output.depth++;
+ super.visitCalcTerm(node);
+ output.depth--;
+ }
+
void visitCssComment(CssComment node) {
heading('Comment', node);
output.depth++;
diff --git a/lib/visitor.dart b/lib/visitor.dart
index 593e43a..b6babbd 100644
--- a/lib/visitor.dart
+++ b/lib/visitor.dart
@@ -13,6 +13,7 @@
part 'src/tree_printer.dart';
abstract class VisitorBase {
+ visitCalcTerm(CalcTerm node);
visitCssComment(CssComment node);
visitCommentDefinition(CommentDefinition node);
visitStyleSheet(StyleSheet node);
@@ -132,6 +133,11 @@
visitDirective(Directive node) {}
+ visitCalcTerm(CalcTerm node) {
+ visitLiteralTerm(node);
+ visitLiteralTerm(node.expr);
+ }
+
visitCssComment(CssComment node) {}
visitCommentDefinition(CommentDefinition node) {}
diff --git a/pubspec.yaml b/pubspec.yaml
index ab709a9..96f403e 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
name: csslib
-version: 0.12.2-dev
+version: 0.12.2
author: Polymer.dart Team <web-ui-dev@dartlang.org>
description: A library for parsing CSS.
homepage: https://github.com/dart-lang/csslib
diff --git a/test/declaration_test.dart b/test/declaration_test.dart
index 702921d..de589d7 100644
--- a/test/declaration_test.dart
+++ b/test/declaration_test.dart
@@ -1015,12 +1015,50 @@
expect(decl.expression.span.text, '50px');
}
-void testDeclarationSpanWithCalc() {
+void simpleCalc() {
final input = r'''.foo { height: calc(100% - 55px); }''';
var stylesheet = parseCss(input);
var decl = stylesheet.topLevels.single.declarationGroup.declarations.single;
- // This fails, with span being "height: calc("
- expect(decl.span.text, 'height: calc(100% - 55px);');
+ expect(decl.span.text, 'height: calc(100% - 55px)');
+}
+
+void complexCalc() {
+ final input = r'''.foo { left: calc((100%/3 - 2) * 1em - 2 * 1px); }''';
+ var stylesheet = parseCss(input);
+ var decl = stylesheet.topLevels.single.declarationGroup.declarations.single;
+ expect(decl.span.text, 'left: calc((100%/3 - 2) * 1em - 2 * 1px)');
+}
+
+void twoCalcs() {
+ final input = r'''.foo { margin: calc(1rem - 2px) calc(1rem - 1px); }''';
+ var stylesheet = parseCss(input);
+ var decl = stylesheet.topLevels.single.declarationGroup.declarations.single;
+ expect(decl.span.text, 'margin: calc(1rem - 2px) calc(1rem - 1px)');
+}
+
+void selectorWithCalcs() {
+ var errors = [];
+ final String input = r'''
+.foo {
+ width: calc(1em + 5 * 2em);
+ height: calc(1px + 2%) !important;
+ border: 5px calc(1pt + 2cm) 6px calc(1em + 1in + 2px) red;
+ border: calc(5px + 1em) 0px 1px calc(10 + 20 + 1px);
+ margin: 25px calc(50px + (100% / (3 - 1em) - 20%)) calc(10px + 10 * 20) calc(100% - 10px);
+}''';
+ final String generated = r'''
+.foo {
+ width: calc(1em + 5 * 2em);
+ height: calc(1px + 2%) !important;
+ border: 5px calc(1pt + 2cm) 6px calc(1em + 1in + 2px) #f00;
+ border: calc(5px + 1em) 0px 1px calc(10 + 20 + 1px);
+ margin: 25px calc(50px + (100% / (3 - 1em) - 20%)) calc(10px + 10 * 20) calc(100% - 10px);
+}''';
+
+ var stylesheet = parseCss(input, errors: errors);
+ expect(stylesheet != null, true);
+ expect(errors.isEmpty, true, reason: errors.toString());
+ expect(prettyPrint(stylesheet), generated);
}
main() {
@@ -1042,7 +1080,11 @@
test('Expression spans', testExpressionSpans,
skip: 'expression spans are broken'
' (https://github.com/dart-lang/csslib/issues/15)');
- test('Declaration span containing calc()', testDeclarationSpanWithCalc,
- skip: 'calc() declarations are broken'
- ' (https://github.com/dart-lang/csslib/issues/17)');
+ group('calc function', () {
+ test('simple calc', simpleCalc);
+ test('single complex', complexCalc);
+ test('two calc terms for same declaration', twoCalcs);
+ test('selector with many calc declarations', selectorWithCalcs);
+ });
}
+