Secure your code as it's written. Use Snyk Code to scan source code in minutes - no build needed - and fix issues immediately.
function match(expr, target) {
// Both expression and target are a number.
if (isNumber(target)) {
return (expr === target) ? [{}] : [];
}
if (isString(target)) {
// Matches any constants (numbers)
if (target[0] == 'c') {
return isNumber(expr) ? [{[target]: expr}] : [];
}
// Matches any variables (strings)
if (target[0] == 'v') {
return isString(expr) ? [{[target]: expr}] : [];
}
// Match any other expressions
return [{[target]: expr}];
}
// Check if functions are the same
if (!isArray(expr)) return [];
let [fn, ...args] = expr;
if (fn != target[0]) return [];
if (expr.length != target.length) return [];
function rewrite(expr, rule) {
if (isString(expr) || isNumber(expr)) return expr;
let [fn, ...args] = expr;
args = args.map(a => rewrite(a, rule));
// For addition and multiplications we need to match subsets of arguments, e.g.
// `a + b + c` should match the rule `a + b`.
let [ruleFn, ...ruleArgs] = rule[0];
if ('+*'.includes(fn) && '+*'.includes(ruleFn) && args.length > ruleArgs.length) {
for (let p of subsets(list(args.length), ruleArgs.length)) {
let argsSubset = p.map(i => args[i]); // The subset of args that match the rule.
let argsOthers = args.filter((x, i) => !p.includes(i)); // All other args.
let placeholders = match([fn, ...argsSubset], rule[0])[0];
if (placeholders) {
let argsReplaced = substitute(rule[1], placeholders);
return flattenAssociative([fn, argsReplaced, ...argsOthers]);
function flattenAssociative(expr) {
if (isString(expr) || isNumber(expr)) return expr;
let [fn, ...args] = expr;
args = args.map(a => flattenAssociative(a));
if (!'+*'.includes(fn)) return [fn, ...args];
let newArgs = [];
for (let a of args) {
if (isArray(a) && a[0] == fn) {
newArgs.push(...a.slice(1))
} else {
newArgs.push(a);
}
}
return [fn, ...newArgs];
}
let variables = args.filter(a => !isNumber(a));
return [fn, constant,...variables]
function evaluate(expr, vars={}) {
if (isNumber(expr)) return expr;
if (isString(expr)) {
if (expr in vars) return vars[expr];
if (expr in CONSTANTS) return CONSTANTS[expr];
return expr;
}
let [fn, ...args] = expr;
args = args.map(a => evaluate(a, vars));
if (args.every(a => isNumber(a))) {
if (fn in vars) return vars[fn](...args);
if (fn in FN_EVALUATE) return FN_EVALUATE[fn](...args);
if (fn in Math) return Math[fn](...args);
} else if ('+*'.includes(fn)) {
let constant = FN_EVALUATE[fn](...args.filter(a => isNumber(a)));
function variables(tree) {
if (isNumber(tree)) return [];
if (isString(tree)) return [tree];
let vars = [];
for (let a of tree.slice(1)) {
for (let v of variables(a)) {
if (!vars.includes(v)) vars.push(v);
}
}
return vars;
}
function substitute(expr, vars) {
if (isNumber(expr)) return expr;
if (isString(expr)) return (expr in vars) ? vars[expr] : expr;
let [fn, ...args] = expr;
args = args.map(a => substitute(a, vars));
return [fn, ...args];
}