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

jsonicjs / expr / 12956624386

24 Jan 2025 07:55PM UTC coverage: 93.636% (-0.4%) from 94.041%
12956624386

push

github

rjrodger
v1.3.0

230 of 259 branches covered (88.8%)

Branch coverage included in aggregate %.

285 of 291 relevant lines covered (97.94%)

3391.89 hits per line

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

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