@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/README.md +4 -2
- package/dist/Expression.d.ts +0 -12
- package/dist/Expression.js +0 -16
- package/dist/Expression.js.map +1 -1
- package/dist/check.d.ts +0 -5
- package/dist/check.js +33 -211
- package/dist/check.js.map +1 -1
- package/dist/conditions.js +3 -3
- package/dist/evaluator/compile.js +18 -2
- package/dist/evaluator/compile.js.map +1 -1
- package/dist/grammar.d.ts +3 -0
- package/dist/grammar.js +18 -16
- package/dist/grammar.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/print.d.ts +9 -0
- package/dist/print.js +189 -0
- package/dist/print.js.map +1 -0
- package/esm/Expression.d.ts +0 -12
- package/esm/Expression.js +0 -16
- package/esm/Expression.js.map +1 -1
- package/esm/check.d.ts +0 -5
- package/esm/check.js +1 -178
- package/esm/check.js.map +1 -1
- package/esm/conditions.js +1 -1
- package/esm/evaluator/compile.js +18 -2
- package/esm/evaluator/compile.js.map +1 -1
- package/esm/grammar.d.ts +3 -0
- package/esm/grammar.js +14 -15
- package/esm/grammar.js.map +1 -1
- package/esm/index.d.ts +2 -1
- package/esm/index.js +2 -1
- package/esm/index.js.map +1 -1
- package/esm/print.d.ts +9 -0
- package/esm/print.js +183 -0
- package/esm/print.js.map +1 -0
- package/package.json +2 -1
- package/src/Expression.ts +0 -20
- package/src/check.ts +2 -198
- package/src/conditions.ts +1 -1
- package/src/evaluator/compile.ts +19 -2
- package/src/grammar.ts +16 -15
- package/src/index.ts +2 -1
- package/src/print.ts +205 -0
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
|
package/esm/print.js.map
ADDED
|
@@ -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.
|
|
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
package/src/evaluator/compile.ts
CHANGED
|
@@ -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
|
-
|
|
379
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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,
|
|
11
|
+
export { check, recordOf, union } from './check.ts'
|
|
12
|
+
export { print } from './print.ts'
|
|
12
13
|
export {
|
|
13
14
|
callSubject,
|
|
14
15
|
conditions,
|