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