@math.gl/expressions 4.2.0-alpha.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +140 -0
- package/README.md +12 -0
- package/dist/dggs.cjs +54 -0
- package/dist/dggs.cjs.map +6 -0
- package/dist/dggs.d.ts +18 -0
- package/dist/dggs.d.ts.map +1 -0
- package/dist/dggs.js +42 -0
- package/dist/dggs.js.map +1 -0
- package/dist/expression-eval.d.ts +103 -0
- package/dist/expression-eval.d.ts.map +1 -0
- package/dist/expression-eval.js +298 -0
- package/dist/expression-eval.js.map +1 -0
- package/dist/function-libraries.d.ts +75 -0
- package/dist/function-libraries.d.ts.map +1 -0
- package/dist/function-libraries.js +98 -0
- package/dist/function-libraries.js.map +1 -0
- package/dist/function-registry.d.ts +93 -0
- package/dist/function-registry.d.ts.map +1 -0
- package/dist/function-registry.js +131 -0
- package/dist/function-registry.js.map +1 -0
- package/dist/get.d.ts +9 -0
- package/dist/get.d.ts.map +1 -0
- package/dist/get.js +32 -0
- package/dist/get.js.map +1 -0
- package/dist/index.cjs +473 -0
- package/dist/index.cjs.map +6 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/parse-expression-string.d.ts +21 -0
- package/dist/parse-expression-string.d.ts.map +1 -0
- package/dist/parse-expression-string.js +61 -0
- package/dist/parse-expression-string.js.map +1 -0
- package/package.json +50 -0
- package/src/dggs.ts +62 -0
- package/src/expression-eval.ts +426 -0
- package/src/function-libraries.ts +168 -0
- package/src/function-registry.ts +163 -0
- package/src/get.ts +37 -0
- package/src/index.ts +26 -0
- package/src/parse-expression-string.ts +80 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export type { ExpressionEvaluationOptions, ExpressionFunction, ExpressionFunctionLibrary } from "./function-libraries.js";
|
|
2
|
+
export { ExpressionFunctionRegistry, type FunctionRegistrationOptions } from "./function-registry.js";
|
|
3
|
+
export type { BinaryOperator, Expression, ExpressionContext, UnaryOperator } from "./expression-eval.js";
|
|
4
|
+
export { addBinaryOp, addUnaryOp, compile, compileAsync, eval, evalAsync, parse } from "./expression-eval.js";
|
|
5
|
+
export { BASIC_MATH_FUNCTION_LIBRARY, GEOSPATIAL_FUNCTION_LIBRARY, mergeFunctionLibraries } from "./function-libraries.js";
|
|
6
|
+
export { parseExpressionString, type AccessorFunction } from "./parse-expression-string.js";
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,YAAY,EACV,2BAA2B,EAC3B,kBAAkB,EAClB,yBAAyB,EAC1B,gCAA6B;AAC9B,OAAO,EAAC,0BAA0B,EAAE,KAAK,2BAA2B,EAAC,+BAA4B;AACjG,YAAY,EAAC,cAAc,EAAE,UAAU,EAAE,iBAAiB,EAAE,aAAa,EAAC,6BAA0B;AACpG,OAAO,EACL,WAAW,EACX,UAAU,EACV,OAAO,EACP,YAAY,EACZ,IAAI,EACJ,SAAS,EACT,KAAK,EACN,6BAA0B;AAC3B,OAAO,EACL,2BAA2B,EAC3B,2BAA2B,EAC3B,sBAAsB,EACvB,gCAA6B;AAC9B,OAAO,EAAC,qBAAqB,EAAE,KAAK,gBAAgB,EAAC,qCAAkC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
export { ExpressionFunctionRegistry } from "./function-registry.js";
|
|
5
|
+
export { addBinaryOp, addUnaryOp, compile, compileAsync, eval, evalAsync, parse } from "./expression-eval.js";
|
|
6
|
+
export { BASIC_MATH_FUNCTION_LIBRARY, GEOSPATIAL_FUNCTION_LIBRARY, mergeFunctionLibraries } from "./function-libraries.js";
|
|
7
|
+
export { parseExpressionString } from "./parse-expression-string.js";
|
|
8
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,UAAU;AACV,+BAA+B;AAC/B,oCAAoC;AAOpC,OAAO,EAAC,0BAA0B,EAAmC,+BAA4B;AAEjG,OAAO,EACL,WAAW,EACX,UAAU,EACV,OAAO,EACP,YAAY,EACZ,IAAI,EACJ,SAAS,EACT,KAAK,EACN,6BAA0B;AAC3B,OAAO,EACL,2BAA2B,EAC3B,2BAA2B,EAC3B,sBAAsB,EACvB,gCAA6B;AAC9B,OAAO,EAAC,qBAAqB,EAAwB,qCAAkC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An accessor compiled from a JSON-style expression string.
|
|
3
|
+
*
|
|
4
|
+
* @param row - Data object against which the expression is evaluated.
|
|
5
|
+
* @returns The value produced by the expression.
|
|
6
|
+
*/
|
|
7
|
+
export type AccessorFunction = (row: Record<string, unknown>) => unknown;
|
|
8
|
+
/**
|
|
9
|
+
* Compiles a JSON-style expression string into an accessor function.
|
|
10
|
+
*
|
|
11
|
+
* @param propValue - Accessor expression to compile.
|
|
12
|
+
* @returns A cached accessor function.
|
|
13
|
+
* @throws If the expression is invalid or contains a function call.
|
|
14
|
+
*
|
|
15
|
+
* @remarks
|
|
16
|
+
* `-` maps to the identity accessor and `a.b.c` maps to nested property
|
|
17
|
+
* access. Function calls are rejected so accessors cannot execute functions
|
|
18
|
+
* supplied by input data.
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseExpressionString(propValue: string): AccessorFunction;
|
|
21
|
+
//# sourceMappingURL=parse-expression-string.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-expression-string.d.ts","sourceRoot":"","sources":["../src/parse-expression-string.ts"],"names":[],"mappings":"AAQA;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;AAMzE;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,MAAM,GAAG,gBAAgB,CAazE"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
import { eval as evaluate, parse } from "./expression-eval.js";
|
|
5
|
+
import { get } from "./get.js";
|
|
6
|
+
const cachedExpressionMap = {
|
|
7
|
+
'-': object => object
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Compiles a JSON-style expression string into an accessor function.
|
|
11
|
+
*
|
|
12
|
+
* @param propValue - Accessor expression to compile.
|
|
13
|
+
* @returns A cached accessor function.
|
|
14
|
+
* @throws If the expression is invalid or contains a function call.
|
|
15
|
+
*
|
|
16
|
+
* @remarks
|
|
17
|
+
* `-` maps to the identity accessor and `a.b.c` maps to nested property
|
|
18
|
+
* access. Function calls are rejected so accessors cannot execute functions
|
|
19
|
+
* supplied by input data.
|
|
20
|
+
*/
|
|
21
|
+
export function parseExpressionString(propValue) {
|
|
22
|
+
if (propValue in cachedExpressionMap) {
|
|
23
|
+
return cachedExpressionMap[propValue];
|
|
24
|
+
}
|
|
25
|
+
const ast = parse(propValue);
|
|
26
|
+
const func = ast.type === 'Identifier'
|
|
27
|
+
? (row) => get(row, propValue)
|
|
28
|
+
: compileAst(ast);
|
|
29
|
+
cachedExpressionMap[propValue] = func;
|
|
30
|
+
return func;
|
|
31
|
+
}
|
|
32
|
+
/** Validates and compiles a parsed accessor expression. */
|
|
33
|
+
function compileAst(ast) {
|
|
34
|
+
traverse(ast, node => {
|
|
35
|
+
if (node.type === 'CallExpression') {
|
|
36
|
+
throw new Error('Function calls not allowed in expression accessors');
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
return (row) => evaluate(ast, row);
|
|
40
|
+
}
|
|
41
|
+
/** Visits each AST-like object in a parsed expression. */
|
|
42
|
+
// eslint-disable-next-line complexity
|
|
43
|
+
function traverse(node, visitor) {
|
|
44
|
+
if (Array.isArray(node)) {
|
|
45
|
+
node.forEach(element => traverse(element, visitor));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (node && typeof node === 'object') {
|
|
49
|
+
if (isNodeLike(node)) {
|
|
50
|
+
visitor(node);
|
|
51
|
+
}
|
|
52
|
+
for (const key in node) {
|
|
53
|
+
traverse(node[key], visitor);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** Tests whether an object resembles a JSEP AST node. */
|
|
58
|
+
function isNodeLike(node) {
|
|
59
|
+
return 'type' in node && typeof node.type === 'string';
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=parse-expression-string.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse-expression-string.js","sourceRoot":"","sources":["../src/parse-expression-string.ts"],"names":[],"mappings":"AAAA,UAAU;AACV,+BAA+B;AAC/B,oCAAoC;AAGpC,OAAO,EAAC,IAAI,IAAI,QAAQ,EAAE,KAAK,EAAC,6BAA0B;AAC1D,OAAO,EAAC,GAAG,EAAC,iBAAc;AAU1B,MAAM,mBAAmB,GAAqC;IAC5D,GAAG,EAAE,MAAM,CAAC,EAAE,CAAC,MAAM;CACtB,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,qBAAqB,CAAC,SAAiB;IACrD,IAAI,SAAS,IAAI,mBAAmB,EAAE,CAAC;QACrC,OAAO,mBAAmB,CAAC,SAAS,CAAC,CAAC;IACxC,CAAC;IAED,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC;IAC7B,MAAM,IAAI,GACR,GAAG,CAAC,IAAI,KAAK,YAAY;QACvB,CAAC,CAAC,CAAC,GAA4B,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC;QACvD,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;IAEtB,mBAAmB,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC;IACtC,OAAO,IAAI,CAAC;AACd,CAAC;AAED,2DAA2D;AAC3D,SAAS,UAAU,CAAC,GAAoB;IACtC,QAAQ,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE;QACnB,IAAI,IAAI,CAAC,IAAI,KAAK,gBAAgB,EAAE,CAAC;YACnC,MAAM,IAAI,KAAK,CAAC,oDAAoD,CAAC,CAAC;QACxE,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,GAA4B,EAAE,EAAE,CAAC,QAAQ,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;AAC9D,CAAC;AAED,0DAA0D;AAC1D,sCAAsC;AACtC,SAAS,QAAQ,CAAC,IAAa,EAAE,OAAuC;IACtE,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACxB,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;QACpD,OAAO;IACT,CAAC;IAED,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACrC,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,OAAO,CAAC,IAAI,CAAC,CAAC;QAChB,CAAC;QACD,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,QAAQ,CAAE,IAAgC,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;AACH,CAAC;AAED,yDAAyD;AACzD,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,MAAM,IAAI,IAAI,IAAI,OAAQ,IAAyB,CAAC,IAAI,KAAK,QAAQ,CAAC;AAC/E,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@math.gl/expressions",
|
|
3
|
+
"description": "Experimental expression parsing and evaluation utilities",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"version": "4.2.0-alpha.5",
|
|
10
|
+
"keywords": [
|
|
11
|
+
"javascript",
|
|
12
|
+
"math",
|
|
13
|
+
"expressions",
|
|
14
|
+
"parser",
|
|
15
|
+
"evaluator",
|
|
16
|
+
"jsep"
|
|
17
|
+
],
|
|
18
|
+
"repository": {
|
|
19
|
+
"type": "git",
|
|
20
|
+
"url": "https://github.com/visgl/math.gl.git"
|
|
21
|
+
},
|
|
22
|
+
"types": "dist/index.d.ts",
|
|
23
|
+
"main": "dist/index.cjs",
|
|
24
|
+
"module": "dist/index.js",
|
|
25
|
+
"exports": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"require": "./dist/index.cjs"
|
|
30
|
+
},
|
|
31
|
+
"./dggs": {
|
|
32
|
+
"types": "./dist/dggs.d.ts",
|
|
33
|
+
"import": "./dist/dggs.js",
|
|
34
|
+
"require": "./dist/dggs.cjs"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"src"
|
|
40
|
+
],
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"@math.gl/core": "4.2.0-alpha.5",
|
|
43
|
+
"@math.gl/dggs-geohash": "4.2.0-alpha.5",
|
|
44
|
+
"@math.gl/dggs-quadkey": "4.2.0-alpha.5",
|
|
45
|
+
"@math.gl/dggs-s2": "4.2.0-alpha.5",
|
|
46
|
+
"@math.gl/geospatial": "4.2.0-alpha.5",
|
|
47
|
+
"jsep": "^1.4.0"
|
|
48
|
+
},
|
|
49
|
+
"gitHead": "9c13785761867748e2edb167b5f2749dce803039"
|
|
50
|
+
}
|
package/src/dggs.ts
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getGeohashBoundary,
|
|
7
|
+
getGeohashBoundaryFlat,
|
|
8
|
+
getGeohashBounds,
|
|
9
|
+
getGeohashLngLat
|
|
10
|
+
} from '@math.gl/dggs-geohash';
|
|
11
|
+
import {
|
|
12
|
+
getQuadkeyBoundary,
|
|
13
|
+
getQuadkeyBoundaryFlat,
|
|
14
|
+
getQuadkeyLngLat,
|
|
15
|
+
quadkeyToWorldBounds
|
|
16
|
+
} from '@math.gl/dggs-quadkey';
|
|
17
|
+
import {
|
|
18
|
+
getS2BoundaryFlat,
|
|
19
|
+
getS2ChildIndex,
|
|
20
|
+
getS2IndexFromToken,
|
|
21
|
+
getS2TokenFromIndex
|
|
22
|
+
} from '@math.gl/dggs-s2';
|
|
23
|
+
import type {ExpressionFunctionLibrary} from './function-libraries';
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* GeoHash functions ready to register with an expression evaluator.
|
|
27
|
+
*/
|
|
28
|
+
export const GEOHASH_FUNCTION_LIBRARY: ExpressionFunctionLibrary = {
|
|
29
|
+
getGeohashBoundary,
|
|
30
|
+
getGeohashBoundaryFlat,
|
|
31
|
+
getGeohashBounds,
|
|
32
|
+
getGeohashLngLat
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Quadkey functions ready to register with an expression evaluator.
|
|
37
|
+
*/
|
|
38
|
+
export const QUADKEY_FUNCTION_LIBRARY: ExpressionFunctionLibrary = {
|
|
39
|
+
getQuadkeyBoundary,
|
|
40
|
+
getQuadkeyBoundaryFlat,
|
|
41
|
+
getQuadkeyLngLat,
|
|
42
|
+
quadkeyToWorldBounds
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* S2 functions ready to register with an expression evaluator.
|
|
47
|
+
*/
|
|
48
|
+
export const S2_FUNCTION_LIBRARY: ExpressionFunctionLibrary = {
|
|
49
|
+
getS2BoundaryFlat,
|
|
50
|
+
getS2ChildIndex,
|
|
51
|
+
getS2IndexFromToken,
|
|
52
|
+
getS2TokenFromIndex
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Combined GeoHash, Quadkey, and S2 function table.
|
|
57
|
+
*/
|
|
58
|
+
export const DGGS_FUNCTION_LIBRARY: ExpressionFunctionLibrary = {
|
|
59
|
+
...GEOHASH_FUNCTION_LIBRARY,
|
|
60
|
+
...QUADKEY_FUNCTION_LIBRARY,
|
|
61
|
+
...S2_FUNCTION_LIBRARY
|
|
62
|
+
};
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
// math.gl
|
|
2
|
+
// SPDX-License-Identifier: MIT
|
|
3
|
+
// Copyright (c) vis.gl contributors
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Sources:
|
|
7
|
+
* - Copyright (c) 2013 Stephen Oney, http://jsep.from.so/, MIT License
|
|
8
|
+
* - Copyright (c) 2023 Don McCurdy, https://github.com/donmccurdy/expression-eval, MIT License
|
|
9
|
+
*/
|
|
10
|
+
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-return, @typescript-eslint/restrict-plus-operands */
|
|
11
|
+
|
|
12
|
+
import jsep from 'jsep';
|
|
13
|
+
import {mergeFunctionLibraries, type ExpressionEvaluationOptions} from './function-libraries';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Named values and functions available while evaluating an expression.
|
|
17
|
+
*/
|
|
18
|
+
export type ExpressionContext = Record<string, unknown>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* A JSEP abstract syntax tree node supported by the evaluator.
|
|
22
|
+
*/
|
|
23
|
+
export type Expression =
|
|
24
|
+
| jsep.ArrayExpression
|
|
25
|
+
| jsep.BinaryExpression
|
|
26
|
+
| jsep.CallExpression
|
|
27
|
+
| jsep.ConditionalExpression
|
|
28
|
+
| jsep.Identifier
|
|
29
|
+
| jsep.Literal
|
|
30
|
+
| jsep.MemberExpression
|
|
31
|
+
| jsep.ThisExpression
|
|
32
|
+
| jsep.UnaryExpression;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Evaluator implementation for a custom unary operator.
|
|
36
|
+
*
|
|
37
|
+
* @param operand - Value produced by the operator's operand expression.
|
|
38
|
+
* @returns The operator result.
|
|
39
|
+
*/
|
|
40
|
+
export type UnaryOperator = (operand: any) => any;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Evaluator implementation for a custom binary operator.
|
|
44
|
+
*
|
|
45
|
+
* @param left - Value produced by the left operand expression.
|
|
46
|
+
* @param right - Value produced by the right operand expression.
|
|
47
|
+
* @returns The operator result.
|
|
48
|
+
*/
|
|
49
|
+
export type BinaryOperator = (left: any, right: any) => any;
|
|
50
|
+
|
|
51
|
+
type Callable = (...args: any[]) => any;
|
|
52
|
+
|
|
53
|
+
const DEFAULT_PRECEDENCE: Record<string, number> = {
|
|
54
|
+
'||': 1,
|
|
55
|
+
'&&': 2,
|
|
56
|
+
'|': 3,
|
|
57
|
+
'^': 4,
|
|
58
|
+
'&': 5,
|
|
59
|
+
'==': 6,
|
|
60
|
+
'!=': 6,
|
|
61
|
+
'===': 6,
|
|
62
|
+
'!==': 6,
|
|
63
|
+
'<': 7,
|
|
64
|
+
'>': 7,
|
|
65
|
+
'<=': 7,
|
|
66
|
+
'>=': 7,
|
|
67
|
+
'<<': 8,
|
|
68
|
+
'>>': 8,
|
|
69
|
+
'>>>': 8,
|
|
70
|
+
'+': 9,
|
|
71
|
+
'-': 9,
|
|
72
|
+
'*': 10,
|
|
73
|
+
'/': 10,
|
|
74
|
+
'%': 10
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const binops: Record<string, (...args: any[]) => any> = {
|
|
78
|
+
'||': (a: unknown, b: unknown) => a || b,
|
|
79
|
+
'&&': (a: unknown, b: unknown) => a && b,
|
|
80
|
+
'|': (a: number, b: number) => a | b,
|
|
81
|
+
'^': (a: number, b: number) => a ^ b,
|
|
82
|
+
'&': (a: number, b: number) => a & b,
|
|
83
|
+
'==': (a: unknown, b: unknown) => {
|
|
84
|
+
// biome-ignore lint/suspicious/noDoubleEquals: this operator implements JavaScript loose equality.
|
|
85
|
+
return a == b;
|
|
86
|
+
},
|
|
87
|
+
'!=': (a: unknown, b: unknown) => {
|
|
88
|
+
// biome-ignore lint/suspicious/noDoubleEquals: this operator implements JavaScript loose inequality.
|
|
89
|
+
return a != b;
|
|
90
|
+
},
|
|
91
|
+
'===': (a: unknown, b: unknown) => a === b,
|
|
92
|
+
'!==': (a: unknown, b: unknown) => a !== b,
|
|
93
|
+
'<': (a: number | string, b: number | string) => a < b,
|
|
94
|
+
'>': (a: number | string, b: number | string) => a > b,
|
|
95
|
+
'<=': (a: number | string, b: number | string) => a <= b,
|
|
96
|
+
'>=': (a: number | string, b: number | string) => a >= b,
|
|
97
|
+
'<<': (a: number, b: number) => a << b,
|
|
98
|
+
'>>': (a: number, b: number) => a >> b,
|
|
99
|
+
'>>>': (a: number, b: number) => a >>> b,
|
|
100
|
+
'+': (a: unknown, b: unknown) => {
|
|
101
|
+
// @ts-expect-error Addition intentionally supports JS coercion semantics.
|
|
102
|
+
return a + b;
|
|
103
|
+
},
|
|
104
|
+
'-': (a: number, b: number) => a - b,
|
|
105
|
+
'*': (a: number, b: number) => a * b,
|
|
106
|
+
'/': (a: number, b: number) => a / b,
|
|
107
|
+
'%': (a: number, b: number) => a % b
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const unops: Record<string, (...args: any[]) => any> = {
|
|
111
|
+
'-': (a: number) => -a,
|
|
112
|
+
'+': (a: unknown) => {
|
|
113
|
+
// eslint-disable-next-line no-implicit-coercion
|
|
114
|
+
return +a;
|
|
115
|
+
},
|
|
116
|
+
'~': (a: number) => ~a,
|
|
117
|
+
'!': (a: unknown) => !a
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
function evaluateArray(list: jsep.Expression[], context: ExpressionContext): unknown[] {
|
|
121
|
+
return list.map(value => evaluate(value, context));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** Evaluates each expression in an array concurrently. */
|
|
125
|
+
async function evaluateArrayAsync(
|
|
126
|
+
list: jsep.Expression[],
|
|
127
|
+
context: ExpressionContext
|
|
128
|
+
): Promise<unknown[]> {
|
|
129
|
+
return await Promise.all(list.map(value => evalAsync(value, context)));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Resolves a member expression and preserves its receiver for method calls. */
|
|
133
|
+
function evaluateMember(
|
|
134
|
+
node: jsep.MemberExpression,
|
|
135
|
+
context: ExpressionContext
|
|
136
|
+
): [Record<string, unknown>, unknown] {
|
|
137
|
+
const object = evaluate(node.object, context) as Record<string, unknown>;
|
|
138
|
+
const key = node.computed
|
|
139
|
+
? (evaluate(node.property, context) as string)
|
|
140
|
+
: (node.property as jsep.Identifier).name;
|
|
141
|
+
|
|
142
|
+
if (/^__proto__|prototype|constructor$/.test(key)) {
|
|
143
|
+
throw new Error(`Access to member "${key}" disallowed.`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return [object, object?.[key]];
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Asynchronously resolves a member expression and its receiver. */
|
|
150
|
+
async function evaluateMemberAsync(
|
|
151
|
+
node: jsep.MemberExpression,
|
|
152
|
+
context: ExpressionContext
|
|
153
|
+
): Promise<[Record<string, unknown>, unknown]> {
|
|
154
|
+
const object = (await evalAsync(node.object, context)) as Record<string, unknown>;
|
|
155
|
+
const key = node.computed
|
|
156
|
+
? ((await evalAsync(node.property, context)) as string)
|
|
157
|
+
: (node.property as jsep.Identifier).name;
|
|
158
|
+
|
|
159
|
+
if (/^__proto__|prototype|constructor$/.test(key)) {
|
|
160
|
+
throw new Error(`Access to member "${key}" disallowed.`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return [object, object?.[key]];
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Evaluates one expression node synchronously. */
|
|
167
|
+
// eslint-disable-next-line complexity
|
|
168
|
+
function evaluateExpression(
|
|
169
|
+
node: jsep.Expression,
|
|
170
|
+
context: ExpressionContext,
|
|
171
|
+
options?: ExpressionEvaluationOptions
|
|
172
|
+
): unknown {
|
|
173
|
+
const expression = node as Expression;
|
|
174
|
+
const mergedContext = mergeFunctionLibraries(context, options);
|
|
175
|
+
|
|
176
|
+
switch (expression.type) {
|
|
177
|
+
case 'ArrayExpression':
|
|
178
|
+
return evaluateArray(expression.elements, mergedContext);
|
|
179
|
+
|
|
180
|
+
case 'BinaryExpression':
|
|
181
|
+
if (expression.operator === '||') {
|
|
182
|
+
return (
|
|
183
|
+
evaluate(expression.left, mergedContext) || evaluate(expression.right, mergedContext)
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
if (expression.operator === '&&') {
|
|
187
|
+
return (
|
|
188
|
+
evaluate(expression.left, mergedContext) && evaluate(expression.right, mergedContext)
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
return binops[expression.operator](
|
|
192
|
+
evaluate(expression.left, mergedContext),
|
|
193
|
+
evaluate(expression.right, mergedContext)
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
case 'CallExpression': {
|
|
197
|
+
let caller: Record<string, unknown> | undefined;
|
|
198
|
+
let fn: Callable | undefined;
|
|
199
|
+
|
|
200
|
+
if (expression.callee.type === 'MemberExpression') {
|
|
201
|
+
const member = evaluateMember(expression.callee as jsep.MemberExpression, mergedContext);
|
|
202
|
+
caller = member[0];
|
|
203
|
+
fn = member[1] as Callable | undefined;
|
|
204
|
+
} else {
|
|
205
|
+
fn = evaluate(expression.callee, mergedContext) as Callable | undefined;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (typeof fn !== 'function') {
|
|
209
|
+
return undefined;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return fn.apply(caller, evaluateArray(expression.arguments, mergedContext));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
case 'ConditionalExpression':
|
|
216
|
+
return evaluate(expression.test, mergedContext)
|
|
217
|
+
? evaluate(expression.consequent, mergedContext)
|
|
218
|
+
: evaluate(expression.alternate, mergedContext);
|
|
219
|
+
|
|
220
|
+
case 'Identifier':
|
|
221
|
+
return mergedContext[expression.name];
|
|
222
|
+
|
|
223
|
+
case 'Literal':
|
|
224
|
+
return expression.value;
|
|
225
|
+
|
|
226
|
+
case 'MemberExpression':
|
|
227
|
+
return evaluateMember(expression, mergedContext)[1];
|
|
228
|
+
|
|
229
|
+
case 'ThisExpression':
|
|
230
|
+
return mergedContext;
|
|
231
|
+
|
|
232
|
+
case 'UnaryExpression':
|
|
233
|
+
return unops[expression.operator](evaluate(expression.argument, mergedContext));
|
|
234
|
+
|
|
235
|
+
default:
|
|
236
|
+
return undefined;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Evaluates a nested expression using an already-merged context. */
|
|
241
|
+
function evaluate(node: jsep.Expression, context: ExpressionContext): unknown {
|
|
242
|
+
return evaluateExpression(node, context);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Evaluates a parsed expression asynchronously.
|
|
247
|
+
*
|
|
248
|
+
* @param node - Expression AST to evaluate.
|
|
249
|
+
* @param context - Named values and functions available to the expression.
|
|
250
|
+
* @param options - Function libraries to add to the context.
|
|
251
|
+
* @returns The resolved expression value.
|
|
252
|
+
*
|
|
253
|
+
* @remarks
|
|
254
|
+
* Function calls are awaited, including calls nested inside arrays, member
|
|
255
|
+
* expressions, conditionals, unary expressions, and binary expressions.
|
|
256
|
+
*/
|
|
257
|
+
// eslint-disable-next-line complexity
|
|
258
|
+
export async function evalAsync(
|
|
259
|
+
node: jsep.Expression,
|
|
260
|
+
context: ExpressionContext,
|
|
261
|
+
options?: ExpressionEvaluationOptions
|
|
262
|
+
): Promise<unknown> {
|
|
263
|
+
const expression = node as Expression;
|
|
264
|
+
const mergedContext = mergeFunctionLibraries(context, options);
|
|
265
|
+
|
|
266
|
+
switch (expression.type) {
|
|
267
|
+
case 'ArrayExpression':
|
|
268
|
+
return await evaluateArrayAsync(expression.elements, mergedContext);
|
|
269
|
+
|
|
270
|
+
case 'BinaryExpression':
|
|
271
|
+
if (expression.operator === '||') {
|
|
272
|
+
return (
|
|
273
|
+
(await evalAsync(expression.left, mergedContext)) ||
|
|
274
|
+
(await evalAsync(expression.right, mergedContext))
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
if (expression.operator === '&&') {
|
|
278
|
+
return (
|
|
279
|
+
(await evalAsync(expression.left, mergedContext)) &&
|
|
280
|
+
(await evalAsync(expression.right, mergedContext))
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
return binops[expression.operator](
|
|
284
|
+
await evalAsync(expression.left, mergedContext),
|
|
285
|
+
await evalAsync(expression.right, mergedContext)
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
case 'CallExpression': {
|
|
289
|
+
let caller: Record<string, unknown> | undefined;
|
|
290
|
+
let fn: Callable | undefined;
|
|
291
|
+
|
|
292
|
+
if (expression.callee.type === 'MemberExpression') {
|
|
293
|
+
const member = await evaluateMemberAsync(
|
|
294
|
+
expression.callee as jsep.MemberExpression,
|
|
295
|
+
mergedContext
|
|
296
|
+
);
|
|
297
|
+
caller = member[0];
|
|
298
|
+
fn = member[1] as Callable | undefined;
|
|
299
|
+
} else {
|
|
300
|
+
fn = (await evalAsync(expression.callee, mergedContext)) as Callable | undefined;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (typeof fn !== 'function') {
|
|
304
|
+
return undefined;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return await fn.apply(caller, await evaluateArrayAsync(expression.arguments, mergedContext));
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
case 'ConditionalExpression':
|
|
311
|
+
return (await evalAsync(expression.test, mergedContext))
|
|
312
|
+
? await evalAsync(expression.consequent, mergedContext)
|
|
313
|
+
: await evalAsync(expression.alternate, mergedContext);
|
|
314
|
+
|
|
315
|
+
case 'Identifier':
|
|
316
|
+
return mergedContext[expression.name];
|
|
317
|
+
|
|
318
|
+
case 'Literal':
|
|
319
|
+
return expression.value;
|
|
320
|
+
|
|
321
|
+
case 'MemberExpression':
|
|
322
|
+
return (await evaluateMemberAsync(expression, mergedContext))[1];
|
|
323
|
+
|
|
324
|
+
case 'ThisExpression':
|
|
325
|
+
return mergedContext;
|
|
326
|
+
|
|
327
|
+
case 'UnaryExpression':
|
|
328
|
+
return unops[expression.operator](await evalAsync(expression.argument, mergedContext));
|
|
329
|
+
|
|
330
|
+
default:
|
|
331
|
+
return undefined;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Compiles an expression into a reusable synchronous evaluator.
|
|
337
|
+
*
|
|
338
|
+
* @param expression - Expression source or a previously parsed AST.
|
|
339
|
+
* @param options - Function libraries to add to each evaluation context.
|
|
340
|
+
* @returns A function that evaluates the expression against a context.
|
|
341
|
+
*/
|
|
342
|
+
export function compile(
|
|
343
|
+
expression: string | jsep.Expression,
|
|
344
|
+
options?: ExpressionEvaluationOptions
|
|
345
|
+
): (context: ExpressionContext) => unknown {
|
|
346
|
+
const ast = parse(expression);
|
|
347
|
+
return (context: ExpressionContext) => evaluateExpression(ast, context, options);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Compiles an expression into a reusable asynchronous evaluator.
|
|
352
|
+
*
|
|
353
|
+
* @param expression - Expression source or a previously parsed AST.
|
|
354
|
+
* @param options - Function libraries to add to each evaluation context.
|
|
355
|
+
* @returns A function that asynchronously evaluates the expression.
|
|
356
|
+
*/
|
|
357
|
+
export function compileAsync(
|
|
358
|
+
expression: string | jsep.Expression,
|
|
359
|
+
options?: ExpressionEvaluationOptions
|
|
360
|
+
): (context: ExpressionContext) => Promise<unknown> {
|
|
361
|
+
const ast = parse(expression);
|
|
362
|
+
return (context: ExpressionContext) => evalAsync(ast, context, options);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Registers a custom unary operator with the parser and evaluator.
|
|
367
|
+
*
|
|
368
|
+
* @param operator - Operator token to register.
|
|
369
|
+
* @param fn - Evaluator implementation for the operator.
|
|
370
|
+
*
|
|
371
|
+
* @remarks
|
|
372
|
+
* Registration changes module-global parser state and affects subsequent
|
|
373
|
+
* calls to {@link parse}.
|
|
374
|
+
*/
|
|
375
|
+
export function addUnaryOp(operator: string, fn: UnaryOperator): void {
|
|
376
|
+
jsep.addUnaryOp(operator);
|
|
377
|
+
unops[operator] = fn;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* Registers a custom binary operator with the parser and evaluator.
|
|
382
|
+
*
|
|
383
|
+
* @param operator - Operator token to register.
|
|
384
|
+
* @param precedenceOrFn - Parser precedence, or the evaluator implementation
|
|
385
|
+
* when using the default precedence for a built-in operator.
|
|
386
|
+
* @param fn - Evaluator implementation when an explicit precedence is given.
|
|
387
|
+
*
|
|
388
|
+
* @remarks
|
|
389
|
+
* Registration changes module-global parser state and affects subsequent
|
|
390
|
+
* calls to {@link parse}. New operators should provide explicit precedence.
|
|
391
|
+
*/
|
|
392
|
+
export function addBinaryOp(
|
|
393
|
+
operator: string,
|
|
394
|
+
precedenceOrFn: number | BinaryOperator,
|
|
395
|
+
fn?: BinaryOperator
|
|
396
|
+
): void {
|
|
397
|
+
if (fn) {
|
|
398
|
+
jsep.addBinaryOp(operator, precedenceOrFn as number);
|
|
399
|
+
binops[operator] = fn;
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
jsep.addBinaryOp(operator, DEFAULT_PRECEDENCE[operator] || 1);
|
|
404
|
+
binops[operator] = precedenceOrFn as BinaryOperator;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/**
|
|
408
|
+
* Parses expression source into a JSEP abstract syntax tree.
|
|
409
|
+
*
|
|
410
|
+
* @param expression - Expression source or an existing AST.
|
|
411
|
+
* @returns A parsed AST, or the supplied AST unchanged.
|
|
412
|
+
* @throws If the expression string is not valid JSEP syntax.
|
|
413
|
+
*/
|
|
414
|
+
export function parse(expression: string | jsep.Expression): jsep.Expression {
|
|
415
|
+
return typeof expression === 'string' ? jsep(expression) : expression;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Evaluates a parsed expression synchronously.
|
|
420
|
+
*
|
|
421
|
+
* @param node - Expression AST to evaluate.
|
|
422
|
+
* @param context - Named values and functions available to the expression.
|
|
423
|
+
* @param options - Function libraries to add to the context.
|
|
424
|
+
* @returns The expression value.
|
|
425
|
+
*/
|
|
426
|
+
export {evaluateExpression as eval};
|