math.js 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. "use strict";
  2. exports.__esModule = true;
  3. exports["default"] = math;
  4. var _defaultSymbols = /*#__PURE__*/_interopRequireDefault( /*#__PURE__*/require("./presets/defaultSymbols"));
  5. var _errors = /*#__PURE__*/_interopRequireDefault( /*#__PURE__*/require("../internalHelpers/_errors"));
  6. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; }
  7. function _extends() { _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; return _extends.apply(this, arguments); }
  8. var unitRegExp = /((?!\w)a|na|hc|mc|dg|me[r]?|xe|ni(?![a-zA-Z])|mm|cp|tp|xp|q(?!s)|hv|xamv|nimv|wv|sm|s(?!\D|$)|ged|darg?|nrut)/g; // Merges additional math functionality into the defaults.
  9. function mergeSymbolMaps(additionalSymbols) {
  10. var symbolMap = {};
  11. symbolMap.symbols = additionalSymbols ? _extends({}, _defaultSymbols["default"].symbols, additionalSymbols.symbols) : _extends({}, _defaultSymbols["default"].symbols);
  12. return symbolMap;
  13. }
  14. function exec(operators, values) {
  15. var _ref;
  16. var op = operators.pop();
  17. values.push(op.f.apply(op, (_ref = []).concat.apply(_ref, values.splice(-op.argCount))));
  18. return op.precedence;
  19. }
  20. function calculate(expression, additionalSymbols) {
  21. var symbolMap = mergeSymbolMaps(additionalSymbols);
  22. var match;
  23. var operators = [symbolMap.symbols['('].prefix];
  24. var values = [];
  25. var pattern = new RegExp( // Pattern for numbers
  26. "\\d+(?:\\.\\d+)?|" + // ...and patterns for individual operators/function names
  27. Object.keys(symbolMap.symbols).map(function (key) {
  28. return symbolMap.symbols[key];
  29. }) // longer symbols should be listed first
  30. // $FlowFixMe
  31. .sort(function (a, b) {
  32. return b.symbol.length - a.symbol.length;
  33. }) // $FlowFixMe
  34. .map(function (val) {
  35. return val.regSymbol;
  36. }).join('|') + "|(\\S)", 'g');
  37. pattern.lastIndex = 0; // Reset regular expression object
  38. var afterValue = false;
  39. do {
  40. match = pattern.exec(expression);
  41. var _ref2 = match || [')', undefined],
  42. token = _ref2[0],
  43. bad = _ref2[1];
  44. var notNumber = symbolMap.symbols[token];
  45. var notNewValue = notNumber && !notNumber.prefix && !notNumber.func;
  46. var notAfterValue = !notNumber || !notNumber.postfix && !notNumber.infix; // Check for syntax errors:
  47. if (bad || (afterValue ? notAfterValue : notNewValue)) {
  48. throw new _errors["default"](37, match ? match.index : expression.length, expression);
  49. }
  50. if (afterValue) {
  51. // We either have an infix or postfix operator (they should be mutually exclusive)
  52. var curr = notNumber.postfix || notNumber.infix;
  53. do {
  54. var prev = operators[operators.length - 1];
  55. if ((curr.precedence - prev.precedence || prev.rightToLeft) > 0) break; // Apply previous operator, since it has precedence over current one
  56. } while (exec(operators, values)); // Exit loop after executing an opening parenthesis or function
  57. afterValue = curr.notation === 'postfix';
  58. if (curr.symbol !== ')') {
  59. operators.push(curr); // Postfix always has precedence over any operator that follows after it
  60. if (afterValue) exec(operators, values);
  61. }
  62. } else if (notNumber) {
  63. // prefix operator or function
  64. operators.push(notNumber.prefix || notNumber.func);
  65. if (notNumber.func) {
  66. // Require an opening parenthesis
  67. match = pattern.exec(expression);
  68. if (!match || match[0] !== '(') {
  69. throw new _errors["default"](38, match ? match.index : expression.length, expression);
  70. }
  71. }
  72. } else {
  73. // number
  74. values.push(+token);
  75. afterValue = true;
  76. }
  77. } while (match && operators.length);
  78. if (operators.length) {
  79. throw new _errors["default"](39, match ? match.index : expression.length, expression);
  80. } else if (match) {
  81. throw new _errors["default"](40, match ? match.index : expression.length, expression);
  82. } else {
  83. return values.pop();
  84. }
  85. }
  86. function reverseString(str) {
  87. return str.split('').reverse().join('');
  88. }
  89. /**
  90. * Helper for doing math with CSS Units. Accepts a formula as a string. All values in the formula must have the same unit (or be unitless). Supports complex formulas utliziing addition, subtraction, multiplication, division, square root, powers, factorial, min, max, as well as parentheses for order of operation.
  91. *
  92. *In cases where you need to do calculations with mixed units where one unit is a [relative length unit](https://developer.mozilla.org/en-US/docs/Web/CSS/length#Relative_length_units), you will want to use [CSS Calc](https://developer.mozilla.org/en-US/docs/Web/CSS/calc).
  93. *
  94. * *warning* While we've done everything possible to ensure math safely evalutes formulas expressed as strings, you should always use extreme caution when passing `math` user provided values.
  95. * @example
  96. * // Styles as object usage
  97. * const styles = {
  98. * fontSize: math('12rem + 8rem'),
  99. * fontSize: math('(12px + 2px) * 3'),
  100. * fontSize: math('3px^2 + sqrt(4)'),
  101. * }
  102. *
  103. * // styled-components usage
  104. * const div = styled.div`
  105. * fontSize: ${math('12rem + 8rem')};
  106. * fontSize: ${math('(12px + 2px) * 3')};
  107. * fontSize: ${math('3px^2 + sqrt(4)')};
  108. * `
  109. *
  110. * // CSS as JS Output
  111. *
  112. * div: {
  113. * fontSize: '20rem',
  114. * fontSize: '42px',
  115. * fontSize: '11px',
  116. * }
  117. */
  118. function math(formula, additionalSymbols) {
  119. var reversedFormula = reverseString(formula);
  120. var formulaMatch = reversedFormula.match(unitRegExp); // Check that all units are the same
  121. if (formulaMatch && !formulaMatch.every(function (unit) {
  122. return unit === formulaMatch[0];
  123. })) {
  124. throw new _errors["default"](41);
  125. }
  126. var cleanFormula = reverseString(reversedFormula.replace(unitRegExp, ''));
  127. return "" + calculate(cleanFormula, additionalSymbols) + (formulaMatch ? reverseString(formulaMatch[0]) : '');
  128. }
  129. module.exports = exports.default;