• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

jsonicjs / expr / 12893846928

21 Jan 2025 06:53PM UTC coverage: 94.041% (+0.1%) from 93.939%
12893846928

push

github

rjrodger
v1.2.0

224 of 251 branches covered (89.24%)

Branch coverage included in aggregate %.

5 of 5 new or added lines in 1 file covered. (100.0%)

4 existing lines in 1 file now uncovered.

281 of 286 relevant lines covered (98.25%)

3372.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.04
/expr.js
1
"use strict";
2
/* Copyright (c) 2021-2025 Richard Rodger, MIT License */
3
Object.defineProperty(exports, "__esModule", { value: true });
18✔
4
exports.testing = exports.Expr = void 0;
18✔
5
exports.evaluate = evaluate;
18✔
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: increase infix base binding values
36
// TODO: error on incomplete expr: 1+2+
37
const jsonic_1 = require("jsonic");
18✔
38
const { omap, entries, values } = jsonic_1.util;
18✔
39
// Mark Operator objects as owned by this plugin.
40
const OP_MARK = {};
18✔
41
// The plugin itself.
42
let Expr = function expr(jsonic, options) {
18✔
43
    // Ensure comment matcher is first to avoid conflicts with
44
    // comment markers (//, /*, etc)
45
    // let lexm = jsonic.options.lex?.match || []
46
    // let cmI: number = lexm.map((m) => m.name).indexOf('makeCommentMatcher')
47
    // if (0 < cmI) {
48
    //   jsonic.options({
49
    //     lex: {
50
    //       match: [lexm[cmI], ...lexm.slice(0, cmI), ...lexm.slice(cmI + 1)],
51
    //     },
52
    //   })
53
    // }
54
    let token = jsonic.token.bind(jsonic);
324✔
55
    let fixed = jsonic.fixed.bind(jsonic);
324✔
56
    // Build token maps (TM).
57
    let optop = options.op || {};
324!
58
    const prefixTM = makeOpMap(token, fixed, optop, 'prefix');
324✔
59
    const suffixTM = makeOpMap(token, fixed, optop, 'suffix');
324✔
60
    const infixTM = makeOpMap(token, fixed, optop, 'infix');
324✔
61
    const ternaryTM = makeOpMap(token, fixed, optop, 'ternary');
324✔
62
    const parenOTM = makeParenMap(token, fixed, optop);
324✔
63
    const parenCTM = omap(parenOTM, ([_, pdef]) => [
360✔
64
        undefined,
65
        undefined,
66
        pdef.ctin,
67
        pdef,
68
    ]);
69
    let parenFixed = Object.values({ ...parenOTM, ...parenCTM }).reduce((a, p) => ((a[p.otkn] = p.osrc), (a[p.ctkn] = p.csrc), a), {});
720✔
70
    // NOTE: operators with same src will generate same token - this is correct.
71
    let operatorFixed = Object.values({
324✔
72
        ...prefixTM,
73
        ...suffixTM,
74
        ...infixTM,
75
        ...ternaryTM,
76
    }).reduce((a, op) => ((a[op.tkn] = op.src), a), {});
1,926✔
77
    jsonic.options({
324✔
78
        fixed: {
79
            token: { ...operatorFixed, ...parenFixed },
80
        },
81
        lex: {
82
            match: {
83
                comment: { order: 1e5 },
84
            },
85
        },
86
    });
87
    const PREFIX = values(prefixTM).map((op) => op.tin);
675✔
88
    const INFIX = values(infixTM).map((op) => op.tin);
1,683✔
89
    const SUFFIX = values(suffixTM).map((op) => op.tin);
324✔
90
    const TERN0 = values(ternaryTM)
324✔
91
        .filter((op) => 0 === op.use.ternary.opI)
126✔
92
        .map((op) => op.tin);
63✔
93
    const TERN1 = values(ternaryTM)
324✔
94
        .filter((op) => 1 === op.use.ternary.opI)
126✔
95
        .map((op) => op.tin);
63✔
96
    const OP = values(parenOTM).map((pdef) => pdef.otin);
360✔
97
    const CP = values(parenCTM).map((pdef) => pdef.ctin);
360✔
98
    const hasPrefix = 0 < PREFIX.length;
324✔
99
    const hasInfix = 0 < INFIX.length;
324✔
100
    const hasSuffix = 0 < SUFFIX.length;
324✔
101
    const hasTernary = 0 < TERN0.length && 0 < TERN1.length;
324✔
102
    const hasParen = 0 < OP.length && 0 < CP.length;
324✔
103
    const CA = jsonic.token.CA;
324✔
104
    const CS = jsonic.token.CS;
324✔
105
    const CB = jsonic.token.CB;
324✔
106
    const TX = jsonic.token.TX;
324✔
107
    const NR = jsonic.token.NR;
324✔
108
    const ST = jsonic.token.ST;
324✔
109
    const VL = jsonic.token.VL;
324✔
110
    const ZZ = jsonic.token.ZZ;
324✔
111
    const VAL = [TX, NR, ST, VL];
324✔
112
    const NONE = null;
324✔
113
    jsonic.rule('val', (rs) => {
324✔
114
        // TODO: jsonic - make it easier to handle this case
115
        // Implicit pair not allowed inside ternary
116
        if (hasTernary && TERN1.includes(jsonic.token.CL)) {
324✔
117
            // let pairkeyalt: any = rs.def.open.find((a: any) => a.g.includes('pair'))
118
            // pairkeyalt.c = (r: Rule) => !r.n.expr_ternary
119
            rs.def.open
36✔
120
                .filter((a) => a.g.includes('pair'))
324✔
121
                .map((alt) => {
122
                let origcond = alt.c;
72✔
123
                let internary = (r) => !r.n.expr_ternary;
3,348✔
124
                alt.c = origcond
72✔
125
                    ? (r, ctx) => origcond(r, ctx) && internary(r)
3,348✔
126
                    : internary;
127
            });
128
        }
129
        rs.open([
324✔
130
            // The prefix operator of the first term of an expression.
131
            hasPrefix
324!
132
                ? {
133
                    s: [PREFIX],
134
                    b: 1,
135
                    n: { expr_prefix: 1, expr_suffix: 0 },
136
                    p: 'expr',
137
                    g: 'expr,expr-prefix',
138
                }
139
                : NONE,
140
            // An opening parenthesis of an expression.
141
            hasParen
324!
142
                ? {
143
                    s: [OP],
144
                    b: 1,
145
                    p: 'paren',
146
                    c: (r, ctx) => {
147
                        const pdef = parenOTM[r.o0.tin];
9,306✔
148
                        let pass = true;
9,306✔
149
                        if (pdef.preval.required) {
9,306✔
150
                            pass = 'val' === r.prev.name && r.prev.u.paren_preval;
981✔
151
                        }
152
                        // Paren with preval as first term becomes root.
153
                        if (pass) {
9,306✔
154
                            if (1 === r.prev.i) {
8,964✔
155
                                ctx.root = () => r;
414✔
156
                            }
157
                        }
158
                        return pass;
9,306✔
159
                    },
160
                    g: 'expr,expr-paren',
161
                }
162
                : NONE,
163
        ]).close([
164
            hasTernary
324✔
165
                ? {
166
                    s: [TERN0],
167
                    c: (r) => !r.n.expr,
3,348✔
168
                    b: 1,
169
                    r: 'ternary',
170
                    g: 'expr,expr-ternary',
171
                }
172
                : NONE,
173
            // The infix operator following the first term of an expression.
174
            hasInfix
324!
175
                ? {
176
                    s: [INFIX],
177
                    b: 1,
178
                    n: { expr_prefix: 0, expr_suffix: 0 },
179
                    r: (r) => (!r.n.expr ? 'expr' : ''),
7,101✔
180
                    g: 'expr,expr-infix',
181
                }
182
                : NONE,
183
            // The suffix operator following the first term of an expression.
184
            hasSuffix
324✔
185
                ? {
186
                    s: [SUFFIX],
187
                    b: 1,
188
                    n: { expr_prefix: 0, expr_suffix: 1 },
189
                    r: (r) => (!r.n.expr ? 'expr' : ''),
2,565✔
190
                    g: 'expr,expr-suffix',
191
                }
192
                : NONE,
193
            // The closing parenthesis of an expression.
194
            hasParen
324!
195
                ? {
196
                    s: [CP],
197
                    c: (r) => !!r.n.expr_paren,
9,054✔
198
                    b: 1,
199
                    g: 'expr,expr-paren',
200
                }
201
                : NONE,
202
            // The opening parenthesis of an expression with a preceding value.
203
            // foo(1) => ['(','foo',1]
204
            hasParen
324!
205
                ? {
206
                    s: [OP],
207
                    b: 1,
208
                    r: 'val',
209
                    c: (r) => parenOTM[r.c0.tin].preval.active,
2,259✔
210
                    u: { paren_preval: true },
211
                    g: 'expr,expr-paren,expr-paren-preval',
212
                }
213
                : NONE,
214
            hasTernary
324✔
215
                ? {
216
                    s: [TERN1],
217
                    c: (r) => !!r.n.expr_ternary,
3,465✔
218
                    b: 1,
219
                    g: 'expr,expr-ternary',
220
                }
221
                : NONE,
222
            // Don't create implicit list inside expression (comma separator).
223
            {
224
                s: [CA],
225
                c: (r) => (1 === r.d && (1 <= r.n.expr || 1 <= r.n.expr_ternary)) ||
9,756✔
226
                    (1 <= r.n.expr_ternary && 1 <= r.n.expr_paren),
227
                b: 1,
228
                g: 'expr,list,val,imp,comma,top',
229
            },
230
            // Don't create implicit list inside expression (space separator).
231
            {
232
                s: [VAL],
233
                c: (r) => (1 === r.d && (1 <= r.n.expr || 1 <= r.n.expr_ternary)) ||
5,625✔
234
                    (1 <= r.n.expr_ternary && 1 <= r.n.expr_paren),
235
                b: 1,
236
                g: 'expr,list,val,imp,space,top',
237
            },
238
        ]);
239
    });
240
    jsonic.rule('list', (rs) => {
324✔
241
        // rs.bo(false, (...rest: any) => {
242
        rs.bo(false, (r) => {
324✔
243
            // List elements are new expressions.
244
            // Unless this is an implicit list.
245
            if (!r.prev.u.implist) {
7,146✔
246
                r.n.expr = 0;
2,124✔
247
                r.n.expr_prefix = 0;
2,124✔
248
                r.n.expr_suffix = 0;
2,124✔
249
                r.n.expr_paren = 0;
2,124✔
250
                r.n.expr_ternary = 0;
2,124✔
251
            }
252
        }).close([
253
            hasParen && {
648✔
254
                s: [CP],
255
                // If end of normal list, consume `]` - it's not a close paren.
256
                b: (r) => (CS === r.c0.tin && !r.n.expr_paren ? 0 : 1),
4,104✔
257
            },
258
        ]);
259
    });
260
    jsonic.rule('map', (rs) => {
324✔
261
        rs.bo(false, (...rest) => {
324✔
262
            // Map values are new expressions.
263
            rest[0].n.expr = 0;
3,780✔
264
            rest[0].n.expr_prefix = 0;
3,780✔
265
            rest[0].n.expr_suffix = 0;
3,780✔
266
            rest[0].n.expr_paren = 0;
3,780✔
267
            rest[0].n.expr_ternary = 0;
3,780✔
268
        }).close([
269
            hasParen && {
648✔
270
                s: [CP],
271
                // If end of normal map, consume `}` - it's not a close paren.
272
                b: (r) => (CB === r.c0.tin && !r.n.expr_paren ? 0 : 1),
234✔
273
            },
274
        ]);
275
    });
276
    jsonic.rule('elem', (rs) => {
324✔
277
        rs.close([
324✔
278
            // Close implicit list within parens.
279
            hasParen
324!
280
                ? {
281
                    s: [CP],
282
                    b: 1,
283
                    c: (r) => !!r.n.expr_paren,
4,671✔
284
                    g: 'expr,expr-paren,imp,close,list',
285
                }
286
                : NONE,
287
            // Following elem is a paren expression.
288
            hasParen
324!
289
                ? {
290
                    s: [OP],
291
                    b: 1,
292
                    r: 'elem',
293
                    g: 'expr,expr-paren,imp,open,list',
294
                }
295
                : NONE,
296
        ]);
297
    });
298
    jsonic.rule('pair', (rs) => {
324✔
299
        rs.close([
324✔
300
            // Close implicit map within parens.
301
            hasParen
324!
302
                ? {
303
                    s: [CP],
304
                    b: 1,
305
                    c: (r) => !!r.n.expr_paren || 0 < r.n.pk,
234✔
306
                    g: 'expr,expr-paren,imp,map',
307
                }
308
                : NONE,
309
        ]);
310
    });
311
    jsonic.rule('expr', (rs) => {
324✔
312
        rs.open([
324✔
313
            hasPrefix
324!
314
                ? {
315
                    s: [PREFIX],
316
                    c: (r) => !!r.n.expr_prefix,
9,423✔
317
                    n: { expr: 1, dlist: 1, dmap: 1 },
318
                    p: 'val',
319
                    g: 'expr,expr-prefix',
320
                    a: (r) => {
321
                        const op = makeOp(r.o0, prefixTM);
3,204✔
322
                        r.node = isOp(r.parent.node)
3,204!
323
                            ? prattify(r.parent.node, op)
324
                            : prior(r, r.parent, op);
325
                    },
326
                }
327
                : NONE,
328
            hasInfix
324!
329
                ? {
330
                    s: [INFIX],
331
                    p: 'val',
332
                    n: { expr: 1, expr_prefix: 0, dlist: 1, dmap: 1 },
333
                    a: (r) => {
334
                        const prev = r.prev;
7,038✔
335
                        const parent = r.parent;
7,038✔
336
                        const op = makeOp(r.o0, infixTM);
7,038✔
337
                        // Second and further operators.
338
                        if (isOp(parent.node) && !isTernaryOp(parent.node)) {
7,038✔
339
                            r.node = prattify(parent.node, op);
2,754✔
340
                        }
341
                        // First term was unary expression.
342
                        else if (isOp(prev.node)) {
4,284✔
343
                            r.node = prattify(prev.node, op);
531✔
344
                            r.parent = prev;
531✔
345
                        }
346
                        // First term was plain value or ternary part.
347
                        else {
348
                            r.node = prior(r, prev, op);
3,753✔
349
                        }
350
                    },
351
                    g: 'expr,expr-infix',
352
                }
353
                : NONE,
354
            hasSuffix
324✔
355
                ? {
356
                    s: [SUFFIX],
357
                    n: { expr: 1, expr_prefix: 0, dlist: 1, dmap: 1 },
358
                    a: (r) => {
359
                        const prev = r.prev;
2,475✔
360
                        const op = makeOp(r.o0, suffixTM);
2,475✔
361
                        r.node = isOp(prev.node)
2,475✔
362
                            ? prattify(prev.node, op)
363
                            : prior(r, prev, op);
364
                    },
365
                    g: 'expr,expr-suffix',
366
                }
367
                : NONE,
368
        ])
369
            .bc((r) => {
370
            var _a;
371
            // Append final term to expression.
372
            if (isOp(r.node) && ((_a = r.node) === null || _a === void 0 ? void 0 : _a.length) - 1 < r.node[0].terms) {
12,717!
373
                r.node.push(r.child.node);
10,242✔
374
            }
375
        })
376
            .close([
377
            hasInfix
324!
378
                ? {
379
                    s: [INFIX],
380
                    // Complete prefix first.
381
                    c: (r) => !r.n.expr_prefix,
3,357✔
382
                    b: 1,
383
                    r: 'expr',
384
                    g: 'expr,expr-infix',
385
                }
386
                : NONE,
387
            hasSuffix
324✔
388
                ? {
389
                    s: [SUFFIX],
390
                    c: (r) => !r.n.expr_prefix,
1,179✔
391
                    b: 1,
392
                    r: 'expr',
393
                    g: 'expr,expr-suffix',
394
                }
395
                : NONE,
396
            hasParen
324!
397
                ? {
398
                    s: [CP],
399
                    c: (r) => !!r.n.expr_paren,
450✔
400
                    b: 1,
401
                }
402
                : NONE,
403
            hasTernary
324✔
404
                ? {
405
                    s: [TERN0],
406
                    c: (r) => !r.n.expr_prefix,
936✔
407
                    b: 1,
408
                    r: 'ternary',
409
                    g: 'expr,expr-ternary',
410
                }
411
                : NONE,
412
            // Implicit list at the top level.
413
            {
414
                s: [CA],
415
                // c: { d: 0 },
416
                c: (r) => r.d <= 0,
1,017✔
417
                n: { expr: 0 },
418
                r: 'elem',
419
                a: (r) => (r.parent.node = r.node = [r.node]),
99✔
420
                g: 'expr,comma,list,top',
421
            },
422
            // Implicit list at the top level.
423
            {
424
                s: [VAL],
425
                // c: { d: 0 },
426
                c: (r) => r.d <= 0,
909✔
427
                n: { expr: 0 },
428
                b: 1,
429
                r: 'elem',
430
                a: (r) => (r.parent.node = r.node = [r.node]),
81✔
431
                g: 'expr,space,list,top',
432
            },
433
            // Implicit list indicated by comma.
434
            {
435
                s: [CA],
436
                c: (r) => r.lte('pk'),
918✔
437
                n: { expr: 0 },
438
                b: 1,
439
                h: implicitList,
440
                g: 'expr,list,val,imp,comma',
441
            },
442
            // Implicit list indicated by space separated value.
443
            {
444
                c: (r) => r.lte('pk') && r.lte('expr_suffix'),
6,930✔
445
                n: { expr: 0 },
446
                h: implicitList,
447
                g: 'expr,list,val,imp,space',
448
            },
449
            // Expression ends on non-expression token.
450
            {
451
                n: { expr: 0 },
452
                g: 'expr,expr-end',
453
            },
454
        ])
455
            .ac((r, ctx) => {
456
            // Only evaluate at root of expr (where r.n.expr === 0)
457
            if (options.evaluate && 0 === r.n.expr) {
12,717✔
458
                // The parent node will contain the root of the expr tree
459
                r.parent.node = evaluate(r.parent, ctx, r.parent.node, options.evaluate);
108✔
460
            }
461
        });
462
    });
463
    jsonic.rule('paren', (rs) => {
324✔
464
        rs.bo((r) => {
324✔
465
            // Allow implicits inside parens
466
            r.n.dmap = 0;
8,964✔
467
            r.n.dlist = 0;
8,964✔
468
            r.n.pk = 0;
8,964✔
469
        })
470
            .open([
471
            hasParen
324!
472
                ? {
473
                    s: [OP, CP],
474
                    b: 1,
475
                    g: 'expr,expr-paren,empty',
476
                    c: (r) => parenOTM[r.o0.tin].name === parenCTM[r.o1.tin].name,
297✔
477
                    a: makeOpenParen(parenOTM),
478
                }
479
                : NONE,
480
            hasParen
324!
481
                ? {
482
                    s: [OP],
483
                    p: 'val',
484
                    n: {
485
                        expr_paren: 1,
486
                        expr: 0,
487
                        expr_prefix: 0,
488
                        expr_suffix: 0,
489
                    },
490
                    g: 'expr,expr-paren,open',
491
                    a: makeOpenParen(parenOTM),
492
                }
493
                : NONE,
494
        ])
495
            .close([
496
            hasParen
324!
497
                ? {
498
                    s: [CP],
499
                    c: (r) => {
500
                        const pdef = parenCTM[r.c0.tin];
8,964✔
501
                        let pd = 'expr_paren_depth_' + pdef.name;
8,964✔
502
                        return !!r.n[pd];
8,964✔
503
                    },
504
                    a: makeCloseParen(parenCTM),
505
                    g: 'expr,expr-paren,close',
506
                }
507
                : NONE,
508
        ]);
509
    });
510
    // Ternary operators are like fancy parens.
511
    if (hasTernary) {
324✔
512
        jsonic.rule('ternary', (rs) => {
36✔
513
            rs.open([
36✔
514
                {
515
                    s: [TERN0],
516
                    p: 'val',
517
                    n: {
518
                        expr_ternary: 1,
519
                        expr: 0,
520
                        expr_prefix: 0,
521
                        expr_suffix: 0,
522
                    },
523
                    u: { expr_ternary_step: 1 },
524
                    g: 'expr,expr-ternary,open',
525
                    a: (r) => {
526
                        let op = makeOp(r.o0, ternaryTM);
3,492✔
527
                        r.u.expr_ternary_name = op.name;
3,492✔
528
                        if (isOp(r.prev.node)) {
3,492✔
529
                            r.node = makeNode(r.prev.node, op, dupNode(r.prev.node));
936✔
530
                        }
531
                        else {
532
                            r.node = r.prev.node = makeNode([], op, r.prev.node);
2,556✔
533
                        }
534
                        r.u.expr_ternary_paren =
3,492✔
535
                            r.n.expr_paren || r.prev.u.expr_ternary_paren || 0;
10,152✔
536
                        r.n.expr_paren = 0;
3,492✔
537
                    },
538
                },
539
                {
540
                    p: 'val',
541
                    c: (r) => 2 === r.prev.u.expr_ternary_step,
3,492✔
542
                    a: (r) => {
543
                        r.u.expr_ternary_step = r.prev.u.expr_ternary_step;
3,492✔
544
                        r.n.expr_paren = r.u.expr_ternary_paren =
3,492✔
545
                            r.prev.u.expr_ternary_paren;
546
                    },
547
                    g: 'expr,expr-ternary,step',
548
                },
549
            ]).close([
550
                {
551
                    s: [TERN1],
552
                    c: (r) => {
553
                        return (1 === r.u.expr_ternary_step &&
4,716✔
554
                            r.u.expr_ternary_name === ternaryTM[r.c0.tin].name);
555
                    },
556
                    r: 'ternary',
557
                    a: (r) => {
558
                        r.u.expr_ternary_step++;
3,492✔
559
                        r.node.push(r.child.node);
3,492✔
560
                    },
561
                    g: 'expr,expr-ternary,step',
562
                },
563
                // End of ternary at top level. Implicit list indicated by comma.
564
                {
565
                    s: [[CA, ...CP]],
566
                    c: implicitTernaryCond,
567
                    // Handle ternary as first item of imp list inside paren.
568
                    b: (_r, ctx) => (CP.includes(ctx.t0.tin) ? 1 : 0),
126✔
569
                    r: (r, ctx) => {
570
                        var _a;
571
                        return !CP.includes(ctx.t0.tin) &&
126✔
572
                            (0 === r.d ||
573
                                (r.prev.u.expr_ternary_paren && !((_a = r.parent.node) === null || _a === void 0 ? void 0 : _a.length)))
162✔
574
                            ? 'elem'
575
                            : '';
576
                    },
577
                    a: implicitTernaryAction,
578
                    g: 'expr,expr-ternary,list,val,imp,comma',
579
                },
580
                // End of ternary at top level.
581
                // Implicit list indicated by space separated value.
582
                {
583
                    c: implicitTernaryCond,
584
                    // Handle ternary as first item of imp list inside paren.
585
                    r: (r, ctx) => {
586
                        var _a;
587
                        return (0 === r.d ||
1,350!
588
                            !CP.includes(ctx.t0.tin) ||
589
                            r.prev.u.expr_ternary_paren) &&
590
                            !((_a = r.parent.node) === null || _a === void 0 ? void 0 : _a.length) &&
4,050✔
591
                            ZZ !== ctx.t0.tin
592
                            ? 'elem'
593
                            : '';
594
                    },
595
                    a: implicitTernaryAction,
596
                    g: 'expr,expr-ternary,list,val,imp,space',
597
                },
598
                // End of ternary.
599
                {
600
                    c: (r) => 0 < r.d && 2 === r.u.expr_ternary_step,
2,016✔
601
                    a: (r) => {
602
                        r.node.push(r.child.node);
2,016✔
603
                    },
604
                    g: 'expr,expr-ternary,close',
605
                },
606
            ]);
607
        });
608
    }
609
};
610
exports.Expr = Expr;
18✔
611
// Convert prior (parent or previous) rule node into an expression.
612
function prior(rule, prior, op) {
613
    let prior_node = prior.node;
8,334✔
614
    if (isOp(prior.node)) {
8,334!
UNCOV
615
        prior_node = dupNode(prior.node);
×
616
    }
617
    else {
618
        prior.node = [];
8,334✔
619
    }
620
    makeNode(prior.node, op);
8,334✔
621
    if (!op.prefix) {
8,334✔
622
        prior.node[1] = prior_node;
5,130✔
623
    }
624
    // Ensure first term val rule contains final expression.
625
    rule.parent = prior;
8,334✔
626
    return prior.node;
8,334✔
627
}
628
// Add token so that expression evaluator can reference source locations.
629
function makeOp(t, om) {
630
    return { ...om[t.tin], token: t, OP_MARK };
34,128✔
631
}
632
function makeNode(node, op, ...terms) {
633
    let out = node;
16,911✔
634
    out[0] = op;
16,911✔
635
    let tI = 0;
16,911✔
636
    for (; tI < terms.length; tI++) {
16,911✔
637
        out[tI + 1] = terms[tI];
8,964✔
638
    }
639
    out.length = tI + 1;
16,911✔
640
    return out;
16,911✔
641
}
642
function dupNode(node) {
643
    let out = [...node];
4,734✔
644
    return out;
4,734✔
645
}
646
function makeOpenParen(parenOTM) {
647
    return function openParen(r) {
648✔
648
        const op = makeOp(r.o0, parenOTM);
8,964✔
649
        let pd = 'expr_paren_depth_' + op.name;
8,964✔
650
        r.u[pd] = r.n[pd] = 1;
8,964✔
651
        r.node = undefined;
8,964✔
652
    };
653
}
654
function makeCloseParen(parenCTM) {
655
    return function closeParen(r) {
324✔
656
        if (isOp(r.child.node)) {
8,955✔
657
            r.node = r.child.node;
675✔
658
        }
659
        else if (undefined === r.node) {
8,280!
660
            r.node = r.child.node;
8,280✔
661
        }
662
        const op = makeOp(r.c0, parenCTM);
8,955✔
663
        let pd = 'expr_paren_depth_' + op.name;
8,955✔
664
        // Construct completed paren expression.
665
        if (r.u[pd] === r.n[pd]) {
8,955!
666
            const val = r.node;
8,955✔
667
            // r.node = [op.osrc]
668
            r.node = [op];
8,955✔
669
            if (undefined !== val) {
8,955✔
670
                r.node[1] = val;
8,658✔
671
            }
672
            if (r.parent.prev.u.paren_preval) {
8,955✔
673
                if (isParenOp(r.parent.prev.node)) {
1,341✔
674
                    r.node = makeNode(r.parent.prev.node, r.node[0], dupNode(r.parent.prev.node), r.node[1]);
432✔
675
                }
676
                else {
677
                    r.node.splice(1, 0, r.parent.prev.node);
909✔
678
                    r.parent.prev.node = r.node;
909✔
679
                }
680
            }
681
        }
682
    };
683
}
684
function implicitList(rule, ctx, a) {
685
    let paren = null;
6,525✔
686
    // Find the paren rule that contains this implicit list.
687
    for (let rI = ctx.rsI - 1; -1 < rI; rI--) {
6,525✔
688
        if ('paren' === ctx.rs[rI].name) {
10,980✔
689
            paren = ctx.rs[rI];
1,071✔
690
            break;
1,071✔
691
        }
692
    }
693
    if (paren) {
6,525✔
694
        // Create a list value for the paren rule.
695
        if (null == paren.child.node) {
1,071!
696
            paren.child.node = [rule.node];
×
UNCOV
697
            a.r = 'elem';
×
UNCOV
698
            a.b = 0;
×
699
        }
700
        // Convert paren value into a list value.
701
        else if (isOp(paren.child.node)) {
1,071✔
702
            paren.child.node = [paren.child.node];
513✔
703
            a.r = 'elem';
513✔
704
            a.b = 0;
513✔
705
        }
706
        rule.node = paren.child.node;
1,071✔
707
    }
708
    return a;
6,525✔
709
}
710
function implicitTernaryCond(r) {
711
    let cond = (0 === r.d || 1 <= r.n.expr_paren) && !r.n.pk && 2 === r.u.expr_ternary_step;
3,510✔
712
    // console.log('****** ITC', cond, r.n.pk)
713
    return cond;
3,510✔
714
}
715
function implicitTernaryAction(r, _ctx, a) {
716
    r.n.expr_paren = r.prev.u.expr_ternary_paren;
1,476✔
717
    r.node.push(r.child.node);
1,476✔
718
    if ('elem' === a.r) {
1,476✔
719
        r.node[0] = dupNode(r.node);
90✔
720
        r.node.length = 1;
90✔
721
    }
722
}
723
function isParenOp(node) {
724
    return isOpKind('paren', node);
1,341✔
725
}
726
function isTernaryOp(node) {
727
    return isOpKind('ternary', node);
3,186✔
728
}
729
function isOpKind(kind, node) {
730
    return null == node ? false : isOp(node) && true === node[0][kind];
4,527!
731
}
732
function isOp(node) {
733
    return null == node ? false : node[0] && node[0].OP_MARK === OP_MARK;
59,220✔
734
}
735
function makeOpMap(token, fixed, op, anyfix) {
736
    return Object.entries(op)
1,296✔
737
        .filter(([_, opdef]) => opdef[anyfix])
11,556✔
738
        .reduce((odm, [name, opdef]) => {
739
        let tkn = '';
2,529✔
740
        let tin = -1;
2,529✔
741
        let src = '';
2,529✔
742
        if ('string' === typeof opdef.src) {
2,529✔
743
            src = opdef.src;
2,466✔
744
        }
745
        else {
746
            src = opdef.src[0];
63✔
747
        }
748
        tin = (fixed(src) || token('#E' + src));
2,529✔
749
        tkn = token(tin);
2,529✔
750
        let op = (odm[tin] = {
2,529✔
751
            src: src,
752
            left: opdef.left || Number.MIN_SAFE_INTEGER,
3,267✔
753
            right: opdef.right || Number.MAX_SAFE_INTEGER,
2,700✔
754
            name: name + (name.endsWith('-' + anyfix) ? '' : '-' + anyfix),
2,529✔
755
            infix: 'infix' === anyfix,
756
            prefix: 'prefix' === anyfix,
757
            suffix: 'suffix' === anyfix,
758
            ternary: 'ternary' === anyfix,
759
            tkn,
760
            tin,
761
            terms: 'ternary' === anyfix ? 3 : 'infix' === anyfix ? 2 : 1,
4,995✔
762
            use: {},
763
            paren: false,
764
            osrc: '',
765
            csrc: '',
766
            otkn: '',
767
            ctkn: '',
768
            otin: -1,
769
            ctin: -1,
770
            preval: {
771
                active: false,
772
                required: false,
773
            },
774
            token: {},
775
            OP_MARK,
776
        });
777
        // Handle the second operator if ternary.
778
        if (op.ternary) {
2,529✔
779
            let srcs = opdef.src;
63✔
780
            op.src = srcs[0];
63✔
781
            op.use.ternary = { opI: 0 };
63✔
782
            let op2 = { ...op };
63✔
783
            src = opdef.src[1];
63✔
784
            tin = (fixed(src) || token('#E' + src));
63✔
785
            tkn = token(tin);
63✔
786
            op2.src = src;
63✔
787
            op2.use = { ternary: { opI: 1 } };
63✔
788
            op2.tkn = tkn;
63✔
789
            op2.tin = tin;
63✔
790
            odm[tin] = op2;
63✔
791
        }
792
        return odm;
2,529✔
793
    }, {});
794
}
795
function makeParenMap(token, fixed, optop) {
796
    return entries(optop).reduce((a, [name, pdef]) => {
324✔
797
        if (pdef.paren) {
2,889✔
798
            let otin = (fixed(pdef.osrc) || token('#E' + pdef.osrc));
360✔
799
            let otkn = token(otin);
360✔
800
            let ctin = (fixed(pdef.csrc) || token('#E' + pdef.csrc));
360✔
801
            let ctkn = token(ctin);
360✔
802
            a[otin] = {
360✔
803
                name: name + '-paren',
804
                osrc: pdef.osrc,
805
                csrc: pdef.csrc,
806
                otkn,
807
                otin,
808
                ctkn,
809
                ctin,
810
                preval: {
811
                    // True by default if preval specified.
812
                    active: null == pdef.preval
360✔
813
                        ? false
814
                        : null == pdef.preval.active
54✔
815
                            ? true
816
                            : pdef.preval.active,
817
                    // False by default.
818
                    required: null == pdef.preval
360✔
819
                        ? false
820
                        : null == pdef.preval.required
54✔
821
                            ? false
822
                            : pdef.preval.required,
823
                },
824
                use: {},
825
                paren: true,
826
                src: pdef.osrc,
827
                // left: -1,
828
                // right: -1,
829
                left: Number.MIN_SAFE_INTEGER,
830
                right: Number.MAX_SAFE_INTEGER,
831
                infix: false,
832
                prefix: false,
833
                suffix: false,
834
                ternary: false,
835
                tkn: '',
836
                tin: -1,
837
                terms: 1,
838
                token: {},
839
                OP_MARK,
840
            };
841
        }
842
        return a;
2,889✔
843
    }, {});
844
}
845
Expr.defaults = {
18✔
846
    op: {
847
        positive: {
848
            prefix: true,
849
            right: 14000,
850
            src: '+',
851
        },
852
        negative: {
853
            prefix: true,
854
            right: 14000,
855
            src: '-',
856
        },
857
        // NOTE: all these are left-associative as left < right
858
        // Example: 2+3+4 === (2+3)+4
859
        addition: {
860
            infix: true,
861
            left: 140,
862
            right: 150,
863
            src: '+',
864
        },
865
        subtraction: {
866
            infix: true,
867
            left: 140,
868
            right: 150,
869
            src: '-',
870
        },
871
        multiplication: {
872
            infix: true,
873
            left: 160,
874
            right: 170,
875
            src: '*',
876
        },
877
        division: {
878
            infix: true,
879
            left: 160,
880
            right: 170,
881
            src: '/',
882
        },
883
        remainder: {
884
            infix: true,
885
            left: 160,
886
            right: 170,
887
            src: '%',
888
        },
889
        plain: {
890
            paren: true,
891
            osrc: '(',
892
            csrc: ')',
893
        },
894
    },
895
};
896
// Pratt algorithm embeds next operator.
897
// NOTE: preserves referential integrity of root expression.
898
function prattify(expr, op) {
899
    let out = expr;
5,031✔
900
    let expr_op = expr[0];
5,031✔
901
    if (op) {
5,031!
902
        if (op.infix) {
5,031✔
903
            // op is lower
904
            if (expr_op.suffix || op.left <= expr_op.right) {
3,744✔
905
                makeNode(expr, op, dupNode(expr));
2,790✔
906
            }
907
            // op is higher
908
            else {
909
                const end = expr_op.terms;
954✔
910
                if (isOp(expr[end]) && expr[end][0].right < op.left) {
954✔
911
                    out = prattify(expr[end], op);
315✔
912
                }
913
                else {
914
                    out = expr[end] = makeNode([], op, expr[end]);
639✔
915
                }
916
            }
917
        }
918
        else if (op.prefix) {
1,287✔
919
            out = expr[expr_op.terms] = makeNode([], op);
45✔
920
        }
921
        else if (op.suffix) {
1,242!
922
            if (!expr_op.suffix && expr_op.right <= op.left) {
1,242✔
923
                const end = expr_op.terms;
756✔
924
                // NOTE: special case: higher precedence suffix "drills" into
925
                // lower precedence prefixes: @@1! => @(@(1!)), not @((@1)!)
926
                if (isOp(expr[end]) &&
756✔
927
                    expr[end][0].prefix &&
928
                    expr[end][0].right < op.left) {
929
                    prattify(expr[end], op);
63✔
930
                }
931
                else {
932
                    expr[end] = makeNode([], op, expr[end]);
693✔
933
                }
934
            }
935
            else {
936
                makeNode(expr, op, dupNode(expr));
486✔
937
            }
938
        }
939
    }
940
    return out;
5,031✔
941
}
942
function evaluate(rule, ctx, expr, resolve) {
943
    if (null == expr) {
1,413!
UNCOV
944
        return expr;
×
945
    }
946
    if (isOp(expr)) {
1,413✔
947
        return resolve(rule, ctx, expr[0], expr.slice(1).map((term) => evaluate(rule, ctx, term, resolve)));
1,089✔
948
    }
949
    return expr;
828✔
950
}
951
const testing = {
18✔
952
    prattify,
953
    opify: (x) => ((x.OP_MARK = OP_MARK), x),
522✔
954
};
955
exports.testing = testing;
18✔
956
//# sourceMappingURL=expr.js.map
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc