operators: Use config when calculating precedence etc.

Use the parenthesis configuration to decide wether ParenthesisNodes
should be skipped or not.
This commit is contained in:
Max Bruckner 2015-05-05 09:31:41 +02:00
parent 60e2b5700a
commit 2959858b99
7 changed files with 111 additions and 54 deletions

View File

@ -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)) {

View File

@ -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 + ')';

View File

@ -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)) {

View File

@ -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]) {

View File

@ -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 + ')';
}

View File

@ -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;

View File

@ -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);
});
});