UNPKG

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