From 62ff0850e2792820fb017419491cb28ba005e5ab Mon Sep 17 00:00:00 2001 From: Daniel Mittelman Date: Tue, 14 Aug 2018 18:40:39 +0300 Subject: [PATCH 1/5] Changed how expressions are simplified to eliminate constant repetitions --- src/simplify.js | 104 ++++++++++++++++++++++++--------------------- test/expression.js | 4 +- 2 files changed, 57 insertions(+), 51 deletions(-) diff --git a/src/simplify.js b/src/simplify.js index e68845c..1cf6b2f 100644 --- a/src/simplify.js +++ b/src/simplify.js @@ -1,57 +1,63 @@ import { Instruction, INUMBER, IOP1, IOP2, IOP3, IVAR, IEXPR, IMEMBER } from './instruction'; export default function simplify(tokens, unaryOps, binaryOps, ternaryOps, values) { - var nstack = []; - var newexpression = []; - var n1, n2, n3; - var f; - for (var i = 0; i < tokens.length; i++) { - var item = tokens[i]; - var type = item.type; - if (type === INUMBER) { - nstack.push(item); - } else if (type === IVAR && values.hasOwnProperty(item.value)) { - item = new Instruction(INUMBER, values[item.value]); - nstack.push(item); - } else if (type === IOP2 && nstack.length > 1) { - n2 = nstack.pop(); - n1 = nstack.pop(); - f = binaryOps[item.value]; - item = new Instruction(INUMBER, f(n1.value, n2.value)); - nstack.push(item); - } else if (type === IOP3 && nstack.length > 2) { - n3 = nstack.pop(); - n2 = nstack.pop(); - n1 = nstack.pop(); - if (item.value === '?') { - nstack.push(n1.value ? n2.value : n3.value); - } else { - f = ternaryOps[item.value]; - item = new Instruction(INUMBER, f(n1.value, n2.value, n3.value)); + var nstack = []; + var newexpression = []; + var n1, n2, n3; + var f; + for (var i = 0; i < tokens.length; i++) { + var item = tokens[i]; + var type = item.type; + if (type === INUMBER) { nstack.push(item); + } else if (type === IVAR && values.hasOwnProperty(item.value)) { + item = new Instruction(INUMBER, values[item.value]); + nstack.push(item); + } else if (type === IOP2 && nstack.length > 1) { + n2 = nstack.pop(); + n1 = nstack.pop(); + f = binaryOps[item.value]; + item = new Instruction(INUMBER, f(n1.value, n2.value)); + nstack.push(item); + } else if (type === IOP3 && nstack.length > 2) { + n3 = nstack.pop(); + n2 = nstack.pop(); + n1 = nstack.pop(); + if (item.value === '?') { + nstack.push(n1.value ? n2.value : n3.value); + } else { + f = ternaryOps[item.value]; + item = new Instruction(INUMBER, f(n1.value, n2.value, n3.value)); + nstack.push(item); + } + } else if (type === IOP1 && nstack.length > 0) { + n1 = nstack.pop(); + f = unaryOps[item.value]; + item = new Instruction(INUMBER, f(n1.value)); + nstack.push(item); + } else if (type === IEXPR) { + var simplified = simplify(item.value, unaryOps, binaryOps, ternaryOps, values); + if(simplified.length === 1 && simplified[0].type === INUMBER) { + nstack.push(simplified[0]); } - } else if (type === IOP1 && nstack.length > 0) { - n1 = nstack.pop(); - f = unaryOps[item.value]; - item = new Instruction(INUMBER, f(n1.value)); - nstack.push(item); - } else if (type === IEXPR) { - while (nstack.length > 0) { - newexpression.push(nstack.shift()); - } - newexpression.push(new Instruction(IEXPR, simplify(item.value, unaryOps, binaryOps, ternaryOps, values))); - } else if (type === IMEMBER && nstack.length > 0) { - n1 = nstack.pop(); - nstack.push(new Instruction(INUMBER, n1.value[item.value])); - } else { - while (nstack.length > 0) { - newexpression.push(nstack.shift()); + else { + while (nstack.length > 0) { + newexpression.push(nstack.shift()); + } + newexpression.push(new Instruction(IEXPR, simplified)); + } + } else if (type === IMEMBER && nstack.length > 0) { + n1 = nstack.pop(); + nstack.push(new Instruction(INUMBER, n1.value[item.value])); + } else { + while (nstack.length > 0) { + newexpression.push(nstack.shift()); + } + newexpression.push(item); } - newexpression.push(item); } + while (nstack.length > 0) { + newexpression.push(nstack.shift()); + } + return newexpression; } - while (nstack.length > 0) { - newexpression.push(nstack.shift()); - } - return newexpression; -} diff --git a/test/expression.js b/test/expression.js index 51d24f1..d6a8d83 100644 --- a/test/expression.js +++ b/test/expression.js @@ -172,11 +172,11 @@ describe('Expression', function () { }); it('x ? (y + 1) : z', function () { - assert.strictEqual(Parser.parse('x ? (y + 1) : z').simplify({ y: 2 }).toString(), '(x ? (3) : (z))'); + assert.strictEqual(Parser.parse('x ? (y + 1) : z').simplify({ y: 2 }).toString(), '(x ? 3 : (z))'); }); it('x ? y : (z * 4)', function () { - assert.strictEqual(Parser.parse('x ? y : (z * 4)').simplify({ z: 3 }).toString(), '(x ? (y) : (12))'); + assert.strictEqual(Parser.parse('x ? y : (z * 4)').simplify({ z: 3 }).toString(), '(x ? (y) : 12)'); }); }); From ddc76fea958b2b83b92b60155c3a45f7805605c9 Mon Sep 17 00:00:00 2001 From: Daniel Mittelman Date: Wed, 15 Aug 2018 09:53:24 +0300 Subject: [PATCH 2/5] Built dist --- dist/bundle.js | 1481 ++++++++++++++++++++++++++++++++++++++++++++ dist/bundle.min.js | 1 + package-lock.json | 2 +- 3 files changed, 1483 insertions(+), 1 deletion(-) create mode 100644 dist/bundle.js create mode 100644 dist/bundle.min.js diff --git a/dist/bundle.js b/dist/bundle.js new file mode 100644 index 0000000..b5e4f6e --- /dev/null +++ b/dist/bundle.js @@ -0,0 +1,1481 @@ +(function (global, factory) { + typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : + typeof define === 'function' && define.amd ? define(factory) : + (global.exprEval = factory()); +}(this, (function () { 'use strict'; + + var INUMBER = 'INUMBER'; + var IOP1 = 'IOP1'; + var IOP2 = 'IOP2'; + var IOP3 = 'IOP3'; + var IVAR = 'IVAR'; + var IFUNCALL = 'IFUNCALL'; + var IEXPR = 'IEXPR'; + var IMEMBER = 'IMEMBER'; + + function Instruction(type, value) { + this.type = type; + this.value = (value !== undefined && value !== null) ? value : 0; + } + + Instruction.prototype.toString = function () { + switch (this.type) { + case INUMBER: + case IOP1: + case IOP2: + case IOP3: + case IVAR: + return this.value; + case IFUNCALL: + return 'CALL ' + this.value; + case IMEMBER: + return '.' + this.value; + default: + return 'Invalid Instruction'; + } + }; + + function unaryInstruction(value) { + return new Instruction(IOP1, value); + } + + function binaryInstruction(value) { + return new Instruction(IOP2, value); + } + + function ternaryInstruction(value) { + return new Instruction(IOP3, value); + } + + function simplify(tokens, unaryOps, binaryOps, ternaryOps, values) { + var nstack = []; + var newexpression = []; + var n1, n2, n3; + var f; + for (var i = 0; i < tokens.length; i++) { + var item = tokens[i]; + var type = item.type; + if (type === INUMBER) { + nstack.push(item); + } else if (type === IVAR && values.hasOwnProperty(item.value)) { + item = new Instruction(INUMBER, values[item.value]); + nstack.push(item); + } else if (type === IOP2 && nstack.length > 1) { + n2 = nstack.pop(); + n1 = nstack.pop(); + f = binaryOps[item.value]; + item = new Instruction(INUMBER, f(n1.value, n2.value)); + nstack.push(item); + } else if (type === IOP3 && nstack.length > 2) { + n3 = nstack.pop(); + n2 = nstack.pop(); + n1 = nstack.pop(); + if (item.value === '?') { + nstack.push(n1.value ? n2.value : n3.value); + } else { + f = ternaryOps[item.value]; + item = new Instruction(INUMBER, f(n1.value, n2.value, n3.value)); + nstack.push(item); + } + } else if (type === IOP1 && nstack.length > 0) { + n1 = nstack.pop(); + f = unaryOps[item.value]; + item = new Instruction(INUMBER, f(n1.value)); + nstack.push(item); + } else if (type === IEXPR) { + var simplified = simplify(item.value, unaryOps, binaryOps, ternaryOps, values); + if(simplified.length === 1 && simplified[0].type === INUMBER) { + nstack.push(simplified[0]); + } + else { + while (nstack.length > 0) { + newexpression.push(nstack.shift()); + } + newexpression.push(new Instruction(IEXPR, simplified)); + } + } else if (type === IMEMBER && nstack.length > 0) { + n1 = nstack.pop(); + nstack.push(new Instruction(INUMBER, n1.value[item.value])); + } else { + while (nstack.length > 0) { + newexpression.push(nstack.shift()); + } + newexpression.push(item); + } + } + while (nstack.length > 0) { + newexpression.push(nstack.shift()); + } + return newexpression; + } + + function substitute(tokens, variable, expr) { + var newexpression = []; + for (var i = 0; i < tokens.length; i++) { + var item = tokens[i]; + var type = item.type; + if (type === IVAR && item.value === variable) { + for (var j = 0; j < expr.tokens.length; j++) { + var expritem = expr.tokens[j]; + var replitem; + if (expritem.type === IOP1) { + replitem = unaryInstruction(expritem.value); + } else if (expritem.type === IOP2) { + replitem = binaryInstruction(expritem.value); + } else if (expritem.type === IOP3) { + replitem = ternaryInstruction(expritem.value); + } else { + replitem = new Instruction(expritem.type, expritem.value); + } + newexpression.push(replitem); + } + } else if (type === IEXPR) { + newexpression.push(new Instruction(IEXPR, substitute(item.value, variable, expr))); + } else { + newexpression.push(item); + } + } + return newexpression; + } + + function evaluate(tokens, expr, values) { + var nstack = []; + var n1, n2, n3; + var f; + for (var i = 0; i < tokens.length; i++) { + var item = tokens[i]; + var type = item.type; + if (type === INUMBER) { + nstack.push(item.value); + } else if (type === IOP2) { + n2 = nstack.pop(); + n1 = nstack.pop(); + if (item.value === 'and') { + nstack.push(n1 ? !!evaluate(n2, expr, values) : false); + } else if (item.value === 'or') { + nstack.push(n1 ? true : !!evaluate(n2, expr, values)); + } else { + f = expr.binaryOps[item.value]; + nstack.push(f(n1, n2)); + } + } else if (type === IOP3) { + n3 = nstack.pop(); + n2 = nstack.pop(); + n1 = nstack.pop(); + if (item.value === '?') { + nstack.push(evaluate(n1 ? n2 : n3, expr, values)); + } else { + f = expr.ternaryOps[item.value]; + nstack.push(f(n1, n2, n3)); + } + } else if (type === IVAR) { + if (item.value in expr.functions) { + nstack.push(expr.functions[item.value]); + } else { + var v = values[item.value]; + if (v !== undefined) { + nstack.push(v); + } else { + throw new Error('undefined variable: ' + item.value); + } + } + } else if (type === IOP1) { + n1 = nstack.pop(); + f = expr.unaryOps[item.value]; + nstack.push(f(n1)); + } else if (type === IFUNCALL) { + var argCount = item.value; + var args = []; + while (argCount-- > 0) { + args.unshift(nstack.pop()); + } + f = nstack.pop(); + if (f.apply && f.call) { + nstack.push(f.apply(undefined, args)); + } else { + throw new Error(f + ' is not a function'); + } + } else if (type === IEXPR) { + nstack.push(item.value); + } else if (type === IMEMBER) { + n1 = nstack.pop(); + nstack.push(n1[item.value]); + } else { + throw new Error('invalid Expression'); + } + } + if (nstack.length > 1) { + throw new Error('invalid Expression (parity)'); + } + return nstack[0]; + } + + function expressionToString(tokens, toJS) { + var nstack = []; + var n1, n2, n3; + var f; + for (var i = 0; i < tokens.length; i++) { + var item = tokens[i]; + var type = item.type; + if (type === INUMBER) { + if (typeof item.value === 'number' && item.value < 0) { + nstack.push('(' + item.value + ')'); + } else { + nstack.push(escapeValue(item.value)); + } + } else if (type === IOP2) { + n2 = nstack.pop(); + n1 = nstack.pop(); + f = item.value; + if (toJS) { + if (f === '^') { + nstack.push('Math.pow(' + n1 + ', ' + n2 + ')'); + } else if (f === 'and') { + nstack.push('(!!' + n1 + ' && !!' + n2 + ')'); + } else if (f === 'or') { + nstack.push('(!!' + n1 + ' || !!' + n2 + ')'); + } else if (f === '||') { + nstack.push('(String(' + n1 + ') + String(' + n2 + '))'); + } else if (f === '==') { + nstack.push('(' + n1 + ' === ' + n2 + ')'); + } else if (f === '!=') { + nstack.push('(' + n1 + ' !== ' + n2 + ')'); + } else { + nstack.push('(' + n1 + ' ' + f + ' ' + n2 + ')'); + } + } else { + nstack.push('(' + n1 + ' ' + f + ' ' + n2 + ')'); + } + } else if (type === IOP3) { + n3 = nstack.pop(); + n2 = nstack.pop(); + n1 = nstack.pop(); + f = item.value; + if (f === '?') { + nstack.push('(' + n1 + ' ? ' + n2 + ' : ' + n3 + ')'); + } else { + throw new Error('invalid Expression'); + } + } else if (type === IVAR) { + nstack.push(item.value); + } else if (type === IOP1) { + n1 = nstack.pop(); + f = item.value; + if (f === '-' || f === '+') { + nstack.push('(' + f + n1 + ')'); + } else if (toJS) { + if (f === 'not') { + nstack.push('(' + '!' + n1 + ')'); + } else if (f === '!') { + nstack.push('fac(' + n1 + ')'); + } else { + nstack.push(f + '(' + n1 + ')'); + } + } else if (f === '!') { + nstack.push('(' + n1 + '!)'); + } else { + nstack.push('(' + f + ' ' + n1 + ')'); + } + } else if (type === IFUNCALL) { + var argCount = item.value; + var args = []; + while (argCount-- > 0) { + args.unshift(nstack.pop()); + } + f = nstack.pop(); + nstack.push(f + '(' + args.join(', ') + ')'); + } else if (type === IMEMBER) { + n1 = nstack.pop(); + nstack.push(n1 + '.' + item.value); + } else if (type === IEXPR) { + nstack.push('(' + expressionToString(item.value, toJS) + ')'); + } else { + throw new Error('invalid Expression'); + } + } + if (nstack.length > 1) { + throw new Error('invalid Expression (parity)'); + } + return String(nstack[0]); + } + + function escapeValue(v) { + if (typeof v === 'string') { + return JSON.stringify(v).replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029'); + } + return v; + } + + function contains(array, obj) { + for (var i = 0; i < array.length; i++) { + if (array[i] === obj) { + return true; + } + } + return false; + } + + function getSymbols(tokens, symbols, options) { + options = options || {}; + var withMembers = !!options.withMembers; + var prevVar = null; + + for (var i = 0; i < tokens.length; i++) { + var item = tokens[i]; + if (item.type === IVAR && !contains(symbols, item.value)) { + if (!withMembers) { + symbols.push(item.value); + } else if (prevVar !== null) { + if (!contains(symbols, prevVar)) { + symbols.push(prevVar); + } + prevVar = item.value; + } else { + prevVar = item.value; + } + } else if (item.type === IMEMBER && withMembers && prevVar !== null) { + prevVar += '.' + item.value; + } else if (item.type === IEXPR) { + getSymbols(item.value, symbols, options); + } else if (prevVar !== null) { + if (!contains(symbols, prevVar)) { + symbols.push(prevVar); + } + prevVar = null; + } + } + + if (prevVar !== null && !contains(symbols, prevVar)) { + symbols.push(prevVar); + } + } + + function Expression(tokens, parser) { + this.tokens = tokens; + this.parser = parser; + this.unaryOps = parser.unaryOps; + this.binaryOps = parser.binaryOps; + this.ternaryOps = parser.ternaryOps; + this.functions = parser.functions; + } + + Expression.prototype.simplify = function (values) { + values = values || {}; + return new Expression(simplify(this.tokens, this.unaryOps, this.binaryOps, this.ternaryOps, values), this.parser); + }; + + Expression.prototype.substitute = function (variable, expr) { + if (!(expr instanceof Expression)) { + expr = this.parser.parse(String(expr)); + } + + return new Expression(substitute(this.tokens, variable, expr), this.parser); + }; + + Expression.prototype.evaluate = function (values) { + values = values || {}; + return evaluate(this.tokens, this, values); + }; + + Expression.prototype.toString = function () { + return expressionToString(this.tokens, false); + }; + + Expression.prototype.symbols = function (options) { + options = options || {}; + var vars = []; + getSymbols(this.tokens, vars, options); + return vars; + }; + + Expression.prototype.variables = function (options) { + options = options || {}; + var vars = []; + getSymbols(this.tokens, vars, options); + var functions = this.functions; + return vars.filter(function (name) { + return !(name in functions); + }); + }; + + Expression.prototype.toJSFunction = function (param, variables) { + var expr = this; + var f = new Function(param, 'with(this.functions) with (this.ternaryOps) with (this.binaryOps) with (this.unaryOps) { return ' + expressionToString(this.simplify(variables).tokens, true) + '; }'); // eslint-disable-line no-new-func + return function () { + return f.apply(expr, arguments); + }; + }; + + var TEOF = 'TEOF'; + var TOP = 'TOP'; + var TNUMBER = 'TNUMBER'; + var TSTRING = 'TSTRING'; + var TPAREN = 'TPAREN'; + var TCOMMA = 'TCOMMA'; + var TNAME = 'TNAME'; + + function Token(type, value, index) { + this.type = type; + this.value = value; + this.index = index; + } + + Token.prototype.toString = function () { + return this.type + ': ' + this.value; + }; + + function TokenStream(parser, expression) { + this.pos = 0; + this.current = null; + this.unaryOps = parser.unaryOps; + this.binaryOps = parser.binaryOps; + this.ternaryOps = parser.ternaryOps; + this.consts = parser.consts; + this.expression = expression; + this.savedPosition = 0; + this.savedCurrent = null; + this.options = parser.options; + } + + TokenStream.prototype.newToken = function (type, value, pos) { + return new Token(type, value, pos != null ? pos : this.pos); + }; + + TokenStream.prototype.save = function () { + this.savedPosition = this.pos; + this.savedCurrent = this.current; + }; + + TokenStream.prototype.restore = function () { + this.pos = this.savedPosition; + this.current = this.savedCurrent; + }; + + TokenStream.prototype.next = function () { + if (this.pos >= this.expression.length) { + return this.newToken(TEOF, 'EOF'); + } + + if (this.isWhitespace() || this.isComment()) { + return this.next(); + } else if (this.isRadixInteger() || + this.isNumber() || + this.isOperator() || + this.isString() || + this.isParen() || + this.isComma() || + this.isNamedOp() || + this.isConst() || + this.isName()) { + return this.current; + } else { + this.parseError('Unknown character "' + this.expression.charAt(this.pos) + '"'); + } + }; + + TokenStream.prototype.isString = function () { + var r = false; + var startPos = this.pos; + var quote = this.expression.charAt(startPos); + + if (quote === '\'' || quote === '"') { + var index = this.expression.indexOf(quote, startPos + 1); + while (index >= 0 && this.pos < this.expression.length) { + this.pos = index + 1; + if (this.expression.charAt(index - 1) !== '\\') { + var rawString = this.expression.substring(startPos + 1, index); + this.current = this.newToken(TSTRING, this.unescape(rawString), startPos); + r = true; + break; + } + index = this.expression.indexOf(quote, index + 1); + } + } + return r; + }; + + TokenStream.prototype.isParen = function () { + var c = this.expression.charAt(this.pos); + if (c === '(' || c === ')') { + this.current = this.newToken(TPAREN, c); + this.pos++; + return true; + } + return false; + }; + + TokenStream.prototype.isComma = function () { + var c = this.expression.charAt(this.pos); + if (c === ',') { + this.current = this.newToken(TCOMMA, ','); + this.pos++; + return true; + } + return false; + }; + + TokenStream.prototype.isConst = function () { + var startPos = this.pos; + var i = startPos; + for (; i < this.expression.length; i++) { + var c = this.expression.charAt(i); + if (c.toUpperCase() === c.toLowerCase()) { + if (i === this.pos || (c !== '_' && c !== '.' && (c < '0' || c > '9'))) { + break; + } + } + } + if (i > startPos) { + var str = this.expression.substring(startPos, i); + if (str in this.consts) { + this.current = this.newToken(TNUMBER, this.consts[str]); + this.pos += str.length; + return true; + } + } + return false; + }; + + TokenStream.prototype.isNamedOp = function () { + var startPos = this.pos; + var i = startPos; + for (; i < this.expression.length; i++) { + var c = this.expression.charAt(i); + if (c.toUpperCase() === c.toLowerCase()) { + if (i === this.pos || (c !== '_' && (c < '0' || c > '9'))) { + break; + } + } + } + if (i > startPos) { + var str = this.expression.substring(startPos, i); + if (this.isOperatorEnabled(str) && (str in this.binaryOps || str in this.unaryOps || str in this.ternaryOps)) { + this.current = this.newToken(TOP, str); + this.pos += str.length; + return true; + } + } + return false; + }; + + TokenStream.prototype.isName = function () { + var startPos = this.pos; + var i = startPos; + var hasLetter = false; + for (; i < this.expression.length; i++) { + var c = this.expression.charAt(i); + if (c.toUpperCase() === c.toLowerCase()) { + if (i === this.pos && (c === '$' || c === '_')) { + if (c === '_') { + hasLetter = true; + } + continue; + } else if (i === this.pos || !hasLetter || (c !== '_' && (c < '0' || c > '9'))) { + break; + } + } else { + hasLetter = true; + } + } + if (hasLetter) { + var str = this.expression.substring(startPos, i); + this.current = this.newToken(TNAME, str); + this.pos += str.length; + return true; + } + return false; + }; + + TokenStream.prototype.isWhitespace = function () { + var r = false; + var c = this.expression.charAt(this.pos); + while (c === ' ' || c === '\t' || c === '\n' || c === '\r') { + r = true; + this.pos++; + if (this.pos >= this.expression.length) { + break; + } + c = this.expression.charAt(this.pos); + } + return r; + }; + + var codePointPattern = /^[0-9a-f]{4}$/i; + + TokenStream.prototype.unescape = function (v) { + var index = v.indexOf('\\'); + if (index < 0) { + return v; + } + + var buffer = v.substring(0, index); + while (index >= 0) { + var c = v.charAt(++index); + switch (c) { + case '\'': + buffer += '\''; + break; + case '"': + buffer += '"'; + break; + case '\\': + buffer += '\\'; + break; + case '/': + buffer += '/'; + break; + case 'b': + buffer += '\b'; + break; + case 'f': + buffer += '\f'; + break; + case 'n': + buffer += '\n'; + break; + case 'r': + buffer += '\r'; + break; + case 't': + buffer += '\t'; + break; + case 'u': + // interpret the following 4 characters as the hex of the unicode code point + var codePoint = v.substring(index + 1, index + 5); + if (!codePointPattern.test(codePoint)) { + this.parseError('Illegal escape sequence: \\u' + codePoint); + } + buffer += String.fromCharCode(parseInt(codePoint, 16)); + index += 4; + break; + default: + throw this.parseError('Illegal escape sequence: "\\' + c + '"'); + } + ++index; + var backslash = v.indexOf('\\', index); + buffer += v.substring(index, backslash < 0 ? v.length : backslash); + index = backslash; + } + + return buffer; + }; + + TokenStream.prototype.isComment = function () { + var c = this.expression.charAt(this.pos); + if (c === '/' && this.expression.charAt(this.pos + 1) === '*') { + this.pos = this.expression.indexOf('*/', this.pos) + 2; + if (this.pos === 1) { + this.pos = this.expression.length; + } + return true; + } + return false; + }; + + TokenStream.prototype.isRadixInteger = function () { + var pos = this.pos; + + if (pos >= this.expression.length - 2 || this.expression.charAt(pos) !== '0') { + return false; + } + ++pos; + + var radix; + var validDigit; + if (this.expression.charAt(pos) === 'x') { + radix = 16; + validDigit = /^[0-9a-f]$/i; + ++pos; + } else if (this.expression.charAt(pos) === 'b') { + radix = 2; + validDigit = /^[01]$/i; + ++pos; + } else { + return false; + } + + var valid = false; + var startPos = pos; + + while (pos < this.expression.length) { + var c = this.expression.charAt(pos); + if (validDigit.test(c)) { + pos++; + valid = true; + } else { + break; + } + } + + if (valid) { + this.current = this.newToken(TNUMBER, parseInt(this.expression.substring(startPos, pos), radix)); + this.pos = pos; + } + return valid; + }; + + TokenStream.prototype.isNumber = function () { + var valid = false; + var pos = this.pos; + var startPos = pos; + var resetPos = pos; + var foundDot = false; + var foundDigits = false; + var c; + + while (pos < this.expression.length) { + c = this.expression.charAt(pos); + if ((c >= '0' && c <= '9') || (!foundDot && c === '.')) { + if (c === '.') { + foundDot = true; + } else { + foundDigits = true; + } + pos++; + valid = foundDigits; + } else { + break; + } + } + + if (valid) { + resetPos = pos; + } + + if (c === 'e' || c === 'E') { + pos++; + var acceptSign = true; + var validExponent = false; + while (pos < this.expression.length) { + c = this.expression.charAt(pos); + if (acceptSign && (c === '+' || c === '-')) { + acceptSign = false; + } else if (c >= '0' && c <= '9') { + validExponent = true; + acceptSign = false; + } else { + break; + } + pos++; + } + + if (!validExponent) { + pos = resetPos; + } + } + + if (valid) { + this.current = this.newToken(TNUMBER, parseFloat(this.expression.substring(startPos, pos))); + this.pos = pos; + } else { + this.pos = resetPos; + } + return valid; + }; + + TokenStream.prototype.isOperator = function () { + var startPos = this.pos; + var c = this.expression.charAt(this.pos); + + if (c === '+' || c === '-' || c === '*' || c === '/' || c === '%' || c === '^' || c === '?' || c === ':' || c === '.') { + this.current = this.newToken(TOP, c); + } else if (c === '∙' || c === '•') { + this.current = this.newToken(TOP, '*'); + } else if (c === '>') { + if (this.expression.charAt(this.pos + 1) === '=') { + this.current = this.newToken(TOP, '>='); + this.pos++; + } else { + this.current = this.newToken(TOP, '>'); + } + } else if (c === '<') { + if (this.expression.charAt(this.pos + 1) === '=') { + this.current = this.newToken(TOP, '<='); + this.pos++; + } else { + this.current = this.newToken(TOP, '<'); + } + } else if (c === '|') { + if (this.expression.charAt(this.pos + 1) === '|') { + this.current = this.newToken(TOP, '||'); + this.pos++; + } else { + return false; + } + } else if (c === '=') { + if (this.expression.charAt(this.pos + 1) === '=') { + this.current = this.newToken(TOP, '=='); + this.pos++; + } else { + return false; + } + } else if (c === '!') { + if (this.expression.charAt(this.pos + 1) === '=') { + this.current = this.newToken(TOP, '!='); + this.pos++; + } else { + this.current = this.newToken(TOP, c); + } + } else { + return false; + } + this.pos++; + + if (this.isOperatorEnabled(this.current.value)) { + return true; + } else { + this.pos = startPos; + return false; + } + }; + + var optionNameMap = { + '+': 'add', + '-': 'subtract', + '*': 'multiply', + '/': 'divide', + '%': 'remainder', + '^': 'power', + '!': 'factorial', + '<': 'comparison', + '>': 'comparison', + '<=': 'comparison', + '>=': 'comparison', + '==': 'comparison', + '!=': 'comparison', + '||': 'concatenate', + 'and': 'logical', + 'or': 'logical', + 'not': 'logical', + '?': 'conditional', + ':': 'conditional' + }; + + function getOptionName(op) { + return optionNameMap.hasOwnProperty(op) ? optionNameMap[op] : op; + } + + TokenStream.prototype.isOperatorEnabled = function (op) { + var optionName = getOptionName(op); + var operators = this.options.operators || {}; + + // in is a special case for now because it's disabled by default + if (optionName === 'in') { + return !!operators['in']; + } + + return !(optionName in operators) || !!operators[optionName]; + }; + + TokenStream.prototype.getCoordinates = function () { + var line = 0; + var column; + var newline = -1; + do { + line++; + column = this.pos - newline; + newline = this.expression.indexOf('\n', newline + 1); + } while (newline >= 0 && newline < this.pos); + + return { + line: line, + column: column + }; + }; + + TokenStream.prototype.parseError = function (msg) { + var coords = this.getCoordinates(); + throw new Error('parse error [' + coords.line + ':' + coords.column + ']: ' + msg); + }; + + function ParserState(parser, tokenStream, options) { + this.parser = parser; + this.tokens = tokenStream; + this.current = null; + this.nextToken = null; + this.next(); + this.savedCurrent = null; + this.savedNextToken = null; + this.allowMemberAccess = options.allowMemberAccess !== false; + } + + ParserState.prototype.next = function () { + this.current = this.nextToken; + return (this.nextToken = this.tokens.next()); + }; + + ParserState.prototype.tokenMatches = function (token, value) { + if (typeof value === 'undefined') { + return true; + } else if (Array.isArray(value)) { + return contains(value, token.value); + } else if (typeof value === 'function') { + return value(token); + } else { + return token.value === value; + } + }; + + ParserState.prototype.save = function () { + this.savedCurrent = this.current; + this.savedNextToken = this.nextToken; + this.tokens.save(); + }; + + ParserState.prototype.restore = function () { + this.tokens.restore(); + this.current = this.savedCurrent; + this.nextToken = this.savedNextToken; + }; + + ParserState.prototype.accept = function (type, value) { + if (this.nextToken.type === type && this.tokenMatches(this.nextToken, value)) { + this.next(); + return true; + } + return false; + }; + + ParserState.prototype.expect = function (type, value) { + if (!this.accept(type, value)) { + var coords = this.tokens.getCoordinates(); + throw new Error('parse error [' + coords.line + ':' + coords.column + ']: Expected ' + (value || type)); + } + }; + + ParserState.prototype.parseAtom = function (instr) { + if (this.accept(TNAME)) { + instr.push(new Instruction(IVAR, this.current.value)); + } else if (this.accept(TNUMBER)) { + instr.push(new Instruction(INUMBER, this.current.value)); + } else if (this.accept(TSTRING)) { + instr.push(new Instruction(INUMBER, this.current.value)); + } else if (this.accept(TPAREN, '(')) { + this.parseExpression(instr); + this.expect(TPAREN, ')'); + } else { + throw new Error('unexpected ' + this.nextToken); + } + }; + + ParserState.prototype.parseExpression = function (instr) { + this.parseConditionalExpression(instr); + }; + + ParserState.prototype.parseConditionalExpression = function (instr) { + this.parseOrExpression(instr); + while (this.accept(TOP, '?')) { + var trueBranch = []; + var falseBranch = []; + this.parseConditionalExpression(trueBranch); + this.expect(TOP, ':'); + this.parseConditionalExpression(falseBranch); + instr.push(new Instruction(IEXPR, trueBranch)); + instr.push(new Instruction(IEXPR, falseBranch)); + instr.push(ternaryInstruction('?')); + } + }; + + ParserState.prototype.parseOrExpression = function (instr) { + this.parseAndExpression(instr); + while (this.accept(TOP, 'or')) { + var falseBranch = []; + this.parseAndExpression(falseBranch); + instr.push(new Instruction(IEXPR, falseBranch)); + instr.push(binaryInstruction('or')); + } + }; + + ParserState.prototype.parseAndExpression = function (instr) { + this.parseComparison(instr); + while (this.accept(TOP, 'and')) { + var trueBranch = []; + this.parseComparison(trueBranch); + instr.push(new Instruction(IEXPR, trueBranch)); + instr.push(binaryInstruction('and')); + } + }; + + var COMPARISON_OPERATORS = ['==', '!=', '<', '<=', '>=', '>', 'in']; + + ParserState.prototype.parseComparison = function (instr) { + this.parseAddSub(instr); + while (this.accept(TOP, COMPARISON_OPERATORS)) { + var op = this.current; + this.parseAddSub(instr); + instr.push(binaryInstruction(op.value)); + } + }; + + var ADD_SUB_OPERATORS = ['+', '-', '||']; + + ParserState.prototype.parseAddSub = function (instr) { + this.parseTerm(instr); + while (this.accept(TOP, ADD_SUB_OPERATORS)) { + var op = this.current; + this.parseTerm(instr); + instr.push(binaryInstruction(op.value)); + } + }; + + var TERM_OPERATORS = ['*', '/', '%']; + + ParserState.prototype.parseTerm = function (instr) { + this.parseFactor(instr); + while (this.accept(TOP, TERM_OPERATORS)) { + var op = this.current; + this.parseFactor(instr); + instr.push(binaryInstruction(op.value)); + } + }; + + ParserState.prototype.parseFactor = function (instr) { + var unaryOps = this.tokens.unaryOps; + function isPrefixOperator(token) { + return token.value in unaryOps; + } + + this.save(); + if (this.accept(TOP, isPrefixOperator)) { + if ((this.current.value !== '-' && this.current.value !== '+' && this.nextToken.type === TPAREN && this.nextToken.value === '(')) { + this.restore(); + this.parseExponential(instr); + } else { + var op = this.current; + this.parseFactor(instr); + instr.push(unaryInstruction(op.value)); + } + } else { + this.parseExponential(instr); + } + }; + + ParserState.prototype.parseExponential = function (instr) { + this.parsePostfixExpression(instr); + while (this.accept(TOP, '^')) { + this.parseFactor(instr); + instr.push(binaryInstruction('^')); + } + }; + + ParserState.prototype.parsePostfixExpression = function (instr) { + this.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, '.')) { + if (!this.allowMemberAccess) { + throw new Error('unexpected ".", member access is not permitted'); + } + + this.expect(TNAME); + instr.push(new Instruction(IMEMBER, this.current.value)); + } + }; + + function add(a, b) { + return Number(a) + Number(b); + } + + function sub(a, b) { + return a - b; + } + + function mul(a, b) { + return a * b; + } + + function div(a, b) { + return a / b; + } + + function mod(a, b) { + return a % b; + } + + function concat(a, b) { + return '' + a + b; + } + + function equal(a, b) { + return a === b; + } + + function notEqual(a, b) { + return a !== b; + } + + function greaterThan(a, b) { + return a > b; + } + + function lessThan(a, b) { + return a < b; + } + + function greaterThanEqual(a, b) { + return a >= b; + } + + function lessThanEqual(a, b) { + return a <= b; + } + + function andOperator(a, b) { + return Boolean(a && b); + } + + function orOperator(a, b) { + return Boolean(a || b); + } + + function inOperator(a, b) { + return contains(b, a); + } + + function sinh(a) { + return ((Math.exp(a) - Math.exp(-a)) / 2); + } + + function cosh(a) { + return ((Math.exp(a) + Math.exp(-a)) / 2); + } + + function tanh(a) { + if (a === Infinity) return 1; + if (a === -Infinity) return -1; + return (Math.exp(a) - Math.exp(-a)) / (Math.exp(a) + Math.exp(-a)); + } + + function asinh(a) { + if (a === -Infinity) return a; + return Math.log(a + Math.sqrt((a * a) + 1)); + } + + function acosh(a) { + return Math.log(a + Math.sqrt((a * a) - 1)); + } + + function atanh(a) { + return (Math.log((1 + a) / (1 - a)) / 2); + } + + function log10(a) { + return Math.log(a) * Math.LOG10E; + } + + function neg(a) { + return -a; + } + + function not(a) { + return !a; + } + + function trunc(a) { + return a < 0 ? Math.ceil(a) : Math.floor(a); + } + + function random(a) { + return Math.random() * (a || 1); + } + + function factorial(a) { // a! + return gamma(a + 1); + } + + function isInteger(value) { + return isFinite(value) && (value === Math.round(value)); + } + + var GAMMA_G = 4.7421875; + var GAMMA_P = [ + 0.99999999999999709182, + 57.156235665862923517, -59.597960355475491248, + 14.136097974741747174, -0.49191381609762019978, + 0.33994649984811888699e-4, + 0.46523628927048575665e-4, -0.98374475304879564677e-4, + 0.15808870322491248884e-3, -0.21026444172410488319e-3, + 0.21743961811521264320e-3, -0.16431810653676389022e-3, + 0.84418223983852743293e-4, -0.26190838401581408670e-4, + 0.36899182659531622704e-5 + ]; + + // Gamma function from math.js + function gamma(n) { + var t, x; + + if (isInteger(n)) { + if (n <= 0) { + return isFinite(n) ? Infinity : NaN; + } + + if (n > 171) { + return Infinity; // Will overflow + } + + var value = n - 2; + var res = n - 1; + while (value > 1) { + res *= value; + value--; + } + + if (res === 0) { + res = 1; // 0! is per definition 1 + } + + return res; + } + + if (n < 0.5) { + return Math.PI / (Math.sin(Math.PI * n) * gamma(1 - n)); + } + + if (n >= 171.35) { + return Infinity; // will overflow + } + + if (n > 85.0) { // Extended Stirling Approx + var twoN = n * n; + var threeN = twoN * n; + var fourN = threeN * n; + var fiveN = fourN * n; + return Math.sqrt(2 * Math.PI / n) * Math.pow((n / Math.E), n) * + (1 + (1 / (12 * n)) + (1 / (288 * twoN)) - (139 / (51840 * threeN)) - + (571 / (2488320 * fourN)) + (163879 / (209018880 * fiveN)) + + (5246819 / (75246796800 * fiveN * n))); + } + + --n; + x = GAMMA_P[0]; + for (var i = 1; i < GAMMA_P.length; ++i) { + x += GAMMA_P[i] / (n + i); + } + + t = n + GAMMA_G + 0.5; + return Math.sqrt(2 * Math.PI) * Math.pow(t, n + 0.5) * Math.exp(-t) * x; + } + + function stringLength(s) { + return String(s).length; + } + + function hypot() { + var sum = 0; + var larg = 0; + for (var i = 0; i < arguments.length; i++) { + var arg = Math.abs(arguments[i]); + var div; + if (larg < arg) { + div = larg / arg; + sum = (sum * div * div) + 1; + larg = arg; + } else if (arg > 0) { + div = arg / larg; + sum += div * div; + } else { + sum += arg; + } + } + return larg === Infinity ? Infinity : larg * Math.sqrt(sum); + } + + function condition(cond, yep, nope) { + return cond ? yep : nope; + } + + /** + * Decimal adjustment of a number. + * From @escopecz. + * + * @param {Number} value The number. + * @param {Integer} exp The exponent (the 10 logarithm of the adjustment base). + * @return {Number} The adjusted value. + */ + function roundTo(value, exp) { + // If the exp is undefined or zero... + if (typeof exp === 'undefined' || +exp === 0) { + return Math.round(value); + } + value = +value; + exp = -(+exp); + // If the value is not a number or the exp is not an integer... + if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) { + return NaN; + } + // Shift + value = value.toString().split('e'); + value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp))); + // Shift back + value = value.toString().split('e'); + return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)); + } + + function Parser(options) { + this.options = options || {}; + this.unaryOps = { + sin: Math.sin, + cos: Math.cos, + tan: Math.tan, + asin: Math.asin, + acos: Math.acos, + atan: Math.atan, + sinh: Math.sinh || sinh, + cosh: Math.cosh || cosh, + tanh: Math.tanh || tanh, + asinh: Math.asinh || asinh, + acosh: Math.acosh || acosh, + atanh: Math.atanh || atanh, + sqrt: Math.sqrt, + log: Math.log, + ln: Math.log, + lg: Math.log10 || log10, + log10: Math.log10 || log10, + abs: Math.abs, + ceil: Math.ceil, + floor: Math.floor, + round: Math.round, + trunc: Math.trunc || trunc, + '-': neg, + '+': Number, + exp: Math.exp, + not: not, + length: stringLength, + '!': factorial + }; + + this.binaryOps = { + '+': add, + '-': sub, + '*': mul, + '/': div, + '%': mod, + '^': Math.pow, + '||': concat, + '==': equal, + '!=': notEqual, + '>': greaterThan, + '<': lessThan, + '>=': greaterThanEqual, + '<=': lessThanEqual, + and: andOperator, + or: orOperator, + 'in': inOperator + }; + + this.ternaryOps = { + '?': condition + }; + + this.functions = { + random: random, + fac: factorial, + min: Math.min, + max: Math.max, + hypot: Math.hypot || hypot, + pyt: Math.hypot || hypot, // backward compat + pow: Math.pow, + atan2: Math.atan2, + 'if': condition, + gamma: gamma, + roundTo: roundTo + }; + + this.consts = { + E: Math.E, + PI: Math.PI, + 'true': true, + 'false': false + }; + } + + Parser.prototype.parse = function (expr) { + var instr = []; + var parserState = new ParserState( + this, + new TokenStream(this, expr), + { allowMemberAccess: this.options.allowMemberAccess } + ); + + parserState.parseExpression(instr); + parserState.expect(TEOF, 'EOF'); + + return new Expression(instr, this); + }; + + Parser.prototype.evaluate = function (expr, variables) { + return this.parse(expr).evaluate(variables); + }; + + var sharedParser = new Parser(); + + Parser.parse = function (expr) { + return sharedParser.parse(expr); + }; + + Parser.evaluate = function (expr, variables) { + return sharedParser.parse(expr).evaluate(variables); + }; + + /*! + Based on ndef.parser, by Raphael Graf(r@undefined.ch) + http://www.undefined.ch/mparser/index.html + + Ported to JavaScript and modified by Matthew Crumley (email@matthewcrumley.com, http://silentmatt.com/) + + You are free to use and modify this code in anyway you find useful. Please leave this comment in the code + to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code, + but don't feel like you have to let me know or ask permission. + */ + + var index = { + Parser: Parser, + Expression: Expression + }; + + return index; + +}))); diff --git a/dist/bundle.min.js b/dist/bundle.min.js new file mode 100644 index 0000000..4f1c171 --- /dev/null +++ b/dist/bundle.min.js @@ -0,0 +1 @@ +!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.exprEval=e()}(this,function(){"use strict";var t="INUMBER",e="IOP1",s="IOP2",r="IOP3",n="IVAR",i="IFUNCALL",o="IEXPR",a="IMEMBER";function h(t,e){this.type=t,this.value=void 0!==e&&null!==e?e:0}function p(t){return new h(e,t)}function u(t){return new h(s,t)}function c(t){return new h(r,t)}function l(h,p){for(var u,c,v,x,y=[],w=0;w0;)E.unshift(y.pop());x=y.pop(),y.push(x+"("+E.join(", ")+")")}else if(M===a)u=y.pop(),y.push(u+"."+d.value);else{if(M!==o)throw new Error("invalid Expression");y.push("("+l(d.value,p)+")")}}if(y.length>1)throw new Error("invalid Expression (parity)");return String(y[0])}function f(t){return"string"==typeof t?JSON.stringify(t).replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029"):t}function v(t,e){for(var s=0;s1)x=d.pop(),v=d.pop(),w=c[E.value],E=new h(t,w(v.value,x.value)),d.push(E);else if(k===r&&d.length>2)y=d.pop(),x=d.pop(),v=d.pop(),"?"===E.value?d.push(v.value?x.value:y.value):(w=l[E.value],E=new h(t,w(v.value,x.value,y.value)),d.push(E));else if(k===e&&d.length>0)v=d.pop(),w=u[E.value],E=new h(t,w(v.value)),d.push(E);else if(k===o){var b=i(E.value,u,c,l,f);if(1===b.length&&b[0].type===t)d.push(b[0]);else{for(;d.length>0;)M.push(d.shift());M.push(new h(o,b))}}else if(k===a&&d.length>0)v=d.pop(),d.push(new h(t,v.value[E.value]));else{for(;d.length>0;)M.push(d.shift());M.push(E)}}for(;d.length>0;)M.push(d.shift());return M}(this.tokens,this.unaryOps,this.binaryOps,this.ternaryOps,i),this.parser)},y.prototype.substitute=function(t,i){return i instanceof y||(i=this.parser.parse(String(i))),new y(function t(i,a,l){for(var f=[],v=0;v0;)k.unshift(y.pop());if(!(x=y.pop()).apply||!x.call)throw new Error(x+" is not a function");y.push(x.apply(void 0,k))}else if(M===o)y.push(d.value);else{if(M!==a)throw new Error("invalid Expression");l=y.pop(),y.push(l[d.value])}}if(y.length>1)throw new Error("invalid Expression (parity)");return y[0]}(this.tokens,this,h)},y.prototype.toString=function(){return l(this.tokens,!1)},y.prototype.symbols=function(t){t=t||{};var e=[];return x(this.tokens,e,t),e},y.prototype.variables=function(t){t=t||{};var e=[];x(this.tokens,e,t);var s=this.functions;return e.filter(function(t){return!(t in s)})},y.prototype.toJSFunction=function(t,e){var s=this,r=new Function(t,"with(this.functions) with (this.ternaryOps) with (this.binaryOps) with (this.unaryOps) { return "+l(this.simplify(e).tokens,!0)+"; }");return function(){return r.apply(s,arguments)}};var w="TOP";function d(t,e,s){this.type=t,this.value=e,this.index=s}function M(t,e){this.pos=0,this.current=null,this.unaryOps=t.unaryOps,this.binaryOps=t.binaryOps,this.ternaryOps=t.ternaryOps,this.consts=t.consts,this.expression=e,this.savedPosition=0,this.savedCurrent=null,this.options=t.options}d.prototype.toString=function(){return this.type+": "+this.value},M.prototype.newToken=function(t,e,s){return new d(t,e,null!=s?s:this.pos)},M.prototype.save=function(){this.savedPosition=this.pos,this.savedCurrent=this.current},M.prototype.restore=function(){this.pos=this.savedPosition,this.current=this.savedCurrent},M.prototype.next=function(){return this.pos>=this.expression.length?this.newToken("TEOF","EOF"):this.isWhitespace()||this.isComment()?this.next():this.isRadixInteger()||this.isNumber()||this.isOperator()||this.isString()||this.isParen()||this.isComma()||this.isNamedOp()||this.isConst()||this.isName()?this.current:void this.parseError('Unknown character "'+this.expression.charAt(this.pos)+'"')},M.prototype.isString=function(){var t=!1,e=this.pos,s=this.expression.charAt(e);if("'"===s||'"'===s)for(var r=this.expression.indexOf(s,e+1);r>=0&&this.pos"9")))break}if(e>t){var r=this.expression.substring(t,e);if(r in this.consts)return this.current=this.newToken("TNUMBER",this.consts[r]),this.pos+=r.length,!0}return!1},M.prototype.isNamedOp=function(){for(var t=this.pos,e=t;e"9")))break}if(e>t){var r=this.expression.substring(t,e);if(this.isOperatorEnabled(r)&&(r in this.binaryOps||r in this.unaryOps||r in this.ternaryOps))return this.current=this.newToken(w,r),this.pos+=r.length,!0}return!1},M.prototype.isName=function(){for(var t=this.pos,e=t,s=!1;e"9"))break}else s=!0}if(s){var n=this.expression.substring(t,e);return this.current=this.newToken("TNAME",n),this.pos+=n.length,!0}return!1},M.prototype.isWhitespace=function(){for(var t=!1,e=this.expression.charAt(this.pos);!(" "!==e&&"\t"!==e&&"\n"!==e&&"\r"!==e||(t=!0,this.pos++,this.pos>=this.expression.length));)e=this.expression.charAt(this.pos);return t};var g=/^[0-9a-f]{4}$/i;M.prototype.unescape=function(t){var e=t.indexOf("\\");if(e<0)return t;for(var s=t.substring(0,e);e>=0;){var r=t.charAt(++e);switch(r){case"'":s+="'";break;case'"':s+='"';break;case"\\":s+="\\";break;case"/":s+="/";break;case"b":s+="\b";break;case"f":s+="\f";break;case"n":s+="\n";break;case"r":s+="\r";break;case"t":s+="\t";break;case"u":var n=t.substring(e+1,e+5);g.test(n)||this.parseError("Illegal escape sequence: \\u"+n),s+=String.fromCharCode(parseInt(n,16)),e+=4;break;default:throw this.parseError('Illegal escape sequence: "\\'+r+'"')}++e;var i=t.indexOf("\\",e);s+=t.substring(e,i<0?t.length:i),e=i}return s},M.prototype.isComment=function(){return"/"===this.expression.charAt(this.pos)&&"*"===this.expression.charAt(this.pos+1)&&(this.pos=this.expression.indexOf("*/",this.pos)+2,1===this.pos&&(this.pos=this.expression.length),!0)},M.prototype.isRadixInteger=function(){var t,e,s=this.pos;if(s>=this.expression.length-2||"0"!==this.expression.charAt(s))return!1;if(++s,"x"===this.expression.charAt(s))t=16,e=/^[0-9a-f]$/i,++s;else{if("b"!==this.expression.charAt(s))return!1;t=2,e=/^[01]$/i,++s}for(var r=!1,n=s;s="0"&&t<="9"||!i&&"."===t);)"."===t?i=!0:o=!0,s++,e=o;if(e&&(n=s),"e"===t||"E"===t){s++;for(var a=!0,h=!1;s="0"&&t<="9"))break;h=!0,a=!1}else a=!1;s++}h||(s=n)}return e?(this.current=this.newToken("TNUMBER",parseFloat(this.expression.substring(r,s))),this.pos=s):this.pos=n,e},M.prototype.isOperator=function(){var t=this.pos,e=this.expression.charAt(this.pos);if("+"===e||"-"===e||"*"===e||"/"===e||"%"===e||"^"===e||"?"===e||":"===e||"."===e)this.current=this.newToken(w,e);else if("∙"===e||"•"===e)this.current=this.newToken(w,"*");else if(">"===e)"="===this.expression.charAt(this.pos+1)?(this.current=this.newToken(w,">="),this.pos++):this.current=this.newToken(w,">");else if("<"===e)"="===this.expression.charAt(this.pos+1)?(this.current=this.newToken(w,"<="),this.pos++):this.current=this.newToken(w,"<");else if("|"===e){if("|"!==this.expression.charAt(this.pos+1))return!1;this.current=this.newToken(w,"||"),this.pos++}else if("="===e){if("="!==this.expression.charAt(this.pos+1))return!1;this.current=this.newToken(w,"=="),this.pos++}else{if("!"!==e)return!1;"="===this.expression.charAt(this.pos+1)?(this.current=this.newToken(w,"!="),this.pos++):this.current=this.newToken(w,e)}return this.pos++,!!this.isOperatorEnabled(this.current.value)||(this.pos=t,!1)};var E={"+":"add","-":"subtract","*":"multiply","/":"divide","%":"remainder","^":"power","!":"factorial","<":"comparison",">":"comparison","<=":"comparison",">=":"comparison","==":"comparison","!=":"comparison","||":"concatenate",and:"logical",or:"logical",not:"logical","?":"conditional",":":"conditional"};function k(t,e,s){this.parser=t,this.tokens=e,this.current=null,this.nextToken=null,this.next(),this.savedCurrent=null,this.savedNextToken=null,this.allowMemberAccess=!1!==s.allowMemberAccess}M.prototype.isOperatorEnabled=function(t){var e=function(t){return E.hasOwnProperty(t)?E[t]:t}(t),s=this.options.operators||{};return"in"===e?!!s.in:!(e in s&&!s[e])},M.prototype.getCoordinates=function(){var t,e=0,s=-1;do{e++,t=this.pos-s,s=this.expression.indexOf("\n",s+1)}while(s>=0&&s=",">","in"];k.prototype.parseComparison=function(t){for(this.parseAddSub(t);this.accept(w,b);){var e=this.current;this.parseAddSub(t),t.push(u(e.value))}};var m=["+","-","||"];k.prototype.parseAddSub=function(t){for(this.parseTerm(t);this.accept(w,m);){var e=this.current;this.parseTerm(t),t.push(u(e.value))}};var T=["*","/","%"];function A(t,e){return Number(t)+Number(e)}function O(t,e){return t-e}function N(t,e){return t*e}function C(t,e){return t/e}function P(t,e){return t%e}function I(t,e){return""+t+e}function S(t,e){return t===e}function R(t,e){return t!==e}function F(t,e){return t>e}function L(t,e){return t=e}function q(t,e){return t<=e}function B(t,e){return Boolean(t&&e)}function _(t,e){return Boolean(t||e)}function $(t,e){return v(e,t)}function G(t){return(Math.exp(t)-Math.exp(-t))/2}function j(t){return(Math.exp(t)+Math.exp(-t))/2}function J(t){return t===1/0?1:t===-1/0?-1:(Math.exp(t)-Math.exp(-t))/(Math.exp(t)+Math.exp(-t))}function W(t){return t===-1/0?t:Math.log(t+Math.sqrt(t*t+1))}function V(t){return Math.log(t+Math.sqrt(t*t-1))}function X(t){return Math.log((1+t)/(1-t))/2}function z(t){return Math.log(t)*Math.LOG10E}function D(t){return-t}function H(t){return!t}function K(t){return t<0?Math.ceil(t):Math.floor(t)}function Q(t){return Math.random()*(t||1)}function Y(t){return et(t+1)}k.prototype.parseTerm=function(t){for(this.parseFactor(t);this.accept(w,T);){var e=this.current;this.parseFactor(t),t.push(u(e.value))}},k.prototype.parseFactor=function(t){var e=this.tokens.unaryOps;if(this.save(),this.accept(w,function(t){return t.value in e}))if("-"!==this.current.value&&"+"!==this.current.value&&"TPAREN"===this.nextToken.type&&"("===this.nextToken.value)this.restore(),this.parseExponential(t);else{var s=this.current;this.parseFactor(t),t.push(p(s.value))}else this.parseExponential(t)},k.prototype.parseExponential=function(t){for(this.parsePostfixExpression(t);this.accept(w,"^");)this.parseFactor(t),t.push(u("^"))},k.prototype.parsePostfixExpression=function(t){for(this.parseFunctionCall(t);this.accept(w,"!");)t.push(p("!"))},k.prototype.parseFunctionCall=function(t){var e=this.tokens.unaryOps;if(this.accept(w,function(t){return t.value in e})){var s=this.current;this.parseAtom(t),t.push(p(s.value))}else for(this.parseMemberExpression(t);this.accept("TPAREN","(");)if(this.accept("TPAREN",")"))t.push(new h(i,0));else{var r=this.parseArgumentList(t);t.push(new h(i,r))}},k.prototype.parseArgumentList=function(t){for(var e=0;!this.accept("TPAREN",")");)for(this.parseExpression(t),++e;this.accept("TCOMMA");)this.parseExpression(t),++e;return e},k.prototype.parseMemberExpression=function(t){for(this.parseAtom(t);this.accept(w,".");){if(!this.allowMemberAccess)throw new Error('unexpected ".", member access is not permitted');this.expect("TNAME"),t.push(new h(a,this.current.value))}};var Z=4.7421875,tt=[.9999999999999971,57.15623566586292,-59.59796035547549,14.136097974741746,-.4919138160976202,3399464998481189e-20,4652362892704858e-20,-9837447530487956e-20,.0001580887032249125,-.00021026444172410488,.00021743961811521265,-.0001643181065367639,8441822398385275e-20,-26190838401581408e-21,36899182659531625e-22];function et(t){var e,s;if(function(t){return isFinite(t)&&t===Math.round(t)}(t)){if(t<=0)return isFinite(t)?1/0:NaN;if(t>171)return 1/0;for(var r=t-2,n=t-1;r>1;)n*=r,r--;return 0===n&&(n=1),n}if(t<.5)return Math.PI/(Math.sin(Math.PI*t)*et(1-t));if(t>=171.35)return 1/0;if(t>85){var i=t*t,o=i*t,a=o*t,h=a*t;return Math.sqrt(2*Math.PI/t)*Math.pow(t/Math.E,t)*(1+1/(12*t)+1/(288*i)-139/(51840*o)-571/(2488320*a)+163879/(209018880*h)+5246819/(75246796800*h*t))}--t,s=tt[0];for(var p=1;p0?(r=n/e)*r:n}return e===1/0?1/0:e*Math.sqrt(t)}function nt(t,e,s){return t?e:s}function it(t,e){return void 0===e||0==+e?Math.round(t):(t=+t,e=-+e,isNaN(t)||"number"!=typeof e||e%1!=0?NaN:(t=t.toString().split("e"),+((t=(t=Math.round(+(t[0]+"e"+(t[1]?+t[1]-e:-e)))).toString().split("e"))[0]+"e"+(t[1]?+t[1]+e:e))))}function ot(t){this.options=t||{},this.unaryOps={sin:Math.sin,cos:Math.cos,tan:Math.tan,asin:Math.asin,acos:Math.acos,atan:Math.atan,sinh:Math.sinh||G,cosh:Math.cosh||j,tanh:Math.tanh||J,asinh:Math.asinh||W,acosh:Math.acosh||V,atanh:Math.atanh||X,sqrt:Math.sqrt,log:Math.log,ln:Math.log,lg:Math.log10||z,log10:Math.log10||z,abs:Math.abs,ceil:Math.ceil,floor:Math.floor,round:Math.round,trunc:Math.trunc||K,"-":D,"+":Number,exp:Math.exp,not:H,length:st,"!":Y},this.binaryOps={"+":A,"-":O,"*":N,"/":C,"%":P,"^":Math.pow,"||":I,"==":S,"!=":R,">":F,"<":L,">=":U,"<=":q,and:B,or:_,in:$},this.ternaryOps={"?":nt},this.functions={random:Q,fac:Y,min:Math.min,max:Math.max,hypot:Math.hypot||rt,pyt:Math.hypot||rt,pow:Math.pow,atan2:Math.atan2,if:nt,gamma:et,roundTo:it},this.consts={E:Math.E,PI:Math.PI,true:!0,false:!1}}ot.prototype.parse=function(t){var e=[],s=new k(this,new M(this,t),{allowMemberAccess:this.options.allowMemberAccess});return s.parseExpression(e),s.expect("TEOF","EOF"),new y(e,this)},ot.prototype.evaluate=function(t,e){return this.parse(t).evaluate(e)};var at=new ot;return ot.parse=function(t){return at.parse(t)},ot.evaluate=function(t,e){return at.parse(t).evaluate(e)},{Parser:ot,Expression:y}}); diff --git a/package-lock.json b/package-lock.json index f8e76ba..ab41d32 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "expr-eval", - "version": "1.2.0", + "version": "1.2.2", "lockfileVersion": 1, "requires": true, "dependencies": { From af792736309fd9cac54e301443852e9dd555ad0d Mon Sep 17 00:00:00 2001 From: Daniel Mittelman Date: Wed, 15 Aug 2018 09:55:50 +0300 Subject: [PATCH 3/5] Revert "Changed how expressions are simplified to eliminate constant repetitions" This reverts commit 62ff0850e2792820fb017419491cb28ba005e5ab. --- src/simplify.js | 104 +++++++++++++++++++++------------------------ test/expression.js | 4 +- 2 files changed, 51 insertions(+), 57 deletions(-) diff --git a/src/simplify.js b/src/simplify.js index 1cf6b2f..e68845c 100644 --- a/src/simplify.js +++ b/src/simplify.js @@ -1,63 +1,57 @@ import { Instruction, INUMBER, IOP1, IOP2, IOP3, IVAR, IEXPR, IMEMBER } from './instruction'; export default function simplify(tokens, unaryOps, binaryOps, ternaryOps, values) { - var nstack = []; - var newexpression = []; - var n1, n2, n3; - var f; - for (var i = 0; i < tokens.length; i++) { - var item = tokens[i]; - var type = item.type; - if (type === INUMBER) { - nstack.push(item); - } else if (type === IVAR && values.hasOwnProperty(item.value)) { - item = new Instruction(INUMBER, values[item.value]); - nstack.push(item); - } else if (type === IOP2 && nstack.length > 1) { - n2 = nstack.pop(); - n1 = nstack.pop(); - f = binaryOps[item.value]; - item = new Instruction(INUMBER, f(n1.value, n2.value)); - nstack.push(item); - } else if (type === IOP3 && nstack.length > 2) { - n3 = nstack.pop(); - n2 = nstack.pop(); - n1 = nstack.pop(); - if (item.value === '?') { - nstack.push(n1.value ? n2.value : n3.value); - } else { - f = ternaryOps[item.value]; - item = new Instruction(INUMBER, f(n1.value, n2.value, n3.value)); - nstack.push(item); - } - } else if (type === IOP1 && nstack.length > 0) { - n1 = nstack.pop(); - f = unaryOps[item.value]; - item = new Instruction(INUMBER, f(n1.value)); + var nstack = []; + var newexpression = []; + var n1, n2, n3; + var f; + for (var i = 0; i < tokens.length; i++) { + var item = tokens[i]; + var type = item.type; + if (type === INUMBER) { + nstack.push(item); + } else if (type === IVAR && values.hasOwnProperty(item.value)) { + item = new Instruction(INUMBER, values[item.value]); + nstack.push(item); + } else if (type === IOP2 && nstack.length > 1) { + n2 = nstack.pop(); + n1 = nstack.pop(); + f = binaryOps[item.value]; + item = new Instruction(INUMBER, f(n1.value, n2.value)); + nstack.push(item); + } else if (type === IOP3 && nstack.length > 2) { + n3 = nstack.pop(); + n2 = nstack.pop(); + n1 = nstack.pop(); + if (item.value === '?') { + nstack.push(n1.value ? n2.value : n3.value); + } else { + f = ternaryOps[item.value]; + item = new Instruction(INUMBER, f(n1.value, n2.value, n3.value)); nstack.push(item); - } else if (type === IEXPR) { - var simplified = simplify(item.value, unaryOps, binaryOps, ternaryOps, values); - if(simplified.length === 1 && simplified[0].type === INUMBER) { - nstack.push(simplified[0]); } - else { - while (nstack.length > 0) { - newexpression.push(nstack.shift()); - } - newexpression.push(new Instruction(IEXPR, simplified)); - } - } else if (type === IMEMBER && nstack.length > 0) { - n1 = nstack.pop(); - nstack.push(new Instruction(INUMBER, n1.value[item.value])); - } else { - while (nstack.length > 0) { - newexpression.push(nstack.shift()); - } - newexpression.push(item); + } else if (type === IOP1 && nstack.length > 0) { + n1 = nstack.pop(); + f = unaryOps[item.value]; + item = new Instruction(INUMBER, f(n1.value)); + nstack.push(item); + } else if (type === IEXPR) { + while (nstack.length > 0) { + newexpression.push(nstack.shift()); } + newexpression.push(new Instruction(IEXPR, simplify(item.value, unaryOps, binaryOps, ternaryOps, values))); + } else if (type === IMEMBER && nstack.length > 0) { + n1 = nstack.pop(); + nstack.push(new Instruction(INUMBER, n1.value[item.value])); + } else { + while (nstack.length > 0) { + newexpression.push(nstack.shift()); + } + newexpression.push(item); } - while (nstack.length > 0) { - newexpression.push(nstack.shift()); - } - return newexpression; } + while (nstack.length > 0) { + newexpression.push(nstack.shift()); + } + return newexpression; +} diff --git a/test/expression.js b/test/expression.js index d6a8d83..51d24f1 100644 --- a/test/expression.js +++ b/test/expression.js @@ -172,11 +172,11 @@ describe('Expression', function () { }); it('x ? (y + 1) : z', function () { - assert.strictEqual(Parser.parse('x ? (y + 1) : z').simplify({ y: 2 }).toString(), '(x ? 3 : (z))'); + assert.strictEqual(Parser.parse('x ? (y + 1) : z').simplify({ y: 2 }).toString(), '(x ? (3) : (z))'); }); it('x ? y : (z * 4)', function () { - assert.strictEqual(Parser.parse('x ? y : (z * 4)').simplify({ z: 3 }).toString(), '(x ? (y) : 12)'); + assert.strictEqual(Parser.parse('x ? y : (z * 4)').simplify({ z: 3 }).toString(), '(x ? (y) : (12))'); }); }); From 38c9003cdd7f7dd6b5e26488cb884bfa7d079a94 Mon Sep 17 00:00:00 2001 From: Daniel Mittelman Date: Wed, 15 Aug 2018 09:56:35 +0300 Subject: [PATCH 4/5] Revert "Built dist" This reverts commit ddc76fea958b2b83b92b60155c3a45f7805605c9. --- dist/bundle.js | 1481 -------------------------------------------- dist/bundle.min.js | 1 - package-lock.json | 2 +- 3 files changed, 1 insertion(+), 1483 deletions(-) delete mode 100644 dist/bundle.js delete mode 100644 dist/bundle.min.js diff --git a/dist/bundle.js b/dist/bundle.js deleted file mode 100644 index b5e4f6e..0000000 --- a/dist/bundle.js +++ /dev/null @@ -1,1481 +0,0 @@ -(function (global, factory) { - typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : - typeof define === 'function' && define.amd ? define(factory) : - (global.exprEval = factory()); -}(this, (function () { 'use strict'; - - var INUMBER = 'INUMBER'; - var IOP1 = 'IOP1'; - var IOP2 = 'IOP2'; - var IOP3 = 'IOP3'; - var IVAR = 'IVAR'; - var IFUNCALL = 'IFUNCALL'; - var IEXPR = 'IEXPR'; - var IMEMBER = 'IMEMBER'; - - function Instruction(type, value) { - this.type = type; - this.value = (value !== undefined && value !== null) ? value : 0; - } - - Instruction.prototype.toString = function () { - switch (this.type) { - case INUMBER: - case IOP1: - case IOP2: - case IOP3: - case IVAR: - return this.value; - case IFUNCALL: - return 'CALL ' + this.value; - case IMEMBER: - return '.' + this.value; - default: - return 'Invalid Instruction'; - } - }; - - function unaryInstruction(value) { - return new Instruction(IOP1, value); - } - - function binaryInstruction(value) { - return new Instruction(IOP2, value); - } - - function ternaryInstruction(value) { - return new Instruction(IOP3, value); - } - - function simplify(tokens, unaryOps, binaryOps, ternaryOps, values) { - var nstack = []; - var newexpression = []; - var n1, n2, n3; - var f; - for (var i = 0; i < tokens.length; i++) { - var item = tokens[i]; - var type = item.type; - if (type === INUMBER) { - nstack.push(item); - } else if (type === IVAR && values.hasOwnProperty(item.value)) { - item = new Instruction(INUMBER, values[item.value]); - nstack.push(item); - } else if (type === IOP2 && nstack.length > 1) { - n2 = nstack.pop(); - n1 = nstack.pop(); - f = binaryOps[item.value]; - item = new Instruction(INUMBER, f(n1.value, n2.value)); - nstack.push(item); - } else if (type === IOP3 && nstack.length > 2) { - n3 = nstack.pop(); - n2 = nstack.pop(); - n1 = nstack.pop(); - if (item.value === '?') { - nstack.push(n1.value ? n2.value : n3.value); - } else { - f = ternaryOps[item.value]; - item = new Instruction(INUMBER, f(n1.value, n2.value, n3.value)); - nstack.push(item); - } - } else if (type === IOP1 && nstack.length > 0) { - n1 = nstack.pop(); - f = unaryOps[item.value]; - item = new Instruction(INUMBER, f(n1.value)); - nstack.push(item); - } else if (type === IEXPR) { - var simplified = simplify(item.value, unaryOps, binaryOps, ternaryOps, values); - if(simplified.length === 1 && simplified[0].type === INUMBER) { - nstack.push(simplified[0]); - } - else { - while (nstack.length > 0) { - newexpression.push(nstack.shift()); - } - newexpression.push(new Instruction(IEXPR, simplified)); - } - } else if (type === IMEMBER && nstack.length > 0) { - n1 = nstack.pop(); - nstack.push(new Instruction(INUMBER, n1.value[item.value])); - } else { - while (nstack.length > 0) { - newexpression.push(nstack.shift()); - } - newexpression.push(item); - } - } - while (nstack.length > 0) { - newexpression.push(nstack.shift()); - } - return newexpression; - } - - function substitute(tokens, variable, expr) { - var newexpression = []; - for (var i = 0; i < tokens.length; i++) { - var item = tokens[i]; - var type = item.type; - if (type === IVAR && item.value === variable) { - for (var j = 0; j < expr.tokens.length; j++) { - var expritem = expr.tokens[j]; - var replitem; - if (expritem.type === IOP1) { - replitem = unaryInstruction(expritem.value); - } else if (expritem.type === IOP2) { - replitem = binaryInstruction(expritem.value); - } else if (expritem.type === IOP3) { - replitem = ternaryInstruction(expritem.value); - } else { - replitem = new Instruction(expritem.type, expritem.value); - } - newexpression.push(replitem); - } - } else if (type === IEXPR) { - newexpression.push(new Instruction(IEXPR, substitute(item.value, variable, expr))); - } else { - newexpression.push(item); - } - } - return newexpression; - } - - function evaluate(tokens, expr, values) { - var nstack = []; - var n1, n2, n3; - var f; - for (var i = 0; i < tokens.length; i++) { - var item = tokens[i]; - var type = item.type; - if (type === INUMBER) { - nstack.push(item.value); - } else if (type === IOP2) { - n2 = nstack.pop(); - n1 = nstack.pop(); - if (item.value === 'and') { - nstack.push(n1 ? !!evaluate(n2, expr, values) : false); - } else if (item.value === 'or') { - nstack.push(n1 ? true : !!evaluate(n2, expr, values)); - } else { - f = expr.binaryOps[item.value]; - nstack.push(f(n1, n2)); - } - } else if (type === IOP3) { - n3 = nstack.pop(); - n2 = nstack.pop(); - n1 = nstack.pop(); - if (item.value === '?') { - nstack.push(evaluate(n1 ? n2 : n3, expr, values)); - } else { - f = expr.ternaryOps[item.value]; - nstack.push(f(n1, n2, n3)); - } - } else if (type === IVAR) { - if (item.value in expr.functions) { - nstack.push(expr.functions[item.value]); - } else { - var v = values[item.value]; - if (v !== undefined) { - nstack.push(v); - } else { - throw new Error('undefined variable: ' + item.value); - } - } - } else if (type === IOP1) { - n1 = nstack.pop(); - f = expr.unaryOps[item.value]; - nstack.push(f(n1)); - } else if (type === IFUNCALL) { - var argCount = item.value; - var args = []; - while (argCount-- > 0) { - args.unshift(nstack.pop()); - } - f = nstack.pop(); - if (f.apply && f.call) { - nstack.push(f.apply(undefined, args)); - } else { - throw new Error(f + ' is not a function'); - } - } else if (type === IEXPR) { - nstack.push(item.value); - } else if (type === IMEMBER) { - n1 = nstack.pop(); - nstack.push(n1[item.value]); - } else { - throw new Error('invalid Expression'); - } - } - if (nstack.length > 1) { - throw new Error('invalid Expression (parity)'); - } - return nstack[0]; - } - - function expressionToString(tokens, toJS) { - var nstack = []; - var n1, n2, n3; - var f; - for (var i = 0; i < tokens.length; i++) { - var item = tokens[i]; - var type = item.type; - if (type === INUMBER) { - if (typeof item.value === 'number' && item.value < 0) { - nstack.push('(' + item.value + ')'); - } else { - nstack.push(escapeValue(item.value)); - } - } else if (type === IOP2) { - n2 = nstack.pop(); - n1 = nstack.pop(); - f = item.value; - if (toJS) { - if (f === '^') { - nstack.push('Math.pow(' + n1 + ', ' + n2 + ')'); - } else if (f === 'and') { - nstack.push('(!!' + n1 + ' && !!' + n2 + ')'); - } else if (f === 'or') { - nstack.push('(!!' + n1 + ' || !!' + n2 + ')'); - } else if (f === '||') { - nstack.push('(String(' + n1 + ') + String(' + n2 + '))'); - } else if (f === '==') { - nstack.push('(' + n1 + ' === ' + n2 + ')'); - } else if (f === '!=') { - nstack.push('(' + n1 + ' !== ' + n2 + ')'); - } else { - nstack.push('(' + n1 + ' ' + f + ' ' + n2 + ')'); - } - } else { - nstack.push('(' + n1 + ' ' + f + ' ' + n2 + ')'); - } - } else if (type === IOP3) { - n3 = nstack.pop(); - n2 = nstack.pop(); - n1 = nstack.pop(); - f = item.value; - if (f === '?') { - nstack.push('(' + n1 + ' ? ' + n2 + ' : ' + n3 + ')'); - } else { - throw new Error('invalid Expression'); - } - } else if (type === IVAR) { - nstack.push(item.value); - } else if (type === IOP1) { - n1 = nstack.pop(); - f = item.value; - if (f === '-' || f === '+') { - nstack.push('(' + f + n1 + ')'); - } else if (toJS) { - if (f === 'not') { - nstack.push('(' + '!' + n1 + ')'); - } else if (f === '!') { - nstack.push('fac(' + n1 + ')'); - } else { - nstack.push(f + '(' + n1 + ')'); - } - } else if (f === '!') { - nstack.push('(' + n1 + '!)'); - } else { - nstack.push('(' + f + ' ' + n1 + ')'); - } - } else if (type === IFUNCALL) { - var argCount = item.value; - var args = []; - while (argCount-- > 0) { - args.unshift(nstack.pop()); - } - f = nstack.pop(); - nstack.push(f + '(' + args.join(', ') + ')'); - } else if (type === IMEMBER) { - n1 = nstack.pop(); - nstack.push(n1 + '.' + item.value); - } else if (type === IEXPR) { - nstack.push('(' + expressionToString(item.value, toJS) + ')'); - } else { - throw new Error('invalid Expression'); - } - } - if (nstack.length > 1) { - throw new Error('invalid Expression (parity)'); - } - return String(nstack[0]); - } - - function escapeValue(v) { - if (typeof v === 'string') { - return JSON.stringify(v).replace(/\u2028/g, '\\u2028').replace(/\u2029/g, '\\u2029'); - } - return v; - } - - function contains(array, obj) { - for (var i = 0; i < array.length; i++) { - if (array[i] === obj) { - return true; - } - } - return false; - } - - function getSymbols(tokens, symbols, options) { - options = options || {}; - var withMembers = !!options.withMembers; - var prevVar = null; - - for (var i = 0; i < tokens.length; i++) { - var item = tokens[i]; - if (item.type === IVAR && !contains(symbols, item.value)) { - if (!withMembers) { - symbols.push(item.value); - } else if (prevVar !== null) { - if (!contains(symbols, prevVar)) { - symbols.push(prevVar); - } - prevVar = item.value; - } else { - prevVar = item.value; - } - } else if (item.type === IMEMBER && withMembers && prevVar !== null) { - prevVar += '.' + item.value; - } else if (item.type === IEXPR) { - getSymbols(item.value, symbols, options); - } else if (prevVar !== null) { - if (!contains(symbols, prevVar)) { - symbols.push(prevVar); - } - prevVar = null; - } - } - - if (prevVar !== null && !contains(symbols, prevVar)) { - symbols.push(prevVar); - } - } - - function Expression(tokens, parser) { - this.tokens = tokens; - this.parser = parser; - this.unaryOps = parser.unaryOps; - this.binaryOps = parser.binaryOps; - this.ternaryOps = parser.ternaryOps; - this.functions = parser.functions; - } - - Expression.prototype.simplify = function (values) { - values = values || {}; - return new Expression(simplify(this.tokens, this.unaryOps, this.binaryOps, this.ternaryOps, values), this.parser); - }; - - Expression.prototype.substitute = function (variable, expr) { - if (!(expr instanceof Expression)) { - expr = this.parser.parse(String(expr)); - } - - return new Expression(substitute(this.tokens, variable, expr), this.parser); - }; - - Expression.prototype.evaluate = function (values) { - values = values || {}; - return evaluate(this.tokens, this, values); - }; - - Expression.prototype.toString = function () { - return expressionToString(this.tokens, false); - }; - - Expression.prototype.symbols = function (options) { - options = options || {}; - var vars = []; - getSymbols(this.tokens, vars, options); - return vars; - }; - - Expression.prototype.variables = function (options) { - options = options || {}; - var vars = []; - getSymbols(this.tokens, vars, options); - var functions = this.functions; - return vars.filter(function (name) { - return !(name in functions); - }); - }; - - Expression.prototype.toJSFunction = function (param, variables) { - var expr = this; - var f = new Function(param, 'with(this.functions) with (this.ternaryOps) with (this.binaryOps) with (this.unaryOps) { return ' + expressionToString(this.simplify(variables).tokens, true) + '; }'); // eslint-disable-line no-new-func - return function () { - return f.apply(expr, arguments); - }; - }; - - var TEOF = 'TEOF'; - var TOP = 'TOP'; - var TNUMBER = 'TNUMBER'; - var TSTRING = 'TSTRING'; - var TPAREN = 'TPAREN'; - var TCOMMA = 'TCOMMA'; - var TNAME = 'TNAME'; - - function Token(type, value, index) { - this.type = type; - this.value = value; - this.index = index; - } - - Token.prototype.toString = function () { - return this.type + ': ' + this.value; - }; - - function TokenStream(parser, expression) { - this.pos = 0; - this.current = null; - this.unaryOps = parser.unaryOps; - this.binaryOps = parser.binaryOps; - this.ternaryOps = parser.ternaryOps; - this.consts = parser.consts; - this.expression = expression; - this.savedPosition = 0; - this.savedCurrent = null; - this.options = parser.options; - } - - TokenStream.prototype.newToken = function (type, value, pos) { - return new Token(type, value, pos != null ? pos : this.pos); - }; - - TokenStream.prototype.save = function () { - this.savedPosition = this.pos; - this.savedCurrent = this.current; - }; - - TokenStream.prototype.restore = function () { - this.pos = this.savedPosition; - this.current = this.savedCurrent; - }; - - TokenStream.prototype.next = function () { - if (this.pos >= this.expression.length) { - return this.newToken(TEOF, 'EOF'); - } - - if (this.isWhitespace() || this.isComment()) { - return this.next(); - } else if (this.isRadixInteger() || - this.isNumber() || - this.isOperator() || - this.isString() || - this.isParen() || - this.isComma() || - this.isNamedOp() || - this.isConst() || - this.isName()) { - return this.current; - } else { - this.parseError('Unknown character "' + this.expression.charAt(this.pos) + '"'); - } - }; - - TokenStream.prototype.isString = function () { - var r = false; - var startPos = this.pos; - var quote = this.expression.charAt(startPos); - - if (quote === '\'' || quote === '"') { - var index = this.expression.indexOf(quote, startPos + 1); - while (index >= 0 && this.pos < this.expression.length) { - this.pos = index + 1; - if (this.expression.charAt(index - 1) !== '\\') { - var rawString = this.expression.substring(startPos + 1, index); - this.current = this.newToken(TSTRING, this.unescape(rawString), startPos); - r = true; - break; - } - index = this.expression.indexOf(quote, index + 1); - } - } - return r; - }; - - TokenStream.prototype.isParen = function () { - var c = this.expression.charAt(this.pos); - if (c === '(' || c === ')') { - this.current = this.newToken(TPAREN, c); - this.pos++; - return true; - } - return false; - }; - - TokenStream.prototype.isComma = function () { - var c = this.expression.charAt(this.pos); - if (c === ',') { - this.current = this.newToken(TCOMMA, ','); - this.pos++; - return true; - } - return false; - }; - - TokenStream.prototype.isConst = function () { - var startPos = this.pos; - var i = startPos; - for (; i < this.expression.length; i++) { - var c = this.expression.charAt(i); - if (c.toUpperCase() === c.toLowerCase()) { - if (i === this.pos || (c !== '_' && c !== '.' && (c < '0' || c > '9'))) { - break; - } - } - } - if (i > startPos) { - var str = this.expression.substring(startPos, i); - if (str in this.consts) { - this.current = this.newToken(TNUMBER, this.consts[str]); - this.pos += str.length; - return true; - } - } - return false; - }; - - TokenStream.prototype.isNamedOp = function () { - var startPos = this.pos; - var i = startPos; - for (; i < this.expression.length; i++) { - var c = this.expression.charAt(i); - if (c.toUpperCase() === c.toLowerCase()) { - if (i === this.pos || (c !== '_' && (c < '0' || c > '9'))) { - break; - } - } - } - if (i > startPos) { - var str = this.expression.substring(startPos, i); - if (this.isOperatorEnabled(str) && (str in this.binaryOps || str in this.unaryOps || str in this.ternaryOps)) { - this.current = this.newToken(TOP, str); - this.pos += str.length; - return true; - } - } - return false; - }; - - TokenStream.prototype.isName = function () { - var startPos = this.pos; - var i = startPos; - var hasLetter = false; - for (; i < this.expression.length; i++) { - var c = this.expression.charAt(i); - if (c.toUpperCase() === c.toLowerCase()) { - if (i === this.pos && (c === '$' || c === '_')) { - if (c === '_') { - hasLetter = true; - } - continue; - } else if (i === this.pos || !hasLetter || (c !== '_' && (c < '0' || c > '9'))) { - break; - } - } else { - hasLetter = true; - } - } - if (hasLetter) { - var str = this.expression.substring(startPos, i); - this.current = this.newToken(TNAME, str); - this.pos += str.length; - return true; - } - return false; - }; - - TokenStream.prototype.isWhitespace = function () { - var r = false; - var c = this.expression.charAt(this.pos); - while (c === ' ' || c === '\t' || c === '\n' || c === '\r') { - r = true; - this.pos++; - if (this.pos >= this.expression.length) { - break; - } - c = this.expression.charAt(this.pos); - } - return r; - }; - - var codePointPattern = /^[0-9a-f]{4}$/i; - - TokenStream.prototype.unescape = function (v) { - var index = v.indexOf('\\'); - if (index < 0) { - return v; - } - - var buffer = v.substring(0, index); - while (index >= 0) { - var c = v.charAt(++index); - switch (c) { - case '\'': - buffer += '\''; - break; - case '"': - buffer += '"'; - break; - case '\\': - buffer += '\\'; - break; - case '/': - buffer += '/'; - break; - case 'b': - buffer += '\b'; - break; - case 'f': - buffer += '\f'; - break; - case 'n': - buffer += '\n'; - break; - case 'r': - buffer += '\r'; - break; - case 't': - buffer += '\t'; - break; - case 'u': - // interpret the following 4 characters as the hex of the unicode code point - var codePoint = v.substring(index + 1, index + 5); - if (!codePointPattern.test(codePoint)) { - this.parseError('Illegal escape sequence: \\u' + codePoint); - } - buffer += String.fromCharCode(parseInt(codePoint, 16)); - index += 4; - break; - default: - throw this.parseError('Illegal escape sequence: "\\' + c + '"'); - } - ++index; - var backslash = v.indexOf('\\', index); - buffer += v.substring(index, backslash < 0 ? v.length : backslash); - index = backslash; - } - - return buffer; - }; - - TokenStream.prototype.isComment = function () { - var c = this.expression.charAt(this.pos); - if (c === '/' && this.expression.charAt(this.pos + 1) === '*') { - this.pos = this.expression.indexOf('*/', this.pos) + 2; - if (this.pos === 1) { - this.pos = this.expression.length; - } - return true; - } - return false; - }; - - TokenStream.prototype.isRadixInteger = function () { - var pos = this.pos; - - if (pos >= this.expression.length - 2 || this.expression.charAt(pos) !== '0') { - return false; - } - ++pos; - - var radix; - var validDigit; - if (this.expression.charAt(pos) === 'x') { - radix = 16; - validDigit = /^[0-9a-f]$/i; - ++pos; - } else if (this.expression.charAt(pos) === 'b') { - radix = 2; - validDigit = /^[01]$/i; - ++pos; - } else { - return false; - } - - var valid = false; - var startPos = pos; - - while (pos < this.expression.length) { - var c = this.expression.charAt(pos); - if (validDigit.test(c)) { - pos++; - valid = true; - } else { - break; - } - } - - if (valid) { - this.current = this.newToken(TNUMBER, parseInt(this.expression.substring(startPos, pos), radix)); - this.pos = pos; - } - return valid; - }; - - TokenStream.prototype.isNumber = function () { - var valid = false; - var pos = this.pos; - var startPos = pos; - var resetPos = pos; - var foundDot = false; - var foundDigits = false; - var c; - - while (pos < this.expression.length) { - c = this.expression.charAt(pos); - if ((c >= '0' && c <= '9') || (!foundDot && c === '.')) { - if (c === '.') { - foundDot = true; - } else { - foundDigits = true; - } - pos++; - valid = foundDigits; - } else { - break; - } - } - - if (valid) { - resetPos = pos; - } - - if (c === 'e' || c === 'E') { - pos++; - var acceptSign = true; - var validExponent = false; - while (pos < this.expression.length) { - c = this.expression.charAt(pos); - if (acceptSign && (c === '+' || c === '-')) { - acceptSign = false; - } else if (c >= '0' && c <= '9') { - validExponent = true; - acceptSign = false; - } else { - break; - } - pos++; - } - - if (!validExponent) { - pos = resetPos; - } - } - - if (valid) { - this.current = this.newToken(TNUMBER, parseFloat(this.expression.substring(startPos, pos))); - this.pos = pos; - } else { - this.pos = resetPos; - } - return valid; - }; - - TokenStream.prototype.isOperator = function () { - var startPos = this.pos; - var c = this.expression.charAt(this.pos); - - if (c === '+' || c === '-' || c === '*' || c === '/' || c === '%' || c === '^' || c === '?' || c === ':' || c === '.') { - this.current = this.newToken(TOP, c); - } else if (c === '∙' || c === '•') { - this.current = this.newToken(TOP, '*'); - } else if (c === '>') { - if (this.expression.charAt(this.pos + 1) === '=') { - this.current = this.newToken(TOP, '>='); - this.pos++; - } else { - this.current = this.newToken(TOP, '>'); - } - } else if (c === '<') { - if (this.expression.charAt(this.pos + 1) === '=') { - this.current = this.newToken(TOP, '<='); - this.pos++; - } else { - this.current = this.newToken(TOP, '<'); - } - } else if (c === '|') { - if (this.expression.charAt(this.pos + 1) === '|') { - this.current = this.newToken(TOP, '||'); - this.pos++; - } else { - return false; - } - } else if (c === '=') { - if (this.expression.charAt(this.pos + 1) === '=') { - this.current = this.newToken(TOP, '=='); - this.pos++; - } else { - return false; - } - } else if (c === '!') { - if (this.expression.charAt(this.pos + 1) === '=') { - this.current = this.newToken(TOP, '!='); - this.pos++; - } else { - this.current = this.newToken(TOP, c); - } - } else { - return false; - } - this.pos++; - - if (this.isOperatorEnabled(this.current.value)) { - return true; - } else { - this.pos = startPos; - return false; - } - }; - - var optionNameMap = { - '+': 'add', - '-': 'subtract', - '*': 'multiply', - '/': 'divide', - '%': 'remainder', - '^': 'power', - '!': 'factorial', - '<': 'comparison', - '>': 'comparison', - '<=': 'comparison', - '>=': 'comparison', - '==': 'comparison', - '!=': 'comparison', - '||': 'concatenate', - 'and': 'logical', - 'or': 'logical', - 'not': 'logical', - '?': 'conditional', - ':': 'conditional' - }; - - function getOptionName(op) { - return optionNameMap.hasOwnProperty(op) ? optionNameMap[op] : op; - } - - TokenStream.prototype.isOperatorEnabled = function (op) { - var optionName = getOptionName(op); - var operators = this.options.operators || {}; - - // in is a special case for now because it's disabled by default - if (optionName === 'in') { - return !!operators['in']; - } - - return !(optionName in operators) || !!operators[optionName]; - }; - - TokenStream.prototype.getCoordinates = function () { - var line = 0; - var column; - var newline = -1; - do { - line++; - column = this.pos - newline; - newline = this.expression.indexOf('\n', newline + 1); - } while (newline >= 0 && newline < this.pos); - - return { - line: line, - column: column - }; - }; - - TokenStream.prototype.parseError = function (msg) { - var coords = this.getCoordinates(); - throw new Error('parse error [' + coords.line + ':' + coords.column + ']: ' + msg); - }; - - function ParserState(parser, tokenStream, options) { - this.parser = parser; - this.tokens = tokenStream; - this.current = null; - this.nextToken = null; - this.next(); - this.savedCurrent = null; - this.savedNextToken = null; - this.allowMemberAccess = options.allowMemberAccess !== false; - } - - ParserState.prototype.next = function () { - this.current = this.nextToken; - return (this.nextToken = this.tokens.next()); - }; - - ParserState.prototype.tokenMatches = function (token, value) { - if (typeof value === 'undefined') { - return true; - } else if (Array.isArray(value)) { - return contains(value, token.value); - } else if (typeof value === 'function') { - return value(token); - } else { - return token.value === value; - } - }; - - ParserState.prototype.save = function () { - this.savedCurrent = this.current; - this.savedNextToken = this.nextToken; - this.tokens.save(); - }; - - ParserState.prototype.restore = function () { - this.tokens.restore(); - this.current = this.savedCurrent; - this.nextToken = this.savedNextToken; - }; - - ParserState.prototype.accept = function (type, value) { - if (this.nextToken.type === type && this.tokenMatches(this.nextToken, value)) { - this.next(); - return true; - } - return false; - }; - - ParserState.prototype.expect = function (type, value) { - if (!this.accept(type, value)) { - var coords = this.tokens.getCoordinates(); - throw new Error('parse error [' + coords.line + ':' + coords.column + ']: Expected ' + (value || type)); - } - }; - - ParserState.prototype.parseAtom = function (instr) { - if (this.accept(TNAME)) { - instr.push(new Instruction(IVAR, this.current.value)); - } else if (this.accept(TNUMBER)) { - instr.push(new Instruction(INUMBER, this.current.value)); - } else if (this.accept(TSTRING)) { - instr.push(new Instruction(INUMBER, this.current.value)); - } else if (this.accept(TPAREN, '(')) { - this.parseExpression(instr); - this.expect(TPAREN, ')'); - } else { - throw new Error('unexpected ' + this.nextToken); - } - }; - - ParserState.prototype.parseExpression = function (instr) { - this.parseConditionalExpression(instr); - }; - - ParserState.prototype.parseConditionalExpression = function (instr) { - this.parseOrExpression(instr); - while (this.accept(TOP, '?')) { - var trueBranch = []; - var falseBranch = []; - this.parseConditionalExpression(trueBranch); - this.expect(TOP, ':'); - this.parseConditionalExpression(falseBranch); - instr.push(new Instruction(IEXPR, trueBranch)); - instr.push(new Instruction(IEXPR, falseBranch)); - instr.push(ternaryInstruction('?')); - } - }; - - ParserState.prototype.parseOrExpression = function (instr) { - this.parseAndExpression(instr); - while (this.accept(TOP, 'or')) { - var falseBranch = []; - this.parseAndExpression(falseBranch); - instr.push(new Instruction(IEXPR, falseBranch)); - instr.push(binaryInstruction('or')); - } - }; - - ParserState.prototype.parseAndExpression = function (instr) { - this.parseComparison(instr); - while (this.accept(TOP, 'and')) { - var trueBranch = []; - this.parseComparison(trueBranch); - instr.push(new Instruction(IEXPR, trueBranch)); - instr.push(binaryInstruction('and')); - } - }; - - var COMPARISON_OPERATORS = ['==', '!=', '<', '<=', '>=', '>', 'in']; - - ParserState.prototype.parseComparison = function (instr) { - this.parseAddSub(instr); - while (this.accept(TOP, COMPARISON_OPERATORS)) { - var op = this.current; - this.parseAddSub(instr); - instr.push(binaryInstruction(op.value)); - } - }; - - var ADD_SUB_OPERATORS = ['+', '-', '||']; - - ParserState.prototype.parseAddSub = function (instr) { - this.parseTerm(instr); - while (this.accept(TOP, ADD_SUB_OPERATORS)) { - var op = this.current; - this.parseTerm(instr); - instr.push(binaryInstruction(op.value)); - } - }; - - var TERM_OPERATORS = ['*', '/', '%']; - - ParserState.prototype.parseTerm = function (instr) { - this.parseFactor(instr); - while (this.accept(TOP, TERM_OPERATORS)) { - var op = this.current; - this.parseFactor(instr); - instr.push(binaryInstruction(op.value)); - } - }; - - ParserState.prototype.parseFactor = function (instr) { - var unaryOps = this.tokens.unaryOps; - function isPrefixOperator(token) { - return token.value in unaryOps; - } - - this.save(); - if (this.accept(TOP, isPrefixOperator)) { - if ((this.current.value !== '-' && this.current.value !== '+' && this.nextToken.type === TPAREN && this.nextToken.value === '(')) { - this.restore(); - this.parseExponential(instr); - } else { - var op = this.current; - this.parseFactor(instr); - instr.push(unaryInstruction(op.value)); - } - } else { - this.parseExponential(instr); - } - }; - - ParserState.prototype.parseExponential = function (instr) { - this.parsePostfixExpression(instr); - while (this.accept(TOP, '^')) { - this.parseFactor(instr); - instr.push(binaryInstruction('^')); - } - }; - - ParserState.prototype.parsePostfixExpression = function (instr) { - this.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, '.')) { - if (!this.allowMemberAccess) { - throw new Error('unexpected ".", member access is not permitted'); - } - - this.expect(TNAME); - instr.push(new Instruction(IMEMBER, this.current.value)); - } - }; - - function add(a, b) { - return Number(a) + Number(b); - } - - function sub(a, b) { - return a - b; - } - - function mul(a, b) { - return a * b; - } - - function div(a, b) { - return a / b; - } - - function mod(a, b) { - return a % b; - } - - function concat(a, b) { - return '' + a + b; - } - - function equal(a, b) { - return a === b; - } - - function notEqual(a, b) { - return a !== b; - } - - function greaterThan(a, b) { - return a > b; - } - - function lessThan(a, b) { - return a < b; - } - - function greaterThanEqual(a, b) { - return a >= b; - } - - function lessThanEqual(a, b) { - return a <= b; - } - - function andOperator(a, b) { - return Boolean(a && b); - } - - function orOperator(a, b) { - return Boolean(a || b); - } - - function inOperator(a, b) { - return contains(b, a); - } - - function sinh(a) { - return ((Math.exp(a) - Math.exp(-a)) / 2); - } - - function cosh(a) { - return ((Math.exp(a) + Math.exp(-a)) / 2); - } - - function tanh(a) { - if (a === Infinity) return 1; - if (a === -Infinity) return -1; - return (Math.exp(a) - Math.exp(-a)) / (Math.exp(a) + Math.exp(-a)); - } - - function asinh(a) { - if (a === -Infinity) return a; - return Math.log(a + Math.sqrt((a * a) + 1)); - } - - function acosh(a) { - return Math.log(a + Math.sqrt((a * a) - 1)); - } - - function atanh(a) { - return (Math.log((1 + a) / (1 - a)) / 2); - } - - function log10(a) { - return Math.log(a) * Math.LOG10E; - } - - function neg(a) { - return -a; - } - - function not(a) { - return !a; - } - - function trunc(a) { - return a < 0 ? Math.ceil(a) : Math.floor(a); - } - - function random(a) { - return Math.random() * (a || 1); - } - - function factorial(a) { // a! - return gamma(a + 1); - } - - function isInteger(value) { - return isFinite(value) && (value === Math.round(value)); - } - - var GAMMA_G = 4.7421875; - var GAMMA_P = [ - 0.99999999999999709182, - 57.156235665862923517, -59.597960355475491248, - 14.136097974741747174, -0.49191381609762019978, - 0.33994649984811888699e-4, - 0.46523628927048575665e-4, -0.98374475304879564677e-4, - 0.15808870322491248884e-3, -0.21026444172410488319e-3, - 0.21743961811521264320e-3, -0.16431810653676389022e-3, - 0.84418223983852743293e-4, -0.26190838401581408670e-4, - 0.36899182659531622704e-5 - ]; - - // Gamma function from math.js - function gamma(n) { - var t, x; - - if (isInteger(n)) { - if (n <= 0) { - return isFinite(n) ? Infinity : NaN; - } - - if (n > 171) { - return Infinity; // Will overflow - } - - var value = n - 2; - var res = n - 1; - while (value > 1) { - res *= value; - value--; - } - - if (res === 0) { - res = 1; // 0! is per definition 1 - } - - return res; - } - - if (n < 0.5) { - return Math.PI / (Math.sin(Math.PI * n) * gamma(1 - n)); - } - - if (n >= 171.35) { - return Infinity; // will overflow - } - - if (n > 85.0) { // Extended Stirling Approx - var twoN = n * n; - var threeN = twoN * n; - var fourN = threeN * n; - var fiveN = fourN * n; - return Math.sqrt(2 * Math.PI / n) * Math.pow((n / Math.E), n) * - (1 + (1 / (12 * n)) + (1 / (288 * twoN)) - (139 / (51840 * threeN)) - - (571 / (2488320 * fourN)) + (163879 / (209018880 * fiveN)) + - (5246819 / (75246796800 * fiveN * n))); - } - - --n; - x = GAMMA_P[0]; - for (var i = 1; i < GAMMA_P.length; ++i) { - x += GAMMA_P[i] / (n + i); - } - - t = n + GAMMA_G + 0.5; - return Math.sqrt(2 * Math.PI) * Math.pow(t, n + 0.5) * Math.exp(-t) * x; - } - - function stringLength(s) { - return String(s).length; - } - - function hypot() { - var sum = 0; - var larg = 0; - for (var i = 0; i < arguments.length; i++) { - var arg = Math.abs(arguments[i]); - var div; - if (larg < arg) { - div = larg / arg; - sum = (sum * div * div) + 1; - larg = arg; - } else if (arg > 0) { - div = arg / larg; - sum += div * div; - } else { - sum += arg; - } - } - return larg === Infinity ? Infinity : larg * Math.sqrt(sum); - } - - function condition(cond, yep, nope) { - return cond ? yep : nope; - } - - /** - * Decimal adjustment of a number. - * From @escopecz. - * - * @param {Number} value The number. - * @param {Integer} exp The exponent (the 10 logarithm of the adjustment base). - * @return {Number} The adjusted value. - */ - function roundTo(value, exp) { - // If the exp is undefined or zero... - if (typeof exp === 'undefined' || +exp === 0) { - return Math.round(value); - } - value = +value; - exp = -(+exp); - // If the value is not a number or the exp is not an integer... - if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) { - return NaN; - } - // Shift - value = value.toString().split('e'); - value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp))); - // Shift back - value = value.toString().split('e'); - return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)); - } - - function Parser(options) { - this.options = options || {}; - this.unaryOps = { - sin: Math.sin, - cos: Math.cos, - tan: Math.tan, - asin: Math.asin, - acos: Math.acos, - atan: Math.atan, - sinh: Math.sinh || sinh, - cosh: Math.cosh || cosh, - tanh: Math.tanh || tanh, - asinh: Math.asinh || asinh, - acosh: Math.acosh || acosh, - atanh: Math.atanh || atanh, - sqrt: Math.sqrt, - log: Math.log, - ln: Math.log, - lg: Math.log10 || log10, - log10: Math.log10 || log10, - abs: Math.abs, - ceil: Math.ceil, - floor: Math.floor, - round: Math.round, - trunc: Math.trunc || trunc, - '-': neg, - '+': Number, - exp: Math.exp, - not: not, - length: stringLength, - '!': factorial - }; - - this.binaryOps = { - '+': add, - '-': sub, - '*': mul, - '/': div, - '%': mod, - '^': Math.pow, - '||': concat, - '==': equal, - '!=': notEqual, - '>': greaterThan, - '<': lessThan, - '>=': greaterThanEqual, - '<=': lessThanEqual, - and: andOperator, - or: orOperator, - 'in': inOperator - }; - - this.ternaryOps = { - '?': condition - }; - - this.functions = { - random: random, - fac: factorial, - min: Math.min, - max: Math.max, - hypot: Math.hypot || hypot, - pyt: Math.hypot || hypot, // backward compat - pow: Math.pow, - atan2: Math.atan2, - 'if': condition, - gamma: gamma, - roundTo: roundTo - }; - - this.consts = { - E: Math.E, - PI: Math.PI, - 'true': true, - 'false': false - }; - } - - Parser.prototype.parse = function (expr) { - var instr = []; - var parserState = new ParserState( - this, - new TokenStream(this, expr), - { allowMemberAccess: this.options.allowMemberAccess } - ); - - parserState.parseExpression(instr); - parserState.expect(TEOF, 'EOF'); - - return new Expression(instr, this); - }; - - Parser.prototype.evaluate = function (expr, variables) { - return this.parse(expr).evaluate(variables); - }; - - var sharedParser = new Parser(); - - Parser.parse = function (expr) { - return sharedParser.parse(expr); - }; - - Parser.evaluate = function (expr, variables) { - return sharedParser.parse(expr).evaluate(variables); - }; - - /*! - Based on ndef.parser, by Raphael Graf(r@undefined.ch) - http://www.undefined.ch/mparser/index.html - - Ported to JavaScript and modified by Matthew Crumley (email@matthewcrumley.com, http://silentmatt.com/) - - You are free to use and modify this code in anyway you find useful. Please leave this comment in the code - to acknowledge its original source. If you feel like it, I enjoy hearing about projects that use my code, - but don't feel like you have to let me know or ask permission. - */ - - var index = { - Parser: Parser, - Expression: Expression - }; - - return index; - -}))); diff --git a/dist/bundle.min.js b/dist/bundle.min.js deleted file mode 100644 index 4f1c171..0000000 --- a/dist/bundle.min.js +++ /dev/null @@ -1 +0,0 @@ -!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):t.exprEval=e()}(this,function(){"use strict";var t="INUMBER",e="IOP1",s="IOP2",r="IOP3",n="IVAR",i="IFUNCALL",o="IEXPR",a="IMEMBER";function h(t,e){this.type=t,this.value=void 0!==e&&null!==e?e:0}function p(t){return new h(e,t)}function u(t){return new h(s,t)}function c(t){return new h(r,t)}function l(h,p){for(var u,c,v,x,y=[],w=0;w0;)E.unshift(y.pop());x=y.pop(),y.push(x+"("+E.join(", ")+")")}else if(M===a)u=y.pop(),y.push(u+"."+d.value);else{if(M!==o)throw new Error("invalid Expression");y.push("("+l(d.value,p)+")")}}if(y.length>1)throw new Error("invalid Expression (parity)");return String(y[0])}function f(t){return"string"==typeof t?JSON.stringify(t).replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029"):t}function v(t,e){for(var s=0;s1)x=d.pop(),v=d.pop(),w=c[E.value],E=new h(t,w(v.value,x.value)),d.push(E);else if(k===r&&d.length>2)y=d.pop(),x=d.pop(),v=d.pop(),"?"===E.value?d.push(v.value?x.value:y.value):(w=l[E.value],E=new h(t,w(v.value,x.value,y.value)),d.push(E));else if(k===e&&d.length>0)v=d.pop(),w=u[E.value],E=new h(t,w(v.value)),d.push(E);else if(k===o){var b=i(E.value,u,c,l,f);if(1===b.length&&b[0].type===t)d.push(b[0]);else{for(;d.length>0;)M.push(d.shift());M.push(new h(o,b))}}else if(k===a&&d.length>0)v=d.pop(),d.push(new h(t,v.value[E.value]));else{for(;d.length>0;)M.push(d.shift());M.push(E)}}for(;d.length>0;)M.push(d.shift());return M}(this.tokens,this.unaryOps,this.binaryOps,this.ternaryOps,i),this.parser)},y.prototype.substitute=function(t,i){return i instanceof y||(i=this.parser.parse(String(i))),new y(function t(i,a,l){for(var f=[],v=0;v0;)k.unshift(y.pop());if(!(x=y.pop()).apply||!x.call)throw new Error(x+" is not a function");y.push(x.apply(void 0,k))}else if(M===o)y.push(d.value);else{if(M!==a)throw new Error("invalid Expression");l=y.pop(),y.push(l[d.value])}}if(y.length>1)throw new Error("invalid Expression (parity)");return y[0]}(this.tokens,this,h)},y.prototype.toString=function(){return l(this.tokens,!1)},y.prototype.symbols=function(t){t=t||{};var e=[];return x(this.tokens,e,t),e},y.prototype.variables=function(t){t=t||{};var e=[];x(this.tokens,e,t);var s=this.functions;return e.filter(function(t){return!(t in s)})},y.prototype.toJSFunction=function(t,e){var s=this,r=new Function(t,"with(this.functions) with (this.ternaryOps) with (this.binaryOps) with (this.unaryOps) { return "+l(this.simplify(e).tokens,!0)+"; }");return function(){return r.apply(s,arguments)}};var w="TOP";function d(t,e,s){this.type=t,this.value=e,this.index=s}function M(t,e){this.pos=0,this.current=null,this.unaryOps=t.unaryOps,this.binaryOps=t.binaryOps,this.ternaryOps=t.ternaryOps,this.consts=t.consts,this.expression=e,this.savedPosition=0,this.savedCurrent=null,this.options=t.options}d.prototype.toString=function(){return this.type+": "+this.value},M.prototype.newToken=function(t,e,s){return new d(t,e,null!=s?s:this.pos)},M.prototype.save=function(){this.savedPosition=this.pos,this.savedCurrent=this.current},M.prototype.restore=function(){this.pos=this.savedPosition,this.current=this.savedCurrent},M.prototype.next=function(){return this.pos>=this.expression.length?this.newToken("TEOF","EOF"):this.isWhitespace()||this.isComment()?this.next():this.isRadixInteger()||this.isNumber()||this.isOperator()||this.isString()||this.isParen()||this.isComma()||this.isNamedOp()||this.isConst()||this.isName()?this.current:void this.parseError('Unknown character "'+this.expression.charAt(this.pos)+'"')},M.prototype.isString=function(){var t=!1,e=this.pos,s=this.expression.charAt(e);if("'"===s||'"'===s)for(var r=this.expression.indexOf(s,e+1);r>=0&&this.pos"9")))break}if(e>t){var r=this.expression.substring(t,e);if(r in this.consts)return this.current=this.newToken("TNUMBER",this.consts[r]),this.pos+=r.length,!0}return!1},M.prototype.isNamedOp=function(){for(var t=this.pos,e=t;e"9")))break}if(e>t){var r=this.expression.substring(t,e);if(this.isOperatorEnabled(r)&&(r in this.binaryOps||r in this.unaryOps||r in this.ternaryOps))return this.current=this.newToken(w,r),this.pos+=r.length,!0}return!1},M.prototype.isName=function(){for(var t=this.pos,e=t,s=!1;e"9"))break}else s=!0}if(s){var n=this.expression.substring(t,e);return this.current=this.newToken("TNAME",n),this.pos+=n.length,!0}return!1},M.prototype.isWhitespace=function(){for(var t=!1,e=this.expression.charAt(this.pos);!(" "!==e&&"\t"!==e&&"\n"!==e&&"\r"!==e||(t=!0,this.pos++,this.pos>=this.expression.length));)e=this.expression.charAt(this.pos);return t};var g=/^[0-9a-f]{4}$/i;M.prototype.unescape=function(t){var e=t.indexOf("\\");if(e<0)return t;for(var s=t.substring(0,e);e>=0;){var r=t.charAt(++e);switch(r){case"'":s+="'";break;case'"':s+='"';break;case"\\":s+="\\";break;case"/":s+="/";break;case"b":s+="\b";break;case"f":s+="\f";break;case"n":s+="\n";break;case"r":s+="\r";break;case"t":s+="\t";break;case"u":var n=t.substring(e+1,e+5);g.test(n)||this.parseError("Illegal escape sequence: \\u"+n),s+=String.fromCharCode(parseInt(n,16)),e+=4;break;default:throw this.parseError('Illegal escape sequence: "\\'+r+'"')}++e;var i=t.indexOf("\\",e);s+=t.substring(e,i<0?t.length:i),e=i}return s},M.prototype.isComment=function(){return"/"===this.expression.charAt(this.pos)&&"*"===this.expression.charAt(this.pos+1)&&(this.pos=this.expression.indexOf("*/",this.pos)+2,1===this.pos&&(this.pos=this.expression.length),!0)},M.prototype.isRadixInteger=function(){var t,e,s=this.pos;if(s>=this.expression.length-2||"0"!==this.expression.charAt(s))return!1;if(++s,"x"===this.expression.charAt(s))t=16,e=/^[0-9a-f]$/i,++s;else{if("b"!==this.expression.charAt(s))return!1;t=2,e=/^[01]$/i,++s}for(var r=!1,n=s;s="0"&&t<="9"||!i&&"."===t);)"."===t?i=!0:o=!0,s++,e=o;if(e&&(n=s),"e"===t||"E"===t){s++;for(var a=!0,h=!1;s="0"&&t<="9"))break;h=!0,a=!1}else a=!1;s++}h||(s=n)}return e?(this.current=this.newToken("TNUMBER",parseFloat(this.expression.substring(r,s))),this.pos=s):this.pos=n,e},M.prototype.isOperator=function(){var t=this.pos,e=this.expression.charAt(this.pos);if("+"===e||"-"===e||"*"===e||"/"===e||"%"===e||"^"===e||"?"===e||":"===e||"."===e)this.current=this.newToken(w,e);else if("∙"===e||"•"===e)this.current=this.newToken(w,"*");else if(">"===e)"="===this.expression.charAt(this.pos+1)?(this.current=this.newToken(w,">="),this.pos++):this.current=this.newToken(w,">");else if("<"===e)"="===this.expression.charAt(this.pos+1)?(this.current=this.newToken(w,"<="),this.pos++):this.current=this.newToken(w,"<");else if("|"===e){if("|"!==this.expression.charAt(this.pos+1))return!1;this.current=this.newToken(w,"||"),this.pos++}else if("="===e){if("="!==this.expression.charAt(this.pos+1))return!1;this.current=this.newToken(w,"=="),this.pos++}else{if("!"!==e)return!1;"="===this.expression.charAt(this.pos+1)?(this.current=this.newToken(w,"!="),this.pos++):this.current=this.newToken(w,e)}return this.pos++,!!this.isOperatorEnabled(this.current.value)||(this.pos=t,!1)};var E={"+":"add","-":"subtract","*":"multiply","/":"divide","%":"remainder","^":"power","!":"factorial","<":"comparison",">":"comparison","<=":"comparison",">=":"comparison","==":"comparison","!=":"comparison","||":"concatenate",and:"logical",or:"logical",not:"logical","?":"conditional",":":"conditional"};function k(t,e,s){this.parser=t,this.tokens=e,this.current=null,this.nextToken=null,this.next(),this.savedCurrent=null,this.savedNextToken=null,this.allowMemberAccess=!1!==s.allowMemberAccess}M.prototype.isOperatorEnabled=function(t){var e=function(t){return E.hasOwnProperty(t)?E[t]:t}(t),s=this.options.operators||{};return"in"===e?!!s.in:!(e in s&&!s[e])},M.prototype.getCoordinates=function(){var t,e=0,s=-1;do{e++,t=this.pos-s,s=this.expression.indexOf("\n",s+1)}while(s>=0&&s=",">","in"];k.prototype.parseComparison=function(t){for(this.parseAddSub(t);this.accept(w,b);){var e=this.current;this.parseAddSub(t),t.push(u(e.value))}};var m=["+","-","||"];k.prototype.parseAddSub=function(t){for(this.parseTerm(t);this.accept(w,m);){var e=this.current;this.parseTerm(t),t.push(u(e.value))}};var T=["*","/","%"];function A(t,e){return Number(t)+Number(e)}function O(t,e){return t-e}function N(t,e){return t*e}function C(t,e){return t/e}function P(t,e){return t%e}function I(t,e){return""+t+e}function S(t,e){return t===e}function R(t,e){return t!==e}function F(t,e){return t>e}function L(t,e){return t=e}function q(t,e){return t<=e}function B(t,e){return Boolean(t&&e)}function _(t,e){return Boolean(t||e)}function $(t,e){return v(e,t)}function G(t){return(Math.exp(t)-Math.exp(-t))/2}function j(t){return(Math.exp(t)+Math.exp(-t))/2}function J(t){return t===1/0?1:t===-1/0?-1:(Math.exp(t)-Math.exp(-t))/(Math.exp(t)+Math.exp(-t))}function W(t){return t===-1/0?t:Math.log(t+Math.sqrt(t*t+1))}function V(t){return Math.log(t+Math.sqrt(t*t-1))}function X(t){return Math.log((1+t)/(1-t))/2}function z(t){return Math.log(t)*Math.LOG10E}function D(t){return-t}function H(t){return!t}function K(t){return t<0?Math.ceil(t):Math.floor(t)}function Q(t){return Math.random()*(t||1)}function Y(t){return et(t+1)}k.prototype.parseTerm=function(t){for(this.parseFactor(t);this.accept(w,T);){var e=this.current;this.parseFactor(t),t.push(u(e.value))}},k.prototype.parseFactor=function(t){var e=this.tokens.unaryOps;if(this.save(),this.accept(w,function(t){return t.value in e}))if("-"!==this.current.value&&"+"!==this.current.value&&"TPAREN"===this.nextToken.type&&"("===this.nextToken.value)this.restore(),this.parseExponential(t);else{var s=this.current;this.parseFactor(t),t.push(p(s.value))}else this.parseExponential(t)},k.prototype.parseExponential=function(t){for(this.parsePostfixExpression(t);this.accept(w,"^");)this.parseFactor(t),t.push(u("^"))},k.prototype.parsePostfixExpression=function(t){for(this.parseFunctionCall(t);this.accept(w,"!");)t.push(p("!"))},k.prototype.parseFunctionCall=function(t){var e=this.tokens.unaryOps;if(this.accept(w,function(t){return t.value in e})){var s=this.current;this.parseAtom(t),t.push(p(s.value))}else for(this.parseMemberExpression(t);this.accept("TPAREN","(");)if(this.accept("TPAREN",")"))t.push(new h(i,0));else{var r=this.parseArgumentList(t);t.push(new h(i,r))}},k.prototype.parseArgumentList=function(t){for(var e=0;!this.accept("TPAREN",")");)for(this.parseExpression(t),++e;this.accept("TCOMMA");)this.parseExpression(t),++e;return e},k.prototype.parseMemberExpression=function(t){for(this.parseAtom(t);this.accept(w,".");){if(!this.allowMemberAccess)throw new Error('unexpected ".", member access is not permitted');this.expect("TNAME"),t.push(new h(a,this.current.value))}};var Z=4.7421875,tt=[.9999999999999971,57.15623566586292,-59.59796035547549,14.136097974741746,-.4919138160976202,3399464998481189e-20,4652362892704858e-20,-9837447530487956e-20,.0001580887032249125,-.00021026444172410488,.00021743961811521265,-.0001643181065367639,8441822398385275e-20,-26190838401581408e-21,36899182659531625e-22];function et(t){var e,s;if(function(t){return isFinite(t)&&t===Math.round(t)}(t)){if(t<=0)return isFinite(t)?1/0:NaN;if(t>171)return 1/0;for(var r=t-2,n=t-1;r>1;)n*=r,r--;return 0===n&&(n=1),n}if(t<.5)return Math.PI/(Math.sin(Math.PI*t)*et(1-t));if(t>=171.35)return 1/0;if(t>85){var i=t*t,o=i*t,a=o*t,h=a*t;return Math.sqrt(2*Math.PI/t)*Math.pow(t/Math.E,t)*(1+1/(12*t)+1/(288*i)-139/(51840*o)-571/(2488320*a)+163879/(209018880*h)+5246819/(75246796800*h*t))}--t,s=tt[0];for(var p=1;p0?(r=n/e)*r:n}return e===1/0?1/0:e*Math.sqrt(t)}function nt(t,e,s){return t?e:s}function it(t,e){return void 0===e||0==+e?Math.round(t):(t=+t,e=-+e,isNaN(t)||"number"!=typeof e||e%1!=0?NaN:(t=t.toString().split("e"),+((t=(t=Math.round(+(t[0]+"e"+(t[1]?+t[1]-e:-e)))).toString().split("e"))[0]+"e"+(t[1]?+t[1]+e:e))))}function ot(t){this.options=t||{},this.unaryOps={sin:Math.sin,cos:Math.cos,tan:Math.tan,asin:Math.asin,acos:Math.acos,atan:Math.atan,sinh:Math.sinh||G,cosh:Math.cosh||j,tanh:Math.tanh||J,asinh:Math.asinh||W,acosh:Math.acosh||V,atanh:Math.atanh||X,sqrt:Math.sqrt,log:Math.log,ln:Math.log,lg:Math.log10||z,log10:Math.log10||z,abs:Math.abs,ceil:Math.ceil,floor:Math.floor,round:Math.round,trunc:Math.trunc||K,"-":D,"+":Number,exp:Math.exp,not:H,length:st,"!":Y},this.binaryOps={"+":A,"-":O,"*":N,"/":C,"%":P,"^":Math.pow,"||":I,"==":S,"!=":R,">":F,"<":L,">=":U,"<=":q,and:B,or:_,in:$},this.ternaryOps={"?":nt},this.functions={random:Q,fac:Y,min:Math.min,max:Math.max,hypot:Math.hypot||rt,pyt:Math.hypot||rt,pow:Math.pow,atan2:Math.atan2,if:nt,gamma:et,roundTo:it},this.consts={E:Math.E,PI:Math.PI,true:!0,false:!1}}ot.prototype.parse=function(t){var e=[],s=new k(this,new M(this,t),{allowMemberAccess:this.options.allowMemberAccess});return s.parseExpression(e),s.expect("TEOF","EOF"),new y(e,this)},ot.prototype.evaluate=function(t,e){return this.parse(t).evaluate(e)};var at=new ot;return ot.parse=function(t){return at.parse(t)},ot.evaluate=function(t,e){return at.parse(t).evaluate(e)},{Parser:ot,Expression:y}}); diff --git a/package-lock.json b/package-lock.json index ab41d32..f8e76ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "expr-eval", - "version": "1.2.2", + "version": "1.2.0", "lockfileVersion": 1, "requires": true, "dependencies": { From 4ec7272c9000bd2f77784cbb000de7f049f8281b Mon Sep 17 00:00:00 2001 From: Daniel Mittelman Date: Tue, 14 Aug 2018 18:40:39 +0300 Subject: [PATCH 5/5] Changed how expressions are simplified to eliminate constant repetitions --- src/simplify.js | 104 ++++++++++++++++++++++++--------------------- test/expression.js | 4 +- 2 files changed, 57 insertions(+), 51 deletions(-) diff --git a/src/simplify.js b/src/simplify.js index e68845c..1cf6b2f 100644 --- a/src/simplify.js +++ b/src/simplify.js @@ -1,57 +1,63 @@ import { Instruction, INUMBER, IOP1, IOP2, IOP3, IVAR, IEXPR, IMEMBER } from './instruction'; export default function simplify(tokens, unaryOps, binaryOps, ternaryOps, values) { - var nstack = []; - var newexpression = []; - var n1, n2, n3; - var f; - for (var i = 0; i < tokens.length; i++) { - var item = tokens[i]; - var type = item.type; - if (type === INUMBER) { - nstack.push(item); - } else if (type === IVAR && values.hasOwnProperty(item.value)) { - item = new Instruction(INUMBER, values[item.value]); - nstack.push(item); - } else if (type === IOP2 && nstack.length > 1) { - n2 = nstack.pop(); - n1 = nstack.pop(); - f = binaryOps[item.value]; - item = new Instruction(INUMBER, f(n1.value, n2.value)); - nstack.push(item); - } else if (type === IOP3 && nstack.length > 2) { - n3 = nstack.pop(); - n2 = nstack.pop(); - n1 = nstack.pop(); - if (item.value === '?') { - nstack.push(n1.value ? n2.value : n3.value); - } else { - f = ternaryOps[item.value]; - item = new Instruction(INUMBER, f(n1.value, n2.value, n3.value)); + var nstack = []; + var newexpression = []; + var n1, n2, n3; + var f; + for (var i = 0; i < tokens.length; i++) { + var item = tokens[i]; + var type = item.type; + if (type === INUMBER) { nstack.push(item); + } else if (type === IVAR && values.hasOwnProperty(item.value)) { + item = new Instruction(INUMBER, values[item.value]); + nstack.push(item); + } else if (type === IOP2 && nstack.length > 1) { + n2 = nstack.pop(); + n1 = nstack.pop(); + f = binaryOps[item.value]; + item = new Instruction(INUMBER, f(n1.value, n2.value)); + nstack.push(item); + } else if (type === IOP3 && nstack.length > 2) { + n3 = nstack.pop(); + n2 = nstack.pop(); + n1 = nstack.pop(); + if (item.value === '?') { + nstack.push(n1.value ? n2.value : n3.value); + } else { + f = ternaryOps[item.value]; + item = new Instruction(INUMBER, f(n1.value, n2.value, n3.value)); + nstack.push(item); + } + } else if (type === IOP1 && nstack.length > 0) { + n1 = nstack.pop(); + f = unaryOps[item.value]; + item = new Instruction(INUMBER, f(n1.value)); + nstack.push(item); + } else if (type === IEXPR) { + var simplified = simplify(item.value, unaryOps, binaryOps, ternaryOps, values); + if(simplified.length === 1 && simplified[0].type === INUMBER) { + nstack.push(simplified[0]); } - } else if (type === IOP1 && nstack.length > 0) { - n1 = nstack.pop(); - f = unaryOps[item.value]; - item = new Instruction(INUMBER, f(n1.value)); - nstack.push(item); - } else if (type === IEXPR) { - while (nstack.length > 0) { - newexpression.push(nstack.shift()); - } - newexpression.push(new Instruction(IEXPR, simplify(item.value, unaryOps, binaryOps, ternaryOps, values))); - } else if (type === IMEMBER && nstack.length > 0) { - n1 = nstack.pop(); - nstack.push(new Instruction(INUMBER, n1.value[item.value])); - } else { - while (nstack.length > 0) { - newexpression.push(nstack.shift()); + else { + while (nstack.length > 0) { + newexpression.push(nstack.shift()); + } + newexpression.push(new Instruction(IEXPR, simplified)); + } + } else if (type === IMEMBER && nstack.length > 0) { + n1 = nstack.pop(); + nstack.push(new Instruction(INUMBER, n1.value[item.value])); + } else { + while (nstack.length > 0) { + newexpression.push(nstack.shift()); + } + newexpression.push(item); } - newexpression.push(item); } + while (nstack.length > 0) { + newexpression.push(nstack.shift()); + } + return newexpression; } - while (nstack.length > 0) { - newexpression.push(nstack.shift()); - } - return newexpression; -} diff --git a/test/expression.js b/test/expression.js index 51d24f1..d6a8d83 100644 --- a/test/expression.js +++ b/test/expression.js @@ -172,11 +172,11 @@ describe('Expression', function () { }); it('x ? (y + 1) : z', function () { - assert.strictEqual(Parser.parse('x ? (y + 1) : z').simplify({ y: 2 }).toString(), '(x ? (3) : (z))'); + assert.strictEqual(Parser.parse('x ? (y + 1) : z').simplify({ y: 2 }).toString(), '(x ? 3 : (z))'); }); it('x ? y : (z * 4)', function () { - assert.strictEqual(Parser.parse('x ? y : (z * 4)').simplify({ z: 3 }).toString(), '(x ? (y) : (12))'); + assert.strictEqual(Parser.parse('x ? y : (z * 4)').simplify({ z: 3 }).toString(), '(x ? (y) : 12)'); }); });