"use strict";
const Postfix = {};
Postfix.eval = function(stack, operations, parseLiteral) {
if (stack.peek() === null) throw Error("Empty expression stack.");
const ops = operations, token = stack.pop();
if (!ops.contains(token)) return parseLiteral(token);
const operation = ops.get(token), arity = operation.arity();
switch (arity) {
case 0: return operation.apply();
case 1:
const arg = Postfix.eval(stack, ops, parseLiteral);
return operation.apply(arg);
case 2:
const arg2 = Postfix.eval(stack, ops, parseLiteral);
const arg1 = Postfix.eval(stack, ops, parseLiteral);
return operation.apply(arg1, arg2);
default: throw Error("Invalid operation arity: " + airity);
}
}
Postfix.parseStack = function(tokens, ops, lower, upper) {
if (lower === undefined) lower = 0;
if (upper === undefined) upper = tokens.length;
if (upper - lower === 1) return new Stack([tokens[lower]]);
for (let p = ops.minPrecedence(); p <= ops.maxPrecedence(); p++) {
let j = -1;
for (let i = upper - 1; i >= lower; i--) {
const token = tokens[i];
if (!ops.contains(token)) continue; // token not an operator
const operation = ops.get(token);
console.assert(token === operation.operator());
if (operation.precedence() !== p) continue;
if (operation.isPostfix()) {
const lhs = Postfix.parseStack(tokens, ops, lower, i);
return lhs.push(operation.operator());
}
else if (operation.isInfix()) {
const lhs = Postfix.parseStack(tokens, ops, lower, i);
const rhs = Postfix.parseStack(tokens, ops, i + 1, upper);
return lhs.pushStack(rhs).push(operation.operator());
}
else if (operation.isPrefix()) j = i;
}
if (j >= 0) {
const token = tokens[j], operation = ops.get(token);
const rhs = Postfix.parseStack(tokens, ops, j + 1, upper);
return rhs.push(operation.operator());
}
}
};
Object.freeze(Postfix);
const Expr = {};
Expr.parseTokens = function(input) {
if (Array.isArray(input)) return toStrings(input);
else if (typeof input === "string") return toStrings(input.split(" "));
else if (Number.isFinite(input)) return toStrings([input]);
else if (input === true || input === false) return toStrings([input]);
throw Error("Invalid expression");
function toStrings(array) { // Returns a frozen array of strings.
const strings = [];
array.forEach((element) => strings.push(element.toString()));
return Object.freeze(strings);
}
};
Expr.random = function(n, ops, randomTerm) {
const opBagSize = // The number of operators will either be
Number.isInteger(n) && n > 0 ? n : // the value of n or
Expr.random.DEFAULT_OPERATOR_COUNT; // the default value.
// PART I. COLLECT TOKENS
let opBag = [], // Start with zero operations
termBag = [randomTerm()]; // and one term.
while (opBag.length < opBagSize) { // Repeat until the bag of operations is full:
const op = ops.random(); // 1. Get the next random operation.
const lhsIsArg = op.lhsIsArg(); // Note whether an operand is needed on the left.
const rhsIsArg = op.rhsIsArg(); // Note whether an operand is needed on the right.
if (lhsIsArg && rhsIsArg) { // 2. If operands are needed on both sides,
termBag.unshift(randomTerm()); // then add another term to the bag of terms,
opBag.push(op); // and store the operation in the bag.
} // (Put term literals at the bottom of the bag.)
else if (!(lhsIsArg || rhsIsArg)) // 3. Otherwise, if no operands are required,
termBag.push(op.operator()); // then treat the operator as a term.
else // 4. Otherwise, exactly one operand is needed,
opBag.push(op); // so we only store the operation.
}
// PART II. SEQUENCE TOKENS
let tokens = [], isComplete = false; // Initially, the expression is empty; it's incomplete.
while (opBag.length > 0) { // Repeat until the bag of operation objects is empty:
const op = opBag.pop(); // 1. Take an object out of the bag of operations.
const lhsIsArg = op.lhsIsArg(); // Note whether an operand is needed on the left.
const rhsIsArg = op.rhsIsArg(); // Note whether an operand is needed on the right.
if (lhsIsArg) { // 2. If an operand is needed on the left,
if (!isComplete) // and if the expression is not complete,
tokens.push(termBag.pop()); // then we append a term to complete it.
tokens.push(op.operator()); // Next, append the operator token.
isComplete = !rhsIsArg; // The expression is complete if and only if a
} // RHS operand is not required.
else if (rhsIsArg) { // 3. But if we need a RHS (not a LHS) operand,
if (!isComplete) // and if the expression is not complete,
tokens.push(op.operator()); // then put the operator AT THE END;
else // however, if the expression is complete,
tokens.unshift(op.operator()); // then put the operator AT THE BEGINNING.
}
} // PART III. TERMINATE AND RETURN
if (!isComplete) // If necessary,
tokens.push(termBag.pop()); // add a term to terminate the series.
return tokens.join(" "); // Return tokens representing a valid expression.
};
Expr.random.DEFAULT_OPERATOR_COUNT = 8;
Object.freeze(Expr);
function GenericExpr(e, ops, parseLiteral) {
const tokens = Expr.parseTokens(e);
const postfixStack = Postfix.parseStack(tokens, ops);
const postfixString = postfixStack.toString();
const value = Postfix.eval(postfixStack, ops, parseLiteral);
this.valueOf = () => value;
this.toArray = () => tokens;
this.toString = (opt) => opt === "postfix" ? postfixString : e;
Object.freeze(this);
}
Object.freeze(GenericExpr);
function BooleanExpr(e) { GenericExpr.call(this, e, BooleanExpr.ops, BooleanExpr.parseLiteral);
Object.freeze(this);
}
BooleanExpr.parseLiteral = (term) => term.toString();
BooleanExpr.random = (n) => Expr.random(n, BooleanExpr.ops, BooleanExpr.randomTerm);
BooleanExpr.randomTerm = () => Math.random() < 0.5 ? "true" : "false";
BooleanExpr.ops = new Operations([
new Operation("&&", 0, true, true, (a, b) => MyMath.logical.and(a, b)),
new Operation("||", 0, true, true, (a, b) => MyMath.logical.or(a, b)),
new Operation("!", 1, false, true, (b) => MyMath.logical.not(b))
]);
Object.freeze(BooleanExpr);
function NumericExpr(e) { GenericExpr.call(this, e, NumericExpr.ops, NumericExpr.parseLiteral);
Object.freeze(this);
}
NumericExpr.parseLiteral = parseFloat;
NumericExpr.MIN_TERM = -10;
NumericExpr.MAX_TERM = 10;
NumericExpr.random = (n) => Expr.random(n, NumericExpr.ops, NumericExpr.randomTerm);
NumericExpr.randomTerm = () => Math.floor(NumericExpr.MIN_TERM + Math.random() *
NumericExpr.MAX_TERM - NumericExpr.MIN_TERM);
NumericExpr.ops = new Operations([
new Operation("+", 0, true, true, (a, b) => MyMath.add(a, b)),
new Operation("-", 0, true, true, (a, b) => MyMath.sub(a, b)),
new Operation("*", 1, true, true, (a, b) => MyMath.mul(a, b)),
new Operation("/", 1, true, true, (a, b) => MyMath.div(a, b)),
new Operation("%", 1, true, true, (a, b) => MyMath.rem(a, b)),
new Operation("^", 2, true, true, (a, b) => MyMath.pow(a, b)),
new Operation("!", 3, true, false, (a) => MyMath.factorial(a)),
new Operation("neg", 3, false, true, (b) => MyMath.neg(b)),
new Operation("sin", 3, false, true, (b) => MyMath.sin(b)),
new Operation("cos", 3, false, true, (b) => MyMath.cos(b)),
new Operation("PI", 4, false, false, () => MyMath.pi())
]);
Object.freeze(NumericExpr);
const MyMath = Object.freeze({
pi: function() { return Math.PI; },
neg: function(b) { return -b; },
sin: function(b) { return Math.sin(b); },
cos: function(b) { return Math.cos(b); },
factorial: function(a) { return 1; }, // to do
pow: function(a, b) { return Math.pow(a, b); },
mul: function(a, b) { return a * b; },
div: function(a, b) { return a / b; },
rem: function(a, b) { return a % b; },
add: function(a, b) { return a + b; },
sub: function(a, b) { return a - b; },
logical: {
and: function(a, b) { return a && b; },
or: function(a, b) { return a || b; },
not: function(b) { return !b; }
}
});
function Operation(operator, precedence, lhsIsArg, rhsIsArg, f) {
this.operator = () => operator;
this.precedence = () => precedence;
this.apply = f;
this.lhsIsArg = () => lhsIsArg;
this.rhsIsArg = () => rhsIsArg;
this.isInfix = () => lhsIsArg && rhsIsArg;
this.isPostfix = () => lhsIsArg && !rhsIsArg;
this.isPrefix = () => !lhsIsArg && rhsIsArg;
this.arity = () => (lhsIsArg ? 1 : 0) + (rhsIsArg ? 1 : 0);
Object.freeze(this);
}
Object.freeze(Operation)
function Operations(ops) {
const minPrec = (function() {
let min = undefined;
ops.forEach(function(op) {
const p = op.precedence();
if (min == undefined || p < min) min = p;
});
return min;
})();
const maxPrec = (function() {
let max = undefined;
ops.forEach(function(op) {
const p = op.precedence();
if (max == undefined || p > max) max = p;
});
return max;
})();
const operatorMap = (function() {
const map = Object.create(null);
ops.forEach(function(op) { map[op.operator()] = op; });
return map;
})();
this.contains = (operator) => operatorMap[operator] !== undefined;
this.get = (operator) => operatorMap[operator];
this.minPrecedence = () => minPrec;
this.maxPrecedence = () => maxPrec;
this.random = () => ops[Math.floor(Math.random() * ops.length)];
Object.freeze(this);
}
Object.freeze(Operations);
function Stack(elements) {
const thisStack = this;
this.pushStack = function (otherStack) {
elements = elements.concat(otherStack.toString().split(" "));
return thisStack;
};
this.push = function (element) {
elements.push(element);
return thisStack;
};
this.pop = () => elements.pop();
this.toString = () => elements.join(" ");
this.size = () => elements.length;
this.peek = () => elements.length > 0 ? elements[elements.length - 1] : null;
}
Object.freeze(Stack);