@jbrowse/jexl 3.1.0 → 3.2.0
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/README.md +15 -0
- package/dist/Expression.d.ts +3 -1
- package/dist/Expression.js +3 -8
- package/dist/Expression.js.map +1 -1
- package/dist/Lexer.d.ts +0 -1
- package/dist/Lexer.js +5 -8
- package/dist/Lexer.js.map +1 -1
- package/dist/evaluator/compile.js +34 -16
- package/dist/evaluator/compile.js.map +1 -1
- package/dist/grammar.js +8 -8
- package/dist/grammar.js.map +1 -1
- package/dist/parser/Parser.d.ts +2 -1
- package/dist/parser/Parser.js +29 -12
- package/dist/parser/Parser.js.map +1 -1
- package/dist/parser/handlers.d.ts +8 -2
- package/dist/parser/handlers.js +32 -7
- package/dist/parser/handlers.js.map +1 -1
- package/dist/parser/states.js +19 -20
- package/dist/parser/states.js.map +1 -1
- package/esm/Expression.d.ts +3 -1
- package/esm/Expression.js +3 -8
- package/esm/Expression.js.map +1 -1
- package/esm/Lexer.d.ts +0 -1
- package/esm/Lexer.js +5 -8
- package/esm/Lexer.js.map +1 -1
- package/esm/evaluator/compile.js +34 -16
- package/esm/evaluator/compile.js.map +1 -1
- package/esm/grammar.js +8 -8
- package/esm/grammar.js.map +1 -1
- package/esm/parser/Parser.d.ts +2 -1
- package/esm/parser/Parser.js +29 -12
- package/esm/parser/Parser.js.map +1 -1
- package/esm/parser/handlers.d.ts +8 -2
- package/esm/parser/handlers.js +29 -7
- package/esm/parser/handlers.js.map +1 -1
- package/esm/parser/states.js +19 -20
- package/esm/parser/states.js.map +1 -1
- package/package.json +1 -1
- package/src/Expression.ts +3 -9
- package/src/Lexer.ts +5 -8
- package/src/evaluator/compile.ts +51 -44
- package/src/grammar.ts +8 -9
- package/src/parser/Parser.ts +35 -17
- package/src/parser/handlers.ts +33 -10
- package/src/parser/states.ts +20 -20
package/esm/parser/handlers.js
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
* Copyright 2020 Tom Shawver
|
|
4
4
|
*/
|
|
5
5
|
import { precedenceOf } from "../grammar.js";
|
|
6
|
+
// circular with Parser.ts, which is fine: the binding is only read from inside
|
|
7
|
+
// a handler, long after both modules have finished evaluating
|
|
8
|
+
import Parser from "./Parser.js";
|
|
6
9
|
/**
|
|
7
10
|
* Handles a subexpression that's used to define a transform argument's value.
|
|
8
11
|
* @param {{type: <string>}} ast The subexpression tree
|
|
@@ -115,13 +118,26 @@ export function filter(ast) {
|
|
|
115
118
|
/**
|
|
116
119
|
* Handles identifier tokens when used to indicate the name of a function to
|
|
117
120
|
* be called.
|
|
118
|
-
*
|
|
121
|
+
*
|
|
122
|
+
* A call written against a value, `a.b(x)`, becomes `b(a, x)`: jexl functions
|
|
123
|
+
* live in one global pool and have no receiver, so the value the name was read
|
|
124
|
+
* from is passed as the first argument instead. Every function in the pool is
|
|
125
|
+
* already written that way — `get(feature, key)`, `split(str, sep)` — which is
|
|
126
|
+
* what makes the two spellings equivalent. Previously the receiver was simply
|
|
127
|
+
* dropped, so `refName.split(' ')` quietly called `split(' ')`.
|
|
119
128
|
*/
|
|
120
129
|
export function functionCall() {
|
|
130
|
+
const cursor = this._cursor;
|
|
131
|
+
if (cursor?.type !== 'Identifier') {
|
|
132
|
+
// reached by `a[expr]()`, whose subject is a filter rather than a name;
|
|
133
|
+
// there is nothing to look up in the function pool
|
|
134
|
+
throw new Error(`Functions must be called by name: ${this._exprStr}`);
|
|
135
|
+
}
|
|
136
|
+
const { from } = cursor;
|
|
121
137
|
const node = {
|
|
122
138
|
type: 'FunctionCall',
|
|
123
|
-
name:
|
|
124
|
-
args: [],
|
|
139
|
+
name: cursor.value,
|
|
140
|
+
args: from ? [from] : [],
|
|
125
141
|
pool: 'functions'
|
|
126
142
|
};
|
|
127
143
|
this._placeBeforeCursor(node);
|
|
@@ -177,8 +193,7 @@ export function templateString(token) {
|
|
|
177
193
|
}
|
|
178
194
|
else {
|
|
179
195
|
const subTokens = this._lexer.tokenize(part.value);
|
|
180
|
-
const
|
|
181
|
-
const subParser = new ParserClass(this._grammar, this._lexer);
|
|
196
|
+
const subParser = new Parser(this._grammar, this._lexer);
|
|
182
197
|
subParser.addTokens(subTokens);
|
|
183
198
|
const subAst = subParser.complete();
|
|
184
199
|
if (!subAst) {
|
|
@@ -220,8 +235,15 @@ export function objStart() {
|
|
|
220
235
|
* @param {{type: <string>}} ast The subexpression tree
|
|
221
236
|
*/
|
|
222
237
|
export function objVal(ast) {
|
|
223
|
-
|
|
224
|
-
|
|
238
|
+
// defined rather than assigned so that the key "__proto__" becomes an
|
|
239
|
+
// ordinary entry of the node's key map instead of re-pointing its prototype,
|
|
240
|
+
// which dropped the entry before the Evaluator ever saw it
|
|
241
|
+
Object.defineProperty(this._cursor.value, this._curObjKey, {
|
|
242
|
+
value: ast,
|
|
243
|
+
writable: true,
|
|
244
|
+
enumerable: true,
|
|
245
|
+
configurable: true
|
|
246
|
+
});
|
|
225
247
|
}
|
|
226
248
|
/**
|
|
227
249
|
* Handles traditional subexpressions, delineated with the groupStart and
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/parser/handlers.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;
|
|
1
|
+
{"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/parser/handlers.ts"],"names":[],"mappings":"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"}
|
package/esm/parser/states.js
CHANGED
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
* Copyright 2020 Tom Shawver
|
|
4
4
|
*/
|
|
5
5
|
import * as h from "./handlers.js";
|
|
6
|
+
/**
|
|
7
|
+
* The tokens that may follow a finished operand: an operator to combine it
|
|
8
|
+
* with, a dot or subscript to keep drilling into it, a question mark opening a
|
|
9
|
+
* ternary, or a semicolon ending the statement. Shared by every state that
|
|
10
|
+
* sits on a complete value, which is also why each of them is completable.
|
|
11
|
+
*/
|
|
12
|
+
const afterOperand = {
|
|
13
|
+
binaryOp: { toState: 'expectOperand' },
|
|
14
|
+
dot: { toState: 'traverse' },
|
|
15
|
+
openBracket: { toState: 'filter' },
|
|
16
|
+
question: { toState: 'ternaryMid', handler: h.ternaryStart },
|
|
17
|
+
semicolon: { handler: h.semicolon }
|
|
18
|
+
};
|
|
6
19
|
/**
|
|
7
20
|
* A mapping of all states in the finite state machine to a set of instructions
|
|
8
21
|
* for handling or transitioning into other states. Each state can be handled
|
|
@@ -49,13 +62,7 @@ export const states = {
|
|
|
49
62
|
}
|
|
50
63
|
},
|
|
51
64
|
expectBinOp: {
|
|
52
|
-
tokenTypes:
|
|
53
|
-
binaryOp: { toState: 'expectOperand' },
|
|
54
|
-
dot: { toState: 'traverse' },
|
|
55
|
-
openBracket: { toState: 'filter' },
|
|
56
|
-
question: { toState: 'ternaryMid', handler: h.ternaryStart },
|
|
57
|
-
semicolon: { handler: h.semicolon }
|
|
58
|
-
},
|
|
65
|
+
tokenTypes: afterOperand,
|
|
59
66
|
completable: true
|
|
60
67
|
},
|
|
61
68
|
expectObjKey: {
|
|
@@ -71,23 +78,15 @@ export const states = {
|
|
|
71
78
|
}
|
|
72
79
|
},
|
|
73
80
|
postArgs: {
|
|
74
|
-
tokenTypes:
|
|
75
|
-
binaryOp: { toState: 'expectOperand' },
|
|
76
|
-
dot: { toState: 'traverse' },
|
|
77
|
-
openBracket: { toState: 'filter' },
|
|
78
|
-
question: { toState: 'ternaryMid', handler: h.ternaryStart },
|
|
79
|
-
semicolon: { handler: h.semicolon }
|
|
80
|
-
},
|
|
81
|
+
tokenTypes: afterOperand,
|
|
81
82
|
completable: true
|
|
82
83
|
},
|
|
83
84
|
identifier: {
|
|
84
85
|
tokenTypes: {
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
openParen: { toState: 'argVal', handler: h.functionCall }
|
|
89
|
-
question: { toState: 'ternaryMid', handler: h.ternaryStart },
|
|
90
|
-
semicolon: { handler: h.semicolon }
|
|
86
|
+
...afterOperand,
|
|
87
|
+
// a name is the only operand that can be followed by an argument list,
|
|
88
|
+
// since a function is called by name rather than evaluated as a value
|
|
89
|
+
openParen: { toState: 'argVal', handler: h.functionCall }
|
|
91
90
|
},
|
|
92
91
|
completable: true
|
|
93
92
|
},
|
package/esm/parser/states.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"states.js","sourceRoot":"","sources":["../../src/parser/states.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,CAAC,MAAM,eAAe,CAAA;AAmBlC
|
|
1
|
+
{"version":3,"file":"states.js","sourceRoot":"","sources":["../../src/parser/states.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
package/src/Expression.ts
CHANGED
|
@@ -15,6 +15,9 @@ import type { AstNode } from './types.ts'
|
|
|
15
15
|
class Expression {
|
|
16
16
|
_grammar: Grammar
|
|
17
17
|
_exprStr: string
|
|
18
|
+
/** The parsed tree, exposed for inspection. Legitimately null for an
|
|
19
|
+
* expression with no tokens, which is why compilation is tracked by
|
|
20
|
+
* `_compiled` rather than by this being set. */
|
|
18
21
|
_ast: AstNode | null
|
|
19
22
|
_lexer: Lexer
|
|
20
23
|
_compiled = false
|
|
@@ -70,15 +73,6 @@ class Expression {
|
|
|
70
73
|
}
|
|
71
74
|
return this._fn(new Evaluator(this._grammar, context))
|
|
72
75
|
}
|
|
73
|
-
|
|
74
|
-
_getAst() {
|
|
75
|
-
// tracked separately from _ast, which is legitimately null for an
|
|
76
|
-
// expression with no tokens and would otherwise recompile on every eval
|
|
77
|
-
if (!this._compiled) {
|
|
78
|
-
this.compile()
|
|
79
|
-
}
|
|
80
|
-
return this._ast
|
|
81
|
-
}
|
|
82
76
|
}
|
|
83
77
|
|
|
84
78
|
export default Expression
|
package/src/Lexer.ts
CHANGED
|
@@ -10,6 +10,9 @@ const numericRegex = /^-?(?:(?:[0-9]*\.[0-9]+)|[0-9]+)$/
|
|
|
10
10
|
const identRegex =
|
|
11
11
|
/^[a-zA-Zа-яА-Я_\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF$][a-zA-Zа-яА-Я0-9_\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF$]*$/
|
|
12
12
|
const escEscRegex = /\\\\/g
|
|
13
|
+
// a string literal opens with one of exactly two quote characters, so the two
|
|
14
|
+
// unescaping regexes can just be named rather than built and cached per quote
|
|
15
|
+
const escQuoteRegex = { "'": /\\'/g, '"': /\\"/g }
|
|
13
16
|
const whitespaceRegex = /^\s*$/
|
|
14
17
|
const preOpRegexElems = [
|
|
15
18
|
// Template strings
|
|
@@ -58,7 +61,6 @@ const minusNegatesAfter = new Set([
|
|
|
58
61
|
class Lexer {
|
|
59
62
|
_grammar: Grammar
|
|
60
63
|
_splitRegex?: RegExp
|
|
61
|
-
_escQuoteRegexCache = new Map<string, RegExp>()
|
|
62
64
|
|
|
63
65
|
constructor(grammar: Grammar) {
|
|
64
66
|
this._grammar = grammar
|
|
@@ -284,15 +286,10 @@ class Lexer {
|
|
|
284
286
|
* @private
|
|
285
287
|
*/
|
|
286
288
|
_unquote(str: string) {
|
|
287
|
-
const quote = str
|
|
288
|
-
let escQuoteRegex = this._escQuoteRegexCache.get(quote)
|
|
289
|
-
if (!escQuoteRegex) {
|
|
290
|
-
escQuoteRegex = new RegExp('\\\\' + quote, 'g')
|
|
291
|
-
this._escQuoteRegexCache.set(quote, escQuoteRegex)
|
|
292
|
-
}
|
|
289
|
+
const quote = str.startsWith('"') ? '"' : "'"
|
|
293
290
|
return str
|
|
294
291
|
.slice(1, -1)
|
|
295
|
-
.replaceAll(escQuoteRegex, quote)
|
|
292
|
+
.replaceAll(escQuoteRegex[quote], quote)
|
|
296
293
|
.replaceAll(escEscRegex, '\\')
|
|
297
294
|
}
|
|
298
295
|
|
package/src/evaluator/compile.ts
CHANGED
|
@@ -4,22 +4,7 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { Grammar } from '../grammar.ts'
|
|
7
|
-
import type {
|
|
8
|
-
ArrayLiteral,
|
|
9
|
-
AssignmentExpression,
|
|
10
|
-
AstNode,
|
|
11
|
-
BinaryExpression,
|
|
12
|
-
ConditionalExpression,
|
|
13
|
-
FilterExpression,
|
|
14
|
-
FunctionCall,
|
|
15
|
-
Identifier,
|
|
16
|
-
JexlValue,
|
|
17
|
-
Literal,
|
|
18
|
-
ObjectLiteral,
|
|
19
|
-
SequenceExpression,
|
|
20
|
-
TemplateLiteral,
|
|
21
|
-
UnaryExpression
|
|
22
|
-
} from '../types.ts'
|
|
7
|
+
import type { AstNode, AstNodeUnion, JexlValue } from '../types.ts'
|
|
23
8
|
import type Evaluator from './Evaluator.ts'
|
|
24
9
|
|
|
25
10
|
/**
|
|
@@ -28,6 +13,34 @@ import type Evaluator from './Evaluator.ts'
|
|
|
28
13
|
*/
|
|
29
14
|
export type CompiledNode = (ev: Evaluator) => JexlValue
|
|
30
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Writes a key that a plain assignment would mishandle. Storing to
|
|
18
|
+
* "__proto__" invokes the prototype setter rather than creating a property, so
|
|
19
|
+
* an object literal or assignment using that key silently lost its value (and
|
|
20
|
+
* re-pointed the target's prototype). Defining the property instead matches
|
|
21
|
+
* what `JSON.parse('{"__proto__":1}')` produces: an ordinary own property.
|
|
22
|
+
*/
|
|
23
|
+
function defineOwn(
|
|
24
|
+
target: Record<string, JexlValue>,
|
|
25
|
+
key: string,
|
|
26
|
+
value: JexlValue
|
|
27
|
+
) {
|
|
28
|
+
Object.defineProperty(target, key, {
|
|
29
|
+
value,
|
|
30
|
+
writable: true,
|
|
31
|
+
enumerable: true,
|
|
32
|
+
configurable: true
|
|
33
|
+
})
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function assignOwn(
|
|
37
|
+
target: Record<string, JexlValue>,
|
|
38
|
+
key: string,
|
|
39
|
+
value: JexlValue
|
|
40
|
+
) {
|
|
41
|
+
target[key] = value
|
|
42
|
+
}
|
|
43
|
+
|
|
31
44
|
/** Renders an interpolated value for a template literal. */
|
|
32
45
|
function stringify(value: JexlValue) {
|
|
33
46
|
if (value == null) {
|
|
@@ -62,14 +75,17 @@ function stringify(value: JexlValue) {
|
|
|
62
75
|
* @throws {Error} if the tree contains an unrecognized node type
|
|
63
76
|
*/
|
|
64
77
|
export function compileAst(ast: AstNode, grammar: Grammar): CompiledNode {
|
|
65
|
-
|
|
78
|
+
// AstNode types its `type` as a plain string so the Parser can build the tree
|
|
79
|
+
// loosely; narrowing to the union once, here, lets every case below see its
|
|
80
|
+
// own node type instead of repeating the same cast in each branch
|
|
81
|
+
const node = ast as AstNodeUnion
|
|
82
|
+
switch (node.type) {
|
|
66
83
|
case 'Literal': {
|
|
67
|
-
const { value } =
|
|
84
|
+
const { value } = node
|
|
68
85
|
return () => value
|
|
69
86
|
}
|
|
70
87
|
|
|
71
88
|
case 'Identifier': {
|
|
72
|
-
const node = ast as Identifier
|
|
73
89
|
const name = node.value
|
|
74
90
|
if (!node.from) {
|
|
75
91
|
return node.relative
|
|
@@ -91,7 +107,6 @@ export function compileAst(ast: AstNode, grammar: Grammar): CompiledNode {
|
|
|
91
107
|
}
|
|
92
108
|
|
|
93
109
|
case 'BinaryExpression': {
|
|
94
|
-
const node = ast as BinaryExpression
|
|
95
110
|
const op = grammar.elements[node.operator]
|
|
96
111
|
if (op?.type !== 'binaryOp') {
|
|
97
112
|
// matches the tree-walking behaviour: an unknown operator yields
|
|
@@ -119,7 +134,6 @@ export function compileAst(ast: AstNode, grammar: Grammar): CompiledNode {
|
|
|
119
134
|
}
|
|
120
135
|
|
|
121
136
|
case 'UnaryExpression': {
|
|
122
|
-
const node = ast as UnaryExpression
|
|
123
137
|
const right = compileAst(node.right!, grammar)
|
|
124
138
|
const elem = grammar.elements[node.operator]
|
|
125
139
|
const fn =
|
|
@@ -139,7 +153,6 @@ export function compileAst(ast: AstNode, grammar: Grammar): CompiledNode {
|
|
|
139
153
|
}
|
|
140
154
|
|
|
141
155
|
case 'ConditionalExpression': {
|
|
142
|
-
const node = ast as ConditionalExpression
|
|
143
156
|
const test = compileAst(node.test, grammar)
|
|
144
157
|
const consequent = node.consequent
|
|
145
158
|
? compileAst(node.consequent, grammar)
|
|
@@ -158,7 +171,6 @@ export function compileAst(ast: AstNode, grammar: Grammar): CompiledNode {
|
|
|
158
171
|
}
|
|
159
172
|
|
|
160
173
|
case 'FilterExpression': {
|
|
161
|
-
const node = ast as FilterExpression
|
|
162
174
|
if (node.relative) {
|
|
163
175
|
// thrown on evaluation rather than compilation, as before
|
|
164
176
|
return () => {
|
|
@@ -180,9 +192,7 @@ export function compileAst(ast: AstNode, grammar: Grammar): CompiledNode {
|
|
|
180
192
|
}
|
|
181
193
|
|
|
182
194
|
case 'ArrayLiteral': {
|
|
183
|
-
const items =
|
|
184
|
-
compileAst(item, grammar)
|
|
185
|
-
)
|
|
195
|
+
const items = node.value.map((item) => compileAst(item, grammar))
|
|
186
196
|
const len = items.length
|
|
187
197
|
return (ev) => {
|
|
188
198
|
const out: JexlValue[] = new Array(len)
|
|
@@ -194,14 +204,16 @@ export function compileAst(ast: AstNode, grammar: Grammar): CompiledNode {
|
|
|
194
204
|
}
|
|
195
205
|
|
|
196
206
|
case 'ObjectLiteral': {
|
|
197
|
-
const entries = Object.entries(
|
|
207
|
+
const entries = Object.entries(node.value)
|
|
198
208
|
const keys = entries.map(([key]) => key)
|
|
199
|
-
const values = entries.map(([,
|
|
209
|
+
const values = entries.map(([, value]) => compileAst(value, grammar))
|
|
200
210
|
const len = keys.length
|
|
211
|
+
// resolved once, here, so the common case keeps its plain store
|
|
212
|
+
const store = keys.includes('__proto__') ? defineOwn : assignOwn
|
|
201
213
|
return (ev) => {
|
|
202
214
|
const out: Record<string, JexlValue> = {}
|
|
203
215
|
for (let i = 0; i < len; i++) {
|
|
204
|
-
out
|
|
216
|
+
store(out, keys[i]!, values[i]!(ev))
|
|
205
217
|
}
|
|
206
218
|
return out
|
|
207
219
|
}
|
|
@@ -209,16 +221,14 @@ export function compileAst(ast: AstNode, grammar: Grammar): CompiledNode {
|
|
|
209
221
|
|
|
210
222
|
case 'TemplateLiteral': {
|
|
211
223
|
// every part renders to a string, so these are narrower than CompiledNode
|
|
212
|
-
const parts =
|
|
213
|
-
(part
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return () => value
|
|
217
|
-
}
|
|
218
|
-
const expr = compileAst(part.value, grammar)
|
|
219
|
-
return (ev) => stringify(expr(ev))
|
|
224
|
+
const parts = node.parts.map((part): ((ev: Evaluator) => string) => {
|
|
225
|
+
if (part.type === 'static') {
|
|
226
|
+
const { value } = part
|
|
227
|
+
return () => value
|
|
220
228
|
}
|
|
221
|
-
|
|
229
|
+
const expr = compileAst(part.value, grammar)
|
|
230
|
+
return (ev) => stringify(expr(ev))
|
|
231
|
+
})
|
|
222
232
|
return (ev) => {
|
|
223
233
|
let out = ''
|
|
224
234
|
for (const part of parts) {
|
|
@@ -229,7 +239,6 @@ export function compileAst(ast: AstNode, grammar: Grammar): CompiledNode {
|
|
|
229
239
|
}
|
|
230
240
|
|
|
231
241
|
case 'FunctionCall': {
|
|
232
|
-
const node = ast as FunctionCall
|
|
233
242
|
const { name } = node
|
|
234
243
|
const args = node.args.map((arg) => compileAst(arg, grammar))
|
|
235
244
|
// hasOwn, so that inherited Object.prototype members such as `toString`
|
|
@@ -279,9 +288,7 @@ export function compileAst(ast: AstNode, grammar: Grammar): CompiledNode {
|
|
|
279
288
|
}
|
|
280
289
|
|
|
281
290
|
case 'SequenceExpression': {
|
|
282
|
-
const exprs =
|
|
283
|
-
compileAst(expr, grammar)
|
|
284
|
-
)
|
|
291
|
+
const exprs = node.expressions.map((expr) => compileAst(expr, grammar))
|
|
285
292
|
const len = exprs.length
|
|
286
293
|
return (ev) => {
|
|
287
294
|
let last: JexlValue
|
|
@@ -293,12 +300,12 @@ export function compileAst(ast: AstNode, grammar: Grammar): CompiledNode {
|
|
|
293
300
|
}
|
|
294
301
|
|
|
295
302
|
case 'AssignmentExpression': {
|
|
296
|
-
const node = ast as AssignmentExpression
|
|
297
303
|
const name = node.left.value
|
|
298
304
|
const right = compileAst(node.right!, grammar)
|
|
305
|
+
const store = name === '__proto__' ? defineOwn : assignOwn
|
|
299
306
|
return (ev) => {
|
|
300
307
|
const value = right(ev)
|
|
301
|
-
ev._context
|
|
308
|
+
store(ev._context, name, value)
|
|
302
309
|
return value
|
|
303
310
|
}
|
|
304
311
|
}
|
package/src/grammar.ts
CHANGED
|
@@ -182,18 +182,17 @@ export const getGrammar = (): Grammar => ({
|
|
|
182
182
|
precedence: 20,
|
|
183
183
|
eval: (left, right) => {
|
|
184
184
|
if (typeof right === 'string') {
|
|
185
|
-
|
|
186
|
-
|
|
185
|
+
// only a primitive has a meaningful substring form. An absent or
|
|
186
|
+
// structured left operand is not "in" a string, and must not be
|
|
187
|
+
// coerced to '' — every string contains the empty string, so that
|
|
188
|
+
// made `feature.missingAttr in "someString"` true for every feature.
|
|
189
|
+
return typeof left === 'string' ||
|
|
187
190
|
typeof left === 'number' ||
|
|
188
191
|
typeof left === 'boolean'
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
return right.includes(leftStr)
|
|
192
|
+
? right.includes(String(left))
|
|
193
|
+
: false
|
|
192
194
|
}
|
|
193
|
-
|
|
194
|
-
return right.includes(left)
|
|
195
|
-
}
|
|
196
|
-
return false
|
|
195
|
+
return Array.isArray(right) ? right.includes(left) : false
|
|
197
196
|
}
|
|
198
197
|
},
|
|
199
198
|
'!': {
|
package/src/parser/Parser.ts
CHANGED
|
@@ -10,6 +10,25 @@ import type Lexer from '../Lexer.ts'
|
|
|
10
10
|
import type { Grammar } from '../grammar.ts'
|
|
11
11
|
import type { AstNode, SequenceExpression, Token } from '../types.ts'
|
|
12
12
|
|
|
13
|
+
/**
|
|
14
|
+
* The handler to run for a token type when the state it appears in doesn't
|
|
15
|
+
* name one of its own. Token types absent here — the brackets, parens and
|
|
16
|
+
* separators — carry no behavior beyond the state transition the state machine
|
|
17
|
+
* gives them.
|
|
18
|
+
*/
|
|
19
|
+
const tokenHandlers: Record<
|
|
20
|
+
string,
|
|
21
|
+
((this: Parser, token: Token) => void) | undefined
|
|
22
|
+
> = {
|
|
23
|
+
binaryOp: handlers.binaryOp,
|
|
24
|
+
dot: handlers.dot,
|
|
25
|
+
identifier: handlers.identifier,
|
|
26
|
+
literal: handlers.literal,
|
|
27
|
+
semicolon: handlers.semicolon,
|
|
28
|
+
templateString: handlers.templateString,
|
|
29
|
+
unaryOp: handlers.unaryOp
|
|
30
|
+
}
|
|
31
|
+
|
|
13
32
|
/**
|
|
14
33
|
* The Parser is a state machine that converts tokens from the {@link Lexer}
|
|
15
34
|
* into an Abstract Syntax Tree (AST), capable of being evaluated in any
|
|
@@ -76,10 +95,8 @@ class Parser {
|
|
|
76
95
|
const startExpr = this._exprStr
|
|
77
96
|
this._exprStr += token.raw
|
|
78
97
|
if (state.subHandler) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
const stopState = this._subParser!.addToken(token)
|
|
98
|
+
const subParser = this._subParser ?? this._startSubExpression(startExpr)
|
|
99
|
+
const stopState = subParser.addToken(token)
|
|
83
100
|
if (stopState) {
|
|
84
101
|
this._endSubExpression()
|
|
85
102
|
if (stopState === '_semicolon') {
|
|
@@ -94,18 +111,10 @@ class Parser {
|
|
|
94
111
|
return this._stopMap[token.type]!
|
|
95
112
|
} else if (state.tokenTypes?.[token.type]) {
|
|
96
113
|
const typeOpts = state.tokenTypes[token.type]!
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
>
|
|
102
|
-
)[token.type]
|
|
103
|
-
if (typeOpts.handler) {
|
|
104
|
-
handleFunc = typeOpts.handler
|
|
105
|
-
}
|
|
106
|
-
if (handleFunc) {
|
|
107
|
-
handleFunc.call(this, token)
|
|
108
|
-
}
|
|
114
|
+
// the state's own handler wins over the token type's default one, and a
|
|
115
|
+
// token type with neither only drives the transition below
|
|
116
|
+
const handleFunc = typeOpts.handler ?? tokenHandlers[token.type]
|
|
117
|
+
handleFunc?.call(this, token)
|
|
109
118
|
if (typeOpts.toState) {
|
|
110
119
|
this._state = typeOpts.toState
|
|
111
120
|
}
|
|
@@ -140,7 +149,14 @@ class Parser {
|
|
|
140
149
|
* the expression, indicating that the expression is incomplete
|
|
141
150
|
*/
|
|
142
151
|
complete() {
|
|
143
|
-
|
|
152
|
+
// `_subParser` counts as unfinished business alongside `_cursor`: a group
|
|
153
|
+
// that opened before anything was placed in the tree, as in "(1", leaves
|
|
154
|
+
// the cursor null, and testing the cursor alone let the missing ")" pass
|
|
155
|
+
// silently and evaluate as if it were closed.
|
|
156
|
+
if (
|
|
157
|
+
(this._cursor || this._subParser) &&
|
|
158
|
+
!states[this._state]!.completable
|
|
159
|
+
) {
|
|
144
160
|
throw new Error(`Unexpected end of expression: ${this._exprStr}`)
|
|
145
161
|
}
|
|
146
162
|
if (this._subParser) {
|
|
@@ -231,6 +247,7 @@ class Parser {
|
|
|
231
247
|
* Prepares the Parser to accept a subexpression by (re)instantiating the
|
|
232
248
|
* subParser.
|
|
233
249
|
* @param {string} [exprStr] The expression string to prefix to the new Parser
|
|
250
|
+
* @returns {Parser} the new subParser, also stored on this instance
|
|
234
251
|
* @private
|
|
235
252
|
*/
|
|
236
253
|
_startSubExpression(exprStr?: string) {
|
|
@@ -243,6 +260,7 @@ class Parser {
|
|
|
243
260
|
endStates = { ...endStates, semicolon: '_semicolon' }
|
|
244
261
|
}
|
|
245
262
|
this._subParser = new Parser(this._grammar, this._lexer, exprStr, endStates)
|
|
263
|
+
return this._subParser
|
|
246
264
|
}
|
|
247
265
|
}
|
|
248
266
|
|
package/src/parser/handlers.ts
CHANGED
|
@@ -4,6 +4,9 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { precedenceOf } from '../grammar.ts'
|
|
7
|
+
// circular with Parser.ts, which is fine: the binding is only read from inside
|
|
8
|
+
// a handler, long after both modules have finished evaluating
|
|
9
|
+
import Parser from './Parser.ts'
|
|
7
10
|
|
|
8
11
|
import type {
|
|
9
12
|
ArrayLiteral,
|
|
@@ -20,7 +23,6 @@ import type {
|
|
|
20
23
|
Token,
|
|
21
24
|
UnaryExpression
|
|
22
25
|
} from '../types.ts'
|
|
23
|
-
import type Parser from './Parser.ts'
|
|
24
26
|
|
|
25
27
|
/**
|
|
26
28
|
* Handles a subexpression that's used to define a transform argument's value.
|
|
@@ -145,13 +147,26 @@ export function filter(this: Parser, ast: AstNode | null) {
|
|
|
145
147
|
/**
|
|
146
148
|
* Handles identifier tokens when used to indicate the name of a function to
|
|
147
149
|
* be called.
|
|
148
|
-
*
|
|
150
|
+
*
|
|
151
|
+
* A call written against a value, `a.b(x)`, becomes `b(a, x)`: jexl functions
|
|
152
|
+
* live in one global pool and have no receiver, so the value the name was read
|
|
153
|
+
* from is passed as the first argument instead. Every function in the pool is
|
|
154
|
+
* already written that way — `get(feature, key)`, `split(str, sep)` — which is
|
|
155
|
+
* what makes the two spellings equivalent. Previously the receiver was simply
|
|
156
|
+
* dropped, so `refName.split(' ')` quietly called `split(' ')`.
|
|
149
157
|
*/
|
|
150
158
|
export function functionCall(this: Parser) {
|
|
159
|
+
const cursor = this._cursor
|
|
160
|
+
if (cursor?.type !== 'Identifier') {
|
|
161
|
+
// reached by `a[expr]()`, whose subject is a filter rather than a name;
|
|
162
|
+
// there is nothing to look up in the function pool
|
|
163
|
+
throw new Error(`Functions must be called by name: ${this._exprStr}`)
|
|
164
|
+
}
|
|
165
|
+
const { from } = cursor as Identifier
|
|
151
166
|
const node: FunctionCall = {
|
|
152
167
|
type: 'FunctionCall',
|
|
153
|
-
name: (
|
|
154
|
-
args: [],
|
|
168
|
+
name: (cursor as Identifier).value,
|
|
169
|
+
args: from ? [from] : [],
|
|
155
170
|
pool: 'functions'
|
|
156
171
|
}
|
|
157
172
|
this._placeBeforeCursor(node)
|
|
@@ -209,11 +224,7 @@ export function templateString(this: Parser, token: Token) {
|
|
|
209
224
|
})
|
|
210
225
|
} else {
|
|
211
226
|
const subTokens = this._lexer.tokenize(part.value)
|
|
212
|
-
const
|
|
213
|
-
grammar: typeof this._grammar,
|
|
214
|
-
lexer: typeof this._lexer
|
|
215
|
-
) => Parser
|
|
216
|
-
const subParser = new ParserClass(this._grammar, this._lexer)
|
|
227
|
+
const subParser = new Parser(this._grammar, this._lexer)
|
|
217
228
|
subParser.addTokens(subTokens)
|
|
218
229
|
const subAst = subParser.complete()
|
|
219
230
|
|
|
@@ -261,7 +272,19 @@ export function objStart(this: Parser) {
|
|
|
261
272
|
* @param {{type: <string>}} ast The subexpression tree
|
|
262
273
|
*/
|
|
263
274
|
export function objVal(this: Parser, ast: AstNode | null) {
|
|
264
|
-
|
|
275
|
+
// defined rather than assigned so that the key "__proto__" becomes an
|
|
276
|
+
// ordinary entry of the node's key map instead of re-pointing its prototype,
|
|
277
|
+
// which dropped the entry before the Evaluator ever saw it
|
|
278
|
+
Object.defineProperty(
|
|
279
|
+
(this._cursor as ObjectLiteral).value,
|
|
280
|
+
this._curObjKey!,
|
|
281
|
+
{
|
|
282
|
+
value: ast!,
|
|
283
|
+
writable: true,
|
|
284
|
+
enumerable: true,
|
|
285
|
+
configurable: true
|
|
286
|
+
}
|
|
287
|
+
)
|
|
265
288
|
}
|
|
266
289
|
|
|
267
290
|
/**
|