UNPKG

12.3 kBMarkdownView Raw
1# Customization
2
3Besides parsing and evaluating expressions, the expression parser supports
4a number of features to customize processing and evaluation of expressions
5and outputting expressions.
6
7On this page:
8
9- [Function transforms](#function-transforms)
10- [Custom argument parsing](#custom-argument-parsing)
11- [Custom LaTeX handlers](#custom-latex-handlers)
12- [Custom HTML, LaTeX and string output](#custom-html-latex-and-string-output)
13- [Customize supported characters](#customize-supported-characters)
14
15## Function transforms
16
17It is possible to preprocess function arguments and post process a functions
18return value by writing a *transform* for the function. A transform is a
19function wrapping around a function to be transformed or completely replaces
20a function.
21
22For example, the functions for math.js use zero-based matrix indices (as is
23common in programing languages), but the expression parser uses one-based
24indices. To enable this, all functions dealing with indices have a transform,
25which changes input from one-based to zero-based, and transforms output (and
26error message) from zero-based to one-based.
27
28```js
29// using plain JavaScript, indices are zero-based:
30const a = [[1, 2], [3, 4]] // a 2x2 matrix
31math.subset(a, math.index(0, 1)) // returns 2
32
33// using the expression parser, indices are transformed to one-based:
34const a = [[1, 2], [3, 4]] // a 2x2 matrix
35let scope = {
36 a: a
37}
38math.eval('subset(a, index(1, 2))', scope) // returns 2
39```
40
41To create a transform for a function, the transform function must be attached
42to the function as property `transform`:
43
44```js
45const math = require('../index')
46
47// create a function
48function addIt(a, b) {
49 return a + b
50}
51
52// attach a transform function to the function addIt
53addIt.transform = function (a, b) {
54 console.log('input: a=' + a + ', b=' + b)
55 // we can manipulate input here before executing addIt
56
57 const res = addIt(a, b)
58
59 console.log('result: ' + res)
60 // we can manipulate result here before returning
61
62 return res
63}
64
65// import the function into math.js
66math.import({
67 addIt: addIt
68})
69
70// use the function via the expression parser
71console.log('Using expression parser:')
72console.log('2+4=' + math.eval('addIt(2, 4)'))
73// This will output:
74//
75// input: a=2, b=4
76// result: 6
77// 2+4=6
78
79// when used via plain JavaScript, the transform is not invoked
80console.log('')
81console.log('Using plain JavaScript:')
82console.log('2+4=' + math.addIt(2, 4))
83// This will output:
84//
85// 6
86```
87
88Functions with a transform must be imported in the `math` namespace, as they
89need to be processed at compile time. They are not supported when passed via a
90scope at evaluation time.
91
92
93## Custom argument parsing
94
95The expression parser of math.js has support for letting functions
96parse and evaluate arguments themselves, instead of calling them with
97evaluated arguments. This is useful for example when creating a function
98like `plot(f(x), x)` or `integrate(f(x), x, start, end)`, where some of the
99arguments need to be processed in a special way. In these cases, the expression
100`f(x)` will be evaluated repeatedly by the function, and `x` is not evaluated
101but used to specify the variable looping over the function `f(x)`.
102
103Functions having a property `rawArgs` with value `true` are treated in a special
104way by the expression parser: they will be invoked with unevaluated arguments,
105allowing the function to process the arguments in a customized way. Raw
106functions are called as:
107
108```
109rawFunction(args: Node[], math: Object, scope: Object)
110```
111
112Where :
113
114- `args` is an Array with nodes of the parsed arguments.
115- `math` is the math namespace against which the expression was compiled.
116- `scope` is the scope provided when evaluating the expression.
117
118Raw functions must be imported in the `math` namespace, as they need to be
119processed at compile time. They are not supported when passed via a scope
120at evaluation time.
121
122A simple example:
123
124```js
125function myFunction(args, math, scope) {
126 // get string representation of the arguments
127 const str = args.map(function (arg) {
128 return arg.toString()
129 })
130
131 // evaluate the arguments
132 const res = args.map(function (arg) {
133 return arg.compile().eval(scope)
134 })
135
136 return 'arguments: ' + str.join(',') + ', evaluated: ' + res.join(',')
137}
138
139// mark the function as "rawArgs", so it will be called with unevaluated arguments
140myFunction.rawArgs = true
141
142// import the new function in the math namespace
143math.import({
144 myFunction: myFunction
145})
146
147// use the function
148math.eval('myFunction(2 + 3, sqrt(4))')
149// returns 'arguments: 2 + 3, sqrt(4), evaluated: 5, 2'
150```
151
152## Custom LaTeX handlers
153
154You can attach a `toTex` property to your custom functions before importing them to define their LaTeX output. This
155`toTex` property can be a handler in the format described in the next section 'Custom LaTeX and String conversion'
156or a template string similar to ES6 templates.
157
158### Template syntax
159
160- `${name}`: Gets replaced by the name of the function
161- `${args}`: Gets replaced by a comma separated list of the arguments of the function.
162- `${args[0]}`: Gets replaced by the first argument of a function
163- `$$`: Gets replaced by `$`
164
165#### Example
166
167```js
168const customFunctions = {
169 plus: function (a, b) {
170 return a + b
171 },
172 minus: function (a, b) {
173 return a - b
174 },
175 binom: function (n, k) {
176 return 1
177 }
178}
179
180customFunctions.plus.toTex = '${args[0]}+${args[1]}' //template string
181customFunctions.binom.toTex = '\\mathrm{${name}}\\left(${args}\\right)' //template string
182customFunctions.minus.toTex = function (node, options) { //handler function
183 return node.args[0].toTex(options) + node.name + node.args[1].toTex(options)
184}
185
186math.import(customFunctions)
187
188math.parse('plus(1,2)').toTex() // '1+2'
189math.parse('binom(1,2)').toTex() // '\\mathrm{binom}\\left(1,2\\right)'
190math.parse('minus(1,2)').toTex() // '1minus2'
191```
192
193## Custom HTML, LaTeX and string output
194
195All expression nodes have a method `toTex` and `toString` to output an expression respectively in HTML or LaTex format or as regular text .
196The functions `toHTML`, `toTex` and `toString` accept an `options` argument to customise output. This object is of the following form:
197
198```js
199{
200 parenthesis: 'keep', // parenthesis option
201 handler: someHandler, // handler to change the output
202 implicit: 'hide' // how to treat implicit multiplication
203}
204```
205
206### Parenthesis
207
208The `parenthesis` option changes the way parentheses are used in the output. There are three options available:
209
210- `keep` Keep the parentheses from the input and display them as is. This is the default.
211- `auto` Only display parentheses that are necessary. Mathjs tries to get rid of as much parentheses as possible.
212- `all` Display all parentheses that are given by the structure of the node tree. This makes the output precedence unambiguous.
213
214There's two ways of passing callbacks:
215
2161. Pass an object that maps function names to callbacks. Those callbacks will be used for FunctionNodes with
217functions of that name.
2182. Pass a function to `toTex`. This function will then be used for every node.
219
220```js
221const expression = math.parse('(1+1+1)')
222
223expression.toString() // (1 + 1 + 1)
224expression.toString({parenthesis: 'keep'}) // (1 + 1 + 1)
225expression.toString({parenthesis: 'auto'}) // 1 + 1 + 1
226expression.toString({parenthesis: 'all'}) // (1 + 1) + 1
227```
228
229### Handler
230
231You can provide the `toTex` and `toString` functions of an expression with your own custom handlers that override the internal behaviour. This is especially useful to provide LaTeX/string output for your own custom functions. This can be done in two ways:
232
2331. Pass an object that maps function names to callbacks. Those callbacks will be used for FunctionNodes that contain functions with that name.
2342. Pass a callback directly. This callback will run for every node, so you can replace the output of anything you like.
235
236A callback function has the following form:
237
238```js
239function callback (node, options) {
240 ...
241}
242```
243Where `options` is the object passed to `toHTML`/`toTex`/`toString`. Don't forget to pass this on to the child nodes, and `node` is a reference to the current node.
244
245If a callback returns nothing, the standard output will be used. If your callback returns a string, this string will be used.
246
247**Although the following examples use `toTex`, it works for `toString` and `toHTML` in the same way**
248
249#### Examples for option 1
250
251```js
252const customFunctions = {
253 binomial: function (n, k) {
254 //calculate n choose k
255 // (do some stuff)
256 return result
257 }
258}
259
260const customLaTeX = {
261 'binomial': function (node, options) { //provide toTex for your own custom function
262 return '\\binom{' + node.args[0].toTex(options) + '}{' + node.args[1].toTex(options) + '}'
263 },
264 'factorial': function (node, options) { //override toTex for builtin functions
265 return 'factorial\\left(' + node.args[0] + '\\right)'
266 }
267}
268```
269
270You can simply use your custom toTex functions by passing them to `toTex`:
271
272```js
273math.import(customFunctions)
274const expression = math.parse('binomial(factorial(2),1)')
275const latex = expression.toTex({handler: customLaTeX})
276// latex now contains "\binom{factorial\\left(2\\right)}{1}"
277```
278
279#### Examples for option 2:
280
281```js
282function customLaTeX(node, options) {
283 if ((node.type === 'OperatorNode') && (node.fn === 'add')) {
284 //don't forget to pass the options to the toTex functions
285 return node.args[0].toTex(options) + ' plus ' + node.args[1].toTex(options)
286 }
287 else if (node.type === 'ConstantNode') {
288 if (node.value === 0) {
289 return '\\mbox{zero}'
290 }
291 else if (node.value === 1) {
292 return '\\mbox{one}'
293 }
294 else if (node.value === 2) {
295 return '\\mbox{two}'
296 }
297 else {
298 return node.value
299 }
300 }
301}
302
303const expression = math.parse('1+2')
304const latex = expression.toTex({handler: customLaTeX})
305// latex now contains '\mbox{one} plus \mbox{two}'
306```
307
308Another example in conjunction with custom functions:
309
310```js
311const customFunctions = {
312 binomial: function (n, k) {
313 //calculate n choose k
314 // (do some stuff)
315 return result
316 }
317}
318
319function customLaTeX(node, options) {
320 if ((node.type === 'FunctionNode') && (node.name === 'binomial')) {
321 return '\\binom{' + node.args[0].toTex(options) + '}{' + node.args[1].toTex(options) + '}'
322 }
323}
324
325math.import(customFunctions)
326const expression = math.parse('binomial(2,1)')
327const latex = expression.toTex({handler: customLaTeX})
328// latex now contains "\binom{2}{1}"
329```
330
331### Implicit multiplication
332
333You 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.
334
335Example:
336
337```js
338const node = math.parse('2a')
339
340node.toString() // '2 a'
341node.toString({implicit: 'hide'}) // '2 a'
342node.toString({implicit: 'show'}) // '2 * a'
343
344node.toTex() // '2~ a'
345node.toTex({implicit: 'hide'}) // '2~ a'
346node.toTex({implicit: 'show'}) // '2\\cdot a'
347```
348
349
350## Customize supported characters
351
352It is possible to customize the characters allowed in symbols and digits.
353The `parse` function exposes the following test functions:
354
355- `math.expression.parse.isAlpha(c, cPrev, cNext)`
356- `math.expression.parse.isWhitespace(c, nestingLevel)`
357- `math.expression.parse.isDecimalMark(c, cNext)`
358- `math.expression.parse.isDigitDot(c)`
359- `math.expression.parse.isDigit(c)`
360
361The exact signature and implementation of these functions can be looked up in
362the [source code of the parser](https://github.com/josdejong/mathjs/blob/master/lib/expression/parse.js). The allowed alpha characters are described here: [Constants and variables](syntax.md#constants-and-variables).
363
364For example, the phone character <code>&#9742;</code> is not supported by default. It can be enabled
365by replacing the `isAlpha` function:
366
367```js
368const isAlphaOriginal = math.expression.parse.isAlpha
369math.expression.parse.isAlpha = function (c, cPrev, cNext) {
370 return isAlphaOriginal(c, cPrev, cNext) || (c === '\u260E')
371}
372
373// now we can use the \u260E (phone) character in expressions
374const result = math.eval('\u260Efoo', {'\u260Efoo': 42}) // returns 42
375console.log(result)
376```