grammatik 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/bin/grammatik.js +8 -0
- package/dist/cjs/index.js +1637 -139
- package/dist/cjs/runtime.js +642 -34
- package/dist/esm/index.js +1637 -139
- package/dist/esm/runtime.js +642 -34
- package/dist/types/index.d.ts +36 -0
- package/dist/types/runtime.d.ts +4 -3
- package/package.json +3 -2
- package/src/analyze.js +225 -18
- package/src/codegen.js +218 -27
- package/src/definition.js +5 -0
- package/src/emit.js +41 -7
- package/src/lexer.d.ts +43 -2
- package/src/lexer.js +573 -44
- package/src/lexgen.js +451 -0
- package/src/memoplan.js +220 -0
- package/src/methods.js +1 -1
- package/src/parser.js +38 -3
- package/src/plan.js +142 -30
- package/src/runtime.js +8 -6
- package/src/scan.js +422 -0
- package/src/state.js +33 -1
package/dist/cjs/index.js
CHANGED
|
@@ -365,7 +365,338 @@ function firstCharSet(source, ignoreCase) {
|
|
|
365
365
|
return found;
|
|
366
366
|
}
|
|
367
367
|
|
|
368
|
-
// src/
|
|
368
|
+
// src/scan.js
|
|
369
|
+
var DEFER = -2;
|
|
370
|
+
var CLASS = 0;
|
|
371
|
+
var SEQ2 = 1;
|
|
372
|
+
var KEYWORD = 2;
|
|
373
|
+
var LINE = 3;
|
|
374
|
+
var QUOTED = 4;
|
|
375
|
+
var NUMBER = 5;
|
|
376
|
+
var STRING = 6;
|
|
377
|
+
var SCANNER_KINDS = Object.freeze({ CLASS, SEQ2, KEYWORD, LINE, QUOTED, NUMBER, STRING });
|
|
378
|
+
var NONE = new Uint8Array(128);
|
|
379
|
+
function make(kind, fields) {
|
|
380
|
+
return {
|
|
381
|
+
kind,
|
|
382
|
+
wide: false,
|
|
383
|
+
first: NONE,
|
|
384
|
+
rest: NONE,
|
|
385
|
+
second: NONE,
|
|
386
|
+
look: NONE,
|
|
387
|
+
lower: [],
|
|
388
|
+
upper: [],
|
|
389
|
+
prefix: "",
|
|
390
|
+
quote: -1,
|
|
391
|
+
sign: false,
|
|
392
|
+
jsonInt: false,
|
|
393
|
+
frac: false,
|
|
394
|
+
exp: false,
|
|
395
|
+
ctrl: false,
|
|
396
|
+
uni: false,
|
|
397
|
+
escAny: false,
|
|
398
|
+
esc: NONE,
|
|
399
|
+
...fields
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
var SHORTHAND = {
|
|
403
|
+
d: (t) => {
|
|
404
|
+
for (let k = 48; k < 58; k++) t[k] = 1;
|
|
405
|
+
},
|
|
406
|
+
w: (t) => {
|
|
407
|
+
for (let k = 0; k < 128; k++) if (/\w/.test(String.fromCharCode(k))) t[k] = 1;
|
|
408
|
+
},
|
|
409
|
+
s: (t) => {
|
|
410
|
+
for (const k of [9, 10, 11, 12, 13, 32]) t[k] = 1;
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
var SIMPLE_ESCAPES2 = { t: " ", r: "\r", n: "\n", f: "\f", v: "\v" };
|
|
414
|
+
var ALNUM = /[a-zA-Z0-9]/;
|
|
415
|
+
function parseClass(body) {
|
|
416
|
+
if (body.length === 0 || body[0] === "^") return null;
|
|
417
|
+
const table = new Uint8Array(128);
|
|
418
|
+
for (let i = 0; i < body.length; i++) {
|
|
419
|
+
let c = body[i];
|
|
420
|
+
if (c === "\\") {
|
|
421
|
+
const n = body[++i];
|
|
422
|
+
if (n === "d" || n === "w") {
|
|
423
|
+
SHORTHAND[n](table);
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
if (n === void 0) return null;
|
|
427
|
+
if (ALNUM.test(n) && !(n in SIMPLE_ESCAPES2)) return null;
|
|
428
|
+
c = SIMPLE_ESCAPES2[n] ?? n;
|
|
429
|
+
}
|
|
430
|
+
if (body[i + 1] === "-" && i + 2 < body.length) {
|
|
431
|
+
let hi = body[i + 2];
|
|
432
|
+
let advance = 2;
|
|
433
|
+
if (hi === "\\") {
|
|
434
|
+
hi = body[i + 3];
|
|
435
|
+
advance = 3;
|
|
436
|
+
if (hi === void 0 || ALNUM.test(hi)) return null;
|
|
437
|
+
}
|
|
438
|
+
const from = c.charCodeAt(0);
|
|
439
|
+
const to = hi.charCodeAt(0);
|
|
440
|
+
if (from > to || to >= 128) return null;
|
|
441
|
+
for (let k = from; k <= to; k++) table[k] = 1;
|
|
442
|
+
i += advance;
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
const code = c.charCodeAt(0);
|
|
446
|
+
if (code >= 128) return null;
|
|
447
|
+
table[code] = 1;
|
|
448
|
+
}
|
|
449
|
+
return table;
|
|
450
|
+
}
|
|
451
|
+
var BODY = String.raw`((?:[^\]\\]|\\.)*)`;
|
|
452
|
+
var RE_CLASS_RUN = new RegExp(String.raw`^\[${BODY}\]([+*])$`);
|
|
453
|
+
var RE_CLASS_PAIR = new RegExp(String.raw`^\[${BODY}\]\[${BODY}\]\*$`);
|
|
454
|
+
var RE_CLASS_SEQ = new RegExp(String.raw`^\[${BODY}\]\+\[${BODY}\]\+$`);
|
|
455
|
+
var RE_SHORTHAND = /^\\([sdw])[+*]$/;
|
|
456
|
+
var RE_KEYWORD = new RegExp(String.raw`^([A-Za-z0-9_]+)\(\?!\[${BODY}\]\)$`);
|
|
457
|
+
var RE_LINE = new RegExp(String.raw`^([^\\\[\]()*+?.|^$]+)\[\^${BODY}\]\*$`);
|
|
458
|
+
var RE_QUOTED = /^(['"])\(\?:\1\1\|\[\^\1\]\)\*\1$/;
|
|
459
|
+
var RE_NUMBER = /^(-\?)?(?:(\(\?:0\|\[1-9\]\\d\*\))|\\d\+)(\(\?:\\\.\\d\+\)\?)?(\(\?:\[eE\]\[\+-\]\?\\d\+\)\?)?$/;
|
|
460
|
+
var RE_STRING_ESC = new RegExp(String.raw`^(["'])\(\?:\[\^\1\\\\(\\u0000-\\u001f|\\x00-\\x1f)?\]\|\\\\\(\?:\[${BODY}\](\|u\[0-9a-fA-F\]\{4\})?\)\)\*\1$`);
|
|
461
|
+
var RE_STRING_ANY = new RegExp(String.raw`^(["'])\(\?:\[\^\1\\\\\]\|\\\\\.\)\*\1$`);
|
|
462
|
+
function buildScanner(source, ignoreCase) {
|
|
463
|
+
let m = RE_KEYWORD.exec(source);
|
|
464
|
+
if (m) {
|
|
465
|
+
const look = parseClass(m[2]);
|
|
466
|
+
if (look === null) return null;
|
|
467
|
+
const word = m[1];
|
|
468
|
+
const lower = [];
|
|
469
|
+
const upper = [];
|
|
470
|
+
for (const ch of word) {
|
|
471
|
+
lower.push((ignoreCase ? ch.toLowerCase() : ch).charCodeAt(0));
|
|
472
|
+
upper.push((ignoreCase ? ch.toUpperCase() : ch).charCodeAt(0));
|
|
473
|
+
}
|
|
474
|
+
return make(KEYWORD, { look, lower, upper });
|
|
475
|
+
}
|
|
476
|
+
if (ignoreCase) return null;
|
|
477
|
+
m = RE_STRING_ESC.exec(source);
|
|
478
|
+
if (m) {
|
|
479
|
+
const esc = parseClass(m[3]);
|
|
480
|
+
if (esc === null) return null;
|
|
481
|
+
return make(STRING, { quote: m[1].charCodeAt(0), ctrl: m[2] !== void 0, uni: m[4] !== void 0, esc });
|
|
482
|
+
}
|
|
483
|
+
m = RE_STRING_ANY.exec(source);
|
|
484
|
+
if (m) return make(STRING, { quote: m[1].charCodeAt(0), escAny: true });
|
|
485
|
+
m = RE_NUMBER.exec(source.replace(/\[0-9\]/g, "\\d"));
|
|
486
|
+
if (m && (m[1] || m[2] || m[3] || m[4])) {
|
|
487
|
+
return make(NUMBER, { sign: m[1] !== void 0, jsonInt: m[2] !== void 0, frac: m[3] !== void 0, exp: m[4] !== void 0 });
|
|
488
|
+
}
|
|
489
|
+
m = RE_CLASS_RUN.exec(source);
|
|
490
|
+
if (m) {
|
|
491
|
+
const table = parseClass(m[1]);
|
|
492
|
+
return table && make(CLASS, { first: table, rest: table });
|
|
493
|
+
}
|
|
494
|
+
m = RE_CLASS_PAIR.exec(source);
|
|
495
|
+
if (m) {
|
|
496
|
+
const first = parseClass(m[1]);
|
|
497
|
+
const rest = parseClass(m[2]);
|
|
498
|
+
return first && rest && make(CLASS, { first, rest });
|
|
499
|
+
}
|
|
500
|
+
m = RE_CLASS_SEQ.exec(source);
|
|
501
|
+
if (m) {
|
|
502
|
+
const first = parseClass(m[1]);
|
|
503
|
+
const second = parseClass(m[2]);
|
|
504
|
+
if (first && second && first.every((v, i) => !(v && second[i]))) {
|
|
505
|
+
return make(SEQ2, { first, rest: first, second });
|
|
506
|
+
}
|
|
507
|
+
return null;
|
|
508
|
+
}
|
|
509
|
+
m = RE_SHORTHAND.exec(source);
|
|
510
|
+
if (m) {
|
|
511
|
+
const table = new Uint8Array(128);
|
|
512
|
+
SHORTHAND[m[1]](table);
|
|
513
|
+
return make(CLASS, { first: table, rest: table, wide: m[1] === "s" });
|
|
514
|
+
}
|
|
515
|
+
m = RE_QUOTED.exec(source);
|
|
516
|
+
if (m) return make(QUOTED, { quote: m[1].charCodeAt(0) });
|
|
517
|
+
m = RE_LINE.exec(source);
|
|
518
|
+
if (m) {
|
|
519
|
+
const stop = parseClass(m[2]);
|
|
520
|
+
return stop && make(LINE, { prefix: m[1], look: stop });
|
|
521
|
+
}
|
|
522
|
+
return null;
|
|
523
|
+
}
|
|
524
|
+
var skipDigits = (text, i, length) => {
|
|
525
|
+
while (i < length) {
|
|
526
|
+
const c = text.charCodeAt(i);
|
|
527
|
+
if (c < 48 || c > 57) break;
|
|
528
|
+
i++;
|
|
529
|
+
}
|
|
530
|
+
return i;
|
|
531
|
+
};
|
|
532
|
+
var isHex = (c) => c >= 48 && c <= 57 || c >= 65 && c <= 70 || c >= 97 && c <= 102;
|
|
533
|
+
function scanEnd(sc, text, offset, length) {
|
|
534
|
+
switch (sc.kind) {
|
|
535
|
+
case CLASS: {
|
|
536
|
+
const code = text.charCodeAt(offset);
|
|
537
|
+
if (code >= 128) return sc.wide ? DEFER : -1;
|
|
538
|
+
if (sc.first[code] !== 1) return -1;
|
|
539
|
+
const rest = sc.rest;
|
|
540
|
+
let j = offset + 1;
|
|
541
|
+
while (j < length) {
|
|
542
|
+
const c = text.charCodeAt(j);
|
|
543
|
+
if (c >= 128) return sc.wide ? DEFER : j;
|
|
544
|
+
if (rest[c] !== 1) break;
|
|
545
|
+
j++;
|
|
546
|
+
}
|
|
547
|
+
return j;
|
|
548
|
+
}
|
|
549
|
+
case SEQ2: {
|
|
550
|
+
const c0 = text.charCodeAt(offset);
|
|
551
|
+
if (c0 >= 128 || sc.first[c0] !== 1) return -1;
|
|
552
|
+
const { first, second } = sc;
|
|
553
|
+
let j = offset + 1;
|
|
554
|
+
while (j < length) {
|
|
555
|
+
const c = text.charCodeAt(j);
|
|
556
|
+
if (c < 128 && first[c] === 1) j++;
|
|
557
|
+
else break;
|
|
558
|
+
}
|
|
559
|
+
const mid = j;
|
|
560
|
+
while (j < length) {
|
|
561
|
+
const c = text.charCodeAt(j);
|
|
562
|
+
if (c < 128 && second[c] === 1) j++;
|
|
563
|
+
else break;
|
|
564
|
+
}
|
|
565
|
+
return j > mid ? j : -1;
|
|
566
|
+
}
|
|
567
|
+
case KEYWORD: {
|
|
568
|
+
const { lower, upper } = sc;
|
|
569
|
+
const n = lower.length;
|
|
570
|
+
if (offset + n > length) return -1;
|
|
571
|
+
for (let i = 0; i < n; i++) {
|
|
572
|
+
const c = text.charCodeAt(offset + i);
|
|
573
|
+
if (c !== lower[i] && c !== upper[i]) return -1;
|
|
574
|
+
}
|
|
575
|
+
const end = offset + n;
|
|
576
|
+
if (end < length) {
|
|
577
|
+
const c = text.charCodeAt(end);
|
|
578
|
+
if (c < 128 && sc.look[c] === 1) return -1;
|
|
579
|
+
}
|
|
580
|
+
return end;
|
|
581
|
+
}
|
|
582
|
+
case LINE: {
|
|
583
|
+
if (!text.startsWith(sc.prefix, offset)) return -1;
|
|
584
|
+
let j = offset + sc.prefix.length;
|
|
585
|
+
const stop = sc.look;
|
|
586
|
+
while (j < length) {
|
|
587
|
+
const c = text.charCodeAt(j);
|
|
588
|
+
if (c < 128 && stop[c] === 1) break;
|
|
589
|
+
j++;
|
|
590
|
+
}
|
|
591
|
+
return j;
|
|
592
|
+
}
|
|
593
|
+
case NUMBER: {
|
|
594
|
+
let j = offset;
|
|
595
|
+
if (sc.sign && text.charCodeAt(j) === 45) j++;
|
|
596
|
+
const c = text.charCodeAt(j);
|
|
597
|
+
if (sc.jsonInt) {
|
|
598
|
+
if (c === 48) j++;
|
|
599
|
+
else if (c >= 49 && c <= 57) j = skipDigits(text, j + 1, length);
|
|
600
|
+
else return -1;
|
|
601
|
+
} else {
|
|
602
|
+
if (!(c >= 48 && c <= 57)) return -1;
|
|
603
|
+
j = skipDigits(text, j + 1, length);
|
|
604
|
+
}
|
|
605
|
+
if (sc.frac && text.charCodeAt(j) === 46) {
|
|
606
|
+
const k = skipDigits(text, j + 1, length);
|
|
607
|
+
if (k > j + 1) j = k;
|
|
608
|
+
}
|
|
609
|
+
if (sc.exp) {
|
|
610
|
+
const e = text.charCodeAt(j);
|
|
611
|
+
if (e === 101 || e === 69) {
|
|
612
|
+
let k = j + 1;
|
|
613
|
+
const s = text.charCodeAt(k);
|
|
614
|
+
if (s === 43 || s === 45) k++;
|
|
615
|
+
const end = skipDigits(text, k, length);
|
|
616
|
+
if (end > k) j = end;
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
return j;
|
|
620
|
+
}
|
|
621
|
+
case STRING: {
|
|
622
|
+
const q2 = sc.quote;
|
|
623
|
+
if (text.charCodeAt(offset) !== q2) return -1;
|
|
624
|
+
let j = offset + 1;
|
|
625
|
+
for (; ; ) {
|
|
626
|
+
if (j >= length) return -1;
|
|
627
|
+
const c = text.charCodeAt(j);
|
|
628
|
+
if (c === q2) return j + 1;
|
|
629
|
+
if (c === 92) {
|
|
630
|
+
const d = text.charCodeAt(j + 1);
|
|
631
|
+
if (sc.escAny) {
|
|
632
|
+
if (!(d >= 0) || d === 10 || d === 13 || d === 8232 || d === 8233) return -1;
|
|
633
|
+
j += 2;
|
|
634
|
+
} else if (d < 128 && sc.esc[d] === 1) {
|
|
635
|
+
j += 2;
|
|
636
|
+
} else if (sc.uni && d === 117 && isHex(text.charCodeAt(j + 2)) && isHex(text.charCodeAt(j + 3)) && isHex(text.charCodeAt(j + 4)) && isHex(text.charCodeAt(j + 5))) {
|
|
637
|
+
j += 6;
|
|
638
|
+
} else {
|
|
639
|
+
return -1;
|
|
640
|
+
}
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
if (sc.ctrl && c < 32) return -1;
|
|
644
|
+
j++;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
default: {
|
|
648
|
+
const q2 = sc.quote;
|
|
649
|
+
if (text.charCodeAt(offset) !== q2) return -1;
|
|
650
|
+
const quote2 = String.fromCharCode(q2);
|
|
651
|
+
let fallback = -1;
|
|
652
|
+
let j = offset + 1;
|
|
653
|
+
for (; ; ) {
|
|
654
|
+
j = text.indexOf(quote2, j);
|
|
655
|
+
if (j === -1) return fallback;
|
|
656
|
+
if (text.charCodeAt(j + 1) !== q2) return j + 1;
|
|
657
|
+
fallback = j + 1;
|
|
658
|
+
j += 2;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
// src/state.js
|
|
665
|
+
var FAIL = /* @__PURE__ */ Symbol("grammatik.fail");
|
|
666
|
+
var memoTables = (slots, length) => {
|
|
667
|
+
if (!slots) return null;
|
|
668
|
+
const tables = new Array(slots);
|
|
669
|
+
for (let i = 0; i < slots; i++) tables[i] = { ends: new Int32Array(length + 1), vals: new Array(length + 1) };
|
|
670
|
+
return tables;
|
|
671
|
+
};
|
|
672
|
+
var createState = (tokens, grammar, options = {}) => ({
|
|
673
|
+
tokens,
|
|
674
|
+
idx: 0,
|
|
675
|
+
maxIdx: 0,
|
|
676
|
+
expected: [],
|
|
677
|
+
grammar,
|
|
678
|
+
memo: options.memo ? /* @__PURE__ */ new Map() : null,
|
|
679
|
+
rm: memoTables(options.memoSlots, tokens.length),
|
|
680
|
+
// Entering a rule is one array store rather than an object allocation, and
|
|
681
|
+
// the stack is snapshotted only on a furthest failure — by the time an error
|
|
682
|
+
// is built it would otherwise have unwound to just the start rule.
|
|
683
|
+
ruleNames: [],
|
|
684
|
+
ruleDepth: 0,
|
|
685
|
+
failureStack: null,
|
|
686
|
+
track: options.track === true,
|
|
687
|
+
steps: 0,
|
|
688
|
+
maxSteps: options.maxSteps || 0
|
|
689
|
+
});
|
|
690
|
+
function expect(state, expectation) {
|
|
691
|
+
if (state.idx > state.maxIdx) {
|
|
692
|
+
state.maxIdx = state.idx;
|
|
693
|
+
if (!state.track) return;
|
|
694
|
+
state.expected = [];
|
|
695
|
+
state.failureStack = state.ruleNames.slice(0, state.ruleDepth);
|
|
696
|
+
}
|
|
697
|
+
if (!state.track || state.idx !== state.maxIdx) return;
|
|
698
|
+
if (!state.expected.includes(expectation)) state.expected.push(expectation);
|
|
699
|
+
}
|
|
369
700
|
var LexError = class extends Error {
|
|
370
701
|
/**
|
|
371
702
|
* @param {string} message
|
|
@@ -379,6 +710,33 @@ var LexError = class extends Error {
|
|
|
379
710
|
this.col = position.col;
|
|
380
711
|
}
|
|
381
712
|
};
|
|
713
|
+
var ParseError = class extends Error {
|
|
714
|
+
/**
|
|
715
|
+
* @param {string} message
|
|
716
|
+
* @param {{ token: import('./lexer.js').Token | null, expected: string[], ruleStack: string[] }} details
|
|
717
|
+
*/
|
|
718
|
+
constructor(message, details) {
|
|
719
|
+
super(message);
|
|
720
|
+
this.name = "ParseError";
|
|
721
|
+
this.token = details.token;
|
|
722
|
+
this.expected = details.expected;
|
|
723
|
+
this.ruleStack = details.ruleStack;
|
|
724
|
+
}
|
|
725
|
+
};
|
|
726
|
+
var describeExpected = (expected) => expected.length === 0 ? "nothing" : expected.length === 1 ? expected[0] : `one of [${expected.join(", ")}]`;
|
|
727
|
+
function buildParseError(state) {
|
|
728
|
+
const token = state.tokens[state.maxIdx] || null;
|
|
729
|
+
const expected = [...state.expected].sort();
|
|
730
|
+
const ruleStack = state.failureStack || state.ruleNames.slice(0, state.ruleDepth);
|
|
731
|
+
const at = (t) => state.positions === "offset" ? `offset ${t.start}` : `line ${t.line} column ${t.col}`;
|
|
732
|
+
const found = token ? `--> ${token.image} <-- (${token.type}) at ${at(token)}` : "end of input";
|
|
733
|
+
return new ParseError(
|
|
734
|
+
`Expecting ${describeExpected(expected)} but found ${found}`,
|
|
735
|
+
{ token, expected, ruleStack }
|
|
736
|
+
);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
// src/lexer.js
|
|
382
740
|
var DEFAULT_MODE = "default";
|
|
383
741
|
function modesOf(def) {
|
|
384
742
|
if (Array.isArray(def.modes) && def.modes.length > 0) return def.modes;
|
|
@@ -388,6 +746,8 @@ function modesOf(def) {
|
|
|
388
746
|
function createLexer(defs, options = {}) {
|
|
389
747
|
const positions = options.positions === "offset" ? "offset" : "full";
|
|
390
748
|
const trackLines = positions === "full";
|
|
749
|
+
const useScanners = options.scanners !== false;
|
|
750
|
+
const useFuse = options.fuse !== false;
|
|
391
751
|
if (!Array.isArray(defs) || defs.length === 0) throw new Error("createLexer: expected a non-empty array of token definitions");
|
|
392
752
|
const tokenNames = defs.map((d) => d.name);
|
|
393
753
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -407,7 +767,7 @@ function createLexer(defs, options = {}) {
|
|
|
407
767
|
return id;
|
|
408
768
|
};
|
|
409
769
|
for (const def of defs) for (const mode of modesOf(def)) idOf(mode);
|
|
410
|
-
const compiled = defs.map((def) => {
|
|
770
|
+
const compiled = defs.map((def, id) => {
|
|
411
771
|
if (!def.name) throw new Error("createLexer: every token definition needs a name");
|
|
412
772
|
if (!def.pattern && !def.literal) throw new Error(`createLexer: token '${def.name}' needs a pattern or a literal`);
|
|
413
773
|
if (def.pattern && def.literal) throw new Error(`createLexer: token '${def.name}' has both a pattern and a literal; pick one`);
|
|
@@ -422,13 +782,24 @@ function createLexer(defs, options = {}) {
|
|
|
422
782
|
}
|
|
423
783
|
return {
|
|
424
784
|
name: def.name,
|
|
785
|
+
// The declaration index. `createParser` derives the identical mapping
|
|
786
|
+
// from `tokenNames` independently (same array, same order), so a
|
|
787
|
+
// combinator that resolves a `consume` target's id at build time agrees
|
|
788
|
+
// with what a token actually carries without the two ever comparing notes.
|
|
789
|
+
id,
|
|
425
790
|
skip: !!def.skip,
|
|
426
791
|
literal: def.literal,
|
|
427
792
|
longerAlt: def.longerAlt || null,
|
|
428
793
|
regex: def.pattern ? new RegExp(def.pattern, def.ignoreCase ? "iy" : "y") : null,
|
|
794
|
+
// A table-driven stand-in for the regex where its shape is recognised;
|
|
795
|
+
// null otherwise (and always, with `scanners: false`). It is a field on
|
|
796
|
+
// every definition so they all keep one shape. See scan.js.
|
|
797
|
+
scanner: useScanners && def.pattern ? buildScanner(def.pattern, !!def.ignoreCase) : null,
|
|
429
798
|
modes: modesOf(def),
|
|
430
799
|
pushMode: def.pushMode ? idOf(def.pushMode) : -1,
|
|
431
|
-
popMode: !!def.popMode
|
|
800
|
+
popMode: !!def.popMode,
|
|
801
|
+
// Set only on the stand-in `fuse` builds for a run of regexes; see there.
|
|
802
|
+
members: null
|
|
432
803
|
};
|
|
433
804
|
});
|
|
434
805
|
const byName = new Map(compiled.map((c) => [c.name, c]));
|
|
@@ -445,14 +816,47 @@ function createLexer(defs, options = {}) {
|
|
|
445
816
|
for (const c of compiled) {
|
|
446
817
|
c.firstChars = c.literal !== void 0 ? c.literal.length > 0 ? /* @__PURE__ */ new Set([c.literal.charCodeAt(0)]) : null : firstCharSet(c.regex.source, !!c.regex.ignoreCase);
|
|
447
818
|
}
|
|
448
|
-
const
|
|
819
|
+
const FUSE_MIN = 3;
|
|
820
|
+
const fusible = (def) => def.literal === void 0 && def.scanner === null && def.regex !== null && !/\\[1-9]|\\k</.test(def.regex.source) && // `exec` of an always-matching `|` alternative returns one slot per capture group.
|
|
821
|
+
new RegExp(def.regex.source + "|").exec("").length === 1;
|
|
822
|
+
function fuse(list) {
|
|
823
|
+
if (!useFuse) return list;
|
|
824
|
+
const out = [];
|
|
825
|
+
for (let i = 0; i < list.length; ) {
|
|
826
|
+
let j = i;
|
|
827
|
+
while (j < list.length && fusible(list[j]) && list[j].regex.flags === list[i].regex.flags) j++;
|
|
828
|
+
if (j - i < FUSE_MIN) {
|
|
829
|
+
out.push(list[i++]);
|
|
830
|
+
continue;
|
|
831
|
+
}
|
|
832
|
+
const run = list.slice(i, j);
|
|
833
|
+
out.push({
|
|
834
|
+
name: run.map((d) => d.name).join("|"),
|
|
835
|
+
id: -1,
|
|
836
|
+
skip: false,
|
|
837
|
+
literal: void 0,
|
|
838
|
+
longerAlt: null,
|
|
839
|
+
longerAltDef: void 0,
|
|
840
|
+
regex: new RegExp(run.map((d) => `(${d.regex.source})`).join("|"), run[0].regex.flags),
|
|
841
|
+
scanner: null,
|
|
842
|
+
modes: run[0].modes,
|
|
843
|
+
pushMode: -1,
|
|
844
|
+
popMode: false,
|
|
845
|
+
members: run
|
|
846
|
+
});
|
|
847
|
+
i = j;
|
|
848
|
+
}
|
|
849
|
+
return out;
|
|
850
|
+
}
|
|
851
|
+
const modes = modeNames.map((name, id) => {
|
|
449
852
|
const members = compiled.filter((c) => c.modes.includes(name));
|
|
450
853
|
const alwaysTry = members.filter((c) => c.firstChars === null);
|
|
451
|
-
const candidatesFor = (code) => members.filter((c) => c.firstChars === null || c.firstChars.has(code));
|
|
854
|
+
const candidatesFor = (code) => fuse(members.filter((c) => c.firstChars === null || c.firstChars.has(code)));
|
|
452
855
|
const ascii = new Array(128);
|
|
453
856
|
for (let code = 0; code < 128; code++) ascii[code] = candidatesFor(code);
|
|
454
857
|
return {
|
|
455
858
|
name,
|
|
859
|
+
id,
|
|
456
860
|
members,
|
|
457
861
|
ascii,
|
|
458
862
|
wide: /* @__PURE__ */ new Map(),
|
|
@@ -476,6 +880,30 @@ function createLexer(defs, options = {}) {
|
|
|
476
880
|
const multiMode = modes.length > 1 || compiled.some((c) => c.popMode || c.pushMode !== -1);
|
|
477
881
|
const namesModes = modes.length > 1;
|
|
478
882
|
const initial = modes[0];
|
|
883
|
+
let fusedMember = null;
|
|
884
|
+
let fusedText = "";
|
|
885
|
+
function fusedEnd(run, text, offset) {
|
|
886
|
+
run.regex.lastIndex = offset;
|
|
887
|
+
const match = run.regex.exec(text);
|
|
888
|
+
if (match === null) return -1;
|
|
889
|
+
if (match[0].length === 0) {
|
|
890
|
+
for (const member of run.members) {
|
|
891
|
+
member.regex.lastIndex = offset;
|
|
892
|
+
const one = member.regex.exec(text);
|
|
893
|
+
if (one !== null && one[0].length > 0) {
|
|
894
|
+
fusedMember = member;
|
|
895
|
+
fusedText = one[0];
|
|
896
|
+
return offset + fusedText.length;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
return -1;
|
|
900
|
+
}
|
|
901
|
+
let group = 1;
|
|
902
|
+
while (match[group] === void 0) group++;
|
|
903
|
+
fusedMember = run.members[group - 1];
|
|
904
|
+
fusedText = match[0];
|
|
905
|
+
return offset + fusedText.length;
|
|
906
|
+
}
|
|
479
907
|
function longerAltEnd(def, text, offset) {
|
|
480
908
|
const alt2 = def.longerAltDef;
|
|
481
909
|
if (alt2.literal !== void 0) return text.startsWith(alt2.literal, offset) ? offset + alt2.literal.length : -1;
|
|
@@ -495,19 +923,30 @@ function createLexer(defs, options = {}) {
|
|
|
495
923
|
outer: while (offset < length) {
|
|
496
924
|
const applicable = mode.dispatch ? candidates(mode, text.charCodeAt(offset)) : mode.members;
|
|
497
925
|
for (let i = 0; i < applicable.length; i++) {
|
|
498
|
-
|
|
926
|
+
let def = applicable[i];
|
|
499
927
|
let end = -1;
|
|
500
928
|
let matched = null;
|
|
501
|
-
if (def.
|
|
929
|
+
if (def.members !== null) {
|
|
930
|
+
end = fusedEnd(def, text, offset);
|
|
931
|
+
if (end === -1) continue;
|
|
932
|
+
def = fusedMember;
|
|
933
|
+
matched = fusedText;
|
|
934
|
+
} else if (def.literal !== void 0) {
|
|
502
935
|
if (text.startsWith(def.literal, offset)) end = offset + def.literal.length;
|
|
503
|
-
} else if (def.skip) {
|
|
504
|
-
def.regex.lastIndex = offset;
|
|
505
|
-
if (def.regex.test(text) && def.regex.lastIndex > offset) end = def.regex.lastIndex;
|
|
506
936
|
} else {
|
|
507
|
-
def.
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
937
|
+
end = def.scanner === null ? DEFER : scanEnd(def.scanner, text, offset, length);
|
|
938
|
+
if (end === DEFER) {
|
|
939
|
+
end = -1;
|
|
940
|
+
if (def.skip) {
|
|
941
|
+
def.regex.lastIndex = offset;
|
|
942
|
+
if (def.regex.test(text) && def.regex.lastIndex > offset) end = def.regex.lastIndex;
|
|
943
|
+
} else {
|
|
944
|
+
def.regex.lastIndex = offset;
|
|
945
|
+
const match = def.regex.exec(text);
|
|
946
|
+
if (match !== null && match[0].length > 0) matched = match[0];
|
|
947
|
+
if (matched !== null) end = offset + matched.length;
|
|
948
|
+
}
|
|
949
|
+
}
|
|
511
950
|
}
|
|
512
951
|
if (end === -1) continue;
|
|
513
952
|
let winner = def;
|
|
@@ -520,14 +959,7 @@ function createLexer(defs, options = {}) {
|
|
|
520
959
|
}
|
|
521
960
|
if (!winner.skip) {
|
|
522
961
|
const image = winner.literal !== void 0 ? winner.literal : winner === def && matched !== null ? matched : text.slice(offset, end);
|
|
523
|
-
tokens.push({
|
|
524
|
-
type: winner.name,
|
|
525
|
-
image,
|
|
526
|
-
start: offset,
|
|
527
|
-
end,
|
|
528
|
-
line: trackLines ? line : 0,
|
|
529
|
-
col: trackLines ? offset - lineStart + 1 : 0
|
|
530
|
-
});
|
|
962
|
+
tokens.push(trackLines ? { type: winner.name, image, start: offset, end, line, col: offset - lineStart + 1 } : { type: winner.name, id: winner.id, image, start: offset, end });
|
|
531
963
|
}
|
|
532
964
|
if (trackLines) {
|
|
533
965
|
for (let j = offset; j < end; j++) {
|
|
@@ -563,62 +995,238 @@ function createLexer(defs, options = {}) {
|
|
|
563
995
|
}
|
|
564
996
|
const inMode = (mode) => namesModes ? ` (in lexer mode '${mode.name}')` : "";
|
|
565
997
|
const describe = (offset, line, lineStart) => trackLines ? `line ${line} column ${offset - lineStart + 1}` : `offset ${offset}`;
|
|
566
|
-
return { tokenize, tokenNames, positions, modeNames };
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
idx: 0,
|
|
574
|
-
maxIdx: 0,
|
|
575
|
-
expected: [],
|
|
576
|
-
grammar,
|
|
577
|
-
memo: options.memo ? /* @__PURE__ */ new Map() : null,
|
|
578
|
-
// Entering a rule is one array store rather than an object allocation, and
|
|
579
|
-
// the stack is snapshotted only on a furthest failure — by the time an error
|
|
580
|
-
// is built it would otherwise have unwound to just the start rule.
|
|
581
|
-
ruleNames: [],
|
|
582
|
-
ruleDepth: 0,
|
|
583
|
-
failureStack: null,
|
|
584
|
-
track: options.track === true,
|
|
585
|
-
steps: 0,
|
|
586
|
-
maxSteps: options.maxSteps || 0
|
|
587
|
-
});
|
|
588
|
-
function expect(state, expectation) {
|
|
589
|
-
if (state.idx > state.maxIdx) {
|
|
590
|
-
state.maxIdx = state.idx;
|
|
591
|
-
if (!state.track) return;
|
|
592
|
-
state.expected = [];
|
|
593
|
-
state.failureStack = state.ruleNames.slice(0, state.ruleDepth);
|
|
998
|
+
if (!options.incremental) return { tokenize, tokenNames, positions, modeNames };
|
|
999
|
+
const recover = !!options.recover;
|
|
1000
|
+
const errorTokenName = options.errorToken || "Error";
|
|
1001
|
+
function snapshotStack(stack, depth) {
|
|
1002
|
+
const ids = new Array(depth);
|
|
1003
|
+
for (let i = 0; i < depth; i++) ids[i] = stack[i].id;
|
|
1004
|
+
return ids;
|
|
594
1005
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
/**
|
|
600
|
-
* @param {string} message
|
|
601
|
-
* @param {{ token: import('./lexer.js').Token | null, expected: string[], ruleStack: string[] }} details
|
|
602
|
-
*/
|
|
603
|
-
constructor(message, details) {
|
|
604
|
-
super(message);
|
|
605
|
-
this.name = "ParseError";
|
|
606
|
-
this.token = details.token;
|
|
607
|
-
this.expected = details.expected;
|
|
608
|
-
this.ruleStack = details.ruleStack;
|
|
1006
|
+
function sameStack(a, b) {
|
|
1007
|
+
if (a.length !== b.length) return false;
|
|
1008
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
1009
|
+
return true;
|
|
609
1010
|
}
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
1011
|
+
function scan(text, offset, mode, stack, depth, line, lineStart) {
|
|
1012
|
+
const tokens = [];
|
|
1013
|
+
const checkpoints = [];
|
|
1014
|
+
const length = text.length;
|
|
1015
|
+
outer: while (offset < length) {
|
|
1016
|
+
checkpoints.push({ modeId: mode.id, stack: snapshotStack(stack, depth), line, lineStart, offset });
|
|
1017
|
+
const applicable = mode.dispatch ? candidates(mode, text.charCodeAt(offset)) : mode.members;
|
|
1018
|
+
for (let i = 0; i < applicable.length; i++) {
|
|
1019
|
+
let def = applicable[i];
|
|
1020
|
+
let end = -1;
|
|
1021
|
+
let matched = null;
|
|
1022
|
+
if (def.members !== null) {
|
|
1023
|
+
end = fusedEnd(def, text, offset);
|
|
1024
|
+
if (end === -1) continue;
|
|
1025
|
+
def = fusedMember;
|
|
1026
|
+
matched = fusedText;
|
|
1027
|
+
} else if (def.literal !== void 0) {
|
|
1028
|
+
if (text.startsWith(def.literal, offset)) end = offset + def.literal.length;
|
|
1029
|
+
} else {
|
|
1030
|
+
end = def.scanner === null ? DEFER : scanEnd(def.scanner, text, offset, length);
|
|
1031
|
+
if (end === DEFER) {
|
|
1032
|
+
end = -1;
|
|
1033
|
+
if (def.skip) {
|
|
1034
|
+
def.regex.lastIndex = offset;
|
|
1035
|
+
if (def.regex.test(text) && def.regex.lastIndex > offset) end = def.regex.lastIndex;
|
|
1036
|
+
} else {
|
|
1037
|
+
def.regex.lastIndex = offset;
|
|
1038
|
+
const match = def.regex.exec(text);
|
|
1039
|
+
if (match !== null && match[0].length > 0) matched = match[0];
|
|
1040
|
+
if (matched !== null) end = offset + matched.length;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
if (end === -1) continue;
|
|
1045
|
+
let winner = def;
|
|
1046
|
+
if (def.longerAltDef) {
|
|
1047
|
+
const altEnd = longerAltEnd(def, text, offset);
|
|
1048
|
+
if (altEnd > end) {
|
|
1049
|
+
end = altEnd;
|
|
1050
|
+
winner = def.longerAltDef;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
if (!winner.skip) {
|
|
1054
|
+
const image = winner.literal !== void 0 ? winner.literal : winner === def && matched !== null ? matched : text.slice(offset, end);
|
|
1055
|
+
tokens.push(trackLines ? { type: winner.name, image, start: offset, end, line, col: offset - lineStart + 1 } : { type: winner.name, id: winner.id, image, start: offset, end });
|
|
1056
|
+
}
|
|
1057
|
+
if (trackLines) {
|
|
1058
|
+
for (let j = offset; j < end; j++) {
|
|
1059
|
+
if (text.charCodeAt(j) !== 10) continue;
|
|
1060
|
+
line++;
|
|
1061
|
+
lineStart = j + 1;
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
if (multiMode) {
|
|
1065
|
+
if (winner.popMode) {
|
|
1066
|
+
if (depth === 0) {
|
|
1067
|
+
if (!recover) {
|
|
1068
|
+
throw new LexError(
|
|
1069
|
+
`Token '${winner.name}' popped the initial lexer mode at ${describe(offset, line, lineStart)}`,
|
|
1070
|
+
{ offset, line: trackLines ? line : 0, col: trackLines ? offset - lineStart + 1 : 0 }
|
|
1071
|
+
);
|
|
1072
|
+
}
|
|
1073
|
+
} else {
|
|
1074
|
+
mode = stack[--depth];
|
|
1075
|
+
}
|
|
1076
|
+
} else if (winner.pushMode !== -1) {
|
|
1077
|
+
stack[depth++] = mode;
|
|
1078
|
+
mode = modes[winner.pushMode];
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
offset = end;
|
|
1082
|
+
continue outer;
|
|
1083
|
+
}
|
|
1084
|
+
if (recover) {
|
|
1085
|
+
tokens.push(trackLines ? { type: errorTokenName, image: text[offset], start: offset, end: offset + 1, line, col: offset - lineStart + 1 } : { type: errorTokenName, id: -1, image: text[offset], start: offset, end: offset + 1 });
|
|
1086
|
+
if (trackLines && text.charCodeAt(offset) === 10) {
|
|
1087
|
+
line++;
|
|
1088
|
+
lineStart = offset + 1;
|
|
1089
|
+
}
|
|
1090
|
+
offset++;
|
|
1091
|
+
continue outer;
|
|
1092
|
+
}
|
|
1093
|
+
const column = offset - lineStart + 1;
|
|
1094
|
+
throw new LexError(
|
|
1095
|
+
trackLines ? `Unexpected character '${text[offset]}' at line ${line} column ${column}${inMode(mode)}` : `Unexpected character '${text[offset]}' at offset ${offset}${inMode(mode)}`,
|
|
1096
|
+
{ offset, line: trackLines ? line : 0, col: trackLines ? column : 0 }
|
|
1097
|
+
);
|
|
1098
|
+
}
|
|
1099
|
+
return { tokens, checkpoints, mode, stack, depth, line, lineStart };
|
|
1100
|
+
}
|
|
1101
|
+
function tokenizeIncremental(text) {
|
|
1102
|
+
if (typeof text !== "string") throw new TypeError("tokenizeIncremental: expected a string");
|
|
1103
|
+
const result = scan(text, 0, initial, multiMode ? [] : [], 0, 1, 0);
|
|
1104
|
+
return { text, tokens: result.tokens, checkpoints: result.checkpoints, eof: { modeId: result.mode.id, stack: snapshotStack(result.stack, result.depth), line: result.line, lineStart: result.lineStart, offset: text.length } };
|
|
1105
|
+
}
|
|
1106
|
+
function tokenFloor(tokens, offset) {
|
|
1107
|
+
let lo = 0;
|
|
1108
|
+
let hi = tokens.length;
|
|
1109
|
+
while (lo < hi) {
|
|
1110
|
+
const mid = lo + hi >>> 1;
|
|
1111
|
+
if (tokens[mid].start < offset) lo = mid + 1;
|
|
1112
|
+
else hi = mid;
|
|
1113
|
+
}
|
|
1114
|
+
return lo;
|
|
1115
|
+
}
|
|
1116
|
+
function checkpointFloor(checkpoints, eof, offset) {
|
|
1117
|
+
let lo = 0;
|
|
1118
|
+
let hi = checkpoints.length + 1;
|
|
1119
|
+
while (lo < hi) {
|
|
1120
|
+
const mid = lo + hi >>> 1;
|
|
1121
|
+
const at = mid < checkpoints.length ? checkpoints[mid].offset : eof.offset;
|
|
1122
|
+
if (at <= offset) lo = mid + 1;
|
|
1123
|
+
else hi = mid;
|
|
1124
|
+
}
|
|
1125
|
+
return lo - 1;
|
|
1126
|
+
}
|
|
1127
|
+
function checkpointAt(checkpoints, eof, offset) {
|
|
1128
|
+
if (eof.offset === offset) return eof;
|
|
1129
|
+
let lo = 0;
|
|
1130
|
+
let hi = checkpoints.length;
|
|
1131
|
+
while (lo < hi) {
|
|
1132
|
+
const mid = lo + hi >>> 1;
|
|
1133
|
+
if (checkpoints[mid].offset < offset) lo = mid + 1;
|
|
1134
|
+
else hi = mid;
|
|
1135
|
+
}
|
|
1136
|
+
return lo < checkpoints.length && checkpoints[lo].offset === offset ? checkpoints[lo] : null;
|
|
1137
|
+
}
|
|
1138
|
+
function relex(newText, prev, edit) {
|
|
1139
|
+
if (typeof newText !== "string") throw new TypeError("relex: expected a string");
|
|
1140
|
+
const tokens = prev.tokens;
|
|
1141
|
+
const checkpoints = prev.checkpoints;
|
|
1142
|
+
const delta = edit.newEnd - edit.oldEnd;
|
|
1143
|
+
let resumeIndex = checkpointFloor(checkpoints, prev.eof, edit.start);
|
|
1144
|
+
if (resumeIndex > 0) resumeIndex--;
|
|
1145
|
+
let boundary = resumeIndex < checkpoints.length ? checkpoints[resumeIndex].offset : prev.eof.offset;
|
|
1146
|
+
let ti = tokenFloor(tokens, boundary);
|
|
1147
|
+
while (ti > 0 && tokens[ti - 1].end === boundary) {
|
|
1148
|
+
ti--;
|
|
1149
|
+
boundary = tokens[ti].start;
|
|
1150
|
+
}
|
|
1151
|
+
if (boundary < (resumeIndex < checkpoints.length ? checkpoints[resumeIndex].offset : prev.eof.offset)) {
|
|
1152
|
+
resumeIndex = checkpointFloor(checkpoints, prev.eof, boundary);
|
|
1153
|
+
}
|
|
1154
|
+
const resume = resumeIndex < checkpoints.length ? checkpoints[resumeIndex] : prev.eof;
|
|
1155
|
+
const resumeMode = modes[resume.modeId];
|
|
1156
|
+
const resumeStack = resume.stack.map((id) => modes[id]);
|
|
1157
|
+
const scanned = scan(newText, resume.offset, resumeMode, resumeStack, resumeStack.length, resume.line, resume.lineStart);
|
|
1158
|
+
const scannedEof = { modeId: scanned.mode.id, stack: snapshotStack(scanned.stack, scanned.depth), line: scanned.line, lineStart: scanned.lineStart, offset: newText.length };
|
|
1159
|
+
const VERIFY_WINDOW = 4;
|
|
1160
|
+
function convergenceVerified(oldOffset, newOffset) {
|
|
1161
|
+
const oldStart = tokenFloor(tokens, oldOffset);
|
|
1162
|
+
const freshStart = tokenFloor(scanned.tokens, newOffset);
|
|
1163
|
+
for (let k = 0; k < VERIFY_WINDOW; k++) {
|
|
1164
|
+
const oldTok = tokens[oldStart + k];
|
|
1165
|
+
const freshTok = scanned.tokens[freshStart + k];
|
|
1166
|
+
if (oldTok === void 0 && freshTok === void 0) return true;
|
|
1167
|
+
if (oldTok === void 0 || freshTok === void 0) return false;
|
|
1168
|
+
if (oldTok.type !== freshTok.type || oldTok.image !== freshTok.image) return false;
|
|
1169
|
+
}
|
|
1170
|
+
return true;
|
|
1171
|
+
}
|
|
1172
|
+
let convergeNewOffset = -1;
|
|
1173
|
+
let convergeOldOffset = -1;
|
|
1174
|
+
let convergeOldCheckpoint = null;
|
|
1175
|
+
let freshCheckpointCount = 0;
|
|
1176
|
+
for (let s = 0; s <= scanned.checkpoints.length; s++) {
|
|
1177
|
+
const cp = s < scanned.checkpoints.length ? scanned.checkpoints[s] : scannedEof;
|
|
1178
|
+
if (cp.offset < edit.newEnd) continue;
|
|
1179
|
+
const oldOffset = cp.offset - delta;
|
|
1180
|
+
const oldCp = checkpointAt(checkpoints, prev.eof, oldOffset);
|
|
1181
|
+
if (oldCp && oldCp.modeId === cp.modeId && sameStack(oldCp.stack, cp.stack) && convergenceVerified(oldOffset, cp.offset)) {
|
|
1182
|
+
convergeNewOffset = cp.offset;
|
|
1183
|
+
convergeOldOffset = oldOffset;
|
|
1184
|
+
convergeOldCheckpoint = oldCp;
|
|
1185
|
+
freshCheckpointCount = s;
|
|
1186
|
+
break;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
const tokenFrom = tokenFloor(tokens, resume.offset);
|
|
1190
|
+
const checkpointFrom = resumeIndex;
|
|
1191
|
+
if (convergeNewOffset === -1) {
|
|
1192
|
+
tokens.splice(tokenFrom, tokens.length - tokenFrom, ...scanned.tokens);
|
|
1193
|
+
checkpoints.splice(checkpointFrom, checkpoints.length - checkpointFrom, ...scanned.checkpoints);
|
|
1194
|
+
prev.text = newText;
|
|
1195
|
+
prev.eof = scannedEof;
|
|
1196
|
+
return prev;
|
|
1197
|
+
}
|
|
1198
|
+
const freshTokenCount = tokenFloor(scanned.tokens, convergeNewOffset);
|
|
1199
|
+
const lineDelta = trackLines ? scanned.checkpoints.length > freshCheckpointCount ? scanned.checkpoints[freshCheckpointCount].line - convergeOldCheckpoint.line : scannedEof.line - convergeOldCheckpoint.line : 0;
|
|
1200
|
+
const boundaryOldLineStart = convergeOldCheckpoint.lineStart;
|
|
1201
|
+
const boundaryNewLineStart = (freshCheckpointCount < scanned.checkpoints.length ? scanned.checkpoints[freshCheckpointCount] : scannedEof).lineStart;
|
|
1202
|
+
const shiftedLineStart = (oldLineStart) => oldLineStart === boundaryOldLineStart ? boundaryNewLineStart : oldLineStart + delta;
|
|
1203
|
+
const tailTokenFrom = tokenFloor(tokens, convergeOldOffset);
|
|
1204
|
+
for (let i = tailTokenFrom; i < tokens.length; i++) {
|
|
1205
|
+
const t = tokens[i];
|
|
1206
|
+
if (trackLines) {
|
|
1207
|
+
const oldLineStart = t.start - (t.col - 1);
|
|
1208
|
+
t.col = t.start + delta - shiftedLineStart(oldLineStart) + 1;
|
|
1209
|
+
t.line += lineDelta;
|
|
1210
|
+
}
|
|
1211
|
+
t.start += delta;
|
|
1212
|
+
t.end += delta;
|
|
1213
|
+
}
|
|
1214
|
+
tokens.splice(tokenFrom, tailTokenFrom - tokenFrom, ...scanned.tokens.slice(0, freshTokenCount));
|
|
1215
|
+
const tailCheckpointFrom = checkpointFloor(checkpoints, prev.eof, convergeOldOffset - 1) + 1;
|
|
1216
|
+
for (let i = tailCheckpointFrom; i < checkpoints.length; i++) {
|
|
1217
|
+
const cp = checkpoints[i];
|
|
1218
|
+
cp.lineStart = shiftedLineStart(cp.lineStart);
|
|
1219
|
+
cp.line += lineDelta;
|
|
1220
|
+
cp.offset += delta;
|
|
1221
|
+
}
|
|
1222
|
+
checkpoints.splice(checkpointFrom, tailCheckpointFrom - checkpointFrom, ...scanned.checkpoints.slice(0, freshCheckpointCount));
|
|
1223
|
+
prev.text = newText;
|
|
1224
|
+
prev.eof.lineStart = shiftedLineStart(prev.eof.lineStart);
|
|
1225
|
+
prev.eof.line += lineDelta;
|
|
1226
|
+
prev.eof.offset += delta;
|
|
1227
|
+
return prev;
|
|
1228
|
+
}
|
|
1229
|
+
return { tokenize, tokenizeIncremental, relex, tokenNames, positions, modeNames };
|
|
622
1230
|
}
|
|
623
1231
|
|
|
624
1232
|
// src/analyze.js
|
|
@@ -651,6 +1259,7 @@ var PARSER_OPS = /* @__PURE__ */ new Set([
|
|
|
651
1259
|
"many",
|
|
652
1260
|
"many1",
|
|
653
1261
|
"option",
|
|
1262
|
+
"flag",
|
|
654
1263
|
"manySep",
|
|
655
1264
|
"many1Sep",
|
|
656
1265
|
"infixLeft",
|
|
@@ -658,6 +1267,8 @@ var PARSER_OPS = /* @__PURE__ */ new Set([
|
|
|
658
1267
|
"postfix",
|
|
659
1268
|
"prefix",
|
|
660
1269
|
"between",
|
|
1270
|
+
"pick",
|
|
1271
|
+
"yields",
|
|
661
1272
|
"as",
|
|
662
1273
|
"oneOf",
|
|
663
1274
|
"eof",
|
|
@@ -711,7 +1322,8 @@ function firstOf(node, ruleFirsts) {
|
|
|
711
1322
|
return result;
|
|
712
1323
|
}
|
|
713
1324
|
case "many":
|
|
714
|
-
case "option":
|
|
1325
|
+
case "option":
|
|
1326
|
+
case "flag": {
|
|
715
1327
|
const inner = firstOf(arg, ruleFirsts);
|
|
716
1328
|
return { tokens: inner.tokens, nullable: true, unknown: inner.unknown };
|
|
717
1329
|
}
|
|
@@ -734,6 +1346,9 @@ function firstOf(node, ruleFirsts) {
|
|
|
734
1346
|
return firstOf(Array.isArray(arg) ? arg[0] : arg && arg.parser, ruleFirsts);
|
|
735
1347
|
case "between":
|
|
736
1348
|
return firstOf({ seq: [arg && arg.open, arg && arg.body, arg && arg.close] }, ruleFirsts);
|
|
1349
|
+
case "pick":
|
|
1350
|
+
case "yields":
|
|
1351
|
+
return firstOf({ seq: arg && Array.isArray(arg.parsers) ? arg.parsers : [] }, ruleFirsts);
|
|
737
1352
|
case "prefix": {
|
|
738
1353
|
const ops = firstOf(arg && arg.operator, ruleFirsts);
|
|
739
1354
|
const inner = firstOf(arg && arg.operand, ruleFirsts);
|
|
@@ -797,12 +1412,94 @@ function recognizeSeqIdiom(children) {
|
|
|
797
1412
|
if (rewritten !== BAIL) return { as: [parser, rewritten] };
|
|
798
1413
|
}
|
|
799
1414
|
}
|
|
1415
|
+
if (children.length >= 2) {
|
|
1416
|
+
const last = children[children.length - 1];
|
|
1417
|
+
const head = children.slice(0, -1);
|
|
1418
|
+
if (opOf(last) === "action" && isConstant(last.action) && head.every(isPlainParser) && !head.some(usesOuterScope)) {
|
|
1419
|
+
return { yields: { parsers: head, value: last.action } };
|
|
1420
|
+
}
|
|
1421
|
+
if (opOf(last) === "action" && opOf(last.action) === "val" && typeof last.action.val === "string") {
|
|
1422
|
+
const name = last.action.val;
|
|
1423
|
+
const labelled = head.map((child, i) => opOf(child) === "label" ? i : -1).filter((i) => i !== -1);
|
|
1424
|
+
if (labelled.length === 1 && head[labelled[0]].label[0] === name && head.every((child, i) => i === labelled[0] || isPlainParser(child)) && !head.some(usesOuterScope)) {
|
|
1425
|
+
return { pick: { parsers: head.map((child, i) => i === labelled[0] ? child.label[1] : child), index: labelled[0] } };
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
}
|
|
800
1429
|
return null;
|
|
801
1430
|
}
|
|
1431
|
+
var isConstant = (value) => value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
1432
|
+
function usesOuterScope(logic) {
|
|
1433
|
+
if (Array.isArray(logic)) return logic.some(usesOuterScope);
|
|
1434
|
+
if (!logic || typeof logic !== "object") return false;
|
|
1435
|
+
for (const key of Object.keys(logic)) {
|
|
1436
|
+
const arg = logic[key];
|
|
1437
|
+
if (key === "val" && Array.isArray(arg) && Array.isArray(arg[0])) return true;
|
|
1438
|
+
if (key === "var") {
|
|
1439
|
+
if (typeof arg === "string" && arg.includes("../")) return true;
|
|
1440
|
+
if (Array.isArray(arg) && typeof arg[0] === "string" && arg[0].includes("../")) return true;
|
|
1441
|
+
}
|
|
1442
|
+
if (usesOuterScope(arg)) return true;
|
|
1443
|
+
}
|
|
1444
|
+
return false;
|
|
1445
|
+
}
|
|
802
1446
|
function isPlainParser(node) {
|
|
803
1447
|
const op = opOf(node);
|
|
804
1448
|
return op !== null && op !== "label" && op !== "action";
|
|
805
1449
|
}
|
|
1450
|
+
function deepEqual(a, b) {
|
|
1451
|
+
if (a === b) return true;
|
|
1452
|
+
if (Array.isArray(a) || Array.isArray(b)) {
|
|
1453
|
+
if (!Array.isArray(a) || !Array.isArray(b) || a.length !== b.length) return false;
|
|
1454
|
+
for (let i = 0; i < a.length; i++) if (!deepEqual(a[i], b[i])) return false;
|
|
1455
|
+
return true;
|
|
1456
|
+
}
|
|
1457
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
|
|
1458
|
+
const keysA = Object.keys(a);
|
|
1459
|
+
const keysB = Object.keys(b);
|
|
1460
|
+
if (keysA.length !== keysB.length) return false;
|
|
1461
|
+
for (const key of keysA) {
|
|
1462
|
+
if (!Object.prototype.hasOwnProperty.call(b, key) || !deepEqual(a[key], b[key])) return false;
|
|
1463
|
+
}
|
|
1464
|
+
return true;
|
|
1465
|
+
}
|
|
1466
|
+
function recognizeAltPrefixIdiom(branches) {
|
|
1467
|
+
if (!Array.isArray(branches) || branches.length < 2) return null;
|
|
1468
|
+
if (!branches.every((b) => opOf(b) === "seq" && Array.isArray(b.seq))) return null;
|
|
1469
|
+
const seqs = branches.map((b) => b.seq);
|
|
1470
|
+
const minLen = Math.min(...seqs.map((s) => s.length));
|
|
1471
|
+
let n = 0;
|
|
1472
|
+
while (n < minLen) {
|
|
1473
|
+
const candidate = seqs[0][n];
|
|
1474
|
+
const op = opOf(candidate);
|
|
1475
|
+
if (op === "label" || op === "action") break;
|
|
1476
|
+
if (!seqs.every((s) => deepEqual(s[n], candidate))) break;
|
|
1477
|
+
n++;
|
|
1478
|
+
}
|
|
1479
|
+
if (n === 0) return null;
|
|
1480
|
+
if (!seqs.every((s) => s.slice(n).some((child) => opOf(child) === "action"))) return null;
|
|
1481
|
+
const prefix = seqs[0].slice(0, n);
|
|
1482
|
+
const remainders = seqs.map((s) => ({ seq: s.slice(n) }));
|
|
1483
|
+
return {
|
|
1484
|
+
seq: [
|
|
1485
|
+
...prefix,
|
|
1486
|
+
{ label: ["__grammatik_rest", { alt: remainders }] },
|
|
1487
|
+
{ action: { val: "__grammatik_rest" } }
|
|
1488
|
+
]
|
|
1489
|
+
};
|
|
1490
|
+
}
|
|
1491
|
+
function recognizeAltConsumeIdiom(branches) {
|
|
1492
|
+
if (!Array.isArray(branches) || branches.length < 2) return null;
|
|
1493
|
+
const names = [];
|
|
1494
|
+
for (const branch of branches) {
|
|
1495
|
+
if (opOf(branch) !== "consume") return null;
|
|
1496
|
+
const arg = branch.consume;
|
|
1497
|
+
const name = typeof arg === "string" ? arg : Array.isArray(arg) && arg.length === 1 ? arg[0] : null;
|
|
1498
|
+
if (typeof name !== "string") return null;
|
|
1499
|
+
names.push(name);
|
|
1500
|
+
}
|
|
1501
|
+
return { oneOf: names };
|
|
1502
|
+
}
|
|
806
1503
|
var BAIL = /* @__PURE__ */ Symbol("grammatik.bail");
|
|
807
1504
|
function rebaseOnLabel(logic, name) {
|
|
808
1505
|
if (Array.isArray(logic)) {
|
|
@@ -869,6 +1566,7 @@ function leftmostCalls(node, ruleFirsts, out) {
|
|
|
869
1566
|
case "many":
|
|
870
1567
|
case "many1":
|
|
871
1568
|
case "option":
|
|
1569
|
+
case "flag":
|
|
872
1570
|
case "lookahead":
|
|
873
1571
|
case "negLookahead":
|
|
874
1572
|
leftmostCalls(arg, ruleFirsts, out);
|
|
@@ -879,6 +1577,10 @@ function leftmostCalls(node, ruleFirsts, out) {
|
|
|
879
1577
|
case "between":
|
|
880
1578
|
if (arg) leftmostCalls({ seq: [arg.open, arg.body, arg.close] }, ruleFirsts, out);
|
|
881
1579
|
break;
|
|
1580
|
+
case "pick":
|
|
1581
|
+
case "yields":
|
|
1582
|
+
if (arg && Array.isArray(arg.parsers)) leftmostCalls({ seq: arg.parsers }, ruleFirsts, out);
|
|
1583
|
+
break;
|
|
882
1584
|
case "prefix":
|
|
883
1585
|
if (arg) {
|
|
884
1586
|
leftmostCalls(arg.operator, ruleFirsts, out);
|
|
@@ -945,6 +1647,7 @@ function validateNode(node, grammar, tokenNames, errors, path) {
|
|
|
945
1647
|
case "many":
|
|
946
1648
|
case "many1":
|
|
947
1649
|
case "option":
|
|
1650
|
+
case "flag":
|
|
948
1651
|
case "text":
|
|
949
1652
|
case "lookahead":
|
|
950
1653
|
case "negLookahead":
|
|
@@ -966,6 +1669,26 @@ function validateNode(node, grammar, tokenNames, errors, path) {
|
|
|
966
1669
|
}
|
|
967
1670
|
break;
|
|
968
1671
|
}
|
|
1672
|
+
case "pick": {
|
|
1673
|
+
if (!arg || !Array.isArray(arg.parsers) || arg.parsers.length === 0) {
|
|
1674
|
+
errors.push(`${path}: 'pick' expects { parsers: [parser, ...], index }`);
|
|
1675
|
+
} else if (!Number.isInteger(arg.index) || arg.index < 0 || arg.index >= arg.parsers.length) {
|
|
1676
|
+
errors.push(`${path}: 'pick' index ${JSON.stringify(arg.index)} is out of range for ${arg.parsers.length} parser(s)`);
|
|
1677
|
+
} else {
|
|
1678
|
+
arg.parsers.forEach((c, i) => validateNode(c, grammar, tokenNames, errors, `${path}.pick[${i}]`));
|
|
1679
|
+
}
|
|
1680
|
+
break;
|
|
1681
|
+
}
|
|
1682
|
+
case "yields": {
|
|
1683
|
+
if (!arg || !Array.isArray(arg.parsers) || arg.parsers.length === 0 || !("value" in arg)) {
|
|
1684
|
+
errors.push(`${path}: 'yields' expects { parsers: [parser, ...], value }`);
|
|
1685
|
+
} else if (!isConstant(arg.value)) {
|
|
1686
|
+
errors.push(`${path}: 'yields' value must be a string, number, boolean or null, got ${JSON.stringify(arg.value)}`);
|
|
1687
|
+
} else {
|
|
1688
|
+
arg.parsers.forEach((c, i) => validateNode(c, grammar, tokenNames, errors, `${path}.yields[${i}]`));
|
|
1689
|
+
}
|
|
1690
|
+
break;
|
|
1691
|
+
}
|
|
969
1692
|
case "prefix": {
|
|
970
1693
|
if (!arg || typeof arg !== "object" || !("operator" in arg) || !("operand" in arg) || !("combine" in arg)) {
|
|
971
1694
|
errors.push(`${path}: 'prefix' expects { operator, operand, combine }`);
|
|
@@ -1052,16 +1775,38 @@ function analyze(grammar, options = {}) {
|
|
|
1052
1775
|
return { firsts, errors, leftRecursive };
|
|
1053
1776
|
}
|
|
1054
1777
|
function buildDispatch(branches, firsts) {
|
|
1055
|
-
const
|
|
1056
|
-
for (const
|
|
1057
|
-
const first = firstOf(branch, firsts);
|
|
1778
|
+
const perBranch = branches.map((branch) => firstOf(branch, firsts));
|
|
1779
|
+
for (const first of perBranch) {
|
|
1058
1780
|
if (first.nullable || first.unknown || first.tokens.size === 0) return null;
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1781
|
+
}
|
|
1782
|
+
const byToken = /* @__PURE__ */ new Map();
|
|
1783
|
+
for (let i = 0; i < branches.length; i++) {
|
|
1784
|
+
for (const token of perBranch[i].tokens) {
|
|
1785
|
+
let list = byToken.get(token);
|
|
1786
|
+
if (list === void 0) {
|
|
1787
|
+
list = [];
|
|
1788
|
+
byToken.set(token, list);
|
|
1789
|
+
}
|
|
1790
|
+
list.push(branches[i]);
|
|
1791
|
+
}
|
|
1792
|
+
}
|
|
1793
|
+
if (byToken.size === 0) return null;
|
|
1794
|
+
const canonical = /* @__PURE__ */ new Map();
|
|
1795
|
+
const table = /* @__PURE__ */ new Map();
|
|
1796
|
+
for (const [token, list] of byToken) {
|
|
1797
|
+
if (list.length === 1) {
|
|
1798
|
+
table.set(token, list[0]);
|
|
1799
|
+
continue;
|
|
1800
|
+
}
|
|
1801
|
+
const key = list.map((branch) => branches.indexOf(branch)).join(",");
|
|
1802
|
+
let group = canonical.get(key);
|
|
1803
|
+
if (group === void 0) {
|
|
1804
|
+
group = list;
|
|
1805
|
+
canonical.set(key, group);
|
|
1062
1806
|
}
|
|
1807
|
+
table.set(token, group);
|
|
1063
1808
|
}
|
|
1064
|
-
return table
|
|
1809
|
+
return table;
|
|
1065
1810
|
}
|
|
1066
1811
|
|
|
1067
1812
|
// src/plan.js
|
|
@@ -1070,20 +1815,6 @@ function declareSync(fn) {
|
|
|
1070
1815
|
fn[SYNC] = true;
|
|
1071
1816
|
return fn;
|
|
1072
1817
|
}
|
|
1073
|
-
function usesOuterScope(logic) {
|
|
1074
|
-
if (Array.isArray(logic)) return logic.some(usesOuterScope);
|
|
1075
|
-
if (!logic || typeof logic !== "object") return false;
|
|
1076
|
-
for (const key of Object.keys(logic)) {
|
|
1077
|
-
const arg = logic[key];
|
|
1078
|
-
if (key === "val" && Array.isArray(arg) && Array.isArray(arg[0])) return true;
|
|
1079
|
-
if (key === "var") {
|
|
1080
|
-
if (typeof arg === "string" && arg.includes("../")) return true;
|
|
1081
|
-
if (Array.isArray(arg) && typeof arg[0] === "string" && arg[0].includes("../")) return true;
|
|
1082
|
-
}
|
|
1083
|
-
if (usesOuterScope(arg)) return true;
|
|
1084
|
-
}
|
|
1085
|
-
return false;
|
|
1086
|
-
}
|
|
1087
1818
|
function mayReturnContext(logic) {
|
|
1088
1819
|
if (Array.isArray(logic)) return logic.some(mayReturnContext);
|
|
1089
1820
|
if (!logic || typeof logic !== "object") return false;
|
|
@@ -1096,7 +1827,7 @@ function mayReturnContext(logic) {
|
|
|
1096
1827
|
return false;
|
|
1097
1828
|
}
|
|
1098
1829
|
function createPlanner(config) {
|
|
1099
|
-
const { engine, rules, firsts, memo, maxSteps, unsafeEval = true } = config;
|
|
1830
|
+
const { engine, rules, firsts, memo, maxSteps, unsafeEval = true, tokenIds = null, memoSlots = /* @__PURE__ */ new Map(), idioms = true } = config;
|
|
1100
1831
|
const slots = /* @__PURE__ */ new Map();
|
|
1101
1832
|
const planCache = /* @__PURE__ */ new WeakMap();
|
|
1102
1833
|
const opCaches = /* @__PURE__ */ new Map();
|
|
@@ -1179,9 +1910,10 @@ function createPlanner(config) {
|
|
|
1179
1910
|
}
|
|
1180
1911
|
const buildConsume = (arg) => {
|
|
1181
1912
|
const name = typeof arg === "string" ? arg : arg[0];
|
|
1913
|
+
const id = tokenIds ? tokenIds.get(name) : void 0;
|
|
1182
1914
|
return (state) => {
|
|
1183
1915
|
const token = state.tokens[state.idx];
|
|
1184
|
-
if (token !== void 0 && token.type === name) {
|
|
1916
|
+
if (token !== void 0 && (token.id === id || token.id === void 0 && token.type === name)) {
|
|
1185
1917
|
state.idx++;
|
|
1186
1918
|
return token;
|
|
1187
1919
|
}
|
|
@@ -1242,6 +1974,17 @@ function createPlanner(config) {
|
|
|
1242
1974
|
return value;
|
|
1243
1975
|
};
|
|
1244
1976
|
};
|
|
1977
|
+
const buildFlag = (arg) => {
|
|
1978
|
+
const child = plan(arg);
|
|
1979
|
+
return (state) => {
|
|
1980
|
+
const start = state.idx;
|
|
1981
|
+
if (child(state) === FAIL) {
|
|
1982
|
+
state.idx = start;
|
|
1983
|
+
return false;
|
|
1984
|
+
}
|
|
1985
|
+
return true;
|
|
1986
|
+
};
|
|
1987
|
+
};
|
|
1245
1988
|
const buildInfixLeft = (arg) => {
|
|
1246
1989
|
const operand = plan(arg.operand);
|
|
1247
1990
|
const operator = arg.operator === void 0 ? null : plan(arg.operator);
|
|
@@ -1277,10 +2020,11 @@ function createPlanner(config) {
|
|
|
1277
2020
|
const buildOneOf = (arg) => {
|
|
1278
2021
|
const names = Array.isArray(arg) ? arg : [arg];
|
|
1279
2022
|
const set = new Set(names);
|
|
2023
|
+
const idSet = tokenIds ? new Set(names.map((n) => tokenIds.get(n))) : null;
|
|
1280
2024
|
const count = names.length;
|
|
1281
2025
|
return (state) => {
|
|
1282
2026
|
const token = state.tokens[state.idx];
|
|
1283
|
-
if (token !== void 0 && set.has(token.type)) {
|
|
2027
|
+
if (token !== void 0 && (idSet ? token.id !== void 0 ? idSet.has(token.id) : set.has(token.type) : set.has(token.type))) {
|
|
1284
2028
|
state.idx++;
|
|
1285
2029
|
return token;
|
|
1286
2030
|
}
|
|
@@ -1366,6 +2110,26 @@ function createPlanner(config) {
|
|
|
1366
2110
|
return value;
|
|
1367
2111
|
};
|
|
1368
2112
|
};
|
|
2113
|
+
const buildSequence = (arg, pickIndex, constant) => {
|
|
2114
|
+
const fns = arg.parsers.map(plan);
|
|
2115
|
+
const count = fns.length;
|
|
2116
|
+
return (state) => {
|
|
2117
|
+
tick(state);
|
|
2118
|
+
const start = state.idx;
|
|
2119
|
+
let picked;
|
|
2120
|
+
for (let i = 0; i < count; i++) {
|
|
2121
|
+
const value = fns[i](state);
|
|
2122
|
+
if (value === FAIL) {
|
|
2123
|
+
state.idx = start;
|
|
2124
|
+
return FAIL;
|
|
2125
|
+
}
|
|
2126
|
+
if (i === pickIndex) picked = value;
|
|
2127
|
+
}
|
|
2128
|
+
return pickIndex === -1 ? constant : picked;
|
|
2129
|
+
};
|
|
2130
|
+
};
|
|
2131
|
+
const buildPick = (arg) => buildSequence(arg, arg.index, void 0);
|
|
2132
|
+
const buildYields = (arg) => buildSequence(arg, -1, arg.value);
|
|
1369
2133
|
const buildPrefix = (arg) => {
|
|
1370
2134
|
const operator = plan(arg.operator);
|
|
1371
2135
|
const operand = plan(arg.operand);
|
|
@@ -1449,6 +2213,31 @@ function createPlanner(config) {
|
|
|
1449
2213
|
return value;
|
|
1450
2214
|
};
|
|
1451
2215
|
}
|
|
2216
|
+
const memoSlot = memoSlots.get(name);
|
|
2217
|
+
if (memoSlot !== void 0) {
|
|
2218
|
+
return (state) => {
|
|
2219
|
+
tick(state);
|
|
2220
|
+
const table = state.rm[memoSlot];
|
|
2221
|
+
const at = state.idx;
|
|
2222
|
+
const end = table.ends[at];
|
|
2223
|
+
if (end !== 0) {
|
|
2224
|
+
state.idx = end - 1;
|
|
2225
|
+
return table.vals[at];
|
|
2226
|
+
}
|
|
2227
|
+
let depth = 0;
|
|
2228
|
+
if (state.track) {
|
|
2229
|
+
depth = state.ruleDepth;
|
|
2230
|
+
state.ruleNames[depth] = name;
|
|
2231
|
+
state.ruleDepth = depth + 1;
|
|
2232
|
+
}
|
|
2233
|
+
const value = slot.fn(state);
|
|
2234
|
+
if (state.track) state.ruleDepth = depth;
|
|
2235
|
+
if (value === FAIL) state.idx = at;
|
|
2236
|
+
table.ends[at] = state.idx + 1;
|
|
2237
|
+
table.vals[at] = value;
|
|
2238
|
+
return value;
|
|
2239
|
+
};
|
|
2240
|
+
}
|
|
1452
2241
|
return (state) => {
|
|
1453
2242
|
tick(state);
|
|
1454
2243
|
if (!state.track) return slot.fn(state);
|
|
@@ -1527,6 +2316,9 @@ function createPlanner(config) {
|
|
|
1527
2316
|
infixRight: buildInfixRight,
|
|
1528
2317
|
as: buildAs,
|
|
1529
2318
|
between: buildBetween,
|
|
2319
|
+
flag: buildFlag,
|
|
2320
|
+
pick: buildPick,
|
|
2321
|
+
yields: buildYields,
|
|
1530
2322
|
prefix: buildPrefix,
|
|
1531
2323
|
postfix: buildPostfix,
|
|
1532
2324
|
manySep: (arg) => planSeparated(arg, false),
|
|
@@ -1546,7 +2338,7 @@ function createPlanner(config) {
|
|
|
1546
2338
|
}
|
|
1547
2339
|
function planSeq(children) {
|
|
1548
2340
|
if (!Array.isArray(children)) throw new Error("'seq' expects an array of parsers");
|
|
1549
|
-
const idiom = recognizeSeqIdiom(children);
|
|
2341
|
+
const idiom = idioms ? recognizeSeqIdiom(children) : null;
|
|
1550
2342
|
if (idiom !== null) return planUncached(idiom);
|
|
1551
2343
|
const PARSER = 0;
|
|
1552
2344
|
const LABELLED = 1;
|
|
@@ -1556,6 +2348,7 @@ function createPlanner(config) {
|
|
|
1556
2348
|
const kinds = [];
|
|
1557
2349
|
const names = [];
|
|
1558
2350
|
const tokens = [];
|
|
2351
|
+
const tokenIdList = [];
|
|
1559
2352
|
const fns = [];
|
|
1560
2353
|
const actionLogic = [];
|
|
1561
2354
|
let hasAction = false;
|
|
@@ -1576,11 +2369,13 @@ function createPlanner(config) {
|
|
|
1576
2369
|
kinds.push(LABELLED_TERMINAL);
|
|
1577
2370
|
names.push(name);
|
|
1578
2371
|
tokens.push(terminal2);
|
|
2372
|
+
tokenIdList.push(tokenIds ? tokenIds.get(terminal2) : void 0);
|
|
1579
2373
|
fns.push(null);
|
|
1580
2374
|
} else {
|
|
1581
2375
|
kinds.push(LABELLED);
|
|
1582
2376
|
names.push(name);
|
|
1583
2377
|
tokens.push(null);
|
|
2378
|
+
tokenIdList.push(void 0);
|
|
1584
2379
|
fns.push(plan(inner));
|
|
1585
2380
|
}
|
|
1586
2381
|
continue;
|
|
@@ -1589,6 +2384,7 @@ function createPlanner(config) {
|
|
|
1589
2384
|
kinds.push(ACTION);
|
|
1590
2385
|
names.push(null);
|
|
1591
2386
|
tokens.push(null);
|
|
2387
|
+
tokenIdList.push(void 0);
|
|
1592
2388
|
fns.push(planAction(child.action));
|
|
1593
2389
|
actionLogic.push(child.action);
|
|
1594
2390
|
hasAction = true;
|
|
@@ -1599,11 +2395,13 @@ function createPlanner(config) {
|
|
|
1599
2395
|
kinds.push(TERMINAL);
|
|
1600
2396
|
names.push(null);
|
|
1601
2397
|
tokens.push(terminal);
|
|
2398
|
+
tokenIdList.push(tokenIds ? tokenIds.get(terminal) : void 0);
|
|
1602
2399
|
fns.push(null);
|
|
1603
2400
|
} else {
|
|
1604
2401
|
kinds.push(PARSER);
|
|
1605
2402
|
names.push(null);
|
|
1606
2403
|
tokens.push(null);
|
|
2404
|
+
tokenIdList.push(void 0);
|
|
1607
2405
|
fns.push(plan(child));
|
|
1608
2406
|
}
|
|
1609
2407
|
}
|
|
@@ -1624,7 +2422,7 @@ function createPlanner(config) {
|
|
|
1624
2422
|
let value;
|
|
1625
2423
|
if (kind === TERMINAL || kind === LABELLED_TERMINAL) {
|
|
1626
2424
|
const token = state.tokens[state.idx];
|
|
1627
|
-
if (token !== void 0 && token.type === tokens[i]) {
|
|
2425
|
+
if (token !== void 0 && (token.id === tokenIdList[i] || token.id === void 0 && token.type === tokens[i])) {
|
|
1628
2426
|
state.idx++;
|
|
1629
2427
|
value = token;
|
|
1630
2428
|
} else {
|
|
@@ -1654,18 +2452,46 @@ function createPlanner(config) {
|
|
|
1654
2452
|
}
|
|
1655
2453
|
function planAlt(branches) {
|
|
1656
2454
|
if (!Array.isArray(branches)) throw new Error("'alt' expects an array of branches");
|
|
2455
|
+
const oneOf = idioms ? recognizeAltConsumeIdiom(branches) : null;
|
|
2456
|
+
if (oneOf !== null) return planUncached(oneOf);
|
|
2457
|
+
const factored = idioms ? recognizeAltPrefixIdiom(branches) : null;
|
|
2458
|
+
if (factored !== null) return planUncached(factored);
|
|
1657
2459
|
const planned = branches.map(plan);
|
|
1658
2460
|
const table = firsts ? buildDispatch(branches, firsts) : null;
|
|
1659
2461
|
if (table !== null) {
|
|
1660
2462
|
const byToken = /* @__PURE__ */ Object.create(null);
|
|
1661
|
-
|
|
2463
|
+
const byId = tokenIds ? [] : null;
|
|
2464
|
+
const groupClosures = /* @__PURE__ */ new Map();
|
|
2465
|
+
for (const [token, branch] of table) {
|
|
2466
|
+
let closure = groupClosures.get(branch);
|
|
2467
|
+
if (closure === void 0) {
|
|
2468
|
+
closure = Array.isArray(branch) ? (() => {
|
|
2469
|
+
const group = branch.map((b) => planned[branches.indexOf(b)]);
|
|
2470
|
+
return (state) => {
|
|
2471
|
+
const start = state.idx;
|
|
2472
|
+
for (let i = 0; i < group.length; i++) {
|
|
2473
|
+
const value = group[i](state);
|
|
2474
|
+
if (value !== FAIL) return value;
|
|
2475
|
+
state.idx = start;
|
|
2476
|
+
}
|
|
2477
|
+
return FAIL;
|
|
2478
|
+
};
|
|
2479
|
+
})() : planned[branches.indexOf(branch)];
|
|
2480
|
+
groupClosures.set(branch, closure);
|
|
2481
|
+
}
|
|
2482
|
+
byToken[token] = closure;
|
|
2483
|
+
if (byId) {
|
|
2484
|
+
const id = tokenIds.get(token);
|
|
2485
|
+
if (id !== void 0) byId[id] = closure;
|
|
2486
|
+
}
|
|
2487
|
+
}
|
|
1662
2488
|
const expectedTokens = [...table.keys()];
|
|
1663
2489
|
const expectedCount = expectedTokens.length;
|
|
1664
2490
|
return (state) => {
|
|
1665
2491
|
tick(state);
|
|
1666
2492
|
const token = state.tokens[state.idx];
|
|
1667
2493
|
if (token !== void 0) {
|
|
1668
|
-
const branch = byToken[token.type];
|
|
2494
|
+
const branch = byId && token.id !== void 0 ? byId[token.id] : byToken[token.type];
|
|
1669
2495
|
if (branch !== void 0) return branch(state);
|
|
1670
2496
|
}
|
|
1671
2497
|
for (let i = 0; i < expectedCount; i++) expect(state, expectedTokens[i]);
|
|
@@ -1750,6 +2576,9 @@ var OPERATORS = [
|
|
|
1750
2576
|
"prefix",
|
|
1751
2577
|
"postfix",
|
|
1752
2578
|
"between",
|
|
2579
|
+
"pick",
|
|
2580
|
+
"yields",
|
|
2581
|
+
"flag",
|
|
1753
2582
|
"as",
|
|
1754
2583
|
"subrule",
|
|
1755
2584
|
"label",
|
|
@@ -1804,7 +2633,12 @@ var createValueMethods = () => ({
|
|
|
1804
2633
|
|
|
1805
2634
|
// src/codegen.js
|
|
1806
2635
|
function createCodegen(config) {
|
|
1807
|
-
const { planner, rules, firsts, memo, maxSteps } = config;
|
|
2636
|
+
const { planner, rules, firsts, memo, maxSteps, tokenIds = null, memoSlots = /* @__PURE__ */ new Map(), inlineRules = true, idioms = true } = config;
|
|
2637
|
+
const typeCheck = (tokenVar, name) => {
|
|
2638
|
+
const id = tokenIds ? tokenIds.get(name) : void 0;
|
|
2639
|
+
if (id === void 0) return `${tokenVar}.type === ${JSON.stringify(name)}`;
|
|
2640
|
+
return `(${tokenVar}.id === ${id} || (${tokenVar}.id === undefined && ${tokenVar}.type === ${JSON.stringify(name)}))`;
|
|
2641
|
+
};
|
|
1808
2642
|
const HELPERS = [];
|
|
1809
2643
|
const ORIGINS = [];
|
|
1810
2644
|
const RULES = [];
|
|
@@ -1820,6 +2654,33 @@ function createCodegen(config) {
|
|
|
1820
2654
|
}
|
|
1821
2655
|
const actionHelper = (logic) => helper(planner.planAction(logic), { kind: "action", logic });
|
|
1822
2656
|
const constantHelper = (node) => helper(planner.plan(node), { kind: "constant", node });
|
|
2657
|
+
const MAX_INLINE_DEPTH = 6;
|
|
2658
|
+
const ONE_OF_SWITCH_AT = 12;
|
|
2659
|
+
const INLINE_OPS = /* @__PURE__ */ new Set(["infixLeft", "infixRight", "prefix", "postfix"]);
|
|
2660
|
+
const callSites = /* @__PURE__ */ new Map();
|
|
2661
|
+
(function count(node) {
|
|
2662
|
+
if (Array.isArray(node)) {
|
|
2663
|
+
for (const child of node) count(child);
|
|
2664
|
+
return;
|
|
2665
|
+
}
|
|
2666
|
+
if (!node || typeof node !== "object") return;
|
|
2667
|
+
if ("subrule" in node) {
|
|
2668
|
+
const target = Array.isArray(node.subrule) ? node.subrule[0] : node.subrule;
|
|
2669
|
+
if (typeof target === "string") callSites.set(target, (callSites.get(target) || 0) + 1);
|
|
2670
|
+
}
|
|
2671
|
+
for (const key of Object.keys(node)) count(node[key]);
|
|
2672
|
+
})(rules);
|
|
2673
|
+
const inlining = /* @__PURE__ */ new Set();
|
|
2674
|
+
let coldDepth = 0;
|
|
2675
|
+
const cold = (write) => {
|
|
2676
|
+
coldDepth++;
|
|
2677
|
+
try {
|
|
2678
|
+
return write();
|
|
2679
|
+
} finally {
|
|
2680
|
+
coldDepth--;
|
|
2681
|
+
}
|
|
2682
|
+
};
|
|
2683
|
+
const canInline = (name) => inlineRules && coldDepth === 0 && !memo && !memoSlots.has(name) && !inlining.has(name) && inlining.size < MAX_INLINE_DEPTH && callSites.get(name) === 1 && INLINE_OPS.has(opOf(rules[name]));
|
|
1823
2684
|
function indexOfRule(name) {
|
|
1824
2685
|
let index = ruleIndex.get(name);
|
|
1825
2686
|
if (index !== void 0) return index;
|
|
@@ -1846,7 +2707,7 @@ function createCodegen(config) {
|
|
|
1846
2707
|
out.push(
|
|
1847
2708
|
`let ${result};`,
|
|
1848
2709
|
`const ${token} = c.tokens[c.idx];`,
|
|
1849
|
-
`if (${token} !== undefined && ${token
|
|
2710
|
+
`if (${token} !== undefined && ${typeCheck(token, name)}) { c.idx++; ${result} = ${token}; }`,
|
|
1850
2711
|
`else { E(c, ${JSON.stringify(name)}); ${result} = F; }`
|
|
1851
2712
|
);
|
|
1852
2713
|
return result;
|
|
@@ -1880,6 +2741,16 @@ function createCodegen(config) {
|
|
|
1880
2741
|
);
|
|
1881
2742
|
return result;
|
|
1882
2743
|
}
|
|
2744
|
+
case "flag": {
|
|
2745
|
+
const start = nextId();
|
|
2746
|
+
out.push(`let ${result};`, `const ${start} = c.idx;`, "{");
|
|
2747
|
+
const value = gen(arg, out, bindingsVar);
|
|
2748
|
+
out.push(
|
|
2749
|
+
`if (${value} === F) { c.idx = ${start}; ${result} = false; } else ${result} = true;`,
|
|
2750
|
+
"}"
|
|
2751
|
+
);
|
|
2752
|
+
return result;
|
|
2753
|
+
}
|
|
1883
2754
|
case "many":
|
|
1884
2755
|
case "many1": {
|
|
1885
2756
|
const atLeastOne = op === "many1";
|
|
@@ -1897,7 +2768,7 @@ function createCodegen(config) {
|
|
|
1897
2768
|
);
|
|
1898
2769
|
}
|
|
1899
2770
|
out.push("for (;;) {", `const ${before} = c.idx;`);
|
|
1900
|
-
const value = gen(arg, out, bindingsVar);
|
|
2771
|
+
const value = atLeastOne ? cold(() => gen(arg, out, bindingsVar)) : gen(arg, out, bindingsVar);
|
|
1901
2772
|
out.push(
|
|
1902
2773
|
`if (${value} === F) { c.idx = ${before}; break; }`,
|
|
1903
2774
|
`if (c.idx === ${before}) P(${JSON.stringify(op)}, ${before});`,
|
|
@@ -1927,7 +2798,7 @@ function createCodegen(config) {
|
|
|
1927
2798
|
operatorVar = gen(arg.operator, out, bindingsVar);
|
|
1928
2799
|
out.push(`if (${operatorVar} === F) { c.idx = ${mark}; break; }`);
|
|
1929
2800
|
}
|
|
1930
|
-
const right = gen(arg.operand, out, bindingsVar);
|
|
2801
|
+
const right = cold(() => gen(arg.operand, out, bindingsVar));
|
|
1931
2802
|
out.push(
|
|
1932
2803
|
`if (${right} === F) { c.idx = ${mark}; break; }`,
|
|
1933
2804
|
`if (${bindings} === null) ${bindings} = { left: null, op: null, right: null };`,
|
|
@@ -1942,12 +2813,27 @@ function createCodegen(config) {
|
|
|
1942
2813
|
case "oneOf": {
|
|
1943
2814
|
const names = Array.isArray(arg) ? arg : [arg];
|
|
1944
2815
|
const token = nextId();
|
|
1945
|
-
const
|
|
2816
|
+
const ids = tokenIds ? names.map((n) => tokenIds.get(n)) : [];
|
|
2817
|
+
const failed = `else { ${names.map((n) => `E(c, ${JSON.stringify(n)});`).join(" ")} ${result} = F; }`;
|
|
2818
|
+
if (names.length > ONE_OF_SWITCH_AT && ids.every((id) => id !== void 0)) {
|
|
2819
|
+
const hit = nextId();
|
|
2820
|
+
const cases = [...new Set(ids)].map((id) => `case ${id}:`).join(" ");
|
|
2821
|
+
out.push(
|
|
2822
|
+
`let ${result};`,
|
|
2823
|
+
`const ${token} = c.tokens[c.idx];`,
|
|
2824
|
+
`let ${hit} = false;`,
|
|
2825
|
+
`if (${token} !== undefined) { switch (${token}.id !== undefined ? ${token}.id : I[${token}.type]) { ${cases} ${hit} = true; } }`,
|
|
2826
|
+
`if (${hit}) { c.idx++; ${result} = ${token}; }`,
|
|
2827
|
+
failed
|
|
2828
|
+
);
|
|
2829
|
+
return result;
|
|
2830
|
+
}
|
|
2831
|
+
const test = names.map((n) => typeCheck(token, n)).join(" || ");
|
|
1946
2832
|
out.push(
|
|
1947
2833
|
`let ${result};`,
|
|
1948
2834
|
`const ${token} = c.tokens[c.idx];`,
|
|
1949
2835
|
`if (${token} !== undefined && (${test})) { c.idx++; ${result} = ${token}; }`,
|
|
1950
|
-
|
|
2836
|
+
failed
|
|
1951
2837
|
);
|
|
1952
2838
|
return result;
|
|
1953
2839
|
}
|
|
@@ -1980,7 +2866,7 @@ function createCodegen(config) {
|
|
|
1980
2866
|
operatorVar = gen(arg.operator, out, bindingsVar);
|
|
1981
2867
|
out.push(`if (${operatorVar} === F) { c.idx = ${mark}; break; }`);
|
|
1982
2868
|
}
|
|
1983
|
-
const right = gen(arg.operand, out, bindingsVar);
|
|
2869
|
+
const right = cold(() => gen(arg.operand, out, bindingsVar));
|
|
1984
2870
|
out.push(
|
|
1985
2871
|
`if (${right} === F) { c.idx = ${mark}; break; }`,
|
|
1986
2872
|
`if (${items} === null) ${items} = [];`,
|
|
@@ -2055,6 +2941,19 @@ function createCodegen(config) {
|
|
|
2055
2941
|
);
|
|
2056
2942
|
return result;
|
|
2057
2943
|
}
|
|
2944
|
+
case "pick":
|
|
2945
|
+
case "yields": {
|
|
2946
|
+
const label2 = "L" + nextId();
|
|
2947
|
+
const start = nextId();
|
|
2948
|
+
out.push(tickSrc, `let ${result};`, `${label2}: {`, `const ${start} = c.idx;`);
|
|
2949
|
+
const values = arg.parsers.map((parser) => {
|
|
2950
|
+
const value = gen(parser, out, bindingsVar);
|
|
2951
|
+
out.push(`if (${value} === F) { c.idx = ${start}; ${result} = F; break ${label2}; }`);
|
|
2952
|
+
return value;
|
|
2953
|
+
});
|
|
2954
|
+
out.push(`${result} = ${op === "pick" ? values[arg.index] : JSON.stringify(arg.value)};`, "}");
|
|
2955
|
+
return result;
|
|
2956
|
+
}
|
|
2058
2957
|
case "prefix": {
|
|
2059
2958
|
const label2 = "L" + nextId();
|
|
2060
2959
|
const start = nextId();
|
|
@@ -2131,7 +3030,7 @@ function createCodegen(config) {
|
|
|
2131
3030
|
);
|
|
2132
3031
|
const sep = gen(sepNode, out, bindingsVar);
|
|
2133
3032
|
out.push(`if (${sep} === F) { c.idx = ${beforeSep}; break; }`, `const ${beforeItem} = c.idx;`);
|
|
2134
|
-
const item = gen(arg.rule, out, bindingsVar);
|
|
3033
|
+
const item = cold(() => gen(arg.rule, out, bindingsVar));
|
|
2135
3034
|
out.push(
|
|
2136
3035
|
`if (${item} === F) { c.idx = ${trailing ? beforeItem : beforeSep}; break; }`,
|
|
2137
3036
|
`${result}.push(${item});`,
|
|
@@ -2146,8 +3045,20 @@ function createCodegen(config) {
|
|
|
2146
3045
|
out.push(`const ${result} = ${helper(planner.plan(node), { kind: "unknownRule", name })}(c);`);
|
|
2147
3046
|
return result;
|
|
2148
3047
|
}
|
|
2149
|
-
const index = indexOfRule(name);
|
|
2150
3048
|
const parent = nextId();
|
|
3049
|
+
if (canInline(name)) {
|
|
3050
|
+
out.push(tickSrc);
|
|
3051
|
+
out.push(
|
|
3052
|
+
`const ${parent} = c.ruleDepth;`,
|
|
3053
|
+
`if (c.track) { c.ruleNames[${parent}] = ${JSON.stringify(name)}; c.ruleDepth = ${parent} + 1; }`
|
|
3054
|
+
);
|
|
3055
|
+
inlining.add(name);
|
|
3056
|
+
const inner = gen(rules[name], out, null);
|
|
3057
|
+
inlining.delete(name);
|
|
3058
|
+
out.push(`if (c.track) c.ruleDepth = ${parent};`);
|
|
3059
|
+
return inner;
|
|
3060
|
+
}
|
|
3061
|
+
const index = indexOfRule(name);
|
|
2151
3062
|
out.push(tickSrc);
|
|
2152
3063
|
if (memo) {
|
|
2153
3064
|
const key = nextId();
|
|
@@ -2168,6 +3079,26 @@ function createCodegen(config) {
|
|
|
2168
3079
|
`c.memo.set(${key}, { value: ${result}, end: c.idx });`,
|
|
2169
3080
|
"}"
|
|
2170
3081
|
);
|
|
3082
|
+
} else if (memoSlots.has(name)) {
|
|
3083
|
+
const table = nextId();
|
|
3084
|
+
const at = nextId();
|
|
3085
|
+
const end = nextId();
|
|
3086
|
+
out.push(
|
|
3087
|
+
`let ${result};`,
|
|
3088
|
+
`const ${table} = c.rm[${memoSlots.get(name)}];`,
|
|
3089
|
+
`const ${at} = c.idx;`,
|
|
3090
|
+
`const ${end} = ${table}.ends[${at}];`,
|
|
3091
|
+
`if (${end} !== 0) { c.idx = ${end} - 1; ${result} = ${table}.vals[${at}]; }`,
|
|
3092
|
+
"else {",
|
|
3093
|
+
`const ${parent} = c.ruleDepth;`,
|
|
3094
|
+
`if (c.track) { c.ruleNames[${parent}] = ${JSON.stringify(name)}; c.ruleDepth = ${parent} + 1; }`,
|
|
3095
|
+
`${result} = R[${index}](c);`,
|
|
3096
|
+
`if (c.track) c.ruleDepth = ${parent};`,
|
|
3097
|
+
`if (${result} === F) c.idx = ${at};`,
|
|
3098
|
+
`${table}.ends[${at}] = c.idx + 1;`,
|
|
3099
|
+
`${table}.vals[${at}] = ${result};`,
|
|
3100
|
+
"}"
|
|
3101
|
+
);
|
|
2171
3102
|
} else {
|
|
2172
3103
|
out.push(
|
|
2173
3104
|
`const ${parent} = c.ruleDepth;`,
|
|
@@ -2185,7 +3116,7 @@ function createCodegen(config) {
|
|
|
2185
3116
|
}
|
|
2186
3117
|
function genSeq(children, out, result) {
|
|
2187
3118
|
if (!Array.isArray(children)) throw new Error("'seq' expects an array of parsers");
|
|
2188
|
-
const idiom = recognizeSeqIdiom(children);
|
|
3119
|
+
const idiom = idioms ? recognizeSeqIdiom(children) : null;
|
|
2189
3120
|
if (idiom !== null) return gen(idiom, out, null);
|
|
2190
3121
|
let hasLabels = false;
|
|
2191
3122
|
let hasAction = false;
|
|
@@ -2221,24 +3152,53 @@ function createCodegen(config) {
|
|
|
2221
3152
|
out.push(`${result} = ${hasAction ? actionResult : "[" + collected.join(", ") + "]"};`, "}");
|
|
2222
3153
|
return result;
|
|
2223
3154
|
}
|
|
3155
|
+
function genOrderedBacktrack(list, out, bindingsVar) {
|
|
3156
|
+
const result = nextId();
|
|
3157
|
+
const label2 = "L" + nextId();
|
|
3158
|
+
const start = nextId();
|
|
3159
|
+
out.push(`let ${result};`, `${label2}: {`, `const ${start} = c.idx;`);
|
|
3160
|
+
for (const branch of list) {
|
|
3161
|
+
out.push("{");
|
|
3162
|
+
const value = gen(branch, out, bindingsVar);
|
|
3163
|
+
out.push(`if (${value} !== F) { ${result} = ${value}; break ${label2}; }`, `c.idx = ${start};`, "}");
|
|
3164
|
+
}
|
|
3165
|
+
out.push(`${result} = F;`, "}");
|
|
3166
|
+
return result;
|
|
3167
|
+
}
|
|
2224
3168
|
function genAlt(branches, out, result, bindingsVar) {
|
|
2225
3169
|
if (!Array.isArray(branches)) throw new Error("'alt' expects an array of branches");
|
|
3170
|
+
const oneOf = idioms ? recognizeAltConsumeIdiom(branches) : null;
|
|
3171
|
+
if (oneOf !== null) return gen(oneOf, out, bindingsVar);
|
|
3172
|
+
const factored = idioms ? recognizeAltPrefixIdiom(branches) : null;
|
|
3173
|
+
if (factored !== null) return gen(factored, out, bindingsVar);
|
|
2226
3174
|
const table = firsts ? buildDispatch(branches, firsts) : null;
|
|
2227
3175
|
const label2 = "L" + nextId();
|
|
2228
3176
|
if (table !== null) {
|
|
2229
3177
|
const expected = [...table.keys()];
|
|
3178
|
+
const expectedIds = tokenIds ? expected.map((t) => tokenIds.get(t)) : null;
|
|
3179
|
+
const numeric = expectedIds !== null && expectedIds.every((id) => id !== void 0);
|
|
2230
3180
|
const token = nextId();
|
|
2231
3181
|
out.push(tickSrc, `let ${result};`, `${label2}: {`, `const ${token} = c.tokens[c.idx];`);
|
|
2232
3182
|
out.push(`if (${token} === undefined) { ${expected.map((t) => `E(c, ${JSON.stringify(t)});`).join(" ")} ${result} = F; break ${label2}; }`);
|
|
2233
|
-
|
|
3183
|
+
let discriminant;
|
|
3184
|
+
if (numeric) {
|
|
3185
|
+
discriminant = nextId();
|
|
3186
|
+
out.push(`const ${discriminant} = ${token}.id !== undefined ? ${token}.id : I[${token}.type];`);
|
|
3187
|
+
out.push(`switch (${discriminant}) {`);
|
|
3188
|
+
} else {
|
|
3189
|
+
discriminant = `${token}.type`;
|
|
3190
|
+
out.push(`switch (${discriminant}) {`);
|
|
3191
|
+
}
|
|
2234
3192
|
const byBranch = /* @__PURE__ */ new Map();
|
|
2235
|
-
for (const
|
|
3193
|
+
for (const tokenName of expected) {
|
|
3194
|
+
const branch = table.get(tokenName);
|
|
2236
3195
|
if (!byBranch.has(branch)) byBranch.set(branch, []);
|
|
2237
3196
|
byBranch.get(branch).push(tokenName);
|
|
2238
3197
|
}
|
|
3198
|
+
const caseLabel = (t) => numeric ? tokenIds.get(t) : JSON.stringify(t);
|
|
2239
3199
|
for (const [branch, tokenNames] of byBranch) {
|
|
2240
|
-
out.push(tokenNames.map((t) => `case ${
|
|
2241
|
-
const value = gen(branch, out, bindingsVar);
|
|
3200
|
+
out.push(tokenNames.map((t) => `case ${caseLabel(t)}:`).join(" ") + " {");
|
|
3201
|
+
const value = Array.isArray(branch) ? genOrderedBacktrack(branch, out, bindingsVar) : gen(branch, out, bindingsVar);
|
|
2242
3202
|
out.push(`${result} = ${value}; break ${label2};`, "}");
|
|
2243
3203
|
}
|
|
2244
3204
|
out.push(
|
|
@@ -2248,16 +3208,10 @@ function createCodegen(config) {
|
|
|
2248
3208
|
);
|
|
2249
3209
|
return result;
|
|
2250
3210
|
}
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
for (const branch of branches) {
|
|
2254
|
-
out.push("{");
|
|
2255
|
-
const value = gen(branch, out, bindingsVar);
|
|
2256
|
-
out.push(`if (${value} !== F) { ${result} = ${value}; break ${label2}; }`, `c.idx = ${start};`, "}");
|
|
2257
|
-
}
|
|
2258
|
-
out.push(`${result} = F;`, "}");
|
|
2259
|
-
return result;
|
|
3211
|
+
out.push(tickSrc);
|
|
3212
|
+
return genOrderedBacktrack(branches, out, bindingsVar);
|
|
2260
3213
|
}
|
|
3214
|
+
const idByName = tokenIds ? Object.fromEntries(tokenIds) : null;
|
|
2261
3215
|
function buildRule(name) {
|
|
2262
3216
|
const out = [];
|
|
2263
3217
|
const value = gen(rules[name], out, null);
|
|
@@ -2267,8 +3221,8 @@ return ${value};
|
|
|
2267
3221
|
}`;
|
|
2268
3222
|
SOURCES[ruleIndex.get(name)] = body;
|
|
2269
3223
|
const source = `return ${body}`;
|
|
2270
|
-
const factory = new Function("F", "E", "P", "T", "R", "H", source);
|
|
2271
|
-
return factory(FAIL, expect, progressError, stepsError, RULES, HELPERS);
|
|
3224
|
+
const factory = new Function("F", "E", "P", "T", "R", "H", "I", source);
|
|
3225
|
+
return factory(FAIL, expect, progressError, stepsError, RULES, HELPERS, idByName);
|
|
2272
3226
|
}
|
|
2273
3227
|
function progressError(op, at) {
|
|
2274
3228
|
throw new Error(
|
|
@@ -2309,6 +3263,165 @@ return ${value};
|
|
|
2309
3263
|
return { compile, functionFor, sourceFor, ruleIndex, RULES, SOURCES, ORIGINS, guard };
|
|
2310
3264
|
}
|
|
2311
3265
|
|
|
3266
|
+
// src/memoplan.js
|
|
3267
|
+
function selectMemoRules(grammar, firsts, options = {}) {
|
|
3268
|
+
const ll1 = options.ll1 !== false;
|
|
3269
|
+
const names = Object.keys(grammar);
|
|
3270
|
+
const leftEdges = /* @__PURE__ */ new Map();
|
|
3271
|
+
const callEdges = /* @__PURE__ */ new Map();
|
|
3272
|
+
for (const name of names) {
|
|
3273
|
+
const left = /* @__PURE__ */ new Set();
|
|
3274
|
+
leftmostCalls(grammar[name], firsts, left);
|
|
3275
|
+
leftEdges.set(name, [...left].filter((r) => r in grammar));
|
|
3276
|
+
const calls = /* @__PURE__ */ new Set();
|
|
3277
|
+
walk(grammar[name], (node) => {
|
|
3278
|
+
if (opOf(node) !== "subrule") return;
|
|
3279
|
+
const arg = node.subrule;
|
|
3280
|
+
const target = Array.isArray(arg) ? arg[0] : arg;
|
|
3281
|
+
if (typeof target === "string" && target in grammar) calls.add(target);
|
|
3282
|
+
});
|
|
3283
|
+
callEdges.set(name, [...calls]);
|
|
3284
|
+
}
|
|
3285
|
+
const closure = (edges, from) => {
|
|
3286
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3287
|
+
const stack = [...from];
|
|
3288
|
+
while (stack.length > 0) {
|
|
3289
|
+
const next = stack.pop();
|
|
3290
|
+
if (seen.has(next)) continue;
|
|
3291
|
+
seen.add(next);
|
|
3292
|
+
for (const edge of edges.get(next) || []) stack.push(edge);
|
|
3293
|
+
}
|
|
3294
|
+
return seen;
|
|
3295
|
+
};
|
|
3296
|
+
const leftCache = /* @__PURE__ */ new Map();
|
|
3297
|
+
const leftReach = (name) => {
|
|
3298
|
+
let reach = leftCache.get(name);
|
|
3299
|
+
if (reach === void 0) {
|
|
3300
|
+
reach = closure(leftEdges, leftEdges.get(name) || []);
|
|
3301
|
+
leftCache.set(name, reach);
|
|
3302
|
+
}
|
|
3303
|
+
return reach;
|
|
3304
|
+
};
|
|
3305
|
+
const startsWith = (branch) => {
|
|
3306
|
+
const direct = /* @__PURE__ */ new Set();
|
|
3307
|
+
leftmostCalls(branch, firsts, direct);
|
|
3308
|
+
const all = /* @__PURE__ */ new Set();
|
|
3309
|
+
for (const rule of direct) {
|
|
3310
|
+
if (!(rule in grammar)) continue;
|
|
3311
|
+
all.add(rule);
|
|
3312
|
+
for (const deeper of leftReach(rule)) all.add(deeper);
|
|
3313
|
+
}
|
|
3314
|
+
return all;
|
|
3315
|
+
};
|
|
3316
|
+
const sites = [];
|
|
3317
|
+
for (const name of names) {
|
|
3318
|
+
walk(grammar[name], (node) => {
|
|
3319
|
+
if (opOf(node) !== "alt" || !Array.isArray(node.alt) || node.alt.length < 2) return;
|
|
3320
|
+
for (const group of collisionGroups(node.alt, firsts, ll1)) {
|
|
3321
|
+
const counts = /* @__PURE__ */ new Map();
|
|
3322
|
+
for (const branch of group) {
|
|
3323
|
+
for (const rule of startsWith(branch)) counts.set(rule, (counts.get(rule) || 0) + 1);
|
|
3324
|
+
}
|
|
3325
|
+
const shared = [...counts].filter(([, n]) => n >= 2).map(([rule]) => rule);
|
|
3326
|
+
const topmost = shared.filter((rule) => !shared.some((other) => other !== rule && leftReach(other).has(rule) && !leftReach(rule).has(other)));
|
|
3327
|
+
if (topmost.length > 0) sites.push({ host: name, rules: topmost });
|
|
3328
|
+
}
|
|
3329
|
+
});
|
|
3330
|
+
}
|
|
3331
|
+
const hosts = new Set(sites.map((site) => site.host));
|
|
3332
|
+
const chosen = /* @__PURE__ */ new Set();
|
|
3333
|
+
for (const { rules } of sites) {
|
|
3334
|
+
for (const rule of rules) {
|
|
3335
|
+
const inside = closure(callEdges, [rule]);
|
|
3336
|
+
inside.add(rule);
|
|
3337
|
+
for (const host of hosts) {
|
|
3338
|
+
if (inside.has(host)) {
|
|
3339
|
+
chosen.add(rule);
|
|
3340
|
+
break;
|
|
3341
|
+
}
|
|
3342
|
+
}
|
|
3343
|
+
}
|
|
3344
|
+
}
|
|
3345
|
+
const dependsOnCaller = (name) => {
|
|
3346
|
+
const reachable = closure(callEdges, [name]);
|
|
3347
|
+
reachable.add(name);
|
|
3348
|
+
for (const rule of reachable) if (usesOuterScope(grammar[rule])) return true;
|
|
3349
|
+
return false;
|
|
3350
|
+
};
|
|
3351
|
+
return names.filter((name) => chosen.has(name) && !dependsOnCaller(name));
|
|
3352
|
+
}
|
|
3353
|
+
function collisionGroups(branches, firsts, ll1) {
|
|
3354
|
+
const table = ll1 ? buildDispatch(branches, firsts) : null;
|
|
3355
|
+
if (table === null) return [branches];
|
|
3356
|
+
const groups = /* @__PURE__ */ new Set();
|
|
3357
|
+
for (const entry of table.values()) if (Array.isArray(entry) && entry.length > 1) groups.add(entry);
|
|
3358
|
+
return [...groups];
|
|
3359
|
+
}
|
|
3360
|
+
function walk(node, visit) {
|
|
3361
|
+
if (Array.isArray(node)) {
|
|
3362
|
+
for (const child of node) walk(child, visit);
|
|
3363
|
+
return;
|
|
3364
|
+
}
|
|
3365
|
+
const op = opOf(node);
|
|
3366
|
+
if (op === null) return;
|
|
3367
|
+
visit(node);
|
|
3368
|
+
const arg = node[op];
|
|
3369
|
+
switch (op) {
|
|
3370
|
+
case "seq":
|
|
3371
|
+
case "alt":
|
|
3372
|
+
if (Array.isArray(arg)) for (const child of arg) walk(child, visit);
|
|
3373
|
+
break;
|
|
3374
|
+
case "label":
|
|
3375
|
+
if (Array.isArray(arg)) walk(arg[1], visit);
|
|
3376
|
+
break;
|
|
3377
|
+
case "many":
|
|
3378
|
+
case "many1":
|
|
3379
|
+
case "option":
|
|
3380
|
+
case "flag":
|
|
3381
|
+
case "text":
|
|
3382
|
+
case "lookahead":
|
|
3383
|
+
case "negLookahead":
|
|
3384
|
+
walk(arg, visit);
|
|
3385
|
+
break;
|
|
3386
|
+
case "as":
|
|
3387
|
+
walk(Array.isArray(arg) ? arg[0] : arg && arg.parser, visit);
|
|
3388
|
+
break;
|
|
3389
|
+
case "between":
|
|
3390
|
+
if (arg) for (const part of [arg.open, arg.body, arg.close]) walk(part, visit);
|
|
3391
|
+
break;
|
|
3392
|
+
case "pick":
|
|
3393
|
+
case "yields":
|
|
3394
|
+
if (arg && Array.isArray(arg.parsers)) for (const part of arg.parsers) walk(part, visit);
|
|
3395
|
+
break;
|
|
3396
|
+
case "prefix":
|
|
3397
|
+
if (arg) {
|
|
3398
|
+
walk(arg.operator, visit);
|
|
3399
|
+
walk(arg.operand, visit);
|
|
3400
|
+
}
|
|
3401
|
+
break;
|
|
3402
|
+
case "postfix":
|
|
3403
|
+
if (arg) {
|
|
3404
|
+
walk(arg.operand, visit);
|
|
3405
|
+
walk(arg.suffix, visit);
|
|
3406
|
+
}
|
|
3407
|
+
break;
|
|
3408
|
+
case "infixLeft":
|
|
3409
|
+
case "infixRight":
|
|
3410
|
+
if (arg) {
|
|
3411
|
+
walk(arg.operand, visit);
|
|
3412
|
+
if (arg.operator !== void 0) walk(arg.operator, visit);
|
|
3413
|
+
}
|
|
3414
|
+
break;
|
|
3415
|
+
case "manySep":
|
|
3416
|
+
case "many1Sep":
|
|
3417
|
+
if (arg) {
|
|
3418
|
+
walk(arg.rule, visit);
|
|
3419
|
+
if (typeof arg.sep !== "string") walk(arg.sep, visit);
|
|
3420
|
+
}
|
|
3421
|
+
break;
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
|
|
2312
3425
|
// src/parser.js
|
|
2313
3426
|
var EXECUTION_MODES = /* @__PURE__ */ new Set(["generated", "interpreted"]);
|
|
2314
3427
|
var GrammarError = class extends Error {
|
|
@@ -2324,6 +3437,9 @@ function createParser(spec, options = {}) {
|
|
|
2324
3437
|
const { tokens: tokenDefs, rules, start } = spec;
|
|
2325
3438
|
const {
|
|
2326
3439
|
memo = false,
|
|
3440
|
+
autoMemo = true,
|
|
3441
|
+
inlineRules = true,
|
|
3442
|
+
idioms = true,
|
|
2327
3443
|
strict = true,
|
|
2328
3444
|
validate = true,
|
|
2329
3445
|
maxSteps = 0,
|
|
@@ -2350,9 +3466,12 @@ function createParser(spec, options = {}) {
|
|
|
2350
3466
|
for (const [name, method] of Object.entries(extraMethods || {})) engine.addMethod(name, method);
|
|
2351
3467
|
const startRule = rules[start];
|
|
2352
3468
|
const firsts = ll1 ? analysis.firsts : null;
|
|
2353
|
-
const
|
|
3469
|
+
const tokenIds = new Map(lexer.tokenNames.map((name, id) => [name, id]));
|
|
3470
|
+
const memoRules = autoMemo && !memo && analysis.errors.length === 0 ? selectMemoRules(rules, analysis.firsts, { ll1 }) : [];
|
|
3471
|
+
const memoSlots = new Map(memoRules.map((name, slot) => [name, slot]));
|
|
3472
|
+
const planner = createPlanner({ engine, rules, firsts, memo, maxSteps, unsafeEval, tokenIds, memoSlots, idioms });
|
|
2354
3473
|
engine.__jlGrammar = { planner };
|
|
2355
|
-
const codegen = compile && unsafeEval ? createCodegen({ planner, rules, firsts, memo, maxSteps }) : null;
|
|
3474
|
+
const codegen = compile && unsafeEval ? createCodegen({ planner, rules, firsts, memo, maxSteps, tokenIds, memoSlots, inlineRules, idioms }) : null;
|
|
2356
3475
|
const compiledStart = codegen && codegen.compile(start);
|
|
2357
3476
|
if (codegen && !engine.disableInterpretedOptimization) {
|
|
2358
3477
|
for (const name of Object.keys(rules)) {
|
|
@@ -2361,7 +3480,7 @@ function createParser(spec, options = {}) {
|
|
|
2361
3480
|
}
|
|
2362
3481
|
}
|
|
2363
3482
|
function parseTokens(tokens, source, track = false) {
|
|
2364
|
-
const state = createState(tokens, rules, { memo, maxSteps, track });
|
|
3483
|
+
const state = createState(tokens, rules, { memo, maxSteps, track, memoSlots: memoRules.length });
|
|
2365
3484
|
state.firsts = firsts;
|
|
2366
3485
|
state.source = source;
|
|
2367
3486
|
state.positions = lexer.positions;
|
|
@@ -2391,6 +3510,8 @@ function createParser(spec, options = {}) {
|
|
|
2391
3510
|
planner,
|
|
2392
3511
|
codegen,
|
|
2393
3512
|
execution: mode,
|
|
3513
|
+
/** Rules memoized by `autoMemo`, for introspection. */
|
|
3514
|
+
memoRules,
|
|
2394
3515
|
compiled: Boolean(compiledStart),
|
|
2395
3516
|
/** Rules whose `alt`s resolved to O(1) LL(1) dispatch, for introspection. */
|
|
2396
3517
|
get firsts() {
|
|
@@ -2696,6 +3817,7 @@ var CONSTRUCTORS = {
|
|
|
2696
3817
|
many: { arity: [1, 1], of: ([a]) => ({ many: a }) },
|
|
2697
3818
|
many1: { arity: [1, 1], of: ([a]) => ({ many1: a }) },
|
|
2698
3819
|
option: { arity: [1, 1], of: ([a]) => ({ option: a }) },
|
|
3820
|
+
flag: { arity: [1, 1], of: ([a]) => ({ flag: a }) },
|
|
2699
3821
|
action: { arity: [1, 1], of: ([a]) => ({ action: a }) },
|
|
2700
3822
|
lookahead: { arity: [1, 1], of: ([a]) => ({ lookahead: a }) },
|
|
2701
3823
|
negLookahead: { arity: [1, 1], of: ([a]) => ({ negLookahead: a }) },
|
|
@@ -2707,6 +3829,10 @@ var CONSTRUCTORS = {
|
|
|
2707
3829
|
manySep: { arity: [2, 2], of: ([a, b]) => ({ manySep: { rule: a, sep: b } }) },
|
|
2708
3830
|
many1Sep: { arity: [2, 2], of: ([a, b]) => ({ many1Sep: { rule: a, sep: b } }) },
|
|
2709
3831
|
between: { arity: [3, 3], of: ([a, b, c]) => ({ between: { open: a, body: b, close: c } }) },
|
|
3832
|
+
// pick(1, consume(Open), subrule(Body), consume(Close)) — the result at that index.
|
|
3833
|
+
pick: { arity: [2, Infinity], of: ([index, ...parsers]) => ({ pick: { parsers, index } }) },
|
|
3834
|
+
// yields(consume(True), true) — run the parsers, give back the constant.
|
|
3835
|
+
yields: { arity: [2, Infinity], of: (args) => ({ yields: { parsers: args.slice(0, -1), value: args[args.length - 1] } }) },
|
|
2710
3836
|
infixLeft: { arity: [3, 3], of: ([a, b, c]) => ({ infixLeft: { operand: a, operator: b, combine: logicOf(c) } }) },
|
|
2711
3837
|
infixRight: { arity: [3, 3], of: ([a, b, c]) => ({ infixRight: { operand: a, operator: b, combine: logicOf(c) } }) },
|
|
2712
3838
|
prefix: { arity: [3, 3], of: ([a, b, c]) => ({ prefix: { operator: a, operand: b, combine: logicOf(c) } }) },
|
|
@@ -2727,6 +3853,355 @@ function createParserFromSource(source, options = {}) {
|
|
|
2727
3853
|
return createParser(grammar, { ...options, methods });
|
|
2728
3854
|
}
|
|
2729
3855
|
|
|
3856
|
+
// src/lexgen.js
|
|
3857
|
+
function generateLexer(defs, options = {}) {
|
|
3858
|
+
const positions = options.positions === "offset" ? "offset" : "full";
|
|
3859
|
+
const trackLines = positions === "full";
|
|
3860
|
+
const useScanners = options.scanners !== false;
|
|
3861
|
+
const useFuse = options.fuse !== false;
|
|
3862
|
+
const modeIds = /* @__PURE__ */ new Map([[DEFAULT_MODE2, 0]]);
|
|
3863
|
+
const modeNames = [DEFAULT_MODE2];
|
|
3864
|
+
const idOf = (name) => {
|
|
3865
|
+
let id = modeIds.get(name);
|
|
3866
|
+
if (id === void 0) {
|
|
3867
|
+
id = modeNames.length;
|
|
3868
|
+
modeIds.set(name, id);
|
|
3869
|
+
modeNames.push(name);
|
|
3870
|
+
}
|
|
3871
|
+
return id;
|
|
3872
|
+
};
|
|
3873
|
+
for (const def of defs) for (const mode of modesOf2(def)) idOf(mode);
|
|
3874
|
+
const compiled = defs.map((def, id) => {
|
|
3875
|
+
const flags = def.ignoreCase ? "iy" : "y";
|
|
3876
|
+
return {
|
|
3877
|
+
id,
|
|
3878
|
+
name: def.name,
|
|
3879
|
+
skip: !!def.skip,
|
|
3880
|
+
literal: def.literal,
|
|
3881
|
+
pattern: def.pattern,
|
|
3882
|
+
flags,
|
|
3883
|
+
longerAlt: def.longerAlt || null,
|
|
3884
|
+
scanner: useScanners && def.pattern ? buildScanner(def.pattern, !!def.ignoreCase) : null,
|
|
3885
|
+
modes: modesOf2(def),
|
|
3886
|
+
pushMode: def.pushMode ? idOf(def.pushMode) : -1,
|
|
3887
|
+
popMode: !!def.popMode,
|
|
3888
|
+
firstChars: def.literal !== void 0 ? def.literal.length > 0 ? /* @__PURE__ */ new Set([def.literal.charCodeAt(0)]) : null : firstCharSet(new RegExp(def.pattern, flags).source, !!def.ignoreCase)
|
|
3889
|
+
};
|
|
3890
|
+
});
|
|
3891
|
+
const byName = new Map(compiled.map((c) => [c.name, c]));
|
|
3892
|
+
const multiMode = modeNames.length > 1 || compiled.some((c) => c.popMode || c.pushMode !== -1);
|
|
3893
|
+
const regexes = [];
|
|
3894
|
+
const regexIndex = /* @__PURE__ */ new Map();
|
|
3895
|
+
const rxFor = (c) => {
|
|
3896
|
+
if (!regexIndex.has(c.id)) {
|
|
3897
|
+
regexIndex.set(c.id, regexes.length);
|
|
3898
|
+
regexes.push([c.id, c.flags]);
|
|
3899
|
+
}
|
|
3900
|
+
return (
|
|
3901
|
+
/** @type {number} */
|
|
3902
|
+
regexIndex.get(c.id)
|
|
3903
|
+
);
|
|
3904
|
+
};
|
|
3905
|
+
const fusedRuns = [];
|
|
3906
|
+
const helpers = [];
|
|
3907
|
+
const keywords = [];
|
|
3908
|
+
const lookFns = /* @__PURE__ */ new Map();
|
|
3909
|
+
const need = { digits: false, hex: false, keyword: false };
|
|
3910
|
+
function emitRegex(c) {
|
|
3911
|
+
const rx = rxFor(c);
|
|
3912
|
+
if (c.skip) {
|
|
3913
|
+
return `RX[${rx}].lastIndex = offset; if (RX[${rx}].test(text) && RX[${rx}].lastIndex > offset) { id = ${c.id}; end = RX[${rx}].lastIndex; matched = null; break found }`;
|
|
3914
|
+
}
|
|
3915
|
+
return `RX[${rx}].lastIndex = offset; m = RX[${rx}].exec(text); if (m !== null && m[0].length > 0) { id = ${c.id}; matched = m[0]; end = offset + matched.length; break found }`;
|
|
3916
|
+
}
|
|
3917
|
+
const emitted = /* @__PURE__ */ new Map();
|
|
3918
|
+
function emitDef(c) {
|
|
3919
|
+
let code = emitted.get(c.id);
|
|
3920
|
+
if (code === void 0) {
|
|
3921
|
+
code = emitDefOnce(c);
|
|
3922
|
+
emitted.set(c.id, code);
|
|
3923
|
+
}
|
|
3924
|
+
return code;
|
|
3925
|
+
}
|
|
3926
|
+
function emitDefOnce(c) {
|
|
3927
|
+
if (c.literal !== void 0) {
|
|
3928
|
+
const lit = c.literal;
|
|
3929
|
+
const test = lit.length === 1 ? `code === ${lit.charCodeAt(0)}` : `text.startsWith(${q(lit)}, offset)`;
|
|
3930
|
+
return `if (${test}) { id = ${c.id}; end = offset + ${lit.length}; matched = ${q(lit)}; break found }`;
|
|
3931
|
+
}
|
|
3932
|
+
const s = c.scanner;
|
|
3933
|
+
if (s === null) return emitRegex(c);
|
|
3934
|
+
const hit = `{ id = ${c.id}; end = e; matched = null; break found }`;
|
|
3935
|
+
switch (s.kind) {
|
|
3936
|
+
case SCANNER_KINDS.CLASS: {
|
|
3937
|
+
if (s.wide) {
|
|
3938
|
+
helpers.push(wideClassHelper(c.id, s));
|
|
3939
|
+
return `{ const e = scan${c.id}(text, offset, length); if (e === -2) { ${emitRegex(c)} } else if (e !== -1) ${hit} }`;
|
|
3940
|
+
}
|
|
3941
|
+
return `if (${condition(s.first, "code")}) { let j = offset + 1; while (j < length) { const c = text.charCodeAt(j); if (!(${condition(s.rest, "c")})) break; j++ } id = ${c.id}; end = j; matched = null; break found }`;
|
|
3942
|
+
}
|
|
3943
|
+
case SCANNER_KINDS.SEQ2:
|
|
3944
|
+
return `if (${condition(s.first, "code")}) { let j = offset + 1; while (j < length) { const c = text.charCodeAt(j); if (${condition(s.first, "c")}) j++; else break } const mid = j; while (j < length) { const c = text.charCodeAt(j); if (${condition(s.second, "c")}) j++; else break } if (j > mid) { id = ${c.id}; end = j; matched = null; break found } }`;
|
|
3945
|
+
case SCANNER_KINDS.KEYWORD: {
|
|
3946
|
+
need.keyword = true;
|
|
3947
|
+
const cond = condition(s.look, "c");
|
|
3948
|
+
let look = lookFns.get(cond);
|
|
3949
|
+
if (look === void 0) {
|
|
3950
|
+
look = `look${lookFns.size}`;
|
|
3951
|
+
lookFns.set(cond, look);
|
|
3952
|
+
}
|
|
3953
|
+
keywords.push([s.lower, s.upper]);
|
|
3954
|
+
return `{ const e = scanKeyword(text, offset, length, KW[${keywords.length - 1}][0], KW[${keywords.length - 1}][1], ${look}); if (e !== -1) ${hit} }`;
|
|
3955
|
+
}
|
|
3956
|
+
case SCANNER_KINDS.LINE:
|
|
3957
|
+
return `if (text.startsWith(${q(s.prefix)}, offset)) { let j = offset + ${s.prefix.length}; while (j < length) { const c = text.charCodeAt(j); if (${condition(s.look, "c")}) break; j++ } id = ${c.id}; end = j; matched = null; break found }`;
|
|
3958
|
+
case SCANNER_KINDS.QUOTED:
|
|
3959
|
+
helpers.push(quotedHelper(c.id, s));
|
|
3960
|
+
return `{ const e = scan${c.id}(text, offset); if (e !== -1) ${hit} }`;
|
|
3961
|
+
case SCANNER_KINDS.NUMBER:
|
|
3962
|
+
need.digits = true;
|
|
3963
|
+
helpers.push(numberHelper(c.id, s));
|
|
3964
|
+
return `{ const e = scan${c.id}(text, offset, length); if (e !== -1) ${hit} }`;
|
|
3965
|
+
case SCANNER_KINDS.STRING:
|
|
3966
|
+
if (s.uni) need.hex = true;
|
|
3967
|
+
helpers.push(stringHelper(c.id, s));
|
|
3968
|
+
return `{ const e = scan${c.id}(text, offset, length); if (e !== -1) ${hit} }`;
|
|
3969
|
+
default:
|
|
3970
|
+
return emitRegex(c);
|
|
3971
|
+
}
|
|
3972
|
+
}
|
|
3973
|
+
const fusible = (c) => c.literal === void 0 && c.scanner === null && !/\\[1-9]|\\k</.test(c.pattern) && new RegExp(c.pattern + "|").exec("").length === 1;
|
|
3974
|
+
function emitList(list) {
|
|
3975
|
+
const out = [];
|
|
3976
|
+
for (let i = 0; i < list.length; ) {
|
|
3977
|
+
let j = i;
|
|
3978
|
+
while (useFuse && j < list.length && fusible(list[j]) && list[j].flags === list[i].flags) j++;
|
|
3979
|
+
if (j - i < 3) {
|
|
3980
|
+
out.push(emitDef(list[i]));
|
|
3981
|
+
i++;
|
|
3982
|
+
continue;
|
|
3983
|
+
}
|
|
3984
|
+
const run = list.slice(i, j);
|
|
3985
|
+
run.forEach(rxFor);
|
|
3986
|
+
const k = fusedRuns.length;
|
|
3987
|
+
fusedRuns.push({ flags: run[0].flags, ids: run.map((d) => d.id), rx: run.map((d) => (
|
|
3988
|
+
/** @type {number} */
|
|
3989
|
+
regexIndex.get(d.id)
|
|
3990
|
+
)) });
|
|
3991
|
+
out.push(
|
|
3992
|
+
`FX[${k}].lastIndex = offset; m = FX[${k}].exec(text)
|
|
3993
|
+
if (m !== null) {
|
|
3994
|
+
if (m[0].length > 0) { let g = 1; while (m[g] === undefined) g++; id = FIDS[${k}][g - 1]; matched = m[0]; end = offset + matched.length; break found }
|
|
3995
|
+
// An empty match is "no match" to a lexer, so the members after it still get their turn.
|
|
3996
|
+
for (let k = 0; k < FRX[${k}].length; k++) { const r = RX[FRX[${k}][k]]; r.lastIndex = offset; const mm = r.exec(text); if (mm !== null && mm[0].length > 0) { id = FIDS[${k}][k]; matched = mm[0]; end = offset + matched.length; break found } }
|
|
3997
|
+
}`
|
|
3998
|
+
);
|
|
3999
|
+
i = j;
|
|
4000
|
+
}
|
|
4001
|
+
return out.join("\n ");
|
|
4002
|
+
}
|
|
4003
|
+
const lists = [{ list: (
|
|
4004
|
+
/** @type {any[]} */
|
|
4005
|
+
[]
|
|
4006
|
+
) }];
|
|
4007
|
+
const listIds = /* @__PURE__ */ new Map([["", 0]]);
|
|
4008
|
+
const listFor = (list) => {
|
|
4009
|
+
const key = list.map((c) => c.id).join(",");
|
|
4010
|
+
let id = listIds.get(key);
|
|
4011
|
+
if (id === void 0) {
|
|
4012
|
+
id = lists.length;
|
|
4013
|
+
listIds.set(key, id);
|
|
4014
|
+
lists.push({ list });
|
|
4015
|
+
}
|
|
4016
|
+
return id;
|
|
4017
|
+
};
|
|
4018
|
+
const WIDTH = 129;
|
|
4019
|
+
const table = [];
|
|
4020
|
+
for (const name of modeNames) {
|
|
4021
|
+
const members = compiled.filter((c) => c.modes.includes(name));
|
|
4022
|
+
if (members.length === 0) throw new Error(`generateLexer: mode '${name}' has no tokens`);
|
|
4023
|
+
const dispatch = members.some((c) => c.firstChars !== null);
|
|
4024
|
+
for (let code = 0; code < 128; code++) {
|
|
4025
|
+
table.push(listFor(dispatch ? members.filter((c) => c.firstChars === null || c.firstChars.has(code)) : members));
|
|
4026
|
+
}
|
|
4027
|
+
table.push(listFor(dispatch ? members.filter((c) => c.firstChars === null || [...c.firstChars].some((x) => x >= 128)) : members));
|
|
4028
|
+
}
|
|
4029
|
+
if (lists.length > 6e4) throw new Error("generateLexer: too many distinct candidate lists");
|
|
4030
|
+
const dispatchTable = String.fromCharCode(...table.map((id) => 256 + id));
|
|
4031
|
+
const listCases = lists.slice(1).map(({ list }, i) => `case ${i + 1}: {
|
|
4032
|
+
${emitList(list)}
|
|
4033
|
+
break
|
|
4034
|
+
}`);
|
|
4035
|
+
const longer = compiled.filter((c) => c.longerAlt);
|
|
4036
|
+
const longerCode = longer.length === 0 ? "" : `
|
|
4037
|
+
switch (id) {
|
|
4038
|
+
${longer.map((c) => {
|
|
4039
|
+
const alt2 = (
|
|
4040
|
+
/** @type {any} */
|
|
4041
|
+
byName.get(
|
|
4042
|
+
/** @type {string} */
|
|
4043
|
+
c.longerAlt
|
|
4044
|
+
)
|
|
4045
|
+
);
|
|
4046
|
+
const altEnd = alt2.literal !== void 0 ? `text.startsWith(${q(alt2.literal)}, offset) ? offset + ${alt2.literal.length} : -1` : `(RX[${rxFor(alt2)}].lastIndex = offset, RX[${rxFor(alt2)}].test(text) ? RX[${rxFor(alt2)}].lastIndex : -1)`;
|
|
4047
|
+
return ` case ${c.id}: { const altEnd = ${altEnd}; if (altEnd > end) { end = altEnd; id = ${alt2.id}; matched = ${alt2.literal !== void 0 ? q(alt2.literal) : "null"} } break }`;
|
|
4048
|
+
}).join("\n")}
|
|
4049
|
+
}`;
|
|
4050
|
+
const transitions = compiled.filter((c) => c.popMode || c.pushMode !== -1);
|
|
4051
|
+
const describe = trackLines ? "'line ' + line + ' column ' + (offset - lineStart + 1)" : "'offset ' + offset";
|
|
4052
|
+
const transitionCode = transitions.length === 0 ? "" : `
|
|
4053
|
+
switch (id) {
|
|
4054
|
+
${transitions.map((c) => c.popMode ? ` case ${c.id}:
|
|
4055
|
+
if (depth === 0) throw new LexError('Token \\'' + NAMES[id] + '\\' popped the initial lexer mode at ' + ${describe}, { offset, line: ${trackLines ? "line" : "0"}, col: ${trackLines ? "offset - lineStart + 1" : "0"} })
|
|
4056
|
+
mode = stack[--depth]
|
|
4057
|
+
break` : ` case ${c.id}: stack[depth++] = mode; mode = ${c.pushMode}; break`).join("\n")}
|
|
4058
|
+
}`;
|
|
4059
|
+
const where = trackLines ? "'line ' + line + ' column ' + column" : "'offset ' + offset";
|
|
4060
|
+
const inMode = modeNames.length > 1 ? " + ' (in lexer mode \\'' + MODE_NAMES[mode] + '\\')'" : "";
|
|
4061
|
+
return `(() => {
|
|
4062
|
+
const NAMES = tokens.map((t) => t.name)
|
|
4063
|
+
const SKIP = tokens.map((t) => (t.skip ? 1 : 0))
|
|
4064
|
+
const DISPATCH = ${q(dispatchTable)}
|
|
4065
|
+
${modeNames.length > 1 ? `const MODE_NAMES = ${q(modeNames)}` : ""}
|
|
4066
|
+
// Patterns are read from the token table above rather than repeated here.
|
|
4067
|
+
const RX = ${q(regexes)}.map(([i, flags]) => new RegExp(tokens[i].pattern, flags))
|
|
4068
|
+
${fusedRuns.length ? `const FX = ${q(fusedRuns.map((f) => [f.ids, f.flags]))}.map(([ids, flags]) => new RegExp(ids.map((i) => '(' + tokens[i].pattern + ')').join('|'), flags))
|
|
4069
|
+
const FIDS = ${q(fusedRuns.map((f) => f.ids))}
|
|
4070
|
+
const FRX = ${q(fusedRuns.map((f) => f.rx))}` : ""}
|
|
4071
|
+
${need.digits ? "const skipDigits = (text, i, length) => { while (i < length) { const c = text.charCodeAt(i); if (c < 48 || c > 57) break; i++ } return i }" : ""}
|
|
4072
|
+
${need.hex ? "const isHex = (c) => (c >= 48 && c <= 57) || (c >= 65 && c <= 70) || (c >= 97 && c <= 102)" : ""}
|
|
4073
|
+
${need.keyword ? `const KW = ${q(keywords)}
|
|
4074
|
+
${[...lookFns].map(([cond, name]) => `const ${name} = (c) => ${cond}`).join("\n ")}
|
|
4075
|
+
${KEYWORD_HELPER}` : ""}
|
|
4076
|
+
${helpers.join("\n")}
|
|
4077
|
+
|
|
4078
|
+
function tokenize (text) {
|
|
4079
|
+
if (typeof text !== 'string') throw new TypeError('tokenize: expected a string')
|
|
4080
|
+
const tokens_ = []
|
|
4081
|
+
let offset = 0
|
|
4082
|
+
let line = 1
|
|
4083
|
+
let lineStart = 0
|
|
4084
|
+
const length = text.length
|
|
4085
|
+
${multiMode ? "let mode = 0\n const stack = []\n let depth = 0" : ""}
|
|
4086
|
+
let m = null
|
|
4087
|
+
while (offset < length) {
|
|
4088
|
+
const code = text.charCodeAt(offset)
|
|
4089
|
+
let id = -1
|
|
4090
|
+
let end = -1
|
|
4091
|
+
let matched = null
|
|
4092
|
+
found: {
|
|
4093
|
+
switch (DISPATCH.charCodeAt(${multiMode ? `mode * ${WIDTH} + ` : ""}(code < 128 ? code : 128)) - 256) {
|
|
4094
|
+
${listCases.join("\n ")}
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
if (id === -1) {
|
|
4098
|
+
const column = offset - lineStart + 1
|
|
4099
|
+
throw new LexError('Unexpected character \\'' + text[offset] + '\\' at ' + ${where}${inMode}, { offset, line: ${trackLines ? "line" : "0"}, col: ${trackLines ? "column" : "0"} })
|
|
4100
|
+
}${longerCode}
|
|
4101
|
+
if (SKIP[id] === 0) {
|
|
4102
|
+
const image = matched !== null ? matched : text.slice(offset, end)
|
|
4103
|
+
tokens_.push(${trackLines ? "{ type: NAMES[id], image, start: offset, end, line, col: offset - lineStart + 1 }" : "{ type: NAMES[id], id, image, start: offset, end }"})
|
|
4104
|
+
}
|
|
4105
|
+
${trackLines ? "for (let j = offset; j < end; j++) { if (text.charCodeAt(j) !== 10) continue; line++; lineStart = j + 1 }" : ""}${transitionCode}
|
|
4106
|
+
offset = end
|
|
4107
|
+
}
|
|
4108
|
+
return tokens_
|
|
4109
|
+
}
|
|
4110
|
+
return { tokenize, tokenNames: NAMES, positions: ${q(positions)}, modeNames: ${q(modeNames)} }
|
|
4111
|
+
})()`;
|
|
4112
|
+
}
|
|
4113
|
+
var DEFAULT_MODE2 = "default";
|
|
4114
|
+
var q = JSON.stringify;
|
|
4115
|
+
function modesOf2(def) {
|
|
4116
|
+
if (Array.isArray(def.modes) && def.modes.length > 0) return def.modes;
|
|
4117
|
+
if (def.mode) return [def.mode];
|
|
4118
|
+
return [DEFAULT_MODE2];
|
|
4119
|
+
}
|
|
4120
|
+
function condition(table, v) {
|
|
4121
|
+
const parts = [];
|
|
4122
|
+
for (let i = 0; i < 128; ) {
|
|
4123
|
+
if (table[i] !== 1) {
|
|
4124
|
+
i++;
|
|
4125
|
+
continue;
|
|
4126
|
+
}
|
|
4127
|
+
let j = i;
|
|
4128
|
+
while (j + 1 < 128 && table[j + 1] === 1) j++;
|
|
4129
|
+
parts.push(i === j ? `${v} === ${i}` : `(${v} >= ${i} && ${v} <= ${j})`);
|
|
4130
|
+
i = j + 1;
|
|
4131
|
+
}
|
|
4132
|
+
return parts.length > 0 ? parts.join(" || ") : "false";
|
|
4133
|
+
}
|
|
4134
|
+
var KEYWORD_HELPER = `function scanKeyword (text, offset, length, lower, upper, look) {
|
|
4135
|
+
const n = lower.length
|
|
4136
|
+
if (offset + n > length) return -1
|
|
4137
|
+
for (let i = 0; i < n; i++) {
|
|
4138
|
+
const c = text.charCodeAt(offset + i)
|
|
4139
|
+
if (c !== lower[i] && c !== upper[i]) return -1
|
|
4140
|
+
}
|
|
4141
|
+
const end = offset + n
|
|
4142
|
+
if (end < length && look(text.charCodeAt(end))) return -1
|
|
4143
|
+
return end
|
|
4144
|
+
}`;
|
|
4145
|
+
function wideClassHelper(id, s) {
|
|
4146
|
+
return `function scan${id} (text, offset, length) {
|
|
4147
|
+
const code = text.charCodeAt(offset)
|
|
4148
|
+
if (code >= 128) return -2
|
|
4149
|
+
if (!(${condition(s.first, "code")})) return -1
|
|
4150
|
+
let j = offset + 1
|
|
4151
|
+
while (j < length) {
|
|
4152
|
+
const c = text.charCodeAt(j)
|
|
4153
|
+
if (c >= 128) return -2
|
|
4154
|
+
if (!(${condition(s.rest, "c")})) break
|
|
4155
|
+
j++
|
|
4156
|
+
}
|
|
4157
|
+
return j
|
|
4158
|
+
}`;
|
|
4159
|
+
}
|
|
4160
|
+
function quotedHelper(id, s) {
|
|
4161
|
+
return `function scan${id} (text, offset) {
|
|
4162
|
+
if (text.charCodeAt(offset) !== ${s.quote}) return -1
|
|
4163
|
+
let fallback = -1
|
|
4164
|
+
let j = offset + 1
|
|
4165
|
+
for (;;) {
|
|
4166
|
+
j = text.indexOf(${q(String.fromCharCode(s.quote))}, j)
|
|
4167
|
+
if (j === -1) return fallback
|
|
4168
|
+
if (text.charCodeAt(j + 1) !== ${s.quote}) return j + 1
|
|
4169
|
+
fallback = j + 1
|
|
4170
|
+
j += 2
|
|
4171
|
+
}
|
|
4172
|
+
}`;
|
|
4173
|
+
}
|
|
4174
|
+
function numberHelper(id, s) {
|
|
4175
|
+
return `function scan${id} (text, offset, length) {
|
|
4176
|
+
let j = offset
|
|
4177
|
+
${s.sign ? "if (text.charCodeAt(j) === 45) j++" : ""}
|
|
4178
|
+
const c = text.charCodeAt(j)
|
|
4179
|
+
${s.jsonInt ? "if (c === 48) j++\n else if (c >= 49 && c <= 57) j = skipDigits(text, j + 1, length)\n else return -1" : "if (!(c >= 48 && c <= 57)) return -1\n j = skipDigits(text, j + 1, length)"}
|
|
4180
|
+
${s.frac ? "if (text.charCodeAt(j) === 46) { const k = skipDigits(text, j + 1, length); if (k > j + 1) j = k }" : ""}
|
|
4181
|
+
${s.exp ? "const e = text.charCodeAt(j)\n if (e === 101 || e === 69) { let k = j + 1; const sg = text.charCodeAt(k); if (sg === 43 || sg === 45) k++; const end = skipDigits(text, k, length); if (end > k) j = end }" : ""}
|
|
4182
|
+
return j
|
|
4183
|
+
}`;
|
|
4184
|
+
}
|
|
4185
|
+
function stringHelper(id, s) {
|
|
4186
|
+
return `function scan${id} (text, offset, length) {
|
|
4187
|
+
if (text.charCodeAt(offset) !== ${s.quote}) return -1
|
|
4188
|
+
let j = offset + 1
|
|
4189
|
+
for (;;) {
|
|
4190
|
+
if (j >= length) return -1
|
|
4191
|
+
const c = text.charCodeAt(j)
|
|
4192
|
+
if (c === ${s.quote}) return j + 1
|
|
4193
|
+
if (c === 92) {
|
|
4194
|
+
const d = text.charCodeAt(j + 1)
|
|
4195
|
+
${s.escAny ? "if (!(d >= 0) || d === 10 || d === 13 || d === 0x2028 || d === 0x2029) return -1\n j += 2" : `if (${condition(s.esc, "d")}) j += 2
|
|
4196
|
+
${s.uni ? "else if (d === 117 && isHex(text.charCodeAt(j + 2)) && isHex(text.charCodeAt(j + 3)) && isHex(text.charCodeAt(j + 4)) && isHex(text.charCodeAt(j + 5))) j += 6\n " : ""}else return -1`}
|
|
4197
|
+
continue
|
|
4198
|
+
}
|
|
4199
|
+
${s.ctrl ? "if (c < 32) return -1" : ""}
|
|
4200
|
+
j++
|
|
4201
|
+
}
|
|
4202
|
+
}`;
|
|
4203
|
+
}
|
|
4204
|
+
|
|
2730
4205
|
// src/emit.js
|
|
2731
4206
|
var import_json_logic_engine2 = require("json-logic-engine");
|
|
2732
4207
|
var UNSUPPORTED = /\b(coerceArray|precoerceNumber|compareCheck|assertAllowedDepth|assertSize|asyncIterators|notTraversed)\b|(?<!engine\.)\bmethods\[|engine\.(?!methods\b)/;
|
|
@@ -2775,11 +4250,21 @@ function emitModule(spec, options = {}) {
|
|
|
2775
4250
|
const { tokens: tokenDefs, rules, start } = spec;
|
|
2776
4251
|
const {
|
|
2777
4252
|
memo = false,
|
|
4253
|
+
autoMemo = true,
|
|
4254
|
+
// Off here, on in `createParser`. Inlining a precedence chain is worth 2-3x on a bare chain but only
|
|
4255
|
+
// 1.02-1.04x on the formula and SQL examples, and it makes the file bigger: the formula module grows
|
|
4256
|
+
// 32% minified (38 to 50 KB) even after the second copy of each operand is kept as a call. A parser
|
|
4257
|
+
// built at run time pays nothing for that; a file someone ships does.
|
|
4258
|
+
inlineRules = false,
|
|
4259
|
+
idioms = true,
|
|
2778
4260
|
strict = true,
|
|
2779
4261
|
ll1 = true,
|
|
2780
4262
|
maxSteps = 0,
|
|
2781
4263
|
positions = "full",
|
|
2782
4264
|
execution = "generated",
|
|
4265
|
+
lexer: lexerMode = "generated",
|
|
4266
|
+
scanners = true,
|
|
4267
|
+
fuse = true,
|
|
2783
4268
|
format = "esm",
|
|
2784
4269
|
validate = true,
|
|
2785
4270
|
moduleName = spec.name || "parser",
|
|
@@ -2788,6 +4273,7 @@ function emitModule(spec, options = {}) {
|
|
|
2788
4273
|
methods: semanticMethods
|
|
2789
4274
|
} = options;
|
|
2790
4275
|
if (format !== "esm" && format !== "cjs") throw new Error(`emitModule: format must be 'esm' or 'cjs', got ${quote(format)}`);
|
|
4276
|
+
if (lexerMode !== "generated" && lexerMode !== "runtime") throw new Error(`emitModule: lexer must be 'generated' or 'runtime', got ${quote(lexerMode)}`);
|
|
2791
4277
|
const methodsBlock = options.methodsBlock !== void 0 ? options.methodsBlock : spec.methodsBlock;
|
|
2792
4278
|
const block = methodsBlock ? emitMethodsBlock(methodsBlock, format) : null;
|
|
2793
4279
|
if (!start || !(start in rules)) throw new Error(`emitModule: start rule '${start}' is not defined in rules`);
|
|
@@ -2795,12 +4281,15 @@ function emitModule(spec, options = {}) {
|
|
|
2795
4281
|
const analysis = analyze(rules, { tokenNames: lexer.tokenNames });
|
|
2796
4282
|
if (validate && analysis.errors.length > 0) throw new GrammarError(analysis.errors);
|
|
2797
4283
|
const firsts = ll1 ? analysis.firsts : null;
|
|
4284
|
+
const tokenIds = new Map(lexer.tokenNames.map((name, id) => [name, id]));
|
|
2798
4285
|
const engine = { build: () => {
|
|
2799
4286
|
throw new Error("not compiled during emit");
|
|
2800
4287
|
}, run: () => {
|
|
2801
4288
|
} };
|
|
2802
|
-
const
|
|
2803
|
-
const
|
|
4289
|
+
const memoRules = autoMemo && !memo && analysis.errors.length === 0 ? selectMemoRules(rules, analysis.firsts, { ll1 }) : [];
|
|
4290
|
+
const memoSlots = new Map(memoRules.map((name, slot) => [name, slot]));
|
|
4291
|
+
const planner = createPlanner({ engine, rules, firsts, memo, maxSteps, unsafeEval: false, tokenIds, memoSlots, idioms });
|
|
4292
|
+
const codegen = createCodegen({ planner, rules, firsts, memo, maxSteps, tokenIds, memoSlots, inlineRules, idioms });
|
|
2804
4293
|
codegen.compile(start);
|
|
2805
4294
|
const reached = [...codegen.ruleIndex.entries()].sort((a, b) => a[1] - b[1]);
|
|
2806
4295
|
const unsupported = codegen.ORIGINS.map((origin, index) => ({ origin, index })).filter(({ origin }) => origin.kind === "unknownRule");
|
|
@@ -2841,7 +4330,8 @@ R[${index}] = ${codegen.SOURCES[index]}`
|
|
|
2841
4330
|
).join("\n\n");
|
|
2842
4331
|
const interpretedActions = execution === "interpreted";
|
|
2843
4332
|
const cjs = format === "cjs";
|
|
2844
|
-
const
|
|
4333
|
+
const generatedLexer = lexerMode === "generated";
|
|
4334
|
+
const runtimeImports = ["FAIL as F", "expect as E", "createState", "buildParseError", generatedLexer ? "LexError" : "createLexer"];
|
|
2845
4335
|
if (needsEngine) runtimeImports.push("createValueMethods");
|
|
2846
4336
|
const importRuntime = cjs ? `const { ${runtimeImports.map((b) => b.replace(" as ", ": ")).join(", ")} } = require(${quote(runtimeSpecifier)})` : `import { ${runtimeImports.join(", ")} } from ${quote(runtimeSpecifier)}`;
|
|
2847
4337
|
const importEngine = engineFree ? "" : cjs ? `const { LogicEngine } = require(${quote(engineSpecifier)})
|
|
@@ -2860,7 +4350,7 @@ export default { ${PUBLIC.join(", ")} }`;
|
|
|
2860
4350
|
//
|
|
2861
4351
|
// The grammar was analyzed when this file was written: FIRST sets, left-recursion
|
|
2862
4352
|
// checks and rule specialization all happened then. Importing it does no grammar
|
|
2863
|
-
// work and generates no code.
|
|
4353
|
+
// work and generates no code.${generatedLexer ? "\n// The lexer was written out as source too, so there is no lexer engine to load." : ""}
|
|
2864
4354
|
${engineFree ? "// Every semantic action was compiled to source too, so this file has no\n// dependency on json-logic-engine at all." : interpretedActions ? "// Semantic actions are interpreted, so nothing here calls `eval`." : "// Semantic actions that could not be compiled to source are shipped as JSON\n// Logic and built by json-logic-engine when this module loads."}
|
|
2865
4355
|
|
|
2866
4356
|
${importRuntime}
|
|
@@ -2870,7 +4360,7 @@ const tokens = ${literal(tokenDefs)}
|
|
|
2870
4360
|
|
|
2871
4361
|
const start = ${quote(start)}
|
|
2872
4362
|
|
|
2873
|
-
const lexer = createLexer(tokens, { positions: ${quote(positions)} })
|
|
4363
|
+
const lexer = ${generatedLexer ? generateLexer(tokenDefs, { positions, scanners, fuse }) : `createLexer(tokens, { positions: ${quote(positions)}, scanners: ${scanners}, fuse: ${fuse} })`}
|
|
2874
4364
|
const tokenize = lexer.tokenize
|
|
2875
4365
|
|
|
2876
4366
|
${engineFree ? `// Every semantic action compiled to source, so the engine reduced to a table
|
|
@@ -2943,6 +4433,14 @@ const H = [
|
|
|
2943
4433
|
${helpers.map((source, index) => ` /* ${index} */ ${source}`).join(",\n")}
|
|
2944
4434
|
]
|
|
2945
4435
|
|
|
4436
|
+
/**
|
|
4437
|
+
* Name -> id, the same numbering \`lexer\` assigns. A generated \`alt\`'s switch
|
|
4438
|
+
* dispatches on a token's id when it has one; this is only read as a fallback,
|
|
4439
|
+
* for a hand-built token from \`parseTokens\` that bypassed this file's own
|
|
4440
|
+
* lexer and so has no id of its own.
|
|
4441
|
+
*/
|
|
4442
|
+
const I = ${literal(Object.fromEntries(tokenIds))}
|
|
4443
|
+
|
|
2946
4444
|
${ruleSources}
|
|
2947
4445
|
|
|
2948
4446
|
const RULES = ${literal(Object.fromEntries(reached.map(([name, index]) => [name, index])))}
|
|
@@ -2953,7 +4451,7 @@ const RULES = ${literal(Object.fromEntries(reached.map(([name, index]) => [name,
|
|
|
2953
4451
|
* @param {string} [source]
|
|
2954
4452
|
*/
|
|
2955
4453
|
function parseTokens (tokenList, source, track = false) {
|
|
2956
|
-
const state = createState(tokenList, {}, { memo: ${memo}, maxSteps: ${maxSteps}, track })
|
|
4454
|
+
const state = createState(tokenList, {}, { memo: ${memo}, maxSteps: ${maxSteps}, track, memoSlots: ${memoRules.length} })
|
|
2957
4455
|
state.source = source
|
|
2958
4456
|
state.positions = ${quote(positions)}
|
|
2959
4457
|
state.ruleNames[0] = start
|