Skip to content

Commit

Permalink
Merge pull request #151 from ewilligers/process-calc-expression
Browse files Browse the repository at this point in the history
Parse and evaluate calc expressions
  • Loading branch information
darrnshn authored Jul 20, 2017
2 parents ec226e6 + a081deb commit decd4e5
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 2 deletions.
76 changes: 74 additions & 2 deletions src/dimension-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,78 @@

(function(scope, testing) {

// Evaluates a calc expression.
// https://drafts.csswg.org/css-values-3/#calc-notation
function calculate(expression) {
// In calc expressions, white space is required on both sides of the
// + and - operators. https://drafts.csswg.org/css-values-3/#calc-notation
// Thus any + or - immediately adjacent to . or 0..9 is part of the number,
// e.g. -1.23e+45
// This regular expression matches ( ) * / + - and numbers.
var tokenRegularExpression = /([\+\-\w\.]+|[\(\)\*\/])/g;
var currentToken;
function consume() {
var matchResult = tokenRegularExpression.exec(expression);
if (matchResult)
currentToken = matchResult[0];
else
currentToken = undefined;
}
consume(); // Read the initial token.

function calcNumber() {
// https://drafts.csswg.org/css-values-3/#number-value
var result = Number(currentToken);
consume();
return result;
}

function calcValue() {
// <calc-value> = <number> | <dimension> | <percentage> | ( <calc-sum> )
if (currentToken !== '(')
return calcNumber();
consume();
var result = calcSum();
if (currentToken !== ')')
return NaN;
consume();
return result;
}

function calcProduct() {
// <calc-product> = <calc-value> [ '*' <calc-value> | '/' <calc-number-value> ]*
var left = calcValue();
while (currentToken === '*' || currentToken === '/') {
var operator = currentToken;
consume();
var right = calcValue();
if (operator === '*')
left *= right;
else
left /= right;
}
return left;
}

function calcSum() {
// <calc-sum> = <calc-product> [ [ '+' | '-' ] <calc-product> ]*
var left = calcProduct();
while (currentToken === '+' || currentToken === '-') {
var operator = currentToken;
consume();
var right = calcProduct();
if (operator === '+')
left += right;
else
left -= right;
}
return left;
}

// <calc()> = calc( <calc-sum> )
return calcSum();
}

function parseDimension(unitRegExp, string) {
string = string.trim().toLowerCase();

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

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

for (var unit in matchedUnits) {
var result = eval(string.replace(new RegExp('U' + unit, 'g'), '').replace(new RegExp(taggedUnitRegExp, 'g'), '*0'));
var result = calculate(string.replace(new RegExp('U' + unit, 'g'), '').replace(new RegExp(taggedUnitRegExp, 'g'), '*0'));
if (!isFinite(result))
return;
matchedUnits[unit] = result;
Expand Down
2 changes: 2 additions & 0 deletions test/js/dimension-handler.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ suite('dimension-handler', function() {
{px: 50.0, em: -6.5});
assert.deepEqual(webAnimations1.parseLength('calc((5px + 2px)*(1 + 2*(4 + 2*-5)) + 7px - (5em + 6vw/2)*4)'),
{px: -70, em: -20, vw: -12});
assert.deepEqual(webAnimations1.parseLength('calc(-13.2E+1rem/+12e-1/(+1 + +2 - -2 * 2 - -3))'),
{rem: -11});
assert.deepEqual(webAnimations1.parseLength('calc(calc(5px) + calc(((3))) *calc(calc(10px)))'),
{px: 35});
});
Expand Down

0 comments on commit decd4e5

Please sign in to comment.