@tabnas/expr 0.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/dist/expr.js ADDED
@@ -0,0 +1,1221 @@
1
+ "use strict";
2
+ /* Copyright (c) 2021-2025 Richard Rodger, MIT License */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.testing = exports.Expr = void 0;
5
+ exports.evaluation = evaluation;
6
+ // This algorithm is based on Pratt parsing, and draws heavily from
7
+ // the explanation written by Aleksey Kladov here:
8
+ // https://matklad.github.io/2020/04/13/simple-but-powerful-pratt-parsing.html
9
+ // See the `prattify` function for the core implementation.
10
+ //
11
+ // Expressions are encoded as LISP-style S-expressions using
12
+ // arrays. The operation meta data is provided as the first array
13
+ // element. To maintain the integrity of the overall JSON AST,
14
+ // expression rules cannot simply re-assign nodes. Instead the
15
+ // existing partial expression nodes are rewritten in-place.
16
+ //
17
+ // Parentheses can have preceeding values, which allows for the using function
18
+ // call ("foo(1)") and index ("a[1]") syntax. See the tests for examples and
19
+ // configuration options.
20
+ //
21
+ // Ternary expressions are implemented as special rule that is similar to
22
+ // the parenthesis rule. You can have multiple ternaries.
23
+ //
24
+ // Standard Jsonic allows for implicit lists and maps (e.g. a,b =>
25
+ // ['a','b']) at the top level. This expression grammar also allows
26
+ // for implicits within parentheses, so that "foo(1,2)" =>
27
+ // ['(','foo',[1,2]]. To support implicits additional counters and
28
+ // flags are needed, as well as context-sensitive edge-case
29
+ // handling. See the ternary rule for a glorious example.
30
+ //
31
+ // There is a specific recurring edge-case: when expressions are the
32
+ // first item of a list, special care is need not to embed the list
33
+ // inside the expression.
34
+ // TODO: custom ctx.F for Op - make this automatic in options
35
+ // TODO: error on incomplete expr: 1+2+
36
+ // The engine is the tabnas parser; jsonic supplies the relaxed-JSON
37
+ // grammar. Engine types are re-exported by @tabnas/parser.
38
+ const parser_1 = require("@tabnas/parser");
39
+ const { omap, entries, values } = parser_1.util;
40
+ // Mark Operator objects as owned by this plugin.
41
+ const OP_MARK = {};
42
+ // The plugin itself.
43
+ let Expr = function Expr(tn, options) {
44
+ // Ensure comment matcher is first to avoid conflicts with
45
+ // comment markers (//, /*, etc)
46
+ // let lexm = jsonic.options.lex?.match || []
47
+ // let cmI: number = lexm.map((m) => m.name).indexOf('makeCommentMatcher')
48
+ // if (0 < cmI) {
49
+ // jsonic.options({
50
+ // lex: {
51
+ // match: [lexm[cmI], ...lexm.slice(0, cmI), ...lexm.slice(cmI + 1)],
52
+ // },
53
+ // })
54
+ // }
55
+ // console.log('EXPR', options)
56
+ let token = tn.token.bind(tn);
57
+ let fixed = tn.fixed.bind(tn);
58
+ // Build token maps (TM).
59
+ let optop = options.op || {};
60
+ // Delete operations marked null.
61
+ for (let opname in optop) {
62
+ if (null === optop[opname]) {
63
+ delete optop[opname];
64
+ }
65
+ }
66
+ const prefixTM = makeOpMap(token, fixed, optop, 'prefix');
67
+ const suffixTM = makeOpMap(token, fixed, optop, 'suffix');
68
+ const infixTM = makeOpMap(token, fixed, optop, 'infix');
69
+ const ternaryTM = makeOpMap(token, fixed, optop, 'ternary');
70
+ const parenOTM = makeParenMap(token, fixed, optop);
71
+ const parenCTM = omap(parenOTM, ([_, pdef]) => [
72
+ undefined,
73
+ undefined,
74
+ pdef.ctin,
75
+ pdef,
76
+ ]);
77
+ let parenFixed = Object.values({ ...parenOTM, ...parenCTM }).reduce((a, p) => ((a[p.otkn] = p.osrc), (a[p.ctkn] = p.csrc), a), {});
78
+ // NOTE: operators with same src will generate same token - this is correct.
79
+ let operatorFixed = Object.values({
80
+ ...prefixTM,
81
+ ...suffixTM,
82
+ ...infixTM,
83
+ ...ternaryTM,
84
+ }).reduce((a, op) => ((a[op.tkn] = op.src), a), {});
85
+ tn.options({
86
+ fixed: {
87
+ token: { ...operatorFixed, ...parenFixed },
88
+ },
89
+ lex: {
90
+ match: {
91
+ comment: { order: 1e5 },
92
+ },
93
+ },
94
+ });
95
+ // Append 'expr' to the g (group tag) of every alt added below. Mirrors
96
+ // the jsonic grammar(...) setting {rule:{alt:{g:'expr'}}}, applied
97
+ // manually because the plugin uses jsonic.rule() (not jsonic.grammar()).
98
+ const tagExpr = (alts) => {
99
+ if (!Array.isArray(alts))
100
+ return alts;
101
+ return alts.map((a) => {
102
+ if (null == a || 'object' !== typeof a)
103
+ return a;
104
+ const existing = null == a.g
105
+ ? []
106
+ : Array.isArray(a.g)
107
+ ? [...a.g]
108
+ : String(a.g).split(/\s*,\s*/).filter((s) => s.length > 0);
109
+ return { ...a, g: [...existing, 'expr'] };
110
+ });
111
+ };
112
+ const rule = (name, fn) => {
113
+ tn.rule(name, (rs) => {
114
+ const origOpen = rs.open.bind(rs);
115
+ const origClose = rs.close.bind(rs);
116
+ rs.open = (alts, flags) => origOpen(tagExpr(alts), flags);
117
+ rs.close = (alts, flags) => origClose(tagExpr(alts), flags);
118
+ fn(rs);
119
+ });
120
+ };
121
+ const PREFIX = values(prefixTM).map((op) => op.tin);
122
+ const INFIX = values(infixTM).map((op) => op.tin);
123
+ const SUFFIX = values(suffixTM).map((op) => op.tin);
124
+ const TERN0 = values(ternaryTM)
125
+ .filter((op) => 0 === op.use.ternary.opI)
126
+ .map((op) => op.tin);
127
+ const TERN1 = values(ternaryTM)
128
+ .filter((op) => 1 === op.use.ternary.opI)
129
+ .map((op) => op.tin);
130
+ const OP = values(parenOTM).map((pdef) => pdef.otin);
131
+ const CP = values(parenCTM).map((pdef) => pdef.ctin);
132
+ const hasPrefix = 0 < PREFIX.length;
133
+ const hasInfix = 0 < INFIX.length;
134
+ const hasSuffix = 0 < SUFFIX.length;
135
+ const hasTernary = 0 < TERN0.length && 0 < TERN1.length;
136
+ const hasParen = 0 < OP.length && 0 < CP.length;
137
+ const CA = tn.token.CA;
138
+ const CS = tn.token.CS;
139
+ const CB = tn.token.CB;
140
+ const TX = tn.token.TX;
141
+ const NR = tn.token.NR;
142
+ const ST = tn.token.ST;
143
+ const VL = tn.token.VL;
144
+ const ZZ = tn.token.ZZ;
145
+ const VAL = [TX, NR, ST, VL];
146
+ const NONE = null;
147
+ rule('val', (rs) => {
148
+ // TODO: jsonic - make it easier to handle this case
149
+ // Implicit pair not allowed inside ternary
150
+ if (hasTernary && TERN1.includes(tn.token.CL)) {
151
+ rs.def.open
152
+ .filter((a) => a.g.includes('pair'))
153
+ .map((alt) => {
154
+ let origcond = alt.c;
155
+ let internary = (r) => !r.n.expr_ternary;
156
+ alt.c = origcond
157
+ ? (r, ctx) => origcond(r, ctx) && internary(r)
158
+ : internary;
159
+ });
160
+ }
161
+ rs.open([
162
+ // The prefix operator of the first term of an expression.
163
+ hasPrefix
164
+ ? {
165
+ s: [PREFIX],
166
+ b: 1,
167
+ n: { expr_prefix: 1, expr_suffix: 0 },
168
+ p: 'expr',
169
+ g: 'expr,expr-prefix',
170
+ }
171
+ : NONE,
172
+ // WWW
173
+ hasParen
174
+ ? {
175
+ s: [VAL, OP],
176
+ b: 1,
177
+ p: 'expr',
178
+ c: (r, ctx) => {
179
+ const pdef = parenOTM[r.o1.tin];
180
+ const preval = r.o0.resolveVal(r, ctx);
181
+ return pdef.preval.active &&
182
+ (null == pdef.preval.allow || pdef.preval.allow.includes(preval));
183
+ },
184
+ u: { paren_preval: true },
185
+ g: 'expr,expr-paren,expr-paren-preval',
186
+ a: (r, ctx) => {
187
+ r.node = r.o0.resolveVal(r, ctx);
188
+ }
189
+ }
190
+ : NONE,
191
+ // An opening parenthesis.
192
+ // NOTE: this can happen outside an expression.
193
+ hasParen
194
+ ? {
195
+ s: [OP],
196
+ b: 1,
197
+ p: 'expr',
198
+ c: (r) => {
199
+ const pdef = parenOTM[r.o0.tin];
200
+ return !pdef.preval.required;
201
+ },
202
+ g: 'expr,expr-paren',
203
+ }
204
+ : NONE,
205
+ ]).close([
206
+ // Comma-op suppression. When a parent rule (e.g. an embedding
207
+ // grammar's wrapper rule) sets n.no_comma_op, bail at `,` without
208
+ // treating it as the comma operator — the parent then consumes
209
+ // the `,` itself as a separator. Match by `src` on the next infix
210
+ // token so this works regardless of which token the embedding
211
+ // grammar uses for `,`.
212
+ hasInfix
213
+ ? {
214
+ s: [INFIX],
215
+ c: (r) => 0 < (r.n.no_comma_op || 0) && r.c0?.src === ',',
216
+ b: 1,
217
+ g: 'expr,no-comma-op-bail',
218
+ }
219
+ : NONE,
220
+ hasTernary
221
+ ? {
222
+ s: [TERN0],
223
+ c: (r) => !r.n.expr,
224
+ b: 1,
225
+ r: 'ternary',
226
+ g: 'expr,expr-ternary',
227
+ }
228
+ : NONE,
229
+ // The infix operator following the first term of an expression.
230
+ hasInfix
231
+ ? {
232
+ s: [INFIX],
233
+ b: 1,
234
+ n: { expr_prefix: 0, expr_suffix: 0 },
235
+ r: (r) => (!r.n.expr ? 'expr' : ''),
236
+ g: 'expr,expr-infix',
237
+ }
238
+ : NONE,
239
+ // The suffix operator following the first term of an expression.
240
+ hasSuffix
241
+ ? {
242
+ s: [SUFFIX],
243
+ b: 1,
244
+ n: { expr_prefix: 0, expr_suffix: 1 },
245
+ r: (r) => (!r.n.expr ? 'expr' : ''),
246
+ g: 'expr,expr-suffix',
247
+ }
248
+ : NONE,
249
+ // The closing parenthesis of an expression.
250
+ hasParen
251
+ ? {
252
+ s: [CP],
253
+ c: (r) => !!r.n.expr_paren,
254
+ b: 1,
255
+ g: 'expr,expr-paren',
256
+ }
257
+ : NONE,
258
+ // Chain for postfix paren forms. When a val has just produced a
259
+ // value (e.g. `a[0]`, `f(0)`, or a parenthesised expression like
260
+ // `(*p)`) and the next token is another preval-active paren-open,
261
+ // push expr (which descends into paren) so the new paren-form
262
+ // picks up this val's node as the preval. Use `p: 'expr'` (not
263
+ // `r: 'val'`) so the current val rule stays alive and
264
+ // `ctx.root().node` still reflects the chained result on parser
265
+ // return. `u: { paren_preval: true }` is set so makeCloseParen
266
+ // finds it on r.parent.parent (= this val) and pushes our node
267
+ // into the new paren CST. This complements the open-time
268
+ // s:[VAL,OP] preval alt above, which only fires on the first
269
+ // preval-paren of an expression; chain-time detection is needed
270
+ // for subsequent parens because the leading "value" is by then a
271
+ // produced node, not a token in the lex buffer.
272
+ hasParen
273
+ ? {
274
+ s: [OP],
275
+ b: 1,
276
+ c: (r) => {
277
+ const pdef = parenOTM[r.c0.tin];
278
+ return pdef.preval.active &&
279
+ undefined !== r.node &&
280
+ (null == pdef.preval.allow || pdef.preval.allow.includes(r.node));
281
+ },
282
+ p: 'expr',
283
+ u: { paren_preval: true },
284
+ g: 'expr,expr-paren,expr-paren-preval-chain',
285
+ }
286
+ : NONE,
287
+ hasTernary
288
+ ? {
289
+ s: [TERN1],
290
+ c: (r) => !!r.n.expr_ternary,
291
+ b: 1,
292
+ g: 'expr,expr-ternary',
293
+ }
294
+ : NONE,
295
+ // Don't create implicit list inside expression (comma separator).
296
+ {
297
+ s: [CA],
298
+ c: (r) => (1 === r.d && (1 <= r.n.expr || 1 <= r.n.expr_ternary)) ||
299
+ (1 <= r.n.expr_ternary && 1 <= r.n.expr_paren),
300
+ b: 1,
301
+ g: 'expr,list,val,imp,comma,top',
302
+ },
303
+ // Don't create implicit list inside expression (space separator).
304
+ {
305
+ s: [VAL],
306
+ c: (r) => (1 === r.d && (1 <= r.n.expr || 1 <= r.n.expr_ternary)) ||
307
+ (1 <= r.n.expr_ternary && 1 <= r.n.expr_paren),
308
+ b: 1,
309
+ g: 'expr,list,val,imp,space,top',
310
+ },
311
+ ]);
312
+ });
313
+ rule('list', (rs) => {
314
+ // rs.bo(false, (...rest: any) => {
315
+ rs.bo(false, (r) => {
316
+ // List elements are new expressions.
317
+ // Unless this is an implicit list.
318
+ if (!r.prev.u.implist) {
319
+ r.n.expr = 0;
320
+ r.n.expr_prefix = 0;
321
+ r.n.expr_suffix = 0;
322
+ r.n.expr_paren = 0;
323
+ r.n.expr_ternary = 0;
324
+ }
325
+ }).close([
326
+ hasParen && {
327
+ s: [CP],
328
+ // If end of normal list, consume `]` - it's not a close paren.
329
+ b: (r) => (CS === r.c0.tin && !r.n.expr_paren ? 0 : 1),
330
+ },
331
+ ]);
332
+ });
333
+ rule('map', (rs) => {
334
+ rs.bo(false, (...rest) => {
335
+ // Map values are new expressions.
336
+ rest[0].n.expr = 0;
337
+ rest[0].n.expr_prefix = 0;
338
+ rest[0].n.expr_suffix = 0;
339
+ rest[0].n.expr_paren = 0;
340
+ rest[0].n.expr_ternary = 0;
341
+ }).close([
342
+ hasParen && {
343
+ s: [CP],
344
+ // If end of normal map, consume `}` - it's not a close paren.
345
+ b: (r) => (CB === r.c0.tin && !r.n.expr_paren ? 0 : 1),
346
+ },
347
+ ]);
348
+ });
349
+ rule('elem', (rs) => {
350
+ rs.close([
351
+ // Close implicit list within parens.
352
+ hasParen
353
+ ? {
354
+ s: [CP],
355
+ b: 1,
356
+ c: (r) => !!r.n.expr_paren,
357
+ g: 'expr,expr-paren,imp,close,list',
358
+ }
359
+ : NONE,
360
+ // Following elem is a paren expression.
361
+ hasParen
362
+ ? {
363
+ s: [OP],
364
+ b: 1,
365
+ r: 'elem',
366
+ g: 'expr,expr-paren,imp,open,list',
367
+ }
368
+ : NONE,
369
+ ]);
370
+ });
371
+ rule('pair', (rs) => {
372
+ rs.close([
373
+ // Close implicit map within parens.
374
+ hasParen
375
+ ? {
376
+ s: [CP],
377
+ b: 1,
378
+ c: (r) => !!r.n.expr_paren || 0 < r.n.pk,
379
+ g: 'expr,expr-paren,imp,map',
380
+ }
381
+ : NONE,
382
+ ]);
383
+ });
384
+ rule('expr', (rs) => {
385
+ rs.open([
386
+ // An opening parenthesis of an expression.
387
+ hasParen
388
+ ? {
389
+ s: [OP],
390
+ p: 'paren',
391
+ b: 1,
392
+ g: 'expr,expr-paren,expr-start',
393
+ }
394
+ : NONE,
395
+ hasPrefix
396
+ ? {
397
+ s: [PREFIX],
398
+ c: (r) => !!r.n.expr_prefix,
399
+ n: { expr: 1, dlist: 1, dmap: 1 },
400
+ p: 'val',
401
+ g: 'expr,expr-prefix',
402
+ a: (r) => {
403
+ const op = makeOp(r.o0, prefixTM);
404
+ // Only fold into the parent's node when it is an expression
405
+ // op THIS prefix chain is building (e.g. the outer `-` of
406
+ // `--1`). A ternary/paren op-array on the parent is a foreign
407
+ // seed the engine threaded down by parent-seeding — prattify
408
+ // would splice the new prefix into the wrapper (corrupting it
409
+ // with a stray hole + duplicated operand). Start a fresh
410
+ // operand expression for those (mirrors the infix guard).
411
+ r.node = isOp(r.parent.node)
412
+ && !isTernaryOp(r.parent.node)
413
+ && !isParenOp(r.parent.node)
414
+ ? prattify(r.parent.node, op, 'expr-prefix')
415
+ : prior(r, r.parent, op, 'expr-prefix');
416
+ },
417
+ }
418
+ : NONE,
419
+ hasInfix
420
+ ? {
421
+ s: [INFIX],
422
+ p: 'val',
423
+ n: { expr: 1, expr_prefix: 0, dlist: 1, dmap: 1 },
424
+ a: (r) => {
425
+ const prev = r.prev;
426
+ const parent = r.parent;
427
+ const op = makeOp(r.o0, infixTM);
428
+ // Second and further operators.
429
+ if (isOp(parent.node) && !isTernaryOp(parent.node)) {
430
+ // console.log('INFIX-A', r.i, p(r.node), parent.i, p(parent.node))
431
+ r.node = prattify(parent.node, op, 'expr-infix-more');
432
+ // console.log('INFIX-B', r.i, p(r.node), parent.i, p(parent.node))
433
+ }
434
+ // First term was unary expression.
435
+ else if (isOp(prev.node)) {
436
+ r.node = prattify(prev.node, op, 'expr-infix-unary');
437
+ r.parent = prev;
438
+ }
439
+ // First term was plain value or ternary part.
440
+ else {
441
+ r.node = prior(r, prev, op, 'expr-infix');
442
+ }
443
+ },
444
+ g: 'expr,expr-infix',
445
+ }
446
+ : NONE,
447
+ hasSuffix
448
+ ? {
449
+ s: [SUFFIX],
450
+ n: { expr: 1, expr_prefix: 0, dlist: 1, dmap: 1 },
451
+ a: (r) => {
452
+ const prev = r.prev;
453
+ const op = makeOp(r.o0, suffixTM);
454
+ r.node = isOp(prev.node)
455
+ ? prattify(prev.node, op, 'expr-suffix')
456
+ : prior(r, prev, op, 'expr-suffix');
457
+ },
458
+ g: 'expr,expr-suffix',
459
+ }
460
+ : NONE,
461
+ ])
462
+ .bc((r) => {
463
+ const addterm = isOp(r.node)
464
+ && r.node?.length - 1 < r.node[0].terms
465
+ && ('object' !== typeof r.node || r.node !== r.child.node);
466
+ // Append final term to expression.
467
+ if (addterm) {
468
+ r.node.push(r.child.node);
469
+ }
470
+ })
471
+ .close([
472
+ {
473
+ c: (r) => 'paren' === r.child.name,
474
+ n: { expr: 0 },
475
+ g: 'expr,expr-end,expr-paren-end',
476
+ },
477
+ // Comma-op suppression. When n.no_comma_op is set by a parent
478
+ // rule, terminate the expression at `,` without treating it as
479
+ // the comma operator (mirrors the val.close bail above for
480
+ // expressions that have already passed the first term). Match
481
+ // by `src` on the next infix token. n: { expr: 0 } closes the
482
+ // expression frame so the parent rule regains control before
483
+ // any comma-op INFIX alt below fires.
484
+ hasInfix
485
+ ? {
486
+ s: [INFIX],
487
+ c: (r) => 0 < (r.n.no_comma_op || 0) && r.c0?.src === ',',
488
+ b: 1,
489
+ n: { expr: 0 },
490
+ g: 'expr,no-comma-op-bail',
491
+ }
492
+ : NONE,
493
+ hasInfix
494
+ ? {
495
+ s: [INFIX],
496
+ // Complete prefix first.
497
+ c: (r) => !r.n.expr_prefix,
498
+ b: 1,
499
+ r: 'expr',
500
+ g: 'expr,expr-infix,expr-prefix',
501
+ }
502
+ : NONE,
503
+ // TTT
504
+ hasInfix
505
+ ? {
506
+ s: [INFIX],
507
+ // Complete prefix first.
508
+ c: (r) => !!r.n.expr_prefix,
509
+ b: 1,
510
+ // r: 'expr',
511
+ g: 'expr,expr-infix',
512
+ }
513
+ : NONE,
514
+ hasSuffix
515
+ ? {
516
+ s: [SUFFIX],
517
+ c: (r) => !r.n.expr_prefix,
518
+ b: 1,
519
+ r: 'expr',
520
+ g: 'expr,expr-suffix,expr-prefix',
521
+ }
522
+ : NONE,
523
+ // TTT
524
+ // hasSuffix
525
+ // ? {
526
+ // s: [SUFFIX],
527
+ // c: (r: Rule) => !!r.n.expr_prefix,
528
+ // b: 1,
529
+ // r: 'expr',
530
+ // g: 'expr,expr-suffix',
531
+ // }
532
+ // : NONE,
533
+ hasParen
534
+ ? {
535
+ s: [CP],
536
+ c: (r) => !!r.n.expr_paren,
537
+ b: 1,
538
+ }
539
+ : NONE,
540
+ hasTernary
541
+ ? {
542
+ s: [TERN0],
543
+ c: (r) => !r.n.expr_prefix,
544
+ b: 1,
545
+ r: 'ternary',
546
+ g: 'expr,expr-ternary',
547
+ }
548
+ : NONE,
549
+ // Implicit list at the top level.
550
+ {
551
+ s: [CA],
552
+ // c: { d: 0 },
553
+ c: (r) => r.d <= 0,
554
+ n: { expr: 0 },
555
+ r: 'elem',
556
+ a: (r) => (r.parent.node = r.node = [r.node]),
557
+ g: 'expr,comma,list,top',
558
+ },
559
+ // Implicit list at the top level.
560
+ {
561
+ s: [VAL],
562
+ // c: { d: 0 },
563
+ c: (r) => r.d <= 0,
564
+ n: { expr: 0 },
565
+ b: 1,
566
+ r: 'elem',
567
+ a: (r) => (r.parent.node = r.node = [r.node]),
568
+ g: 'expr,space,list,top',
569
+ },
570
+ // Implicit list indicated by comma.
571
+ {
572
+ s: [CA],
573
+ c: (r) => r.lte('pk'),
574
+ n: { expr: 0 },
575
+ b: 1,
576
+ h: implicitList,
577
+ g: 'expr,list,val,imp,comma',
578
+ },
579
+ // Implicit list indicated by space separated value.
580
+ {
581
+ c: (r) => r.lte('pk') && r.lte('expr_suffix'),
582
+ n: { expr: 0 },
583
+ h: implicitList,
584
+ g: 'expr,list,val,imp,space',
585
+ },
586
+ // Expression ends on non-expression token.
587
+ {
588
+ n: { expr: 0 },
589
+ g: 'expr,expr-end',
590
+ },
591
+ ])
592
+ .ac((r, ctx) => {
593
+ // Only evaluate at root of expr (where r.n.expr === 0)
594
+ // console.log('EXPR-AC', r.name, r.i, r.n, p(r.node),
595
+ // 'P', r.parent.name, r.parent.i, r.parent.n, p(r.parent.node))
596
+ if (options.evaluate && 0 === r.n.expr) {
597
+ // The parent node will contain the root of the expr tree.
598
+ let out = evaluation(r.parent, ctx, r.parent.node, options.evaluate);
599
+ r.parent.node = out;
600
+ // Also write the evaluated result onto this expr rule's own
601
+ // node. When the expr rule was PUSHED from a still-open val
602
+ // (the prefix/paren forms: `+1`, `(a)`), that val's close
603
+ // coalescing reads its child's node (this expr rule). Under
604
+ // the new builtins val-close coalesces child-container over
605
+ // the seeded node, which would otherwise restore the raw
606
+ // op-array and discard the evaluated value. Writing `out`
607
+ // here makes the coalesced value the evaluated result (a
608
+ // primitive scalar or a real container), not the op-array.
609
+ r.node = out;
610
+ }
611
+ });
612
+ });
613
+ rule('paren', (rs) => {
614
+ rs.bo((r) => {
615
+ // Allow implicits inside parens
616
+ r.n.dmap = 0;
617
+ r.n.dlist = 0;
618
+ r.n.pk = 0;
619
+ })
620
+ .open([
621
+ hasParen
622
+ ? {
623
+ s: [OP, CP],
624
+ b: 1,
625
+ g: 'expr,expr-paren,empty',
626
+ c: (r) => parenOTM[r.o0.tin].name === parenCTM[r.o1.tin].name,
627
+ a: makeOpenParen(parenOTM),
628
+ }
629
+ : NONE,
630
+ hasParen
631
+ ? {
632
+ s: [OP],
633
+ p: 'val',
634
+ n: {
635
+ expr_paren: 1,
636
+ expr: 0,
637
+ expr_prefix: 0,
638
+ expr_suffix: 0,
639
+ },
640
+ g: 'expr,expr-paren,open',
641
+ a: makeOpenParen(parenOTM),
642
+ }
643
+ : NONE,
644
+ ])
645
+ .close([
646
+ hasParen
647
+ ? {
648
+ s: [CP],
649
+ c: (r) => {
650
+ const pdef = parenCTM[r.c0.tin];
651
+ let pd = 'expr_paren_depth_' + pdef.name;
652
+ return !!r.n[pd];
653
+ },
654
+ a: makeCloseParen(parenCTM),
655
+ g: 'expr,expr-paren,close',
656
+ }
657
+ : NONE,
658
+ ])
659
+ .ac((r, _ctx) => {
660
+ r.parent.node = r.node;
661
+ r.parent.parent.node = r.node;
662
+ });
663
+ });
664
+ // Ternary operators are like fancy parens.
665
+ if (hasTernary) {
666
+ rule('ternary', (rs) => {
667
+ rs.open([
668
+ {
669
+ s: [TERN0],
670
+ p: 'val',
671
+ n: {
672
+ expr_ternary: 1,
673
+ expr: 0,
674
+ expr_prefix: 0,
675
+ expr_suffix: 0,
676
+ },
677
+ u: { expr_ternary_step: 1 },
678
+ g: 'expr,expr-ternary,open',
679
+ a: (r) => {
680
+ let op = makeOp(r.o0, ternaryTM);
681
+ r.u.expr_ternary_name = op.name;
682
+ if (isOp(r.prev.node)) {
683
+ r.node = updateExprNode(r.prev.node, op, dupNode(r.prev.node));
684
+ }
685
+ else {
686
+ r.node = r.prev.node = updateExprNode([], op, r.prev.node);
687
+ }
688
+ r.u.expr_ternary_paren =
689
+ r.n.expr_paren || r.prev.u.expr_ternary_paren || 0;
690
+ r.n.expr_paren = 0;
691
+ },
692
+ },
693
+ {
694
+ p: 'val',
695
+ c: (r) => 2 === r.prev.u.expr_ternary_step,
696
+ a: (r) => {
697
+ r.u.expr_ternary_step = r.prev.u.expr_ternary_step;
698
+ r.n.expr_paren = r.u.expr_ternary_paren =
699
+ r.prev.u.expr_ternary_paren;
700
+ },
701
+ g: 'expr,expr-ternary,step',
702
+ },
703
+ ]).close([
704
+ {
705
+ s: [TERN1],
706
+ c: (r) => {
707
+ return (1 === r.u.expr_ternary_step &&
708
+ r.u.expr_ternary_name === ternaryTM[r.c0.tin].name);
709
+ },
710
+ r: 'ternary',
711
+ a: (r) => {
712
+ r.u.expr_ternary_step++;
713
+ r.node.push(r.child.node);
714
+ },
715
+ g: 'expr,expr-ternary,step',
716
+ },
717
+ // End of ternary at top level. Implicit list indicated by comma.
718
+ {
719
+ s: [[CA, ...CP]],
720
+ c: implicitTernaryCond,
721
+ // Handle ternary as first item of imp list inside paren.
722
+ b: (_r, ctx) => (CP.includes(ctx.t0.tin) ? 1 : 0),
723
+ r: (r, ctx) => !CP.includes(ctx.t0.tin) &&
724
+ (0 === r.d ||
725
+ (r.prev.u.expr_ternary_paren && !r.parent.node?.length))
726
+ ? 'elem'
727
+ : '',
728
+ a: implicitTernaryAction,
729
+ g: 'expr,expr-ternary,list,val,imp,comma',
730
+ },
731
+ // End of ternary at top level.
732
+ // Implicit list indicated by space separated value.
733
+ {
734
+ c: implicitTernaryCond,
735
+ // Handle ternary as first item of imp list inside paren.
736
+ r: (r, ctx) => {
737
+ return (0 === r.d ||
738
+ !CP.includes(ctx.t0.tin) ||
739
+ r.prev.u.expr_ternary_paren) &&
740
+ !r.parent.node?.length &&
741
+ ZZ !== ctx.t0.tin
742
+ ? 'elem'
743
+ : '';
744
+ },
745
+ a: implicitTernaryAction,
746
+ g: 'expr,expr-ternary,list,val,imp,space',
747
+ },
748
+ // End of ternary.
749
+ {
750
+ c: (r) => 0 < r.d && 2 === r.u.expr_ternary_step,
751
+ a: (r) => {
752
+ r.node.push(r.child.node);
753
+ },
754
+ g: 'expr,expr-ternary,close',
755
+ },
756
+ ])
757
+ // Ensure ternary results get evaluated. Without this, ternaries
758
+ // that aren't wrapped in expr (e.g. val.close's TERN0 alt does
759
+ // `r: 'ternary'` directly, not via an expr intermediate) leave
760
+ // the result as a raw [op_ternary, ...] op-array. Fire on every
761
+ // ternary instance's after-close, but only act when the chain
762
+ // has reached its final step (the node has accumulated all 3
763
+ // operands and r.next is not another ternary). Walk the
764
+ // r.prev chain back to the original rule (the val that started
765
+ // `r: 'ternary'`) and write the evaluated CST so jsonic returns
766
+ // the structured conditional_expression instead of the op-array.
767
+ .ac((r, ctx) => {
768
+ if (!options.evaluate)
769
+ return;
770
+ // Skip while the chain is still ongoing: r:'ternary' replaces
771
+ // the current rule with another ternary instance, and we want
772
+ // to evaluate only on the FINAL step. r.next is the rule the
773
+ // parser will process after this .ac returns.
774
+ if (r.next && r.next.name === 'ternary')
775
+ return;
776
+ if (!Array.isArray(r.node))
777
+ return;
778
+ if (!isOp(r.node))
779
+ return;
780
+ // Op-array isn't fully populated until 3 operands (cond, then,
781
+ // else) sit at r.node[1..3]. Early steps have length 2 or 3.
782
+ if (r.node.length < 4)
783
+ return;
784
+ const out = evaluation(r, ctx, r.node, options.evaluate);
785
+ // Write the evaluated CST back to every rule along the
786
+ // r:-replacement chain (each successive ternary instance and
787
+ // the original val that started the chain). Their .node
788
+ // references currently all point at the same op-array;
789
+ // replace them all with the structured CST so ctx.root().node
790
+ // — whichever one jsonic returns — reflects the evaluated form.
791
+ let cur = r;
792
+ while (cur) {
793
+ cur.node = out;
794
+ cur = cur.prev;
795
+ }
796
+ if (r.parent)
797
+ r.parent.node = out;
798
+ });
799
+ });
800
+ }
801
+ };
802
+ exports.Expr = Expr;
803
+ // Convert prior (parent or previous) rule node into an expression.
804
+ function prior(rule, prior, op, whence) {
805
+ // console.log('PRIOR', whence, rule.i, p(rule.node), 'PR', prior.i, p(prior.node))
806
+ let prior_node = prior.node;
807
+ if (isOp(prior.node) && !op.prefix) {
808
+ // Infix/suffix: the prior node is the genuine first term to wrap
809
+ // (e.g. the `(1+2)` of `(1+2)+3`). Duplicate it so the in-place
810
+ // rewrite below preserves referential integrity.
811
+ prior_node = dupNode(prior.node);
812
+ }
813
+ else {
814
+ // A prefix op never has a prior term, so any op-array sitting on
815
+ // prior.node here is a foreign parent-seed (a ternary/paren wrapper
816
+ // threaded down by the engine). Start fresh rather than mutating it.
817
+ prior.node = [];
818
+ }
819
+ updateExprNode(prior.node, op);
820
+ if (!op.prefix) {
821
+ prior.node[1] = prior_node;
822
+ }
823
+ // Ensure first term val rule contains final expression.
824
+ rule.parent = prior;
825
+ return prior.node;
826
+ }
827
+ // Add token so that expression evaluator can reference source locations.
828
+ function makeOp(t, om) {
829
+ return { ...om[t.tin], token: t, OP_MARK };
830
+ }
831
+ function updateExprNode(node, op, ...terms) {
832
+ let out = node;
833
+ out[0] = op;
834
+ let tI = 0;
835
+ for (; tI < terms.length; tI++) {
836
+ out[tI + 1] = terms[tI];
837
+ }
838
+ out.length = tI + 1;
839
+ return out;
840
+ }
841
+ function dupNode(node) {
842
+ let out = [...node];
843
+ return out;
844
+ }
845
+ function makeOpenParen(parenOTM) {
846
+ return function openParen(r) {
847
+ const op = makeOp(r.o0, parenOTM);
848
+ let pd = 'expr_paren_depth_' + op.name;
849
+ r.u[pd] = r.n[pd] = 1;
850
+ r.node = undefined;
851
+ };
852
+ }
853
+ function makeCloseParen(parenCTM) {
854
+ return function closeParen(r) {
855
+ if (isOp(r.child.node)) {
856
+ r.node = r.child.node;
857
+ }
858
+ else if (undefined === r.node) {
859
+ r.node = r.child.node;
860
+ }
861
+ const op = makeOp(r.c0, parenCTM);
862
+ let pd = 'expr_paren_depth_' + op.name;
863
+ // Construct completed paren expression.
864
+ if (r.u[pd] === r.n[pd]) {
865
+ const val = r.node;
866
+ // r.node = [op.osrc]
867
+ r.node = [op];
868
+ // WWW
869
+ if (r.parent.parent?.u?.paren_preval
870
+ && undefined !== r.parent.parent.node) {
871
+ r.node.push(r.parent.parent.node);
872
+ }
873
+ if (undefined !== val) {
874
+ r.node.push(val);
875
+ }
876
+ // console.log('MCP', r.i, r.name, p(r.node),
877
+ // 'PP', r.parent.parent?.u, p(r.parent.parent?.node), 'N', p(r.node))
878
+ // WWW
879
+ // if (r.parent.prev.u.paren_preval) {
880
+ // if (isParenOp(r.parent.prev.node)) {
881
+ // r.node = updateExprNode(
882
+ // r.parent.prev.node,
883
+ // r.node[0],
884
+ // dupNode(r.parent.prev.node),
885
+ // r.node[1],
886
+ // )
887
+ // } else {
888
+ // r.node.splice(1, 0, r.parent.prev.node)
889
+ // r.parent.prev.node = r.node
890
+ // }
891
+ // }
892
+ }
893
+ };
894
+ }
895
+ function implicitList(rule, ctx, a) {
896
+ let paren = null;
897
+ // Find the paren rule that contains this implicit list.
898
+ // If a map or list rule sits between the expression and the paren,
899
+ // the expression is inside a contained value — not a direct paren
900
+ // child — so don't create an implicit list.
901
+ for (let rI = ctx.rsI - 1; -1 < rI; rI--) {
902
+ if ('paren' === ctx.rs[rI].name) {
903
+ paren = ctx.rs[rI];
904
+ break;
905
+ }
906
+ if ('map' === ctx.rs[rI].name || 'list' === ctx.rs[rI].name) {
907
+ return a;
908
+ }
909
+ }
910
+ if (paren) {
911
+ // Create a list value for the paren rule.
912
+ if (null == paren.child.node) {
913
+ paren.child.node = [rule.node];
914
+ a.r = 'elem';
915
+ a.b = 0;
916
+ }
917
+ // Convert paren value into a list value.
918
+ else if (isOp(paren.child.node)) {
919
+ paren.child.node = [paren.child.node];
920
+ a.r = 'elem';
921
+ a.b = 0;
922
+ }
923
+ rule.node = paren.child.node;
924
+ }
925
+ return a;
926
+ }
927
+ function implicitTernaryCond(r) {
928
+ let cond = (0 === r.d || 1 <= r.n.expr_paren) && !r.n.pk && 2 === r.u.expr_ternary_step;
929
+ return cond;
930
+ }
931
+ function implicitTernaryAction(r, _ctx, a) {
932
+ r.n.expr_paren = r.prev.u.expr_ternary_paren;
933
+ r.node.push(r.child.node);
934
+ if ('elem' === a.r) {
935
+ r.node[0] = dupNode(r.node);
936
+ r.node.length = 1;
937
+ }
938
+ }
939
+ function isParenOp(node) {
940
+ return isOpKind('paren', node);
941
+ }
942
+ function isTernaryOp(node) {
943
+ return isOpKind('ternary', node);
944
+ }
945
+ function isOpKind(kind, node) {
946
+ return null == node ? false : isOp(node) && true === node[0][kind];
947
+ }
948
+ function isOp(node) {
949
+ return null == node ? false : node[0] && node[0].OP_MARK === OP_MARK;
950
+ }
951
+ function makeOpMap(token, fixed, op, anyfix) {
952
+ return Object.entries(op)
953
+ .filter(([_, opdef]) => opdef[anyfix])
954
+ .reduce((odm, [name, opdef]) => {
955
+ let tkn = '';
956
+ let tin = -1;
957
+ let src = '';
958
+ if ('string' === typeof opdef.src) {
959
+ src = opdef.src;
960
+ }
961
+ else {
962
+ src = opdef.src[0];
963
+ }
964
+ tin = (fixed(src) || token('#E' + src));
965
+ tkn = token(tin);
966
+ let op = (odm[tin] = {
967
+ src: src,
968
+ left: opdef.left || Number.MIN_SAFE_INTEGER,
969
+ right: opdef.right || Number.MAX_SAFE_INTEGER,
970
+ name: name + (name.endsWith('-' + anyfix) ? '' : '-' + anyfix),
971
+ infix: 'infix' === anyfix,
972
+ prefix: 'prefix' === anyfix,
973
+ suffix: 'suffix' === anyfix,
974
+ ternary: 'ternary' === anyfix,
975
+ tkn,
976
+ tin,
977
+ terms: 'ternary' === anyfix ? 3 : 'infix' === anyfix ? 2 : 1,
978
+ use: {},
979
+ paren: false,
980
+ osrc: '',
981
+ csrc: '',
982
+ otkn: '',
983
+ ctkn: '',
984
+ otin: -1,
985
+ ctin: -1,
986
+ preval: {
987
+ active: false,
988
+ required: false,
989
+ },
990
+ token: {},
991
+ OP_MARK,
992
+ });
993
+ // Handle the second operator if ternary.
994
+ if (op.ternary) {
995
+ let srcs = opdef.src;
996
+ op.src = srcs[0];
997
+ op.use.ternary = { opI: 0 };
998
+ let op2 = { ...op };
999
+ src = opdef.src[1];
1000
+ tin = (fixed(src) || token('#E' + src));
1001
+ tkn = token(tin);
1002
+ op2.src = src;
1003
+ op2.use = { ternary: { opI: 1 } };
1004
+ op2.tkn = tkn;
1005
+ op2.tin = tin;
1006
+ odm[tin] = op2;
1007
+ }
1008
+ return odm;
1009
+ }, {});
1010
+ }
1011
+ function makeParenMap(token, fixed, optop) {
1012
+ return entries(optop).reduce((a, [name, pdef]) => {
1013
+ if (pdef.paren) {
1014
+ let otin = (fixed(pdef.osrc) || token('#E' + pdef.osrc));
1015
+ let otkn = token(otin);
1016
+ let ctin = (fixed(pdef.csrc) || token('#E' + pdef.csrc));
1017
+ let ctkn = token(ctin);
1018
+ a[otin] = {
1019
+ name: name + '-paren',
1020
+ osrc: pdef.osrc,
1021
+ csrc: pdef.csrc,
1022
+ otkn,
1023
+ otin,
1024
+ ctkn,
1025
+ ctin,
1026
+ preval: {
1027
+ // True by default if preval specified.
1028
+ active: null == pdef.preval
1029
+ ? false
1030
+ : null == pdef.preval.active
1031
+ ? true
1032
+ : pdef.preval.active,
1033
+ // False by default.
1034
+ required: null == pdef.preval
1035
+ ? false
1036
+ : null == pdef.preval.required
1037
+ ? false
1038
+ : pdef.preval.required,
1039
+ },
1040
+ use: {},
1041
+ paren: true,
1042
+ src: pdef.osrc,
1043
+ // left: -1,
1044
+ // right: -1,
1045
+ left: Number.MIN_SAFE_INTEGER,
1046
+ right: Number.MAX_SAFE_INTEGER,
1047
+ infix: false,
1048
+ prefix: false,
1049
+ suffix: false,
1050
+ ternary: false,
1051
+ tkn: '',
1052
+ tin: -1,
1053
+ terms: 1,
1054
+ token: {},
1055
+ OP_MARK,
1056
+ };
1057
+ }
1058
+ return a;
1059
+ }, {});
1060
+ }
1061
+ // Default operator precedence — the "binding power" scale.
1062
+ //
1063
+ // Binding powers are compared only by ORDER, never by magnitude (every
1064
+ // comparison in prattify() is a `<` / `<=`): a higher number binds tighter.
1065
+ // Associativity is set by left vs right — left < right is left-associative
1066
+ // (a-b-c => (a-b)-c); left > right is right-associative. An unset left/right
1067
+ // falls back to MIN/MAX_SAFE_INTEGER (binds loosest on the left, tightest on
1068
+ // the right), which is why prefix ops give only `right` and parens give
1069
+ // neither.
1070
+ //
1071
+ // The defaults occupy a compact low block on a base-1000000 ladder, leaving the
1072
+ // entire range above the built-ins open for client operators. Tiers are 1000000
1073
+ // apart; the +100000 on `right` is the left-associativity offset, so a tier
1074
+ // occupies a 100000-wide band inside its 1000000-wide slot and adjacent tiers
1075
+ // can never overlap. The unary prefix is the tightest built-in (4000000);
1076
+ // everything above it is free for tighter client operators (exponent, postfix,
1077
+ // call/member, …), and looser operators (assignment, ternary, logical, …) slot
1078
+ // in below addition:
1079
+ //
1080
+ // <1000000 looser client ops (assignment, ternary, logical, comparison, …)
1081
+ // 1000000 sequence / comma
1082
+ // 2000000 addition / subtraction <-- built-in
1083
+ // 3000000 multiplication / division / remainder <-- built-in
1084
+ // 4000000 unary prefix (+ -) <-- built-in (tightest)
1085
+ // 5000000 exponent (** ^, right-assoc: left 5100000, right 5000000)
1086
+ // 6000000 postfix / suffix (! ? ++)
1087
+ // 7000000 call / index / member (f() a[i] a.b)
1088
+ // 8000000+ free — the whole range above 4000000 is open for client ops
1089
+ //
1090
+ // To add an operator, pick a tier base = N*1000000 (left = base, right = base +
1091
+ // 100000 for left-assoc; swap them for right-assoc). Tighter than the built-ins?
1092
+ // Use 5000000 and up. Looser? Use below 2000000. Sub-divide any gap with
1093
+ // base + 200000, base + 400000, … — each 1000000-wide gap holds ~4 sub-tiers.
1094
+ Expr.defaults = {
1095
+ op: {
1096
+ positive: {
1097
+ prefix: true,
1098
+ right: 4000000,
1099
+ src: '+',
1100
+ },
1101
+ negative: {
1102
+ prefix: true,
1103
+ right: 4000000,
1104
+ src: '-',
1105
+ },
1106
+ // NOTE: all these are left-associative as left < right
1107
+ // Example: 2+3+4 === (2+3)+4
1108
+ addition: {
1109
+ infix: true,
1110
+ left: 2000000,
1111
+ right: 2100000,
1112
+ src: '+',
1113
+ },
1114
+ subtraction: {
1115
+ infix: true,
1116
+ left: 2000000,
1117
+ right: 2100000,
1118
+ src: '-',
1119
+ },
1120
+ multiplication: {
1121
+ infix: true,
1122
+ left: 3000000,
1123
+ right: 3100000,
1124
+ src: '*',
1125
+ },
1126
+ division: {
1127
+ infix: true,
1128
+ left: 3000000,
1129
+ right: 3100000,
1130
+ src: '/',
1131
+ },
1132
+ remainder: {
1133
+ infix: true,
1134
+ left: 3000000,
1135
+ right: 3100000,
1136
+ src: '%',
1137
+ },
1138
+ plain: {
1139
+ paren: true,
1140
+ osrc: '(',
1141
+ csrc: ')',
1142
+ },
1143
+ },
1144
+ };
1145
+ // Pratt algorithm embeds next operator.
1146
+ // NOTE: preserves referential integrity of root expression.
1147
+ function prattify(expr, op, whence) {
1148
+ // console.log('PRATT-START', whence, p(expr), op?.name)
1149
+ let out = expr;
1150
+ let expr_op = expr[0];
1151
+ if (op) {
1152
+ if (op.infix) {
1153
+ // op is lower
1154
+ if (expr_op.suffix || op.left <= expr_op.right) {
1155
+ updateExprNode(expr, op, dupNode(expr));
1156
+ }
1157
+ // op is higher
1158
+ else {
1159
+ const end = expr_op.terms;
1160
+ if (isOp(expr[end]) && expr[end][0].right < op.left) {
1161
+ out = prattify(expr[end], op, 'prattify-infix');
1162
+ // console.log('PRATT-INFIX-H0', end, out)
1163
+ }
1164
+ else {
1165
+ out = expr[end] = updateExprNode([], op, expr[end]);
1166
+ // console.log('PRATT-INFIX-H1', end, out)
1167
+ }
1168
+ }
1169
+ }
1170
+ else if (op.prefix) {
1171
+ out = expr[expr_op.terms] = updateExprNode([], op);
1172
+ }
1173
+ else if (op.suffix) {
1174
+ if (!expr_op.suffix && expr_op.right <= op.left) {
1175
+ const end = expr_op.terms;
1176
+ // NOTE: special case: higher precedence suffix "drills" into
1177
+ // lower precedence prefixes: @@1! => @(@(1!)), not @((@1)!)
1178
+ if (isOp(expr[end]) &&
1179
+ expr[end][0].prefix &&
1180
+ expr[end][0].right < op.left) {
1181
+ prattify(expr[end], op, 'prattify-suffix');
1182
+ }
1183
+ else {
1184
+ expr[end] = updateExprNode([], op, expr[end]);
1185
+ }
1186
+ }
1187
+ else {
1188
+ updateExprNode(expr, op, dupNode(expr));
1189
+ }
1190
+ }
1191
+ }
1192
+ // console.log('PRATT-END', whence, p(out), op?.name)
1193
+ return out;
1194
+ }
1195
+ function evaluation(rule, ctx, expr, evaluate) {
1196
+ let out = expr ?? null;
1197
+ if (null != expr && isOp(expr)) {
1198
+ out = evaluate(rule, ctx, expr[0], expr.slice(1).map((term) => evaluation(rule, ctx, term, evaluate)));
1199
+ }
1200
+ // console.log('EXPR-EVAL', expr, '->', out)
1201
+ return out;
1202
+ }
1203
+ function p(node, seen) {
1204
+ seen = seen ?? new WeakSet();
1205
+ if (seen.has(node)) {
1206
+ return '[CIRCLE]';
1207
+ }
1208
+ else if (null != node && 'object' === typeof node) {
1209
+ seen.add(node);
1210
+ }
1211
+ if (Array.isArray(node) && 'string' === typeof node[0]?.src) {
1212
+ return ['OP' + node[0]?.src, ...(node.slice(1).map((n) => p(n, seen)))];
1213
+ }
1214
+ return node;
1215
+ }
1216
+ const testing = {
1217
+ prattify,
1218
+ opify: (x) => ((x.OP_MARK = OP_MARK), x),
1219
+ };
1220
+ exports.testing = testing;
1221
+ //# sourceMappingURL=expr.js.map