@jbrowse/jexl 5.0.1 → 5.0.2

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.
@@ -0,0 +1 @@
1
+ {"version":3,"file":"print.js","sourceRoot":"","sources":["../src/print.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jbrowse/jexl",
3
- "version": "5.0.1",
3
+ "version": "5.0.2",
4
4
  "description": "A fork of the jexl lang for jbrowse",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -8,6 +8,7 @@
8
8
  "import": "./esm/index.js",
9
9
  "require": "./dist/index.js"
10
10
  },
11
+ "sideEffects": false,
11
12
  "files": [
12
13
  "dist",
13
14
  "esm",
package/src/Expression.ts CHANGED
@@ -4,13 +4,9 @@
4
4
  */
5
5
 
6
6
  import Lexer from './Lexer.ts'
7
- import { analyze } from './analyze.ts'
8
- import { check } from './check.ts'
9
7
  import { compileAst } from './evaluator/compile.ts'
10
8
  import Parser from './parser/Parser.ts'
11
9
 
12
- import type { AnalyzeOptions } from './analyze.ts'
13
- import type { CheckOptions } from './check.ts'
14
10
  import type { CompiledNode } from './evaluator/compile.ts'
15
11
  import type { Grammar } from './grammar.ts'
16
12
  import type { AstNode } from './types.ts'
@@ -86,22 +82,6 @@ class Expression {
86
82
  }
87
83
  return this._ast
88
84
  }
89
-
90
- /**
91
- * Lists what the expression reads from its context, without evaluating it.
92
- * See {@link analyze}.
93
- */
94
- analyze(options?: AnalyzeOptions) {
95
- return analyze(this.ast, options)
96
- }
97
-
98
- /**
99
- * Checks the expression against a host's fields and functions, without
100
- * evaluating it. See {@link check}.
101
- */
102
- check(options?: CheckOptions) {
103
- return check(this.ast, options)
104
- }
105
85
  }
106
86
 
107
87
  export default Expression
package/src/check.ts CHANGED
@@ -3,11 +3,12 @@
3
3
  * Copyright 2020 Tom Shawver
4
4
  */
5
5
 
6
+ import { isName, key, print, quote } from './print.ts'
7
+
6
8
  import type { AnalyzeOptions } from './analyze.ts'
7
9
  import type {
8
10
  AstNode,
9
11
  AstNodeUnion,
10
- BinaryExpression,
11
12
  FunctionCall,
12
13
  Identifier,
13
14
  Lambda,
@@ -160,203 +161,6 @@ const MAX_VALUES = 64
160
161
  const ARITHMETIC = new Set(['-', '*', '/', '//', '%', '^'])
161
162
  const ORDER = new Set(['<', '<=', '>', '>='])
162
163
  const EQUALITY = new Set(['==', '!='])
163
- const PRECEDENCE: Record<string, number> = {
164
- '=': 2,
165
- '||': 10,
166
- '??': 10,
167
- '&&': 11,
168
- '==': 20,
169
- '!=': 20,
170
- '~': 20,
171
- '!~': 20,
172
- '<': 20,
173
- '<=': 20,
174
- '>': 20,
175
- '>=': 20,
176
- in: 20,
177
- '+': 30,
178
- '-': 30,
179
- '*': 40,
180
- '/': 40,
181
- '//': 40,
182
- '%': 50,
183
- '^': 50
184
- }
185
-
186
- const RIGHT_ASSOCIATIVE = new Set(['^'])
187
- const LOGICAL = new Set(['||', '&&'])
188
-
189
- // how tightly each form binds, mirroring the Parser's levels: `;`, then `=`
190
- // and lambdas, then `?:`, then binary operators, then prefix operators
191
- const SEQUENCE = 0
192
- const ASSIGNMENT = 1
193
- const CONDITIONAL = 2
194
- const CUSTOM_BINARY = 3
195
- const UNARY = 100
196
-
197
- const letter = String.raw`a-zA-Zа-яА-Я_À-ÖØ-öø-ÿ$`
198
- const IDENTIFIER = new RegExp(`^[${letter}][${letter}0-9]*$`)
199
- const WORDS = new Set(['in', 'true', 'false', 'null'])
200
-
201
- function isName(text: string) {
202
- return IDENTIFIER.test(text) && !WORDS.has(text)
203
- }
204
-
205
- function key(name: string) {
206
- return isName(name) ? `.${name}` : `[${quote(name)}]`
207
- }
208
-
209
- function quote(text: string) {
210
- return `'${text.replaceAll('\\', '\\\\').replaceAll("'", String.raw`\'`)}'`
211
- }
212
-
213
- function precedenceOf(node: Node) {
214
- switch (node.type) {
215
- case 'SequenceExpression': {
216
- return SEQUENCE
217
- }
218
- case 'AssignmentExpression':
219
- case 'Lambda': {
220
- return ASSIGNMENT
221
- }
222
- case 'ConditionalExpression': {
223
- // with no alternate, it ends only before `)`, `]`, `}`, `,`, `;` or the end
224
- return node.alternate ? CONDITIONAL : SEQUENCE
225
- }
226
- case 'BinaryExpression': {
227
- return PRECEDENCE[node.operator] ?? CUSTOM_BINARY
228
- }
229
- case 'UnaryExpression': {
230
- return UNARY
231
- }
232
- default: {
233
- return Infinity
234
- }
235
- }
236
- }
237
-
238
- function within(ast: AstNode, least: number) {
239
- const text = print(ast)
240
- return precedenceOf(ast as Node) < least ? `(${text})` : text
241
- }
242
-
243
- function binaryOperand(parent: BinaryExpression, ast: AstNode, left: boolean) {
244
- const own = PRECEDENCE[parent.operator]
245
- if (own === undefined) {
246
- return within(ast, UNARY)
247
- }
248
- const child = ast as Node
249
- const theirs = precedenceOf(child)
250
- const chained =
251
- child.type === 'BinaryExpression' &&
252
- RIGHT_ASSOCIATIVE.has(parent.operator) &&
253
- RIGHT_ASSOCIATIVE.has(child.operator)
254
- const mixesNullish =
255
- child.type === 'BinaryExpression' &&
256
- ((parent.operator === '??' && LOGICAL.has(child.operator)) ||
257
- (child.operator === '??' && LOGICAL.has(parent.operator)))
258
- const text = print(ast)
259
- return theirs < own || (theirs === own && left === chained) || mixesNullish
260
- ? `(${text})`
261
- : text
262
- }
263
-
264
- /**
265
- * Renders a tree back to expression text, for messages and suggestions. The
266
- * text parses back to the same tree.
267
- */
268
- export function print(ast: AstNode): string {
269
- const node = ast as Node
270
- switch (node.type) {
271
- case 'Literal': {
272
- const { value } = node
273
- return typeof value === 'string'
274
- ? quote(value)
275
- : Object.is(value, -0)
276
- ? '-0'
277
- : String(value)
278
- }
279
- case 'Identifier': {
280
- return node.from
281
- ? within(node.from, Infinity) + key(node.value)
282
- : node.value
283
- }
284
- case 'FilterExpression': {
285
- return `${within(node.subject, Infinity)}[${within(node.expr, ASSIGNMENT)}]`
286
- }
287
- case 'BinaryExpression': {
288
- return `${binaryOperand(node, node.left, true)} ${node.operator} ${binaryOperand(node, node.right!, false)}`
289
- }
290
- case 'UnaryExpression': {
291
- const { operator } = node
292
- const right = within(node.right!, UNARY)
293
- // the Lexer folds a minus before a digit into the number
294
- if (operator === '-' && /^\d/.test(right)) {
295
- return `-(${right})`
296
- }
297
- return IDENTIFIER.test(operator)
298
- ? `${operator} ${right}`
299
- : operator + right
300
- }
301
- case 'ConditionalExpression': {
302
- const test = within(node.test, CUSTOM_BINARY)
303
- const consequent = node.consequent
304
- ? ` ${within(node.consequent, ASSIGNMENT)} `
305
- : ''
306
- const alternate = node.alternate
307
- ? ` ${within(node.alternate, ASSIGNMENT)}`
308
- : ''
309
- return `${test} ?${consequent}:${alternate}`
310
- }
311
- case 'FunctionCall': {
312
- const args = node.args.map((arg) => within(arg, ASSIGNMENT))
313
- return `${node.name}(${args.join(', ')})`
314
- }
315
- case 'ArrayLiteral': {
316
- const items = node.value.map((item) => within(item, ASSIGNMENT))
317
- return `[${items.join(', ')}]`
318
- }
319
- case 'ObjectLiteral': {
320
- const entries = Object.entries(node.value).map(
321
- ([name, value]) =>
322
- `${isName(name) ? name : quote(name)}: ${within(value, ASSIGNMENT)}`
323
- )
324
- return `{${entries.join(', ')}}`
325
- }
326
- case 'TemplateLiteral': {
327
- const parts = node.parts.map((part) =>
328
- part.type === 'static'
329
- ? part.value
330
- .replaceAll('\\', '\\\\')
331
- .replaceAll('`', '\\`')
332
- .replaceAll('${', '\\${')
333
- : `\${${print(part.value)}}`
334
- )
335
- return `\`${parts.join('')}\``
336
- }
337
- case 'SequenceExpression': {
338
- const { expressions } = node
339
- const text = expressions
340
- .map((expr) => within(expr, ASSIGNMENT))
341
- .join('; ')
342
- return expressions.length === 1 ? `${text};` : text
343
- }
344
- case 'AssignmentExpression': {
345
- return `${node.left.value} = ${within(node.right!, ASSIGNMENT)}`
346
- }
347
- case 'Lambda': {
348
- const params =
349
- node.params.length === 1
350
- ? node.params[0]
351
- : `(${node.params.join(', ')})`
352
- return `${params} => ${within(node.body, ASSIGNMENT)}`
353
- }
354
- default: {
355
- return '?'
356
- }
357
- }
358
- }
359
-
360
164
  function distance(a: string, b: string) {
361
165
  let before: number[] = []
362
166
  let previous = Array.from({ length: b.length + 1 }, (_, i) => i)
package/src/conditions.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  */
5
5
 
6
6
  import { analyze } from './analyze.ts'
7
- import { print } from './check.ts'
7
+ import { print } from './print.ts'
8
8
 
9
9
  import type { PathKey } from './analyze.ts'
10
10
  import type { AstNode, AstNodeUnion } from './types.ts'
package/src/index.ts CHANGED
@@ -8,7 +8,8 @@ export type { JexlOptions } from './Jexl.ts'
8
8
  export type { GetMember, VariableReader } from './grammar.ts'
9
9
  export { default as Expression } from './Expression.ts'
10
10
  export { analyze } from './analyze.ts'
11
- export { check, print, recordOf, union } from './check.ts'
11
+ export { check, recordOf, union } from './check.ts'
12
+ export { print } from './print.ts'
12
13
  export {
13
14
  callSubject,
14
15
  conditions,
package/src/print.ts ADDED
@@ -0,0 +1,205 @@
1
+ /*
2
+ * Jexl
3
+ * Copyright 2020 Tom Shawver
4
+ */
5
+
6
+ import type { AstNode, AstNodeUnion, BinaryExpression } from './types.ts'
7
+
8
+ type Node = AstNodeUnion
9
+
10
+ const PRECEDENCE: Record<string, number> = {
11
+ '=': 2,
12
+ '||': 10,
13
+ '??': 10,
14
+ '&&': 11,
15
+ '==': 20,
16
+ '!=': 20,
17
+ '~': 20,
18
+ '!~': 20,
19
+ '<': 20,
20
+ '<=': 20,
21
+ '>': 20,
22
+ '>=': 20,
23
+ in: 20,
24
+ '+': 30,
25
+ '-': 30,
26
+ '*': 40,
27
+ '/': 40,
28
+ '//': 40,
29
+ '%': 50,
30
+ '^': 50
31
+ }
32
+
33
+ const RIGHT_ASSOCIATIVE = new Set(['^'])
34
+ const LOGICAL = new Set(['||', '&&'])
35
+
36
+ // how tightly each form binds, mirroring the Parser's levels: `;`, then `=`
37
+ // and lambdas, then `?:`, then binary operators, then prefix operators
38
+ const SEQUENCE = 0
39
+ const ASSIGNMENT = 1
40
+ const CONDITIONAL = 2
41
+ const CUSTOM_BINARY = 3
42
+ const UNARY = 100
43
+
44
+ const letter = String.raw`a-zA-Zа-яА-Я_À-ÖØ-öø-ÿ$`
45
+ const IDENTIFIER = new RegExp(`^[${letter}][${letter}0-9]*$`)
46
+ const WORDS = new Set(['in', 'true', 'false', 'null'])
47
+
48
+ export function isName(text: string) {
49
+ return IDENTIFIER.test(text) && !WORDS.has(text)
50
+ }
51
+
52
+ export function key(name: string) {
53
+ return isName(name) ? `.${name}` : `[${quote(name)}]`
54
+ }
55
+
56
+ export function quote(text: string) {
57
+ return `'${text.replaceAll('\\', '\\\\').replaceAll("'", String.raw`\'`)}'`
58
+ }
59
+
60
+ function precedenceOf(node: Node) {
61
+ switch (node.type) {
62
+ case 'SequenceExpression': {
63
+ return SEQUENCE
64
+ }
65
+ case 'AssignmentExpression':
66
+ case 'Lambda': {
67
+ return ASSIGNMENT
68
+ }
69
+ case 'ConditionalExpression': {
70
+ // with no alternate, it ends only before `)`, `]`, `}`, `,`, `;` or the end
71
+ return node.alternate ? CONDITIONAL : SEQUENCE
72
+ }
73
+ case 'BinaryExpression': {
74
+ return PRECEDENCE[node.operator] ?? CUSTOM_BINARY
75
+ }
76
+ case 'UnaryExpression': {
77
+ return UNARY
78
+ }
79
+ default: {
80
+ return Infinity
81
+ }
82
+ }
83
+ }
84
+
85
+ function within(ast: AstNode, least: number) {
86
+ const text = print(ast)
87
+ return precedenceOf(ast as Node) < least ? `(${text})` : text
88
+ }
89
+
90
+ function binaryOperand(parent: BinaryExpression, ast: AstNode, left: boolean) {
91
+ const own = PRECEDENCE[parent.operator]
92
+ if (own === undefined) {
93
+ return within(ast, UNARY)
94
+ }
95
+ const child = ast as Node
96
+ const theirs = precedenceOf(child)
97
+ const chained =
98
+ child.type === 'BinaryExpression' &&
99
+ RIGHT_ASSOCIATIVE.has(parent.operator) &&
100
+ RIGHT_ASSOCIATIVE.has(child.operator)
101
+ const mixesNullish =
102
+ child.type === 'BinaryExpression' &&
103
+ ((parent.operator === '??' && LOGICAL.has(child.operator)) ||
104
+ (child.operator === '??' && LOGICAL.has(parent.operator)))
105
+ const text = print(ast)
106
+ return theirs < own || (theirs === own && left === chained) || mixesNullish
107
+ ? `(${text})`
108
+ : text
109
+ }
110
+
111
+ /**
112
+ * Renders a tree back to expression text, for messages and suggestions. The
113
+ * text parses back to the same tree.
114
+ */
115
+ export function print(ast: AstNode): string {
116
+ const node = ast as Node
117
+ switch (node.type) {
118
+ case 'Literal': {
119
+ const { value } = node
120
+ return typeof value === 'string'
121
+ ? quote(value)
122
+ : Object.is(value, -0)
123
+ ? '-0'
124
+ : String(value)
125
+ }
126
+ case 'Identifier': {
127
+ return node.from
128
+ ? within(node.from, Infinity) + key(node.value)
129
+ : node.value
130
+ }
131
+ case 'FilterExpression': {
132
+ return `${within(node.subject, Infinity)}[${within(node.expr, ASSIGNMENT)}]`
133
+ }
134
+ case 'BinaryExpression': {
135
+ return `${binaryOperand(node, node.left, true)} ${node.operator} ${binaryOperand(node, node.right!, false)}`
136
+ }
137
+ case 'UnaryExpression': {
138
+ const { operator } = node
139
+ const right = within(node.right!, UNARY)
140
+ // the Lexer folds a minus before a digit into the number
141
+ if (operator === '-' && /^\d/.test(right)) {
142
+ return `-(${right})`
143
+ }
144
+ return IDENTIFIER.test(operator)
145
+ ? `${operator} ${right}`
146
+ : operator + right
147
+ }
148
+ case 'ConditionalExpression': {
149
+ const test = within(node.test, CUSTOM_BINARY)
150
+ const consequent = node.consequent
151
+ ? ` ${within(node.consequent, ASSIGNMENT)} `
152
+ : ''
153
+ const alternate = node.alternate
154
+ ? ` ${within(node.alternate, ASSIGNMENT)}`
155
+ : ''
156
+ return `${test} ?${consequent}:${alternate}`
157
+ }
158
+ case 'FunctionCall': {
159
+ const args = node.args.map((arg) => within(arg, ASSIGNMENT))
160
+ return `${node.name}(${args.join(', ')})`
161
+ }
162
+ case 'ArrayLiteral': {
163
+ const items = node.value.map((item) => within(item, ASSIGNMENT))
164
+ return `[${items.join(', ')}]`
165
+ }
166
+ case 'ObjectLiteral': {
167
+ const entries = Object.entries(node.value).map(
168
+ ([name, value]) =>
169
+ `${isName(name) ? name : quote(name)}: ${within(value, ASSIGNMENT)}`
170
+ )
171
+ return `{${entries.join(', ')}}`
172
+ }
173
+ case 'TemplateLiteral': {
174
+ const parts = node.parts.map((part) =>
175
+ part.type === 'static'
176
+ ? part.value
177
+ .replaceAll('\\', '\\\\')
178
+ .replaceAll('`', '\\`')
179
+ .replaceAll('${', '\\${')
180
+ : `\${${print(part.value)}}`
181
+ )
182
+ return `\`${parts.join('')}\``
183
+ }
184
+ case 'SequenceExpression': {
185
+ const { expressions } = node
186
+ const text = expressions
187
+ .map((expr) => within(expr, ASSIGNMENT))
188
+ .join('; ')
189
+ return expressions.length === 1 ? `${text};` : text
190
+ }
191
+ case 'AssignmentExpression': {
192
+ return `${node.left.value} = ${within(node.right!, ASSIGNMENT)}`
193
+ }
194
+ case 'Lambda': {
195
+ const params =
196
+ node.params.length === 1
197
+ ? node.params[0]
198
+ : `(${node.params.join(', ')})`
199
+ return `${params} => ${within(node.body, ASSIGNMENT)}`
200
+ }
201
+ default: {
202
+ return '?'
203
+ }
204
+ }
205
+ }