From 2959858b990ca2d6741d155dbb60f905fa098f26 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Tue, 5 May 2015 09:31:41 +0200 Subject: [PATCH] operators: Use config when calculating precedence etc. Use the parenthesis configuration to decide wether ParenthesisNodes should be skipped or not. --- lib/expression/node/AssignmentNode.js | 8 +- lib/expression/node/ConditionalNode.js | 8 +- lib/expression/node/FunctionAssignmentNode.js | 8 +- lib/expression/node/OperatorNode.js | 18 ++-- lib/expression/node/RangeNode.js | 8 +- lib/expression/operators.js | 32 +++++-- test/expression/operators.test.js | 83 ++++++++++++++----- 7 files changed, 111 insertions(+), 54 deletions(-) diff --git a/lib/expression/node/AssignmentNode.js b/lib/expression/node/AssignmentNode.js index 972ec9ecf..c6720dad9 100644 --- a/lib/expression/node/AssignmentNode.js +++ b/lib/expression/node/AssignmentNode.js @@ -80,8 +80,8 @@ function factory (type, config, load, typed) { * @return {String} */ AssignmentNode.prototype._toString = function() { - var precedence = operators.getPrecedence(this); - var exprPrecedence = operators.getPrecedence(this.expr); + var precedence = operators.getPrecedence(this, config); + var exprPrecedence = operators.getPrecedence(this.expr, config); var expr = this.expr.toString(); if ((exprPrecedence !== null) && (exprPrecedence <= precedence)) { expr = '(' + expr + ')'; @@ -95,8 +95,8 @@ function factory (type, config, load, typed) { * @return {String} */ AssignmentNode.prototype._toTex = function(callbacks) { - var precedence = operators.getPrecedence(this); - var exprPrecedence = operators.getPrecedence(this.expr); + var precedence = operators.getPrecedence(this, config); + var exprPrecedence = operators.getPrecedence(this.expr, config); var expr = this.expr.toTex(callbacks); if ((exprPrecedence !== null) && (exprPrecedence <= precedence)) { diff --git a/lib/expression/node/ConditionalNode.js b/lib/expression/node/ConditionalNode.js index 8a7951c37..ae9fe5903 100644 --- a/lib/expression/node/ConditionalNode.js +++ b/lib/expression/node/ConditionalNode.js @@ -125,28 +125,28 @@ function factory (type, config, load, typed) { * @return {String} str */ ConditionalNode.prototype._toString = function () { - var precedence = operators.getPrecedence(this); + var precedence = operators.getPrecedence(this, config); //Enclose Arguments in parentheses if they are an OperatorNode //or have lower or equal precedence //NOTE: enclosing all OperatorNodes in parentheses is a decision //purely based on aesthetics and readability var condition = this.condition.toString(); - var conditionPrecedence = operators.getPrecedence(this.condition); + var conditionPrecedence = operators.getPrecedence(this.condition, config); if ((this.condition.type === 'OperatorNode') || ((conditionPrecedence !== null) && (conditionPrecedence <= precedence))) { condition = '(' + condition + ')'; } var trueExpr = this.trueExpr.toString(); - var truePrecedence = operators.getPrecedence(this.trueExpr); + var truePrecedence = operators.getPrecedence(this.trueExpr, config); if ((this.trueExpr.type === 'OperatorNode') || ((truePrecedence !== null) && (truePrecedence <= precedence))) { trueExpr = '(' + trueExpr + ')'; } var falseExpr = this.falseExpr.toString(); - var falsePrecedence = operators.getPrecedence(this.falseExpr); + var falsePrecedence = operators.getPrecedence(this.falseExpr, config); if ((this.falseExpr.type === 'OperatorNode') || ((falsePrecedence !== null) && (falsePrecedence <= precedence))) { falseExpr = '(' + falseExpr + ')'; diff --git a/lib/expression/node/FunctionAssignmentNode.js b/lib/expression/node/FunctionAssignmentNode.js index 846a25cab..3785d713d 100644 --- a/lib/expression/node/FunctionAssignmentNode.js +++ b/lib/expression/node/FunctionAssignmentNode.js @@ -104,8 +104,8 @@ function factory (type, config, load, typed) { * @return {String} str */ FunctionAssignmentNode.prototype._toString = function () { - var precedence = operators.getPrecedence(this); - var exprPrecedence = operators.getPrecedence(this.expr); + var precedence = operators.getPrecedence(this, config); + var exprPrecedence = operators.getPrecedence(this.expr, config); var expr = this.expr.toString(); if ((exprPrecedence !== null) && (exprPrecedence <= precedence)) { @@ -121,8 +121,8 @@ function factory (type, config, load, typed) { * @return {String} str */ FunctionAssignmentNode.prototype._toTex = function (callbacks) { - var precedence = operators.getPrecedence(this); - var exprPrecedence = operators.getPrecedence(this.expr); + var precedence = operators.getPrecedence(this, config); + var exprPrecedence = operators.getPrecedence(this.expr, config); var expr = this.expr.toTex(callbacks); if ((exprPrecedence !== null) && (exprPrecedence <= precedence)) { diff --git a/lib/expression/node/OperatorNode.js b/lib/expression/node/OperatorNode.js index 82931d1df..0b7a86911 100644 --- a/lib/expression/node/OperatorNode.js +++ b/lib/expression/node/OperatorNode.js @@ -111,13 +111,13 @@ function factory (type, config, load, typed, math) { */ function calculateNecessaryParentheses(root, args, latex) { //precedence of the root OperatorNode - var precedence = operators.getPrecedence(root); - var associativity = operators.getAssociativity(root); + var precedence = operators.getPrecedence(root, config); + var associativity = operators.getAssociativity(root, config); switch (args.length) { case 1: //unary operators //precedence of the operand - var operandPrecedence = operators.getPrecedence(args[0]); + var operandPrecedence = operators.getPrecedence(args[0], config); //handle special cases for LaTeX, where some of the parentheses aren't needed if (latex && (operandPrecedence !== null)) { @@ -148,9 +148,9 @@ function factory (type, config, load, typed, math) { case 2: //binary operators var lhsParens; //left hand side needs parenthesis? //precedence of the left hand side - var lhsPrecedence = operators.getPrecedence(args[0]); + var lhsPrecedence = operators.getPrecedence(args[0], config); //is the root node associative with the left hand side - var assocWithLhs = operators.isAssociativeWith(root, args[0]); + var assocWithLhs = operators.isAssociativeWith(root, args[0], config); if (lhsPrecedence === null) { //if the left hand side has no defined precedence, no parens are needed @@ -173,9 +173,9 @@ function factory (type, config, load, typed, math) { var rhsParens; //right hand side needs parenthesis? //precedence of the right hand side - var rhsPrecedence = operators.getPrecedence(args[1]); + var rhsPrecedence = operators.getPrecedence(args[1], config); //is the root node associative with the right hand side? - var assocWithRhs = operators.isAssociativeWith(root, args[1]); + var assocWithRhs = operators.isAssociativeWith(root, args[1], config); if (rhsPrecedence === null) { //if the right hand side has no defined precedence, no parens are needed @@ -244,7 +244,7 @@ function factory (type, config, load, typed, math) { switch (args.length) { case 1: //unary operators - var assoc = operators.getAssociativity(this); + var assoc = operators.getAssociativity(this, config); var operand = args[0].toString(); if (parens[0]) { @@ -292,7 +292,7 @@ function factory (type, config, load, typed, math) { switch (args.length) { case 1: //unary operators - var assoc = operators.getAssociativity(this); + var assoc = operators.getAssociativity(this, config); var operand = args[0].toTex(callbacks); if (parens[0]) { diff --git a/lib/expression/node/RangeNode.js b/lib/expression/node/RangeNode.js index bf8955a48..99733cf26 100644 --- a/lib/expression/node/RangeNode.js +++ b/lib/expression/node/RangeNode.js @@ -90,13 +90,13 @@ function factory (type, config, load, typed) { * @return {String} str */ RangeNode.prototype._toString = function () { - var precedence = operators.getPrecedence(this); + var precedence = operators.getPrecedence(this, config); //format string as start:step:stop var str; var start = this.start.toString(); - var startPrecedence = operators.getPrecedence(this.start); + var startPrecedence = operators.getPrecedence(this.start, config); if ((startPrecedence !== null) && (startPrecedence <= precedence)) { start = '(' + start + ')'; } @@ -104,7 +104,7 @@ function factory (type, config, load, typed) { if (this.step) { var step = this.step.toString(); - var stepPrecedence = operators.getPrecedence(this.step); + var stepPrecedence = operators.getPrecedence(this.step, config); if ((stepPrecedence !== null) && (stepPrecedence <= precedence)) { step = '(' + step + ')'; } @@ -112,7 +112,7 @@ function factory (type, config, load, typed) { } var end = this.end.toString(); - var endPrecedence = operators.getPrecedence(this.end); + var endPrecedence = operators.getPrecedence(this.end, config); if ((endPrecedence !== null) && (endPrecedence <= precedence)) { end = '(' + end + ')'; } diff --git a/lib/expression/operators.js b/lib/expression/operators.js index 1312189b7..6df69f21b 100644 --- a/lib/expression/operators.js +++ b/lib/expression/operators.js @@ -214,7 +214,12 @@ var properties = [ * @param {Node} * @return {Number|null} */ -function getPrecedence (node) { +function getPrecedence (_node, config) { + var node = _node; + if (config.parenthesis !== 'keep') { + //ParenthesisNodes are only ignored when not in 'keep' mode + node = _node.getContent(); + } var identifier = node.getIdentifier(); for (var i = 0; i < properties.length; i++) { if (identifier in properties[i]) { @@ -233,9 +238,14 @@ function getPrecedence (node) { * @return {String|null} * @throws {Error} */ -function getAssociativity (node) { +function getAssociativity (_node, config) { + var node = _node; + if (config.parenthesis !== 'keep') { + //ParenthesisNodes are only ignored when not in 'keep' mode + node = _node.getContent(); + } var identifier = node.getIdentifier(); - var index = getPrecedence(node); + var index = getPrecedence(node, config); if (index === null) { //node isn't in the list return null; @@ -264,12 +274,20 @@ function getAssociativity (node) { * * @param {Node} nodeA * @param {Node} nodeB + * @param {bool} doGetContent * @return {bool|null} */ -function isAssociativeWith (nodeA, nodeB) { - var identifierA = nodeA.getIdentifier(); - var identifierB = nodeB.getIdentifier(); - var index = getPrecedence(nodeA); +function isAssociativeWith (nodeA, nodeB, config) { + var a = nodeA; + var b = nodeB; + if (config.parenthesis !== 'keep') { + //ParenthesisNodes are only ignored when not in 'keep' mode + var a = nodeA.getContent(); + var b = nodeB.getContent(); + } + var identifierA = a.getIdentifier(); + var identifierB = b.getIdentifier(); + var index = getPrecedence(a, config); if (index === null) { //node isn't in the list return null; diff --git a/test/expression/operators.test.js b/test/expression/operators.test.js index 14bf24c1e..aef051841 100644 --- a/test/expression/operators.test.js +++ b/test/expression/operators.test.js @@ -1,12 +1,14 @@ var assert = require('assert'); -var math = require('../../core').create(); +var math = require('../../index'); var operators = require('../../lib/expression/operators'); -var OperatorNode = math.import(require('../../lib/expression/node/OperatorNode')); -var AssignmentNode = math.import(require('../../lib/expression/node/AssignmentNode')); -var ConstantNode = math.import(require('../../lib/expression/node/ConstantNode')); -var Node = math.import(require('../../lib/expression/node/Node')); +var OperatorNode = math.expression.node.OperatorNode; +var AssignmentNode = math.expression.node.AssignmentNode; +var ConstantNode = math.expression.node.ConstantNode; +var Node = math.expression.node.Node; +var ParenthesisNode = math.expression.node.ParenthesisNode; +var config = {parenthesis: 'keep'}; describe('operators', function () { it('should return the precedence of a node', function () { @@ -16,14 +18,26 @@ describe('operators', function () { var n1 = new AssignmentNode('a', a); var n2 = new OperatorNode('or', 'or', [a, b]); - assert.equal(operators.getPrecedence(n1), 0); - assert.equal(operators.getPrecedence(n2), 2); + assert.equal(operators.getPrecedence(n1, config), 0); + assert.equal(operators.getPrecedence(n2, config), 2); }); it('should return null if precedence is not defined for a node', function () { var n = new Node(); - assert.equal(operators.getPrecedence(n), null); + assert.equal(operators.getPrecedence(n, config), null); + }); + + it ('should return the precedence of a ParenthesisNode', function () { + var c = new ConstantNode(1); + + var op = new OperatorNode('or', 'or', [c, c]); + + var p = new ParenthesisNode(op); + + assert.equal(operators.getPrecedence(p, {parenthesis: 'all'}), operators.getPrecedence(op, config)); + assert.equal(operators.getPrecedence(p, {parenthesis: 'auto'}), operators.getPrecedence(op, config)); + assert.equal(operators.getPrecedence(p, config), null); }); it('should return the associativity of a node', function () { @@ -34,10 +48,22 @@ describe('operators', function () { var n3 = new OperatorNode('-', 'unaryMinus', [a]); var n4 = new OperatorNode('!', 'factorial', [a]); - assert.equal(operators.getAssociativity(n1), 'left'); - assert.equal(operators.getAssociativity(n2), 'right'); - assert.equal(operators.getAssociativity(n3), 'right'); - assert.equal(operators.getAssociativity(n4), 'left'); + assert.equal(operators.getAssociativity(n1, config), 'left'); + assert.equal(operators.getAssociativity(n2, config), 'right'); + assert.equal(operators.getAssociativity(n3, config), 'right'); + assert.equal(operators.getAssociativity(n4, config), 'left'); + }); + + it ('should return the associativity of a ParenthesisNode', function () { + var c = new ConstantNode(1); + + var op = new OperatorNode('or', 'or', [c, c]); + + var p = new ParenthesisNode(op); + + assert.equal(operators.getAssociativity(p, {parenthesis: 'all'}), operators.getAssociativity(op, config)); + assert.equal(operators.getAssociativity(p, {parenthesis: 'auto'}), operators.getAssociativity(op, config)); + assert.equal(operators.getAssociativity(p, config), null); }); it('should return null if associativity is not defined for a node', function () { @@ -46,8 +72,8 @@ describe('operators', function () { var n1 = new Node(); var n2 = new AssignmentNode('a', a); - assert.equal(operators.getAssociativity(n1), null); - assert.equal(operators.getAssociativity(n2), null); + assert.equal(operators.getAssociativity(n1, config), null); + assert.equal(operators.getAssociativity(n2, config), null); }); it('should return if a Node is associative with another Node', function () { @@ -56,10 +82,10 @@ describe('operators', function () { var n1 = new OperatorNode('+', 'add', [a, a]); var n2 = new OperatorNode('-', 'subtract', [a, a]); - assert.equal(operators.isAssociativeWith(n1, n1), true); - assert.equal(operators.isAssociativeWith(n1, n2), true); - assert.equal(operators.isAssociativeWith(n2, n2), false); - assert.equal(operators.isAssociativeWith(n2, n1), false); + assert.equal(operators.isAssociativeWith(n1, n1, config), true); + assert.equal(operators.isAssociativeWith(n1, n2, config), true); + assert.equal(operators.isAssociativeWith(n2, n2, config), false); + assert.equal(operators.isAssociativeWith(n2, n1, config), false); }); it('should return null if the associativity between two Nodes is not defined', function () { @@ -68,9 +94,22 @@ describe('operators', function () { var n1 = new Node(); var n2 = new AssignmentNode('a', a); - assert.equal(operators.isAssociativeWith(n1, n1), null); - assert.equal(operators.isAssociativeWith(n1, n2), null); - assert.equal(operators.isAssociativeWith(n2, n2), null); - assert.equal(operators.isAssociativeWith(n2, n1), null); + assert.equal(operators.isAssociativeWith(n1, n1, config), null); + assert.equal(operators.isAssociativeWith(n1, n2, config), null); + assert.equal(operators.isAssociativeWith(n2, n2, config), null); + assert.equal(operators.isAssociativeWith(n2, n1, config), null); + }); + + it ('should return if a ParenthesisNode is associative with another Node', function () { + var a = new ConstantNode(1); + + var add = new OperatorNode('+', 'add', [a, a]); + var sub = new OperatorNode('-', 'subtract', [a, a]); + + var p = new ParenthesisNode(add); + + assert.equal(operators.isAssociativeWith(p, sub, {parenthesis: 'all'}), true); + assert.equal(operators.isAssociativeWith(p, sub, {parenthesis: 'auto'}), true); + assert.equal(operators.isAssociativeWith(p, sub, config), null); }); });