const escodegen = require("escodegen");
const espree = require("espree");
const estraverse = require("estraverse");
const { isConstant, newArrayNode, newNode, getValue } = require("./utils");
"use strict";
module.exports = constantFolding;
/**
* A function that does constant folding onto the code it receives
* @param {string} code A string containing the code to do the constant folding on
* @returns {string} Returns the resulting code
*/
function constantFolding(code) {
const t = espree.parse(code, { ecmaVersion: 6, loc: false });
estraverse.traverse(t, {
leave: function (n, p) {
if (
n.type == "BinaryExpression" &&
n.left.type == "Literal" && n.right.type == "Literal"
) {
binaryConstantFolding(n);
}
if (
(n.type === "MemberExpression") &&
(n.object.type === "ArrayExpression") &&
(p.type !== "CallExpression")
) {
let value = eval(arrayConstantFolding(n));
Object.assign(n, newNode(value));
}
if (
(n.type === "CallExpression") &&
(n.callee.object.type === "ArrayExpression") &&
isConstant(n.arguments) &&
isConstant(n.callee.object.elements)
) {
let value = arrayConstantFolding(n.callee, n.arguments);
Array.isArray(value) ?
Object.assign(n, newArrayNode(value)) :
Object.assign(n, newNode(value));
}
}
}
);
return escodegen.generate(t);
}
/**
* Does ConstantFolding on binaryExpression nodes. I.E. turns 2+3 into 5.
* @param {Object} n an AST node representing a binaryExpression.
*/
function binaryConstantFolding(n) {
n.type = "Literal";
n.value = eval(`${n.left.raw} ${n.operator} ${n.right.raw}`);
n.raw = String(n.value);
delete n.left;
delete n.right;
}
/**
* Does constant folding on Array operations.
* @param {Object} code an AST node containing the expression statement
* @param {Array} args arguments used on the function call. I.E. for [1].concat('d', 'e'), args = ['d', 'e']
* @returns {string} the resulting js code to be used by escodegen
*/
function arrayConstantFolding(code, args) {
let result = `[${code.object.elements.map(e => getValue(e))}]`;
result += code.property.type === "Literal" ? `[${getValue(code.property)}]` : `.${getValue(code.property)}`;
result += (args ? `(${args.map(e => getValue(e))})` : "");
return eval(result);
}