diff --git a/HISTORY.md b/HISTORY.md index bb6acf9f58..63454dd643 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -5,6 +5,7 @@ - Implemented units `kilogramforce` (`kgf`). Thanks @rnd-debug. - Fix #2026: Implement a new option `fractionsLimit` for function `simplify`, defaulting to `Infinity`. +- Fix #660: Implement a way to customize handling undefined symbols. # 2020-11-09, version 8.0.1 diff --git a/docs/expressions/customization.md b/docs/expressions/customization.md index 20895b2f8e..75391336e7 100644 --- a/docs/expressions/customization.md +++ b/docs/expressions/customization.md @@ -377,3 +377,22 @@ math.parse.isAlpha = function (c, cPrev, cNext) { const result = math.evaluate('\u260Efoo', {'\u260Efoo': 42}) // returns 42 console.log(result) ``` + + +## Customize handling of undefined symbols + +When evaluating an expression which contains undefined symbols, an error will be thrown: + +```js +math.evaluate('foo + 42') // throws "Error: Undefined symbol foo" +``` + +In some cases, you may want to resolve the undefined symbol instead of throwing an error. You could for example return `0` instead, or resolve the undefined symbol by looking it up somewhere. This is possible by overriding the method `handleUndefinedSymbol` on the prototype of `SymbolNode`: + +```js +math.SymbolNode.prototype.handleUndefinedSymbol = function (name) { + return 0 +} + +math.evaluate('foo + 42') // returns 42 +``` diff --git a/src/expression/node/SymbolNode.js b/src/expression/node/SymbolNode.js index b3cc888641..026793467f 100644 --- a/src/expression/node/SymbolNode.js +++ b/src/expression/node/SymbolNode.js @@ -74,13 +74,14 @@ export const createSymbolNode = /* #__PURE__ */ factory(name, dependencies, ({ m } } else { const isUnit = isValuelessUnit(name) + const handleUndefinedSymbol = SymbolNode.prototype.handleUndefinedSymbol return function (scope, args, context) { return name in scope ? getSafeProperty(scope, name) : isUnit ? new Unit(null, name) - : undef(name) + : handleUndefinedSymbol(name) } } } @@ -107,7 +108,7 @@ export const createSymbolNode = /* #__PURE__ */ factory(name, dependencies, ({ m * Throws an error 'Undefined symbol {name}' * @param {string} name */ - function undef (name) { + SymbolNode.prototype.handleUndefinedSymbol = function (name) { throw new Error('Undefined symbol ' + name) } diff --git a/test/unit-tests/expression/node/SymbolNode.test.js b/test/unit-tests/expression/node/SymbolNode.test.js index cebea67c73..1237695257 100644 --- a/test/unit-tests/expression/node/SymbolNode.test.js +++ b/test/unit-tests/expression/node/SymbolNode.test.js @@ -50,6 +50,18 @@ describe('SymbolNode', function () { assert.strictEqual(expr2.evaluate(scope2), math.sqrt) }) + it('should allow customizing the behavior for undefined symbols', function () { + const original = SymbolNode.prototype.handleUndefinedSymbol + + const foo = new SymbolNode('foo') + assert.throws(() => foo.evaluate(), /Undefined symbol foo/) + + SymbolNode.prototype.handleUndefinedSymbol = () => 0 + assert.strictEqual(foo.evaluate(), 0) + + SymbolNode.prototype.handleUndefinedSymbol = original + }) + it('should filter a SymbolNode', function () { const n = new SymbolNode('x') assert.deepStrictEqual(n.filter(function (node) { return node instanceof SymbolNode }), [n])