Skip to content

Commit decd4e5

Browse files
authored
Merge pull request #151 from ewilligers/process-calc-expression
Parse and evaluate calc expressions
2 parents ec226e6 + a081deb commit decd4e5

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)