1 | # Customization
|
2 |
|
3 | Besides parsing and evaluating expressions, the expression parser supports
|
4 | a number of features to customize processing and evaluation of expressions
|
5 | and outputting expressions.
|
6 |
|
7 | On 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 |
|
17 | It is possible to preprocess function arguments and post process a functions
|
18 | return value by writing a *transform* for the function. A transform is a
|
19 | function wrapping around a function to be transformed or completely replaces
|
20 | a function.
|
21 |
|
22 | For example, the functions for math.js use zero-based matrix indices (as is
|
23 | common in programing languages), but the expression parser uses one-based
|
24 | indices. To enable this, all functions dealing with indices have a transform,
|
25 | which changes input from one-based to zero-based, and transforms output (and
|
26 | error message) from zero-based to one-based.
|
27 |
|
28 | ```js
|
29 | // using plain JavaScript, indices are zero-based:
|
30 | const a = [[1, 2], [3, 4]] // a 2x2 matrix
|
31 | math.subset(a, math.index(0, 1)) // returns 2
|
32 |
|
33 | // using the expression parser, indices are transformed to one-based:
|
34 | const a = [[1, 2], [3, 4]] // a 2x2 matrix
|
35 | let scope = {
|
36 | a: a
|
37 | }
|
38 | math.eval('subset(a, index(1, 2))', scope) // returns 2
|
39 | ```
|
40 |
|
41 | To create a transform for a function, the transform function must be attached
|
42 | to the function as property `transform`:
|
43 |
|
44 | ```js
|
45 | const math = require('../index')
|
46 |
|
47 | // create a function
|
48 | function addIt(a, b) {
|
49 | return a + b
|
50 | }
|
51 |
|
52 | // attach a transform function to the function addIt
|
53 | addIt.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
|
66 | math.import({
|
67 | addIt: addIt
|
68 | })
|
69 |
|
70 | // use the function via the expression parser
|
71 | console.log('Using expression parser:')
|
72 | console.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
|
80 | console.log('')
|
81 | console.log('Using plain JavaScript:')
|
82 | console.log('2+4=' + math.addIt(2, 4))
|
83 | // This will output:
|
84 | //
|
85 | // 6
|
86 | ```
|
87 |
|
88 | Functions with a transform must be imported in the `math` namespace, as they
|
89 | need to be processed at compile time. They are not supported when passed via a
|
90 | scope at evaluation time.
|
91 |
|
92 |
|
93 | ## Custom argument parsing
|
94 |
|
95 | The expression parser of math.js has support for letting functions
|
96 | parse and evaluate arguments themselves, instead of calling them with
|
97 | evaluated arguments. This is useful for example when creating a function
|
98 | like `plot(f(x), x)` or `integrate(f(x), x, start, end)`, where some of the
|
99 | arguments 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
|
101 | but used to specify the variable looping over the function `f(x)`.
|
102 |
|
103 | Functions having a property `rawArgs` with value `true` are treated in a special
|
104 | way by the expression parser: they will be invoked with unevaluated arguments,
|
105 | allowing the function to process the arguments in a customized way. Raw
|
106 | functions are called as:
|
107 |
|
108 | ```
|
109 | rawFunction(args: Node[], math: Object, scope: Object)
|
110 | ```
|
111 |
|
112 | Where :
|
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 |
|
118 | Raw functions must be imported in the `math` namespace, as they need to be
|
119 | processed at compile time. They are not supported when passed via a scope
|
120 | at evaluation time.
|
121 |
|
122 | A simple example:
|
123 |
|
124 | ```js
|
125 | function 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
|
140 | myFunction.rawArgs = true
|
141 |
|
142 | // import the new function in the math namespace
|
143 | math.import({
|
144 | myFunction: myFunction
|
145 | })
|
146 |
|
147 | // use the function
|
148 | math.eval('myFunction(2 + 3, sqrt(4))')
|
149 | // returns 'arguments: 2 + 3, sqrt(4), evaluated: 5, 2'
|
150 | ```
|
151 |
|
152 | ## Custom LaTeX handlers
|
153 |
|
154 | You 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'
|
156 | or 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
|
168 | const 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 |
|
180 | customFunctions.plus.toTex = '${args[0]}+${args[1]}' //template string
|
181 | customFunctions.binom.toTex = '\\mathrm{${name}}\\left(${args}\\right)' //template string
|
182 | customFunctions.minus.toTex = function (node, options) { //handler function
|
183 | return node.args[0].toTex(options) + node.name + node.args[1].toTex(options)
|
184 | }
|
185 |
|
186 | math.import(customFunctions)
|
187 |
|
188 | math.parse('plus(1,2)').toTex() // '1+2'
|
189 | math.parse('binom(1,2)').toTex() // '\\mathrm{binom}\\left(1,2\\right)'
|
190 | math.parse('minus(1,2)').toTex() // '1minus2'
|
191 | ```
|
192 |
|
193 | ## Custom HTML, LaTeX and string output
|
194 |
|
195 | All expression nodes have a method `toTex` and `toString` to output an expression respectively in HTML or LaTex format or as regular text .
|
196 | The 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 |
|
208 | The `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 |
|
214 | There's two ways of passing callbacks:
|
215 |
|
216 | 1. Pass an object that maps function names to callbacks. Those callbacks will be used for FunctionNodes with
|
217 | functions of that name.
|
218 | 2. Pass a function to `toTex`. This function will then be used for every node.
|
219 |
|
220 | ```js
|
221 | const expression = math.parse('(1+1+1)')
|
222 |
|
223 | expression.toString() // (1 + 1 + 1)
|
224 | expression.toString({parenthesis: 'keep'}) // (1 + 1 + 1)
|
225 | expression.toString({parenthesis: 'auto'}) // 1 + 1 + 1
|
226 | expression.toString({parenthesis: 'all'}) // (1 + 1) + 1
|
227 | ```
|
228 |
|
229 | ### Handler
|
230 |
|
231 | You 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 |
|
233 | 1. Pass an object that maps function names to callbacks. Those callbacks will be used for FunctionNodes that contain functions with that name.
|
234 | 2. Pass a callback directly. This callback will run for every node, so you can replace the output of anything you like.
|
235 |
|
236 | A callback function has the following form:
|
237 |
|
238 | ```js
|
239 | function callback (node, options) {
|
240 | ...
|
241 | }
|
242 | ```
|
243 | Where `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 |
|
245 | If 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
|
252 | const customFunctions = {
|
253 | binomial: function (n, k) {
|
254 | //calculate n choose k
|
255 | // (do some stuff)
|
256 | return result
|
257 | }
|
258 | }
|
259 |
|
260 | const 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 |
|
270 | You can simply use your custom toTex functions by passing them to `toTex`:
|
271 |
|
272 | ```js
|
273 | math.import(customFunctions)
|
274 | const expression = math.parse('binomial(factorial(2),1)')
|
275 | const 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
|
282 | function 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 |
|
303 | const expression = math.parse('1+2')
|
304 | const latex = expression.toTex({handler: customLaTeX})
|
305 | // latex now contains '\mbox{one} plus \mbox{two}'
|
306 | ```
|
307 |
|
308 | Another example in conjunction with custom functions:
|
309 |
|
310 | ```js
|
311 | const customFunctions = {
|
312 | binomial: function (n, k) {
|
313 | //calculate n choose k
|
314 | // (do some stuff)
|
315 | return result
|
316 | }
|
317 | }
|
318 |
|
319 | function 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 |
|
325 | math.import(customFunctions)
|
326 | const expression = math.parse('binomial(2,1)')
|
327 | const latex = expression.toTex({handler: customLaTeX})
|
328 | // latex now contains "\binom{2}{1}"
|
329 | ```
|
330 |
|
331 | ### Implicit multiplication
|
332 |
|
333 | 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.
|
334 |
|
335 | Example:
|
336 |
|
337 | ```js
|
338 | const node = math.parse('2a')
|
339 |
|
340 | node.toString() // '2 a'
|
341 | node.toString({implicit: 'hide'}) // '2 a'
|
342 | node.toString({implicit: 'show'}) // '2 * a'
|
343 |
|
344 | node.toTex() // '2~ a'
|
345 | node.toTex({implicit: 'hide'}) // '2~ a'
|
346 | node.toTex({implicit: 'show'}) // '2\\cdot a'
|
347 | ```
|
348 |
|
349 |
|
350 | ## Customize supported characters
|
351 |
|
352 | It is possible to customize the characters allowed in symbols and digits.
|
353 | The `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 |
|
361 | The exact signature and implementation of these functions can be looked up in
|
362 | the [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 |
|
364 | For example, the phone character <code>☎</code> is not supported by default. It can be enabled
|
365 | by replacing the `isAlpha` function:
|
366 |
|
367 | ```js
|
368 | const isAlphaOriginal = math.expression.parse.isAlpha
|
369 | math.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
|
374 | const result = math.eval('\u260Efoo', {'\u260Efoo': 42}) // returns 42
|
375 | console.log(result)
|
376 | ```
|