reducer.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. var _convertUnit = _interopRequireDefault(require("./convertUnit"));
  7. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  8. function isValueType(type) {
  9. switch (type) {
  10. case 'LengthValue':
  11. case 'AngleValue':
  12. case 'TimeValue':
  13. case 'FrequencyValue':
  14. case 'ResolutionValue':
  15. case 'EmValue':
  16. case 'ExValue':
  17. case 'ChValue':
  18. case 'RemValue':
  19. case 'VhValue':
  20. case 'VwValue':
  21. case 'VminValue':
  22. case 'VmaxValue':
  23. case 'PercentageValue':
  24. case 'Number':
  25. return true;
  26. }
  27. return false;
  28. }
  29. function flip(operator) {
  30. return operator === '+' ? '-' : '+';
  31. }
  32. function isAddSubOperator(operator) {
  33. return operator === '+' || operator === '-';
  34. }
  35. function collectAddSubItems(preOperator, node, collected, precision) {
  36. if (!isAddSubOperator(preOperator)) {
  37. throw new Error(`invalid operator ${preOperator}`);
  38. }
  39. var type = node.type;
  40. if (isValueType(type)) {
  41. var itemIndex = collected.findIndex(function (x) {
  42. return x.node.type === type;
  43. });
  44. if (itemIndex >= 0) {
  45. if (node.value === 0) {
  46. return;
  47. }
  48. var _covertNodesUnits = covertNodesUnits(collected[itemIndex].node, node, precision),
  49. reducedNode = _covertNodesUnits.left,
  50. current = _covertNodesUnits.right;
  51. if (collected[itemIndex].preOperator === '-') {
  52. collected[itemIndex].preOperator = '+';
  53. reducedNode.value *= -1;
  54. }
  55. if (preOperator === "+") {
  56. reducedNode.value += current.value;
  57. } else {
  58. reducedNode.value -= current.value;
  59. } // make sure reducedNode.value >= 0
  60. if (reducedNode.value >= 0) {
  61. collected[itemIndex] = {
  62. node: reducedNode,
  63. preOperator: '+'
  64. };
  65. } else {
  66. reducedNode.value *= -1;
  67. collected[itemIndex] = {
  68. node: reducedNode,
  69. preOperator: '-'
  70. };
  71. }
  72. } else {
  73. // make sure node.value >= 0
  74. if (node.value >= 0) {
  75. collected.push({
  76. node,
  77. preOperator
  78. });
  79. } else {
  80. node.value *= -1;
  81. collected.push({
  82. node,
  83. preOperator: flip(preOperator)
  84. });
  85. }
  86. }
  87. } else if (type === "MathExpression") {
  88. if (isAddSubOperator(node.operator)) {
  89. collectAddSubItems(preOperator, node.left, collected, precision);
  90. var collectRightOperator = preOperator === '-' ? flip(node.operator) : node.operator;
  91. collectAddSubItems(collectRightOperator, node.right, collected, precision);
  92. } else {
  93. // * or /
  94. var _reducedNode = reduce(node, precision); // prevent infinite recursive call
  95. if (_reducedNode.type !== "MathExpression" || isAddSubOperator(_reducedNode.operator)) {
  96. collectAddSubItems(preOperator, _reducedNode, collected, precision);
  97. } else {
  98. collected.push({
  99. node: _reducedNode,
  100. preOperator
  101. });
  102. }
  103. }
  104. } else {
  105. collected.push({
  106. node,
  107. preOperator
  108. });
  109. }
  110. }
  111. function reduceAddSubExpression(node, precision) {
  112. var collected = [];
  113. collectAddSubItems('+', node, collected, precision);
  114. var withoutZeroItem = collected.filter(function (item) {
  115. return !(isValueType(item.node.type) && item.node.value === 0);
  116. });
  117. var firstNonZeroItem = withoutZeroItem[0]; // could be undefined
  118. // prevent producing "calc(-var(--a))" or "calc()"
  119. // which is invalid css
  120. if (!firstNonZeroItem || firstNonZeroItem.preOperator === '-' && !isValueType(firstNonZeroItem.node.type)) {
  121. var firstZeroItem = collected.find(function (item) {
  122. return isValueType(item.node.type) && item.node.value === 0;
  123. });
  124. withoutZeroItem.unshift(firstZeroItem);
  125. } // make sure the preOperator of the first item is +
  126. if (withoutZeroItem[0].preOperator === '-' && isValueType(withoutZeroItem[0].node.type)) {
  127. withoutZeroItem[0].node.value *= -1;
  128. withoutZeroItem[0].preOperator = '+';
  129. }
  130. var root = withoutZeroItem[0].node;
  131. for (var i = 1; i < withoutZeroItem.length; i++) {
  132. root = {
  133. type: 'MathExpression',
  134. operator: withoutZeroItem[i].preOperator,
  135. left: root,
  136. right: withoutZeroItem[i].node
  137. };
  138. }
  139. return root;
  140. }
  141. function reduceDivisionExpression(node) {
  142. if (!isValueType(node.right.type)) {
  143. return node;
  144. }
  145. if (node.right.type !== 'Number') {
  146. throw new Error(`Cannot divide by "${node.right.unit}", number expected`);
  147. }
  148. return applyNumberDivision(node.left, node.right.value);
  149. } // apply (expr) / number
  150. function applyNumberDivision(node, divisor) {
  151. if (divisor === 0) {
  152. throw new Error('Cannot divide by zero');
  153. }
  154. if (isValueType(node.type)) {
  155. node.value /= divisor;
  156. return node;
  157. }
  158. if (node.type === "MathExpression" && isAddSubOperator(node.operator)) {
  159. // turn (a + b) / num into a/num + b/num
  160. // is good for further reduction
  161. // checkout the test case
  162. // "should reduce division before reducing additions"
  163. return {
  164. type: "MathExpression",
  165. operator: node.operator,
  166. left: applyNumberDivision(node.left, divisor),
  167. right: applyNumberDivision(node.right, divisor)
  168. };
  169. } // it is impossible to reduce it into a single value
  170. // .e.g the node contains css variable
  171. // so we just preserve the division and let browser do it
  172. return {
  173. type: "MathExpression",
  174. operator: '/',
  175. left: node,
  176. right: {
  177. type: "Number",
  178. value: divisor
  179. }
  180. };
  181. }
  182. function reduceMultiplicationExpression(node) {
  183. // (expr) * number
  184. if (node.right.type === 'Number') {
  185. return applyNumberMultiplication(node.left, node.right.value);
  186. } // number * (expr)
  187. if (node.left.type === 'Number') {
  188. return applyNumberMultiplication(node.right, node.left.value);
  189. }
  190. return node;
  191. } // apply (expr) / number
  192. function applyNumberMultiplication(node, multiplier) {
  193. if (isValueType(node.type)) {
  194. node.value *= multiplier;
  195. return node;
  196. }
  197. if (node.type === "MathExpression" && isAddSubOperator(node.operator)) {
  198. // turn (a + b) * num into a*num + b*num
  199. // is good for further reduction
  200. // checkout the test case
  201. // "should reduce multiplication before reducing additions"
  202. return {
  203. type: "MathExpression",
  204. operator: node.operator,
  205. left: applyNumberMultiplication(node.left, multiplier),
  206. right: applyNumberMultiplication(node.right, multiplier)
  207. };
  208. } // it is impossible to reduce it into a single value
  209. // .e.g the node contains css variable
  210. // so we just preserve the division and let browser do it
  211. return {
  212. type: "MathExpression",
  213. operator: '*',
  214. left: node,
  215. right: {
  216. type: "Number",
  217. value: multiplier
  218. }
  219. };
  220. }
  221. function covertNodesUnits(left, right, precision) {
  222. switch (left.type) {
  223. case 'LengthValue':
  224. case 'AngleValue':
  225. case 'TimeValue':
  226. case 'FrequencyValue':
  227. case 'ResolutionValue':
  228. if (right.type === left.type && right.unit && left.unit) {
  229. var converted = (0, _convertUnit.default)(right.value, right.unit, left.unit, precision);
  230. right = {
  231. type: left.type,
  232. value: converted,
  233. unit: left.unit
  234. };
  235. }
  236. return {
  237. left,
  238. right
  239. };
  240. default:
  241. return {
  242. left,
  243. right
  244. };
  245. }
  246. }
  247. function reduce(node, precision) {
  248. if (node.type === "MathExpression") {
  249. if (isAddSubOperator(node.operator)) {
  250. // reduceAddSubExpression will call reduce recursively
  251. return reduceAddSubExpression(node, precision);
  252. }
  253. node.left = reduce(node.left, precision);
  254. node.right = reduce(node.right, precision);
  255. switch (node.operator) {
  256. case "/":
  257. return reduceDivisionExpression(node, precision);
  258. case "*":
  259. return reduceMultiplicationExpression(node, precision);
  260. }
  261. return node;
  262. }
  263. return node;
  264. }
  265. var _default = reduce;
  266. exports.default = _default;
  267. module.exports = exports.default;