mirror of
https://github.com/josdejong/mathjs.git
synced 2026-01-25 15:07:57 +00:00
Merge remote-tracking branch 'origin/develop' into develop
This commit is contained in:
commit
4a119a3465
@ -114,10 +114,13 @@ Where :
|
||||
|
||||
- `args` is an Array with nodes of the parsed arguments.
|
||||
- `math` is the math namespace against which the expression was compiled.
|
||||
- `scope` is a `Map` containing the variables defined in the scope passed
|
||||
via `evaluate(scope)`. In case of using a custom defined function like
|
||||
`f(x) = rawFunction(x) ^ 2`, the scope passed to `rawFunction` also contains
|
||||
the current value of parameter `x`.
|
||||
- `scope` is a [`Map`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map)
|
||||
interface containing the variables defined in the scope
|
||||
passed via `evaluate(scope)`. The passed scope is always a `Map` interface,
|
||||
and normally a `PartitionedMap` is used to separate local function variables
|
||||
like `x` in a custom defined function `f(x) = rawFunction(x) ^ 2` from the
|
||||
scope variables. Note that a `PartitionedMap` can recursively link to another
|
||||
`PartitionedMap`.
|
||||
|
||||
Raw functions must be imported in the `math` namespace, as they need to be
|
||||
processed at compile time. They are not supported when passed via a scope
|
||||
|
||||
@ -20,7 +20,7 @@ In this case, the expression `sqrt(2 + x)` is parsed as:
|
||||
ConstantNode 2 x SymbolNode
|
||||
```
|
||||
|
||||
Alternatively, this expression tree can be build by manually creating nodes:
|
||||
Alternatively, this expression tree can be built by manually creating nodes:
|
||||
|
||||
```js
|
||||
const node1 = new math.ConstantNode(2)
|
||||
|
||||
@ -22,9 +22,19 @@ math.evaluate([expr1, expr2, expr3, ...], scope)
|
||||
|
||||
Function `evaluate` accepts a single expression or an array with
|
||||
expressions as the first argument and has an optional second argument
|
||||
containing a scope with variables and functions. The scope can be a regular
|
||||
JavaScript Object, or Map. The scope will be used to resolve symbols, and to write
|
||||
assigned variables or function.
|
||||
containing a `scope` with variables and functions. The scope can be a regular
|
||||
JavaScript `Map` (recommended), a plain JavaScript `object`, or any custom
|
||||
class that implements the `Map` interface with methods `get`, `set`, `keys`
|
||||
and `has`. The scope will be used to resolve symbols, and to write assigned
|
||||
variables and functions.
|
||||
|
||||
When an `Object` is used as scope, mathjs will internally wrap it in an
|
||||
`ObjectWrappingMap` interface since the internal functions can only use a `Map`
|
||||
interface. In case of custom defined functions like `f(x) = x^2`, the scope
|
||||
will be wrapped in a `PartitionedMap`, which reads and writes the function
|
||||
variables (like `x` in this example) from a temporary map, and reads and writes
|
||||
other variables from the original scope. The original scope is never copied, it
|
||||
is only wrapped around when needed.
|
||||
|
||||
The following code demonstrates how to evaluate expressions.
|
||||
|
||||
|
||||
@ -2,8 +2,8 @@ import { all, create } from '../../lib/esm/index.js'
|
||||
|
||||
const math = create(all)
|
||||
|
||||
// The expression evaluator accepts an optional scope object.
|
||||
// This is the symbol table for variable defintions and function declations.
|
||||
// The expression evaluator accepts an optional scope Map or object that can
|
||||
// be used to keep additional variables and functions.
|
||||
|
||||
// Scope can be a bare object.
|
||||
function withObjectScope () {
|
||||
@ -28,11 +28,11 @@ function withMapScope (scope, name) {
|
||||
math.evaluate('area(length, width) = length * width * scalar', scope)
|
||||
math.evaluate('A = area(x, y)', scope)
|
||||
|
||||
console.log(`Map-like scope (${name}):`, scope.localScope)
|
||||
console.log(`Map-like scope (${name}):`, scope)
|
||||
}
|
||||
|
||||
// This is a minimal set of functions to look like a Map.
|
||||
class MapScope {
|
||||
class CustomMap {
|
||||
constructor () {
|
||||
this.localScope = new Map()
|
||||
}
|
||||
@ -61,7 +61,7 @@ class MapScope {
|
||||
* used in mathjs.
|
||||
*
|
||||
*/
|
||||
class AdvancedMapScope extends MapScope {
|
||||
class AdvancedCustomMap extends CustomMap {
|
||||
constructor (parent) {
|
||||
super()
|
||||
this.parentScope = parent
|
||||
@ -91,25 +91,19 @@ class AdvancedMapScope extends MapScope {
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
// Use a plain JavaScript object
|
||||
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')
|
||||
|
||||
// use a Map (recommended)
|
||||
withMapScope(new Map(), 'Map example')
|
||||
|
||||
// Use a custom Map implementation
|
||||
withMapScope(new CustomMap(), 'CustomMap example')
|
||||
|
||||
// Use a more advanced custom Map implementation
|
||||
withMapScope(new AdvancedCustomMap(), 'AdvancedCustomMap example')
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
import typedFunction from 'typed-function'
|
||||
import { deepFlatten, isLegacyFactory } from '../utils/object.js'
|
||||
import * as emitter from './../utils/emitter.js'
|
||||
import { importFactory } from './function/import.js'
|
||||
import { configFactory } from './function/config.js'
|
||||
import { ArgumentsError } from '../error/ArgumentsError.js'
|
||||
import { DimensionError } from '../error/DimensionError.js'
|
||||
import { IndexError } from '../error/IndexError.js'
|
||||
import { factory, isFactory } from '../utils/factory.js'
|
||||
import {
|
||||
isAccessorNode,
|
||||
isArray,
|
||||
isArrayNode,
|
||||
isAssignmentNode,
|
||||
isBigInt,
|
||||
isBigNumber,
|
||||
isBlockNode,
|
||||
isBoolean,
|
||||
@ -26,30 +26,33 @@ import {
|
||||
isHelp,
|
||||
isIndex,
|
||||
isIndexNode,
|
||||
isMap,
|
||||
isMatrix,
|
||||
isNode,
|
||||
isNull,
|
||||
isNumber,
|
||||
isObject,
|
||||
isObjectNode,
|
||||
isObjectWrappingMap,
|
||||
isOperatorNode,
|
||||
isParenthesisNode,
|
||||
isPartitionedMap,
|
||||
isRange,
|
||||
isRangeNode,
|
||||
isRelationalNode,
|
||||
isRegExp,
|
||||
isRelationalNode,
|
||||
isResultSet,
|
||||
isSparseMatrix,
|
||||
isString,
|
||||
isSymbolNode,
|
||||
isUndefined,
|
||||
isUnit,
|
||||
isBigInt
|
||||
isUnit
|
||||
} from '../utils/is.js'
|
||||
import { ArgumentsError } from '../error/ArgumentsError.js'
|
||||
import { DimensionError } from '../error/DimensionError.js'
|
||||
import { IndexError } from '../error/IndexError.js'
|
||||
import { deepFlatten, isLegacyFactory } from '../utils/object.js'
|
||||
import * as emitter from './../utils/emitter.js'
|
||||
import { DEFAULT_CONFIG } from './config.js'
|
||||
import { configFactory } from './function/config.js'
|
||||
import { importFactory } from './function/import.js'
|
||||
|
||||
/**
|
||||
* Create a mathjs instance from given factory functions and optionally config
|
||||
@ -126,6 +129,9 @@ export function create (factories, config) {
|
||||
isDate,
|
||||
isRegExp,
|
||||
isObject,
|
||||
isMap,
|
||||
isPartitionedMap,
|
||||
isObjectWrappingMap,
|
||||
isNull,
|
||||
isUndefined,
|
||||
|
||||
|
||||
@ -36,11 +36,14 @@
|
||||
* @returns {function} The created typed-function.
|
||||
*/
|
||||
|
||||
import typedFunction from 'typed-function'
|
||||
import { factory } from '../../utils/factory.js'
|
||||
import {
|
||||
isAccessorNode,
|
||||
isArray,
|
||||
isArrayNode,
|
||||
isAssignmentNode,
|
||||
isBigInt,
|
||||
isBigNumber,
|
||||
isBlockNode,
|
||||
isBoolean,
|
||||
@ -58,6 +61,7 @@ import {
|
||||
isHelp,
|
||||
isIndex,
|
||||
isIndexNode,
|
||||
isMap,
|
||||
isMatrix,
|
||||
isNode,
|
||||
isNull,
|
||||
@ -68,19 +72,16 @@ import {
|
||||
isParenthesisNode,
|
||||
isRange,
|
||||
isRangeNode,
|
||||
isRelationalNode,
|
||||
isRegExp,
|
||||
isRelationalNode,
|
||||
isResultSet,
|
||||
isSparseMatrix,
|
||||
isString,
|
||||
isSymbolNode,
|
||||
isUndefined,
|
||||
isUnit, isBigInt
|
||||
isUnit
|
||||
} from '../../utils/is.js'
|
||||
import typedFunction from 'typed-function'
|
||||
import { digits } from '../../utils/number.js'
|
||||
import { factory } from '../../utils/factory.js'
|
||||
import { isMap } from '../../utils/map.js'
|
||||
|
||||
// returns a new instance of typed-function
|
||||
let _createTyped = function () {
|
||||
|
||||
@ -29,6 +29,9 @@ export {
|
||||
isString,
|
||||
isUndefined,
|
||||
isObject,
|
||||
isMap,
|
||||
isPartitionedMap,
|
||||
isObjectWrappingMap,
|
||||
isObjectNode,
|
||||
isOperatorNode,
|
||||
isParenthesisNode,
|
||||
|
||||
@ -92,7 +92,7 @@ export const createFunctionNode = /* #__PURE__ */ factory(name, dependencies, ({
|
||||
* invoke a list with arguments on a node
|
||||
* @param {./Node | string} fn
|
||||
* Item resolving to a function on which to invoke
|
||||
* the arguments, typically a SymboNode or AccessorNode
|
||||
* the arguments, typically a SymbolNode or AccessorNode
|
||||
* @param {./Node[]} args
|
||||
*/
|
||||
constructor (fn, args) {
|
||||
|
||||
@ -12,6 +12,8 @@
|
||||
// for security reasons, so these functions are not exposed in the expression
|
||||
// parser.
|
||||
|
||||
import { ObjectWrappingMap } from './map.js'
|
||||
|
||||
export function isNumber (x) {
|
||||
return typeof x === 'number'
|
||||
}
|
||||
@ -125,6 +127,38 @@ export function isObject (x) {
|
||||
!isFraction(x))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the passed object appears to be a Map (i.e. duck typing).
|
||||
*
|
||||
* Methods looked for are `get`, `set`, `keys` and `has`.
|
||||
*
|
||||
* @param {Map | object} object
|
||||
* @returns
|
||||
*/
|
||||
export function isMap (object) {
|
||||
// We can use the fast instanceof, or a slower duck typing check.
|
||||
// The duck typing method needs to cover enough methods to not be confused with DenseMatrix.
|
||||
if (!object) {
|
||||
return false
|
||||
}
|
||||
return object instanceof Map ||
|
||||
object instanceof ObjectWrappingMap ||
|
||||
(
|
||||
typeof object.set === 'function' &&
|
||||
typeof object.get === 'function' &&
|
||||
typeof object.keys === 'function' &&
|
||||
typeof object.has === 'function'
|
||||
)
|
||||
}
|
||||
|
||||
export function isPartitionedMap (object) {
|
||||
return isMap(object) && isMap(object.a) && isMap(object.b)
|
||||
}
|
||||
|
||||
export function isObjectWrappingMap (object) {
|
||||
return isMap(object) && isObject(object.wrappedObject)
|
||||
}
|
||||
|
||||
export function isNull (x) {
|
||||
return x === null
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { setSafeProperty, hasSafeProperty, getSafeProperty } from './customs.js'
|
||||
import { isObject } from './is.js'
|
||||
import { getSafeProperty, hasSafeProperty, setSafeProperty } from './customs.js'
|
||||
import { isMap, isObject } from './is.js'
|
||||
|
||||
/**
|
||||
* A map facade on a bare object.
|
||||
@ -202,30 +202,6 @@ export function toObject (map) {
|
||||
return object
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if the passed object appears to be a Map (i.e. duck typing).
|
||||
*
|
||||
* Methods looked for are `get`, `set`, `keys` and `has`.
|
||||
*
|
||||
* @param {Map | object} object
|
||||
* @returns
|
||||
*/
|
||||
export function isMap (object) {
|
||||
// We can use the fast instanceof, or a slower duck typing check.
|
||||
// The duck typing method needs to cover enough methods to not be confused with DenseMatrix.
|
||||
if (!object) {
|
||||
return false
|
||||
}
|
||||
return object instanceof Map ||
|
||||
object instanceof ObjectWrappingMap ||
|
||||
(
|
||||
typeof object.set === 'function' &&
|
||||
typeof object.get === 'function' &&
|
||||
typeof object.keys === 'function' &&
|
||||
typeof object.has === 'function'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Copies the contents of key-value pairs from each `objects` in to `map`.
|
||||
*
|
||||
|
||||
@ -181,6 +181,9 @@ const knownUndocumented = new Set([
|
||||
'isDate',
|
||||
'isRegExp',
|
||||
'isObject',
|
||||
'isMap',
|
||||
'isPartitionedMap',
|
||||
'isObjectWrappingMap',
|
||||
'isNull',
|
||||
'isUndefined',
|
||||
'isAccessorNode',
|
||||
|
||||
@ -2293,6 +2293,9 @@ Factory Test
|
||||
math.isDate,
|
||||
math.isRegExp,
|
||||
math.isObject,
|
||||
math.isMap,
|
||||
math.isPartitionedMap,
|
||||
math.isObjectWrappingMap,
|
||||
math.isNull,
|
||||
math.isUndefined,
|
||||
math.isAccessorNode,
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import assert from 'assert'
|
||||
import math from '../../../src/defaultInstance.js'
|
||||
import Decimal from 'decimal.js'
|
||||
import { ObjectWrappingMap, PartitionedMap } from '../../../src/utils/map.js'
|
||||
const math2 = math.create()
|
||||
|
||||
describe('typed', function () {
|
||||
@ -177,6 +178,37 @@ describe('typed', function () {
|
||||
assert.strictEqual(math.isNull(), false)
|
||||
})
|
||||
|
||||
it('should test whether a value is an object', function () {
|
||||
assert.strictEqual(math.isObject({}), true)
|
||||
assert.strictEqual(math.isObject({ a: 2 }), true)
|
||||
assert.strictEqual(math.isObject(Object.create({})), true)
|
||||
assert.strictEqual(math.isObject(null), false)
|
||||
assert.strictEqual(math.isObject([]), false)
|
||||
assert.strictEqual(math.isObject(), false)
|
||||
assert.strictEqual(math.isObject(undefined), false)
|
||||
})
|
||||
|
||||
it('should test whether a value is a Map', function () {
|
||||
assert.strictEqual(math.isMap({}), false)
|
||||
assert.strictEqual(math.isMap(new Map()), true)
|
||||
assert.strictEqual(math.isMap(new ObjectWrappingMap({})), true)
|
||||
assert.strictEqual(math.isMap(new PartitionedMap(new Map(), new Map(), new Set(['x']))), true)
|
||||
})
|
||||
|
||||
it('should test whether a value is a PartitionedMap', function () {
|
||||
assert.strictEqual(math.isPartitionedMap({}), false)
|
||||
assert.strictEqual(math.isPartitionedMap(new Map()), false)
|
||||
assert.strictEqual(math.isPartitionedMap(new ObjectWrappingMap({})), false)
|
||||
assert.strictEqual(math.isPartitionedMap(new PartitionedMap(new Map(), new Map(), new Set(['x']))), true)
|
||||
})
|
||||
|
||||
it('should test whether a value is an ObjectWrappingMap', function () {
|
||||
assert.strictEqual(math.isObjectWrappingMap({}), false)
|
||||
assert.strictEqual(math.isObjectWrappingMap(new Map()), false)
|
||||
assert.strictEqual(math.isObjectWrappingMap(new ObjectWrappingMap({})), true)
|
||||
assert.strictEqual(math.isObjectWrappingMap(new PartitionedMap(new Map(), new Map(), new Set(['x']))), false)
|
||||
})
|
||||
|
||||
it('should test whether a value is undefined', function () {
|
||||
assert.strictEqual(math.isUndefined(undefined), true)
|
||||
assert.strictEqual(math.isUndefined(math.matrix()), false)
|
||||
@ -201,7 +233,7 @@ describe('typed', function () {
|
||||
assert.strictEqual(math.isConstantNode(), false)
|
||||
})
|
||||
|
||||
it('should test whether a value is a SymolNode', function () {
|
||||
it('should test whether a value is a SymbolNode', function () {
|
||||
assert.strictEqual(math.isSymbolNode(new math.SymbolNode('')), true)
|
||||
assert.strictEqual(math.isSymbolNode(new math2.SymbolNode('')), true)
|
||||
assert.strictEqual(math.isSymbolNode({ isSymbolNode: true }), false)
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
// test parse
|
||||
import assert from 'assert'
|
||||
|
||||
import { approxEqual, approxDeepEqual } from '../../../tools/approx.js'
|
||||
import math from '../../../src/defaultInstance.js'
|
||||
import { isMap, isObjectWrappingMap, isPartitionedMap } from '../../../src/utils/is.js'
|
||||
import { PartitionedMap } from '../../../src/utils/map.js'
|
||||
|
||||
import { approxDeepEqual, approxEqual } from '../../../tools/approx.js'
|
||||
|
||||
const parse = math.parse
|
||||
const ConditionalNode = math.ConditionalNode
|
||||
@ -1599,6 +1601,31 @@ describe('parse', function () {
|
||||
assert.strictEqual(scope.a, false)
|
||||
})
|
||||
|
||||
it('should always pass a Map as scope to a rawArgs function', function () {
|
||||
const myMath = math.create()
|
||||
function myFunction (args, _math, _scope) {
|
||||
return {
|
||||
type: isObjectWrappingMap(_scope)
|
||||
? 'ObjectWrappingMap'
|
||||
: isPartitionedMap(_scope)
|
||||
? 'PartitionedMap'
|
||||
: isMap(_scope)
|
||||
? 'Map'
|
||||
: 'unknown',
|
||||
scope: _scope
|
||||
}
|
||||
}
|
||||
myFunction.rawArgs = true
|
||||
myMath.import({ myFunction })
|
||||
|
||||
assert.strictEqual(myMath.parse('myFunction()').evaluate({}).type, 'PartitionedMap')
|
||||
const map = new Map()
|
||||
assert.strictEqual(myMath.parse('myFunction()').evaluate(map).type, 'PartitionedMap')
|
||||
assert.strictEqual(myMath.parse('myFunction()').evaluate(map).scope.a, map)
|
||||
assert.strictEqual(myMath.parse('myFunction()').evaluate(new PartitionedMap(new Map(), new Map(), new Set('x'))).type, 'PartitionedMap')
|
||||
assert.deepStrictEqual(myMath.parse('f(x) = myFunction(x); f(2)').evaluate(new Map()).entries[0].type, 'PartitionedMap')
|
||||
})
|
||||
|
||||
it('should parse logical xor', function () {
|
||||
assert.strictEqual(parseAndEval('2 xor 6'), false)
|
||||
assert.strictEqual(parseAndEval('2 xor 0'), true)
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import assert from 'assert'
|
||||
import { isMap, ObjectWrappingMap, toObject, createMap, assign, PartitionedMap } from '../../../src/utils/map.js'
|
||||
import { isMap } from '../../../src/utils/is.js'
|
||||
import { assign, createMap, ObjectWrappingMap, PartitionedMap, toObject } from '../../../src/utils/map.js'
|
||||
|
||||
describe('maps', function () {
|
||||
it('should provide isMap, a function to tell maps from non-maps', function () {
|
||||
|
||||
17
types/index.d.ts
vendored
17
types/index.d.ts
vendored
@ -3381,6 +3381,14 @@ export interface MathJsInstance extends MathJsFactory {
|
||||
|
||||
isObject(x: unknown): boolean
|
||||
|
||||
isMap<T, U>(x: unknown): x is Map<T, U>
|
||||
|
||||
isPartitionedMap<T, U>(x: unknown): x is PartitionedMap<T, U>
|
||||
|
||||
isObjectWrappingMap<T extends string | number | symbol, U>(
|
||||
x: unknown
|
||||
): x is ObjectWrappingMap<T, U>
|
||||
|
||||
isNull(x: unknown): x is null
|
||||
|
||||
isUndefined(x: unknown): x is undefined
|
||||
@ -4157,6 +4165,15 @@ export interface UnitDefinition {
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-interface
|
||||
export interface Index {}
|
||||
|
||||
export interface PartitionedMap<T, U> {
|
||||
a: Map<T, U>
|
||||
b: Map<T, U>
|
||||
}
|
||||
|
||||
export interface ObjectWrappingMap<T extends string | number | symbol, U> {
|
||||
wrappedObject: Record<T, U>
|
||||
}
|
||||
|
||||
export interface EvalFunction {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
evaluate(scope?: any): any
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user