simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

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>&#9742;</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 ```