@firehammer/jexl
Version:
Javascript Expression Language: Powerful context-based expression parser and evaluator
101 lines (100 loc) • 5.24 kB
JavaScript
;
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
function escapeKeyOfExpressionIdentifier(identifier) {
for (var _len = arguments.length, keys = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
keys[_key - 1] = arguments[_key];
}
if (keys.length === 0) {
return identifier;
}
var key = keys[0];
return escapeKeyOfExpressionIdentifier.apply(void 0, [key.match(/^[A-Za-z_]\w*$/) ? "".concat(identifier, ".").concat(key) : "".concat(identifier, "[\"").concat(key.replace(/"/g, '\\"'), "\"]")].concat((0, _toConsumableArray2.default)(keys.slice(1))));
}
function stringify(grammar, ast) {
var ancestors = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
if (!ast) {
return "";
}
var recur = function recur(childAst) {
return stringify(grammar, childAst, [].concat((0, _toConsumableArray2.default)(ancestors), [ast]));
};
var recurWithBracketsIfRequired = function recurWithBracketsIfRequired(parentElement, childAst) {
var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
_ref$isRightHandSide = _ref.isRightHandSide,
isRightHandSide = _ref$isRightHandSide === void 0 ? false : _ref$isRightHandSide;
var parentPrecedence = parentElement && parentElement.type === "binaryOp" ? parentElement.precedence : 0;
var childBinaryExpressionElement = childAst.type === "BinaryExpression" ? grammar.elements[childAst.operator] : null;
var childPrecedence = childAst.type === "ConditionalExpression" ? 0 : childBinaryExpressionElement && childBinaryExpressionElement.type === "binaryOp" ? childBinaryExpressionElement.precedence : Infinity;
var rhsOfBinaryOpInTernaryConditionWorkaround = isRightHandSide && childAst.type === "FunctionCall" && childAst.pool === "transforms" && parentElement && parentElement.type === "binaryOp" && ancestors[ancestors.length - 1] && ancestors[ancestors.length - 1].type === "ConditionalExpression";
var childExpressionString = recur(childAst);
if (isRightHandSide ? parentPrecedence >= childPrecedence || rhsOfBinaryOpInTernaryConditionWorkaround : parentPrecedence > childPrecedence) {
return "(".concat(childExpressionString, ")");
}
return childExpressionString;
};
switch (ast.type) {
case "Literal":
if (typeof ast.value === "number" && ast.value.toString().includes("e")) {
var prefix = ast.value < 0 ? "-" : "";
return prefix + Math.abs(ast.value).toLocaleString("en-US", {
useGrouping: false
});
}
return JSON.stringify(ast.value);
case "Identifier":
// TODO: if identifierAst can generate FilterExpressions when required then can we ditch `escapeKeyOfExpressionIdentifier`?
if (ast.from) {
return "".concat(recur(ast.from)).concat(escapeKeyOfExpressionIdentifier("", ast.value));
} else {
return escapeKeyOfExpressionIdentifier(ast.value);
}
case "UnaryExpression":
{
var right = recur(ast.right);
if (ast.right.type === "BinaryExpression" || ast.right.type === "ConditionalExpression") {
right = "(".concat(right, ")");
}
return "".concat(ast.operator).concat(right);
}
case "BinaryExpression":
{
var left = recurWithBracketsIfRequired(grammar.elements[ast.operator], ast.left);
var _right = recurWithBracketsIfRequired(grammar.elements[ast.operator], ast.right, {
isRightHandSide: true
});
return "".concat(left, " ").concat(ast.operator, " ").concat(_right);
}
case "ConditionalExpression":
{
var test = recurWithBracketsIfRequired(null, ast.test);
var consequent = recurWithBracketsIfRequired(null, ast.consequent);
var alternate = recurWithBracketsIfRequired(null, ast.alternate);
return "".concat(test, " ? ").concat(consequent, " : ").concat(alternate);
}
case "ArrayLiteral":
return "[".concat(ast.value.map(recur).join(", "), "]");
case "ObjectLiteral":
return "{ ".concat(Object.entries(ast.value).map(function (_ref2) {
var _ref3 = (0, _slicedToArray2.default)(_ref2, 2),
key = _ref3[0],
value = _ref3[1];
return "\"".concat(key, "\": ").concat(recur(value));
}).join(", "), " }");
case "FilterExpression":
return "".concat(recur(ast.subject), "[").concat(ast.relative ? "." : "").concat(recur(ast.expr), "]");
case "FunctionCall":
switch (ast.pool) {
case "functions":
return "".concat(ast.name, "(").concat(ast.args.map(recur).join(", "), ")");
case "transforms":
// Note that transforms always have at least one argument
// i.e. `a | b` is `b` with one argument of `a`
return "".concat(recur(ast.args[0]), " | ").concat(ast.name).concat(ast.args.length > 1 ? "(".concat(ast.args.slice(1).map(recur).join(", "), ")") : "");
}
}
}
module.exports = {
stringify: stringify
};