Repository: josdejong/mathjs
Pull request #294 (2015-03-01 16:22:50)

Update toTex to better handle parenthesis
This should be ok for now.

But the code in `lib/util/latex.js` really requires some love! ( But that requires that I actually fully understand it first ).


Patch:
diff --git a/lib/expression/node/AssignmentNode.js b/lib/expression/node/AssignmentNode.js
index 49ce5a8a4..90fb93373 100644
--- a/lib/expression/node/AssignmentNode.js
+++ b/lib/expression/node/AssignmentNode.js
@@ -92,12 +92,24 @@ AssignmentNode.prototype.toString = function() {
  * @return {String}
  */
 AssignmentNode.prototype.toTex = function() {
+  var precedence = operators.getPrecedence(this);
+  var exprPrecedence = operators.getPrecedence(this.expr);
+
+  var expr = this.expr.toTex();
+  if ((exprPrecedence !== null) && (exprPrecedence <= precedence)) {
+    //adds visible round brackets
+    expr = latex.addBraces(expr, true);
+  }
+  else {
+    //adds (invisible) curly braces
+    expr = latex.addBraces(expr, false);
+  }
+
   var brace;
   if (this.expr instanceof ArrayNode) {
     brace = ['\\mathbf{', '}'];
   }
-  return latex.addBraces(latex.toSymbol(this.name), brace) + '=' +
-      latex.addBraces(this.expr.toTex());
+  return latex.addBraces(latex.toSymbol(this.name), brace) + '=' + expr;
 };
 
-module.exports = AssignmentNode;
\ No newline at end of file
+module.exports = AssignmentNode;
diff --git a/lib/expression/node/FunctionAssignmentNode.js b/lib/expression/node/FunctionAssignmentNode.js
index c2a8d5477..629360ca1 100644
--- a/lib/expression/node/FunctionAssignmentNode.js
+++ b/lib/expression/node/FunctionAssignmentNode.js
@@ -113,9 +113,22 @@ FunctionAssignmentNode.prototype.toString = function() {
  * @return {String} str
  */
 FunctionAssignmentNode.prototype.toTex = function() {
-  return this.name +
-      latex.addBraces(this.params.map(latex.toSymbol).join(', '), true) + '=' +
-      latex.addBraces(this.expr.toTex());
+  var precedence = operators.getPrecedence(this);
+  var exprPrecedence = operators.getPrecedence(this.expr);
+
+  var expr = this.expr.toTex();
+  if ((exprPrecedence !== null) && (exprPrecedence <= precedence)) {
+    //adds visible round brackets
+    expr = latex.addBraces(expr, true);
+  }
+  else {
+    //add (invisible) curly braces
+    expr = latex.addBraces(expr, false);
+  }
+
+  return latex.toFunction(this.name)
+       + latex.addBraces(this.params.map(latex.toSymbol).join(', '), true) + '='
+       + expr;
 };
 
 module.exports = FunctionAssignmentNode;
diff --git a/lib/expression/node/OperatorNode.js b/lib/expression/node/OperatorNode.js
index 8398fc17b..5c97904e4 100644
--- a/lib/expression/node/OperatorNode.js
+++ b/lib/expression/node/OperatorNode.js
@@ -239,98 +239,53 @@ OperatorNode.prototype.toString = function() {
  * @return {String} str
  */
 OperatorNode.prototype.toTex = function() {
-  var args = this.args,
-      mop = latex.toOperator(this.op),
-      lp = args[0],
-      rp = args[1];
-
-  switch (args.length) {
-    case 1:
-      var operand = lp.toTex();
-      switch (this.op) {
-        case '-': //unary minus needs brackets around '-' and '+'
-          if (lp instanceof OperatorNode && (lp.op === '-' || lp.op === '+')) {
-            return this.op + latex.addBraces(operand, true);
-          }
-        case '+':
-          return this.op + operand;
-          break;
-        default: // fox example '5!'
-          if (lp instanceof OperatorNode) {
-            return latex.addBraces(operand, true) + this.op;
-          }
-          return operand + this.op;
-      }
-
-    case 2: // for example '2+3'
-      var lhs = lp.toTex(),
-          lhb = false,
-          rhs = rp.toTex(),
-          rhb = false,
-          lop = '',
-          rop = '';
-
-      switch (this.op) {
-        case '/':
-          lop = mop;
-          mop = '';
-
-          break;
-
-        case '*':
-          if (lp instanceof OperatorNode) {
-            if (lp.op === '+' || lp.op === '-') {
-              lhb = true;
-            }
-          }
-
-          if (rp instanceof OperatorNode) {
-            if (rp.op === '+' || rp.op === '-' || rp.op === '*') {
-              rhb = true;
-            }
-          }
-
-          if ((lp instanceof ConstantNode || lp instanceof OperatorNode) &&
-              (rp instanceof ConstantNode || rp instanceof OperatorNode)) {
-            mop = ' \\cdot ';
-          }
-          else {
-            mop = ' \\, ';
-          }
-
-          break;
-
-        case '-':
-          if (rp instanceof OperatorNode) {
-            if (rp.op === '+' | rp.op === '-' ) {
-              rhb = true;
-            }
-          }
-          break;
-
-        case '^':
-          if (lp instanceof OperatorNode || lp instanceof FunctionNode) {
-            lhb = true;
-          }
-          else if (lp instanceof SymbolNode) {
-            lhb = null;
-          }
-
-          break;
-
-        case 'to':
-          rhs = latex.toUnit(rhs, true);
-          break;
-      }
-
-      lhs = latex.addBraces(lhs, lhb);
-      rhs = latex.addBraces(rhs, rhb);
-
-      return lop + lhs + mop + rhs + rop;
-
-    default: // this should not occur. format as a function call
-      return mop + '(' + this.args.map(latex.toSymbol).join(', ') + ')';
-  }
+ var args = this.args; 
+ var parens = calculateNecessaryParentheses(this, args);
+ var op = latex.toOperator(this.op); //operator
+
+ switch (args.length) {
+   case 1: //unary operators
+     var assoc = operators.getAssociativity(this);
+
+     var operand = args[0].toTex();
+     if (parens[0]) {
+       operand = latex.addBraces(operand, true);
+     }
+
+     if (assoc === 'right') { //prefix operator
+       return op + operand;
+     }
+     else if (assoc === 'left') { //postfix operator
+       return operand + op;
+     }
+
+     //fall back to postfix
+     return operand + op;
+
+   case 2: //binary operators
+     var lhs = args[0]; //left hand side
+     //reminder: if parens[0] is false, this puts it in curly braces
+     var lhsTex = latex.addBraces(lhs.toTex(), parens[0]);
+     var rhs = args[1]; //right hand side
+     var rhsTex = latex.addBraces(rhs.toTex(), parens[1]);
+
+     switch (this.getIdentifier()) {
+       case 'OperatorNode:divide':
+         //op contains '\\frac' at this point
+         return op + lhsTex + rhsTex;
+
+       case 'OperatorNode:to':
+         rhsTex = latex.toUnit(rhs.toTex());
+         rhsTex = latex.addBraces(rhsTex, parens[1]);
+         break;
+     }
+     return lhsTex + ' ' + op + ' ' + rhsTex;
+
+   default:
+     //fall back to formatting as a function call
+     var argumentList = this.args.map(latex.toSymbol).join(', ');
+     return latex.toFunction(this.fn) + latex.addBraces(argumentList, true);
+ }
 };
 
 /**
diff --git a/package.json b/package.json
index b837c7e3d..0370adab2 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,6 @@
   "author": "Jos de Jong <wjosdejong@gmail.com> (https://github.com/josdejong)",
   "contributors": [
     "Favian Contreras (https://github.com/BigFav)",
-    "Max Bruckner (https://github.com/FSMaxB)",
     "Sebastien Piquemal (https://github.com/sebpiq)",
     "Rogelio J. Baucells  (https://github.com/rjbaucells)",
     "Guillermo Indalecio Fernández (https://github.com/guillermobox)",


Test patch:
diff --git a/test/expression/node/AssignmentNode.test.js b/test/expression/node/AssignmentNode.test.js
index 94c733ac6..8c555081d 100644
--- a/test/expression/node/AssignmentNode.test.js
+++ b/test/expression/node/AssignmentNode.test.js
@@ -216,4 +216,13 @@ describe('AssignmentNode', function() {
     assert.equal(n.toTex(), '{b}={3}');
   });
 
+  it ('should LaTeX an AssignmentNode containing an AssignmentNode', function () {
+    var a = new ConstantNode(2);
+    var b = new AssignmentNode('a', a);
+
+    var n = new AssignmentNode('b', b);
+
+    assert.equal(n.toTex(), '{b}=\\left({{a}={2}}\\right)');
+  });
+
 });
diff --git a/test/expression/node/FunctionAssignmentNode.test.js b/test/expression/node/FunctionAssignmentNode.test.js
index 6f3cc3c22..9b6209ea7 100644
--- a/test/expression/node/FunctionAssignmentNode.test.js
+++ b/test/expression/node/FunctionAssignmentNode.test.js
@@ -195,7 +195,15 @@ describe('FunctionAssignmentNode', function() {
     var p = new OperatorNode('^', 'pow', [o, a]);
     var n = new FunctionAssignmentNode('f', ['x'], p);
 
-    assert.equal(n.toTex(), 'f\\left({x}\\right)={\\left({\\frac{x}{2}}\\right)^{2}}');
+    assert.equal(n.toTex(), 'f\\left({x}\\right)={\\left({\\frac{x}{2}}\\right) ^ {2}}');
   });
 
+  it ('should LaTeX a FunctionAssignmentNode containing an AssignmentNode', function () {
+    var a = new ConstantNode(2);
+
+    var n1 = new AssignmentNode('a', a);
+    var n = new FunctionAssignmentNode('f', ['x'], n1);
+
+    assert.equal(n.toTex(), 'f\\left({x}\\right)=\\left({{a}={2}}\\right)');
+  });
 });
diff --git a/test/expression/node/FunctionNode.test.js b/test/expression/node/FunctionNode.test.js
index fe6e118ea..d5e743746 100644
--- a/test/expression/node/FunctionNode.test.js
+++ b/test/expression/node/FunctionNode.test.js
@@ -278,7 +278,7 @@ describe('FunctionNode', function() {
 
     var o = new OperatorNode('+', 'add', [c1, c2]);
     var n3 = new FunctionNode('permutations', [o]);
-    assert.equal(n3.toTex(), '{\\left({4}+{5}\\right)!}');
+    assert.equal(n3.toTex(), '{\\left({4} + {5}\\right)!}');
   });
 
   it ('should have an identifier', function () {
diff --git a/test/expression/node/OperatorNode.test.js b/test/expression/node/OperatorNode.test.js
index 607e94122..07ded28f0 100644
--- a/test/expression/node/OperatorNode.test.js
+++ b/test/expression/node/OperatorNode.test.js
@@ -276,7 +276,7 @@ describe('OperatorNode', function() {
     var c = new ConstantNode(4);
 
     var n = new OperatorNode('+', 'add', [a, b]);
-    assert.equal(n.toTex(), '{2}+{3}');
+    assert.equal(n.toTex(), '{2} + {3}');
   });
 
   it ('should LaTeX an OperatorNode with factorial', function () {
@@ -298,8 +298,8 @@ describe('OperatorNode', function() {
     var n2= new OperatorNode('!', 'factorial', [add] );
     var n3= new OperatorNode('!', 'factorial', [mult] );
     var n4= new OperatorNode('!', 'factorial', [div] );
-    assert.equal(n1.toTex(), '\\left({{2}-{3}}\\right)!');
-    assert.equal(n2.toTex(), '\\left({{2}+{3}}\\right)!');
+    assert.equal(n1.toTex(), '\\left({{2} - {3}}\\right)!');
+    assert.equal(n2.toTex(), '\\left({{2} + {3}}\\right)!');
     assert.equal(n3.toTex(), '\\left({{2} \\cdot {3}}\\right)!');
     assert.equal(n4.toTex(), '\\left({\\frac{2}{3}}\\right)!');
   });
@@ -316,8 +316,8 @@ describe('OperatorNode', function() {
     var n3 = new OperatorNode('-', 'unaryMinus', [add]);
 
     assert.equal(n1.toTex(), '-2');
-    assert.equal(n2.toTex(), '-\\left({{2}-{3}}\\right)');
-    assert.equal(n3.toTex(), '-\\left({{2}+{3}}\\right)');
+    assert.equal(n2.toTex(), '-\\left({{2} - {3}}\\right)');
+    assert.equal(n3.toTex(), '-\\left({{2} + {3}}\\right)');
   });
 
   it ('should LaTeX an OperatorNode that subtracts an OperatorNode', function() {
@@ -331,13 +331,13 @@ describe('OperatorNode', function() {
     var n1 = new OperatorNode('-', 'subtract', [a, sub]);
     var n2 = new OperatorNode('-', 'subtract', [a, add]);
 
-    assert.equal(n1.toTex(), '{1}-\\left({{2}-{3}}\\right)');
-    assert.equal(n2.toTex(), '{1}-\\left({{2}+{3}}\\right)');
+    assert.equal(n1.toTex(), '{1} - \\left({{2} - {3}}\\right)');
+    assert.equal(n2.toTex(), '{1} - \\left({{2} + {3}}\\right)');
   });
 
   it ('should LaTeX an OperatorNode with zero arguments', function () {
     var n = new OperatorNode('foo', 'foo', []);
-    assert.equal(n.toTex(), 'foo()');
+    assert.equal(n.toTex(), 'foo\\left({}\\right)');
   });
 
   it ('should LaTeX an OperatorNode with more than two operators', function () {
@@ -346,7 +346,7 @@ describe('OperatorNode', function() {
     var c = new ConstantNode(4);
 
     var n = new OperatorNode('foo', 'foo', [a, b, c]);
-    assert.equal(n.toTex(), 'foo(2, 3, 4)');
+    assert.equal(n.toTex(), 'foo\\left({2, 3, 4}\\right)');
 
   });
 
@@ -363,10 +363,10 @@ describe('OperatorNode', function() {
     var m2 = new OperatorNode('*', 'multiply', [n1, c]);
     var m3 = new OperatorNode('-', 'subtract', [m2, d]);
 
-    assert.equal(n1.toTex(), '{2}+{3}');
-    assert.equal(n2.toTex(), '{4}-{5}');
-    assert.equal(n3.toTex(), '\\left({{2}+{3}}\\right) \\cdot \\left({{4}-{5}}\\right)');
-    assert.equal(m3.toTex(), '{\\left({{2}+{3}}\\right) \\cdot {4}}-{5}');
+    assert.equal(n1.toTex(), '{2} + {3}');
+    assert.equal(n2.toTex(), '{4} - {5}');
+    assert.equal(n3.toTex(), '\\left({{2} + {3}}\\right) \\cdot \\left({{4} - {5}}\\right)');
+    assert.equal(m3.toTex(), '{\\left({{2} + {3}}\\right) \\cdot {4}} - {5}');
   });
 
   it ('should have an identifier', function () {