From 6f39ec5da992e2c5c35ef71396c991fcf87b3866 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Sat, 19 Mar 2016 14:25:58 +0100 Subject: [PATCH 1/6] OperatorNode: new 'implicit' attribute to mark implicit multiplication This also modifies the parser to set this attribute --- lib/expression/node/OperatorNode.js | 4 +++- lib/expression/parse.js | 2 +- test/expression/parse.test.js | 12 ++++++++++++ 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/expression/node/OperatorNode.js b/lib/expression/node/OperatorNode.js index c7d7f0a8a..ac770415b 100644 --- a/lib/expression/node/OperatorNode.js +++ b/lib/expression/node/OperatorNode.js @@ -17,8 +17,9 @@ function factory (type, config, load, typed, math) { * @param {string} op Operator name, for example '+' * @param {string} fn Function name, for example 'add' * @param {Node[]} args Operator arguments + * @param {boolean} implicit Is this an implicit multiplication? */ - function OperatorNode(op, fn, args) { + function OperatorNode(op, fn, args, implicit) { if (!(this instanceof OperatorNode)) { throw new SyntaxError('Constructor must be called with the new operator'); } @@ -35,6 +36,7 @@ function factory (type, config, load, typed, math) { throw new TypeError('Array containing Nodes expected for parameter "args"'); } + this.implicit = (implicit === true); this.op = op; this.fn = fn; this.args = args || []; diff --git a/lib/expression/parse.js b/lib/expression/parse.js index ef890dc4c..38c08e518 100644 --- a/lib/expression/parse.js +++ b/lib/expression/parse.js @@ -896,7 +896,7 @@ function factory (type, config, load, typed) { // number: implicit multiplication like '(2+3)2' // parenthesis: implicit multiplication like '2(3+4)', '2[1,2,3]' last = parseUnary(); - node = new OperatorNode('*', 'multiply', [node, last]); + node = new OperatorNode('*', 'multiply', [node, last], true /*implicit*/); } else { break; diff --git a/test/expression/parse.test.js b/test/expression/parse.test.js index fcad09711..140cc4bd5 100644 --- a/test/expression/parse.test.js +++ b/test/expression/parse.test.js @@ -1084,6 +1084,18 @@ describe('parse', function() { assert.deepEqual(parseAndEval('[1,2;3,4] [2,2]', {A: [[1,2], [3,4]]}), 4); // index, no multiplication }); + it('should tell the OperatorNode about implicit multiplications', function() { + assert.equal(parse('4a').implicit, true); + assert.equal(parse('4 a').implicit, true); + assert.equal(parse('a b').implicit, true); + assert.equal(parse('2a b').implicit, true); + assert.equal(parse('a b c').implicit, true); + + assert.equal(parse('(2+3)a').implicit, true); + assert.equal(parse('(2+3)2').implicit, true); + assert.equal(parse('2(3+4)').implicit, true); + }); + it('should correctly order consecutive multiplications and implicit multiplications', function() { var node = parse('9km*3km'); assert.equal(node.toString({parenthesis: 'all'}), '((9 * km) * 3) * km'); From 876740e123fc29f85a6473a1d4746b4ff6125d09 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Sat, 19 Mar 2016 18:38:58 +0100 Subject: [PATCH 2/6] fix: ParenthesisNode doesn't print parentheses with empty options When the options to toString or toTex where an empty object, ParenthesisNode didn't fall back to the default 'keep' behavior. --- lib/expression/node/ParenthesisNode.js | 4 ++-- test/expression/node/ParenthesisNode.test.js | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/expression/node/ParenthesisNode.js b/lib/expression/node/ParenthesisNode.js index dd81dd551..0df0ef7fb 100644 --- a/lib/expression/node/ParenthesisNode.js +++ b/lib/expression/node/ParenthesisNode.js @@ -88,7 +88,7 @@ function factory (type, config, load, typed) { * @override */ ParenthesisNode.prototype._toString = function(options) { - if ((!options) || (options && options.parenthesis === 'keep')) { + if ((!options) || (options && !options.parenthesis) || (options && options.parenthesis === 'keep')) { return '(' + this.content.toString(options) + ')'; } return this.content.toString(options); @@ -101,7 +101,7 @@ function factory (type, config, load, typed) { * @override */ ParenthesisNode.prototype._toTex = function(options) { - if ((!options) || (options && options.parenthesis === 'keep')) { + if ((!options) || (options && !options.parenthesis) || (options && options.parenthesis === 'keep')) { return '\\left(' + this.content.toTex(options) + '\\right)'; } return this.content.toTex(options); diff --git a/test/expression/node/ParenthesisNode.test.js b/test/expression/node/ParenthesisNode.test.js index e2b5c55c1..acd0c79a9 100644 --- a/test/expression/node/ParenthesisNode.test.js +++ b/test/expression/node/ParenthesisNode.test.js @@ -129,6 +129,7 @@ describe('ParenthesisNode', function() { var n = new ParenthesisNode(a); assert.equal(n.toString(), '(1)'); + assert.equal(n.toString({}), '(1)'); }); it ('should stringify a ParenthesisNode when not in keep mode', function () { @@ -158,6 +159,7 @@ describe('ParenthesisNode', function() { var n = new ParenthesisNode(a); assert.equal(n.toTex(), '\\left(1\\right)'); + assert.equal(n.toTex({}), '\\left(1\\right)'); }); it ('should LaTeX a ParenthesisNode when not in keep mode', function () { From 65817dbf033da17715db46a3a61469fbc5be42c0 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Sat, 19 Mar 2016 18:48:54 +0100 Subject: [PATCH 3/6] OperatorNode: toString support for implicit multiplication Use the option `implicit` with the value `hide` to not show the multiplication sign for implicit multiplication. This is the default. Use `show` otherwise. --- lib/expression/node/OperatorNode.js | 5 +++ test/expression/node/OperatorNode.test.js | 43 +++++++++++++++++++++++ test/expression/parse.test.js | 6 ++-- 3 files changed, 51 insertions(+), 3 deletions(-) diff --git a/lib/expression/node/OperatorNode.js b/lib/expression/node/OperatorNode.js index ac770415b..d751f5d18 100644 --- a/lib/expression/node/OperatorNode.js +++ b/lib/expression/node/OperatorNode.js @@ -281,6 +281,7 @@ function factory (type, config, load, typed, math) { */ OperatorNode.prototype._toString = function (options) { var parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep'; + var implicit = (options && options.implicit) ? options.implicit : 'hide'; var args = this.args; var parens = calculateNecessaryParentheses(this, parenthesis, args, false); @@ -313,6 +314,10 @@ function factory (type, config, load, typed, math) { rhs = '(' + rhs + ')'; } + if (this.implicit && (this.getIdentifier() === 'OperatorNode:multiply') && (implicit == 'hide')) { + return lhs + ' ' + rhs; + } + return lhs + ' ' + this.op + ' ' + rhs; default: diff --git a/test/expression/node/OperatorNode.test.js b/test/expression/node/OperatorNode.test.js index e44e87321..eff54965b 100644 --- a/test/expression/node/OperatorNode.test.js +++ b/test/expression/node/OperatorNode.test.js @@ -505,4 +505,47 @@ describe('OperatorNode', function() { assert.equal(math.parse('1+(1+1)').toTex({parenthesis: 'auto'}), '1+1+1'); }); + it ('should stringify implicit multiplications', function () { + var a = math.parse('4a'); + var b = math.parse('4 a'); + var c = math.parse('a b'); + var d = math.parse('2a b'); + var e = math.parse('a b c'); + var f = math.parse('(2+3)a'); + var g = math.parse('(2+3)2'); + var h = math.parse('2(3+4)'); + + assert.equal(a.toString(), a.toString({implicit: 'hide'})); + assert.equal(a.toString({implicit: 'hide'}), '4 a'); + assert.equal(a.toString({implicit: 'show'}), '4 * a'); + + assert.equal(b.toString(), b.toString({implicit: 'hide'})); + assert.equal(b.toString({implicit: 'hide'}), '4 a'); + assert.equal(b.toString({implicit: 'show'}), '4 * a'); + + assert.equal(c.toString(), c.toString({implicit: 'hide'})); + assert.equal(c.toString({implicit: 'hide'}), 'a b'); + assert.equal(c.toString({implicit: 'show'}), 'a * b'); + + assert.equal(d.toString(), d.toString({implicit: 'hide'})); + assert.equal(d.toString({implicit: 'hide'}), '2 a b'); + assert.equal(d.toString({implicit: 'show'}), '2 * a * b'); + + assert.equal(e.toString(), e.toString({implicit: 'hide'})); + assert.equal(e.toString({implicit: 'hide'}), 'a b c'); + assert.equal(e.toString({implicit: 'show'}), 'a * b * c'); + + assert.equal(f.toString(), f.toString({implicit: 'hide'})); + assert.equal(f.toString({implicit: 'hide'}), '(2 + 3) a'); + assert.equal(f.toString({implicit: 'show'}), '(2 + 3) * a'); + + assert.equal(g.toString(), g.toString({implicit: 'hide'})); + assert.equal(g.toString({implicit: 'hide'}), '(2 + 3) 2'); + assert.equal(g.toString({implicit: 'show'}), '(2 + 3) * 2'); + + assert.equal(h.toString(), h.toString({implicit: 'hide'})); + assert.equal(h.toString({implicit: 'hide'}), '2 (3 + 4)'); + assert.equal(h.toString({implicit: 'show'}), '2 * (3 + 4)'); + }); + }); diff --git a/test/expression/parse.test.js b/test/expression/parse.test.js index 140cc4bd5..ef2ee87a6 100644 --- a/test/expression/parse.test.js +++ b/test/expression/parse.test.js @@ -1098,7 +1098,7 @@ describe('parse', function() { it('should correctly order consecutive multiplications and implicit multiplications', function() { var node = parse('9km*3km'); - assert.equal(node.toString({parenthesis: 'all'}), '((9 * km) * 3) * km'); + assert.equal(node.toString({parenthesis: 'all'}), '((9 km) * 3) km'); }); it('should throw an error when having an implicit multiplication between two numbers', function() { @@ -1861,8 +1861,8 @@ describe('parse', function() { it('should correctly stringify a node tree', function() { assert.equal(parse('0').toString(), '0'); assert.equal(parse('"hello"').toString(), '"hello"'); - assert.equal(parse('[1, 2 + 3i, 4]').toString(), '[1, 2 + 3 * i, 4]'); - assert.equal(parse('1/2a').toString(), '1 / 2 * a'); + assert.equal(parse('[1, 2 + 3i, 4]').toString(), '[1, 2 + 3 i, 4]'); + assert.equal(parse('1/2a').toString(), '1 / 2 a'); }); it('should correctly stringify an index with dot notation', function() { From c72071dfb7a425e9d5e691c5fdadf75e7b8cd392 Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Sat, 19 Mar 2016 19:09:14 +0100 Subject: [PATCH 4/6] OperatorNode: toTex support for implicit multiplication Use the option `implicit` with the value `hide` to not show the multiplication sign for implicit multiplication. This is the default. Use `show` otherwise. --- lib/expression/node/OperatorNode.js | 5 +++ test/expression/node/OperatorNode.test.js | 42 +++++++++++++++++++++++ test/function/unit/to.test.js | 2 +- 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/lib/expression/node/OperatorNode.js b/lib/expression/node/OperatorNode.js index d751f5d18..6316055d3 100644 --- a/lib/expression/node/OperatorNode.js +++ b/lib/expression/node/OperatorNode.js @@ -333,6 +333,7 @@ function factory (type, config, load, typed, math) { */ OperatorNode.prototype._toTex = function (options) { var parenthesis = (options && options.parenthesis) ? options.parenthesis : 'keep'; + var implicit = (options && options.implicit) ? options.implicit : 'hide'; var args = this.args; var parens = calculateNecessaryParentheses(this, parenthesis, args, true); var op = latex.operators[this.fn]; @@ -391,6 +392,10 @@ function factory (type, config, load, typed, math) { case 'OperatorNode:divide': lhsTex = '\\left(' + lhsTex + '\\right)'; } + case 'OperatorNode:multiply': + if (this.implicit && (implicit === 'hide')) { + return lhsTex + '~' + rhsTex; + } } return lhsTex + op + rhsTex; diff --git a/test/expression/node/OperatorNode.test.js b/test/expression/node/OperatorNode.test.js index eff54965b..fc73d4f02 100644 --- a/test/expression/node/OperatorNode.test.js +++ b/test/expression/node/OperatorNode.test.js @@ -548,4 +548,46 @@ describe('OperatorNode', function() { assert.equal(h.toString({implicit: 'show'}), '2 * (3 + 4)'); }); + it ('should LaTeX implicit multiplications', function () { + var a = math.parse('4a'); + var b = math.parse('4 a'); + var c = math.parse('a b'); + var d = math.parse('2a b'); + var e = math.parse('a b c'); + var f = math.parse('(2+3)a'); + var g = math.parse('(2+3)2'); + var h = math.parse('2(3+4)'); + + assert.equal(a.toTex(), a.toTex({implicit: 'hide'})); + assert.equal(a.toTex({implicit: 'hide'}), '4~ a'); + assert.equal(a.toTex({implicit: 'show'}), '4\\cdot a'); + + assert.equal(b.toTex(), b.toTex({implicit: 'hide'})); + assert.equal(b.toTex({implicit: 'hide'}), '4~ a'); + assert.equal(b.toTex({implicit: 'show'}), '4\\cdot a'); + + assert.equal(c.toTex(), c.toTex({implicit: 'hide'})); + assert.equal(c.toTex({implicit: 'hide'}), ' a~\\mathrm{b}'); + assert.equal(c.toTex({implicit: 'show'}), ' a\\cdot\\mathrm{b}'); + + assert.equal(d.toTex(), d.toTex({implicit: 'hide'})); + assert.equal(d.toTex({implicit: 'hide'}), '2~ a~\\mathrm{b}'); + assert.equal(d.toTex({implicit: 'show'}), '2\\cdot a\\cdot\\mathrm{b}'); + + assert.equal(e.toTex(), e.toTex({implicit: 'hide'})); + assert.equal(e.toTex({implicit: 'hide'}), ' a~\\mathrm{b}~ c'); + assert.equal(e.toTex({implicit: 'show'}), ' a\\cdot\\mathrm{b}\\cdot c'); + + assert.equal(f.toTex(), f.toTex({implicit: 'hide'})); + assert.equal(f.toTex({implicit: 'hide'}), '\\left(2+3\\right)~ a'); + assert.equal(f.toTex({implicit: 'show'}), '\\left(2+3\\right)\\cdot a'); + + assert.equal(g.toTex(), g.toTex({implicit: 'hide'})); + assert.equal(g.toTex({implicit: 'hide'}), '\\left(2+3\\right)~2'); + assert.equal(g.toTex({implicit: 'show'}), '\\left(2+3\\right)\\cdot2'); + + assert.equal(h.toTex(), h.toTex({implicit: 'hide'})); + assert.equal(h.toTex({implicit: 'hide'}), '2~\\left(3+4\\right)'); + assert.equal(h.toTex({implicit: 'show'}), '2\\cdot\\left(3+4\\right)'); + }); }); diff --git a/test/function/unit/to.test.js b/test/function/unit/to.test.js index 083ef0c6d..b0849fc37 100644 --- a/test/function/unit/to.test.js +++ b/test/function/unit/to.test.js @@ -83,6 +83,6 @@ describe('to', function() { it('should LaTeX to', function () { var expression = math.parse('to(2cm,m)'); - assert.equal(expression.toTex(), '\\left(2\\cdot\\mathrm{cm}\\rightarrow\\mathrm{m}\\right)'); + assert.equal(expression.toTex(), '\\left(2~\\mathrm{cm}\\rightarrow\\mathrm{m}\\right)'); }); }); From f038f865022c03dcce0453ab01bc5b0353399e9a Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Sat, 19 Mar 2016 19:23:06 +0100 Subject: [PATCH 5/6] Documentation for implicit multiplication toString/toTex options --- docs/expressions/customization.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/expressions/customization.md b/docs/expressions/customization.md index 08af41ce0..8af9223f8 100644 --- a/docs/expressions/customization.md +++ b/docs/expressions/customization.md @@ -197,7 +197,8 @@ The functions `toTex` and `toString` accept an `options` argument to customise o ```js { parenthesis: 'keep', // parenthesis option - handler: someHandler // handler to change the output + handler: someHandler, // handler to change the output + implicit: 'hide' // how to treat implicit multiplication } ``` @@ -325,3 +326,20 @@ var expression = math.parse('binomial(2,1)'); var latex = expression.toTex({handler: customLaTeX}); //latex now contains "\binom{2}{1}" ``` + +### Implicit multiplication + +You can change the way that implicit multiplication is converted to a string or LaTeX. The two options are `hide`, to not show a multiplication operator for implicit multiplication and `show` to show it. + +Example: +```js +var node = math.parse('2a'); + +node.toString(); //'2 a' +node.toString({implicit: 'hide'}); //'2 a' +node.toString({implicit: 'show'}); //'2 * a' + +node.toTex(); //'2~ a' +node.toTex({implicit: 'hide'}); //'2~ a' +node.toTex({implicit: 'show'}); //'2\\cdot a' +``` From c57964003d7c5ec3f86801f564fcb7e9068bab1b Mon Sep 17 00:00:00 2001 From: Max Bruckner Date: Sat, 19 Mar 2016 19:37:56 +0100 Subject: [PATCH 6/6] pretty printing example: expose implicit multiplication options --- examples/browser/pretty_printing_with_mathjax.html | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/examples/browser/pretty_printing_with_mathjax.html b/examples/browser/pretty_printing_with_mathjax.html index 5a8d5019a..c59e0c0a9 100644 --- a/examples/browser/pretty_printing_with_mathjax.html +++ b/examples/browser/pretty_printing_with_mathjax.html @@ -65,13 +65,18 @@ keep auto all +
+Implicit multiplication: +hide +show