Implement 'all' parenthesis option

This commit is contained in:
Max Bruckner 2015-05-07 01:38:46 +02:00
parent c6f22e3663
commit aa7ca9178f
15 changed files with 193 additions and 42 deletions

View File

@ -75,15 +75,24 @@ function factory (type, config, load, typed) {
return new AssignmentNode(this.name, this.expr);
};
/*
* Is parenthesis needed?
* @private
*/
function needParenthesis(node) {
var precedence = operators.getPrecedence(node, config);
var exprPrecedence = operators.getPrecedence(node.expr, config);
return (config.parenthesis === 'all')
|| ((exprPrecedence !== null) && (exprPrecedence <= precedence));
}
/**
* Get string representation
* @return {String}
*/
AssignmentNode.prototype._toString = function() {
var precedence = operators.getPrecedence(this, config);
var exprPrecedence = operators.getPrecedence(this.expr, config);
var expr = this.expr.toString();
if ((exprPrecedence !== null) && (exprPrecedence <= precedence)) {
if (needParenthesis(this)) {
expr = '(' + expr + ')';
}
return this.name + ' = ' + expr;
@ -95,11 +104,8 @@ function factory (type, config, load, typed) {
* @return {String}
*/
AssignmentNode.prototype._toTex = function(callbacks) {
var precedence = operators.getPrecedence(this, config);
var exprPrecedence = operators.getPrecedence(this.expr, config);
var expr = this.expr.toTex(callbacks);
if ((exprPrecedence !== null) && (exprPrecedence <= precedence)) {
if (needParenthesis(this)) {
expr = '\\left(' + expr + '\\right)';
}

View File

@ -133,21 +133,24 @@ function factory (type, config, load, typed) {
//purely based on aesthetics and readability
var condition = this.condition.toString();
var conditionPrecedence = operators.getPrecedence(this.condition, config);
if ((this.condition.type === 'OperatorNode')
if ((config.parenthesis === 'all')
|| (this.condition.type === 'OperatorNode')
|| ((conditionPrecedence !== null) && (conditionPrecedence <= precedence))) {
condition = '(' + condition + ')';
}
var trueExpr = this.trueExpr.toString();
var truePrecedence = operators.getPrecedence(this.trueExpr, config);
if ((this.trueExpr.type === 'OperatorNode')
if ((config.parenthesis === 'all')
|| (this.trueExpr.type === 'OperatorNode')
|| ((truePrecedence !== null) && (truePrecedence <= precedence))) {
trueExpr = '(' + trueExpr + ')';
}
var falseExpr = this.falseExpr.toString();
var falsePrecedence = operators.getPrecedence(this.falseExpr, config);
if ((this.falseExpr.type === 'OperatorNode')
if ((config.parenthesis === 'all')
|| (this.falseExpr.type === 'OperatorNode')
|| ((falsePrecedence !== null) && (falsePrecedence <= precedence))) {
falseExpr = '(' + falseExpr + ')';
}

View File

@ -99,16 +99,25 @@ function factory (type, config, load, typed) {
return new FunctionAssignmentNode(this.name, this.params.slice(0), this.expr);
};
/**
* Is parenthesis needed?
* @private
*/
function needParenthesis(node) {
var precedence = operators.getPrecedence(node, config);
var exprPrecedence = operators.getPrecedence(node.expr, config);
return (config.parenthesis === 'all')
|| ((exprPrecedence !== null) && (exprPrecedence <= precedence));
}
/**
* get string representation
* @return {String} str
*/
FunctionAssignmentNode.prototype._toString = function () {
var precedence = operators.getPrecedence(this, config);
var exprPrecedence = operators.getPrecedence(this.expr, config);
var expr = this.expr.toString();
if ((exprPrecedence !== null) && (exprPrecedence <= precedence)) {
if (needParenthesis(this)) {
expr = '(' + expr + ')';
}
return 'function ' + this.name +
@ -121,11 +130,8 @@ function factory (type, config, load, typed) {
* @return {String} str
*/
FunctionAssignmentNode.prototype._toTex = function (callbacks) {
var precedence = operators.getPrecedence(this, config);
var exprPrecedence = operators.getPrecedence(this.expr, config);
var expr = this.expr.toTex(callbacks);
if ((exprPrecedence !== null) && (exprPrecedence <= precedence)) {
if (needParenthesis(this)) {
expr = '\\left(' + expr + '\\right)';
}

View File

@ -203,13 +203,34 @@ function factory (type, config, load, typed) {
return new IndexNode(this.object, this.ranges.slice(0));
};
/**
* Is parenthesis needed?
* @private
*/
function needParenthesis(node) {
switch (node.object.type) {
case 'ArrayNode':
case 'ConstantNode': //TODO don't know if this one makes sense
case 'SymbolNode':
case 'ParenthesisNode':
//those nodes don't need parentheses within an index node
return false;
default:
return true;
}
}
/**
* Get string representation
* @return {String} str
*/
IndexNode.prototype._toString = function () {
var object = this.object.toString();
if (needParenthesis(this)) {
object = '(' + object + '(';
}
// format the parameters like "[1, 0:5]"
return this.object.toString() + '[' + this.ranges.join(', ') + ']';
return object + '[' + this.ranges.join(', ') + ']';
};
/**
@ -218,10 +239,16 @@ function factory (type, config, load, typed) {
* @return {String} str
*/
IndexNode.prototype._toTex = function (callbacks) {
var object = this.object.toTex(callbacks);
if (needParenthesis(this)) {
object = '\\left(' + object + '\\right)';
}
var ranges = this.ranges.map(function (range) {
return range.toTex(callbacks);
});
return this.object.toTex(callbacks) + '_{\\left[' + ranges.join(',') + '\\right]}';
return object + '_{' + ranges.join(',') + '}';
};
return IndexNode;

View File

@ -114,7 +114,26 @@ function factory (type, config, load, typed, math) {
var precedence = operators.getPrecedence(root, config);
var associativity = operators.getAssociativity(root, config);
if ((config.parenthesis === 'all') || (args.length > 2)) {
var parens = [];
args.forEach(function (arg) {
switch (arg.getContent().type) { //Nodes that don't need extra parentheses
case 'ArrayNode':
case 'ConstantNode':
case 'SymbolNode':
case 'ParenthesisNode':
parens.push(false);
break;
default:
parens.push(true);
}
});
return parens;
}
switch (args.length) {
case 0:
return [];
case 1: //unary operators
//precedence of the operand
var operandPrecedence = operators.getPrecedence(args[0], config);
@ -244,13 +263,6 @@ function factory (type, config, load, typed, math) {
}
return [lhsParens, rhsParens];
default:
//behavior is undefined, fall back to putting everything in parens
var parens = [];
args.forEach(function () {
parens.push(true);
});
return parens;
}
}

View File

@ -85,35 +85,59 @@ function factory (type, config, load, typed) {
return new RangeNode(this.start, this.end, this.step && this.step);
};
/**
* Calculate the necessary parentheses
* @param {Node} node
* @return {Object} parentheses
* @private
*/
function calculateNecessaryParentheses(node) {
var precedence = operators.getPrecedence(node, config);
var parens = {};
var startPrecedence = operators.getPrecedence(node.start, config);
parens.start = ((startPrecedence !== null) && (startPrecedence <= precedence))
|| (config.parenthesis === 'all');
if (node.step) {
var stepPrecedence = operators.getPrecedence(node.step, config);
parens.step = ((stepPrecedence !== null) && (stepPrecedence <= precedence))
|| (config.parenthesis === 'all');
}
var endPrecedence = operators.getPrecedence(node.end, config);
parens.end = ((endPrecedence !== null) && (endPrecedence <= precedence))
|| (config.parenthesis === 'all');
return parens;
}
/**
* Get string representation
* @return {String} str
*/
RangeNode.prototype._toString = function () {
var precedence = operators.getPrecedence(this, config);
var parens = calculateNecessaryParentheses(this);
//format string as start:step:stop
var str;
var start = this.start.toString();
var startPrecedence = operators.getPrecedence(this.start, config);
if ((startPrecedence !== null) && (startPrecedence <= precedence)) {
if (parens.start) {
start = '(' + start + ')';
}
str = start;
if (this.step) {
var step = this.step.toString();
var stepPrecedence = operators.getPrecedence(this.step, config);
if ((stepPrecedence !== null) && (stepPrecedence <= precedence)) {
if (parens.step) {
step = '(' + step + ')';
}
str += ':' + step;
}
var end = this.end.toString();
var endPrecedence = operators.getPrecedence(this.end, config);
if ((endPrecedence !== null) && (endPrecedence <= precedence)) {
if (parens.end) {
end = '(' + end + ')';
}
str += ':' + end;
@ -127,11 +151,26 @@ function factory (type, config, load, typed) {
* @return {String} str
*/
RangeNode.prototype._toTex = function (callbacks) {
var parens = calculateNecessaryParentheses(this);
var str = this.start.toTex(callbacks);
if (this.step) {
str += ':' + this.step.toTex(callbacks);
if (parens.start) {
str = '\\left(' + str + '\\right)';
}
str += ':' + this.end.toTex(callbacks);
if (this.step) {
var step = this.step.toTex(callbacks);
if (parens.step) {
step = '\\left(' + step + '\\right)';
}
str += ':' + step;
}
var end = this.end.toTex(callbacks);
if (parens.end) {
end = '\\left(' + end + '\\right)';
}
str += ':' + end;
return str;
};

View File

@ -87,7 +87,11 @@ function factory (type, config, load, typed) {
* @return {String}
*/
UpdateNode.prototype._toString = function () {
return this.index.toString() + ' = ' + this.expr._toString();
var expr = this.expr.toString();
if (config.parenthesis === 'all') {
expr = '(' + expr + ')';
}
return this.index.toString() + ' = ' + expr;
};
/**
@ -96,7 +100,11 @@ function factory (type, config, load, typed) {
* @return {String}
*/
UpdateNode.prototype._toTex = function (callbacks) {
return this.index.toTex(callbacks) + ':=' + this.expr.toTex(callbacks);
var expr = this.expr.toTex();
if (config.parenthesis === 'all') {
expr = '\\left(' + expr + '\\right)';
}
return this.index.toTex(callbacks) + ':=' + expr;
};
return UpdateNode;

View File

@ -336,6 +336,7 @@ exports.toSymbol = function (name, isUnit) {
};
//returns the latex output for a given function
//TODO: this doesn't yet use the different parenthesis options
exports.toFunction = function (node, callbacks, name) {
var latexConverter = functions[name];
var args = node.args.map(function (arg) { //get LaTeX of the arguments

View File

@ -198,6 +198,14 @@ describe('AssignmentNode', function() {
assert.strictEqual(e.expr, d.expr);
});
it ('should respect the \'all\' parenthesis option', function () {
var allMath = math.create({parenthesis: 'all'});
var expr = allMath.parse('a=1');
assert.equal(expr.toString(), 'a = (1)');
assert.equal(expr.toTex(), 'a:=\\left(1\\right)');
});
it ('should stringify a AssignmentNode', function () {
var b = new ConstantNode(3);
var n = new AssignmentNode('b', b);

View File

@ -253,6 +253,12 @@ describe('ConditionalNode', function() {
assert.strictEqual(d.falseExpr, c.falseExpr);
});
it ('should respect the \'all\' parenthesis option', function () {
var allMath = math.create({parenthesis: 'all'});
assert.equal(allMath.parse('a?b:c').toString(), '(a) ? (b) : (c)');
});
it ('should stringify a ConditionalNode', function () {
var n = new ConditionalNode(condition, a, b);

View File

@ -245,6 +245,14 @@ describe('FunctionAssignmentNode', function() {
assert.strictEqual(e.expr, d.expr);
});
it ('should respect the \'all\' parenthesis option', function () {
var allMath = math.create({parenthesis: 'all'});
var expr = allMath.parse('f(x)=x+1');
assert.equal(expr.toString(), 'function f(x) = (x + 1)');
assert.equal(expr.toTex(), '\\mathrm{f}\\left(x\\right):=\\left( x+1\\right)');
});
it ('should stringify a FunctionAssignmentNode', function () {
var a = new ConstantNode(2);
var x = new SymbolNode('x');

View File

@ -285,10 +285,10 @@ describe('IndexNode', function() {
];
var n = new IndexNode(a, ranges);
assert.equal(n.toTex(), ' a_{\\left[2,1\\right]}');
assert.equal(n.toTex(), ' a_{2,1}');
var n2 = new IndexNode(a, []);
assert.equal(n2.toTex(), ' a_{\\left[\\right]}')
assert.equal(n2.toTex(), ' a_{}')
});
it ('should LaTeX an IndexNode with custom toTex', function () {

View File

@ -284,6 +284,19 @@ describe('OperatorNode', function() {
});
});
it ('should respect the \'all\' parenthesis option', function () {
var allMath = math.create({parenthesis: 'all'});
assert.equal(allMath.parse('1+1+1').toString(), '(1 + 1) + 1' );
assert.equal(allMath.parse('1+1+1').toTex(), '\\left(1+1\\right)+1' );
});
it ('should correctly LaTeX fractions in \'all\' parenthesis mode', function () {
var allMath = math.create({parenthesis: 'all'});
assert.equal(allMath.parse('1/2/3').toTex(), '\\frac{\\left(\\frac{1}{2}\\right)}{3}');
});
it ('should LaTeX an OperatorNode', function () {
var a = new ConstantNode(2);
var b = new ConstantNode(3);

View File

@ -280,6 +280,13 @@ describe('RangeNode', function() {
assert.equal(n.toString(), '(0:10):2:100');
});
it ('should respect the \'all\' parenthesis option', function () {
var allMath = math.create({parenthesis: 'all'});
assert.equal(allMath.parse('1:2:3').toString(), '(1):(2):(3)');
assert.equal(allMath.parse('1:2:3').toTex(), '\\left(1\\right):\\left(2\\right):\\left(3\\right)');
});
it ('should LaTeX a RangeNode without step', function () {
var start = new ConstantNode(0);
var end = new ConstantNode(10);

View File

@ -321,6 +321,13 @@ describe('UpdateNode', function() {
assert.equal(n.toString(), 'a[2, 1] = 5');
});
it ('should respect the \'all\' parenthesis option', function () {
var allMath = math.create({parenthesis: 'all'});
assert.equal(allMath.parse('a[1]=2').toString(), 'a[1] = (2)' );
assert.equal(allMath.parse('a[1]=2').toTex(), ' a_{1}:=\\left(2\\right)' );
});
it ('should LaTeX an UpdateNode', function () {
var a = new SymbolNode('a');
var ranges = [
@ -330,7 +337,7 @@ describe('UpdateNode', function() {
var v = new ConstantNode(5);
var n = new UpdateNode(new IndexNode(a, ranges), v);
assert.equal(n.toTex(), ' a_{\\left[2,1\\right]}:=5');
assert.equal(n.toTex(), ' a_{2,1}:=5');
});
it ('should LaTeX an UpdateNode with custom toTex', function () {