Skip to content

Commit a081deb

Browse files
committed
Parse and evaluate calc expressions
We no longer rely on 'eval'. Instead, we parse the calc expression and compute the value. We take advantage of the requirement that + and - operators must be surrounded by whitespace. This lets us detect each of the five tokens in -1.23e+45 + -1.23e+45 - -1.23e+45 using a simple regular expression. Spec: https://www.w3.org/TR/css3-values/#calc-syntax resolves #74
1 parent ec226e6 commit a081deb

File tree

2 files changed

+76
-2
lines changed

2 files changed

+76
-2
lines changed

src/dimension-handler.js

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,78 @@
1414

1515
(function(scope, testing) {
1616

17+
// Evaluates a calc expression.
18+
// https://drafts.csswg.org/css-values-3/#calc-notation
19+
function calculate(expression) {
20+
// In calc expressions, white space is required on both sides of the
21+
// + and - operators. https://drafts.csswg.org/css-values-3/#calc-notation
22+
// Thus any + or - immediately adjacent to . or 0..9 is part of the number,
23+
// e.g. -1.23e+45
24+
// This regular expression matches ( ) * / + - and numbers.
25+
var tokenRegularExpression = /([\+\-\w\.]+|[\(\)\*\/])/g;
26+
var currentToken;
27+
function consume() {
28+
var matchResult = tokenRegularExpression.exec(expression);
29+
if (matchResult)
30+
currentToken = matchResult[0];
31+
else
32+
currentToken = undefined;
33+
}
34+
consume(); // Read the initial token.
35+
36+
function calcNumber() {
37+
// https://drafts.csswg.org/css-values-3/#number-value
38+
var result = Number(currentToken);
39+
consume();
40+
return result;
41+
}
42+
43+
function calcValue() {
44+
// <calc-value> = <number> | <dimension> | <percentage> | ( <calc-sum> )
45+
if (currentToken !== '(')
46+
return calcNumber();
47+
consume();
48+
var result = calcSum();
49+
if (currentToken !== ')')
50+
return NaN;
51+
consume();
52+
return result;
53+
}
54+
55+
function calcProduct() {
56+
// <calc-product> = <calc-value> [ '*' <calc-value> | '/' <calc-number-value> ]*
57+
var left = calcValue();
58+
while (currentToken === '*' || currentToken === '/') {
59+
var operator = currentToken;
60+
consume();
61+
var right = calcValue();
62+
if (operator === '*')
63+
left *= right;
64+
else
65+
left /= right;
66+
}
67+
return left;
68+
}
69+
70+
function calcSum() {
71+
// <calc-sum> = <calc-product> [ [ '+' | '-' ] <calc-product> ]*
72+
var left = calcProduct();
73+
while (currentToken === '+' || currentToken === '-') {
74+
var operator = currentToken;
75+
consume();
76+
var right = calcProduct();
77+
if (operator === '+')
78+
left += right;
79+
else
80+
left -= right;
81+
}
82+
return left;
83+
}
84+
85+
// <calc()> = calc( <calc-sum> )
86+
return calcSum();
87+
}
88+
1789
function parseDimension(unitRegExp, string) {
1890
string = string.trim().toLowerCase();
1991

@@ -36,7 +108,7 @@
36108
var taggedUnitRegExp = 'U(' + unitRegExp.source + ')';
37109

38110
// Validating input is simply applying as many reductions as we can.
39-
var typeCheck = string.replace(/[-+]?(\d*\.)?\d+/g, 'N')
111+
var typeCheck = string.replace(/[-+]?(\d*\.)?\d+([Ee][-+]?\d+)?/g, 'N')
40112
.replace(new RegExp('N' + taggedUnitRegExp, 'g'), 'D')
41113
.replace(/\s[+-]\s/g, 'O')
42114
.replace(/\s/g, '');
@@ -54,7 +126,7 @@
54126
return;
55127

56128
for (var unit in matchedUnits) {
57-
var result = eval(string.replace(new RegExp('U' + unit, 'g'), '').replace(new RegExp(taggedUnitRegExp, 'g'), '*0'));
129+
var result = calculate(string.replace(new RegExp('U' + unit, 'g'), '').replace(new RegExp(taggedUnitRegExp, 'g'), '*0'));
58130
if (!isFinite(result))
59131
return;
60132
matchedUnits[unit] = result;

test/js/dimension-handler.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ suite('dimension-handler', function() {
1818
{px: 50.0, em: -6.5});
1919
assert.deepEqual(webAnimations1.parseLength('calc((5px + 2px)*(1 + 2*(4 + 2*-5)) + 7px - (5em + 6vw/2)*4)'),
2020
{px: -70, em: -20, vw: -12});
21+
assert.deepEqual(webAnimations1.parseLength('calc(-13.2E+1rem/+12e-1/(+1 + +2 - -2 * 2 - -3))'),
22+
{rem: -11});
2123
assert.deepEqual(webAnimations1.parseLength('calc(calc(5px) + calc(((3))) *calc(calc(10px)))'),
2224
{px: 35});
2325
});

0 commit comments

Comments
 (0)