Spaces:
Build error
Build error
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([email protected]) | |
http://www.undefined.ch/mparser/index.html | |
Ported to JavaScript and modified by Matthew Crumley ([email protected], 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 | |
}; | |
export default index; | |
export { Expression, Parser }; | |