From 37c1363c2df1b6d8fed90f0d7e27d22ffc5d895e Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Fri, 13 Mar 2015 21:23:13 +0100 Subject: [PATCH 01/10] Pass callbacks to toTex functions --- lib/expression/node/ArrayNode.js | 8 +++++--- lib/expression/node/AssignmentNode.js | 5 +++-- lib/expression/node/BlockNode.js | 5 +++-- lib/expression/node/ConditionalNode.js | 9 +++++---- lib/expression/node/ConstantNode.js | 3 ++- lib/expression/node/FunctionAssignmentNode.js | 7 ++++--- lib/expression/node/FunctionNode.js | 5 +++-- lib/expression/node/IndexNode.js | 7 ++++--- lib/expression/node/OperatorNode.js | 11 +++++----- lib/expression/node/RangeNode.js | 9 +++++---- lib/expression/node/SymbolNode.js | 5 +++-- lib/expression/node/UpdateNode.js | 5 +++-- lib/util/latex.js | 20 +++++++++---------- 13 files changed, 56 insertions(+), 43 deletions(-) diff --git a/lib/expression/node/ArrayNode.js b/lib/expression/node/ArrayNode.js index fbbb67bf7..4d9d05c82 100644 --- a/lib/expression/node/ArrayNode.js +++ b/lib/expression/node/ArrayNode.js @@ -95,20 +95,22 @@ ArrayNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) + * @param {String} type * @return {String} str */ -ArrayNode.prototype.toTex = function(type) { +ArrayNode.prototype.toTex = function(customFunctions, type) { type = type || 'bmatrix'; var s = '\\begin{' + type + '}'; this.nodes.forEach(function(node) { if (node.nodes) { s += node.nodes.map(function(childNode) { - return childNode.toTex(); + return childNode.toTex(customFunctions); }).join('&'); } else { - s += node.toTex(); + s += node.toTex(customFunctions); } // new line diff --git a/lib/expression/node/AssignmentNode.js b/lib/expression/node/AssignmentNode.js index 90fb93373..1223166da 100644 --- a/lib/expression/node/AssignmentNode.js +++ b/lib/expression/node/AssignmentNode.js @@ -89,13 +89,14 @@ AssignmentNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} */ -AssignmentNode.prototype.toTex = function() { +AssignmentNode.prototype.toTex = function(customFunctions) { var precedence = operators.getPrecedence(this); var exprPrecedence = operators.getPrecedence(this.expr); - var expr = this.expr.toTex(); + var expr = this.expr.toTex(customFunctions); if ((exprPrecedence !== null) && (exprPrecedence <= precedence)) { //adds visible round brackets expr = latex.addBraces(expr, true); diff --git a/lib/expression/node/BlockNode.js b/lib/expression/node/BlockNode.js index 57c2e4e03..9f674ba50 100644 --- a/lib/expression/node/BlockNode.js +++ b/lib/expression/node/BlockNode.js @@ -122,11 +122,12 @@ BlockNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} str */ -BlockNode.prototype.toTex = function() { +BlockNode.prototype.toTex = function(customFunctions) { return this.blocks.map(function (param) { - return param.node.toTex() + (param.visible ? '' : ';'); + return param.node.toTex(customFunctions) + (param.visible ? '' : ';'); }).join('\n'); }; diff --git a/lib/expression/node/ConditionalNode.js b/lib/expression/node/ConditionalNode.js index 54ea5c02f..1ab699f12 100644 --- a/lib/expression/node/ConditionalNode.js +++ b/lib/expression/node/ConditionalNode.js @@ -151,15 +151,16 @@ ConditionalNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} str */ -ConditionalNode.prototype.toTex = function() { +ConditionalNode.prototype.toTex = function(customFunctions) { var s = ( - latex.addBraces(this.trueExpr.toTex()) + + latex.addBraces(this.trueExpr.toTex(customFunctions)) + ', &\\quad' + - latex.addBraces('\\text{if}\\;' + this.condition.toTex()) + latex.addBraces('\\text{if}\\;' + this.condition.toTex(customFunctions)) ) + '\\\\' + ( - latex.addBraces(this.falseExpr.toTex()) + + latex.addBraces(this.falseExpr.toTex(customFunctions)) + ', &\\quad' + latex.addBraces('\\text{otherwise}') ); diff --git a/lib/expression/node/ConstantNode.js b/lib/expression/node/ConstantNode.js index 5b47d40e8..f50071886 100644 --- a/lib/expression/node/ConstantNode.js +++ b/lib/expression/node/ConstantNode.js @@ -156,9 +156,10 @@ ConstantNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} str */ -ConstantNode.prototype.toTex = function() { +ConstantNode.prototype.toTex = function(customFunctions) { var value = this.value, index; switch (this.valueType) { diff --git a/lib/expression/node/FunctionAssignmentNode.js b/lib/expression/node/FunctionAssignmentNode.js index 629360ca1..97138fca0 100644 --- a/lib/expression/node/FunctionAssignmentNode.js +++ b/lib/expression/node/FunctionAssignmentNode.js @@ -110,13 +110,14 @@ FunctionAssignmentNode.prototype.toString = function() { /** * get LaTeX representation + * @param {Object|function} callback(s) * @return {String} str */ -FunctionAssignmentNode.prototype.toTex = function() { +FunctionAssignmentNode.prototype.toTex = function(customFunctions) { var precedence = operators.getPrecedence(this); var exprPrecedence = operators.getPrecedence(this.expr); - var expr = this.expr.toTex(); + var expr = this.expr.toTex(customFunctions); if ((exprPrecedence !== null) && (exprPrecedence <= precedence)) { //adds visible round brackets expr = latex.addBraces(expr, true); @@ -127,7 +128,7 @@ FunctionAssignmentNode.prototype.toTex = function() { } return latex.toFunction(this.name) - + latex.addBraces(this.params.map(latex.toSymbol).join(', '), true) + '=' + + latex.addBraces(this.params.map(latex.toSymbol).join(', '), true) + '=' //FIXME, this doesn't call toTex on the parameters AFAIK (toSymbol) + expr; }; diff --git a/lib/expression/node/FunctionNode.js b/lib/expression/node/FunctionNode.js index b36255502..1f613855c 100644 --- a/lib/expression/node/FunctionNode.js +++ b/lib/expression/node/FunctionNode.js @@ -113,10 +113,11 @@ FunctionNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} str */ -FunctionNode.prototype.toTex = function() { - return latex.toArgs(this); +FunctionNode.prototype.toTex = function(customFunctions) { + return latex.toArgs(this, customFunctions); }; /** diff --git a/lib/expression/node/IndexNode.js b/lib/expression/node/IndexNode.js index cb8519a22..d6bd5b995 100644 --- a/lib/expression/node/IndexNode.js +++ b/lib/expression/node/IndexNode.js @@ -209,10 +209,11 @@ IndexNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} str */ -IndexNode.prototype.toTex = function() { - return this.object.toTex() + '[' + this.ranges.join(', ') + ']'; +IndexNode.prototype.toTex = function(customFunctions) { + return this.object.toTex(customFunctions) + '[' + this.ranges.join(', ') + ']'; }; -module.exports = IndexNode; \ No newline at end of file +module.exports = IndexNode; diff --git a/lib/expression/node/OperatorNode.js b/lib/expression/node/OperatorNode.js index 5c97904e4..9a02e79e6 100644 --- a/lib/expression/node/OperatorNode.js +++ b/lib/expression/node/OperatorNode.js @@ -236,9 +236,10 @@ OperatorNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} str */ -OperatorNode.prototype.toTex = function() { +OperatorNode.prototype.toTex = function(customFunctions) { var args = this.args; var parens = calculateNecessaryParentheses(this, args); var op = latex.toOperator(this.op); //operator @@ -247,7 +248,7 @@ OperatorNode.prototype.toTex = function() { case 1: //unary operators var assoc = operators.getAssociativity(this); - var operand = args[0].toTex(); + var operand = args[0].toTex(customFunctions); if (parens[0]) { operand = latex.addBraces(operand, true); } @@ -265,9 +266,9 @@ OperatorNode.prototype.toTex = function() { 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 lhsTex = latex.addBraces(lhs.toTex(customFunctions), parens[0]); var rhs = args[1]; //right hand side - var rhsTex = latex.addBraces(rhs.toTex(), parens[1]); + var rhsTex = latex.addBraces(rhs.toTex(customFunctions), parens[1]); switch (this.getIdentifier()) { case 'OperatorNode:divide': @@ -275,7 +276,7 @@ OperatorNode.prototype.toTex = function() { return op + lhsTex + rhsTex; case 'OperatorNode:to': - rhsTex = latex.toUnit(rhs.toTex()); + rhsTex = latex.toUnit(rhs.toTex(customFunctions)); rhsTex = latex.addBraces(rhsTex, parens[1]); break; } diff --git a/lib/expression/node/RangeNode.js b/lib/expression/node/RangeNode.js index f1f08b2bb..7702c6f6c 100644 --- a/lib/expression/node/RangeNode.js +++ b/lib/expression/node/RangeNode.js @@ -121,14 +121,15 @@ RangeNode.prototype.toString = function() { /** * Get LaTeX representation + * @params {Object|function} callback(s) * @return {String} str */ -RangeNode.prototype.toTex = function() { - var str = this.start.toTex(); +RangeNode.prototype.toTex = function(customFunctions) { + var str = this.start.toTex(customFunctions); if (this.step) { - str += ':' + this.step.toTex(); + str += ':' + this.step.toTex(customFunctions); } - str += ':' + this.end.toTex(); + str += ':' + this.end.toTex(customFunctions); return str; }; diff --git a/lib/expression/node/SymbolNode.js b/lib/expression/node/SymbolNode.js index e6053d379..cb3ff1ad8 100644 --- a/lib/expression/node/SymbolNode.js +++ b/lib/expression/node/SymbolNode.js @@ -99,11 +99,12 @@ SymbolNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} str * @override */ -SymbolNode.prototype.toTex = function() { - return latex.toSymbol(this.name); +SymbolNode.prototype.toTex = function(customFunctions) { + return latex.toSymbol(this.name, customFunctions); }; module.exports = SymbolNode; diff --git a/lib/expression/node/UpdateNode.js b/lib/expression/node/UpdateNode.js index 523019d9e..f48c8cb46 100644 --- a/lib/expression/node/UpdateNode.js +++ b/lib/expression/node/UpdateNode.js @@ -84,10 +84,11 @@ UpdateNode.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} */ -UpdateNode.prototype.toTex = function() { - return this.index.toTex() + ' = ' + this.expr.toTex(); +UpdateNode.prototype.toTex = function(customFunctions) { + return this.index.toTex(customFunctions) + ' = ' + this.expr.toTex(customFunctions); }; module.exports = UpdateNode; diff --git a/lib/util/latex.js b/lib/util/latex.js index 176479d59..f15a36c01 100644 --- a/lib/util/latex.js +++ b/lib/util/latex.js @@ -286,7 +286,7 @@ exports.addBraces = function(s, brace, type) { return braces[0] + s + braces[1]; }; -exports.toArgs = function(that) { +exports.toArgs = function(that, customFunctions) { var name = that.name, args = that.args, func = exports.toSymbol(that.name), @@ -358,13 +358,13 @@ exports.toArgs = function(that) { op = '!'; } else { - return '{\\left(' + args[0].toTex() + '\\right)!}'; + return '{\\left(' + args[0].toTex(customFunctions) + '\\right)!}'; } } else { // op = 'P'; - var n = args[0].toTex(), - k = args[1].toTex(); + var n = args[0].toTex(customFunctions), + k = args[1].toTex(customFunctions); return '\\frac{' + n + '!}{\\left(' + n + ' - ' + k + '\\right)!}'; } break; @@ -385,7 +385,7 @@ exports.toArgs = function(that) { type = 'lr'; if (args.length === 2) { - var tmp = args[1].toTex(); + var tmp = args[1].toTex(customFunctions); if (tmp === '\\text{inf}') { tmp = '\\infty'; @@ -417,7 +417,7 @@ exports.toArgs = function(that) { type = 'lr'; if (args.length === 2) { - suffix = '_' + exports.addBraces(args[1].toTex()); + suffix = '_' + exports.addBraces(args[1].toTex(customFunctions)); args = [args[0]]; } break; @@ -437,7 +437,7 @@ exports.toArgs = function(that) { case 'log': var base = 'e'; if (args.length === 2) { - base = args[1].toTex(); + base = args[1].toTex(customFunctions); func = '\\log_{' + base + '}'; args = [args[0]]; } @@ -466,7 +466,7 @@ exports.toArgs = function(that) { case 'det': if (that.args[0] instanceof ArrayNode) { - return that.args[0].toTex('vmatrix'); + return that.args[0].toTex(customFunctions, 'vmatrix'); } brace = 'vmatrix'; @@ -480,7 +480,7 @@ exports.toArgs = function(that) { if (op !== null) { brace = (op === '+' || op === '-'); - texParams = (new OperatorNode(op, name, args)).toTex(); + texParams = (new OperatorNode(op, name, args)).toTex(customFunctions); } else { op = ', '; @@ -491,7 +491,7 @@ exports.toArgs = function(that) { } texParams = texParams || args.map(function(param) { - return '{' + param.toTex() + '}' ; + return '{' + param.toTex(customFunctions) + '}' ; }).join(op); return prefix + (showFunc ? func : '') + From 35ce7f7fb4e99bd02e50fa2b9804b02b89c12ffa Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Fri, 13 Mar 2015 21:23:14 +0100 Subject: [PATCH 02/10] Make Node.toTex a wrapper like Node.compile Node.prototype.toTex is now a wrapper that calls the node's _toTex --- lib/expression/node/ArrayNode.js | 2 +- lib/expression/node/AssignmentNode.js | 2 +- lib/expression/node/BlockNode.js | 2 +- lib/expression/node/ConditionalNode.js | 2 +- lib/expression/node/ConstantNode.js | 2 +- lib/expression/node/FunctionAssignmentNode.js | 2 +- lib/expression/node/FunctionNode.js | 2 +- lib/expression/node/IndexNode.js | 2 +- lib/expression/node/Node.js | 17 +++++++++++++++-- lib/expression/node/OperatorNode.js | 2 +- lib/expression/node/RangeNode.js | 2 +- lib/expression/node/SymbolNode.js | 2 +- lib/expression/node/UpdateNode.js | 2 +- test/expression/node/Node.test.js | 8 +++++--- 14 files changed, 32 insertions(+), 17 deletions(-) diff --git a/lib/expression/node/ArrayNode.js b/lib/expression/node/ArrayNode.js index 4d9d05c82..caf7a6078 100644 --- a/lib/expression/node/ArrayNode.js +++ b/lib/expression/node/ArrayNode.js @@ -99,7 +99,7 @@ ArrayNode.prototype.toString = function() { * @param {String} type * @return {String} str */ -ArrayNode.prototype.toTex = function(customFunctions, type) { +ArrayNode.prototype._toTex = function(customFunctions, type) { type = type || 'bmatrix'; var s = '\\begin{' + type + '}'; diff --git a/lib/expression/node/AssignmentNode.js b/lib/expression/node/AssignmentNode.js index 1223166da..ce9da5cfc 100644 --- a/lib/expression/node/AssignmentNode.js +++ b/lib/expression/node/AssignmentNode.js @@ -92,7 +92,7 @@ AssignmentNode.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} */ -AssignmentNode.prototype.toTex = function(customFunctions) { +AssignmentNode.prototype._toTex = function(customFunctions) { var precedence = operators.getPrecedence(this); var exprPrecedence = operators.getPrecedence(this.expr); diff --git a/lib/expression/node/BlockNode.js b/lib/expression/node/BlockNode.js index 9f674ba50..90b2d1a14 100644 --- a/lib/expression/node/BlockNode.js +++ b/lib/expression/node/BlockNode.js @@ -125,7 +125,7 @@ BlockNode.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} str */ -BlockNode.prototype.toTex = function(customFunctions) { +BlockNode.prototype._toTex = function(customFunctions) { return this.blocks.map(function (param) { return param.node.toTex(customFunctions) + (param.visible ? '' : ';'); }).join('\n'); diff --git a/lib/expression/node/ConditionalNode.js b/lib/expression/node/ConditionalNode.js index 1ab699f12..29f4c9ff3 100644 --- a/lib/expression/node/ConditionalNode.js +++ b/lib/expression/node/ConditionalNode.js @@ -154,7 +154,7 @@ ConditionalNode.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} str */ -ConditionalNode.prototype.toTex = function(customFunctions) { +ConditionalNode.prototype._toTex = function(customFunctions) { var s = ( latex.addBraces(this.trueExpr.toTex(customFunctions)) + ', &\\quad' + diff --git a/lib/expression/node/ConstantNode.js b/lib/expression/node/ConstantNode.js index f50071886..dd001315e 100644 --- a/lib/expression/node/ConstantNode.js +++ b/lib/expression/node/ConstantNode.js @@ -159,7 +159,7 @@ ConstantNode.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} str */ -ConstantNode.prototype.toTex = function(customFunctions) { +ConstantNode.prototype._toTex = function(customFunctions) { var value = this.value, index; switch (this.valueType) { diff --git a/lib/expression/node/FunctionAssignmentNode.js b/lib/expression/node/FunctionAssignmentNode.js index 97138fca0..5718c0908 100644 --- a/lib/expression/node/FunctionAssignmentNode.js +++ b/lib/expression/node/FunctionAssignmentNode.js @@ -113,7 +113,7 @@ FunctionAssignmentNode.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} str */ -FunctionAssignmentNode.prototype.toTex = function(customFunctions) { +FunctionAssignmentNode.prototype._toTex = function(customFunctions) { var precedence = operators.getPrecedence(this); var exprPrecedence = operators.getPrecedence(this.expr); diff --git a/lib/expression/node/FunctionNode.js b/lib/expression/node/FunctionNode.js index 1f613855c..eaeee60d2 100644 --- a/lib/expression/node/FunctionNode.js +++ b/lib/expression/node/FunctionNode.js @@ -116,7 +116,7 @@ FunctionNode.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} str */ -FunctionNode.prototype.toTex = function(customFunctions) { +FunctionNode.prototype._toTex = function(customFunctions) { return latex.toArgs(this, customFunctions); }; diff --git a/lib/expression/node/IndexNode.js b/lib/expression/node/IndexNode.js index d6bd5b995..175826630 100644 --- a/lib/expression/node/IndexNode.js +++ b/lib/expression/node/IndexNode.js @@ -212,7 +212,7 @@ IndexNode.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} str */ -IndexNode.prototype.toTex = function(customFunctions) { +IndexNode.prototype._toTex = function(customFunctions) { return this.object.toTex(customFunctions) + '[' + this.ranges.join(', ') + ']'; }; diff --git a/lib/expression/node/Node.js b/lib/expression/node/Node.js index 5b97d5693..fcd8d5cc3 100644 --- a/lib/expression/node/Node.js +++ b/lib/expression/node/Node.js @@ -222,10 +222,23 @@ Node.prototype.toString = function() { /** * Get LaTeX representation + * @param {Object|function} callback(s) * @return {String} */ -Node.prototype.toTex = function() { - return ''; +Node.prototype.toTex = function(customFunctions) { + return this._toTex(customFunctions); +}; + +/** + * Internal function to generate the LaTeX output. + * This has to be implemented by every Node + * + * @param {Object}|function} + * @throws {Error} + */ +Node.prototype._toTex = function () { + //must be implemented by each of the Node implementations + throw new Error('_toTex not implemented for this Node'); }; /** diff --git a/lib/expression/node/OperatorNode.js b/lib/expression/node/OperatorNode.js index 9a02e79e6..986fed1af 100644 --- a/lib/expression/node/OperatorNode.js +++ b/lib/expression/node/OperatorNode.js @@ -239,7 +239,7 @@ OperatorNode.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} str */ -OperatorNode.prototype.toTex = function(customFunctions) { +OperatorNode.prototype._toTex = function(customFunctions) { var args = this.args; var parens = calculateNecessaryParentheses(this, args); var op = latex.toOperator(this.op); //operator diff --git a/lib/expression/node/RangeNode.js b/lib/expression/node/RangeNode.js index 7702c6f6c..f28cfc156 100644 --- a/lib/expression/node/RangeNode.js +++ b/lib/expression/node/RangeNode.js @@ -124,7 +124,7 @@ RangeNode.prototype.toString = function() { * @params {Object|function} callback(s) * @return {String} str */ -RangeNode.prototype.toTex = function(customFunctions) { +RangeNode.prototype._toTex = function(customFunctions) { var str = this.start.toTex(customFunctions); if (this.step) { str += ':' + this.step.toTex(customFunctions); diff --git a/lib/expression/node/SymbolNode.js b/lib/expression/node/SymbolNode.js index cb3ff1ad8..34409fe80 100644 --- a/lib/expression/node/SymbolNode.js +++ b/lib/expression/node/SymbolNode.js @@ -103,7 +103,7 @@ SymbolNode.prototype.toString = function() { * @return {String} str * @override */ -SymbolNode.prototype.toTex = function(customFunctions) { +SymbolNode.prototype._toTex = function(customFunctions) { return latex.toSymbol(this.name, customFunctions); }; diff --git a/lib/expression/node/UpdateNode.js b/lib/expression/node/UpdateNode.js index f48c8cb46..346504ff6 100644 --- a/lib/expression/node/UpdateNode.js +++ b/lib/expression/node/UpdateNode.js @@ -87,7 +87,7 @@ UpdateNode.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} */ -UpdateNode.prototype.toTex = function(customFunctions) { +UpdateNode.prototype._toTex = function(customFunctions) { return this.index.toTex(customFunctions) + ' = ' + this.expr.toTex(customFunctions); }; diff --git a/test/expression/node/Node.test.js b/test/expression/node/Node.test.js index 355856d16..02589a4e7 100644 --- a/test/expression/node/Node.test.js +++ b/test/expression/node/Node.test.js @@ -74,9 +74,11 @@ describe('Node', function() { assert.equal(node.toString(), ''); }); - it ('should LaTeX a Node', function () { - var node = new Node(); - assert.equal(node.toTex(), ''); + it ('should throw an error when calling toTex', function () { + assert.throws(function () { + var node = new Node(); + node.toTex(); + }, /_toTex not implemented for this Node/); }); it ('should throw an error in case of wrong arguments for compile', function () { From 0fee9173c84677c781d897821539be54df064478 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Fri, 13 Mar 2015 21:23:14 +0100 Subject: [PATCH 03/10] Enable custom toTex functions --- lib/expression/node/Node.js | 26 +++++++++- test/expression/node/ArrayNode.test.js | 25 ++++++++++ test/expression/node/AssignmentNode.test.js | 18 +++++++ test/expression/node/BlockNode.test.js | 24 ++++++++++ test/expression/node/ConditionalNode.test.js | 22 +++++++++ test/expression/node/ConstantNode.test.js | 13 +++++ .../node/FunctionAssignmentNode.test.js | 25 ++++++++++ test/expression/node/FunctionNode.test.js | 47 +++++++++++++++++++ test/expression/node/IndexNode.test.js | 25 ++++++++++ test/expression/node/Node.test.js | 19 +++++++- test/expression/node/OperatorNode.test.js | 44 +++++++++++++++++ test/expression/node/RangeNode.test.js | 22 +++++++++ test/expression/node/SymbolNode.test.js | 13 +++++ test/expression/node/UpdateNode.test.js | 31 ++++++++++++ 14 files changed, 351 insertions(+), 3 deletions(-) diff --git a/lib/expression/node/Node.js b/lib/expression/node/Node.js index fcd8d5cc3..5378a368c 100644 --- a/lib/expression/node/Node.js +++ b/lib/expression/node/Node.js @@ -221,11 +221,35 @@ Node.prototype.toString = function() { }; /** - * Get LaTeX representation + * Get LaTeX representation. (wrapper function) + * This functions get's either an object containing callbacks or + * a single callback. It decides whether to call the callback and if + * not or if the callback returns nothing, it calls the default + * LaTeX implementation of the node (_toTex). + * * @param {Object|function} callback(s) * @return {String} */ Node.prototype.toTex = function(customFunctions) { + var customTex; + if (typeof customFunctions === 'object') { + //if customFunctions is a map of callback functions + if (customFunctions.hasOwnProperty(this.type)) { + customTex = customFunctions[this.type](this, customFunctions); + } + else if (customFunctions.hasOwnProperty(this.getIdentifier())) { + customTex = customFunctions[this.getIdentifier()](this, customFunctions); + } + } + else if (typeof customFunctions === 'function') { + //if customFunctions is a callback function + customTex = customFunctions(this, customFunctions); + } + + if (typeof customTex !== 'undefined') { + return customTex; + } + return this._toTex(customFunctions); }; diff --git a/test/expression/node/ArrayNode.test.js b/test/expression/node/ArrayNode.test.js index 56259354c..c6f238949 100644 --- a/test/expression/node/ArrayNode.test.js +++ b/test/expression/node/ArrayNode.test.js @@ -236,4 +236,29 @@ describe('ArrayNode', function() { assert.equal(n.toTex(), '\\begin{bmatrix}1&2\\\\3&4\\\\\\end{bmatrix}'); }); + it ('should LaTeX an ArrayNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunctions = { + ArrayNode: function (node, callbacks) { + var latex = '\\left['; + node.nodes.forEach(function (node) { + latex += node.toTex(callbacks) + ', '; + }); + + latex += '\\right]'; + return latex; + }, + ConstantNode: function (node, callbacks) { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + var b = new ConstantNode(2); + + var n = new ArrayNode([a, b]); + + assert.equal(n.toTex(customFunctions), '\\left[const\\left(1, number\\right), const\\left(2, number\\right), \\right]'); + }); + }); diff --git a/test/expression/node/AssignmentNode.test.js b/test/expression/node/AssignmentNode.test.js index 8c555081d..9b155f113 100644 --- a/test/expression/node/AssignmentNode.test.js +++ b/test/expression/node/AssignmentNode.test.js @@ -225,4 +225,22 @@ describe('AssignmentNode', function() { assert.equal(n.toTex(), '{b}=\\left({{a}={2}}\\right)'); }); + it ('should LaTeX an AssignmentNode with custom toTex', function () { + //Also checks if custom funcions get passed to the children + var customFunctions = { + AssignmentNode: function (node, callbacks) { + return node.name + '\\mbox{equals}' + node.expr.toTex(callbacks); + }, + ConstantNode: function (node, callbacks) { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + + var n = new AssignmentNode('a', a); + + assert.equal(n.toTex(customFunctions), 'a\\mbox{equals}const\\left(1, number\\right)'); + }); + }); diff --git a/test/expression/node/BlockNode.test.js b/test/expression/node/BlockNode.test.js index db8d66f6e..d0c4582fb 100644 --- a/test/expression/node/BlockNode.test.js +++ b/test/expression/node/BlockNode.test.js @@ -256,4 +256,28 @@ describe('BlockNode', function() { assert.equal(n.toTex(), '5\n{foo}={3};\nfoo'); }); + it ('should LaTeX a BlockNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunctions = { + BlockNode: function (node, callbacks) { + var latex = ''; + node.blocks.forEach(function (block) { + latex += block.node.toTex(callbacks) + '; '; + }); + + return latex; + }, + ConstantNode: function (node, callbacks) { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + var b = new ConstantNode(2); + + var n = new BlockNode([{node: a}, {node: b}]); + + assert.equal(n.toTex(customFunctions), 'const\\left(1, number\\right); const\\left(2, number\\right); '); + }); + }); diff --git a/test/expression/node/ConditionalNode.test.js b/test/expression/node/ConditionalNode.test.js index e50ab1305..2cc731f9c 100644 --- a/test/expression/node/ConditionalNode.test.js +++ b/test/expression/node/ConditionalNode.test.js @@ -260,4 +260,26 @@ describe('ConditionalNode', function() { assert.equal(n.toTex(), '\\left\\{\\begin{array}{l l}{{a}={2}}, &\\quad{\\text{if}\\;true}\\\\{{b}={3}}, &\\quad{\\text{otherwise}}\\end{array}\\right.'); }); + it ('should LaTeX a ConditionalNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunctions = { + ConditionalNode: function (node, callbacks) { + return 'if ' + node.condition.toTex(callbacks) + + ' then ' + node.trueExpr.toTex(callbacks) + + ' else ' + node.falseExpr.toTex(callbacks); + }, + ConstantNode: function (node, callbacks) { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + var b = new ConstantNode(2); + var c = new ConstantNode(3); + + var n = new ConditionalNode(a, b, c); + + assert.equal(n.toTex(customFunctions), 'if const\\left(1, number\\right) then const\\left(2, number\\right) else const\\left(3, number\\right)'); + }); + }); diff --git a/test/expression/node/ConstantNode.test.js b/test/expression/node/ConstantNode.test.js index a287de12b..4aaf50aa0 100644 --- a/test/expression/node/ConstantNode.test.js +++ b/test/expression/node/ConstantNode.test.js @@ -139,4 +139,17 @@ describe('ConstantNode', function() { assert.equal(new ConstantNode('null', 'null').toTex(), 'null'); }); + it ('should LaTeX a ConstantNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunctions = { + ConstantNode: function (node, callbacks) { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var n = new ConstantNode(1); + + assert.equal(n.toTex(customFunctions), 'const\\left(1, number\\right)'); + }); + }); diff --git a/test/expression/node/FunctionAssignmentNode.test.js b/test/expression/node/FunctionAssignmentNode.test.js index 9b6209ea7..5e2441855 100644 --- a/test/expression/node/FunctionAssignmentNode.test.js +++ b/test/expression/node/FunctionAssignmentNode.test.js @@ -206,4 +206,29 @@ describe('FunctionAssignmentNode', function() { assert.equal(n.toTex(), 'f\\left({x}\\right)=\\left({{a}={2}}\\right)'); }); + + it ('should LaTeX a FunctionAssignmentNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunctions = { + FunctionAssignmentNode: function (node, callbacks) { + var latex = '\\mbox{' + node.name + '}\\left('; + node.params.forEach(function (param) { + latex += param + ', '; + }); + + latex += '\\right)=' + node.expr.toTex(customFunctions); + return latex; + }, + ConstantNode: function (node, callbacks) { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + + var n = new FunctionAssignmentNode('func', ['x'], a); + + assert.equal(n.toTex(customFunctions), '\\mbox{func}\\left(x, \\right)=const\\left(1, number\\right)'); + }); + }); diff --git a/test/expression/node/FunctionNode.test.js b/test/expression/node/FunctionNode.test.js index d5e743746..d2df4aea6 100644 --- a/test/expression/node/FunctionNode.test.js +++ b/test/expression/node/FunctionNode.test.js @@ -288,4 +288,51 @@ describe('FunctionNode', function() { assert.equal(n.getIdentifier(), 'FunctionNode:factorial'); }); + it ('should LaTeX a FunctionNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunctions = { + 'FunctionNode': function (node, callbacks) { + var latex = '\\mbox{' + node.name + '}\\left('; + node.args.forEach(function (arg) { + latex += arg.toTex(callbacks) + ', '; + }); + latex += '\\right)'; + return latex; + }, + ConstantNode: function (node, callbacks) { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + var b = new ConstantNode(2); + + var n1 = new FunctionNode('add', [a, b]); + var n2 = new FunctionNode('subtract', [a, b]); + + assert.equal(n1.toTex(customFunctions), '\\mbox{add}\\left(const\\left(1, number\\right), const\\left(2, number\\right), \\right)'); + assert.equal(n2.toTex(customFunctions), '\\mbox{subtract}\\left(const\\left(1, number\\right), const\\left(2, number\\right), \\right)'); + }); + + it ('should LaTeX a FunctionNode with custom toTex for a single function', function () { + //Also checks if the custom functions get passed on to the children + var customFunctions = { + 'FunctionNode:add': function (node, callbacks) { + return node.args[0].toTex(callbacks) + + ' ' + node.name + ' ' + + node.args[1].toTex(callbacks); + }, + ConstantNode: function (node, callbacks) { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + var b = new ConstantNode(2); + + var n = new FunctionNode('add', [a, b]); + + assert.equal(n.toTex(customFunctions), 'const\\left(1, number\\right) add const\\left(2, number\\right)'); + }); + }); diff --git a/test/expression/node/IndexNode.test.js b/test/expression/node/IndexNode.test.js index dc041b3e9..42e4cf384 100644 --- a/test/expression/node/IndexNode.test.js +++ b/test/expression/node/IndexNode.test.js @@ -285,4 +285,29 @@ describe('IndexNode', function() { assert.equal(n2.toTex(), 'a[]') }); + it ('should LaTeX an IndexNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunctions = { + IndexNode: function (node, callbacks) { + var latex = node.object.toTex(callbacks) + ' at '; + node.ranges.forEach(function (range) { + latex += range.toTex(callbacks) + ', '; + }); + + return latex; + }, + ConstantNode: function (node, callbacks) { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new SymbolNode('a'); + var b = new ConstantNode(1); + var c = new ConstantNode(2); + + var n = new IndexNode(a, [b, c]); + + assert.equal(n.toTex(customFunctions), 'a at const\\left(1, number\\right), const\\left(2, number\\right), '); + }); + }); diff --git a/test/expression/node/Node.test.js b/test/expression/node/Node.test.js index 02589a4e7..02bb6282c 100644 --- a/test/expression/node/Node.test.js +++ b/test/expression/node/Node.test.js @@ -74,13 +74,28 @@ describe('Node', function() { assert.equal(node.toString(), ''); }); - it ('should throw an error when calling toTex', function () { + it ('should throw an error when calling _toTex', function () { assert.throws(function () { var node = new Node(); - node.toTex(); + node._toTex(); }, /_toTex not implemented for this Node/); }); + it ('should ignore custom toTex if it returns nothing', function () { + var callback1 = function (node, callback) {}; + var callback2 = { + Node: function (node, callbacks) {} + }; + var mymath = math.create(); + mymath.expression.node.Node.prototype._toTex = function () { + return 'default'; + }; + var n = new mymath.expression.node.Node(); + + assert.equal(n.toTex(callback1), 'default'); + assert.equal(n.toTex(callback2), 'default'); + }); + it ('should throw an error in case of wrong arguments for compile', function () { var node = new Node(); assert.throws(function () { diff --git a/test/expression/node/OperatorNode.test.js b/test/expression/node/OperatorNode.test.js index 07ded28f0..774b7b892 100644 --- a/test/expression/node/OperatorNode.test.js +++ b/test/expression/node/OperatorNode.test.js @@ -378,4 +378,48 @@ describe('OperatorNode', function() { assert.equal(n.getIdentifier(), 'OperatorNode:add'); }); + it ('should LaTeX an OperatorNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunctions = { + 'OperatorNode': function (node, callbacks) { + return node.op + node.fn + '(' + + node.args[0].toTex(customFunctions) + + ', ' + node.args[1].toTex(customFunctions) + ')'; + }, + ConstantNode: function (node, callbacks) { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + var b = new ConstantNode(2); + + var n1 = new OperatorNode('+', 'add', [a, b]); + var n2 = new OperatorNode('-', 'subtract', [a, b]); + + assert.equal(n1.toTex(customFunctions), '+add(const\\left(1, number\\right), const\\left(2, number\\right))'); + assert.equal(n2.toTex(customFunctions), '-subtract(const\\left(1, number\\right), const\\left(2, number\\right))'); + }); + + it ('should LaTeX an OperatorNode with custom toTex for a single operator', function () { + //Also checks if the custom functions get passed on to the children + var customFunctions = { + 'OperatorNode:add': function (node, callbacks) { + return node.args[0].toTex(customFunctions) + + node.op + node.fn + node.op + + node.args[1].toTex(customFunctions); + }, + ConstantNode: function (node, callbacks) { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + var b = new ConstantNode(2); + + var n = new OperatorNode('+', 'add', [a, b]); + + assert.equal(n.toTex(customFunctions), 'const\\left(1, number\\right)+add+const\\left(2, number\\right)'); + }); + }); diff --git a/test/expression/node/RangeNode.test.js b/test/expression/node/RangeNode.test.js index a69c529cd..88f8162c8 100644 --- a/test/expression/node/RangeNode.test.js +++ b/test/expression/node/RangeNode.test.js @@ -289,4 +289,26 @@ describe('RangeNode', function() { assert.equal(n.toTex(), '0:2:10'); }); + it ('should LaTeX a RangeNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunctions = { + RangeNode: function (node, callbacks) { + return 'from ' + node.start.toTex(customFunctions) + + ' to ' + node.end.toTex(customFunctions) + + ' with steps of ' + node.step.toTex(customFunctions); + }, + ConstantNode: function (node, callbacks) { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new ConstantNode(1); + var b = new ConstantNode(2); + var c = new ConstantNode(3); + + var n = new RangeNode(a, b, c); + + assert.equal(n.toTex(customFunctions), 'from const\\left(1, number\\right) to const\\left(2, number\\right) with steps of const\\left(3, number\\right)'); + }); + }); diff --git a/test/expression/node/SymbolNode.test.js b/test/expression/node/SymbolNode.test.js index 33932fbd2..22b8ed9f9 100644 --- a/test/expression/node/SymbolNode.test.js +++ b/test/expression/node/SymbolNode.test.js @@ -107,4 +107,17 @@ describe('SymbolNode', function() { assert.equal(s.toTex(), 'foo'); }); + it ('should LaTeX a SymbolNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunctions = { + SymbolNode: function (node, callbacks) { + return 'symbol(' + node.name + ')'; + } + }; + + var n = new SymbolNode('a'); + + assert.equal(n.toTex(customFunctions), 'symbol(a)'); + }); + }); diff --git a/test/expression/node/UpdateNode.test.js b/test/expression/node/UpdateNode.test.js index 402029f84..c0739e708 100644 --- a/test/expression/node/UpdateNode.test.js +++ b/test/expression/node/UpdateNode.test.js @@ -322,4 +322,35 @@ describe('UpdateNode', function() { assert.equal(n.toString(), 'a[2, 1] = 5'); }); + it ('should LaTeX an UpdateNode with custom toTex', function () { + //Also checks if the custom functions get passed on to the children + var customFunctions = { + UpdateNode: function (node, callbacks) { + return node.index.toTex(callbacks) + ' equals ' + node.expr.toTex(customFunctions); + }, + IndexNode: function (node, callbacks) { + var latex = node.object.toTex(callbacks) + ' at '; + node.ranges.forEach(function (range) { + latex += range.toTex(callbacks) + ', '; + }); + + return latex; + }, + ConstantNode: function (node, callbacks) { + return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' + } + }; + + var a = new SymbolNode('a'); + var ranges = [ + new ConstantNode(2), + new ConstantNode(1) + ]; + var v = new ConstantNode(5); + + var n = new UpdateNode(new IndexNode(a, ranges), v); + + assert.equal(n.toTex(customFunctions), 'a at const\\left(2, number\\right), const\\left(1, number\\right), equals const\\left(5, number\\right)'); + }); + }); From c9d20285e9900080af29ff830a487c05d03d3414 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Fri, 13 Mar 2015 21:23:14 +0100 Subject: [PATCH 04/10] Fix breaking change ( passing type to ArrayNode.toTex ) --- lib/expression/node/ArrayNode.js | 8 ++++---- lib/expression/node/Node.js | 8 ++++++++ lib/util/latex.js | 4 +++- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/expression/node/ArrayNode.js b/lib/expression/node/ArrayNode.js index caf7a6078..f8d99bbaf 100644 --- a/lib/expression/node/ArrayNode.js +++ b/lib/expression/node/ArrayNode.js @@ -99,9 +99,9 @@ ArrayNode.prototype.toString = function() { * @param {String} type * @return {String} str */ -ArrayNode.prototype._toTex = function(customFunctions, type) { - type = type || 'bmatrix'; - var s = '\\begin{' + type + '}'; +ArrayNode.prototype._toTex = function(customFunctions) { + this.latexType = this.latexType || 'bmatrix'; + var s = '\\begin{' + this.latexType + '}'; this.nodes.forEach(function(node) { if (node.nodes) { @@ -116,7 +116,7 @@ ArrayNode.prototype._toTex = function(customFunctions, type) { // new line s += '\\\\'; }); - s += '\\end{' + type + '}'; + s += '\\end{' + this.latexType + '}'; return s; }; diff --git a/lib/expression/node/Node.js b/lib/expression/node/Node.js index 5378a368c..ed5cd6392 100644 --- a/lib/expression/node/Node.js +++ b/lib/expression/node/Node.js @@ -245,6 +245,14 @@ Node.prototype.toTex = function(customFunctions) { //if customFunctions is a callback function customTex = customFunctions(this, customFunctions); } + else if ((typeof customFunctions === 'string') && (this.type === 'ArrayNode')) { + //FIXME this is only a workaround for a breaking change, + //remove this in version2 + this.latexType = customFunctions; + } + else if (typeof customFunctions !== 'undefined') { + throw new TypeError('Object or function expected as callback'); + } if (typeof customTex !== 'undefined') { return customTex; diff --git a/lib/util/latex.js b/lib/util/latex.js index f15a36c01..a16683370 100644 --- a/lib/util/latex.js +++ b/lib/util/latex.js @@ -466,7 +466,9 @@ exports.toArgs = function(that, customFunctions) { case 'det': if (that.args[0] instanceof ArrayNode) { - return that.args[0].toTex(customFunctions, 'vmatrix'); + //FIXME passing 'vmatrix' like that is really ugly + that.args[0].latexType = 'vmatrix'; + return that.args[0].toTex(customFunctions); } brace = 'vmatrix'; From af763aa996fc07f4c3568cd72a63df998e3250dd Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Fri, 13 Mar 2015 21:23:14 +0100 Subject: [PATCH 05/10] Fix breaking change (Node.toTex should return an empty string) --- lib/expression/node/Node.js | 4 ++++ test/expression/node/Node.test.js | 2 ++ 2 files changed, 6 insertions(+) diff --git a/lib/expression/node/Node.js b/lib/expression/node/Node.js index ed5cd6392..9f1d5d4f6 100644 --- a/lib/expression/node/Node.js +++ b/lib/expression/node/Node.js @@ -269,6 +269,10 @@ Node.prototype.toTex = function(customFunctions) { * @throws {Error} */ Node.prototype._toTex = function () { + if (this.type === 'Node') { + //FIXME remove this in v2 + return ''; + } //must be implemented by each of the Node implementations throw new Error('_toTex not implemented for this Node'); }; diff --git a/test/expression/node/Node.test.js b/test/expression/node/Node.test.js index 02bb6282c..1cec5db87 100644 --- a/test/expression/node/Node.test.js +++ b/test/expression/node/Node.test.js @@ -77,6 +77,8 @@ describe('Node', function() { it ('should throw an error when calling _toTex', function () { assert.throws(function () { var node = new Node(); + node.type = 'SpecialNode'; //this is necessary because toTex + //returns '' for a Node node._toTex(); }, /_toTex not implemented for this Node/); }); From 9a89ab27da9ddc594800299c2490c4d1da26e790 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Fri, 13 Mar 2015 21:23:14 +0100 Subject: [PATCH 06/10] Documentation for custom toTex functions --- docs/expressions/customization.md | 124 ++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/docs/expressions/customization.md b/docs/expressions/customization.md index 245ba7df7..96479e850 100644 --- a/docs/expressions/customization.md +++ b/docs/expressions/customization.md @@ -139,3 +139,127 @@ math.import({ math.eval('myFunction(2 + 3, sqrt(4))'); // returns 'arguments: 2 + 3, sqrt(4), evaluated: 5, 2' ``` + +## Custom LaTeX conversion + +You can provide the `toTex` function of an expression tree with your own LaTeX converters. +This can be used to override the builtin LaTeX conversion or provide LaTeX output for your own custom functions. + +You can pass your own callback(s) to `toTex`. If it returns nothing, the standard LaTeX conversion will be use. +If your callback returns a string, this string will be used. + +There's two ways of passing callbacks: +1. Pass a function to `toTex`. This function will then be used for every node. +2. Pass an object that maps identifiers to callbacks. Those callbacks will be used whenever the identifier applies +to the node's type of `getIdentifier()`. + +**Examples for option 1:** + +```js +var customLaTeX = function (node, callback) { + if ((node.type === 'OperatorNode') && (node.fn === 'add')) { + //don't forget to pass the callback to the toTex functions + return node.args[0].toTex(callback) + ' plus ' + node.args[1].toTex(callback); + } + else if (node.type === 'ConstantNode') { + if (node.value == 0) { + return '\\mbox{zero}'; + } + else if (node.value == 1) { + return '\\mbox{one}'; + } + else if (node.value == 2) { + return '\\mbox{two}'; + } + else { + return node.value; + } + } +}; +``` +You can simply use your custom `toTex` functions by passing them to `toTex`: +```js +var expression = math.parse('1+2'); +var latex = expression.toTex(customLaTeX); +//latex now contains '\mbox{one} plus \mbox{two}' +``` +Another example in conjunction with custom functions: +```js +var customFunctions = { + binomial: function (n, k) { + //calculate n choose k + // (do some stuff) + return result; + } +}; + +var customLaTeX = function (node, callback) { + if ((node.type === 'FunctionNode') && (node.name === 'binomial')) { + return '\\binom{' + node.args[0].toTex(callback) + '}{' + node.args[1].toTex(callback) + '}'; + } +}; + +math.import(customFunctions); +var expression = math.parse('binomial(2,1)'); +var latex = expression.toTex(customLaTeX); +//latex now contains "\binom{2}{1}" +``` + + +**Examples for option 2** +The same examples as above but using the second option: + +```js +var customLaTeX = { + 'OperatorNode:add': function (node, callbacks) { + //don't forget to call the toTex functions of the child nodes with the callbacks + return node.args[0].toTex(callbacks) + ' plus ' + node.args[1].toTex(callbacks); + }, + 'ConstantNode': function (node, callbacks) { + if (node.value == 0) { + return '\\mbox{zero}'; + } + else if (node.value == 1) { + return '\\mbox{one}'; + } + else if (node.value == 2) { + return '\\mbox{two}'; + } + else { + return node.value; + } + } +}; +``` +The object property is either the type of a node (`ConstantNode` in this example) or the identifier of a node (`OperatorNode:add` in this example. The identifier is available via the node's `getIdentifier()` function. In the above example, your custom toTex would get called for every OperatorNode that calls `add`). + +First argument of every callback function is the list of callback functions itself, which has to be passed on to every `toTex` your calling inside the function. The other arguments are the same with which the constructor of the given node is called (those can be found in `lib/expression/node/`). You can also access additional properties of the node via the `this` reference. + +You can simply use your custom `toTex` functions by passing them to `toTex`: +```js +var expression = math.parse('1+2'); +var latex = expression.toTex(customLaTeX); +//latex now contains '\mbox{one} plus \mbox{two}' +``` + +Another example in conjunction with custom functions: +```js +var customFunctions = { + binomial: function (n, k) { + //calculate n choose k + // (do some stuff) + return result; + } +}; + +var customLaTeX = { + 'FunctionNode:binomial': function (node, callbacks) { + return '\\binom{' + node.args[0].toTex(callbacks) + '}{' + node.args[1].toTex(callbacks) + '}'; + } +}; + +math.import(customFunctions); +var expression = math.parse('binomial(2,1)'); +var latex = expression.toTex(customLaTeX); +//latex now contains "\binom{2}{1}" +``` From a973482ef2eb04d2adbbbc65af39fb5bbccbe66e Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Sat, 14 Mar 2015 09:02:20 +0100 Subject: [PATCH 07/10] Fix bug in fix of breaking change Without this fix, the type passed to ArrayNode.toTex would stay there forever until you change it back manually instead of being used only once. --- lib/expression/node/Node.js | 5 +++++ lib/util/latex.js | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/expression/node/Node.js b/lib/expression/node/Node.js index 9f1d5d4f6..5f596fac2 100644 --- a/lib/expression/node/Node.js +++ b/lib/expression/node/Node.js @@ -232,6 +232,11 @@ Node.prototype.toString = function() { */ Node.prototype.toTex = function(customFunctions) { var customTex; + if (this.type === 'ArrayNode') { + //FIXME this is only a workaround for a breaking change, + //remove this in version2 + delete this.latexType; + } if (typeof customFunctions === 'object') { //if customFunctions is a map of callback functions if (customFunctions.hasOwnProperty(this.type)) { diff --git a/lib/util/latex.js b/lib/util/latex.js index a16683370..ed6afd33d 100644 --- a/lib/util/latex.js +++ b/lib/util/latex.js @@ -468,7 +468,9 @@ exports.toArgs = function(that, customFunctions) { if (that.args[0] instanceof ArrayNode) { //FIXME passing 'vmatrix' like that is really ugly that.args[0].latexType = 'vmatrix'; - return that.args[0].toTex(customFunctions); + var latex = that.args[0].toTex(customFunctions); + delete that.args[0].latexType; + return latex; } brace = 'vmatrix'; From 47f76f35ac6afb77d43c24616711a3d821331b9c Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Tue, 17 Mar 2015 18:14:14 +0100 Subject: [PATCH 08/10] Limit custom toTex with multiple callbacks to FunctionNodes Now only FunctionNode's toTex can be overwritten mit a map of callbacks --- lib/expression/node/Node.js | 27 ++++++++--------- test/expression/node/ArrayNode.test.js | 12 ++++---- test/expression/node/AssignmentNode.test.js | 12 ++++---- test/expression/node/BlockNode.test.js | 12 ++++---- test/expression/node/ConditionalNode.test.js | 16 +++++----- test/expression/node/ConstantNode.test.js | 6 ++-- .../node/FunctionAssignmentNode.test.js | 12 ++++---- test/expression/node/FunctionNode.test.js | 23 +++++++------- test/expression/node/IndexNode.test.js | 14 ++++----- test/expression/node/Node.test.js | 9 +++--- test/expression/node/OperatorNode.test.js | 30 +++++++++---------- test/expression/node/RangeNode.test.js | 16 +++++----- test/expression/node/SymbolNode.test.js | 6 ++-- test/expression/node/UpdateNode.test.js | 21 +++++++------ 14 files changed, 105 insertions(+), 111 deletions(-) diff --git a/lib/expression/node/Node.js b/lib/expression/node/Node.js index 5f596fac2..7d4813dd0 100644 --- a/lib/expression/node/Node.js +++ b/lib/expression/node/Node.js @@ -230,32 +230,29 @@ Node.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} */ -Node.prototype.toTex = function(customFunctions) { +Node.prototype.toTex = function(callback) { var customTex; if (this.type === 'ArrayNode') { //FIXME this is only a workaround for a breaking change, //remove this in version2 delete this.latexType; } - if (typeof customFunctions === 'object') { - //if customFunctions is a map of callback functions - if (customFunctions.hasOwnProperty(this.type)) { - customTex = customFunctions[this.type](this, customFunctions); - } - else if (customFunctions.hasOwnProperty(this.getIdentifier())) { - customTex = customFunctions[this.getIdentifier()](this, customFunctions); + if (typeof callback === 'object') { + if ((this.type === 'FunctionNode') && callback.hasOwnProperty(this.name)) { + //if callback is a map of callback functions and this is a FunctionNode + customTex = callback[this.name](this, callback); } } - else if (typeof customFunctions === 'function') { - //if customFunctions is a callback function - customTex = customFunctions(this, customFunctions); + else if (typeof callback === 'function') { + //if callback is a function + customTex = callback(this, callback); } - else if ((typeof customFunctions === 'string') && (this.type === 'ArrayNode')) { + else if ((typeof callback === 'string') && (this.type === 'ArrayNode')) { //FIXME this is only a workaround for a breaking change, //remove this in version2 - this.latexType = customFunctions; + this.latexType = callback; } - else if (typeof customFunctions !== 'undefined') { + else if (typeof callback !== 'undefined') { throw new TypeError('Object or function expected as callback'); } @@ -263,7 +260,7 @@ Node.prototype.toTex = function(customFunctions) { return customTex; } - return this._toTex(customFunctions); + return this._toTex(callback); }; /** diff --git a/test/expression/node/ArrayNode.test.js b/test/expression/node/ArrayNode.test.js index c6f238949..fcfd7e978 100644 --- a/test/expression/node/ArrayNode.test.js +++ b/test/expression/node/ArrayNode.test.js @@ -238,17 +238,17 @@ describe('ArrayNode', function() { it ('should LaTeX an ArrayNode with custom toTex', function () { //Also checks if the custom functions get passed on to the children - var customFunctions = { - ArrayNode: function (node, callbacks) { + var customFunction = function (node, callback) { + if (node.type === 'ArrayNode') { var latex = '\\left['; node.nodes.forEach(function (node) { - latex += node.toTex(callbacks) + ', '; + latex += node.toTex(callback) + ', '; }); latex += '\\right]'; return latex; - }, - ConstantNode: function (node, callbacks) { + } + else if (node.type === 'ConstantNode') { return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' } }; @@ -258,7 +258,7 @@ describe('ArrayNode', function() { var n = new ArrayNode([a, b]); - assert.equal(n.toTex(customFunctions), '\\left[const\\left(1, number\\right), const\\left(2, number\\right), \\right]'); + assert.equal(n.toTex(customFunction), '\\left[const\\left(1, number\\right), const\\left(2, number\\right), \\right]'); }); }); diff --git a/test/expression/node/AssignmentNode.test.js b/test/expression/node/AssignmentNode.test.js index 9b155f113..86cb862dd 100644 --- a/test/expression/node/AssignmentNode.test.js +++ b/test/expression/node/AssignmentNode.test.js @@ -227,11 +227,11 @@ describe('AssignmentNode', function() { it ('should LaTeX an AssignmentNode with custom toTex', function () { //Also checks if custom funcions get passed to the children - var customFunctions = { - AssignmentNode: function (node, callbacks) { - return node.name + '\\mbox{equals}' + node.expr.toTex(callbacks); - }, - ConstantNode: function (node, callbacks) { + var customFunction = function (node, callback) { + if (node.type === 'AssignmentNode') { + return node.name + '\\mbox{equals}' + node.expr.toTex(callback); + } + else if (node.type === 'ConstantNode') { return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' } }; @@ -240,7 +240,7 @@ describe('AssignmentNode', function() { var n = new AssignmentNode('a', a); - assert.equal(n.toTex(customFunctions), 'a\\mbox{equals}const\\left(1, number\\right)'); + assert.equal(n.toTex(customFunction), 'a\\mbox{equals}const\\left(1, number\\right)'); }); }); diff --git a/test/expression/node/BlockNode.test.js b/test/expression/node/BlockNode.test.js index d0c4582fb..8514fb00c 100644 --- a/test/expression/node/BlockNode.test.js +++ b/test/expression/node/BlockNode.test.js @@ -258,16 +258,16 @@ describe('BlockNode', function() { it ('should LaTeX a BlockNode with custom toTex', function () { //Also checks if the custom functions get passed on to the children - var customFunctions = { - BlockNode: function (node, callbacks) { + var customFunction = function (node, callback) { + if (node.type === 'BlockNode') { var latex = ''; node.blocks.forEach(function (block) { - latex += block.node.toTex(callbacks) + '; '; + latex += block.node.toTex(callback) + '; '; }); return latex; - }, - ConstantNode: function (node, callbacks) { + } + else if (node.type === 'ConstantNode') { return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' } }; @@ -277,7 +277,7 @@ describe('BlockNode', function() { var n = new BlockNode([{node: a}, {node: b}]); - assert.equal(n.toTex(customFunctions), 'const\\left(1, number\\right); const\\left(2, number\\right); '); + assert.equal(n.toTex(customFunction), 'const\\left(1, number\\right); const\\left(2, number\\right); '); }); }); diff --git a/test/expression/node/ConditionalNode.test.js b/test/expression/node/ConditionalNode.test.js index 2cc731f9c..5b4001cec 100644 --- a/test/expression/node/ConditionalNode.test.js +++ b/test/expression/node/ConditionalNode.test.js @@ -262,13 +262,13 @@ describe('ConditionalNode', function() { it ('should LaTeX a ConditionalNode with custom toTex', function () { //Also checks if the custom functions get passed on to the children - var customFunctions = { - ConditionalNode: function (node, callbacks) { - return 'if ' + node.condition.toTex(callbacks) - + ' then ' + node.trueExpr.toTex(callbacks) - + ' else ' + node.falseExpr.toTex(callbacks); - }, - ConstantNode: function (node, callbacks) { + var customFunction = function (node, callback) { + if (node.type === 'ConditionalNode') { + return 'if ' + node.condition.toTex(callback) + + ' then ' + node.trueExpr.toTex(callback) + + ' else ' + node.falseExpr.toTex(callback); + } + else if (node.type === 'ConstantNode') { return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' } }; @@ -279,7 +279,7 @@ describe('ConditionalNode', function() { var n = new ConditionalNode(a, b, c); - assert.equal(n.toTex(customFunctions), 'if const\\left(1, number\\right) then const\\left(2, number\\right) else const\\left(3, number\\right)'); + assert.equal(n.toTex(customFunction), 'if const\\left(1, number\\right) then const\\left(2, number\\right) else const\\left(3, number\\right)'); }); }); diff --git a/test/expression/node/ConstantNode.test.js b/test/expression/node/ConstantNode.test.js index 4aaf50aa0..c572377d6 100644 --- a/test/expression/node/ConstantNode.test.js +++ b/test/expression/node/ConstantNode.test.js @@ -141,15 +141,15 @@ describe('ConstantNode', function() { it ('should LaTeX a ConstantNode with custom toTex', function () { //Also checks if the custom functions get passed on to the children - var customFunctions = { - ConstantNode: function (node, callbacks) { + var customFunction = function (node, callback) { + if (node.type === 'ConstantNode') { return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' } }; var n = new ConstantNode(1); - assert.equal(n.toTex(customFunctions), 'const\\left(1, number\\right)'); + assert.equal(n.toTex(customFunction), 'const\\left(1, number\\right)'); }); }); diff --git a/test/expression/node/FunctionAssignmentNode.test.js b/test/expression/node/FunctionAssignmentNode.test.js index 5e2441855..a7d8cff62 100644 --- a/test/expression/node/FunctionAssignmentNode.test.js +++ b/test/expression/node/FunctionAssignmentNode.test.js @@ -209,17 +209,17 @@ describe('FunctionAssignmentNode', function() { it ('should LaTeX a FunctionAssignmentNode with custom toTex', function () { //Also checks if the custom functions get passed on to the children - var customFunctions = { - FunctionAssignmentNode: function (node, callbacks) { + var customFunction = function (node, callback) { + if (node.type === 'FunctionAssignmentNode') { var latex = '\\mbox{' + node.name + '}\\left('; node.params.forEach(function (param) { latex += param + ', '; }); - latex += '\\right)=' + node.expr.toTex(customFunctions); + latex += '\\right)=' + node.expr.toTex(callback); return latex; - }, - ConstantNode: function (node, callbacks) { + } + else if (node.type === 'ConstantNode') { return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' } }; @@ -228,7 +228,7 @@ describe('FunctionAssignmentNode', function() { var n = new FunctionAssignmentNode('func', ['x'], a); - assert.equal(n.toTex(customFunctions), '\\mbox{func}\\left(x, \\right)=const\\left(1, number\\right)'); + assert.equal(n.toTex(customFunction), '\\mbox{func}\\left(x, \\right)=const\\left(1, number\\right)'); }); }); diff --git a/test/expression/node/FunctionNode.test.js b/test/expression/node/FunctionNode.test.js index d2df4aea6..df3a98882 100644 --- a/test/expression/node/FunctionNode.test.js +++ b/test/expression/node/FunctionNode.test.js @@ -290,16 +290,16 @@ describe('FunctionNode', function() { it ('should LaTeX a FunctionNode with custom toTex', function () { //Also checks if the custom functions get passed on to the children - var customFunctions = { - 'FunctionNode': function (node, callbacks) { + var customFunction = function (node, callback) { + if (node.type === 'FunctionNode') { var latex = '\\mbox{' + node.name + '}\\left('; node.args.forEach(function (arg) { - latex += arg.toTex(callbacks) + ', '; + latex += arg.toTex(callback) + ', '; }); latex += '\\right)'; return latex; - }, - ConstantNode: function (node, callbacks) { + } + else if (node.type === 'ConstantNode') { return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' } }; @@ -310,20 +310,17 @@ describe('FunctionNode', function() { var n1 = new FunctionNode('add', [a, b]); var n2 = new FunctionNode('subtract', [a, b]); - assert.equal(n1.toTex(customFunctions), '\\mbox{add}\\left(const\\left(1, number\\right), const\\left(2, number\\right), \\right)'); - assert.equal(n2.toTex(customFunctions), '\\mbox{subtract}\\left(const\\left(1, number\\right), const\\left(2, number\\right), \\right)'); + assert.equal(n1.toTex(customFunction), '\\mbox{add}\\left(const\\left(1, number\\right), const\\left(2, number\\right), \\right)'); + assert.equal(n2.toTex(customFunction), '\\mbox{subtract}\\left(const\\left(1, number\\right), const\\left(2, number\\right), \\right)'); }); it ('should LaTeX a FunctionNode with custom toTex for a single function', function () { //Also checks if the custom functions get passed on to the children - var customFunctions = { - 'FunctionNode:add': function (node, callbacks) { + var customFunction = { + 'add': function (node, callbacks) { return node.args[0].toTex(callbacks) + ' ' + node.name + ' ' + node.args[1].toTex(callbacks); - }, - ConstantNode: function (node, callbacks) { - return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' } }; @@ -332,7 +329,7 @@ describe('FunctionNode', function() { var n = new FunctionNode('add', [a, b]); - assert.equal(n.toTex(customFunctions), 'const\\left(1, number\\right) add const\\left(2, number\\right)'); + assert.equal(n.toTex(customFunction), '1 add 2'); }); }); diff --git a/test/expression/node/IndexNode.test.js b/test/expression/node/IndexNode.test.js index 42e4cf384..149d16bc6 100644 --- a/test/expression/node/IndexNode.test.js +++ b/test/expression/node/IndexNode.test.js @@ -287,16 +287,16 @@ describe('IndexNode', function() { it ('should LaTeX an IndexNode with custom toTex', function () { //Also checks if the custom functions get passed on to the children - var customFunctions = { - IndexNode: function (node, callbacks) { - var latex = node.object.toTex(callbacks) + ' at '; + var customFunction = function (node, callback) { + if (node.type === 'IndexNode') { + var latex = node.object.toTex(callback) + ' at '; node.ranges.forEach(function (range) { - latex += range.toTex(callbacks) + ', '; + latex += range.toTex(callback) + ', '; }); return latex; - }, - ConstantNode: function (node, callbacks) { + } + else if (node.type === 'ConstantNode') { return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' } }; @@ -307,7 +307,7 @@ describe('IndexNode', function() { var n = new IndexNode(a, [b, c]); - assert.equal(n.toTex(customFunctions), 'a at const\\left(1, number\\right), const\\left(2, number\\right), '); + assert.equal(n.toTex(customFunction), 'a at const\\left(1, number\\right), const\\left(2, number\\right), '); }); }); diff --git a/test/expression/node/Node.test.js b/test/expression/node/Node.test.js index 1cec5db87..18868968b 100644 --- a/test/expression/node/Node.test.js +++ b/test/expression/node/Node.test.js @@ -86,16 +86,17 @@ describe('Node', function() { it ('should ignore custom toTex if it returns nothing', function () { var callback1 = function (node, callback) {}; var callback2 = { - Node: function (node, callbacks) {} + bla: function (node, callbacks) {} }; var mymath = math.create(); mymath.expression.node.Node.prototype._toTex = function () { return 'default'; }; - var n = new mymath.expression.node.Node(); + var n1 = new mymath.expression.node.Node(); + var n2 = new mymath.expression.node.FunctionNode('bla', []); - assert.equal(n.toTex(callback1), 'default'); - assert.equal(n.toTex(callback2), 'default'); + assert.equal(n1.toTex(callback1), 'default'); + assert.equal(n2.toTex(callback2), 'bla\\left({}\\right)'); }); it ('should throw an error in case of wrong arguments for compile', function () { diff --git a/test/expression/node/OperatorNode.test.js b/test/expression/node/OperatorNode.test.js index 774b7b892..bd58ff669 100644 --- a/test/expression/node/OperatorNode.test.js +++ b/test/expression/node/OperatorNode.test.js @@ -380,13 +380,13 @@ describe('OperatorNode', function() { it ('should LaTeX an OperatorNode with custom toTex', function () { //Also checks if the custom functions get passed on to the children - var customFunctions = { - 'OperatorNode': function (node, callbacks) { + var customFunction = function (node, callback) { + if (node.type === 'OperatorNode') { return node.op + node.fn + '(' - + node.args[0].toTex(customFunctions) - + ', ' + node.args[1].toTex(customFunctions) + ')'; - }, - ConstantNode: function (node, callbacks) { + + node.args[0].toTex(callback) + + ', ' + node.args[1].toTex(callback) + ')'; + } + else if (node.type === 'ConstantNode') { return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' } }; @@ -397,19 +397,19 @@ describe('OperatorNode', function() { var n1 = new OperatorNode('+', 'add', [a, b]); var n2 = new OperatorNode('-', 'subtract', [a, b]); - assert.equal(n1.toTex(customFunctions), '+add(const\\left(1, number\\right), const\\left(2, number\\right))'); - assert.equal(n2.toTex(customFunctions), '-subtract(const\\left(1, number\\right), const\\left(2, number\\right))'); + assert.equal(n1.toTex(customFunction), '+add(const\\left(1, number\\right), const\\left(2, number\\right))'); + assert.equal(n2.toTex(customFunction), '-subtract(const\\left(1, number\\right), const\\left(2, number\\right))'); }); it ('should LaTeX an OperatorNode with custom toTex for a single operator', function () { //Also checks if the custom functions get passed on to the children - var customFunctions = { - 'OperatorNode:add': function (node, callbacks) { - return node.args[0].toTex(customFunctions) + var customFunction = function (node, callback) { + if ((node.type === 'OperatorNode') && (node.fn === 'add')) { + return node.args[0].toTex(callback) + node.op + node.fn + node.op + - node.args[1].toTex(customFunctions); - }, - ConstantNode: function (node, callbacks) { + node.args[1].toTex(callback); + } + else if (node.type === 'ConstantNode') { return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' } }; @@ -419,7 +419,7 @@ describe('OperatorNode', function() { var n = new OperatorNode('+', 'add', [a, b]); - assert.equal(n.toTex(customFunctions), 'const\\left(1, number\\right)+add+const\\left(2, number\\right)'); + assert.equal(n.toTex(customFunction), 'const\\left(1, number\\right)+add+const\\left(2, number\\right)'); }); }); diff --git a/test/expression/node/RangeNode.test.js b/test/expression/node/RangeNode.test.js index 88f8162c8..ae9ab51b4 100644 --- a/test/expression/node/RangeNode.test.js +++ b/test/expression/node/RangeNode.test.js @@ -291,13 +291,13 @@ describe('RangeNode', function() { it ('should LaTeX a RangeNode with custom toTex', function () { //Also checks if the custom functions get passed on to the children - var customFunctions = { - RangeNode: function (node, callbacks) { - return 'from ' + node.start.toTex(customFunctions) - + ' to ' + node.end.toTex(customFunctions) - + ' with steps of ' + node.step.toTex(customFunctions); - }, - ConstantNode: function (node, callbacks) { + var customFunction = function (node, callback) { + if (node.type === 'RangeNode') { + return 'from ' + node.start.toTex(callback) + + ' to ' + node.end.toTex(callback) + + ' with steps of ' + node.step.toTex(callback); + } + else if (node.type === 'ConstantNode') { return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' } }; @@ -308,7 +308,7 @@ describe('RangeNode', function() { var n = new RangeNode(a, b, c); - assert.equal(n.toTex(customFunctions), 'from const\\left(1, number\\right) to const\\left(2, number\\right) with steps of const\\left(3, number\\right)'); + assert.equal(n.toTex(customFunction), 'from const\\left(1, number\\right) to const\\left(2, number\\right) with steps of const\\left(3, number\\right)'); }); }); diff --git a/test/expression/node/SymbolNode.test.js b/test/expression/node/SymbolNode.test.js index 22b8ed9f9..7152acf29 100644 --- a/test/expression/node/SymbolNode.test.js +++ b/test/expression/node/SymbolNode.test.js @@ -109,15 +109,15 @@ describe('SymbolNode', function() { it ('should LaTeX a SymbolNode with custom toTex', function () { //Also checks if the custom functions get passed on to the children - var customFunctions = { - SymbolNode: function (node, callbacks) { + var customFunction = function (node, callback) { + if (node.type === 'SymbolNode') { return 'symbol(' + node.name + ')'; } }; var n = new SymbolNode('a'); - assert.equal(n.toTex(customFunctions), 'symbol(a)'); + assert.equal(n.toTex(customFunction), 'symbol(a)'); }); }); diff --git a/test/expression/node/UpdateNode.test.js b/test/expression/node/UpdateNode.test.js index c0739e708..543ef1ae7 100644 --- a/test/expression/node/UpdateNode.test.js +++ b/test/expression/node/UpdateNode.test.js @@ -324,19 +324,18 @@ describe('UpdateNode', function() { it ('should LaTeX an UpdateNode with custom toTex', function () { //Also checks if the custom functions get passed on to the children - var customFunctions = { - UpdateNode: function (node, callbacks) { - return node.index.toTex(callbacks) + ' equals ' + node.expr.toTex(customFunctions); - }, - IndexNode: function (node, callbacks) { - var latex = node.object.toTex(callbacks) + ' at '; + var customFunction = function (node, callback) { + if (node.type === 'UpdateNode') { + return node.index.toTex(callback) + ' equals ' + node.expr.toTex(callback); + } + else if (node.type === 'IndexNode') { + var latex = node.object.toTex(callback) + ' at '; node.ranges.forEach(function (range) { - latex += range.toTex(callbacks) + ', '; + latex += range.toTex(callback) + ', '; }); - return latex; - }, - ConstantNode: function (node, callbacks) { + } + else if (node.type === 'ConstantNode') { return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)' } }; @@ -350,7 +349,7 @@ describe('UpdateNode', function() { var n = new UpdateNode(new IndexNode(a, ranges), v); - assert.equal(n.toTex(customFunctions), 'a at const\\left(2, number\\right), const\\left(1, number\\right), equals const\\left(5, number\\right)'); + assert.equal(n.toTex(customFunction), 'a at const\\left(2, number\\right), const\\left(1, number\\right), equals const\\left(5, number\\right)'); }); }); From c80e0baf2077b834ca7b7782d565508b08ba6315 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Tue, 17 Mar 2015 18:27:01 +0100 Subject: [PATCH 09/10] Update documentation of custom toTex --- docs/expressions/customization.md | 100 ++++++++++-------------------- 1 file changed, 34 insertions(+), 66 deletions(-) diff --git a/docs/expressions/customization.md b/docs/expressions/customization.md index 96479e850..543fb4944 100644 --- a/docs/expressions/customization.md +++ b/docs/expressions/customization.md @@ -149,11 +149,40 @@ You can pass your own callback(s) to `toTex`. If it returns nothing, the standar If your callback returns a string, this string will be used. There's two ways of passing callbacks: -1. Pass a function to `toTex`. This function will then be used for every node. -2. Pass an object that maps identifiers to callbacks. Those callbacks will be used whenever the identifier applies -to the node's type of `getIdentifier()`. +1. Pass an object that maps function names to callbacks. Those callbacks will be used for FunctionNodes with +functions of that name. +2. Pass a function to `toTex`. This function will then be used for every node. -**Examples for option 1:** + +**Examples for option 2** +```js +var customFunctions = { + binomial: function (n, k) { + //calculate n choose k + // (do some stuff) + return result; + } +}; + +var customLaTeX = { + 'binomial': function (node, callbacks) { //provide toTex for your own custom function + return '\\binom{' + node.args[0].toTex(callbacks) + '}{' + node.args[1].toTex(callbacks) + '}'; + }, + 'factorial': function (node, callbacks) { //override toTex for builtin functions + return 'factorial\\left(' + node.args[0] + '\\right)'; + } +}; +``` + +You can simply use your custom toTex functions by passing them to `toTex`: +```js +math.import(customFunctions); +var expression = math.parse('binomial(factorial(2),1)'); +var latex = expression.toTex(customLaTeX); +//latex now contains "\binom{factorial\\left(2\\right)}{1}" +``` + +**Examples for option 2:** ```js var customLaTeX = function (node, callback) { @@ -176,9 +205,7 @@ var customLaTeX = function (node, callback) { } } }; -``` -You can simply use your custom `toTex` functions by passing them to `toTex`: -```js + var expression = math.parse('1+2'); var latex = expression.toTex(customLaTeX); //latex now contains '\mbox{one} plus \mbox{two}' @@ -204,62 +231,3 @@ var expression = math.parse('binomial(2,1)'); var latex = expression.toTex(customLaTeX); //latex now contains "\binom{2}{1}" ``` - - -**Examples for option 2** -The same examples as above but using the second option: - -```js -var customLaTeX = { - 'OperatorNode:add': function (node, callbacks) { - //don't forget to call the toTex functions of the child nodes with the callbacks - return node.args[0].toTex(callbacks) + ' plus ' + node.args[1].toTex(callbacks); - }, - 'ConstantNode': function (node, callbacks) { - if (node.value == 0) { - return '\\mbox{zero}'; - } - else if (node.value == 1) { - return '\\mbox{one}'; - } - else if (node.value == 2) { - return '\\mbox{two}'; - } - else { - return node.value; - } - } -}; -``` -The object property is either the type of a node (`ConstantNode` in this example) or the identifier of a node (`OperatorNode:add` in this example. The identifier is available via the node's `getIdentifier()` function. In the above example, your custom toTex would get called for every OperatorNode that calls `add`). - -First argument of every callback function is the list of callback functions itself, which has to be passed on to every `toTex` your calling inside the function. The other arguments are the same with which the constructor of the given node is called (those can be found in `lib/expression/node/`). You can also access additional properties of the node via the `this` reference. - -You can simply use your custom `toTex` functions by passing them to `toTex`: -```js -var expression = math.parse('1+2'); -var latex = expression.toTex(customLaTeX); -//latex now contains '\mbox{one} plus \mbox{two}' -``` - -Another example in conjunction with custom functions: -```js -var customFunctions = { - binomial: function (n, k) { - //calculate n choose k - // (do some stuff) - return result; - } -}; - -var customLaTeX = { - 'FunctionNode:binomial': function (node, callbacks) { - return '\\binom{' + node.args[0].toTex(callbacks) + '}{' + node.args[1].toTex(callbacks) + '}'; - } -}; - -math.import(customFunctions); -var expression = math.parse('binomial(2,1)'); -var latex = expression.toTex(customLaTeX); -//latex now contains "\binom{2}{1}" -``` From e04dfd26bcf97a6ef2fc7fb6a5fb881d6b070699 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Tue, 17 Mar 2015 18:30:26 +0100 Subject: [PATCH 10/10] Rename customFuncions to callbacks --- lib/expression/node/ArrayNode.js | 6 +++--- lib/expression/node/AssignmentNode.js | 4 ++-- lib/expression/node/BlockNode.js | 4 ++-- lib/expression/node/ConditionalNode.js | 8 ++++---- lib/expression/node/ConstantNode.js | 2 +- lib/expression/node/FunctionAssignmentNode.js | 4 ++-- lib/expression/node/FunctionNode.js | 4 ++-- lib/expression/node/IndexNode.js | 4 ++-- lib/expression/node/OperatorNode.js | 10 +++++----- lib/expression/node/RangeNode.js | 8 ++++---- lib/expression/node/SymbolNode.js | 4 ++-- lib/expression/node/UpdateNode.js | 4 ++-- 12 files changed, 31 insertions(+), 31 deletions(-) diff --git a/lib/expression/node/ArrayNode.js b/lib/expression/node/ArrayNode.js index f8d99bbaf..c4636c06c 100644 --- a/lib/expression/node/ArrayNode.js +++ b/lib/expression/node/ArrayNode.js @@ -99,18 +99,18 @@ ArrayNode.prototype.toString = function() { * @param {String} type * @return {String} str */ -ArrayNode.prototype._toTex = function(customFunctions) { +ArrayNode.prototype._toTex = function(callbacks) { this.latexType = this.latexType || 'bmatrix'; var s = '\\begin{' + this.latexType + '}'; this.nodes.forEach(function(node) { if (node.nodes) { s += node.nodes.map(function(childNode) { - return childNode.toTex(customFunctions); + return childNode.toTex(callbacks); }).join('&'); } else { - s += node.toTex(customFunctions); + s += node.toTex(callbacks); } // new line diff --git a/lib/expression/node/AssignmentNode.js b/lib/expression/node/AssignmentNode.js index ce9da5cfc..e47e22349 100644 --- a/lib/expression/node/AssignmentNode.js +++ b/lib/expression/node/AssignmentNode.js @@ -92,11 +92,11 @@ AssignmentNode.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} */ -AssignmentNode.prototype._toTex = function(customFunctions) { +AssignmentNode.prototype._toTex = function(callbacks) { var precedence = operators.getPrecedence(this); var exprPrecedence = operators.getPrecedence(this.expr); - var expr = this.expr.toTex(customFunctions); + var expr = this.expr.toTex(callbacks); if ((exprPrecedence !== null) && (exprPrecedence <= precedence)) { //adds visible round brackets expr = latex.addBraces(expr, true); diff --git a/lib/expression/node/BlockNode.js b/lib/expression/node/BlockNode.js index 90b2d1a14..3bccddbd1 100644 --- a/lib/expression/node/BlockNode.js +++ b/lib/expression/node/BlockNode.js @@ -125,9 +125,9 @@ BlockNode.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} str */ -BlockNode.prototype._toTex = function(customFunctions) { +BlockNode.prototype._toTex = function(callbacks) { return this.blocks.map(function (param) { - return param.node.toTex(customFunctions) + (param.visible ? '' : ';'); + return param.node.toTex(callbacks) + (param.visible ? '' : ';'); }).join('\n'); }; diff --git a/lib/expression/node/ConditionalNode.js b/lib/expression/node/ConditionalNode.js index 29f4c9ff3..e5acb04a5 100644 --- a/lib/expression/node/ConditionalNode.js +++ b/lib/expression/node/ConditionalNode.js @@ -154,13 +154,13 @@ ConditionalNode.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} str */ -ConditionalNode.prototype._toTex = function(customFunctions) { +ConditionalNode.prototype._toTex = function(callbacks) { var s = ( - latex.addBraces(this.trueExpr.toTex(customFunctions)) + + latex.addBraces(this.trueExpr.toTex(callbacks)) + ', &\\quad' + - latex.addBraces('\\text{if}\\;' + this.condition.toTex(customFunctions)) + latex.addBraces('\\text{if}\\;' + this.condition.toTex(callbacks)) ) + '\\\\' + ( - latex.addBraces(this.falseExpr.toTex(customFunctions)) + + latex.addBraces(this.falseExpr.toTex(callbacks)) + ', &\\quad' + latex.addBraces('\\text{otherwise}') ); diff --git a/lib/expression/node/ConstantNode.js b/lib/expression/node/ConstantNode.js index dd001315e..10e8448cf 100644 --- a/lib/expression/node/ConstantNode.js +++ b/lib/expression/node/ConstantNode.js @@ -159,7 +159,7 @@ ConstantNode.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} str */ -ConstantNode.prototype._toTex = function(customFunctions) { +ConstantNode.prototype._toTex = function(callbacks) { var value = this.value, index; switch (this.valueType) { diff --git a/lib/expression/node/FunctionAssignmentNode.js b/lib/expression/node/FunctionAssignmentNode.js index 5718c0908..03fe8f43d 100644 --- a/lib/expression/node/FunctionAssignmentNode.js +++ b/lib/expression/node/FunctionAssignmentNode.js @@ -113,11 +113,11 @@ FunctionAssignmentNode.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} str */ -FunctionAssignmentNode.prototype._toTex = function(customFunctions) { +FunctionAssignmentNode.prototype._toTex = function(callbacks) { var precedence = operators.getPrecedence(this); var exprPrecedence = operators.getPrecedence(this.expr); - var expr = this.expr.toTex(customFunctions); + var expr = this.expr.toTex(callbacks); if ((exprPrecedence !== null) && (exprPrecedence <= precedence)) { //adds visible round brackets expr = latex.addBraces(expr, true); diff --git a/lib/expression/node/FunctionNode.js b/lib/expression/node/FunctionNode.js index eaeee60d2..fcce31025 100644 --- a/lib/expression/node/FunctionNode.js +++ b/lib/expression/node/FunctionNode.js @@ -116,8 +116,8 @@ FunctionNode.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} str */ -FunctionNode.prototype._toTex = function(customFunctions) { - return latex.toArgs(this, customFunctions); +FunctionNode.prototype._toTex = function(callbacks) { + return latex.toArgs(this, callbacks); }; /** diff --git a/lib/expression/node/IndexNode.js b/lib/expression/node/IndexNode.js index 175826630..ed12171f7 100644 --- a/lib/expression/node/IndexNode.js +++ b/lib/expression/node/IndexNode.js @@ -212,8 +212,8 @@ IndexNode.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} str */ -IndexNode.prototype._toTex = function(customFunctions) { - return this.object.toTex(customFunctions) + '[' + this.ranges.join(', ') + ']'; +IndexNode.prototype._toTex = function(callbacks) { + return this.object.toTex(callbacks) + '[' + this.ranges.join(', ') + ']'; }; module.exports = IndexNode; diff --git a/lib/expression/node/OperatorNode.js b/lib/expression/node/OperatorNode.js index 986fed1af..ee8db2afa 100644 --- a/lib/expression/node/OperatorNode.js +++ b/lib/expression/node/OperatorNode.js @@ -239,7 +239,7 @@ OperatorNode.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} str */ -OperatorNode.prototype._toTex = function(customFunctions) { +OperatorNode.prototype._toTex = function(callbacks) { var args = this.args; var parens = calculateNecessaryParentheses(this, args); var op = latex.toOperator(this.op); //operator @@ -248,7 +248,7 @@ OperatorNode.prototype._toTex = function(customFunctions) { case 1: //unary operators var assoc = operators.getAssociativity(this); - var operand = args[0].toTex(customFunctions); + var operand = args[0].toTex(callbacks); if (parens[0]) { operand = latex.addBraces(operand, true); } @@ -266,9 +266,9 @@ OperatorNode.prototype._toTex = function(customFunctions) { 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(customFunctions), parens[0]); + var lhsTex = latex.addBraces(lhs.toTex(callbacks), parens[0]); var rhs = args[1]; //right hand side - var rhsTex = latex.addBraces(rhs.toTex(customFunctions), parens[1]); + var rhsTex = latex.addBraces(rhs.toTex(callbacks), parens[1]); switch (this.getIdentifier()) { case 'OperatorNode:divide': @@ -276,7 +276,7 @@ OperatorNode.prototype._toTex = function(customFunctions) { return op + lhsTex + rhsTex; case 'OperatorNode:to': - rhsTex = latex.toUnit(rhs.toTex(customFunctions)); + rhsTex = latex.toUnit(rhs.toTex(callbacks)); rhsTex = latex.addBraces(rhsTex, parens[1]); break; } diff --git a/lib/expression/node/RangeNode.js b/lib/expression/node/RangeNode.js index f28cfc156..d3af794ee 100644 --- a/lib/expression/node/RangeNode.js +++ b/lib/expression/node/RangeNode.js @@ -124,12 +124,12 @@ RangeNode.prototype.toString = function() { * @params {Object|function} callback(s) * @return {String} str */ -RangeNode.prototype._toTex = function(customFunctions) { - var str = this.start.toTex(customFunctions); +RangeNode.prototype._toTex = function(callbacks) { + var str = this.start.toTex(callbacks); if (this.step) { - str += ':' + this.step.toTex(customFunctions); + str += ':' + this.step.toTex(callbacks); } - str += ':' + this.end.toTex(customFunctions); + str += ':' + this.end.toTex(callbacks); return str; }; diff --git a/lib/expression/node/SymbolNode.js b/lib/expression/node/SymbolNode.js index 34409fe80..6d807d31d 100644 --- a/lib/expression/node/SymbolNode.js +++ b/lib/expression/node/SymbolNode.js @@ -103,8 +103,8 @@ SymbolNode.prototype.toString = function() { * @return {String} str * @override */ -SymbolNode.prototype._toTex = function(customFunctions) { - return latex.toSymbol(this.name, customFunctions); +SymbolNode.prototype._toTex = function(callbacks) { + return latex.toSymbol(this.name, callbacks); }; module.exports = SymbolNode; diff --git a/lib/expression/node/UpdateNode.js b/lib/expression/node/UpdateNode.js index 346504ff6..323ad1f12 100644 --- a/lib/expression/node/UpdateNode.js +++ b/lib/expression/node/UpdateNode.js @@ -87,8 +87,8 @@ UpdateNode.prototype.toString = function() { * @param {Object|function} callback(s) * @return {String} */ -UpdateNode.prototype._toTex = function(customFunctions) { - return this.index.toTex(customFunctions) + ' = ' + this.expr.toTex(customFunctions); +UpdateNode.prototype._toTex = function(callbacks) { + return this.index.toTex(callbacks) + ' = ' + this.expr.toTex(callbacks); }; module.exports = UpdateNode;