@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.
Files changed (42) hide show
  1. package/LICENSE +140 -0
  2. package/README.md +12 -0
  3. package/dist/dggs.cjs +54 -0
  4. package/dist/dggs.cjs.map +6 -0
  5. package/dist/dggs.d.ts +18 -0
  6. package/dist/dggs.d.ts.map +1 -0
  7. package/dist/dggs.js +42 -0
  8. package/dist/dggs.js.map +1 -0
  9. package/dist/expression-eval.d.ts +103 -0
  10. package/dist/expression-eval.d.ts.map +1 -0
  11. package/dist/expression-eval.js +298 -0
  12. package/dist/expression-eval.js.map +1 -0
  13. package/dist/function-libraries.d.ts +75 -0
  14. package/dist/function-libraries.d.ts.map +1 -0
  15. package/dist/function-libraries.js +98 -0
  16. package/dist/function-libraries.js.map +1 -0
  17. package/dist/function-registry.d.ts +93 -0
  18. package/dist/function-registry.d.ts.map +1 -0
  19. package/dist/function-registry.js +131 -0
  20. package/dist/function-registry.js.map +1 -0
  21. package/dist/get.d.ts +9 -0
  22. package/dist/get.d.ts.map +1 -0
  23. package/dist/get.js +32 -0
  24. package/dist/get.js.map +1 -0
  25. package/dist/index.cjs +473 -0
  26. package/dist/index.cjs.map +6 -0
  27. package/dist/index.d.ts +7 -0
  28. package/dist/index.d.ts.map +1 -0
  29. package/dist/index.js +8 -0
  30. package/dist/index.js.map +1 -0
  31. package/dist/parse-expression-string.d.ts +21 -0
  32. package/dist/parse-expression-string.d.ts.map +1 -0
  33. package/dist/parse-expression-string.js +61 -0
  34. package/dist/parse-expression-string.js.map +1 -0
  35. package/package.json +50 -0
  36. package/src/dggs.ts +62 -0
  37. package/src/expression-eval.ts +426 -0
  38. package/src/function-libraries.ts +168 -0
  39. package/src/function-registry.ts +163 -0
  40. package/src/get.ts +37 -0
  41. package/src/index.ts +26 -0
  42. package/src/parse-expression-string.ts +80 -0
@@ -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};