Spaces:
Build error
Build error
| (function (global, factory) { | |
| typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | |
| typeof define === 'function' && define.amd ? define(['exports'], factory) : | |
| (global = global || self, factory(global.exprEval = {})); | |
| }(this, function (exports) { 'use strict'; | |
| var INUMBER = 'INUMBER'; | |
| var IOP1 = 'IOP1'; | |
| var IOP2 = 'IOP2'; | |
| var IOP3 = 'IOP3'; | |
| var IVAR = 'IVAR'; | |
| var IVARNAME = 'IVARNAME'; | |
| var IFUNCALL = 'IFUNCALL'; | |
| var IFUNDEF = 'IFUNDEF'; | |
| var IEXPR = 'IEXPR'; | |
| var IEXPREVAL = 'IEXPREVAL'; | |
| var IMEMBER = 'IMEMBER'; | |
| var IENDSTATEMENT = 'IENDSTATEMENT'; | |
| var IARRAY = 'IARRAY'; | |
| 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 IVARNAME: | |
| case IENDSTATEMENT: | |
| return this.value; | |
| case IFUNCALL: | |
| return 'CALL ' + this.value; | |
| case IFUNDEF: | |
| return 'DEF ' + this.value; | |
| case IARRAY: | |
| return 'ARRAY ' + 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 || type === IVARNAME) { | |
| if (Array.isArray(item.value)) { | |
| nstack.push.apply(nstack, simplify(item.value.map(function (x) { | |
| return new Instruction(INUMBER, x); | |
| }).concat(new Instruction(IARRAY, item.value.length)), unaryOps, binaryOps, ternaryOps, values)); | |
| } else { | |
| 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 if (type === IARRAY && nstack.length >= item.value) { | |
| var length = item.value; | |
| while (length-- > 0) { | |
| newexpression.push(nstack.pop()); | |
| } | |
| newexpression.push(new Instruction(IARRAY, 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, args, argCount; | |
| if (isExpressionEvaluator(tokens)) { | |
| return resolveExpression(tokens, values); | |
| } | |
| var numTokens = tokens.length; | |
| for (var i = 0; i < numTokens; i++) { | |
| var item = tokens[i]; | |
| var type = item.type; | |
| if (type === INUMBER || type === IVARNAME) { | |
| 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 if (item.value === '=') { | |
| f = expr.binaryOps[item.value]; | |
| nstack.push(f(n1, evaluate(n2, expr, values), values)); | |
| } else { | |
| f = expr.binaryOps[item.value]; | |
| nstack.push(f(resolveExpression(n1, values), resolveExpression(n2, values))); | |
| } | |
| } 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(resolveExpression(n1, values), resolveExpression(n2, values), resolveExpression(n3, values))); | |
| } | |
| } else if (type === IVAR) { | |
| if (item.value in expr.functions) { | |
| nstack.push(expr.functions[item.value]); | |
| } else if (item.value in expr.unaryOps && expr.parser.isOperatorEnabled(item.value)) { | |
| nstack.push(expr.unaryOps[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(resolveExpression(n1, values))); | |
| } else if (type === IFUNCALL) { | |
| argCount = item.value; | |
| args = []; | |
| while (argCount-- > 0) { | |
| args.unshift(resolveExpression(nstack.pop(), values)); | |
| } | |
| 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 === IFUNDEF) { | |
| // Create closure to keep references to arguments and expression | |
| nstack.push((function () { | |
| var n2 = nstack.pop(); | |
| var args = []; | |
| var argCount = item.value; | |
| while (argCount-- > 0) { | |
| args.unshift(nstack.pop()); | |
| } | |
| var n1 = nstack.pop(); | |
| var f = function () { | |
| var scope = Object.assign({}, values); | |
| for (var i = 0, len = args.length; i < len; i++) { | |
| scope[args[i]] = arguments[i]; | |
| } | |
| return evaluate(n2, expr, scope); | |
| }; | |
| // f.name = n1 | |
| Object.defineProperty(f, 'name', { | |
| value: n1, | |
| writable: false | |
| }); | |
| values[n1] = f; | |
| return f; | |
| })()); | |
| } else if (type === IEXPR) { | |
| nstack.push(createExpressionEvaluator(item, expr)); | |
| } else if (type === IEXPREVAL) { | |
| nstack.push(item); | |
| } else if (type === IMEMBER) { | |
| n1 = nstack.pop(); | |
| nstack.push(n1[item.value]); | |
| } else if (type === IENDSTATEMENT) { | |
| nstack.pop(); | |
| } else if (type === IARRAY) { | |
| argCount = item.value; | |
| args = []; | |
| while (argCount-- > 0) { | |
| args.unshift(nstack.pop()); | |
| } | |
| nstack.push(args); | |
| } else { | |
| throw new Error('invalid Expression'); | |
| } | |
| } | |
| if (nstack.length > 1) { | |
| throw new Error('invalid Expression (parity)'); | |
| } | |
| // Explicitly return zero to avoid test issues caused by -0 | |
| return nstack[0] === 0 ? 0 : resolveExpression(nstack[0], values); | |
| } | |
| function createExpressionEvaluator(token, expr, values) { | |
| if (isExpressionEvaluator(token)) return token; | |
| return { | |
| type: IEXPREVAL, | |
| value: function (scope) { | |
| return evaluate(token.value, expr, scope); | |
| } | |
| }; | |
| } | |
| function isExpressionEvaluator(n) { | |
| return n && n.type === IEXPREVAL; | |
| } | |
| function resolveExpression(n, values) { | |
| return isExpressionEvaluator(n) ? n.value(values) : n; | |
| } | |
| function expressionToString(tokens, toJS) { | |
| var nstack = []; | |
| var n1, n2, n3; | |
| var f, args, argCount; | |
| 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 if (Array.isArray(item.value)) { | |
| nstack.push('[' + item.value.map(escapeValue).join(', ') + ']'); | |
| } 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('(function(a,b){ return Array.isArray(a) && Array.isArray(b) ? a.concat(b) : String(a) + String(b); }((' + n1 + '),(' + n2 + ')))'); | |
| } else if (f === '==') { | |
| nstack.push('(' + n1 + ' === ' + n2 + ')'); | |
| } else if (f === '!=') { | |
| nstack.push('(' + n1 + ' !== ' + n2 + ')'); | |
| } else if (f === '[') { | |
| nstack.push(n1 + '[(' + n2 + ') | 0]'); | |
| } else { | |
| nstack.push('(' + n1 + ' ' + f + ' ' + n2 + ')'); | |
| } | |
| } else { | |
| if (f === '[') { | |
| nstack.push(n1 + '[' + 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 || type === IVARNAME) { | |
| 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) { | |
| argCount = item.value; | |
| args = []; | |
| while (argCount-- > 0) { | |
| args.unshift(nstack.pop()); | |
| } | |
| f = nstack.pop(); | |
| nstack.push(f + '(' + args.join(', ') + ')'); | |
| } else if (type === IFUNDEF) { | |
| n2 = nstack.pop(); | |
| argCount = item.value; | |
| args = []; | |
| while (argCount-- > 0) { | |
| args.unshift(nstack.pop()); | |
| } | |
| n1 = nstack.pop(); | |
| if (toJS) { | |
| nstack.push('(' + n1 + ' = function(' + args.join(', ') + ') { return ' + n2 + ' })'); | |
| } else { | |
| nstack.push('(' + n1 + '(' + args.join(', ') + ') = ' + n2 + ')'); | |
| } | |
| } else if (type === IMEMBER) { | |
| n1 = nstack.pop(); | |
| nstack.push(n1 + '.' + item.value); | |
| } else if (type === IARRAY) { | |
| argCount = item.value; | |
| args = []; | |
| while (argCount-- > 0) { | |
| args.unshift(nstack.pop()); | |
| } | |
| nstack.push('[' + args.join(', ') + ']'); | |
| } else if (type === IEXPR) { | |
| nstack.push('(' + expressionToString(item.value, toJS) + ')'); | |
| } else if (type === IENDSTATEMENT) ; else { | |
| throw new Error('invalid Expression'); | |
| } | |
| } | |
| if (nstack.length > 1) { | |
| if (toJS) { | |
| nstack = [ nstack.join(',') ]; | |
| } else { | |
| nstack = [ nstack.join(';') ]; | |
| } | |
| } | |
| 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 || item.type === IVARNAME) { | |
| if (!withMembers && !contains(symbols, item.value)) { | |
| 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 TNUMBER = 'TNUMBER'; | |
| var TSTRING = 'TSTRING'; | |
| var TPAREN = 'TPAREN'; | |
| var TBRACKET = 'TBRACKET'; | |
| var TCOMMA = 'TCOMMA'; | |
| var TNAME = 'TNAME'; | |
| var TSEMICOLON = 'TSEMICOLON'; | |
| 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.consts = parser.consts; | |
| this.expression = expression; | |
| this.savedPosition = 0; | |
| this.savedCurrent = null; | |
| this.options = parser.options; | |
| this.parser = parser; | |
| } | |
| 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.isBracket() || | |
| this.isComma() || | |
| this.isSemicolon() || | |
| this.isNamedOp() || | |
| 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.isBracket = function () { | |
| var c = this.expression.charAt(this.pos); | |
| if ((c === '[' || c === ']') && this.isOperatorEnabled('[')) { | |
| this.current = this.newToken(TBRACKET, 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.isSemicolon = function () { | |
| var c = this.expression.charAt(this.pos); | |
| if (c === ';') { | |
| this.current = this.newToken(TSEMICOLON, ';'); | |
| 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.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 { | |
| this.current = this.newToken(TOP, c); | |
| } | |
| } 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; | |
| } | |
| }; | |
| TokenStream.prototype.isOperatorEnabled = function (op) { | |
| return this.parser.isOperatorEnabled(op); | |
| }; | |
| 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) { | |
| var unaryOps = this.tokens.unaryOps; | |
| function isPrefixOperator(token) { | |
| return token.value in unaryOps; | |
| } | |
| if (this.accept(TNAME) || this.accept(TOP, isPrefixOperator)) { | |
| 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 if (this.accept(TBRACKET, '[')) { | |
| if (this.accept(TBRACKET, ']')) { | |
| instr.push(new Instruction(IARRAY, 0)); | |
| } else { | |
| var argCount = this.parseArrayList(instr); | |
| instr.push(new Instruction(IARRAY, argCount)); | |
| } | |
| } else { | |
| throw new Error('unexpected ' + this.nextToken); | |
| } | |
| }; | |
| ParserState.prototype.parseExpression = function (instr) { | |
| var exprInstr = []; | |
| if (this.parseUntilEndStatement(instr, exprInstr)) { | |
| return; | |
| } | |
| this.parseVariableAssignmentExpression(exprInstr); | |
| if (this.parseUntilEndStatement(instr, exprInstr)) { | |
| return; | |
| } | |
| this.pushExpression(instr, exprInstr); | |
| }; | |
| ParserState.prototype.pushExpression = function (instr, exprInstr) { | |
| for (var i = 0, len = exprInstr.length; i < len; i++) { | |
| instr.push(exprInstr[i]); | |
| } | |
| }; | |
| ParserState.prototype.parseUntilEndStatement = function (instr, exprInstr) { | |
| if (!this.accept(TSEMICOLON)) return false; | |
| if (this.nextToken && this.nextToken.type !== TEOF && !(this.nextToken.type === TPAREN && this.nextToken.value === ')')) { | |
| exprInstr.push(new Instruction(IENDSTATEMENT)); | |
| } | |
| if (this.nextToken.type !== TEOF) { | |
| this.parseExpression(exprInstr); | |
| } | |
| instr.push(new Instruction(IEXPR, exprInstr)); | |
| return true; | |
| }; | |
| ParserState.prototype.parseArrayList = function (instr) { | |
| var argCount = 0; | |
| while (!this.accept(TBRACKET, ']')) { | |
| this.parseExpression(instr); | |
| ++argCount; | |
| while (this.accept(TCOMMA)) { | |
| this.parseExpression(instr); | |
| ++argCount; | |
| } | |
| } | |
| return argCount; | |
| }; | |
| ParserState.prototype.parseVariableAssignmentExpression = function (instr) { | |
| this.parseConditionalExpression(instr); | |
| while (this.accept(TOP, '=')) { | |
| var varName = instr.pop(); | |
| var varValue = []; | |
| var lastInstrIndex = instr.length - 1; | |
| if (varName.type === IFUNCALL) { | |
| if (!this.tokens.isOperatorEnabled('()=')) { | |
| throw new Error('function definition is not permitted'); | |
| } | |
| for (var i = 0, len = varName.value + 1; i < len; i++) { | |
| var index = lastInstrIndex - i; | |
| if (instr[index].type === IVAR) { | |
| instr[index] = new Instruction(IVARNAME, instr[index].value); | |
| } | |
| } | |
| this.parseVariableAssignmentExpression(varValue); | |
| instr.push(new Instruction(IEXPR, varValue)); | |
| instr.push(new Instruction(IFUNDEF, varName.value)); | |
| continue; | |
| } | |
| if (varName.type !== IVAR && varName.type !== IMEMBER) { | |
| throw new Error('expected variable for assignment'); | |
| } | |
| this.parseVariableAssignmentExpression(varValue); | |
| instr.push(new Instruction(IVARNAME, varName.value)); | |
| instr.push(new Instruction(IEXPR, varValue)); | |
| instr.push(binaryInstruction('=')); | |
| } | |
| }; | |
| 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 !== '+') { | |
| if (this.nextToken.type === TPAREN && this.nextToken.value === '(') { | |
| this.restore(); | |
| this.parseExponential(instr); | |
| return; | |
| } else if (this.nextToken.type === TSEMICOLON || this.nextToken.type === TCOMMA || this.nextToken.type === TEOF || (this.nextToken.type === TPAREN && this.nextToken.value === ')')) { | |
| this.restore(); | |
| this.parseAtom(instr); | |
| return; | |
| } | |
| } | |
| 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.parseFunctionCall(instr); | |
| while (this.accept(TOP, '!')) { | |
| instr.push(unaryInstruction('!')); | |
| } | |
| }; | |
| 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, '.') || this.accept(TBRACKET, '[')) { | |
| var op = this.current; | |
| if (op.value === '.') { | |
| if (!this.allowMemberAccess) { | |
| throw new Error('unexpected ".", member access is not permitted'); | |
| } | |
| this.expect(TNAME); | |
| instr.push(new Instruction(IMEMBER, this.current.value)); | |
| } else if (op.value === '[') { | |
| if (!this.tokens.isOperatorEnabled('[')) { | |
| throw new Error('unexpected "[]", arrays are disabled'); | |
| } | |
| this.parseExpression(instr); | |
| this.expect(TBRACKET, ']'); | |
| instr.push(binaryInstruction('[')); | |
| } else { | |
| throw new Error('unexpected symbol: ' + op.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) { | |
| if (Array.isArray(a) && Array.isArray(b)) { | |
| return a.concat(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 stringOrArrayLength(s) { | |
| if (Array.isArray(s)) { | |
| return s.length; | |
| } | |
| 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 setVar(name, value, variables) { | |
| if (variables) variables[name] = value; | |
| return value; | |
| } | |
| function arrayIndex(array, index) { | |
| return array[index | 0]; | |
| } | |
| function max(array) { | |
| if (arguments.length === 1 && Array.isArray(array)) { | |
| return Math.max.apply(Math, array); | |
| } else { | |
| return Math.max.apply(Math, arguments); | |
| } | |
| } | |
| function min(array) { | |
| if (arguments.length === 1 && Array.isArray(array)) { | |
| return Math.min.apply(Math, array); | |
| } else { | |
| return Math.min.apply(Math, arguments); | |
| } | |
| } | |
| function arrayMap(f, a) { | |
| if (typeof f !== 'function') { | |
| throw new Error('First argument to map is not a function'); | |
| } | |
| if (!Array.isArray(a)) { | |
| throw new Error('Second argument to map is not an array'); | |
| } | |
| return a.map(function (x, i) { | |
| return f(x, i); | |
| }); | |
| } | |
| function arrayFold(f, init, a) { | |
| if (typeof f !== 'function') { | |
| throw new Error('First argument to fold is not a function'); | |
| } | |
| if (!Array.isArray(a)) { | |
| throw new Error('Second argument to fold is not an array'); | |
| } | |
| return a.reduce(function (acc, x, i) { | |
| return f(acc, x, i); | |
| }, init); | |
| } | |
| function arrayFilter(f, a) { | |
| if (typeof f !== 'function') { | |
| throw new Error('First argument to filter is not a function'); | |
| } | |
| if (!Array.isArray(a)) { | |
| throw new Error('Second argument to filter is not an array'); | |
| } | |
| return a.filter(function (x, i) { | |
| return f(x, i); | |
| }); | |
| } | |
| function stringOrArrayIndexOf(target, s) { | |
| if (!(Array.isArray(s) || typeof s === 'string')) { | |
| throw new Error('Second argument to indexOf is not a string or array'); | |
| } | |
| return s.indexOf(target); | |
| } | |
| function arrayJoin(sep, a) { | |
| if (!Array.isArray(a)) { | |
| throw new Error('Second argument to join is not an array'); | |
| } | |
| return a.join(sep); | |
| } | |
| function sign(x) { | |
| return ((x > 0) - (x < 0)) || +x; | |
| } | |
| var ONE_THIRD = 1/3; | |
| function cbrt(x) { | |
| return x < 0 ? -Math.pow(-x, ONE_THIRD) : Math.pow(x, ONE_THIRD); | |
| } | |
| function expm1(x) { | |
| return Math.exp(x) - 1; | |
| } | |
| function log1p(x) { | |
| return Math.log(1 + x); | |
| } | |
| function log2(x) { | |
| return Math.log(x) / Math.LN2; | |
| } | |
| 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, | |
| cbrt: Math.cbrt || cbrt, | |
| log: Math.log, | |
| log2: Math.log2 || log2, | |
| ln: Math.log, | |
| lg: Math.log10 || log10, | |
| log10: Math.log10 || log10, | |
| expm1: Math.expm1 || expm1, | |
| log1p: Math.log1p || log1p, | |
| abs: Math.abs, | |
| ceil: Math.ceil, | |
| floor: Math.floor, | |
| round: Math.round, | |
| trunc: Math.trunc || trunc, | |
| '-': neg, | |
| '+': Number, | |
| exp: Math.exp, | |
| not: not, | |
| length: stringOrArrayLength, | |
| '!': factorial, | |
| sign: Math.sign || sign | |
| }; | |
| this.binaryOps = { | |
| '+': add, | |
| '-': sub, | |
| '*': mul, | |
| '/': div, | |
| '%': mod, | |
| '^': Math.pow, | |
| '||': concat, | |
| '==': equal, | |
| '!=': notEqual, | |
| '>': greaterThan, | |
| '<': lessThan, | |
| '>=': greaterThanEqual, | |
| '<=': lessThanEqual, | |
| and: andOperator, | |
| or: orOperator, | |
| 'in': inOperator, | |
| '=': setVar, | |
| '[': arrayIndex | |
| }; | |
| this.ternaryOps = { | |
| '?': condition | |
| }; | |
| this.functions = { | |
| random: random, | |
| fac: factorial, | |
| min: min, | |
| max: max, | |
| hypot: Math.hypot || hypot, | |
| pyt: Math.hypot || hypot, // backward compat | |
| pow: Math.pow, | |
| atan2: Math.atan2, | |
| 'if': condition, | |
| gamma: gamma, | |
| roundTo: roundTo, | |
| map: arrayMap, | |
| fold: arrayFold, | |
| filter: arrayFilter, | |
| indexOf: stringOrArrayIndexOf, | |
| join: arrayJoin | |
| }; | |
| 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); | |
| }; | |
| var optionNameMap = { | |
| '+': 'add', | |
| '-': 'subtract', | |
| '*': 'multiply', | |
| '/': 'divide', | |
| '%': 'remainder', | |
| '^': 'power', | |
| '!': 'factorial', | |
| '<': 'comparison', | |
| '>': 'comparison', | |
| '<=': 'comparison', | |
| '>=': 'comparison', | |
| '==': 'comparison', | |
| '!=': 'comparison', | |
| '||': 'concatenate', | |
| 'and': 'logical', | |
| 'or': 'logical', | |
| 'not': 'logical', | |
| '?': 'conditional', | |
| ':': 'conditional', | |
| '=': 'assignment', | |
| '[': 'array', | |
| '()=': 'fndef' | |
| }; | |
| function getOptionName(op) { | |
| return optionNameMap.hasOwnProperty(op) ? optionNameMap[op] : op; | |
| } | |
| Parser.prototype.isOperatorEnabled = function (op) { | |
| var optionName = getOptionName(op); | |
| var operators = this.options.operators || {}; | |
| return !(optionName in operators) || !!operators[optionName]; | |
| }; | |
| /*! | |
| 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. | |
| */ | |
| // Backwards compatibility | |
| var index = { | |
| Parser: Parser, | |
| Expression: Expression | |
| }; | |
| exports.Expression = Expression; | |
| exports.Parser = Parser; | |
| exports.default = index; | |
| Object.defineProperty(exports, '__esModule', { value: true }); | |
| })); | |