mathjs/examples/advanced/custom_scope_objects.js
jhugman e80995d52d
Add support for scopes with get and set methods (#2166)
* Add support for scopes with get and set methods

* Fix build for node v12

* Fixup cli and parser tests

* Add tests for simplify and evaluate

* Add example for a custom scope object

* Function calls need child scopes

* Transitionary step: Separate Safe and Scope Property calls

* Renamed identifiers in FunctionNode

* Evaluate with ObjectScopeWrapper

* Simplify tests passing

* Assume all scopes are map-like. Except parser

* Remove isMapLike check in customs.*SafeProperty() methods

* Change MapLike to Map

* Move keywords from an Object to a Set

* Move ScopeProperty functions in to scope.js

* Removed deprecation warning

* Rename scope.js to map.js

* Rename ScopeProperty to MapProperty

* Add tests and docs for map.js

* Put back the micro-optimization of function calls

* Use Map in the parser

* Called scope methods directly in cli.js

* Coercing of scope into a Map is done in Node, not evaluate

* Move createSubScope to its own file

* Fixup following self-review

* Add scope docs

* Final self-review changes

* Address reviewer comments

* Remove MapProperty witness marks

* Converted broken benchmark possibly lost in a rebase

* Use bare map as scope in benchmark

Co-authored-by: Jos de Jong <wjosdejong@gmail.com>
2021-05-16 13:33:01 +02:00

116 lines
2.7 KiB
JavaScript

const { create, all } = require('../..')
const math = create(all)
// The expression evaluator accepts an optional scope object.
// This is the symbol table for variable defintions and function declations.
// Scope can be a bare object.
function withObjectScope () {
const scope = { x: 3 }
math.evaluate('x', scope) // 1
math.evaluate('y = 2 x', scope)
math.evaluate('scalar = 1', scope)
math.evaluate('area(length, width) = length * width * scalar', scope)
math.evaluate('A = area(x, y)', scope)
console.log('Object scope:', scope)
}
// Where flexibility is important, scope can duck type appear to be a Map.
function withMapScope (scope, name) {
scope.set('x', 3)
math.evaluate('x', scope) // 1
math.evaluate('y = 2 x', scope)
math.evaluate('scalar = 1', scope)
math.evaluate('area(length, width) = length * width * scalar', scope)
math.evaluate('A = area(x, y)', scope)
console.log(`Map-like scope (${name}):`, scope.localScope)
}
// This is a minimal set of functions to look like a Map.
class MapScope {
constructor () {
this.localScope = new Map()
}
get (key) {
// Remember to sanitize your inputs, or use
// a datastructure that isn't a footgun.
return this.localScope.get(key)
}
set (key, value) {
return this.localScope.set(key, value)
}
has (key) {
return this.localScope.has(key)
}
keys () {
return this.localScope.keys()
}
}
/*
* This is a more fully featured example, with all methods
* used in mathjs.
*
*/
class AdvancedMapScope extends MapScope {
constructor (parent) {
super()
this.parentScope = parent
}
get (key) {
return this.localScope.get(key) ?? this.parentScope?.get(key)
}
has (key) {
return this.localScope.has(key) ?? this.parentScope?.get(key)
}
keys () {
if (this.parentScope) {
return new Set([...this.localScope.keys(), ...this.parentScope.keys()])
} else {
return this.localScope.keys()
}
}
delete () {
return this.localScope.delete()
}
clear () {
return this.localScope.clear()
}
/**
* Creates a child scope from this one. This is used in function calls.
*
* @returns a new Map scope that has access to the symbols in the parent, but
* cannot overwrite them.
*/
createSubScope () {
return new AdvancedMapScope(this)
}
toString () {
return this.localScope.toString()
}
}
withObjectScope()
// Where safety is important, scope can also be a Map
withMapScope(new Map(), 'simple Map')
// Where flexibility is important, scope can duck type appear to be a Map.
withMapScope(new MapScope(), 'MapScope example')
// Extra methods allow even finer grain control.
withMapScope(new AdvancedMapScope(), 'AdvancedScope example')