@jbrowse/jexl 3.0.2 → 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 +26 -2
- package/dist/Expression.d.ts +19 -7
- package/dist/Expression.js +32 -11
- package/dist/Expression.js.map +1 -1
- package/dist/Jexl.d.ts +13 -12
- package/dist/Jexl.js +31 -30
- package/dist/Jexl.js.map +1 -1
- package/dist/Lexer.d.ts +9 -9
- package/dist/Lexer.js +61 -24
- package/dist/Lexer.js.map +1 -1
- package/dist/evaluator/Evaluator.d.ts +20 -34
- package/dist/evaluator/Evaluator.js +19 -64
- package/dist/evaluator/Evaluator.js.map +1 -1
- package/dist/evaluator/compile.d.ts +27 -0
- package/dist/evaluator/compile.js +279 -0
- package/dist/evaluator/compile.js.map +1 -0
- package/dist/grammar.d.ts +29 -9
- package/dist/grammar.js +23 -7
- package/dist/grammar.js.map +1 -1
- package/dist/parser/Parser.d.ts +7 -8
- package/dist/parser/Parser.js +46 -14
- package/dist/parser/Parser.js.map +1 -1
- package/dist/parser/handlers.d.ts +8 -8
- package/dist/parser/handlers.js +84 -51
- package/dist/parser/handlers.js.map +1 -1
- package/dist/parser/states.d.ts +2 -2
- package/dist/parser/states.js +19 -40
- package/dist/parser/states.js.map +1 -1
- package/dist/types.d.ts +18 -7
- package/esm/Expression.d.ts +19 -7
- package/esm/Expression.js +32 -11
- package/esm/Expression.js.map +1 -1
- package/esm/Jexl.d.ts +13 -12
- package/esm/Jexl.js +31 -30
- package/esm/Jexl.js.map +1 -1
- package/esm/Lexer.d.ts +9 -9
- package/esm/Lexer.js +61 -24
- package/esm/Lexer.js.map +1 -1
- package/esm/evaluator/Evaluator.d.ts +20 -34
- package/esm/evaluator/Evaluator.js +19 -31
- package/esm/evaluator/Evaluator.js.map +1 -1
- package/esm/evaluator/compile.d.ts +27 -0
- package/esm/evaluator/compile.js +276 -0
- package/esm/evaluator/compile.js.map +1 -0
- package/esm/grammar.d.ts +29 -9
- package/esm/grammar.js +22 -7
- package/esm/grammar.js.map +1 -1
- package/esm/parser/Parser.d.ts +7 -8
- package/esm/parser/Parser.js +46 -14
- package/esm/parser/Parser.js.map +1 -1
- package/esm/parser/handlers.d.ts +8 -8
- package/esm/parser/handlers.js +81 -50
- package/esm/parser/handlers.js.map +1 -1
- package/esm/parser/states.d.ts +2 -2
- package/esm/parser/states.js +19 -40
- package/esm/parser/states.js.map +1 -1
- package/esm/types.d.ts +18 -7
- package/package.json +25 -28
- package/src/Expression.ts +31 -17
- package/src/Jexl.ts +59 -21
- package/src/Lexer.ts +63 -33
- package/src/evaluator/Evaluator.ts +27 -44
- package/src/evaluator/compile.ts +317 -0
- package/src/grammar.ts +69 -21
- package/src/parser/Parser.ts +50 -28
- package/src/parser/handlers.ts +122 -76
- package/src/parser/states.ts +22 -42
- package/src/types.ts +22 -7
- package/CHANGELOG.md +0 -249
- package/dist/evaluator/handlers.d.ts +0 -112
- package/dist/evaluator/handlers.js +0 -208
- package/dist/evaluator/handlers.js.map +0 -1
- package/esm/evaluator/handlers.d.ts +0 -112
- package/esm/evaluator/handlers.js +0 -194
- package/esm/evaluator/handlers.js.map +0 -1
- package/src/evaluator/handlers.ts +0 -212
package/src/parser/handlers.ts
CHANGED
|
@@ -3,8 +3,26 @@
|
|
|
3
3
|
* Copyright 2020 Tom Shawver
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
|
|
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'
|
|
10
|
+
|
|
11
|
+
import type {
|
|
12
|
+
ArrayLiteral,
|
|
13
|
+
AssignmentExpression,
|
|
14
|
+
AstNode,
|
|
15
|
+
BinaryExpression,
|
|
16
|
+
ConditionalExpression,
|
|
17
|
+
FilterExpression,
|
|
18
|
+
FunctionCall,
|
|
19
|
+
Identifier,
|
|
20
|
+
Literal,
|
|
21
|
+
ObjectLiteral,
|
|
22
|
+
TemplateLiteral,
|
|
23
|
+
Token,
|
|
24
|
+
UnaryExpression
|
|
25
|
+
} from '../types.ts'
|
|
8
26
|
|
|
9
27
|
/**
|
|
10
28
|
* Handles a subexpression that's used to define a transform argument's value.
|
|
@@ -12,7 +30,7 @@ import type Parser from './Parser.ts'
|
|
|
12
30
|
*/
|
|
13
31
|
export function argVal(this: Parser, ast: AstNode | null) {
|
|
14
32
|
if (ast) {
|
|
15
|
-
;(this._cursor as
|
|
33
|
+
;(this._cursor as FunctionCall).args.push(ast)
|
|
16
34
|
}
|
|
17
35
|
}
|
|
18
36
|
|
|
@@ -21,10 +39,11 @@ export function argVal(this: Parser, ast: AstNode | null) {
|
|
|
21
39
|
* initialized with an empty array.
|
|
22
40
|
*/
|
|
23
41
|
export function arrayStart(this: Parser) {
|
|
24
|
-
|
|
42
|
+
const node: ArrayLiteral = {
|
|
25
43
|
type: 'ArrayLiteral',
|
|
26
44
|
value: []
|
|
27
|
-
}
|
|
45
|
+
}
|
|
46
|
+
this._placeAtCursor(node)
|
|
28
47
|
}
|
|
29
48
|
|
|
30
49
|
/**
|
|
@@ -33,7 +52,7 @@ export function arrayStart(this: Parser) {
|
|
|
33
52
|
*/
|
|
34
53
|
export function arrayVal(this: Parser, ast: AstNode | null) {
|
|
35
54
|
if (ast) {
|
|
36
|
-
;(this._cursor as
|
|
55
|
+
;(this._cursor as ArrayLiteral).value.push(ast)
|
|
37
56
|
}
|
|
38
57
|
}
|
|
39
58
|
|
|
@@ -44,34 +63,45 @@ export function arrayVal(this: Parser, ast: AstNode | null) {
|
|
|
44
63
|
*/
|
|
45
64
|
export function binaryOp(this: Parser, token: Token) {
|
|
46
65
|
if (token.value === '=') {
|
|
47
|
-
|
|
66
|
+
// `from` marks a member access such as `a.b`; only a bare name can be
|
|
67
|
+
// assigned to, since the Evaluator writes straight into the context
|
|
68
|
+
if (
|
|
69
|
+
this._cursor?.type !== 'Identifier' ||
|
|
70
|
+
(this._cursor as Identifier).from
|
|
71
|
+
) {
|
|
48
72
|
throw new Error('Left side of assignment must be a variable name')
|
|
49
73
|
}
|
|
50
74
|
|
|
51
|
-
const node:
|
|
75
|
+
const node: AssignmentExpression = {
|
|
52
76
|
type: 'AssignmentExpression',
|
|
53
77
|
operator: '=',
|
|
54
|
-
left: this._cursor
|
|
78
|
+
left: this._cursor as Identifier
|
|
55
79
|
}
|
|
56
80
|
this._cursor = this._cursor._parent
|
|
57
81
|
this._placeAtCursor(node)
|
|
58
82
|
return
|
|
59
83
|
}
|
|
60
84
|
|
|
61
|
-
const
|
|
85
|
+
const tokenValue = token.value as string
|
|
86
|
+
const precedence = precedenceOf(this._grammar.elements[tokenValue])
|
|
62
87
|
let parent = this._cursor?._parent
|
|
63
|
-
while (
|
|
64
|
-
parent
|
|
65
|
-
(
|
|
66
|
-
|
|
67
|
-
|
|
88
|
+
while (parent) {
|
|
89
|
+
const parentExpr = parent as BinaryExpression
|
|
90
|
+
if (!parentExpr.operator) {
|
|
91
|
+
break
|
|
92
|
+
}
|
|
93
|
+
if (
|
|
94
|
+
precedenceOf(this._grammar.elements[parentExpr.operator]) < precedence
|
|
95
|
+
) {
|
|
96
|
+
break
|
|
97
|
+
}
|
|
68
98
|
this._cursor = parent
|
|
69
99
|
parent = parent._parent
|
|
70
100
|
}
|
|
71
|
-
const node:
|
|
101
|
+
const node: BinaryExpression = {
|
|
72
102
|
type: 'BinaryExpression',
|
|
73
|
-
operator:
|
|
74
|
-
left: this._cursor
|
|
103
|
+
operator: tokenValue,
|
|
104
|
+
left: this._cursor!
|
|
75
105
|
}
|
|
76
106
|
this._setParent(this._cursor!, node)
|
|
77
107
|
this._cursor = parent
|
|
@@ -84,16 +114,16 @@ export function binaryOp(this: Parser, token: Token) {
|
|
|
84
114
|
* AST.
|
|
85
115
|
*/
|
|
86
116
|
export function dot(this: Parser) {
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
117
|
+
const cursor = this._cursor
|
|
118
|
+
// the dot continues an identifier chain unless the cursor is an operator
|
|
119
|
+
// still waiting on its right-hand side, in which case it opens a relative
|
|
120
|
+
// path instead — as in the leading dot of `list[.price > 5]`
|
|
90
121
|
this._nextIdentEncapsulate =
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
(
|
|
94
|
-
isBinaryExprWithRight(this._cursor))
|
|
122
|
+
!!cursor &&
|
|
123
|
+
cursor.type !== 'UnaryExpression' &&
|
|
124
|
+
(cursor.type !== 'BinaryExpression' || !!cursor.right)
|
|
95
125
|
|
|
96
|
-
this._nextIdentRelative = !this.
|
|
126
|
+
this._nextIdentRelative = !this._nextIdentEncapsulate
|
|
97
127
|
if (this._nextIdentRelative) {
|
|
98
128
|
this._relative = true
|
|
99
129
|
}
|
|
@@ -105,26 +135,41 @@ export function dot(this: Parser) {
|
|
|
105
135
|
* @param {{type: <string>}} ast The subexpression tree
|
|
106
136
|
*/
|
|
107
137
|
export function filter(this: Parser, ast: AstNode | null) {
|
|
108
|
-
|
|
138
|
+
const node: FilterExpression = {
|
|
109
139
|
type: 'FilterExpression',
|
|
110
|
-
expr: ast
|
|
140
|
+
expr: ast!,
|
|
111
141
|
relative: this._subParser!.isRelative(),
|
|
112
|
-
subject: this._cursor
|
|
113
|
-
}
|
|
142
|
+
subject: this._cursor!
|
|
143
|
+
}
|
|
144
|
+
this._placeBeforeCursor(node)
|
|
114
145
|
}
|
|
115
146
|
|
|
116
147
|
/**
|
|
117
148
|
* Handles identifier tokens when used to indicate the name of a function to
|
|
118
149
|
* be called.
|
|
119
|
-
*
|
|
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(' ')`.
|
|
120
157
|
*/
|
|
121
158
|
export function functionCall(this: Parser) {
|
|
122
|
-
this.
|
|
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
|
|
166
|
+
const node: FunctionCall = {
|
|
123
167
|
type: 'FunctionCall',
|
|
124
|
-
name: (
|
|
125
|
-
args: [],
|
|
168
|
+
name: (cursor as Identifier).value,
|
|
169
|
+
args: from ? [from] : [],
|
|
126
170
|
pool: 'functions'
|
|
127
|
-
}
|
|
171
|
+
}
|
|
172
|
+
this._placeBeforeCursor(node)
|
|
128
173
|
}
|
|
129
174
|
|
|
130
175
|
/**
|
|
@@ -132,12 +177,12 @@ export function functionCall(this: Parser) {
|
|
|
132
177
|
* @param {{type: <string>}} token A token object
|
|
133
178
|
*/
|
|
134
179
|
export function identifier(this: Parser, token: Token) {
|
|
135
|
-
const node:
|
|
180
|
+
const node: Identifier = {
|
|
136
181
|
type: 'Identifier',
|
|
137
|
-
value: token.value
|
|
182
|
+
value: token.value as string
|
|
138
183
|
}
|
|
139
184
|
if (this._nextIdentEncapsulate) {
|
|
140
|
-
node.from = this._cursor
|
|
185
|
+
node.from = this._cursor!
|
|
141
186
|
this._placeBeforeCursor(node)
|
|
142
187
|
this._nextIdentEncapsulate = false
|
|
143
188
|
} else {
|
|
@@ -155,10 +200,11 @@ export function identifier(this: Parser, token: Token) {
|
|
|
155
200
|
* @param {{type: <string>}} token A token object
|
|
156
201
|
*/
|
|
157
202
|
export function literal(this: Parser, token: Token) {
|
|
158
|
-
|
|
203
|
+
const node: Literal = {
|
|
159
204
|
type: 'Literal',
|
|
160
|
-
value: token.value
|
|
161
|
-
}
|
|
205
|
+
value: token.value as string | number | boolean
|
|
206
|
+
}
|
|
207
|
+
this._placeAtCursor(node)
|
|
162
208
|
}
|
|
163
209
|
|
|
164
210
|
/**
|
|
@@ -167,9 +213,10 @@ export function literal(this: Parser, token: Token) {
|
|
|
167
213
|
* @param {{type: <string>}} token A token object
|
|
168
214
|
*/
|
|
169
215
|
export function templateString(this: Parser, token: Token) {
|
|
170
|
-
const parts:
|
|
216
|
+
const parts: TemplateLiteral['parts'] = []
|
|
217
|
+
const tokenParts = Array.isArray(token.value) ? token.value : []
|
|
171
218
|
|
|
172
|
-
for (const part of
|
|
219
|
+
for (const part of tokenParts) {
|
|
173
220
|
if (part.type === 'static') {
|
|
174
221
|
parts.push({
|
|
175
222
|
type: 'static',
|
|
@@ -177,10 +224,7 @@ export function templateString(this: Parser, token: Token) {
|
|
|
177
224
|
})
|
|
178
225
|
} else {
|
|
179
226
|
const subTokens = this._lexer.tokenize(part.value)
|
|
180
|
-
const subParser = new (this.
|
|
181
|
-
this._grammar,
|
|
182
|
-
this._lexer
|
|
183
|
-
)
|
|
227
|
+
const subParser = new Parser(this._grammar, this._lexer)
|
|
184
228
|
subParser.addTokens(subTokens)
|
|
185
229
|
const subAst = subParser.complete()
|
|
186
230
|
|
|
@@ -195,10 +239,11 @@ export function templateString(this: Parser, token: Token) {
|
|
|
195
239
|
}
|
|
196
240
|
}
|
|
197
241
|
|
|
198
|
-
|
|
242
|
+
const node: TemplateLiteral = {
|
|
199
243
|
type: 'TemplateLiteral',
|
|
200
|
-
parts
|
|
201
|
-
}
|
|
244
|
+
parts
|
|
245
|
+
}
|
|
246
|
+
this._placeAtCursor(node)
|
|
202
247
|
}
|
|
203
248
|
|
|
204
249
|
/**
|
|
@@ -206,7 +251,7 @@ export function templateString(this: Parser, token: Token) {
|
|
|
206
251
|
* @param {{type: <string>}} token A token object
|
|
207
252
|
*/
|
|
208
253
|
export function objKey(this: Parser, token: Token) {
|
|
209
|
-
this._curObjKey = token.value
|
|
254
|
+
this._curObjKey = token.value as string
|
|
210
255
|
}
|
|
211
256
|
|
|
212
257
|
/**
|
|
@@ -214,10 +259,11 @@ export function objKey(this: Parser, token: Token) {
|
|
|
214
259
|
* initialized with an empty object.
|
|
215
260
|
*/
|
|
216
261
|
export function objStart(this: Parser) {
|
|
217
|
-
|
|
262
|
+
const node: ObjectLiteral = {
|
|
218
263
|
type: 'ObjectLiteral',
|
|
219
264
|
value: {}
|
|
220
|
-
}
|
|
265
|
+
}
|
|
266
|
+
this._placeAtCursor(node)
|
|
221
267
|
}
|
|
222
268
|
|
|
223
269
|
/**
|
|
@@ -226,7 +272,19 @@ export function objStart(this: Parser) {
|
|
|
226
272
|
* @param {{type: <string>}} ast The subexpression tree
|
|
227
273
|
*/
|
|
228
274
|
export function objVal(this: Parser, ast: AstNode | null) {
|
|
229
|
-
|
|
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
|
+
)
|
|
230
288
|
}
|
|
231
289
|
|
|
232
290
|
/**
|
|
@@ -243,7 +301,7 @@ export function subExpression(this: Parser, ast: AstNode | null) {
|
|
|
243
301
|
* @param {{type: <string>}} ast The subexpression tree
|
|
244
302
|
*/
|
|
245
303
|
export function ternaryEnd(this: Parser, ast: AstNode | null) {
|
|
246
|
-
;(this._cursor as
|
|
304
|
+
;(this._cursor as ConditionalExpression).alternate = ast ?? undefined
|
|
247
305
|
}
|
|
248
306
|
|
|
249
307
|
/**
|
|
@@ -251,7 +309,7 @@ export function ternaryEnd(this: Parser, ast: AstNode | null) {
|
|
|
251
309
|
* @param {{type: <string>}} ast The subexpression tree
|
|
252
310
|
*/
|
|
253
311
|
export function ternaryMid(this: Parser, ast: AstNode | null) {
|
|
254
|
-
;(this._cursor as
|
|
312
|
+
;(this._cursor as ConditionalExpression).consequent = ast ?? undefined
|
|
255
313
|
}
|
|
256
314
|
|
|
257
315
|
/**
|
|
@@ -260,37 +318,25 @@ export function ternaryMid(this: Parser, ast: AstNode | null) {
|
|
|
260
318
|
* test element.
|
|
261
319
|
*/
|
|
262
320
|
export function ternaryStart(this: Parser) {
|
|
263
|
-
|
|
321
|
+
const node: ConditionalExpression = {
|
|
264
322
|
type: 'ConditionalExpression',
|
|
265
|
-
test: this._tree
|
|
266
|
-
}
|
|
323
|
+
test: this._tree!
|
|
324
|
+
}
|
|
325
|
+
this._tree = node
|
|
267
326
|
this._cursor = this._tree
|
|
268
327
|
}
|
|
269
328
|
|
|
270
|
-
/**
|
|
271
|
-
* Handles identifier tokens when used to indicate the name of a transform to
|
|
272
|
-
* be applied.
|
|
273
|
-
* @param {{type: <string>}} token A token object
|
|
274
|
-
*/
|
|
275
|
-
export function transform(this: Parser, token: Token) {
|
|
276
|
-
this._placeBeforeCursor({
|
|
277
|
-
type: 'FunctionCall',
|
|
278
|
-
name: token.value,
|
|
279
|
-
args: [this._cursor],
|
|
280
|
-
pool: 'transforms'
|
|
281
|
-
} as any)
|
|
282
|
-
}
|
|
283
|
-
|
|
284
329
|
/**
|
|
285
330
|
* Handles token of type 'unaryOp', indicating that the operation has only
|
|
286
331
|
* one input: a right side.
|
|
287
332
|
* @param {{type: <string>}} token A token object
|
|
288
333
|
*/
|
|
289
334
|
export function unaryOp(this: Parser, token: Token) {
|
|
290
|
-
|
|
335
|
+
const node: UnaryExpression = {
|
|
291
336
|
type: 'UnaryExpression',
|
|
292
|
-
operator: token.value
|
|
293
|
-
}
|
|
337
|
+
operator: token.value as string
|
|
338
|
+
}
|
|
339
|
+
this._placeAtCursor(node)
|
|
294
340
|
}
|
|
295
341
|
|
|
296
342
|
/**
|
package/src/parser/states.ts
CHANGED
|
@@ -5,12 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
import * as h from './handlers.ts'
|
|
7
7
|
|
|
8
|
-
import type { AstNode } from '../types.ts'
|
|
8
|
+
import type { AstNode, Token } from '../types.ts'
|
|
9
9
|
import type Parser from './Parser.ts'
|
|
10
10
|
|
|
11
11
|
interface TokenTypeOptions {
|
|
12
12
|
toState?: string
|
|
13
|
-
handler?: (this: Parser,
|
|
13
|
+
handler?: (this: Parser, token: Token) => void
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
interface StateDefinition {
|
|
@@ -22,6 +22,20 @@ interface StateDefinition {
|
|
|
22
22
|
|
|
23
23
|
type States = Record<string, StateDefinition>
|
|
24
24
|
|
|
25
|
+
/**
|
|
26
|
+
* The tokens that may follow a finished operand: an operator to combine it
|
|
27
|
+
* with, a dot or subscript to keep drilling into it, a question mark opening a
|
|
28
|
+
* ternary, or a semicolon ending the statement. Shared by every state that
|
|
29
|
+
* sits on a complete value, which is also why each of them is completable.
|
|
30
|
+
*/
|
|
31
|
+
const afterOperand: Record<string, TokenTypeOptions> = {
|
|
32
|
+
binaryOp: { toState: 'expectOperand' },
|
|
33
|
+
dot: { toState: 'traverse' },
|
|
34
|
+
openBracket: { toState: 'filter' },
|
|
35
|
+
question: { toState: 'ternaryMid', handler: h.ternaryStart },
|
|
36
|
+
semicolon: { handler: h.semicolon }
|
|
37
|
+
}
|
|
38
|
+
|
|
25
39
|
/**
|
|
26
40
|
* A mapping of all states in the finite state machine to a set of instructions
|
|
27
41
|
* for handling or transitioning into other states. Each state can be handled
|
|
@@ -68,21 +82,9 @@ export const states: States = {
|
|
|
68
82
|
}
|
|
69
83
|
},
|
|
70
84
|
expectBinOp: {
|
|
71
|
-
tokenTypes:
|
|
72
|
-
binaryOp: { toState: 'expectOperand' },
|
|
73
|
-
pipe: { toState: 'expectTransform' },
|
|
74
|
-
dot: { toState: 'traverse' },
|
|
75
|
-
openBracket: { toState: 'filter' },
|
|
76
|
-
question: { toState: 'ternaryMid', handler: h.ternaryStart },
|
|
77
|
-
semicolon: { handler: h.semicolon }
|
|
78
|
-
},
|
|
85
|
+
tokenTypes: afterOperand,
|
|
79
86
|
completable: true
|
|
80
87
|
},
|
|
81
|
-
expectTransform: {
|
|
82
|
-
tokenTypes: {
|
|
83
|
-
identifier: { toState: 'postTransform', handler: h.transform }
|
|
84
|
-
}
|
|
85
|
-
},
|
|
86
88
|
expectObjKey: {
|
|
87
89
|
tokenTypes: {
|
|
88
90
|
literal: { toState: 'expectKeyValSep', handler: h.objKey },
|
|
@@ -95,38 +97,16 @@ export const states: States = {
|
|
|
95
97
|
colon: { toState: 'objVal' }
|
|
96
98
|
}
|
|
97
99
|
},
|
|
98
|
-
postTransform: {
|
|
99
|
-
tokenTypes: {
|
|
100
|
-
openParen: { toState: 'argVal' },
|
|
101
|
-
binaryOp: { toState: 'expectOperand' },
|
|
102
|
-
dot: { toState: 'traverse' },
|
|
103
|
-
openBracket: { toState: 'filter' },
|
|
104
|
-
pipe: { toState: 'expectTransform' },
|
|
105
|
-
question: { toState: 'ternaryMid', handler: h.ternaryStart },
|
|
106
|
-
semicolon: { handler: h.semicolon }
|
|
107
|
-
},
|
|
108
|
-
completable: true
|
|
109
|
-
},
|
|
110
100
|
postArgs: {
|
|
111
|
-
tokenTypes:
|
|
112
|
-
binaryOp: { toState: 'expectOperand' },
|
|
113
|
-
dot: { toState: 'traverse' },
|
|
114
|
-
openBracket: { toState: 'filter' },
|
|
115
|
-
pipe: { toState: 'expectTransform' },
|
|
116
|
-
question: { toState: 'ternaryMid', handler: h.ternaryStart },
|
|
117
|
-
semicolon: { handler: h.semicolon }
|
|
118
|
-
},
|
|
101
|
+
tokenTypes: afterOperand,
|
|
119
102
|
completable: true
|
|
120
103
|
},
|
|
121
104
|
identifier: {
|
|
122
105
|
tokenTypes: {
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
openParen: { toState: 'argVal', handler: h.functionCall }
|
|
127
|
-
pipe: { toState: 'expectTransform' },
|
|
128
|
-
question: { toState: 'ternaryMid', handler: h.ternaryStart },
|
|
129
|
-
semicolon: { handler: h.semicolon }
|
|
106
|
+
...afterOperand,
|
|
107
|
+
// a name is the only operand that can be followed by an argument list,
|
|
108
|
+
// since a function is called by name rather than evaluated as a value
|
|
109
|
+
openParen: { toState: 'argVal', handler: h.functionCall }
|
|
130
110
|
},
|
|
131
111
|
completable: true
|
|
132
112
|
},
|
package/src/types.ts
CHANGED
|
@@ -3,15 +3,30 @@
|
|
|
3
3
|
* Copyright 2020 Tom Shawver
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
export interface TemplatePart {
|
|
7
|
+
type: 'static' | 'interpolation'
|
|
8
|
+
value: string
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export type JexlValue =
|
|
12
|
+
| string
|
|
13
|
+
| number
|
|
14
|
+
| boolean
|
|
15
|
+
| null
|
|
16
|
+
| undefined
|
|
17
|
+
| JexlValue[]
|
|
18
|
+
| { [key: string]: JexlValue }
|
|
19
|
+
|
|
6
20
|
export interface Token {
|
|
7
21
|
type: string
|
|
8
|
-
value:
|
|
22
|
+
value: string | number | boolean | TemplatePart[]
|
|
9
23
|
raw: string
|
|
10
24
|
}
|
|
11
25
|
|
|
12
26
|
export interface AstNode {
|
|
13
27
|
type: string
|
|
14
28
|
_parent?: AstNode
|
|
29
|
+
right?: AstNode
|
|
15
30
|
}
|
|
16
31
|
|
|
17
32
|
export interface Literal extends AstNode {
|
|
@@ -49,19 +64,20 @@ export interface ObjectLiteral extends AstNode {
|
|
|
49
64
|
value: Record<string, AstNode>
|
|
50
65
|
}
|
|
51
66
|
|
|
67
|
+
export type TemplateLiteralPart =
|
|
68
|
+
| { type: 'static'; value: string }
|
|
69
|
+
| { type: 'expression'; value: AstNode }
|
|
70
|
+
|
|
52
71
|
export interface TemplateLiteral extends AstNode {
|
|
53
72
|
type: 'TemplateLiteral'
|
|
54
|
-
parts:
|
|
55
|
-
type: 'static' | 'expression'
|
|
56
|
-
value: string | AstNode
|
|
57
|
-
}[]
|
|
73
|
+
parts: TemplateLiteralPart[]
|
|
58
74
|
}
|
|
59
75
|
|
|
60
76
|
export interface FunctionCall extends AstNode {
|
|
61
77
|
type: 'FunctionCall'
|
|
62
78
|
name: string
|
|
63
79
|
args: AstNode[]
|
|
64
|
-
pool: 'functions'
|
|
80
|
+
pool: 'functions'
|
|
65
81
|
}
|
|
66
82
|
|
|
67
83
|
export interface FilterExpression extends AstNode {
|
|
@@ -87,7 +103,6 @@ export interface AssignmentExpression extends AstNode {
|
|
|
87
103
|
type: 'AssignmentExpression'
|
|
88
104
|
operator: '='
|
|
89
105
|
left: Identifier
|
|
90
|
-
right: AstNode
|
|
91
106
|
}
|
|
92
107
|
|
|
93
108
|
export type AstNodeUnion =
|