grammatik 0.1.1 → 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 +1471 -104
- package/dist/cjs/runtime.js +635 -25
- package/dist/esm/index.js +1471 -104
- package/dist/esm/runtime.js +635 -25
- package/dist/types/index.d.ts +36 -0
- package/dist/types/runtime.d.ts +4 -3
- package/package.json +1 -1
- package/src/analyze.js +114 -4
- package/src/codegen.js +147 -11
- package/src/definition.js +5 -0
- package/src/emit.js +29 -7
- package/src/lexer.d.ts +43 -2
- package/src/lexer.js +535 -28
- package/src/lexgen.js +451 -0
- package/src/memoplan.js +220 -0
- package/src/methods.js +1 -1
- package/src/parser.js +31 -3
- package/src/plan.js +88 -26
- 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();
|
|
@@ -431,9 +791,15 @@ function createLexer(defs, options = {}) {
|
|
|
431
791
|
literal: def.literal,
|
|
432
792
|
longerAlt: def.longerAlt || null,
|
|
433
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,
|
|
434
798
|
modes: modesOf(def),
|
|
435
799
|
pushMode: def.pushMode ? idOf(def.pushMode) : -1,
|
|
436
|
-
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
|
|
437
803
|
};
|
|
438
804
|
});
|
|
439
805
|
const byName = new Map(compiled.map((c) => [c.name, c]));
|
|
@@ -450,14 +816,47 @@ function createLexer(defs, options = {}) {
|
|
|
450
816
|
for (const c of compiled) {
|
|
451
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);
|
|
452
818
|
}
|
|
453
|
-
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) => {
|
|
454
852
|
const members = compiled.filter((c) => c.modes.includes(name));
|
|
455
853
|
const alwaysTry = members.filter((c) => c.firstChars === null);
|
|
456
|
-
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)));
|
|
457
855
|
const ascii = new Array(128);
|
|
458
856
|
for (let code = 0; code < 128; code++) ascii[code] = candidatesFor(code);
|
|
459
857
|
return {
|
|
460
858
|
name,
|
|
859
|
+
id,
|
|
461
860
|
members,
|
|
462
861
|
ascii,
|
|
463
862
|
wide: /* @__PURE__ */ new Map(),
|
|
@@ -481,6 +880,30 @@ function createLexer(defs, options = {}) {
|
|
|
481
880
|
const multiMode = modes.length > 1 || compiled.some((c) => c.popMode || c.pushMode !== -1);
|
|
482
881
|
const namesModes = modes.length > 1;
|
|
483
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
|
+
}
|
|
484
907
|
function longerAltEnd(def, text, offset) {
|
|
485
908
|
const alt2 = def.longerAltDef;
|
|
486
909
|
if (alt2.literal !== void 0) return text.startsWith(alt2.literal, offset) ? offset + alt2.literal.length : -1;
|
|
@@ -500,19 +923,30 @@ function createLexer(defs, options = {}) {
|
|
|
500
923
|
outer: while (offset < length) {
|
|
501
924
|
const applicable = mode.dispatch ? candidates(mode, text.charCodeAt(offset)) : mode.members;
|
|
502
925
|
for (let i = 0; i < applicable.length; i++) {
|
|
503
|
-
|
|
926
|
+
let def = applicable[i];
|
|
504
927
|
let end = -1;
|
|
505
928
|
let matched = null;
|
|
506
|
-
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) {
|
|
507
935
|
if (text.startsWith(def.literal, offset)) end = offset + def.literal.length;
|
|
508
|
-
} else if (def.skip) {
|
|
509
|
-
def.regex.lastIndex = offset;
|
|
510
|
-
if (def.regex.test(text) && def.regex.lastIndex > offset) end = def.regex.lastIndex;
|
|
511
936
|
} else {
|
|
512
|
-
def.
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
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
|
+
}
|
|
516
950
|
}
|
|
517
951
|
if (end === -1) continue;
|
|
518
952
|
let winner = def;
|
|
@@ -561,62 +995,238 @@ function createLexer(defs, options = {}) {
|
|
|
561
995
|
}
|
|
562
996
|
const inMode = (mode) => namesModes ? ` (in lexer mode '${mode.name}')` : "";
|
|
563
997
|
const describe = (offset, line, lineStart) => trackLines ? `line ${line} column ${offset - lineStart + 1}` : `offset ${offset}`;
|
|
564
|
-
return { tokenize, tokenNames, positions, modeNames };
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
idx: 0,
|
|
572
|
-
maxIdx: 0,
|
|
573
|
-
expected: [],
|
|
574
|
-
grammar,
|
|
575
|
-
memo: options.memo ? /* @__PURE__ */ new Map() : null,
|
|
576
|
-
// Entering a rule is one array store rather than an object allocation, and
|
|
577
|
-
// the stack is snapshotted only on a furthest failure — by the time an error
|
|
578
|
-
// is built it would otherwise have unwound to just the start rule.
|
|
579
|
-
ruleNames: [],
|
|
580
|
-
ruleDepth: 0,
|
|
581
|
-
failureStack: null,
|
|
582
|
-
track: options.track === true,
|
|
583
|
-
steps: 0,
|
|
584
|
-
maxSteps: options.maxSteps || 0
|
|
585
|
-
});
|
|
586
|
-
function expect(state, expectation) {
|
|
587
|
-
if (state.idx > state.maxIdx) {
|
|
588
|
-
state.maxIdx = state.idx;
|
|
589
|
-
if (!state.track) return;
|
|
590
|
-
state.expected = [];
|
|
591
|
-
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;
|
|
592
1005
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
/**
|
|
598
|
-
* @param {string} message
|
|
599
|
-
* @param {{ token: import('./lexer.js').Token | null, expected: string[], ruleStack: string[] }} details
|
|
600
|
-
*/
|
|
601
|
-
constructor(message, details) {
|
|
602
|
-
super(message);
|
|
603
|
-
this.name = "ParseError";
|
|
604
|
-
this.token = details.token;
|
|
605
|
-
this.expected = details.expected;
|
|
606
|
-
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;
|
|
607
1010
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
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 };
|
|
620
1230
|
}
|
|
621
1231
|
|
|
622
1232
|
// src/analyze.js
|
|
@@ -649,6 +1259,7 @@ var PARSER_OPS = /* @__PURE__ */ new Set([
|
|
|
649
1259
|
"many",
|
|
650
1260
|
"many1",
|
|
651
1261
|
"option",
|
|
1262
|
+
"flag",
|
|
652
1263
|
"manySep",
|
|
653
1264
|
"many1Sep",
|
|
654
1265
|
"infixLeft",
|
|
@@ -656,6 +1267,8 @@ var PARSER_OPS = /* @__PURE__ */ new Set([
|
|
|
656
1267
|
"postfix",
|
|
657
1268
|
"prefix",
|
|
658
1269
|
"between",
|
|
1270
|
+
"pick",
|
|
1271
|
+
"yields",
|
|
659
1272
|
"as",
|
|
660
1273
|
"oneOf",
|
|
661
1274
|
"eof",
|
|
@@ -709,7 +1322,8 @@ function firstOf(node, ruleFirsts) {
|
|
|
709
1322
|
return result;
|
|
710
1323
|
}
|
|
711
1324
|
case "many":
|
|
712
|
-
case "option":
|
|
1325
|
+
case "option":
|
|
1326
|
+
case "flag": {
|
|
713
1327
|
const inner = firstOf(arg, ruleFirsts);
|
|
714
1328
|
return { tokens: inner.tokens, nullable: true, unknown: inner.unknown };
|
|
715
1329
|
}
|
|
@@ -732,6 +1346,9 @@ function firstOf(node, ruleFirsts) {
|
|
|
732
1346
|
return firstOf(Array.isArray(arg) ? arg[0] : arg && arg.parser, ruleFirsts);
|
|
733
1347
|
case "between":
|
|
734
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);
|
|
735
1352
|
case "prefix": {
|
|
736
1353
|
const ops = firstOf(arg && arg.operator, ruleFirsts);
|
|
737
1354
|
const inner = firstOf(arg && arg.operand, ruleFirsts);
|
|
@@ -795,7 +1412,36 @@ function recognizeSeqIdiom(children) {
|
|
|
795
1412
|
if (rewritten !== BAIL) return { as: [parser, rewritten] };
|
|
796
1413
|
}
|
|
797
1414
|
}
|
|
798
|
-
|
|
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
|
+
}
|
|
1429
|
+
return null;
|
|
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;
|
|
799
1445
|
}
|
|
800
1446
|
function isPlainParser(node) {
|
|
801
1447
|
const op = opOf(node);
|
|
@@ -842,6 +1488,18 @@ function recognizeAltPrefixIdiom(branches) {
|
|
|
842
1488
|
]
|
|
843
1489
|
};
|
|
844
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
|
+
}
|
|
845
1503
|
var BAIL = /* @__PURE__ */ Symbol("grammatik.bail");
|
|
846
1504
|
function rebaseOnLabel(logic, name) {
|
|
847
1505
|
if (Array.isArray(logic)) {
|
|
@@ -908,6 +1566,7 @@ function leftmostCalls(node, ruleFirsts, out) {
|
|
|
908
1566
|
case "many":
|
|
909
1567
|
case "many1":
|
|
910
1568
|
case "option":
|
|
1569
|
+
case "flag":
|
|
911
1570
|
case "lookahead":
|
|
912
1571
|
case "negLookahead":
|
|
913
1572
|
leftmostCalls(arg, ruleFirsts, out);
|
|
@@ -918,6 +1577,10 @@ function leftmostCalls(node, ruleFirsts, out) {
|
|
|
918
1577
|
case "between":
|
|
919
1578
|
if (arg) leftmostCalls({ seq: [arg.open, arg.body, arg.close] }, ruleFirsts, out);
|
|
920
1579
|
break;
|
|
1580
|
+
case "pick":
|
|
1581
|
+
case "yields":
|
|
1582
|
+
if (arg && Array.isArray(arg.parsers)) leftmostCalls({ seq: arg.parsers }, ruleFirsts, out);
|
|
1583
|
+
break;
|
|
921
1584
|
case "prefix":
|
|
922
1585
|
if (arg) {
|
|
923
1586
|
leftmostCalls(arg.operator, ruleFirsts, out);
|
|
@@ -984,6 +1647,7 @@ function validateNode(node, grammar, tokenNames, errors, path) {
|
|
|
984
1647
|
case "many":
|
|
985
1648
|
case "many1":
|
|
986
1649
|
case "option":
|
|
1650
|
+
case "flag":
|
|
987
1651
|
case "text":
|
|
988
1652
|
case "lookahead":
|
|
989
1653
|
case "negLookahead":
|
|
@@ -1005,6 +1669,26 @@ function validateNode(node, grammar, tokenNames, errors, path) {
|
|
|
1005
1669
|
}
|
|
1006
1670
|
break;
|
|
1007
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
|
+
}
|
|
1008
1692
|
case "prefix": {
|
|
1009
1693
|
if (!arg || typeof arg !== "object" || !("operator" in arg) || !("operand" in arg) || !("combine" in arg)) {
|
|
1010
1694
|
errors.push(`${path}: 'prefix' expects { operator, operand, combine }`);
|
|
@@ -1131,20 +1815,6 @@ function declareSync(fn) {
|
|
|
1131
1815
|
fn[SYNC] = true;
|
|
1132
1816
|
return fn;
|
|
1133
1817
|
}
|
|
1134
|
-
function usesOuterScope(logic) {
|
|
1135
|
-
if (Array.isArray(logic)) return logic.some(usesOuterScope);
|
|
1136
|
-
if (!logic || typeof logic !== "object") return false;
|
|
1137
|
-
for (const key of Object.keys(logic)) {
|
|
1138
|
-
const arg = logic[key];
|
|
1139
|
-
if (key === "val" && Array.isArray(arg) && Array.isArray(arg[0])) return true;
|
|
1140
|
-
if (key === "var") {
|
|
1141
|
-
if (typeof arg === "string" && arg.includes("../")) return true;
|
|
1142
|
-
if (Array.isArray(arg) && typeof arg[0] === "string" && arg[0].includes("../")) return true;
|
|
1143
|
-
}
|
|
1144
|
-
if (usesOuterScope(arg)) return true;
|
|
1145
|
-
}
|
|
1146
|
-
return false;
|
|
1147
|
-
}
|
|
1148
1818
|
function mayReturnContext(logic) {
|
|
1149
1819
|
if (Array.isArray(logic)) return logic.some(mayReturnContext);
|
|
1150
1820
|
if (!logic || typeof logic !== "object") return false;
|
|
@@ -1157,7 +1827,7 @@ function mayReturnContext(logic) {
|
|
|
1157
1827
|
return false;
|
|
1158
1828
|
}
|
|
1159
1829
|
function createPlanner(config) {
|
|
1160
|
-
const { engine, rules, firsts, memo, maxSteps, unsafeEval = true, tokenIds = null } = config;
|
|
1830
|
+
const { engine, rules, firsts, memo, maxSteps, unsafeEval = true, tokenIds = null, memoSlots = /* @__PURE__ */ new Map(), idioms = true } = config;
|
|
1161
1831
|
const slots = /* @__PURE__ */ new Map();
|
|
1162
1832
|
const planCache = /* @__PURE__ */ new WeakMap();
|
|
1163
1833
|
const opCaches = /* @__PURE__ */ new Map();
|
|
@@ -1304,6 +1974,17 @@ function createPlanner(config) {
|
|
|
1304
1974
|
return value;
|
|
1305
1975
|
};
|
|
1306
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
|
+
};
|
|
1307
1988
|
const buildInfixLeft = (arg) => {
|
|
1308
1989
|
const operand = plan(arg.operand);
|
|
1309
1990
|
const operator = arg.operator === void 0 ? null : plan(arg.operator);
|
|
@@ -1429,6 +2110,26 @@ function createPlanner(config) {
|
|
|
1429
2110
|
return value;
|
|
1430
2111
|
};
|
|
1431
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);
|
|
1432
2133
|
const buildPrefix = (arg) => {
|
|
1433
2134
|
const operator = plan(arg.operator);
|
|
1434
2135
|
const operand = plan(arg.operand);
|
|
@@ -1512,6 +2213,31 @@ function createPlanner(config) {
|
|
|
1512
2213
|
return value;
|
|
1513
2214
|
};
|
|
1514
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
|
+
}
|
|
1515
2241
|
return (state) => {
|
|
1516
2242
|
tick(state);
|
|
1517
2243
|
if (!state.track) return slot.fn(state);
|
|
@@ -1590,6 +2316,9 @@ function createPlanner(config) {
|
|
|
1590
2316
|
infixRight: buildInfixRight,
|
|
1591
2317
|
as: buildAs,
|
|
1592
2318
|
between: buildBetween,
|
|
2319
|
+
flag: buildFlag,
|
|
2320
|
+
pick: buildPick,
|
|
2321
|
+
yields: buildYields,
|
|
1593
2322
|
prefix: buildPrefix,
|
|
1594
2323
|
postfix: buildPostfix,
|
|
1595
2324
|
manySep: (arg) => planSeparated(arg, false),
|
|
@@ -1609,7 +2338,7 @@ function createPlanner(config) {
|
|
|
1609
2338
|
}
|
|
1610
2339
|
function planSeq(children) {
|
|
1611
2340
|
if (!Array.isArray(children)) throw new Error("'seq' expects an array of parsers");
|
|
1612
|
-
const idiom = recognizeSeqIdiom(children);
|
|
2341
|
+
const idiom = idioms ? recognizeSeqIdiom(children) : null;
|
|
1613
2342
|
if (idiom !== null) return planUncached(idiom);
|
|
1614
2343
|
const PARSER = 0;
|
|
1615
2344
|
const LABELLED = 1;
|
|
@@ -1723,7 +2452,9 @@ function createPlanner(config) {
|
|
|
1723
2452
|
}
|
|
1724
2453
|
function planAlt(branches) {
|
|
1725
2454
|
if (!Array.isArray(branches)) throw new Error("'alt' expects an array of branches");
|
|
1726
|
-
const
|
|
2455
|
+
const oneOf = idioms ? recognizeAltConsumeIdiom(branches) : null;
|
|
2456
|
+
if (oneOf !== null) return planUncached(oneOf);
|
|
2457
|
+
const factored = idioms ? recognizeAltPrefixIdiom(branches) : null;
|
|
1727
2458
|
if (factored !== null) return planUncached(factored);
|
|
1728
2459
|
const planned = branches.map(plan);
|
|
1729
2460
|
const table = firsts ? buildDispatch(branches, firsts) : null;
|
|
@@ -1845,6 +2576,9 @@ var OPERATORS = [
|
|
|
1845
2576
|
"prefix",
|
|
1846
2577
|
"postfix",
|
|
1847
2578
|
"between",
|
|
2579
|
+
"pick",
|
|
2580
|
+
"yields",
|
|
2581
|
+
"flag",
|
|
1848
2582
|
"as",
|
|
1849
2583
|
"subrule",
|
|
1850
2584
|
"label",
|
|
@@ -1899,7 +2633,7 @@ var createValueMethods = () => ({
|
|
|
1899
2633
|
|
|
1900
2634
|
// src/codegen.js
|
|
1901
2635
|
function createCodegen(config) {
|
|
1902
|
-
const { planner, rules, firsts, memo, maxSteps, tokenIds = null } = config;
|
|
2636
|
+
const { planner, rules, firsts, memo, maxSteps, tokenIds = null, memoSlots = /* @__PURE__ */ new Map(), inlineRules = true, idioms = true } = config;
|
|
1903
2637
|
const typeCheck = (tokenVar, name) => {
|
|
1904
2638
|
const id = tokenIds ? tokenIds.get(name) : void 0;
|
|
1905
2639
|
if (id === void 0) return `${tokenVar}.type === ${JSON.stringify(name)}`;
|
|
@@ -1920,6 +2654,33 @@ function createCodegen(config) {
|
|
|
1920
2654
|
}
|
|
1921
2655
|
const actionHelper = (logic) => helper(planner.planAction(logic), { kind: "action", logic });
|
|
1922
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]));
|
|
1923
2684
|
function indexOfRule(name) {
|
|
1924
2685
|
let index = ruleIndex.get(name);
|
|
1925
2686
|
if (index !== void 0) return index;
|
|
@@ -1980,6 +2741,16 @@ function createCodegen(config) {
|
|
|
1980
2741
|
);
|
|
1981
2742
|
return result;
|
|
1982
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
|
+
}
|
|
1983
2754
|
case "many":
|
|
1984
2755
|
case "many1": {
|
|
1985
2756
|
const atLeastOne = op === "many1";
|
|
@@ -1997,7 +2768,7 @@ function createCodegen(config) {
|
|
|
1997
2768
|
);
|
|
1998
2769
|
}
|
|
1999
2770
|
out.push("for (;;) {", `const ${before} = c.idx;`);
|
|
2000
|
-
const value = gen(arg, out, bindingsVar);
|
|
2771
|
+
const value = atLeastOne ? cold(() => gen(arg, out, bindingsVar)) : gen(arg, out, bindingsVar);
|
|
2001
2772
|
out.push(
|
|
2002
2773
|
`if (${value} === F) { c.idx = ${before}; break; }`,
|
|
2003
2774
|
`if (c.idx === ${before}) P(${JSON.stringify(op)}, ${before});`,
|
|
@@ -2027,7 +2798,7 @@ function createCodegen(config) {
|
|
|
2027
2798
|
operatorVar = gen(arg.operator, out, bindingsVar);
|
|
2028
2799
|
out.push(`if (${operatorVar} === F) { c.idx = ${mark}; break; }`);
|
|
2029
2800
|
}
|
|
2030
|
-
const right = gen(arg.operand, out, bindingsVar);
|
|
2801
|
+
const right = cold(() => gen(arg.operand, out, bindingsVar));
|
|
2031
2802
|
out.push(
|
|
2032
2803
|
`if (${right} === F) { c.idx = ${mark}; break; }`,
|
|
2033
2804
|
`if (${bindings} === null) ${bindings} = { left: null, op: null, right: null };`,
|
|
@@ -2042,12 +2813,27 @@ function createCodegen(config) {
|
|
|
2042
2813
|
case "oneOf": {
|
|
2043
2814
|
const names = Array.isArray(arg) ? arg : [arg];
|
|
2044
2815
|
const token = nextId();
|
|
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
|
+
}
|
|
2045
2831
|
const test = names.map((n) => typeCheck(token, n)).join(" || ");
|
|
2046
2832
|
out.push(
|
|
2047
2833
|
`let ${result};`,
|
|
2048
2834
|
`const ${token} = c.tokens[c.idx];`,
|
|
2049
2835
|
`if (${token} !== undefined && (${test})) { c.idx++; ${result} = ${token}; }`,
|
|
2050
|
-
|
|
2836
|
+
failed
|
|
2051
2837
|
);
|
|
2052
2838
|
return result;
|
|
2053
2839
|
}
|
|
@@ -2080,7 +2866,7 @@ function createCodegen(config) {
|
|
|
2080
2866
|
operatorVar = gen(arg.operator, out, bindingsVar);
|
|
2081
2867
|
out.push(`if (${operatorVar} === F) { c.idx = ${mark}; break; }`);
|
|
2082
2868
|
}
|
|
2083
|
-
const right = gen(arg.operand, out, bindingsVar);
|
|
2869
|
+
const right = cold(() => gen(arg.operand, out, bindingsVar));
|
|
2084
2870
|
out.push(
|
|
2085
2871
|
`if (${right} === F) { c.idx = ${mark}; break; }`,
|
|
2086
2872
|
`if (${items} === null) ${items} = [];`,
|
|
@@ -2155,6 +2941,19 @@ function createCodegen(config) {
|
|
|
2155
2941
|
);
|
|
2156
2942
|
return result;
|
|
2157
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
|
+
}
|
|
2158
2957
|
case "prefix": {
|
|
2159
2958
|
const label2 = "L" + nextId();
|
|
2160
2959
|
const start = nextId();
|
|
@@ -2231,7 +3030,7 @@ function createCodegen(config) {
|
|
|
2231
3030
|
);
|
|
2232
3031
|
const sep = gen(sepNode, out, bindingsVar);
|
|
2233
3032
|
out.push(`if (${sep} === F) { c.idx = ${beforeSep}; break; }`, `const ${beforeItem} = c.idx;`);
|
|
2234
|
-
const item = gen(arg.rule, out, bindingsVar);
|
|
3033
|
+
const item = cold(() => gen(arg.rule, out, bindingsVar));
|
|
2235
3034
|
out.push(
|
|
2236
3035
|
`if (${item} === F) { c.idx = ${trailing ? beforeItem : beforeSep}; break; }`,
|
|
2237
3036
|
`${result}.push(${item});`,
|
|
@@ -2246,8 +3045,20 @@ function createCodegen(config) {
|
|
|
2246
3045
|
out.push(`const ${result} = ${helper(planner.plan(node), { kind: "unknownRule", name })}(c);`);
|
|
2247
3046
|
return result;
|
|
2248
3047
|
}
|
|
2249
|
-
const index = indexOfRule(name);
|
|
2250
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);
|
|
2251
3062
|
out.push(tickSrc);
|
|
2252
3063
|
if (memo) {
|
|
2253
3064
|
const key = nextId();
|
|
@@ -2268,6 +3079,26 @@ function createCodegen(config) {
|
|
|
2268
3079
|
`c.memo.set(${key}, { value: ${result}, end: c.idx });`,
|
|
2269
3080
|
"}"
|
|
2270
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
|
+
);
|
|
2271
3102
|
} else {
|
|
2272
3103
|
out.push(
|
|
2273
3104
|
`const ${parent} = c.ruleDepth;`,
|
|
@@ -2285,7 +3116,7 @@ function createCodegen(config) {
|
|
|
2285
3116
|
}
|
|
2286
3117
|
function genSeq(children, out, result) {
|
|
2287
3118
|
if (!Array.isArray(children)) throw new Error("'seq' expects an array of parsers");
|
|
2288
|
-
const idiom = recognizeSeqIdiom(children);
|
|
3119
|
+
const idiom = idioms ? recognizeSeqIdiom(children) : null;
|
|
2289
3120
|
if (idiom !== null) return gen(idiom, out, null);
|
|
2290
3121
|
let hasLabels = false;
|
|
2291
3122
|
let hasAction = false;
|
|
@@ -2336,7 +3167,9 @@ function createCodegen(config) {
|
|
|
2336
3167
|
}
|
|
2337
3168
|
function genAlt(branches, out, result, bindingsVar) {
|
|
2338
3169
|
if (!Array.isArray(branches)) throw new Error("'alt' expects an array of branches");
|
|
2339
|
-
const
|
|
3170
|
+
const oneOf = idioms ? recognizeAltConsumeIdiom(branches) : null;
|
|
3171
|
+
if (oneOf !== null) return gen(oneOf, out, bindingsVar);
|
|
3172
|
+
const factored = idioms ? recognizeAltPrefixIdiom(branches) : null;
|
|
2340
3173
|
if (factored !== null) return gen(factored, out, bindingsVar);
|
|
2341
3174
|
const table = firsts ? buildDispatch(branches, firsts) : null;
|
|
2342
3175
|
const label2 = "L" + nextId();
|
|
@@ -2430,6 +3263,165 @@ return ${value};
|
|
|
2430
3263
|
return { compile, functionFor, sourceFor, ruleIndex, RULES, SOURCES, ORIGINS, guard };
|
|
2431
3264
|
}
|
|
2432
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
|
+
|
|
2433
3425
|
// src/parser.js
|
|
2434
3426
|
var EXECUTION_MODES = /* @__PURE__ */ new Set(["generated", "interpreted"]);
|
|
2435
3427
|
var GrammarError = class extends Error {
|
|
@@ -2445,6 +3437,9 @@ function createParser(spec, options = {}) {
|
|
|
2445
3437
|
const { tokens: tokenDefs, rules, start } = spec;
|
|
2446
3438
|
const {
|
|
2447
3439
|
memo = false,
|
|
3440
|
+
autoMemo = true,
|
|
3441
|
+
inlineRules = true,
|
|
3442
|
+
idioms = true,
|
|
2448
3443
|
strict = true,
|
|
2449
3444
|
validate = true,
|
|
2450
3445
|
maxSteps = 0,
|
|
@@ -2472,9 +3467,11 @@ function createParser(spec, options = {}) {
|
|
|
2472
3467
|
const startRule = rules[start];
|
|
2473
3468
|
const firsts = ll1 ? analysis.firsts : null;
|
|
2474
3469
|
const tokenIds = new Map(lexer.tokenNames.map((name, id) => [name, id]));
|
|
2475
|
-
const
|
|
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 });
|
|
2476
3473
|
engine.__jlGrammar = { planner };
|
|
2477
|
-
const codegen = compile && unsafeEval ? createCodegen({ planner, rules, firsts, memo, maxSteps, tokenIds }) : null;
|
|
3474
|
+
const codegen = compile && unsafeEval ? createCodegen({ planner, rules, firsts, memo, maxSteps, tokenIds, memoSlots, inlineRules, idioms }) : null;
|
|
2478
3475
|
const compiledStart = codegen && codegen.compile(start);
|
|
2479
3476
|
if (codegen && !engine.disableInterpretedOptimization) {
|
|
2480
3477
|
for (const name of Object.keys(rules)) {
|
|
@@ -2483,7 +3480,7 @@ function createParser(spec, options = {}) {
|
|
|
2483
3480
|
}
|
|
2484
3481
|
}
|
|
2485
3482
|
function parseTokens(tokens, source, track = false) {
|
|
2486
|
-
const state = createState(tokens, rules, { memo, maxSteps, track });
|
|
3483
|
+
const state = createState(tokens, rules, { memo, maxSteps, track, memoSlots: memoRules.length });
|
|
2487
3484
|
state.firsts = firsts;
|
|
2488
3485
|
state.source = source;
|
|
2489
3486
|
state.positions = lexer.positions;
|
|
@@ -2513,6 +3510,8 @@ function createParser(spec, options = {}) {
|
|
|
2513
3510
|
planner,
|
|
2514
3511
|
codegen,
|
|
2515
3512
|
execution: mode,
|
|
3513
|
+
/** Rules memoized by `autoMemo`, for introspection. */
|
|
3514
|
+
memoRules,
|
|
2516
3515
|
compiled: Boolean(compiledStart),
|
|
2517
3516
|
/** Rules whose `alt`s resolved to O(1) LL(1) dispatch, for introspection. */
|
|
2518
3517
|
get firsts() {
|
|
@@ -2818,6 +3817,7 @@ var CONSTRUCTORS = {
|
|
|
2818
3817
|
many: { arity: [1, 1], of: ([a]) => ({ many: a }) },
|
|
2819
3818
|
many1: { arity: [1, 1], of: ([a]) => ({ many1: a }) },
|
|
2820
3819
|
option: { arity: [1, 1], of: ([a]) => ({ option: a }) },
|
|
3820
|
+
flag: { arity: [1, 1], of: ([a]) => ({ flag: a }) },
|
|
2821
3821
|
action: { arity: [1, 1], of: ([a]) => ({ action: a }) },
|
|
2822
3822
|
lookahead: { arity: [1, 1], of: ([a]) => ({ lookahead: a }) },
|
|
2823
3823
|
negLookahead: { arity: [1, 1], of: ([a]) => ({ negLookahead: a }) },
|
|
@@ -2829,6 +3829,10 @@ var CONSTRUCTORS = {
|
|
|
2829
3829
|
manySep: { arity: [2, 2], of: ([a, b]) => ({ manySep: { rule: a, sep: b } }) },
|
|
2830
3830
|
many1Sep: { arity: [2, 2], of: ([a, b]) => ({ many1Sep: { rule: a, sep: b } }) },
|
|
2831
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] } }) },
|
|
2832
3836
|
infixLeft: { arity: [3, 3], of: ([a, b, c]) => ({ infixLeft: { operand: a, operator: b, combine: logicOf(c) } }) },
|
|
2833
3837
|
infixRight: { arity: [3, 3], of: ([a, b, c]) => ({ infixRight: { operand: a, operator: b, combine: logicOf(c) } }) },
|
|
2834
3838
|
prefix: { arity: [3, 3], of: ([a, b, c]) => ({ prefix: { operator: a, operand: b, combine: logicOf(c) } }) },
|
|
@@ -2849,6 +3853,355 @@ function createParserFromSource(source, options = {}) {
|
|
|
2849
3853
|
return createParser(grammar, { ...options, methods });
|
|
2850
3854
|
}
|
|
2851
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
|
+
|
|
2852
4205
|
// src/emit.js
|
|
2853
4206
|
var import_json_logic_engine2 = require("json-logic-engine");
|
|
2854
4207
|
var UNSUPPORTED = /\b(coerceArray|precoerceNumber|compareCheck|assertAllowedDepth|assertSize|asyncIterators|notTraversed)\b|(?<!engine\.)\bmethods\[|engine\.(?!methods\b)/;
|
|
@@ -2897,11 +4250,21 @@ function emitModule(spec, options = {}) {
|
|
|
2897
4250
|
const { tokens: tokenDefs, rules, start } = spec;
|
|
2898
4251
|
const {
|
|
2899
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,
|
|
2900
4260
|
strict = true,
|
|
2901
4261
|
ll1 = true,
|
|
2902
4262
|
maxSteps = 0,
|
|
2903
4263
|
positions = "full",
|
|
2904
4264
|
execution = "generated",
|
|
4265
|
+
lexer: lexerMode = "generated",
|
|
4266
|
+
scanners = true,
|
|
4267
|
+
fuse = true,
|
|
2905
4268
|
format = "esm",
|
|
2906
4269
|
validate = true,
|
|
2907
4270
|
moduleName = spec.name || "parser",
|
|
@@ -2910,6 +4273,7 @@ function emitModule(spec, options = {}) {
|
|
|
2910
4273
|
methods: semanticMethods
|
|
2911
4274
|
} = options;
|
|
2912
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)}`);
|
|
2913
4277
|
const methodsBlock = options.methodsBlock !== void 0 ? options.methodsBlock : spec.methodsBlock;
|
|
2914
4278
|
const block = methodsBlock ? emitMethodsBlock(methodsBlock, format) : null;
|
|
2915
4279
|
if (!start || !(start in rules)) throw new Error(`emitModule: start rule '${start}' is not defined in rules`);
|
|
@@ -2922,8 +4286,10 @@ function emitModule(spec, options = {}) {
|
|
|
2922
4286
|
throw new Error("not compiled during emit");
|
|
2923
4287
|
}, run: () => {
|
|
2924
4288
|
} };
|
|
2925
|
-
const
|
|
2926
|
-
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 });
|
|
2927
4293
|
codegen.compile(start);
|
|
2928
4294
|
const reached = [...codegen.ruleIndex.entries()].sort((a, b) => a[1] - b[1]);
|
|
2929
4295
|
const unsupported = codegen.ORIGINS.map((origin, index) => ({ origin, index })).filter(({ origin }) => origin.kind === "unknownRule");
|
|
@@ -2964,7 +4330,8 @@ R[${index}] = ${codegen.SOURCES[index]}`
|
|
|
2964
4330
|
).join("\n\n");
|
|
2965
4331
|
const interpretedActions = execution === "interpreted";
|
|
2966
4332
|
const cjs = format === "cjs";
|
|
2967
|
-
const
|
|
4333
|
+
const generatedLexer = lexerMode === "generated";
|
|
4334
|
+
const runtimeImports = ["FAIL as F", "expect as E", "createState", "buildParseError", generatedLexer ? "LexError" : "createLexer"];
|
|
2968
4335
|
if (needsEngine) runtimeImports.push("createValueMethods");
|
|
2969
4336
|
const importRuntime = cjs ? `const { ${runtimeImports.map((b) => b.replace(" as ", ": ")).join(", ")} } = require(${quote(runtimeSpecifier)})` : `import { ${runtimeImports.join(", ")} } from ${quote(runtimeSpecifier)}`;
|
|
2970
4337
|
const importEngine = engineFree ? "" : cjs ? `const { LogicEngine } = require(${quote(engineSpecifier)})
|
|
@@ -2983,7 +4350,7 @@ export default { ${PUBLIC.join(", ")} }`;
|
|
|
2983
4350
|
//
|
|
2984
4351
|
// The grammar was analyzed when this file was written: FIRST sets, left-recursion
|
|
2985
4352
|
// checks and rule specialization all happened then. Importing it does no grammar
|
|
2986
|
-
// 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." : ""}
|
|
2987
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."}
|
|
2988
4355
|
|
|
2989
4356
|
${importRuntime}
|
|
@@ -2993,7 +4360,7 @@ const tokens = ${literal(tokenDefs)}
|
|
|
2993
4360
|
|
|
2994
4361
|
const start = ${quote(start)}
|
|
2995
4362
|
|
|
2996
|
-
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} })`}
|
|
2997
4364
|
const tokenize = lexer.tokenize
|
|
2998
4365
|
|
|
2999
4366
|
${engineFree ? `// Every semantic action compiled to source, so the engine reduced to a table
|
|
@@ -3084,7 +4451,7 @@ const RULES = ${literal(Object.fromEntries(reached.map(([name, index]) => [name,
|
|
|
3084
4451
|
* @param {string} [source]
|
|
3085
4452
|
*/
|
|
3086
4453
|
function parseTokens (tokenList, source, track = false) {
|
|
3087
|
-
const state = createState(tokenList, {}, { memo: ${memo}, maxSteps: ${maxSteps}, track })
|
|
4454
|
+
const state = createState(tokenList, {}, { memo: ${memo}, maxSteps: ${maxSteps}, track, memoSlots: ${memoRules.length} })
|
|
3088
4455
|
state.source = source
|
|
3089
4456
|
state.positions = ${quote(positions)}
|
|
3090
4457
|
state.ruleNames[0] = start
|