1531 lines
40 KiB
JavaScript
1531 lines
40 KiB
JavaScript
|
/*! JavaScript Expression Evaluator v1.3.0 https://github.com/oat-sa/expr-eval/LICENCE */
|
||
|
(function (global, factory) {
|
||
|
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
|
||
|
typeof define === 'function' && define.amd ? define(factory) :
|
||
|
(global.exprEval = factory());
|
||
|
}(this, (function () { 'use strict';
|
||
|
|
||
|
var INUMBER = 'INUMBER';
|
||
|
var IOP1 = 'IOP1';
|
||
|
var IOP2 = 'IOP2';
|
||
|
var IOP3 = 'IOP3';
|
||
|
var IVAR = 'IVAR';
|
||
|
var IFUNCOP = 'IFUNCOP';
|
||
|
var IFUNCALL = 'IFUNCALL';
|
||
|
var IEXPR = 'IEXPR';
|
||
|
var IMEMBER = 'IMEMBER';
|
||
|
|
||
|
function Instruction(type, value) {
|
||
|
this.type = type;
|
||
|
this.value = (value !== undefined && value !== null) ? value : 0;
|
||
|
}
|
||
|
|
||
|
Instruction.prototype.toString = function () {
|
||
|
switch (this.type) {
|
||
|
case INUMBER:
|
||
|
case IOP1:
|
||
|
case IOP2:
|
||
|
case IOP3:
|
||
|
case IVAR:
|
||
|
case IFUNCOP:
|
||
|
return this.value;
|
||
|
case IFUNCALL:
|
||
|
return 'CALL ' + this.value;
|
||
|
case IMEMBER:
|
||
|
return '.' + this.value;
|
||
|
default:
|
||
|
return 'Invalid Instruction';
|
||
|
}
|
||
|
};
|
||
|
|
||
|
function unaryInstruction(value) {
|
||
|
return new Instruction(IOP1, value);
|
||
|
}
|
||
|
|
||
|
function binaryInstruction(value) {
|
||
|
return new Instruction(IOP2, value);
|
||
|
}
|
||
|
|
||
|
function ternaryInstruction(value) {
|
||
|
return new Instruction(IOP3, value);
|
||
|
}
|
||
|
|
||
|
function simplify(tokens, unaryOps, binaryOps, ternaryOps, values) {
|
||
|
var nstack = [];
|
||
|
var newexpression = [];
|
||
|
var n1, n2, n3;
|
||
|
var f;
|
||
|
for (var i = 0; i < tokens.length; i++) {
|
||
|
var item = tokens[i];
|
||
|
var type = item.type;
|
||
|
if (type === INUMBER) {
|
||
|
nstack.push(item);
|
||
|
} else if (type === IVAR && values.hasOwnProperty(item.value)) {
|
||
|
item = new Instruction(INUMBER, values[item.value]);
|
||
|
nstack.push(item);
|
||
|
} else if (type === IOP2 && nstack.length > 1) {
|
||
|
n2 = nstack.pop();
|
||
|
n1 = nstack.pop();
|
||
|
f = binaryOps[item.value];
|
||
|
item = new Instruction(INUMBER, f(n1.value, n2.value));
|
||
|
nstack.push(item);
|
||
|
} else if (type === IOP3 && nstack.length > 2) {
|
||
|
n3 = nstack.pop();
|
||
|
n2 = nstack.pop();
|
||
|
n1 = nstack.pop();
|
||
|
if (item.value === '?') {
|
||
|
nstack.push(n1.value ? n2.value : n3.value);
|
||
|
} else {
|
||
|
f = ternaryOps[item.value];
|
||
|
item = new Instruction(INUMBER, f(n1.value, n2.value, n3.value));
|
||
|
nstack.push(item);
|
||
|
}
|
||
|
} else if (type === IOP1 && nstack.length > 0) {
|
||
|
n1 = nstack.pop();
|
||
|
f = unaryOps[item.value];
|
||
|
item = new Instruction(INUMBER, f(n1.value));
|
||
|
nstack.push(item);
|
||
|
} else if (type === IEXPR) {
|
||
|
while (nstack.length > 0) {
|
||
|
newexpression.push(nstack.shift());
|
||
|
}
|
||
|
newexpression.push(new Instruction(IEXPR, simplify(item.value, unaryOps, binaryOps, ternaryOps, values)));
|
||
|
} else if (type === IMEMBER && nstack.length > 0) {
|
||
|
n1 = nstack.pop();
|
||
|
nstack.push(new Instruction(INUMBER, n1.value[item.value]));
|
||
|
} else {
|
||
|
while (nstack.length > 0) {
|
||
|
newexpression.push(nstack.shift());
|
||
|
}
|
||
|
newexpression.push(item);
|
||
|
}
|
||
|
}
|
||
|
while (nstack.length > 0) {
|
||
|
newexpression.push(nstack.shift());
|
||
|
}
|
||
|
return newexpression;
|
||
|
}
|
||
|
|
||
|
function substitute(tokens, variable, expr) {
|
||
|
var newexpression = [];
|
||
|
for (var i = 0; i < tokens.length; i++) {
|
||
|
var item = tokens[i];
|
||
|
var type = item.type;
|
||
|
if (type === IVAR && item.value === variable) {
|
||
|
for (var j = 0; j < expr.tokens.length; j++) {
|
||
|
var expritem = expr.tokens[j];
|
||
|
var replitem;
|
||
|
if (expritem.type === IOP1) {
|
||
|
replitem = unaryInstruction(expritem.value);
|
||
|
} else if (expritem.type === IOP2) {
|
||
|
replitem = binaryInstruction(expritem.value);
|
||
|
} else if (expritem.type === IOP3) {
|
||
|
replitem = ternaryInstruction(expritem.value);
|
||
|
} else {
|
||
|
replitem = new Instruction(expritem.type, expritem.value);
|
||
|
}
|
||
|
newexpression.push(replitem);
|
||
|
}
|
||
|
} else if (type === IEXPR) {
|
||
|
newexpression.push(new Instruction(IEXPR, substitute(item.value, variable, expr)));
|
||
|
} else {
|
||
|
newexpression.push(item);
|
||
|
}
|
||
|
}
|
||
|
return newexpression;
|
||
|
}
|
||
|
|
||
|
function evaluate(tokens, expr, values) {
|
||
|
var nstack = [];
|
||
|
var n1, n2, n3;
|
||
|
var f;
|
||
|
for (var i = 0; i < tokens.length; i++) {
|
||
|
var item = tokens[i];
|
||
|
var type = item.type;
|
||
|
if (type === INUMBER) {
|
||
|
nstack.push(item.value);
|
||
|
} else if (type === IOP2) {
|
||
|
n2 = nstack.pop();
|
||
|
n1 = nstack.pop();
|
||
|
if (item.value === 'and') {
|
||
|
nstack.push(n1 ? !!evaluate(n2, expr, values) : false);
|
||
|
} else if (item.value === 'or') {
|
||
|
nstack.push(n1 ? true : !!evaluate(n2, expr, values));
|
||
|
} else {
|
||
|
f = expr.binaryOps[item.value];
|
||
|
nstack.push(f(n1, n2));
|
||
|
}
|
||
|
} else if (type === IOP3) {
|
||
|
n3 = nstack.pop();
|
||
|
n2 = nstack.pop();
|
||
|
n1 = nstack.pop();
|
||
|
if (item.value === '?') {
|
||
|
nstack.push(evaluate(n1 ? n2 : n3, expr, values));
|
||
|
} else {
|
||
|
f = expr.ternaryOps[item.value];
|
||
|
nstack.push(f(n1, n2, n3));
|
||
|
}
|
||
|
} else if (type === IVAR) {
|
||
|
if (item.value in expr.functions) {
|
||
|
nstack.push(expr.functions[item.value]);
|
||
|
} else {
|
||
|
var v = values[item.value];
|
||
|
if (v !== undefined) {
|
||
|
nstack.push(v);
|
||
|
} else {
|
||
|
throw new Error('undefined variable: ' + item.value);
|
||
|
}
|
||
|
}
|
||
|
} else if (type === IOP1) {
|
||
|
n1 = nstack.pop();
|
||
|
f = expr.unaryOps[item.value];
|
||
|
nstack.push(f(n1));
|
||
|
} else if (type === IFUNCOP) {
|
||
|
n2 = nstack.pop();
|
||
|
n1 = nstack.pop();
|
||
|
f = expr.functions[item.value];
|
||
|
if (f.apply && f.call) {
|
||
|
nstack.push(f.apply(undefined, [n1, n2]));
|
||
|
} else {
|
||
|
throw new Error(f + ' is not a function');
|
||
|
}
|
||
|
} else if (type === IFUNCALL) {
|
||
|
var argCount = item.value;
|
||
|
var args = [];
|
||
|
while (argCount-- > 0) {
|
||
|
args.unshift(nstack.pop());
|
||
|
}
|
||
|
f = nstack.pop();
|
||
|
if (f.apply && f.call) {
|
||
|
nstack.push(f.apply(undefined, args));
|
||
|
} else {
|
||
|
throw new Error(f + ' is not a function');
|
||
|
}
|
||
|
} else if (type === IEXPR) {
|
||
|
nstack.push(item.value);
|
||
|
} else if (type === IMEMBER) {
|
||
|
n1 = nstack.pop();
|
||
|
nstack.push(n1[item.value]);
|
||
|
} else {
|
||
|
throw new Error('invalid Expression');
|
||
|
}
|
||
|
}
|
||
|
if (nstack.length > 1) {
|
||
|
throw new Error('invalid Expression (parity)');
|
||
|
}
|
||
|
return nstack[0];
|
||
|
}
|
||
|
|
||
|
function expressionToString(tokens, toJS) {
|
||
|
var nstack = [];
|
||
|
var n1, n2, n3;
|
||
|
var f;
|
||
|
for (var i = 0; i < tokens.length; i++) {
|
||
|
var item = tokens[i];
|
||
|
var type = item.type;
|
||
|
if (type === INUMBER) {
|
||
|
if (typeof item.value === 'number' && item.value < 0) {
|
||
|
nstack.push('(' + item.value + ')');
|
||
|
} else {
|
||
|
nstack.push(escapeValue(item.value));
|
||
|
}
|
||
|
} else if (type === IOP2) {
|
||
|
n2 = nstack.pop();
|
||
|
n1 = nstack.pop();
|
||
|
f = item.value;
|
||
|
if (toJS) {
|
||
|
if (f === '^') {
|
||
|
nstack.push('Math.pow(' + n1 + ', ' + n2 + ')');
|
||
|
} else if (f === 'and') {
|
||
|
nstack.push('(!!' + n1 + ' && !!' + n2 + ')');
|
||
|
} else if (f === 'or') {
|
||
|
nstack.push('(!!' + n1 + ' || !!' + n2 + ')');
|
||
|
} else if (f === '||') {
|
||
|
nstack.push('(String(' + n1 + ') + String(' + n2 + '))');
|
||
|
} else if (f === '==') {
|
||
|
nstack.push('(' + n1 + ' === ' + n2 + ')');
|
||
|
} else if (f === '!=') {
|
||
|
nstack.push('(' + n1 + ' !== ' + n2 + ')');
|
||
|
} else {
|
||
|
nstack.push('(' + n1 + ' ' + f + ' ' + n2 + ')');
|
||
|
}
|
||
|
} else {
|
||
|
nstack.push('(' + n1 + ' ' + f + ' ' + n2 + ')');
|
||
|
}
|
||
|
} else if (type === IOP3) {
|
||
|
n3 = nstack.pop();
|
||
|
n2 = nstack.pop();
|
||
|
n1 = nstack.pop();
|
||
|
f = item.value;
|
||
|
if (f === '?') {
|
||
|
nstack.push('(' + n1 + ' ? ' + n2 + ' : ' + n3 + ')');
|
||
|
} else {
|
||
|
throw new Error('invalid Expression');
|
||
|
}
|
||
|
} else if (type === IVAR) {
|
||
|
nstack.push(item.value);
|
||
|
} else if (type === IOP1) {
|
||
|
n1 = nstack.pop();
|
||
|
f = item.value;
|
||
|
if (f === '-' || f === '+') {
|
||
|
nstack.push('(' + f + n1 + ')');
|
||
|
} else if (toJS) {
|
||
|
if (f === 'not') {
|
||
|
nstack.push('(' + '!' + n1 + ')');
|
||
|
} else if (f === '!') {
|
||
|
nstack.push('fac(' + n1 + ')');
|
||
|
} else {
|
||
|
nstack.push(f + '(' + n1 + ')');
|
||
|
}
|
||
|
} else if (f === '!') {
|
||
|
nstack.push('(' + n1 + '!)');
|
||
|
} else {
|
||
|
nstack.push('(' + f + ' ' + n1 + ')');
|
||
|
}
|
||
|
} else if (type === IFUNCALL) {
|
||
|
var argCount = item.value;
|
||
|
var args = [];
|
||
|
while (argCount-- > 0) {
|
||
|
args.unshift(nstack.pop());
|
||
|
}
|
||
|
f = nstack.pop();
|
||
|
nstack.push(f + '(' + args.join(', ') + ')');
|
||
|
} else if (type === IMEMBER) {
|
||
|
n1 = nstack.pop();
|
||
|
nstack.push(n1 + '.' + item.value);
|
||
|
} else if (type === IEXPR) {
|
||
|
nstack.push('(' + expressionToString(item.value, toJS) + ')');
|
||
|
} else {
|
||
|
throw new Error('invalid Expression');
|
||
|
}
|
||
|
}
|
||
|
if (nstack.length > 1) {
|
||
|
throw new Error('invalid Expression (parity)');
|
||
|
}
|
||
|
return String(nstack[0]);
|
||
|
}
|
||
|
|
||
|
function escapeValue(v) {
|
||
|
if (typeof v === 'string') {
|
||
|
return JSON.stringify(v).replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029');
|
||
|
}
|
||
|
return v;
|
||
|
}
|
||
|
|
||
|
function contains(array, obj) {
|
||
|
for (var i = 0; i < array.length; i++) {
|
||
|
if (array[i] === obj) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
function getSymbols(tokens, symbols, options) {
|
||
|
options = options || {};
|
||
|
var withMembers = !!options.withMembers;
|
||
|
var prevVar = null;
|
||
|
|
||
|
for (var i = 0; i < tokens.length; i++) {
|
||
|
var item = tokens[i];
|
||
|
if (item.type === IVAR && !contains(symbols, item.value)) {
|
||
|
if (!withMembers) {
|
||
|
symbols.push(item.value);
|
||
|
} else if (prevVar !== null) {
|
||
|
if (!contains(symbols, prevVar)) {
|
||
|
symbols.push(prevVar);
|
||
|
}
|
||
|
prevVar = item.value;
|
||
|
} else {
|
||
|
prevVar = item.value;
|
||
|
}
|
||
|
} else if (item.type === IMEMBER && withMembers && prevVar !== null) {
|
||
|
prevVar += '.' + item.value;
|
||
|
} else if (item.type === IEXPR) {
|
||
|
getSymbols(item.value, symbols, options);
|
||
|
} else if (prevVar !== null) {
|
||
|
if (!contains(symbols, prevVar)) {
|
||
|
symbols.push(prevVar);
|
||
|
}
|
||
|
prevVar = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (prevVar !== null && !contains(symbols, prevVar)) {
|
||
|
symbols.push(prevVar);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
function Expression(tokens, parser) {
|
||
|
this.tokens = tokens;
|
||
|
this.parser = parser;
|
||
|
this.unaryOps = parser.unaryOps;
|
||
|
this.binaryOps = parser.binaryOps;
|
||
|
this.ternaryOps = parser.ternaryOps;
|
||
|
this.functions = parser.functions;
|
||
|
}
|
||
|
|
||
|
Expression.prototype.simplify = function (values) {
|
||
|
values = values || {};
|
||
|
return new Expression(simplify(this.tokens, this.unaryOps, this.binaryOps, this.ternaryOps, values), this.parser);
|
||
|
};
|
||
|
|
||
|
Expression.prototype.substitute = function (variable, expr) {
|
||
|
if (!(expr instanceof Expression)) {
|
||
|
expr = this.parser.parse(String(expr));
|
||
|
}
|
||
|
|
||
|
return new Expression(substitute(this.tokens, variable, expr), this.parser);
|
||
|
};
|
||
|
|
||
|
Expression.prototype.evaluate = function (values) {
|
||
|
values = values || {};
|
||
|
return evaluate(this.tokens, this, values);
|
||
|
};
|
||
|
|
||
|
Expression.prototype.toString = function () {
|
||
|
return expressionToString(this.tokens, false);
|
||
|
};
|
||
|
|
||
|
Expression.prototype.symbols = function (options) {
|
||
|
options = options || {};
|
||
|
var vars = [];
|
||
|
getSymbols(this.tokens, vars, options);
|
||
|
return vars;
|
||
|
};
|
||
|
|
||
|
Expression.prototype.variables = function (options) {
|
||
|
options = options || {};
|
||
|
var vars = [];
|
||
|
getSymbols(this.tokens, vars, options);
|
||
|
var functions = this.functions;
|
||
|
return vars.filter(function (name) {
|
||
|
return !(name in functions);
|
||
|
});
|
||
|
};
|
||
|
|
||
|
Expression.prototype.toJSFunction = function (param, variables) {
|
||
|
var expr = this;
|
||
|
var f = new Function(param, 'with(this.functions) with (this.ternaryOps) with (this.binaryOps) with (this.unaryOps) { return ' + expressionToString(this.simplify(variables).tokens, true) + '; }'); // eslint-disable-line no-new-func
|
||
|
return function () {
|
||
|
return f.apply(expr, arguments);
|
||
|
};
|
||
|
};
|
||
|
|
||
|
var TEOF = 'TEOF';
|
||
|
var TOP = 'TOP';
|
||
|
var TFUNCOP = 'TFUNCOP';
|
||
|
var TNUMBER = 'TNUMBER';
|
||
|
var TSTRING = 'TSTRING';
|
||
|
var TPAREN = 'TPAREN';
|
||
|
var TCOMMA = 'TCOMMA';
|
||
|
var TNAME = 'TNAME';
|
||
|
|
||
|
function Token(type, value, index) {
|
||
|
this.type = type;
|
||
|
this.value = value;
|
||
|
this.index = index;
|
||
|
}
|
||
|
|
||
|
Token.prototype.toString = function () {
|
||
|
return this.type + ': ' + this.value;
|
||
|
};
|
||
|
|
||
|
function TokenStream(parser, expression) {
|
||
|
this.pos = 0;
|
||
|
this.current = null;
|
||
|
this.unaryOps = parser.unaryOps;
|
||
|
this.binaryOps = parser.binaryOps;
|
||
|
this.ternaryOps = parser.ternaryOps;
|
||
|
this.functions = parser.functions;
|
||
|
this.consts = parser.consts;
|
||
|
this.expression = expression;
|
||
|
this.savedPosition = 0;
|
||
|
this.savedCurrent = null;
|
||
|
this.options = parser.options;
|
||
|
}
|
||
|
|
||
|
TokenStream.prototype.newToken = function (type, value, pos) {
|
||
|
return new Token(type, value, pos != null ? pos : this.pos);
|
||
|
};
|
||
|
|
||
|
TokenStream.prototype.save = function () {
|
||
|
this.savedPosition = this.pos;
|
||
|
this.savedCurrent = this.current;
|
||
|
};
|
||
|
|
||
|
TokenStream.prototype.restore = function () {
|
||
|
this.pos = this.savedPosition;
|
||
|
this.current = this.savedCurrent;
|
||
|
};
|
||
|
|
||
|
TokenStream.prototype.next = function () {
|
||
|
if (this.pos >= this.expression.length) {
|
||
|
return this.newToken(TEOF, 'EOF');
|
||
|
}
|
||
|
|
||
|
if (this.isWhitespace() || this.isComment()) {
|
||
|
return this.next();
|
||
|
} else if (this.isRadixInteger() ||
|
||
|
this.isNumber() ||
|
||
|
this.isOperator() ||
|
||
|
this.isString() ||
|
||
|
this.isParen() ||
|
||
|
this.isComma() ||
|
||
|
this.isNamedOp() ||
|
||
|
this.isFuncOp() ||
|
||
|
this.isConst() ||
|
||
|
this.isName()) {
|
||
|
return this.current;
|
||
|
} else {
|
||
|
this.parseError('Unknown character "' + this.expression.charAt(this.pos) + '"');
|
||
|
}
|
||
|
};
|
||
|
|
||
|
TokenStream.prototype.isString = function () {
|
||
|
var r = false;
|
||
|
var startPos = this.pos;
|
||
|
var quote = this.expression.charAt(startPos);
|
||
|
|
||
|
if (quote === '\'' || quote === '"') {
|
||
|
var index = this.expression.indexOf(quote, startPos + 1);
|
||
|
while (index >= 0 && this.pos < this.expression.length) {
|
||
|
this.pos = index + 1;
|
||
|
if (this.expression.charAt(index - 1) !== '\\') {
|
||
|
var rawString = this.expression.substring(startPos + 1, index);
|
||
|
this.current = this.newToken(TSTRING, this.unescape(rawString), startPos);
|
||
|
r = true;
|
||
|
break;
|
||
|
}
|
||
|
index = this.expression.indexOf(quote, index + 1);
|
||
|
}
|
||
|
}
|
||
|
return r;
|
||
|
};
|
||
|
|
||
|
TokenStream.prototype.isParen = function () {
|
||
|
var c = this.expression.charAt(this.pos);
|
||
|
if (c === '(' || c === ')') {
|
||
|
this.current = this.newToken(TPAREN, c);
|
||
|
this.pos++;
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
};
|
||
|
|
||
|
TokenStream.prototype.isComma = function () {
|
||
|
var c = this.expression.charAt(this.pos);
|
||
|
if (c === ',') {
|
||
|
this.current = this.newToken(TCOMMA, ',');
|
||
|
this.pos++;
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
};
|
||
|
|
||
|
TokenStream.prototype.isConst = function () {
|
||
|
var startPos = this.pos;
|
||
|
var i = startPos;
|
||
|
for (; i < this.expression.length; i++) {
|
||
|
var c = this.expression.charAt(i);
|
||
|
if (c.toUpperCase() === c.toLowerCase()) {
|
||
|
if (i === this.pos || (c !== '_' && c !== '.' && (c < '0' || c > '9'))) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (i > startPos) {
|
||
|
var str = this.expression.substring(startPos, i);
|
||
|
if (str in this.consts) {
|
||
|
this.current = this.newToken(TNUMBER, this.consts[str]);
|
||
|
this.pos += str.length;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
};
|
||
|
|
||
|
TokenStream.prototype.isNamedOp = function () {
|
||
|
var startPos = this.pos;
|
||
|
var i = startPos;
|
||
|
for (; i < this.expression.length; i++) {
|
||
|
var c = this.expression.charAt(i);
|
||
|
if (c.toUpperCase() === c.toLowerCase()) {
|
||
|
if (i === this.pos || (c !== '_' && (c < '0' || c > '9'))) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (i > startPos) {
|
||
|
var str = this.expression.substring(startPos, i);
|
||
|
if (this.isOperatorEnabled(str) && (str in this.binaryOps || str in this.unaryOps || str in this.ternaryOps)) {
|
||
|
this.current = this.newToken(TOP, str);
|
||
|
this.pos += str.length;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
};
|
||
|
|
||
|
TokenStream.prototype.isFuncOp = function () {
|
||
|
var c = this.expression.charAt(this.pos);
|
||
|
var startPos = this.pos + 1;
|
||
|
var i = startPos;
|
||
|
var str;
|
||
|
if (c === '@') {
|
||
|
for (; i < this.expression.length; i++) {
|
||
|
c = this.expression.charAt(i);
|
||
|
if (c.toUpperCase() === c.toLowerCase()) {
|
||
|
if (i === startPos || (c !== '_' && (c < '0' || c > '9'))) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (i > startPos) {
|
||
|
str = this.expression.substring(startPos, i);
|
||
|
if (str in this.functions) {
|
||
|
this.current = this.newToken(TFUNCOP, str);
|
||
|
this.pos = startPos + str.length;
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false;
|
||
|
};
|
||
|
|
||
|
TokenStream.prototype.isName = function () {
|
||
|
var startPos = this.pos;
|
||
|
var i = startPos;
|
||
|
var hasLetter = false;
|
||
|
for (; i < this.expression.length; i++) {
|
||
|
var c = this.expression.charAt(i);
|
||
|
if (c.toUpperCase() === c.toLowerCase()) {
|
||
|
if (i === this.pos && (c === '$' || c === '_')) {
|
||
|
if (c === '_') {
|
||
|
hasLetter = true;
|
||
|
}
|
||
|
continue;
|
||
|
} else if (i === this.pos || !hasLetter || (c !== '_' && (c < '0' || c > '9'))) {
|
||
|
break;
|
||
|
}
|
||
|
} else {
|
||
|
hasLetter = true;
|
||
|
}
|
||
|
}
|
||
|
if (hasLetter) {
|
||
|
var str = this.expression.substring(startPos, i);
|
||
|
this.current = this.newToken(TNAME, str);
|
||
|
this.pos += str.length;
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
};
|
||
|
|
||
|
TokenStream.prototype.isWhitespace = function () {
|
||
|
var r = false;
|
||
|
var c = this.expression.charAt(this.pos);
|
||
|
while (c === ' ' || c === '\t' || c === '\n' || c === '\r') {
|
||
|
r = true;
|
||
|
this.pos++;
|
||
|
if (this.pos >= this.expression.length) {
|
||
|
break;
|
||
|
}
|
||
|
c = this.expression.charAt(this.pos);
|
||
|
}
|
||
|
return r;
|
||
|
};
|
||
|
|
||
|
var codePointPattern = /^[0-9a-f]{4}$/i;
|
||
|
|
||
|
TokenStream.prototype.unescape = function (v) {
|
||
|
var index = v.indexOf('\\');
|
||
|
if (index < 0) {
|
||
|
return v;
|
||
|
}
|
||
|
|
||
|
var buffer = v.substring(0, index);
|
||
|
while (index >= 0) {
|
||
|
var c = v.charAt(++index);
|
||
|
switch (c) {
|
||
|
case '\'':
|
||
|
buffer += '\'';
|
||
|
break;
|
||
|
case '"':
|
||
|
buffer += '"';
|
||
|
break;
|
||
|
case '\\':
|
||
|
buffer += '\\';
|
||
|
break;
|
||
|
case '/':
|
||
|
buffer += '/';
|
||
|
break;
|
||
|
case 'b':
|
||
|
buffer += '\b';
|
||
|
break;
|
||
|
case 'f':
|
||
|
buffer += '\f';
|
||
|
break;
|
||
|
case 'n':
|
||
|
buffer += '\n';
|
||
|
break;
|
||
|
case 'r':
|
||
|
buffer += '\r';
|
||
|
break;
|
||
|
case 't':
|
||
|
buffer += '\t';
|
||
|
break;
|
||
|
case 'u':
|
||
|
// interpret the following 4 characters as the hex of the unicode code point
|
||
|
var codePoint = v.substring(index + 1, index + 5);
|
||
|
if (!codePointPattern.test(codePoint)) {
|
||
|
this.parseError('Illegal escape sequence: \\u' + codePoint);
|
||
|
}
|
||
|
buffer += String.fromCharCode(parseInt(codePoint, 16));
|
||
|
index += 4;
|
||
|
break;
|
||
|
default:
|
||
|
throw this.parseError('Illegal escape sequence: "\\' + c + '"');
|
||
|
}
|
||
|
++index;
|
||
|
var backslash = v.indexOf('\\', index);
|
||
|
buffer += v.substring(index, backslash < 0 ? v.length : backslash);
|
||
|
index = backslash;
|
||
|
}
|
||
|
|
||
|
return buffer;
|
||
|
};
|
||
|
|
||
|
TokenStream.prototype.isComment = function () {
|
||
|
var c = this.expression.charAt(this.pos);
|
||
|
if (c === '/' && this.expression.charAt(this.pos + 1) === '*') {
|
||
|
this.pos = this.expression.indexOf('*/', this.pos) + 2;
|
||
|
if (this.pos === 1) {
|
||
|
this.pos = this.expression.length;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
};
|
||
|
|
||
|
TokenStream.prototype.isRadixInteger = function () {
|
||
|
var pos = this.pos;
|
||
|
|
||
|
if (pos >= this.expression.length - 2 || this.expression.charAt(pos) !== '0') {
|
||
|
return false;
|
||
|
}
|
||
|
++pos;
|
||
|
|
||
|
var radix;
|
||
|
var validDigit;
|
||
|
if (this.expression.charAt(pos) === 'x') {
|
||
|
radix = 16;
|
||
|
validDigit = /^[0-9a-f]$/i;
|
||
|
++pos;
|
||
|
} else if (this.expression.charAt(pos) === 'b') {
|
||
|
radix = 2;
|
||
|
validDigit = /^[01]$/i;
|
||
|
++pos;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
var valid = false;
|
||
|
var startPos = pos;
|
||
|
|
||
|
while (pos < this.expression.length) {
|
||
|
var c = this.expression.charAt(pos);
|
||
|
if (validDigit.test(c)) {
|
||
|
pos++;
|
||
|
valid = true;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (valid) {
|
||
|
this.current = this.newToken(TNUMBER, parseInt(this.expression.substring(startPos, pos), radix));
|
||
|
this.pos = pos;
|
||
|
}
|
||
|
return valid;
|
||
|
};
|
||
|
|
||
|
TokenStream.prototype.isNumber = function () {
|
||
|
var valid = false;
|
||
|
var pos = this.pos;
|
||
|
var startPos = pos;
|
||
|
var resetPos = pos;
|
||
|
var foundDot = false;
|
||
|
var foundDigits = false;
|
||
|
var c;
|
||
|
|
||
|
while (pos < this.expression.length) {
|
||
|
c = this.expression.charAt(pos);
|
||
|
if ((c >= '0' && c <= '9') || (!foundDot && c === '.')) {
|
||
|
if (c === '.') {
|
||
|
foundDot = true;
|
||
|
} else {
|
||
|
foundDigits = true;
|
||
|
}
|
||
|
pos++;
|
||
|
valid = foundDigits;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (valid) {
|
||
|
resetPos = pos;
|
||
|
}
|
||
|
|
||
|
if (c === 'e' || c === 'E') {
|
||
|
pos++;
|
||
|
var acceptSign = true;
|
||
|
var validExponent = false;
|
||
|
while (pos < this.expression.length) {
|
||
|
c = this.expression.charAt(pos);
|
||
|
if (acceptSign && (c === '+' || c === '-')) {
|
||
|
acceptSign = false;
|
||
|
} else if (c >= '0' && c <= '9') {
|
||
|
validExponent = true;
|
||
|
acceptSign = false;
|
||
|
} else {
|
||
|
break;
|
||
|
}
|
||
|
pos++;
|
||
|
}
|
||
|
|
||
|
if (!validExponent) {
|
||
|
pos = resetPos;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (valid) {
|
||
|
this.current = this.newToken(TNUMBER, parseFloat(this.expression.substring(startPos, pos)));
|
||
|
this.pos = pos;
|
||
|
} else {
|
||
|
this.pos = resetPos;
|
||
|
}
|
||
|
return valid;
|
||
|
};
|
||
|
|
||
|
TokenStream.prototype.isOperator = function () {
|
||
|
var startPos = this.pos;
|
||
|
var c = this.expression.charAt(this.pos);
|
||
|
|
||
|
if (c === '+' || c === '-' || c === '*' || c === '/' || c === '%' || c === '^' || c === '?' || c === ':' || c === '.') {
|
||
|
this.current = this.newToken(TOP, c);
|
||
|
} else if (c === '∙' || c === '•') {
|
||
|
this.current = this.newToken(TOP, '*');
|
||
|
} else if (c === '>') {
|
||
|
if (this.expression.charAt(this.pos + 1) === '=') {
|
||
|
this.current = this.newToken(TOP, '>=');
|
||
|
this.pos++;
|
||
|
} else {
|
||
|
this.current = this.newToken(TOP, '>');
|
||
|
}
|
||
|
} else if (c === '<') {
|
||
|
if (this.expression.charAt(this.pos + 1) === '=') {
|
||
|
this.current = this.newToken(TOP, '<=');
|
||
|
this.pos++;
|
||
|
} else {
|
||
|
this.current = this.newToken(TOP, '<');
|
||
|
}
|
||
|
} else if (c === '|') {
|
||
|
if (this.expression.charAt(this.pos + 1) === '|') {
|
||
|
this.current = this.newToken(TOP, '||');
|
||
|
this.pos++;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
} else if (c === '=') {
|
||
|
if (this.expression.charAt(this.pos + 1) === '=') {
|
||
|
this.current = this.newToken(TOP, '==');
|
||
|
this.pos++;
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
} else if (c === '!') {
|
||
|
if (this.expression.charAt(this.pos + 1) === '=') {
|
||
|
this.current = this.newToken(TOP, '!=');
|
||
|
this.pos++;
|
||
|
} else {
|
||
|
this.current = this.newToken(TOP, c);
|
||
|
}
|
||
|
} else {
|
||
|
return false;
|
||
|
}
|
||
|
this.pos++;
|
||
|
|
||
|
if (this.isOperatorEnabled(this.current.value)) {
|
||
|
return true;
|
||
|
} else {
|
||
|
this.pos = startPos;
|
||
|
return false;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var optionNameMap = {
|
||
|
'+': 'add',
|
||
|
'-': 'subtract',
|
||
|
'*': 'multiply',
|
||
|
'/': 'divide',
|
||
|
'%': 'remainder',
|
||
|
'^': 'power',
|
||
|
'!': 'factorial',
|
||
|
'<': 'comparison',
|
||
|
'>': 'comparison',
|
||
|
'<=': 'comparison',
|
||
|
'>=': 'comparison',
|
||
|
'==': 'comparison',
|
||
|
'!=': 'comparison',
|
||
|
'||': 'concatenate',
|
||
|
'and': 'logical',
|
||
|
'or': 'logical',
|
||
|
'not': 'logical',
|
||
|
'?': 'conditional',
|
||
|
':': 'conditional'
|
||
|
};
|
||
|
|
||
|
function getOptionName(op) {
|
||
|
return optionNameMap.hasOwnProperty(op) ? optionNameMap[op] : op;
|
||
|
}
|
||
|
|
||
|
TokenStream.prototype.isOperatorEnabled = function (op) {
|
||
|
var optionName = getOptionName(op);
|
||
|
var operators = this.options.operators || {};
|
||
|
|
||
|
// in is a special case for now because it's disabled by default
|
||
|
if (optionName === 'in') {
|
||
|
return !!operators['in'];
|
||
|
}
|
||
|
|
||
|
return !(optionName in operators) || !!operators[optionName];
|
||
|
};
|
||
|
|
||
|
TokenStream.prototype.getCoordinates = function () {
|
||
|
var line = 0;
|
||
|
var column;
|
||
|
var newline = -1;
|
||
|
do {
|
||
|
line++;
|
||
|
column = this.pos - newline;
|
||
|
newline = this.expression.indexOf('\n', newline + 1);
|
||
|
} while (newline >= 0 && newline < this.pos);
|
||
|
|
||
|
return {
|
||
|
line: line,
|
||
|
column: column
|
||
|
};
|
||
|
};
|
||
|
|
||
|
TokenStream.prototype.parseError = function (msg) {
|
||
|
var coords = this.getCoordinates();
|
||
|
throw new Error('parse error [' + coords.line + ':' + coords.column + ']: ' + msg);
|
||
|
};
|
||
|
|
||
|
function ParserState(parser, tokenStream, options) {
|
||
|
this.parser = parser;
|
||
|
this.tokens = tokenStream;
|
||
|
this.current = null;
|
||
|
this.nextToken = null;
|
||
|
this.next();
|
||
|
this.savedCurrent = null;
|
||
|
this.savedNextToken = null;
|
||
|
this.allowMemberAccess = options.allowMemberAccess !== false;
|
||
|
}
|
||
|
|
||
|
ParserState.prototype.next = function () {
|
||
|
this.current = this.nextToken;
|
||
|
return (this.nextToken = this.tokens.next());
|
||
|
};
|
||
|
|
||
|
ParserState.prototype.tokenMatches = function (token, value) {
|
||
|
if (typeof value === 'undefined') {
|
||
|
return true;
|
||
|
} else if (Array.isArray(value)) {
|
||
|
return contains(value, token.value);
|
||
|
} else if (typeof value === 'function') {
|
||
|
return value(token);
|
||
|
} else {
|
||
|
return token.value === value;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
ParserState.prototype.save = function () {
|
||
|
this.savedCurrent = this.current;
|
||
|
this.savedNextToken = this.nextToken;
|
||
|
this.tokens.save();
|
||
|
};
|
||
|
|
||
|
ParserState.prototype.restore = function () {
|
||
|
this.tokens.restore();
|
||
|
this.current = this.savedCurrent;
|
||
|
this.nextToken = this.savedNextToken;
|
||
|
};
|
||
|
|
||
|
ParserState.prototype.accept = function (type, value) {
|
||
|
if (this.nextToken.type === type && this.tokenMatches(this.nextToken, value)) {
|
||
|
this.next();
|
||
|
return true;
|
||
|
}
|
||
|
return false;
|
||
|
};
|
||
|
|
||
|
ParserState.prototype.expect = function (type, value) {
|
||
|
if (!this.accept(type, value)) {
|
||
|
var coords = this.tokens.getCoordinates();
|
||
|
throw new Error('parse error [' + coords.line + ':' + coords.column + ']: Expected ' + (value || type));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
ParserState.prototype.parseAtom = function (instr) {
|
||
|
if (this.accept(TNAME)) {
|
||
|
instr.push(new Instruction(IVAR, this.current.value));
|
||
|
} else if (this.accept(TNUMBER)) {
|
||
|
instr.push(new Instruction(INUMBER, this.current.value));
|
||
|
} else if (this.accept(TSTRING)) {
|
||
|
instr.push(new Instruction(INUMBER, this.current.value));
|
||
|
} else if (this.accept(TPAREN, '(')) {
|
||
|
this.parseExpression(instr);
|
||
|
this.expect(TPAREN, ')');
|
||
|
} else {
|
||
|
throw new Error('unexpected ' + this.nextToken);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
ParserState.prototype.parseExpression = function (instr) {
|
||
|
this.parseConditionalExpression(instr);
|
||
|
};
|
||
|
|
||
|
ParserState.prototype.parseConditionalExpression = function (instr) {
|
||
|
this.parseOrExpression(instr);
|
||
|
while (this.accept(TOP, '?')) {
|
||
|
var trueBranch = [];
|
||
|
var falseBranch = [];
|
||
|
this.parseConditionalExpression(trueBranch);
|
||
|
this.expect(TOP, ':');
|
||
|
this.parseConditionalExpression(falseBranch);
|
||
|
instr.push(new Instruction(IEXPR, trueBranch));
|
||
|
instr.push(new Instruction(IEXPR, falseBranch));
|
||
|
instr.push(ternaryInstruction('?'));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
ParserState.prototype.parseOrExpression = function (instr) {
|
||
|
this.parseAndExpression(instr);
|
||
|
while (this.accept(TOP, 'or')) {
|
||
|
var falseBranch = [];
|
||
|
this.parseAndExpression(falseBranch);
|
||
|
instr.push(new Instruction(IEXPR, falseBranch));
|
||
|
instr.push(binaryInstruction('or'));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
ParserState.prototype.parseAndExpression = function (instr) {
|
||
|
this.parseComparison(instr);
|
||
|
while (this.accept(TOP, 'and')) {
|
||
|
var trueBranch = [];
|
||
|
this.parseComparison(trueBranch);
|
||
|
instr.push(new Instruction(IEXPR, trueBranch));
|
||
|
instr.push(binaryInstruction('and'));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var COMPARISON_OPERATORS = ['==', '!=', '<', '<=', '>=', '>', 'in'];
|
||
|
|
||
|
ParserState.prototype.parseComparison = function (instr) {
|
||
|
this.parseAddSub(instr);
|
||
|
while (this.accept(TOP, COMPARISON_OPERATORS)) {
|
||
|
var op = this.current;
|
||
|
this.parseAddSub(instr);
|
||
|
instr.push(binaryInstruction(op.value));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var ADD_SUB_OPERATORS = ['+', '-', '||'];
|
||
|
|
||
|
ParserState.prototype.parseAddSub = function (instr) {
|
||
|
this.parseTerm(instr);
|
||
|
while (this.accept(TOP, ADD_SUB_OPERATORS)) {
|
||
|
var op = this.current;
|
||
|
this.parseTerm(instr);
|
||
|
instr.push(binaryInstruction(op.value));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
var TERM_OPERATORS = ['*', '/', '%'];
|
||
|
|
||
|
ParserState.prototype.parseTerm = function (instr) {
|
||
|
this.parseFactor(instr);
|
||
|
while (this.accept(TOP, TERM_OPERATORS)) {
|
||
|
var op = this.current;
|
||
|
this.parseFactor(instr);
|
||
|
instr.push(binaryInstruction(op.value));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
ParserState.prototype.parseFactor = function (instr) {
|
||
|
var unaryOps = this.tokens.unaryOps;
|
||
|
function isPrefixOperator(token) {
|
||
|
return token.value in unaryOps;
|
||
|
}
|
||
|
|
||
|
this.save();
|
||
|
if (this.accept(TOP, isPrefixOperator)) {
|
||
|
if ((this.current.value !== '-' && this.current.value !== '+' && this.nextToken.type === TPAREN && this.nextToken.value === '(')) {
|
||
|
this.restore();
|
||
|
this.parseExponential(instr);
|
||
|
} else {
|
||
|
var op = this.current;
|
||
|
this.parseFactor(instr);
|
||
|
instr.push(unaryInstruction(op.value));
|
||
|
}
|
||
|
} else {
|
||
|
this.parseExponential(instr);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
ParserState.prototype.parseExponential = function (instr) {
|
||
|
this.parsePostfixExpression(instr);
|
||
|
while (this.accept(TOP, '^')) {
|
||
|
this.parseFactor(instr);
|
||
|
instr.push(binaryInstruction('^'));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
ParserState.prototype.parsePostfixExpression = function (instr) {
|
||
|
this.parseFunctionOperator(instr);
|
||
|
while (this.accept(TOP, '!')) {
|
||
|
instr.push(unaryInstruction('!'));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
ParserState.prototype.parseFunctionOperator = function (instr) {
|
||
|
var functions = this.tokens.functions;
|
||
|
var op;
|
||
|
function isCustomOperator(token) {
|
||
|
return token.value in functions;
|
||
|
}
|
||
|
this.parseFunctionCall(instr);
|
||
|
while (this.accept(TFUNCOP, isCustomOperator)) {
|
||
|
op = this.current;
|
||
|
this.parseFactor(instr);
|
||
|
instr.push(new Instruction(IFUNCOP, op.value));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
ParserState.prototype.parseFunctionCall = function (instr) {
|
||
|
var unaryOps = this.tokens.unaryOps;
|
||
|
function isPrefixOperator(token) {
|
||
|
return token.value in unaryOps;
|
||
|
}
|
||
|
|
||
|
if (this.accept(TOP, isPrefixOperator)) {
|
||
|
var op = this.current;
|
||
|
this.parseAtom(instr);
|
||
|
instr.push(unaryInstruction(op.value));
|
||
|
} else {
|
||
|
this.parseMemberExpression(instr);
|
||
|
while (this.accept(TPAREN, '(')) {
|
||
|
if (this.accept(TPAREN, ')')) {
|
||
|
instr.push(new Instruction(IFUNCALL, 0));
|
||
|
} else {
|
||
|
var argCount = this.parseArgumentList(instr);
|
||
|
instr.push(new Instruction(IFUNCALL, argCount));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
ParserState.prototype.parseArgumentList = function (instr) {
|
||
|
var argCount = 0;
|
||
|
|
||
|
while (!this.accept(TPAREN, ')')) {
|
||
|
this.parseExpression(instr);
|
||
|
++argCount;
|
||
|
while (this.accept(TCOMMA)) {
|
||
|
this.parseExpression(instr);
|
||
|
++argCount;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return argCount;
|
||
|
};
|
||
|
|
||
|
ParserState.prototype.parseMemberExpression = function (instr) {
|
||
|
this.parseAtom(instr);
|
||
|
while (this.accept(TOP, '.')) {
|
||
|
if (!this.allowMemberAccess) {
|
||
|
throw new Error('unexpected ".", member access is not permitted');
|
||
|
}
|
||
|
|
||
|
this.expect(TNAME);
|
||
|
instr.push(new Instruction(IMEMBER, this.current.value));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
function add(a, b) {
|
||
|
return Number(a) + Number(b);
|
||
|
}
|
||
|
|
||
|
function sub(a, b) {
|
||
|
return a - b;
|
||
|
}
|
||
|
|
||
|
function mul(a, b) {
|
||
|
return a * b;
|
||
|
}
|
||
|
|
||
|
function div(a, b) {
|
||
|
return a / b;
|
||
|
}
|
||
|
|
||
|
function mod(a, b) {
|
||
|
return a % b;
|
||
|
}
|
||
|
|
||
|
function concat(a, b) {
|
||
|
return '' + a + b;
|
||
|
}
|
||
|
|
||
|
function equal(a, b) {
|
||
|
return a === b;
|
||
|
}
|
||
|
|
||
|
function notEqual(a, b) {
|
||
|
return a !== b;
|
||
|
}
|
||
|
|
||
|
function greaterThan(a, b) {
|
||
|
return a > b;
|
||
|
}
|
||
|
|
||
|
function lessThan(a, b) {
|
||
|
return a < b;
|
||
|
}
|
||
|
|
||
|
function greaterThanEqual(a, b) {
|
||
|
return a >= b;
|
||
|
}
|
||
|
|
||
|
function lessThanEqual(a, b) {
|
||
|
return a <= b;
|
||
|
}
|
||
|
|
||
|
function andOperator(a, b) {
|
||
|
return Boolean(a && b);
|
||
|
}
|
||
|
|
||
|
function orOperator(a, b) {
|
||
|
return Boolean(a || b);
|
||
|
}
|
||
|
|
||
|
function inOperator(a, b) {
|
||
|
return contains(b, a);
|
||
|
}
|
||
|
|
||
|
function sinh(a) {
|
||
|
return ((Math.exp(a) - Math.exp(-a)) / 2);
|
||
|
}
|
||
|
|
||
|
function cosh(a) {
|
||
|
return ((Math.exp(a) + Math.exp(-a)) / 2);
|
||
|
}
|
||
|
|
||
|
function tanh(a) {
|
||
|
if (a === Infinity) return 1;
|
||
|
if (a === -Infinity) return -1;
|
||
|
return (Math.exp(a) - Math.exp(-a)) / (Math.exp(a) + Math.exp(-a));
|
||
|
}
|
||
|
|
||
|
function asinh(a) {
|
||
|
if (a === -Infinity) return a;
|
||
|
return Math.log(a + Math.sqrt((a * a) + 1));
|
||
|
}
|
||
|
|
||
|
function acosh(a) {
|
||
|
return Math.log(a + Math.sqrt((a * a) - 1));
|
||
|
}
|
||
|
|
||
|
function atanh(a) {
|
||
|
return (Math.log((1 + a) / (1 - a)) / 2);
|
||
|
}
|
||
|
|
||
|
function log10(a) {
|
||
|
return Math.log(a) * Math.LOG10E;
|
||
|
}
|
||
|
|
||
|
function neg(a) {
|
||
|
return -a;
|
||
|
}
|
||
|
|
||
|
function not(a) {
|
||
|
return !a;
|
||
|
}
|
||
|
|
||
|
function trunc(a) {
|
||
|
return a < 0 ? Math.ceil(a) : Math.floor(a);
|
||
|
}
|
||
|
|
||
|
function random(a) {
|
||
|
return Math.random() * (a || 1);
|
||
|
}
|
||
|
|
||
|
function factorial(a) { // a!
|
||
|
return gamma(a + 1);
|
||
|
}
|
||
|
|
||
|
function isInteger(value) {
|
||
|
return isFinite(value) && (value === Math.round(value));
|
||
|
}
|
||
|
|
||
|
var GAMMA_G = 4.7421875;
|
||
|
var GAMMA_P = [
|
||
|
0.99999999999999709182,
|
||
|
57.156235665862923517, -59.597960355475491248,
|
||
|
14.136097974741747174, -0.49191381609762019978,
|
||
|
0.33994649984811888699e-4,
|
||
|
0.46523628927048575665e-4, -0.98374475304879564677e-4,
|
||
|
0.15808870322491248884e-3, -0.21026444172410488319e-3,
|
||
|
0.21743961811521264320e-3, -0.16431810653676389022e-3,
|
||
|
0.84418223983852743293e-4, -0.26190838401581408670e-4,
|
||
|
0.36899182659531622704e-5
|
||
|
];
|
||
|
|
||
|
// Gamma function from math.js
|
||
|
function gamma(n) {
|
||
|
var t, x;
|
||
|
|
||
|
if (isInteger(n)) {
|
||
|
if (n <= 0) {
|
||
|
return isFinite(n) ? Infinity : NaN;
|
||
|
}
|
||
|
|
||
|
if (n > 171) {
|
||
|
return Infinity; // Will overflow
|
||
|
}
|
||
|
|
||
|
var value = n - 2;
|
||
|
var res = n - 1;
|
||
|
while (value > 1) {
|
||
|
res *= value;
|
||
|
value--;
|
||
|
}
|
||
|
|
||
|
if (res === 0) {
|
||
|
res = 1; // 0! is per definition 1
|
||
|
}
|
||
|
|
||
|
return res;
|
||
|
}
|
||
|
|
||
|
if (n < 0.5) {
|
||
|
return Math.PI / (Math.sin(Math.PI * n) * gamma(1 - n));
|
||
|
}
|
||
|
|
||
|
if (n >= 171.35) {
|
||
|
return Infinity; // will overflow
|
||
|
}
|
||
|
|
||
|
if (n > 85.0) { // Extended Stirling Approx
|
||
|
var twoN = n * n;
|
||
|
var threeN = twoN * n;
|
||
|
var fourN = threeN * n;
|
||
|
var fiveN = fourN * n;
|
||
|
return Math.sqrt(2 * Math.PI / n) * Math.pow((n / Math.E), n) *
|
||
|
(1 + (1 / (12 * n)) + (1 / (288 * twoN)) - (139 / (51840 * threeN)) -
|
||
|
(571 / (2488320 * fourN)) + (163879 / (209018880 * fiveN)) +
|
||
|
(5246819 / (75246796800 * fiveN * n)));
|
||
|
}
|
||
|
|
||
|
--n;
|
||
|
x = GAMMA_P[0];
|
||
|
for (var i = 1; i < GAMMA_P.length; ++i) {
|
||
|
x += GAMMA_P[i] / (n + i);
|
||
|
}
|
||
|
|
||
|
t = n + GAMMA_G + 0.5;
|
||
|
return Math.sqrt(2 * Math.PI) * Math.pow(t, n + 0.5) * Math.exp(-t) * x;
|
||
|
}
|
||
|
|
||
|
function stringLength(s) {
|
||
|
return String(s).length;
|
||
|
}
|
||
|
|
||
|
function hypot() {
|
||
|
var sum = 0;
|
||
|
var larg = 0;
|
||
|
for (var i = 0; i < arguments.length; i++) {
|
||
|
var arg = Math.abs(arguments[i]);
|
||
|
var div;
|
||
|
if (larg < arg) {
|
||
|
div = larg / arg;
|
||
|
sum = (sum * div * div) + 1;
|
||
|
larg = arg;
|
||
|
} else if (arg > 0) {
|
||
|
div = arg / larg;
|
||
|
sum += div * div;
|
||
|
} else {
|
||
|
sum += arg;
|
||
|
}
|
||
|
}
|
||
|
return larg === Infinity ? Infinity : larg * Math.sqrt(sum);
|
||
|
}
|
||
|
|
||
|
function condition(cond, yep, nope) {
|
||
|
return cond ? yep : nope;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Decimal adjustment of a number.
|
||
|
* From @escopecz.
|
||
|
*
|
||
|
* @param {Number} value The number.
|
||
|
* @param {Integer} exp The exponent (the 10 logarithm of the adjustment base).
|
||
|
* @return {Number} The adjusted value.
|
||
|
*/
|
||
|
function roundTo(value, exp) {
|
||
|
// If the exp is undefined or zero...
|
||
|
if (typeof exp === 'undefined' || +exp === 0) {
|
||
|
return Math.round(value);
|
||
|
}
|
||
|
value = +value;
|
||
|
exp = -(+exp);
|
||
|
// If the value is not a number or the exp is not an integer...
|
||
|
if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
|
||
|
return NaN;
|
||
|
}
|
||
|
// Shift
|
||
|
value = value.toString().split('e');
|
||
|
value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));
|
||
|
// Shift back
|
||
|
value = value.toString().split('e');
|
||
|
return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
|
||
|
}
|
||
|
|
||
|
function Parser(options) {
|
||
|
this.options = options || {};
|
||
|
this.unaryOps = {
|
||
|
sin: Math.sin,
|
||
|
cos: Math.cos,
|
||
|
tan: Math.tan,
|
||
|
asin: Math.asin,
|
||
|
acos: Math.acos,
|
||
|
atan: Math.atan,
|
||
|
sinh: Math.sinh || sinh,
|
||
|
cosh: Math.cosh || cosh,
|
||
|
tanh: Math.tanh || tanh,
|
||
|
asinh: Math.asinh || asinh,
|
||
|
acosh: Math.acosh || acosh,
|
||
|
atanh: Math.atanh || atanh,
|
||
|
sqrt: Math.sqrt,
|
||
|
log: Math.log,
|
||
|
ln: Math.log,
|
||
|
lg: Math.log10 || log10,
|
||
|
log10: Math.log10 || log10,
|
||
|
abs: Math.abs,
|
||
|
ceil: Math.ceil,
|
||
|
floor: Math.floor,
|
||
|
round: Math.round,
|
||
|
trunc: Math.trunc || trunc,
|
||
|
'-': neg,
|
||
|
'+': Number,
|
||
|
exp: Math.exp,
|
||
|
not: not,
|
||
|
length: stringLength,
|
||
|
'!': factorial
|
||
|
};
|
||
|
|
||
|
this.binaryOps = {
|
||
|
'+': add,
|
||
|
'-': sub,
|
||
|
'*': mul,
|
||
|
'/': div,
|
||
|
'%': mod,
|
||
|
'^': Math.pow,
|
||
|
'||': concat,
|
||
|
'==': equal,
|
||
|
'!=': notEqual,
|
||
|
'>': greaterThan,
|
||
|
'<': lessThan,
|
||
|
'>=': greaterThanEqual,
|
||
|
'<=': lessThanEqual,
|
||
|
and: andOperator,
|
||
|
or: orOperator,
|
||
|
'in': inOperator
|
||
|
};
|
||
|
|
||
|
this.ternaryOps = {
|
||
|
'?': condition
|
||
|
};
|
||
|
|
||
|
this.functions = {
|
||
|
random: random,
|
||
|
fac: factorial,
|
||
|
min: Math.min,
|
||
|
max: Math.max,
|
||
|
hypot: Math.hypot || hypot,
|
||
|
pyt: Math.hypot || hypot, // backward compat
|
||
|
pow: Math.pow,
|
||
|
atan2: Math.atan2,
|
||
|
'if': condition,
|
||
|
gamma: gamma,
|
||
|
roundTo: roundTo
|
||
|
};
|
||
|
|
||
|
this.consts = {
|
||
|
E: Math.E,
|
||
|
PI: Math.PI,
|
||
|
'true': true,
|
||
|
'false': false
|
||
|
};
|
||
|
}
|
||
|
|
||
|
Parser.prototype.parse = function (expr) {
|
||
|
var instr = [];
|
||
|
var parserState = new ParserState(
|
||
|
this,
|
||
|
new TokenStream(this, expr),
|
||
|
{ allowMemberAccess: this.options.allowMemberAccess }
|
||
|
);
|
||
|
|
||
|
parserState.parseExpression(instr);
|
||
|
parserState.expect(TEOF, 'EOF');
|
||
|
|
||
|
return new Expression(instr, this);
|
||
|
};
|
||
|
|
||
|
Parser.prototype.evaluate = function (expr, variables) {
|
||
|
return this.parse(expr).evaluate(variables);
|
||
|
};
|
||
|
|
||
|
var sharedParser = new Parser();
|
||
|
|
||
|
Parser.parse = function (expr) {
|
||
|
return sharedParser.parse(expr);
|
||
|
};
|
||
|
|
||
|
Parser.evaluate = function (expr, variables) {
|
||
|
return sharedParser.parse(expr).evaluate(variables);
|
||
|
};
|
||
|
|
||
|
/*!
|
||
|
Based on ndef.parser, by Raphael Graf(r@undefined.ch)
|
||
|
http://www.undefined.ch/mparser/index.html
|
||
|
|
||
|
Ported to JavaScript and modified by Matthew Crumley (email@matthewcrumley.com, http://silentmatt.com/)
|
||
|
|
||
|
You are free to use and modify this code in anyway you find useful. Please leave this comment in the code
|
||
|
to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code,
|
||
|
but don't feel like you have to let me know or ask permission.
|
||
|
*/
|
||
|
|
||
|
var index = {
|
||
|
Parser: Parser,
|
||
|
Expression: Expression
|
||
|
};
|
||
|
|
||
|
return index;
|
||
|
|
||
|
})));
|