@jbrowse/jexl 5.0.0 → 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.
package/esm/print.js ADDED
@@ -0,0 +1,183 @@
1
+ /*
2
+ * Jexl
3
+ * Copyright 2020 Tom Shawver
4
+ */
5
+ const PRECEDENCE = {
6
+ '=': 2,
7
+ '||': 10,
8
+ '??': 10,
9
+ '&&': 11,
10
+ '==': 20,
11
+ '!=': 20,
12
+ '~': 20,
13
+ '!~': 20,
14
+ '<': 20,
15
+ '<=': 20,
16
+ '>': 20,
17
+ '>=': 20,
18
+ in: 20,
19
+ '+': 30,
20
+ '-': 30,
21
+ '*': 40,
22
+ '/': 40,
23
+ '//': 40,
24
+ '%': 50,
25
+ '^': 50
26
+ };
27
+ const RIGHT_ASSOCIATIVE = new Set(['^']);
28
+ const LOGICAL = new Set(['||', '&&']);
29
+ // how tightly each form binds, mirroring the Parser's levels: `;`, then `=`
30
+ // and lambdas, then `?:`, then binary operators, then prefix operators
31
+ const SEQUENCE = 0;
32
+ const ASSIGNMENT = 1;
33
+ const CONDITIONAL = 2;
34
+ const CUSTOM_BINARY = 3;
35
+ const UNARY = 100;
36
+ const letter = String.raw `a-zA-Zа-яА-Я_À-ÖØ-öø-ÿ$`;
37
+ const IDENTIFIER = new RegExp(`^[${letter}][${letter}0-9]*$`);
38
+ const WORDS = new Set(['in', 'true', 'false', 'null']);
39
+ export function isName(text) {
40
+ return IDENTIFIER.test(text) && !WORDS.has(text);
41
+ }
42
+ export function key(name) {
43
+ return isName(name) ? `.${name}` : `[${quote(name)}]`;
44
+ }
45
+ export function quote(text) {
46
+ return `'${text.replaceAll('\\', '\\\\').replaceAll("'", String.raw `\'`)}'`;
47
+ }
48
+ function precedenceOf(node) {
49
+ switch (node.type) {
50
+ case 'SequenceExpression': {
51
+ return SEQUENCE;
52
+ }
53
+ case 'AssignmentExpression':
54
+ case 'Lambda': {
55
+ return ASSIGNMENT;
56
+ }
57
+ case 'ConditionalExpression': {
58
+ // with no alternate, it ends only before `)`, `]`, `}`, `,`, `;` or the end
59
+ return node.alternate ? CONDITIONAL : SEQUENCE;
60
+ }
61
+ case 'BinaryExpression': {
62
+ return PRECEDENCE[node.operator] ?? CUSTOM_BINARY;
63
+ }
64
+ case 'UnaryExpression': {
65
+ return UNARY;
66
+ }
67
+ default: {
68
+ return Infinity;
69
+ }
70
+ }
71
+ }
72
+ function within(ast, least) {
73
+ const text = print(ast);
74
+ return precedenceOf(ast) < least ? `(${text})` : text;
75
+ }
76
+ function binaryOperand(parent, ast, left) {
77
+ const own = PRECEDENCE[parent.operator];
78
+ if (own === undefined) {
79
+ return within(ast, UNARY);
80
+ }
81
+ const child = ast;
82
+ const theirs = precedenceOf(child);
83
+ const chained = child.type === 'BinaryExpression' &&
84
+ RIGHT_ASSOCIATIVE.has(parent.operator) &&
85
+ RIGHT_ASSOCIATIVE.has(child.operator);
86
+ const mixesNullish = child.type === 'BinaryExpression' &&
87
+ ((parent.operator === '??' && LOGICAL.has(child.operator)) ||
88
+ (child.operator === '??' && LOGICAL.has(parent.operator)));
89
+ const text = print(ast);
90
+ return theirs < own || (theirs === own && left === chained) || mixesNullish
91
+ ? `(${text})`
92
+ : text;
93
+ }
94
+ /**
95
+ * Renders a tree back to expression text, for messages and suggestions. The
96
+ * text parses back to the same tree.
97
+ */
98
+ export function print(ast) {
99
+ const node = ast;
100
+ switch (node.type) {
101
+ case 'Literal': {
102
+ const { value } = node;
103
+ return typeof value === 'string'
104
+ ? quote(value)
105
+ : Object.is(value, -0)
106
+ ? '-0'
107
+ : String(value);
108
+ }
109
+ case 'Identifier': {
110
+ return node.from
111
+ ? within(node.from, Infinity) + key(node.value)
112
+ : node.value;
113
+ }
114
+ case 'FilterExpression': {
115
+ return `${within(node.subject, Infinity)}[${within(node.expr, ASSIGNMENT)}]`;
116
+ }
117
+ case 'BinaryExpression': {
118
+ return `${binaryOperand(node, node.left, true)} ${node.operator} ${binaryOperand(node, node.right, false)}`;
119
+ }
120
+ case 'UnaryExpression': {
121
+ const { operator } = node;
122
+ const right = within(node.right, UNARY);
123
+ // the Lexer folds a minus before a digit into the number
124
+ if (operator === '-' && /^\d/.test(right)) {
125
+ return `-(${right})`;
126
+ }
127
+ return IDENTIFIER.test(operator)
128
+ ? `${operator} ${right}`
129
+ : operator + right;
130
+ }
131
+ case 'ConditionalExpression': {
132
+ const test = within(node.test, CUSTOM_BINARY);
133
+ const consequent = node.consequent
134
+ ? ` ${within(node.consequent, ASSIGNMENT)} `
135
+ : '';
136
+ const alternate = node.alternate
137
+ ? ` ${within(node.alternate, ASSIGNMENT)}`
138
+ : '';
139
+ return `${test} ?${consequent}:${alternate}`;
140
+ }
141
+ case 'FunctionCall': {
142
+ const args = node.args.map((arg) => within(arg, ASSIGNMENT));
143
+ return `${node.name}(${args.join(', ')})`;
144
+ }
145
+ case 'ArrayLiteral': {
146
+ const items = node.value.map((item) => within(item, ASSIGNMENT));
147
+ return `[${items.join(', ')}]`;
148
+ }
149
+ case 'ObjectLiteral': {
150
+ const entries = Object.entries(node.value).map(([name, value]) => `${isName(name) ? name : quote(name)}: ${within(value, ASSIGNMENT)}`);
151
+ return `{${entries.join(', ')}}`;
152
+ }
153
+ case 'TemplateLiteral': {
154
+ const parts = node.parts.map((part) => part.type === 'static'
155
+ ? part.value
156
+ .replaceAll('\\', '\\\\')
157
+ .replaceAll('`', '\\`')
158
+ .replaceAll('${', '\\${')
159
+ : `\${${print(part.value)}}`);
160
+ return `\`${parts.join('')}\``;
161
+ }
162
+ case 'SequenceExpression': {
163
+ const { expressions } = node;
164
+ const text = expressions
165
+ .map((expr) => within(expr, ASSIGNMENT))
166
+ .join('; ');
167
+ return expressions.length === 1 ? `${text};` : text;
168
+ }
169
+ case 'AssignmentExpression': {
170
+ return `${node.left.value} = ${within(node.right, ASSIGNMENT)}`;
171
+ }
172
+ case 'Lambda': {
173
+ const params = node.params.length === 1
174
+ ? node.params[0]
175
+ : `(${node.params.join(', ')})`;
176
+ return `${params} => ${within(node.body, ASSIGNMENT)}`;
177
+ }
178
+ default: {
179
+ return '?';
180
+ }
181
+ }
182
+ }
183
+ //# sourceMappingURL=print.js.map
@@ -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.0",
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'
@@ -3,6 +3,8 @@
3
3
  * Copyright 2020 Tom Shawver
4
4
  */
5
5
 
6
+ import { and, nullish, or } from '../grammar.ts'
7
+
6
8
  import type { Grammar } from '../grammar.ts'
7
9
  import type {
8
10
  AstNode,
@@ -375,8 +377,23 @@ function compileNode(
375
377
  const right = compile(node.right!)
376
378
  if (op?.type === 'binaryOp' && op.evalOnDemand) {
377
379
  const { evalOnDemand } = op
378
- // operands stay unevaluated behind an `eval` thunk, so operators such
379
- // as && and || can short-circuit
380
+ if (evalOnDemand === and) {
381
+ return (ctx) => {
382
+ const value = left(ctx)
383
+ return value ? right(ctx) : value
384
+ }
385
+ }
386
+ if (evalOnDemand === or) {
387
+ return (ctx) => {
388
+ const value = left(ctx)
389
+ return value || right(ctx)
390
+ }
391
+ }
392
+ if (evalOnDemand === nullish) {
393
+ return (ctx) => left(ctx) ?? right(ctx)
394
+ }
395
+ // a host's operator gets its operands unevaluated, behind `eval`
396
+ // thunks, so that it can short-circuit as it chooses
380
397
  return (ctx) =>
381
398
  evalOnDemand({ eval: () => left(ctx) }, { eval: () => right(ctx) })
382
399
  }
package/src/grammar.ts CHANGED
@@ -139,6 +139,19 @@ const less = (left: JexlValue, right: JexlValue) =>
139
139
  left != null && right != null && (left as number) < (right as number)
140
140
  const atMost = (left: JexlValue, right: JexlValue) =>
141
141
  left != null && right != null && (left as number) <= (right as number)
142
+ // named so the compiler can recognize the built-in forms and short-circuit
143
+ // without the thunks a host's evalOnDemand operator is handed
144
+ export const and: BinaryOpEvalOnDemand = (left, right) => {
145
+ const leftVal = left.eval()
146
+ return leftVal ? right.eval() : leftVal
147
+ }
148
+ export const or: BinaryOpEvalOnDemand = (left, right) => {
149
+ const leftVal = left.eval()
150
+ return leftVal || right.eval()
151
+ }
152
+ export const nullish: BinaryOpEvalOnDemand = (left, right) =>
153
+ left.eval() ?? right.eval()
154
+
142
155
  const equals = (left: JexlValue, right: JexlValue) =>
143
156
  isList(left, right) ? anyPair(loose, left, right) : left == right
144
157
 
@@ -267,29 +280,17 @@ export const getGrammar = (): Grammar => ({
267
280
  '&&': {
268
281
  type: 'binaryOp',
269
282
  precedence: 11,
270
- evalOnDemand: (left, right) => {
271
- const leftVal = left.eval()
272
- if (!leftVal) {
273
- return leftVal
274
- }
275
- return right.eval()
276
- }
283
+ evalOnDemand: and
277
284
  },
278
285
  '||': {
279
286
  type: 'binaryOp',
280
287
  precedence: 10,
281
- evalOnDemand: (left, right) => {
282
- const leftVal = left.eval()
283
- if (leftVal) {
284
- return leftVal
285
- }
286
- return right.eval()
287
- }
288
+ evalOnDemand: or
288
289
  },
289
290
  '??': {
290
291
  type: 'binaryOp',
291
292
  precedence: 10,
292
- evalOnDemand: (left, right) => left.eval() ?? right.eval()
293
+ evalOnDemand: nullish
293
294
  },
294
295
  in: {
295
296
  type: 'binaryOp',
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,