Merge pull request #301 from FSMaxB/custom-totex

custom toTex
This commit is contained in:
Jos de Jong 2015-03-17 20:32:38 +01:00
commit b261c55f48
28 changed files with 537 additions and 52 deletions

View File

@ -139,3 +139,95 @@ 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 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 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) {
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;
}
}
};
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}"
```

View File

@ -95,26 +95,28 @@ ArrayNode.prototype.toString = function() {
/**
* Get LaTeX representation
* @param {Object|function} callback(s)
* @param {String} type
* @return {String} str
*/
ArrayNode.prototype.toTex = function(type) {
type = type || 'bmatrix';
var s = '\\begin{' + type + '}';
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();
return childNode.toTex(callbacks);
}).join('&');
}
else {
s += node.toTex();
s += node.toTex(callbacks);
}
// new line
s += '\\\\';
});
s += '\\end{' + type + '}';
s += '\\end{' + this.latexType + '}';
return s;
};

View File

@ -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(callbacks) {
var precedence = operators.getPrecedence(this);
var exprPrecedence = operators.getPrecedence(this.expr);
var expr = this.expr.toTex();
var expr = this.expr.toTex(callbacks);
if ((exprPrecedence !== null) && (exprPrecedence <= precedence)) {
//adds visible round brackets
expr = latex.addBraces(expr, true);

View File

@ -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(callbacks) {
return this.blocks.map(function (param) {
return param.node.toTex() + (param.visible ? '' : ';');
return param.node.toTex(callbacks) + (param.visible ? '' : ';');
}).join('\n');
};

View File

@ -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(callbacks) {
var s = (
latex.addBraces(this.trueExpr.toTex()) +
latex.addBraces(this.trueExpr.toTex(callbacks)) +
', &\\quad' +
latex.addBraces('\\text{if}\\;' + this.condition.toTex())
latex.addBraces('\\text{if}\\;' + this.condition.toTex(callbacks))
) + '\\\\' + (
latex.addBraces(this.falseExpr.toTex()) +
latex.addBraces(this.falseExpr.toTex(callbacks)) +
', &\\quad' +
latex.addBraces('\\text{otherwise}')
);

View File

@ -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(callbacks) {
var value = this.value,
index;
switch (this.valueType) {

View File

@ -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(callbacks) {
var precedence = operators.getPrecedence(this);
var exprPrecedence = operators.getPrecedence(this.expr);
var expr = this.expr.toTex();
var expr = this.expr.toTex(callbacks);
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;
};

View File

@ -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(callbacks) {
return latex.toArgs(this, callbacks);
};
/**

View File

@ -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(callbacks) {
return this.object.toTex(callbacks) + '[' + this.ranges.join(', ') + ']';
};
module.exports = IndexNode;
module.exports = IndexNode;

View File

@ -221,11 +221,62 @@ 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() {
return '';
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 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 callback === 'function') {
//if callback is a function
customTex = callback(this, callback);
}
else if ((typeof callback === 'string') && (this.type === 'ArrayNode')) {
//FIXME this is only a workaround for a breaking change,
//remove this in version2
this.latexType = callback;
}
else if (typeof callback !== 'undefined') {
throw new TypeError('Object or function expected as callback');
}
if (typeof customTex !== 'undefined') {
return customTex;
}
return this._toTex(callback);
};
/**
* Internal function to generate the LaTeX output.
* This has to be implemented by every Node
*
* @param {Object}|function}
* @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');
};
/**

View File

@ -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(callbacks) {
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(callbacks);
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(callbacks), parens[0]);
var rhs = args[1]; //right hand side
var rhsTex = latex.addBraces(rhs.toTex(), parens[1]);
var rhsTex = latex.addBraces(rhs.toTex(callbacks), 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(callbacks));
rhsTex = latex.addBraces(rhsTex, parens[1]);
break;
}

View File

@ -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(callbacks) {
var str = this.start.toTex(callbacks);
if (this.step) {
str += ':' + this.step.toTex();
str += ':' + this.step.toTex(callbacks);
}
str += ':' + this.end.toTex();
str += ':' + this.end.toTex(callbacks);
return str;
};

View File

@ -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(callbacks) {
return latex.toSymbol(this.name, callbacks);
};
module.exports = SymbolNode;

View File

@ -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(callbacks) {
return this.index.toTex(callbacks) + ' = ' + this.expr.toTex(callbacks);
};
module.exports = UpdateNode;

View File

@ -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,11 @@ exports.toArgs = function(that) {
case 'det':
if (that.args[0] instanceof ArrayNode) {
return that.args[0].toTex('vmatrix');
//FIXME passing 'vmatrix' like that is really ugly
that.args[0].latexType = 'vmatrix';
var latex = that.args[0].toTex(customFunctions);
delete that.args[0].latexType;
return latex;
}
brace = 'vmatrix';
@ -480,7 +484,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 +495,7 @@ exports.toArgs = function(that) {
}
texParams = texParams || args.map(function(param) {
return '{' + param.toTex() + '}' ;
return '{' + param.toTex(customFunctions) + '}' ;
}).join(op);
return prefix + (showFunc ? func : '') +

View File

@ -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 customFunction = function (node, callback) {
if (node.type === 'ArrayNode') {
var latex = '\\left[';
node.nodes.forEach(function (node) {
latex += node.toTex(callback) + ', ';
});
latex += '\\right]';
return latex;
}
else if (node.type === 'ConstantNode') {
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(customFunction), '\\left[const\\left(1, number\\right), const\\left(2, number\\right), \\right]');
});
});

View File

@ -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 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)'
}
};
var a = new ConstantNode(1);
var n = new AssignmentNode('a', a);
assert.equal(n.toTex(customFunction), 'a\\mbox{equals}const\\left(1, number\\right)');
});
});

View File

@ -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 customFunction = function (node, callback) {
if (node.type === 'BlockNode') {
var latex = '';
node.blocks.forEach(function (block) {
latex += block.node.toTex(callback) + '; ';
});
return latex;
}
else if (node.type === 'ConstantNode') {
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(customFunction), 'const\\left(1, number\\right); const\\left(2, number\\right); ');
});
});

View File

@ -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 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)'
}
};
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(customFunction), 'if const\\left(1, number\\right) then const\\left(2, number\\right) else const\\left(3, number\\right)');
});
});

View File

@ -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 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(customFunction), 'const\\left(1, number\\right)');
});
});

View File

@ -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 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(callback);
return latex;
}
else if (node.type === 'ConstantNode') {
return 'const\\left(' + node.value + ', ' + node.valueType + '\\right)'
}
};
var a = new ConstantNode(1);
var n = new FunctionAssignmentNode('func', ['x'], a);
assert.equal(n.toTex(customFunction), '\\mbox{func}\\left(x, \\right)=const\\left(1, number\\right)');
});
});

View File

@ -288,4 +288,48 @@ 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 customFunction = function (node, callback) {
if (node.type === 'FunctionNode') {
var latex = '\\mbox{' + node.name + '}\\left(';
node.args.forEach(function (arg) {
latex += arg.toTex(callback) + ', ';
});
latex += '\\right)';
return latex;
}
else if (node.type === 'ConstantNode') {
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(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 customFunction = {
'add': function (node, callbacks) {
return node.args[0].toTex(callbacks)
+ ' ' + node.name + ' '
+ node.args[1].toTex(callbacks);
}
};
var a = new ConstantNode(1);
var b = new ConstantNode(2);
var n = new FunctionNode('add', [a, b]);
assert.equal(n.toTex(customFunction), '1 add 2');
});
});

View File

@ -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 customFunction = function (node, callback) {
if (node.type === 'IndexNode') {
var latex = node.object.toTex(callback) + ' at ';
node.ranges.forEach(function (range) {
latex += range.toTex(callback) + ', ';
});
return latex;
}
else if (node.type === 'ConstantNode') {
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(customFunction), 'a at const\\left(1, number\\right), const\\left(2, number\\right), ');
});
});

View File

@ -74,9 +74,29 @@ 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.type = 'SpecialNode'; //this is necessary because toTex
//returns '' for a Node
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 = {
bla: function (node, callbacks) {}
};
var mymath = math.create();
mymath.expression.node.Node.prototype._toTex = function () {
return 'default';
};
var n1 = new mymath.expression.node.Node();
var n2 = new mymath.expression.node.FunctionNode('bla', []);
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 () {

View File

@ -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 customFunction = function (node, callback) {
if (node.type === 'OperatorNode') {
return node.op + node.fn + '('
+ node.args[0].toTex(callback)
+ ', ' + node.args[1].toTex(callback) + ')';
}
else if (node.type === 'ConstantNode') {
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(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 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(callback);
}
else if (node.type === 'ConstantNode') {
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(customFunction), 'const\\left(1, number\\right)+add+const\\left(2, number\\right)');
});
});

View File

@ -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 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)'
}
};
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(customFunction), 'from const\\left(1, number\\right) to const\\left(2, number\\right) with steps of const\\left(3, number\\right)');
});
});

View File

@ -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 customFunction = function (node, callback) {
if (node.type === 'SymbolNode') {
return 'symbol(' + node.name + ')';
}
};
var n = new SymbolNode('a');
assert.equal(n.toTex(customFunction), 'symbol(a)');
});
});

View File

@ -322,4 +322,34 @@ 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 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(callback) + ', ';
});
return latex;
}
else if (node.type === 'ConstantNode') {
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(customFunction), 'a at const\\left(2, number\\right), const\\left(1, number\\right), equals const\\left(5, number\\right)');
});
});