nerdamer-ts
Version:
javascript light-weight symbolic math expression evaluator
345 lines • 17.2 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.RPN = void 0;
const Token_1 = require("./Token");
const Settings_1 = require("../Settings");
const Symbol_1 = require("../Types/Symbol");
const Collection_1 = require("./Collection");
const Slice_1 = require("./Slice");
const Vector_1 = require("../Types/Vector");
const Set_1 = require("../Types/Set");
const Errors_1 = require("../Core/Errors");
const Utils_1 = require("../Core/Utils");
const Parser_1 = require("./Parser");
class RPN {
constructor(deps, variables, peekers) {
this.deps = deps;
this.peekers = peekers;
this.variables = variables;
}
/**
* Puts token array in Reverse Polish Notation
* @param {Token[] | Token} tokens
* @returns {Token[]}
*/
static TokensToRPN(tokens) {
const fn = tokens.type;
const l = tokens.length;
const output = [];
const stack = [];
const prefixes = [];
const collapse = function (target, destination) {
while (target.length) {
destination.push(target.pop());
}
};
let i;
//mark all the prefixes and add them to the stack
for (i = 0; i < l; i++) {
let token = tokens[i];
if (token.type !== Token_1.Token.OPERATOR)
break;
if (!token.prefix)
throw new Errors_1.OperatorError('Not a prefix operator');
token.is_prefix = true;
stack.push(token);
}
//begin with remaining tokens
for (; i < l; i++) {
let e = tokens[i];
if (e.type === Token_1.Token.OPERATOR) {
let operator = e;
//create the option for the operator being overloaded
if (operator.overloaded) {
let next = tokens[i + 1];
//if it's followed by a number or variable then we assume it's not a postfix operator
if (next && next.type === Token_1.Token.VARIABLE_OR_LITERAL) {
operator.postfix = false;
//override the original function with the overload function
operator.action = operator.overloadAction;
operator.leftAssoc = operator.overloadLeftAssoc;
}
}
//if the stack is not empty
while (stack.length) {
let last = stack[stack.length - 1];
//if (there is an operator at the top of the operator stack with greater precedence)
//or (the operator at the top of the operator stack has equal precedence and is left associative)) ~ wikipedia
//the !prefixes.length makes sure that the operator on stack isn't prematurely taken from the stack.
if (!(last.precedence > operator.precedence || !operator.leftAssoc && last.precedence === operator.precedence))
break;
output.push(stack.pop());
}
//change the behavior of the operator if it's a vector and we've been asked to do so
if ((fn === 'vector' || fn === 'set') && 'vectorFn' in operator)
operator.action = operator.vectorFn;
//if the operator is a postfix operator then we're ready to go since it belongs
//to the preceding token. However the output cannot be empty. It must have either
//an operator or a variable/literal
if (operator.postfix) {
let previous = tokens[i - 1];
if (!previous)
throw new Errors_1.OperatorError("Unexpected prefix operator '" + e.value + "'! at " + e.column);
else if (previous.type === Token_1.Token.OPERATOR) {
//a postfix can only be followed by a postfix
if (!previous.postfix)
throw new Errors_1.OperatorError("Unexpected prefix operator '" + previous.value + "'! at " + previous.column);
}
}
else {
let next_is_operator;
//we must be at an infix so point the operator this
do {
//the first one is an infix operator all others have to be prefix operators so jump to the end
let next = tokens[i + 1]; //take a look ahead
next_is_operator = next ? next.type === Token_1.Token.OPERATOR : false; //check if it's an operator
if (next_is_operator) {
//if it's not a prefix operator then it not in the right place
if (!next.prefix) {
throw new Errors_1.OperatorError('A prefix operator was expected at ' + next.column);
}
//mark it as a confirmed prefix
next.is_prefix = true;
//add it to the prefixes
prefixes.push(next);
i++;
}
} while (next_is_operator);
}
//if it's a prefix it should be on a special stack called prefixes
//we do this to hold on to prefixes because of left associative operators.
//they belong to the variable/literal but if placed on either the stack
//or output there's no way of knowing this. I might be wrong so I welcome
//any discussion about this.
if (operator.is_prefix) //ADD ALL EXCEPTIONS FOR ADDING TO PREFIX STACK HERE. !!!
prefixes.push(operator);
else
stack.push(operator);
//move the prefixes to the stack
while (prefixes.length) {
if (operator.leftAssoc || !operator.leftAssoc && prefixes[prefixes.length - 1].precedence >= operator.precedence) //revisit for commas
stack.push(prefixes.pop());
else
break;
}
}
else if (e.type === Token_1.Token.VARIABLE_OR_LITERAL) {
//move prefixes to stack at beginning of scope
if (output.length === 0)
collapse(prefixes, stack);
//done with token
output.push(e);
let last_on_stack = stack[stack.length - 1];
//then move all the prefixes to the output
if (!last_on_stack || !last_on_stack.leftAssoc)
collapse(prefixes, output);
}
else if (e.type === Token_1.Token.FUNCTION) {
stack.push(e);
}
else if (e.type === Token_1.Token.UNIT) {
//if it's a unit it belongs on the stack since it's tied to the previous token
output.push(e);
}
//if it's an additional scope then put that into RPN form
if (Array.isArray(e)) {
output.push(RPN.TokensToRPN(e));
if (e.type)
output.push(new Token_1.Token(e.type, Token_1.Token.FUNCTION, e.column)); //since it's hidden it needs no column
}
}
//collapse the remainder of the stack and prefixes to output
collapse(stack, output);
collapse(prefixes, output);
return output;
}
/*
* Parses the tokens
* @param {Tokens[]} rpn
* @param {object} substitutions
* @returns {Symbol}
*/
parseRPN(rpn, substitutions = {}) {
let deps = this.deps;
// try {
//prepare the substitutions.
substitutions = substitutions || {};
//we first parse them out as-is
for (let x in substitutions)
substitutions[x] = (0, Parser_1.parse)(substitutions[x], {});
//Although technically constants,
//pi and e are only available when evaluating the expression so add to the subs.
//Doing this avoids rounding errors
//link e and pi
if (Settings_1.Settings.PARSE2NUMBER) {
//use the value provided if the individual for some strange reason prefers this.
//one reason could be to sub e but not pi or vice versa
if (!('e' in substitutions))
substitutions.e = new Symbol_1.Symbol(Settings_1.Settings.E);
if ((!('pi' in substitutions)))
substitutions.pi = new Symbol_1.Symbol(Settings_1.Settings.PI);
}
let Q = [];
for (let i = 0, l = rpn.length; i < l; i++) {
let e = rpn[i];
//Arrays indicate a new scope so parse that out
if (Array.isArray(e)) {
e = this.parseRPN(e, substitutions);
}
if (e) {
if (e.type === Token_1.Token.OPERATOR) {
if (e.is_prefix || e.postfix) {
//resolve the operation assocated with the prefix
Q.push(e.operation(Q.pop()));
}
else {
let b = Q.pop();
let a = Q.pop();
//Throw an error if the RH value is empty. This cannot be a postfix since we already checked
if (typeof a === 'undefined')
throw new Errors_1.OperatorError(e + ' is not a valid postfix operator at ' + e.column);
let is_comma = e.action === 'comma';
//convert Sets to Vectors on all operations at this point. Sets are only recognized functions or individually
if (a instanceof Set_1.Set && !is_comma)
a = Vector_1.Vector.fromSet(a);
if (b instanceof Set_1.Set && !is_comma)
b = Vector_1.Vector.fromSet(b);
//call all the pre-operators
this.callPeekers('pre_operator', a, b, e);
let action = deps.getAction(e.action);
let ans = action(a, b);
//call all the pre-operators
this.callPeekers('post_operator', ans, a, b, e);
Q.push(ans);
}
}
else if (e.type === Token_1.Token.FUNCTION) {
let args = Q.pop();
let parent = args.parent; //make a note of the parent
if (!(args instanceof Collection_1.Collection))
args = Collection_1.Collection.create(args);
//the return value may be a vector. If it is then we check
//Q to see if there's another vector on the stack. If it is then
//we check if has elements. If it does then we know that we're dealing
//with an "getter" object and return the requested values
//call the function. This is the _.callfunction method in nerdamer
//call the function. This is the _.callfunction method in nerdamer
let fn_name = e.value;
let fn_args = args.getItems();
//call the pre-function peekers
this.callPeekers('pre_function', fn_name, fn_args);
let ret = deps.callfunction(fn_name, fn_args);
//call the post-function peekers
this.callPeekers('post_function', ret, fn_name, fn_args);
let last = Q[Q.length - 1];
let next = rpn[i + 1];
let next_is_comma = next && next.type === Token_1.Token.OPERATOR && next.value === ',';
if (!next_is_comma && ret instanceof Vector_1.Vector && last && last.elements && !(last instanceof Collection_1.Collection)) {
//remove the item from the queue
let item = Q.pop();
let getter = ret.elements[0];
//check if it's symbolic. If so put it back and add the item to the stack
if (!getter.isConstant()) {
item.getter = getter;
Q.push(item);
Q.push(ret);
}
else if (getter instanceof Slice_1.Slice) {
//if it's a Slice return the slice
Q.push(Vector_1.Vector.fromArray(item.elements.slice(getter.start, getter.end)));
}
else {
let index = Number(getter);
let il = item.elements.length;
//support for negative indices
if (index < 0)
index = il + index;
//it it's still out of bounds
if (index < 0 || index >= il) //index should no longer be negative since it's been reset above
//range error
throw new Errors_1.OutOfRangeError('Index out of range ' + (e.column + 1));
let element = item.elements[index];
//cyclic but we need to mark this for future reference
item.getter = index;
element.parent = item;
Q.push(element);
}
}
else {
//extend the parent reference
if (parent)
ret.parent = parent;
Q.push(ret);
}
}
else {
let subbed;
let v = e.value;
if (v in Settings_1.Settings.ALIASES)
e = (0, Parser_1.parse)(Settings_1.Settings.ALIASES[e]);
//wrap it in a symbol if need be
else if (e.type === Token_1.Token.VARIABLE_OR_LITERAL)
e = new Symbol_1.Symbol(v);
else if (e.type === Token_1.Token.UNIT) {
e = new Symbol_1.Symbol(v);
e.isUnit = true;
}
//make substitutions
//Always constants first. This avoids the being overridden
if (this.variables.isConstant(v)) {
subbed = e;
e = new Symbol_1.Symbol(this.variables.getConstant(v));
}
//next substitutions. This allows declared variable to be overridden
//check if the values match to avoid erasing the multiplier.
//Example:/e = 3*a. substutiting a for a will wipe out the multiplier.
else if (v in substitutions && v !== substitutions[v].toString()) {
subbed = e;
e = substitutions[v].clone();
}
//next declare variables
else if (this.variables.isVar(v)) {
subbed = e;
e = this.variables.getVar(v).clone();
}
//make notation of what it was before
if (subbed)
e.subbed = subbed;
Q.push(e);
}
}
}
let retval = Q[0];
if (['undefined', 'string', 'number'].indexOf(typeof retval) !== -1) {
throw new Errors_1.UnexpectedTokenError(`Unexpected token: ${typeof retval}, ${retval}`);
}
return retval;
// }
// catch(error) {
// throw error;
// // let rethrowErrors = [OutOfFunctionDomainError];
// // // Rethrow certain errors in the same class to preserve them
// // rethrowErrors.forEach(function (E) {
// // if (error instanceof E) {
// // throw new E(error.message + ': ' + e.column);
// // }
// // });
// //
// // throw new ParseError(error.message + ': ' + e.column);
// }
}
callPeekers(name) {
if (Settings_1.Settings.callPeekers) {
let peekers = this.peekers[name];
//remove the first items and stringify
let args = (0, Utils_1.arguments2Array)(arguments).slice(1).map(o => o ? String(o) : o);
//call each one of the peekers
for (let i = 0; i < peekers.length; i++) {
peekers[i].apply(null, args);
}
}
}
;
}
exports.RPN = RPN;
//# sourceMappingURL=RPN.js.map