@jbrowse/jexl 2.3.1

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 (89) hide show
  1. package/CHANGELOG.md +222 -0
  2. package/LICENSE.txt +19 -0
  3. package/README.md +216 -0
  4. package/dist/Expression.d.ts +38 -0
  5. package/dist/Expression.js +75 -0
  6. package/dist/Expression.js.map +1 -0
  7. package/dist/Jexl.d.ts +148 -0
  8. package/dist/Jexl.js +208 -0
  9. package/dist/Jexl.js.map +1 -0
  10. package/dist/Lexer.d.ts +130 -0
  11. package/dist/Lexer.js +321 -0
  12. package/dist/Lexer.js.map +1 -0
  13. package/dist/PromiseSync.d.ts +13 -0
  14. package/dist/PromiseSync.js +80 -0
  15. package/dist/PromiseSync.js.map +1 -0
  16. package/dist/evaluator/Evaluator.d.ts +92 -0
  17. package/dist/evaluator/Evaluator.js +153 -0
  18. package/dist/evaluator/Evaluator.js.map +1 -0
  19. package/dist/evaluator/handlers.d.ts +111 -0
  20. package/dist/evaluator/handlers.js +216 -0
  21. package/dist/evaluator/handlers.js.map +1 -0
  22. package/dist/grammar.d.ts +25 -0
  23. package/dist/grammar.js +179 -0
  24. package/dist/grammar.js.map +1 -0
  25. package/dist/index.d.ts +5 -0
  26. package/dist/index.js +20 -0
  27. package/dist/index.js.map +1 -0
  28. package/dist/package.json +1 -0
  29. package/dist/parser/Parser.d.ts +112 -0
  30. package/dist/parser/Parser.js +233 -0
  31. package/dist/parser/Parser.js.map +1 -0
  32. package/dist/parser/handlers.d.ts +112 -0
  33. package/dist/parser/handlers.js +302 -0
  34. package/dist/parser/handlers.js.map +1 -0
  35. package/dist/parser/states.d.ts +47 -0
  36. package/dist/parser/states.js +195 -0
  37. package/dist/parser/states.js.map +1 -0
  38. package/dist/types.d.ts +77 -0
  39. package/dist/types.js +7 -0
  40. package/dist/types.js.map +1 -0
  41. package/esm/Expression.d.ts +38 -0
  42. package/esm/Expression.js +70 -0
  43. package/esm/Expression.js.map +1 -0
  44. package/esm/Jexl.d.ts +148 -0
  45. package/esm/Jexl.js +202 -0
  46. package/esm/Jexl.js.map +1 -0
  47. package/esm/Lexer.d.ts +130 -0
  48. package/esm/Lexer.js +319 -0
  49. package/esm/Lexer.js.map +1 -0
  50. package/esm/PromiseSync.d.ts +13 -0
  51. package/esm/PromiseSync.js +78 -0
  52. package/esm/PromiseSync.js.map +1 -0
  53. package/esm/evaluator/Evaluator.d.ts +92 -0
  54. package/esm/evaluator/Evaluator.js +118 -0
  55. package/esm/evaluator/Evaluator.js.map +1 -0
  56. package/esm/evaluator/handlers.d.ts +111 -0
  57. package/esm/evaluator/handlers.js +202 -0
  58. package/esm/evaluator/handlers.js.map +1 -0
  59. package/esm/grammar.d.ts +25 -0
  60. package/esm/grammar.js +175 -0
  61. package/esm/grammar.js.map +1 -0
  62. package/esm/index.d.ts +5 -0
  63. package/esm/index.js +9 -0
  64. package/esm/index.js.map +1 -0
  65. package/esm/parser/Parser.d.ts +112 -0
  66. package/esm/parser/Parser.js +198 -0
  67. package/esm/parser/Parser.js.map +1 -0
  68. package/esm/parser/handlers.d.ts +112 -0
  69. package/esm/parser/handlers.js +280 -0
  70. package/esm/parser/handlers.js.map +1 -0
  71. package/esm/parser/states.d.ts +47 -0
  72. package/esm/parser/states.js +159 -0
  73. package/esm/parser/states.js.map +1 -0
  74. package/esm/types.d.ts +77 -0
  75. package/esm/types.js +6 -0
  76. package/esm/types.js.map +1 -0
  77. package/package.json +67 -0
  78. package/src/Expression.ts +95 -0
  79. package/src/Jexl.ts +232 -0
  80. package/src/Lexer.ts +344 -0
  81. package/src/PromiseSync.ts +86 -0
  82. package/src/evaluator/Evaluator.ts +153 -0
  83. package/src/evaluator/handlers.ts +226 -0
  84. package/src/grammar.ts +200 -0
  85. package/src/index.ts +10 -0
  86. package/src/parser/Parser.ts +235 -0
  87. package/src/parser/handlers.ts +309 -0
  88. package/src/parser/states.ts +177 -0
  89. package/src/types.ts +107 -0
@@ -0,0 +1,159 @@
1
+ /*
2
+ * Jexl
3
+ * Copyright 2020 Tom Shawver
4
+ */
5
+ import * as h from "./handlers.js";
6
+ /**
7
+ * A mapping of all states in the finite state machine to a set of instructions
8
+ * for handling or transitioning into other states. Each state can be handled
9
+ * in one of two schemes: a tokenType map, or a subHandler.
10
+ *
11
+ * Standard expression elements are handled through the tokenType object. This
12
+ * is an object map of all legal token types to encounter in this state (and
13
+ * any unexpected token types will generate a thrown error) to an options
14
+ * object that defines how they're handled. The available options are:
15
+ *
16
+ * {string} toState: The name of the state to which to transition
17
+ * immediately after handling this token
18
+ * {string} handler: The handler function to call when this token type is
19
+ * encountered in this state. If omitted, the default handler
20
+ * matching the token's "type" property will be called. If the handler
21
+ * function does not exist, no call will be made and no error will be
22
+ * generated. This is useful for tokens whose sole purpose is to
23
+ * transition to other states.
24
+ *
25
+ * States that consume a subexpression should define a subHandler, the
26
+ * function to be called with an expression tree argument when the
27
+ * subexpression is complete. Completeness is determined through the
28
+ * endStates object, which maps tokens on which an expression should end to the
29
+ * state to which to transition once the subHandler function has been called.
30
+ *
31
+ * Additionally, any state in which it is legal to mark the AST as completed
32
+ * should have a 'completable' property set to boolean true. Attempting to
33
+ * call {@link Parser#complete} in any state without this property will result
34
+ * in a thrown Error.
35
+ *
36
+ * @type {{}}
37
+ */
38
+ export const states = {
39
+ expectOperand: {
40
+ tokenTypes: {
41
+ literal: { toState: 'expectBinOp' },
42
+ templateString: { toState: 'expectBinOp' },
43
+ identifier: { toState: 'identifier' },
44
+ unaryOp: {},
45
+ openParen: { toState: 'subExpression' },
46
+ openCurl: { toState: 'expectObjKey', handler: h.objStart },
47
+ dot: { toState: 'traverse' },
48
+ openBracket: { toState: 'arrayVal', handler: h.arrayStart }
49
+ }
50
+ },
51
+ expectBinOp: {
52
+ tokenTypes: {
53
+ binaryOp: { toState: 'expectOperand' },
54
+ pipe: { toState: 'expectTransform' },
55
+ dot: { toState: 'traverse' },
56
+ question: { toState: 'ternaryMid', handler: h.ternaryStart },
57
+ semicolon: { handler: h.semicolon }
58
+ },
59
+ completable: true
60
+ },
61
+ expectTransform: {
62
+ tokenTypes: {
63
+ identifier: { toState: 'postTransform', handler: h.transform }
64
+ }
65
+ },
66
+ expectObjKey: {
67
+ tokenTypes: {
68
+ literal: { toState: 'expectKeyValSep', handler: h.objKey },
69
+ identifier: { toState: 'expectKeyValSep', handler: h.objKey },
70
+ closeCurl: { toState: 'expectBinOp' }
71
+ }
72
+ },
73
+ expectKeyValSep: {
74
+ tokenTypes: {
75
+ colon: { toState: 'objVal' }
76
+ }
77
+ },
78
+ postTransform: {
79
+ tokenTypes: {
80
+ openParen: { toState: 'argVal' },
81
+ binaryOp: { toState: 'expectOperand' },
82
+ dot: { toState: 'traverse' },
83
+ openBracket: { toState: 'filter' },
84
+ pipe: { toState: 'expectTransform' },
85
+ question: { toState: 'ternaryMid', handler: h.ternaryStart }
86
+ },
87
+ completable: true
88
+ },
89
+ postArgs: {
90
+ tokenTypes: {
91
+ binaryOp: { toState: 'expectOperand' },
92
+ dot: { toState: 'traverse' },
93
+ openBracket: { toState: 'filter' },
94
+ pipe: { toState: 'expectTransform' },
95
+ question: { toState: 'ternaryMid', handler: h.ternaryStart }
96
+ },
97
+ completable: true
98
+ },
99
+ identifier: {
100
+ tokenTypes: {
101
+ binaryOp: { toState: 'expectOperand' },
102
+ dot: { toState: 'traverse' },
103
+ openBracket: { toState: 'filter' },
104
+ openParen: { toState: 'argVal', handler: h.functionCall },
105
+ pipe: { toState: 'expectTransform' },
106
+ question: { toState: 'ternaryMid', handler: h.ternaryStart }
107
+ },
108
+ completable: true
109
+ },
110
+ traverse: {
111
+ tokenTypes: {
112
+ identifier: { toState: 'identifier' }
113
+ }
114
+ },
115
+ filter: {
116
+ subHandler: h.filter,
117
+ endStates: {
118
+ closeBracket: 'identifier'
119
+ }
120
+ },
121
+ subExpression: {
122
+ subHandler: h.subExpression,
123
+ endStates: {
124
+ closeParen: 'expectBinOp'
125
+ }
126
+ },
127
+ argVal: {
128
+ subHandler: h.argVal,
129
+ endStates: {
130
+ comma: 'argVal',
131
+ closeParen: 'postArgs'
132
+ }
133
+ },
134
+ objVal: {
135
+ subHandler: h.objVal,
136
+ endStates: {
137
+ comma: 'expectObjKey',
138
+ closeCurl: 'expectBinOp'
139
+ }
140
+ },
141
+ arrayVal: {
142
+ subHandler: h.arrayVal,
143
+ endStates: {
144
+ comma: 'arrayVal',
145
+ closeBracket: 'expectBinOp'
146
+ }
147
+ },
148
+ ternaryMid: {
149
+ subHandler: h.ternaryMid,
150
+ endStates: {
151
+ colon: 'ternaryEnd'
152
+ }
153
+ },
154
+ ternaryEnd: {
155
+ subHandler: h.ternaryEnd,
156
+ completable: true
157
+ }
158
+ };
159
+ //# sourceMappingURL=states.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"states.js","sourceRoot":"","sources":["../../src/parser/states.ts"],"names":[],"mappings":"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"}
package/esm/types.d.ts ADDED
@@ -0,0 +1,77 @@
1
+ export interface Token {
2
+ type: string;
3
+ value: any;
4
+ raw: string;
5
+ }
6
+ export interface AstNode {
7
+ type: string;
8
+ _parent?: AstNode;
9
+ }
10
+ export interface Literal extends AstNode {
11
+ type: 'Literal';
12
+ value: string | number | boolean;
13
+ }
14
+ export interface Identifier extends AstNode {
15
+ type: 'Identifier';
16
+ value: string;
17
+ from?: AstNode;
18
+ relative?: boolean;
19
+ }
20
+ export interface BinaryExpression extends AstNode {
21
+ type: 'BinaryExpression';
22
+ operator: string;
23
+ left: AstNode;
24
+ right?: AstNode;
25
+ }
26
+ export interface UnaryExpression extends AstNode {
27
+ type: 'UnaryExpression';
28
+ operator: string;
29
+ right?: AstNode;
30
+ }
31
+ export interface ArrayLiteral extends AstNode {
32
+ type: 'ArrayLiteral';
33
+ value: AstNode[];
34
+ }
35
+ export interface ObjectLiteral extends AstNode {
36
+ type: 'ObjectLiteral';
37
+ value: Record<string, AstNode>;
38
+ }
39
+ export interface TemplateLiteral extends AstNode {
40
+ type: 'TemplateLiteral';
41
+ parts: {
42
+ type: 'static' | 'expression';
43
+ value: string | AstNode;
44
+ }[];
45
+ }
46
+ export interface FunctionCall extends AstNode {
47
+ type: 'FunctionCall';
48
+ name: string;
49
+ args: AstNode[];
50
+ pool: 'functions' | 'transforms';
51
+ }
52
+ export interface FilterExpression extends AstNode {
53
+ type: 'FilterExpression';
54
+ expr: AstNode;
55
+ relative: boolean;
56
+ subject: AstNode;
57
+ }
58
+ export interface ConditionalExpression extends AstNode {
59
+ type: 'ConditionalExpression';
60
+ test: AstNode;
61
+ consequent?: AstNode;
62
+ alternate?: AstNode;
63
+ }
64
+ export interface SequenceExpression extends AstNode {
65
+ type: 'SequenceExpression';
66
+ expressions: AstNode[];
67
+ }
68
+ export interface AssignmentExpression extends AstNode {
69
+ type: 'AssignmentExpression';
70
+ operator: '=';
71
+ left: Identifier;
72
+ right: AstNode;
73
+ }
74
+ export type AstNodeUnion = Literal | Identifier | BinaryExpression | UnaryExpression | ArrayLiteral | ObjectLiteral | TemplateLiteral | FunctionCall | FilterExpression | ConditionalExpression | SequenceExpression | AssignmentExpression;
75
+ export type NodeByType<T extends AstNodeUnion['type']> = Extract<AstNodeUnion, {
76
+ type: T;
77
+ }>;
package/esm/types.js ADDED
@@ -0,0 +1,6 @@
1
+ /*
2
+ * Jexl
3
+ * Copyright 2020 Tom Shawver
4
+ */
5
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@jbrowse/jexl",
3
+ "version": "2.3.1",
4
+ "description": "A fork of the jexl lang for jbrowse",
5
+ "type": "module",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ "import": {
9
+ "import": "./esm/index.js"
10
+ },
11
+ "require": {
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "esm",
18
+ "src"
19
+ ],
20
+ "scripts": {
21
+ "format": "prettier --write .",
22
+ "lint": "eslint --report-unused-disable-directives --max-warnings 0",
23
+ "test": "vitest",
24
+ "clean": "rimraf dist esm",
25
+ "build:esm": "tsc --outDir esm",
26
+ "build:es5": "tsc --module commonjs --outDir dist",
27
+ "build": "yarn build:esm && yarn build:es5",
28
+ "prebuild": "yarn clean",
29
+ "postbuild:es5": "echo '{\"type\": \"commonjs\"}' > dist/package.json",
30
+ "preversion": "yarn lint && yarn test --run && yarn build"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/TomFrost/jexl"
35
+ },
36
+ "keywords": [
37
+ "JSON",
38
+ "expression",
39
+ "evaluator",
40
+ "parser",
41
+ "target",
42
+ "context",
43
+ "jsep",
44
+ "filter",
45
+ "selector"
46
+ ],
47
+ "author": "Tom Shawver <tom@frosteddesign.com>",
48
+ "license": "MIT",
49
+ "bugs": {
50
+ "url": "https://github.com/TomFrost/jexl/issues"
51
+ },
52
+ "homepage": "https://github.com/TomFrost/jexl",
53
+ "devDependencies": {
54
+ "@types/node": "^25.0.3",
55
+ "eslint": "^9.39.2",
56
+ "eslint-plugin-import": "^2.32.0",
57
+ "eslint-plugin-unicorn": "^62.0.0",
58
+ "prettier": "^3.7.4",
59
+ "rimraf": "^6.1.2",
60
+ "typescript": "^5.9.3",
61
+ "typescript-eslint": "^8.50.1",
62
+ "vitest": "^4.0.16"
63
+ },
64
+ "publishConfig": {
65
+ "access": "public"
66
+ }
67
+ }
@@ -0,0 +1,95 @@
1
+ /*
2
+ * Jexl
3
+ * Copyright 2020 Tom Shawver
4
+ */
5
+
6
+ import Lexer from './Lexer.ts'
7
+ import PromiseSync from './PromiseSync.ts'
8
+ import Evaluator from './evaluator/Evaluator.ts'
9
+ import Parser from './parser/Parser.ts'
10
+
11
+ import type { AstNode } from './types.ts'
12
+
13
+ interface Grammar {
14
+ elements: Record<string, any>
15
+ [key: string]: any
16
+ }
17
+
18
+ type PromiseConstructor = typeof Promise | typeof PromiseSync
19
+
20
+ class Expression {
21
+ _grammar: Grammar
22
+ _exprStr: string
23
+ _ast: AstNode | null
24
+
25
+ constructor(grammar: Grammar, exprStr: string) {
26
+ this._grammar = grammar
27
+ this._exprStr = exprStr
28
+ this._ast = null
29
+ }
30
+
31
+ /**
32
+ * Forces a compilation of the expression string that this Expression object
33
+ * was constructed with. This function can be called multiple times; useful
34
+ * if the language elements of the associated Jexl instance change.
35
+ * @returns {Expression} this Expression instance, for convenience
36
+ */
37
+ compile() {
38
+ const lexer = new Lexer(this._grammar)
39
+ const parser = new Parser(this._grammar, lexer)
40
+ const tokens = lexer.tokenize(this._exprStr)
41
+ parser.addTokens(tokens)
42
+ this._ast = parser.complete()
43
+ return this
44
+ }
45
+
46
+ /**
47
+ * Asynchronously evaluates the expression within an optional context.
48
+ * @param {Object} [context] A mapping of variables to values, which will be
49
+ * made accessible to the Jexl expression when evaluating it
50
+ * @returns {Promise<*>} resolves with the result of the evaluation.
51
+ */
52
+ eval(context = {}) {
53
+ return this._eval(context, Promise)
54
+ }
55
+
56
+ /**
57
+ * Synchronously evaluates the expression within an optional context.
58
+ * @param {Object} [context] A mapping of variables to values, which will be
59
+ * made accessible to the Jexl expression when evaluating it
60
+ * @returns {*} the result of the evaluation.
61
+ * @throws {*} on error
62
+ */
63
+ evalSync(context = {}) {
64
+ const res = this._eval(context, PromiseSync as any) as PromiseSync
65
+ if (res.error) {
66
+ if (res.error instanceof Error) {
67
+ throw res.error
68
+ }
69
+ throw new Error(typeof res.error === 'string' ? res.error : JSON.stringify(res.error))
70
+ }
71
+ return res.value
72
+ }
73
+
74
+ _eval(context: any, promise: PromiseConstructor) {
75
+ return promise.resolve().then(() => {
76
+ const ast = this._getAst()
77
+ const evaluator = new Evaluator(
78
+ this._grammar,
79
+ context,
80
+ undefined,
81
+ promise
82
+ )
83
+ return evaluator.eval(ast!)
84
+ })
85
+ }
86
+
87
+ _getAst() {
88
+ if (!this._ast) {
89
+ this.compile()
90
+ }
91
+ return this._ast
92
+ }
93
+ }
94
+
95
+ export default Expression
package/src/Jexl.ts ADDED
@@ -0,0 +1,232 @@
1
+ /*
2
+ * Jexl
3
+ * Copyright 2020 Tom Shawver
4
+ */
5
+
6
+ import Expression from './Expression.ts'
7
+ import { getGrammar } from './grammar.ts'
8
+
9
+ interface Grammar {
10
+ elements: Record<string, any>
11
+ functions: Record<string, (...args: any[]) => any>
12
+ transforms: Record<string, (val: any, ...args: any[]) => any>
13
+ }
14
+
15
+ /**
16
+ * Jexl is the Javascript Expression Language, capable of parsing and
17
+ * evaluating basic to complex expression strings, combined with advanced
18
+ * xpath-like drilldown into native Javascript objects.
19
+ * @constructor
20
+ */
21
+ class Jexl {
22
+ _grammar: Grammar
23
+
24
+ constructor() {
25
+ this._grammar = getGrammar()
26
+ this.expr = this.expr.bind(this)
27
+ }
28
+
29
+ /**
30
+ * Adds a binary operator to Jexl at the specified precedence. The higher the
31
+ * precedence, the earlier the operator is applied in the order of operations.
32
+ * For example, * has a higher precedence than +, because multiplication comes
33
+ * before division.
34
+ *
35
+ * Please see grammar.js for a listing of all default operators and their
36
+ * precedence values in order to choose the appropriate precedence for the
37
+ * new operator.
38
+ * @param {string} operator The operator string to be added
39
+ * @param {number} precedence The operator's precedence
40
+ * @param {function} fn A function to run to calculate the result. The function
41
+ * will be called with two arguments: left and right, denoting the values
42
+ * on either side of the operator. It should return either the resulting
43
+ * value, or a Promise that resolves with the resulting value.
44
+ * @param {boolean} [manualEval] If true, the `left` and `right` arguments
45
+ * will be wrapped in objects with an `eval` function. Calling
46
+ * left.eval() or right.eval() will return a promise that resolves to
47
+ * that operand's actual value. This is useful to conditionally evaluate
48
+ * operands.
49
+ */
50
+ addBinaryOp(
51
+ operator: string,
52
+ precedence: number,
53
+ fn: (left: any, right: any) => any,
54
+ manualEval?: boolean
55
+ ) {
56
+ this._addGrammarElement(operator, {
57
+ type: 'binaryOp',
58
+ precedence: precedence,
59
+ [manualEval ? 'evalOnDemand' : 'eval']: fn
60
+ })
61
+ }
62
+
63
+ /**
64
+ * Adds or replaces an expression function in this Jexl instance.
65
+ * @param {string} name The name of the expression function, as it will be
66
+ * used within Jexl expressions
67
+ * @param {function} fn The javascript function to be executed when this
68
+ * expression function is invoked. It will be provided with each argument
69
+ * supplied in the expression, in the same order.
70
+ */
71
+ addFunction(name: string, fn: (...args: any[]) => any) {
72
+ this._grammar.functions[name] = fn
73
+ }
74
+
75
+ /**
76
+ * Syntactic sugar for calling {@link #addFunction} repeatedly. This function
77
+ * accepts a map of one or more expression function names to their javascript
78
+ * function counterpart.
79
+ * @param {{}} map A map of expression function names to javascript functions
80
+ */
81
+ addFunctions(map: Record<string, (...args: any[]) => any>) {
82
+ Object.assign(this._grammar.functions, map)
83
+ }
84
+
85
+ /**
86
+ * Adds a unary operator to Jexl. Unary operators are currently only supported
87
+ * on the left side of the value on which it will operate.
88
+ * @param {string} operator The operator string to be added
89
+ * @param {function} fn A function to run to calculate the result. The function
90
+ * will be called with one argument: the literal value to the right of the
91
+ * operator. It should return either the resulting value, or a Promise
92
+ * that resolves with the resulting value.
93
+ */
94
+ addUnaryOp(operator: string, fn: (right: any) => any) {
95
+ this._addGrammarElement(operator, {
96
+ type: 'unaryOp',
97
+ weight: Infinity,
98
+ eval: fn
99
+ })
100
+ }
101
+
102
+ /**
103
+ * Adds or replaces a transform function in this Jexl instance.
104
+ * @param {string} name The name of the transform function, as it will be used
105
+ * within Jexl expressions
106
+ * @param {function} fn The function to be executed when this transform is
107
+ * invoked. It will be provided with at least one argument:
108
+ * - {*} value: The value to be transformed
109
+ * - {...*} args: The arguments for this transform
110
+ */
111
+ addTransform(name: string, fn: (val: any, ...args: any[]) => any) {
112
+ this._grammar.transforms[name] = fn
113
+ }
114
+
115
+ /**
116
+ * Syntactic sugar for calling {@link #addTransform} repeatedly. This function
117
+ * accepts a map of one or more transform names to their transform function.
118
+ * @param {{}} map A map of transform names to transform functions
119
+ */
120
+ addTransforms(map: Record<string, (val: any, ...args: any[]) => any>) {
121
+ Object.assign(this._grammar.transforms, map)
122
+ }
123
+
124
+ /**
125
+ * Creates an Expression object from the given Jexl expression string, and
126
+ * immediately compiles it. The returned Expression object can then be
127
+ * evaluated multiple times with new contexts, without generating any
128
+ * additional string processing overhead.
129
+ * @param {string} expression The Jexl expression to be compiled
130
+ * @returns {Expression} The compiled Expression object
131
+ */
132
+ compile(expression: string) {
133
+ const exprObj = this.createExpression(expression)
134
+ return exprObj.compile()
135
+ }
136
+
137
+ /**
138
+ * Constructs an Expression object from a Jexl expression string.
139
+ * @param {string} expression The Jexl expression to be wrapped in an
140
+ * Expression object
141
+ * @returns {Expression} The Expression object representing the given string
142
+ */
143
+ createExpression(expression: string) {
144
+ return new Expression(this._grammar, expression)
145
+ }
146
+
147
+ /**
148
+ * Retrieves a previously set expression function.
149
+ * @param {string} name The name of the expression function
150
+ * @returns {function} The expression function
151
+ */
152
+ getFunction(name: string) {
153
+ return this._grammar.functions[name]
154
+ }
155
+
156
+ /**
157
+ * Retrieves a previously set transform function.
158
+ * @param {string} name The name of the transform function
159
+ * @returns {function} The transform function
160
+ */
161
+ getTransform(name: string) {
162
+ return this._grammar.transforms[name]
163
+ }
164
+
165
+ /**
166
+ * Asynchronously evaluates a Jexl string within an optional context.
167
+ * @param {string} expression The Jexl expression to be evaluated
168
+ * @param {Object} [context] A mapping of variables to values, which will be
169
+ * made accessible to the Jexl expression when evaluating it
170
+ * @returns {Promise<*>} resolves with the result of the evaluation.
171
+ */
172
+ eval(expression: string, context = {}) {
173
+ const exprObj = this.createExpression(expression)
174
+ return exprObj.eval(context)
175
+ }
176
+
177
+ /**
178
+ * Synchronously evaluates a Jexl string within an optional context.
179
+ * @param {string} expression The Jexl expression to be evaluated
180
+ * @param {Object} [context] A mapping of variables to values, which will be
181
+ * made accessible to the Jexl expression when evaluating it
182
+ * @returns {*} the result of the evaluation.
183
+ * @throws {*} on error
184
+ */
185
+ evalSync(expression: string, context = {}) {
186
+ const exprObj = this.createExpression(expression)
187
+ return exprObj.evalSync(context)
188
+ }
189
+
190
+ /**
191
+ * A JavaScript template literal to allow expressions to be defined by the
192
+ * syntax: expr`40 + 2`
193
+ * @param {Array<string>} strs
194
+ * @param {...any} args
195
+ */
196
+ expr(strs: TemplateStringsArray, ...args: any[]) {
197
+ let exprStr = ''
198
+ for (let idx = 0; idx < strs.length; idx++) {
199
+ exprStr += strs[idx]
200
+ if (idx < args.length) {
201
+ exprStr += args[idx]
202
+ }
203
+ }
204
+ return this.createExpression(exprStr)
205
+ }
206
+
207
+ /**
208
+ * Removes a binary or unary operator from the Jexl grammar.
209
+ * @param {string} operator The operator string to be removed
210
+ */
211
+ removeOp(operator: string) {
212
+ const elem = this._grammar.elements[operator]
213
+ if (elem && (elem.type === 'binaryOp' || elem.type === 'unaryOp')) {
214
+ Reflect.deleteProperty(this._grammar.elements, operator)
215
+ }
216
+ }
217
+
218
+ /**
219
+ * Adds an element to the grammar map used by this Jexl instance.
220
+ * @param {string} str The key string to be added
221
+ * @param {{type: <string>}} obj A map of configuration options for this
222
+ * grammar element
223
+ * @private
224
+ */
225
+ _addGrammarElement(str: string, obj: any) {
226
+ this._grammar.elements[str] = obj
227
+ }
228
+ }
229
+
230
+ const jexlInstance = new Jexl()
231
+ export default jexlInstance
232
+ export { Jexl }