customization.md (12394B)
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.evaluate('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 import { create, all } from 'mathjs' 46 const math = create(all) 47 48 // create a function 49 function addIt(a, b) { 50 return a + b 51 } 52 53 // attach a transform function to the function addIt 54 addIt.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 67 math.import({ 68 addIt: addIt 69 }) 70 71 // use the function via the expression parser 72 console.log('Using expression parser:') 73 console.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 81 console.log('') 82 console.log('Using plain JavaScript:') 83 console.log('2+4=' + math.addIt(2, 4)) 84 // This will output: 85 // 86 // 6 87 ``` 88 89 Functions with a transform must be imported in the `math` namespace, as they 90 need to be processed at compile time. They are not supported when passed via a 91 scope at evaluation time. 92 93 94 ## Custom argument parsing 95 96 The expression parser of math.js has support for letting functions 97 parse and evaluate arguments themselves, instead of calling them with 98 evaluated arguments. This is useful for example when creating a function 99 like `plot(f(x), x)` or `integrate(f(x), x, start, end)`, where some of the 100 arguments 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 102 but used to specify the variable looping over the function `f(x)`. 103 104 Functions having a property `rawArgs` with value `true` are treated in a special 105 way by the expression parser: they will be invoked with unevaluated arguments, 106 allowing the function to process the arguments in a customized way. Raw 107 functions are called as: 108 109 ``` 110 rawFunction(args: Node[], math: Object, scope: Object) 111 ``` 112 113 Where : 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 121 Raw functions must be imported in the `math` namespace, as they need to be 122 processed at compile time. They are not supported when passed via a scope 123 at evaluation time. 124 125 A simple example: 126 127 ```js 128 function 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 143 myFunction.rawArgs = true 144 145 // import the new function in the math namespace 146 math.import({ 147 myFunction: myFunction 148 }) 149 150 // use the function 151 math.evaluate('myFunction(2 + 3, sqrt(4))') 152 // returns 'arguments: 2 + 3, sqrt(4), evaluated: 5, 2' 153 ``` 154 155 ## Custom LaTeX handlers 156 157 You 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' 159 or 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 171 const 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 183 customFunctions.plus.toTex = '${args[0]}+${args[1]}' //template string 184 customFunctions.binom.toTex = '\\mathrm{${name}}\\left(${args}\\right)' //template string 185 customFunctions.minus.toTex = function (node, options) { //handler function 186 return node.args[0].toTex(options) + node.name + node.args[1].toTex(options) 187 } 188 189 math.import(customFunctions) 190 191 math.parse('plus(1,2)').toTex() // '1+2' 192 math.parse('binom(1,2)').toTex() // '\\mathrm{binom}\\left(1,2\\right)' 193 math.parse('minus(1,2)').toTex() // '1minus2' 194 ``` 195 196 ## Custom HTML, LaTeX and string output 197 198 All expression nodes have a method `toTex` and `toString` to output an expression respectively in HTML or LaTex format or as regular text . 199 The 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 211 The `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 217 There's two ways of passing callbacks: 218 219 1. Pass an object that maps function names to callbacks. Those callbacks will be used for FunctionNodes with 220 functions of that name. 221 2. Pass a function to `toTex`. This function will then be used for every node. 222 223 ```js 224 const expression = math.parse('(1+1+1)') 225 226 expression.toString() // (1 + 1 + 1) 227 expression.toString({parenthesis: 'keep'}) // (1 + 1 + 1) 228 expression.toString({parenthesis: 'auto'}) // 1 + 1 + 1 229 expression.toString({parenthesis: 'all'}) // (1 + 1) + 1 230 ``` 231 232 ### Handler 233 234 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: 235 236 1. Pass an object that maps function names to callbacks. Those callbacks will be used for FunctionNodes that contain functions with that name. 237 2. Pass a callback directly. This callback will run for every node, so you can replace the output of anything you like. 238 239 A callback function has the following form: 240 241 ```js 242 function callback (node, options) { 243 ... 244 } 245 ``` 246 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. 247 248 If 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 255 const customFunctions = { 256 binomial: function (n, k) { 257 //calculate n choose k 258 // (do some stuff) 259 return result 260 } 261 } 262 263 const 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 273 You can simply use your custom toTex functions by passing them to `toTex`: 274 275 ```js 276 math.import(customFunctions) 277 const expression = math.parse('binomial(factorial(2),1)') 278 const 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 285 function 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 306 const expression = math.parse('1+2') 307 const latex = expression.toTex({handler: customLaTeX}) 308 // latex now contains '\mbox{one} plus \mbox{two}' 309 ``` 310 311 Another example in conjunction with custom functions: 312 313 ```js 314 const customFunctions = { 315 binomial: function (n, k) { 316 //calculate n choose k 317 // (do some stuff) 318 return result 319 } 320 } 321 322 function 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 328 math.import(customFunctions) 329 const expression = math.parse('binomial(2,1)') 330 const latex = expression.toTex({handler: customLaTeX}) 331 // latex now contains "\binom{2}{1}" 332 ``` 333 334 ### Implicit multiplication 335 336 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. 337 338 Example: 339 340 ```js 341 const node = math.parse('2a') 342 343 node.toString() // '2 a' 344 node.toString({implicit: 'hide'}) // '2 a' 345 node.toString({implicit: 'show'}) // '2 * a' 346 347 node.toTex() // '2~ a' 348 node.toTex({implicit: 'hide'}) // '2~ a' 349 node.toTex({implicit: 'show'}) // '2\\cdot a' 350 ``` 351 352 353 ## Customize supported characters 354 355 It is possible to customize the characters allowed in symbols and digits. 356 The `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 364 The exact signature and implementation of these functions can be looked up in 365 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). 366 367 For example, the phone character <code>☎</code> is not supported by default. It can be enabled 368 by replacing the `isAlpha` function: 369 370 ```js 371 const isAlphaOriginal = math.parse.isAlpha 372 math.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 377 const result = math.evaluate('\u260Efoo', {'\u260Efoo': 42}) // returns 42 378 console.log(result) 379 ```