custom_argument_parsing.js (3031B)
1 /** 2 * The expression parser of math.js has support for letting functions 3 * parse and evaluate arguments themselves, instead of calling them with 4 * evaluated arguments. 5 * 6 * By adding a property `raw` with value true to a function, the function 7 * will be invoked with unevaluated arguments, allowing the function 8 * to process the arguments in a customized way. 9 */ 10 const { create, all } = require('../..') 11 const math = create(all) 12 13 /** 14 * Calculate the numeric integration of a function 15 * @param {Function} f 16 * @param {number} start 17 * @param {number} end 18 * @param {number} [step=0.01] 19 */ 20 function integrate (f, start, end, step) { 21 let total = 0 22 step = step || 0.01 23 for (let x = start; x < end; x += step) { 24 total += f(x + step / 2) * step 25 } 26 return total 27 } 28 29 /** 30 * A transformation for the integrate function. This transformation will be 31 * invoked when the function is used via the expression parser of math.js. 32 * 33 * Syntax: 34 * 35 * integrate(integrand, variable, start, end) 36 * integrate(integrand, variable, start, end, step) 37 * 38 * Usage: 39 * 40 * math.evaluate('integrate(2*x, x, 0, 2)') 41 * math.evaluate('integrate(2*x, x, 0, 2, 0.01)') 42 * 43 * @param {Array.<math.Node>} args 44 * Expects the following arguments: [f, x, start, end, step] 45 * @param {Object} math 46 * @param {Object} [scope] 47 */ 48 integrate.transform = function (args, math, scope) { 49 // determine the variable name 50 if (!args[1].isSymbolNode) { 51 throw new Error('Second argument must be a symbol') 52 } 53 const variable = args[1].name 54 55 // evaluate start, end, and step 56 const start = args[2].compile().evaluate(scope) 57 const end = args[3].compile().evaluate(scope) 58 const step = args[4] && args[4].compile().evaluate(scope) // step is optional 59 60 // create a new scope, linked to the provided scope. We use this new scope 61 // to apply the variable. 62 const fnScope = Object.create(scope) 63 64 // construct a function which evaluates the first parameter f after applying 65 // a value for parameter x. 66 const fnCode = args[0].compile() 67 const f = function (x) { 68 fnScope[variable] = x 69 return fnCode.evaluate(fnScope) 70 } 71 72 // execute the integration 73 return integrate(f, start, end, step) 74 } 75 76 // mark the transform function with a "rawArgs" property, so it will be called 77 // with uncompiled, unevaluated arguments. 78 integrate.transform.rawArgs = true 79 80 // import the function into math.js. Raw functions must be imported in the 81 // math namespace, they can't be used via `evaluate(scope)`. 82 math.import({ 83 integrate: integrate 84 }) 85 86 // use the function in JavaScript 87 function f (x) { 88 return math.pow(x, 0.5) 89 } 90 console.log(math.integrate(f, 0, 1)) // outputs 0.6667254718034714 91 92 // use the function via the expression parser 93 console.log(math.evaluate('integrate(x^0.5, x, 0, 1)')) // outputs 0.6667254718034714 94 95 // use the function via the expression parser (2) 96 const scope = {} 97 math.evaluate('f(x) = 2 * x', scope) 98 console.log(math.evaluate('integrate(f(x), x, 0, 2)', scope)) // outputs 4.000000000000003